1use super::TypeExpr;
9use super::argument::{
10 EnumOptions as ParsedEnumOptions, EnumOptionsError as ParsedEnumOptionsError,
11};
12use super::extensions::TypeContext;
13use super::type_ast::TypeExprParam;
14use crate::parse::Parse;
15use crate::parse::text::simple_extensions::type_ast::TypeParseError;
16use crate::text::simple_extensions::{
17 EnumOptions as RawEnumOptions, SimpleExtensionsTypesItem, Type as RawType, TypeParamDefs,
18 TypeParamDefsItem, TypeParamDefsItemType,
19};
20use indexmap::IndexMap;
21use serde_json::{Map, Value};
22use std::convert::TryFrom;
23use std::fmt;
24use std::ops::RangeInclusive;
25use thiserror::Error;
26
27fn write_separated<I, T>(
32 f: &mut fmt::Formatter<'_>,
33 iter: I,
34 start: &str,
35 end: &str,
36 sep: &str,
37) -> fmt::Result
38where
39 I: IntoIterator<Item = T>,
40 T: fmt::Display,
41{
42 let mut it = iter.into_iter();
43 if let Some(first) = it.next() {
44 f.write_str(start)?;
45 write!(f, "{first}")?;
46 for item in it {
47 f.write_str(sep)?;
48 write!(f, "{item}")?;
49 }
50 f.write_str(end)
51 } else {
52 Ok(())
53 }
54}
55
56struct KeyValueDisplay<K, V>(K, V, &'static str);
58
59impl<K, V> fmt::Display for KeyValueDisplay<K, V>
60where
61 K: fmt::Display,
62 V: fmt::Display,
63{
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "{}{}{}", self.0, self.2, self.1)
66 }
67}
68
69#[derive(Clone, Debug, PartialEq)]
72pub enum BasicBuiltinType {
73 Boolean,
75 I8,
77 I16,
79 I32,
81 I64,
83 Fp32,
85 Fp64,
87 String,
89 Binary,
91 Timestamp,
93 TimestampTz,
95 Date,
97 Time,
99 IntervalYear,
101 Uuid,
103 FixedChar {
105 length: i32,
107 },
108 VarChar {
110 length: i32,
112 },
113 FixedBinary {
115 length: i32,
117 },
118 Decimal {
120 precision: i32,
122 scale: i32,
124 },
125 PrecisionTime {
127 precision: i32,
129 },
130 PrecisionTimestamp {
132 precision: i32,
134 },
135 PrecisionTimestampTz {
137 precision: i32,
139 },
140 IntervalDay {
142 precision: i32,
144 },
145 IntervalCompound {
147 precision: i32,
149 },
150}
151
152impl BasicBuiltinType {
153 pub fn is_name(name: &str) -> bool {
155 let lower = name.to_ascii_lowercase();
156 primitive_builtin(&lower).is_some()
157 || matches!(
158 lower.as_str(),
159 "fixedchar"
160 | "varchar"
161 | "fixedbinary"
162 | "decimal"
163 | "precisiontime"
164 | "precision_time"
165 | "precision_timestamp"
166 | "precision_timestamp_tz"
167 | "interval_day"
168 | "interval_compound"
169 )
170 }
171}
172
173impl fmt::Display for BasicBuiltinType {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 match self {
176 BasicBuiltinType::Boolean => f.write_str("bool"),
177 BasicBuiltinType::I8 => f.write_str("i8"),
178 BasicBuiltinType::I16 => f.write_str("i16"),
179 BasicBuiltinType::I32 => f.write_str("i32"),
180 BasicBuiltinType::I64 => f.write_str("i64"),
181 BasicBuiltinType::Fp32 => f.write_str("fp32"),
182 BasicBuiltinType::Fp64 => f.write_str("fp64"),
183 BasicBuiltinType::String => f.write_str("string"),
184 BasicBuiltinType::Binary => f.write_str("binary"),
185 BasicBuiltinType::Timestamp => f.write_str("timestamp"),
186 BasicBuiltinType::TimestampTz => f.write_str("timestamp_tz"),
187 BasicBuiltinType::Date => f.write_str("date"),
188 BasicBuiltinType::Time => f.write_str("time"),
189 BasicBuiltinType::IntervalYear => f.write_str("interval_year"),
190 BasicBuiltinType::Uuid => f.write_str("uuid"),
191 BasicBuiltinType::FixedChar { length } => write!(f, "FIXEDCHAR<{length}>"),
192 BasicBuiltinType::VarChar { length } => write!(f, "VARCHAR<{length}>"),
193 BasicBuiltinType::FixedBinary { length } => write!(f, "FIXEDBINARY<{length}>"),
194 BasicBuiltinType::Decimal { precision, scale } => {
195 write!(f, "DECIMAL<{precision}, {scale}>")
196 }
197 BasicBuiltinType::PrecisionTime { precision } => {
198 write!(f, "PRECISIONTIME<{precision}>")
199 }
200 BasicBuiltinType::PrecisionTimestamp { precision } => {
201 write!(f, "PRECISIONTIMESTAMP<{precision}>")
202 }
203 BasicBuiltinType::PrecisionTimestampTz { precision } => {
204 write!(f, "PRECISIONTIMESTAMPTZ<{precision}>")
205 }
206 BasicBuiltinType::IntervalDay { precision } => write!(f, "INTERVAL_DAY<{precision}>"),
207 BasicBuiltinType::IntervalCompound { precision } => {
208 write!(f, "INTERVAL_COMPOUND<{precision}>")
209 }
210 }
211 }
212}
213
214#[derive(Clone, Debug, PartialEq)]
216pub enum TypeParameter {
217 Integer(i64),
219 Type(ConcreteType),
221 }
224
225impl fmt::Display for TypeParameter {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 match self {
228 TypeParameter::Integer(i) => write!(f, "{i}"),
229 TypeParameter::Type(t) => write!(f, "{t}"),
230 }
231 }
232}
233
234fn primitive_builtin(lower_name: &str) -> Option<BasicBuiltinType> {
236 match lower_name {
237 "bool" | "boolean" => Some(BasicBuiltinType::Boolean),
238 "i8" => Some(BasicBuiltinType::I8),
239 "i16" => Some(BasicBuiltinType::I16),
240 "i32" => Some(BasicBuiltinType::I32),
241 "i64" => Some(BasicBuiltinType::I64),
242 "fp32" => Some(BasicBuiltinType::Fp32),
243 "fp64" => Some(BasicBuiltinType::Fp64),
244 "string" => Some(BasicBuiltinType::String),
245 "binary" => Some(BasicBuiltinType::Binary),
246 "timestamp" => Some(BasicBuiltinType::Timestamp),
247 "timestamp_tz" => Some(BasicBuiltinType::TimestampTz),
248 "date" => Some(BasicBuiltinType::Date),
249 "time" => Some(BasicBuiltinType::Time),
250 "interval_year" => Some(BasicBuiltinType::IntervalYear),
251 "uuid" => Some(BasicBuiltinType::Uuid),
252 _ => None,
253 }
254}
255
256#[derive(Clone, Debug, PartialEq)]
258pub enum ParameterConstraint {
259 DataType,
261 Integer {
263 min: Option<i64>,
265 max: Option<i64>,
267 },
268 Enumeration {
270 options: ParsedEnumOptions,
272 },
273 Boolean,
275 String,
277}
278
279impl ParameterConstraint {
280 fn raw_type(&self) -> TypeParamDefsItemType {
282 match self {
283 ParameterConstraint::DataType => TypeParamDefsItemType::DataType,
284 ParameterConstraint::Boolean => TypeParamDefsItemType::Boolean,
285 ParameterConstraint::Integer { .. } => TypeParamDefsItemType::Integer,
286 ParameterConstraint::Enumeration { .. } => TypeParamDefsItemType::Enumeration,
287 ParameterConstraint::String => TypeParamDefsItemType::String,
288 }
289 }
290
291 fn raw_bounds(&self) -> (Option<f64>, Option<f64>) {
293 match self {
294 ParameterConstraint::Integer { min, max } => {
295 (min.map(|i| i as f64), max.map(|i| i as f64))
296 }
297 _ => (None, None),
298 }
299 }
300
301 fn raw_options(&self) -> Option<RawEnumOptions> {
303 match self {
304 ParameterConstraint::Enumeration { options } => Some(options.clone().into()),
305 _ => None,
306 }
307 }
308
309 pub fn is_valid_value(&self, value: &Value) -> bool {
311 match (self, value) {
312 (ParameterConstraint::DataType, Value::String(_)) => true,
313 (ParameterConstraint::Integer { min, max }, Value::Number(n)) => {
314 if let Some(i) = n.as_i64() {
315 min.is_none_or(|min_val| i >= min_val) && max.is_none_or(|max_val| i <= max_val)
316 } else {
317 false
318 }
319 }
320 (ParameterConstraint::Enumeration { options }, Value::String(s)) => options.contains(s),
321 (ParameterConstraint::Boolean, Value::Bool(_)) => true,
322 (ParameterConstraint::String, Value::String(_)) => true,
323 _ => false,
324 }
325 }
326
327 fn from_raw(
328 t: TypeParamDefsItemType,
329 opts: Option<RawEnumOptions>,
330 min: Option<f64>,
331 max: Option<f64>,
332 ) -> Result<Self, TypeParamError> {
333 Ok(match t {
334 TypeParamDefsItemType::DataType => Self::DataType,
335 TypeParamDefsItemType::Boolean => Self::Boolean,
336 TypeParamDefsItemType::Integer => {
337 match (min, max) {
338 (Some(min_f), _) if min_f.fract() != 0.0 => {
339 return Err(TypeParamError::InvalidIntegerBounds { min, max });
340 }
341 (_, Some(max_f)) if max_f.fract() != 0.0 => {
342 return Err(TypeParamError::InvalidIntegerBounds { min, max });
343 }
344 _ => (),
345 }
346
347 let min_i = min.map(|v| v as i64);
348 let max_i = max.map(|v| v as i64);
349 Self::Integer {
350 min: min_i,
351 max: max_i,
352 }
353 }
354 TypeParamDefsItemType::Enumeration => {
355 let options: ParsedEnumOptions =
356 opts.ok_or(TypeParamError::MissingEnumOptions)?.try_into()?;
357 Self::Enumeration { options }
358 }
359 TypeParamDefsItemType::String => Self::String,
360 })
361 }
362}
363
364#[derive(Clone, Debug, PartialEq)]
366pub struct TypeParam {
367 pub name: String,
369 pub param_type: ParameterConstraint,
371 pub description: Option<String>,
373}
374
375impl TypeParam {
376 pub fn new(name: String, param_type: ParameterConstraint, description: Option<String>) -> Self {
378 Self {
379 name,
380 param_type,
381 description,
382 }
383 }
384
385 pub fn is_valid_value(&self, value: &Value) -> bool {
387 self.param_type.is_valid_value(value)
388 }
389}
390
391impl TryFrom<TypeParamDefsItem> for TypeParam {
392 type Error = TypeParamError;
393
394 fn try_from(item: TypeParamDefsItem) -> Result<Self, Self::Error> {
395 let name = item.name.ok_or(TypeParamError::MissingName)?;
396 let param_type =
397 ParameterConstraint::from_raw(item.type_, item.options, item.min, item.max)?;
398
399 Ok(Self {
400 name,
401 param_type,
402 description: item.description,
403 })
404 }
405}
406
407#[derive(Debug, Error, PartialEq)]
409pub enum ExtensionTypeError {
410 #[error("{0}")]
412 InvalidName(#[from] InvalidTypeName),
413 #[error("Any type variable is invalid for concrete types: any{}{}", id, nullability.then_some("?").unwrap_or(""))]
415 InvalidAnyTypeVariable {
416 id: u32,
418 nullability: bool,
420 },
421 #[error(
423 "Unknown type name: '{}'. Extension types must use the u! prefix (e.g., u!{})",
424 name,
425 name
426 )]
427 UnknownTypeName {
428 name: String,
430 },
431 #[error("Invalid parameter: {0}")]
433 InvalidParameter(#[from] TypeParamError),
434 #[error("Invalid structure field type: {0}")]
436 InvalidFieldType(String),
437 #[error("Duplicate struct field '{field_name}'")]
439 DuplicateFieldName {
440 field_name: String,
442 },
443 #[error("Type '{type_name}' expects {expected} parameters, got {actual}")]
445 InvalidParameterCount {
446 type_name: String,
448 expected: usize,
450 actual: usize,
452 },
453 #[error("Type '{type_name}' parameter {index} must be {expected}")]
455 InvalidParameterKind {
456 type_name: String,
458 index: usize,
460 expected: &'static str,
462 },
463 #[error("Type '{type_name}' parameter {index} value {value} is not within {expected}")]
465 InvalidParameterValue {
466 type_name: String,
468 index: usize,
470 value: i64,
472 expected: &'static str,
474 },
475 #[error("Type '{type_name}' parameter {index} value {value} is out of range {expected:?}")]
477 InvalidParameterRange {
478 type_name: String,
480 index: usize,
482 value: i64,
484 expected: RangeInclusive<i32>,
486 },
487 #[error("Structure representation cannot be nullable: {type_string}")]
489 StructureCannotBeNullable {
490 type_string: String,
492 },
493 #[error("Error parsing type: {0}")]
495 ParseType(#[from] TypeParseError),
496}
497
498#[derive(Debug, Error, PartialEq)]
500pub enum TypeParamError {
501 #[error("Parameter name is required")]
503 MissingName,
504 #[error("Integer parameter has invalid min/max values: min={min:?}, max={max:?}")]
506 InvalidIntegerBounds {
507 min: Option<f64>,
509 max: Option<f64>,
511 },
512 #[error("Enumeration parameter is missing options")]
514 MissingEnumOptions,
515 #[error("Enumeration parameter has invalid options: {0}")]
517 InvalidEnumOptions(#[from] ParsedEnumOptionsError),
518}
519
520#[derive(Clone, Debug, PartialEq)]
522pub struct CustomType {
523 pub name: String,
525 pub parameters: Vec<TypeParam>,
527 pub structure: Option<ConcreteType>,
529 pub variadic: Option<bool>,
531 pub description: Option<String>,
533}
534
535impl CustomType {
536 pub fn validate_name(name: &str) -> Result<(), InvalidTypeName> {
544 let mut chars = name.chars();
545 let first = chars
546 .next()
547 .ok_or_else(|| InvalidTypeName(name.to_string()))?;
548 if !(first.is_ascii_alphabetic() || first == '_' || first == '$') {
549 return Err(InvalidTypeName(name.to_string()));
550 }
551
552 if !chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$') {
553 return Err(InvalidTypeName(name.to_string()));
554 }
555
556 Ok(())
557 }
558
559 pub fn new(
561 name: String,
562 parameters: Vec<TypeParam>,
563 structure: Option<ConcreteType>,
564 variadic: Option<bool>,
565 description: Option<String>,
566 ) -> Result<Self, ExtensionTypeError> {
567 Self::validate_name(&name)?;
568
569 Ok(Self {
570 name,
571 parameters,
572 structure,
573 variadic,
574 description,
575 })
576 }
577}
578
579impl From<CustomType> for SimpleExtensionsTypesItem {
580 fn from(value: CustomType) -> Self {
581 let parameters = if value.parameters.is_empty() {
583 None
584 } else {
585 Some(TypeParamDefs(
586 value
587 .parameters
588 .into_iter()
589 .map(|param| {
590 let (min, max) = param.param_type.raw_bounds();
591 TypeParamDefsItem {
592 name: Some(param.name),
593 description: param.description,
594 type_: param.param_type.raw_type(),
595 min,
596 max,
597 options: param.param_type.raw_options(),
598 optional: None,
602 }
603 })
604 .collect(),
605 ))
606 };
607
608 let structure = value.structure.map(Into::into);
610
611 SimpleExtensionsTypesItem {
612 name: value.name,
613 description: value.description,
614 metadata: Default::default(),
615 parameters,
616 structure,
617 variadic: value.variadic,
618 deprecated: None,
620 }
621 }
622}
623
624impl Parse<TypeContext> for SimpleExtensionsTypesItem {
625 type Parsed = CustomType;
626 type Error = ExtensionTypeError;
627
628 fn parse(self, ctx: &mut TypeContext) -> Result<Self::Parsed, Self::Error> {
629 let name = self.name;
630 CustomType::validate_name(&name)?;
631
632 ctx.found(&name);
634
635 let parameters = if let Some(param_defs) = self.parameters {
636 param_defs
637 .0
638 .into_iter()
639 .map(TypeParam::try_from)
640 .collect::<Result<Vec<_>, _>>()?
641 } else {
642 Vec::new()
643 };
644
645 let structure = match self.structure {
647 Some(structure_data) => {
648 let parsed = Parse::parse(structure_data, ctx)?;
649 Some(parsed)
654 }
655 None => None,
656 };
657
658 Ok(CustomType {
659 name,
660 parameters,
661 structure,
662 variadic: self.variadic,
663 description: self.description,
664 })
665 }
666}
667
668impl Parse<TypeContext> for RawType {
669 type Parsed = ConcreteType;
670 type Error = ExtensionTypeError;
671
672 fn parse(self, ctx: &mut TypeContext) -> Result<Self::Parsed, Self::Error> {
673 match self {
674 RawType::String(type_string) => {
675 let parsed_type = TypeExpr::parse(&type_string)?;
676 let mut link = |name: &str| ctx.linked(name);
677 parsed_type.visit_references(&mut link);
678 let concrete = ConcreteType::try_from(parsed_type)?;
679 Ok(concrete)
680 }
681 RawType::Object(field_map) => {
682 let mut fields = IndexMap::new();
687
688 for (field_name, field_type_value) in field_map {
689 let type_string = match field_type_value {
690 serde_json::Value::String(s) => s,
691 _ => {
692 return Err(ExtensionTypeError::InvalidFieldType(
693 "Struct field types must be strings".to_string(),
694 ));
695 }
696 };
697
698 let parsed_field_type = TypeExpr::parse(&type_string)?;
699 let mut link = |name: &str| ctx.linked(name);
700 parsed_field_type.visit_references(&mut link);
701 let field_concrete_type = ConcreteType::try_from(parsed_field_type)?;
702
703 if fields
704 .insert(field_name.clone(), field_concrete_type)
705 .is_some()
706 {
707 return Err(ExtensionTypeError::DuplicateFieldName { field_name });
708 }
709 }
710
711 Ok(ConcreteType {
712 kind: ConcreteTypeKind::NamedStruct { fields },
713 nullable: false,
714 })
715 }
716 }
717 }
718}
719
720#[derive(Debug, Error, PartialEq)]
722#[error("invalid type name `{0}`")]
723pub struct InvalidTypeName(String);
724
725#[derive(Clone, Debug, PartialEq)]
733pub enum ConcreteTypeKind {
734 Builtin(BasicBuiltinType),
736 Extension {
738 name: String,
740 parameters: Vec<TypeParameter>,
742 },
743 List(Box<ConcreteType>),
745 Map {
747 key: Box<ConcreteType>,
749 value: Box<ConcreteType>,
751 },
752 Struct(Vec<ConcreteType>),
754 NamedStruct {
756 fields: IndexMap<String, ConcreteType>,
759 },
760}
761
762impl fmt::Display for ConcreteTypeKind {
763 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
764 match self {
765 ConcreteTypeKind::Builtin(b) => write!(f, "{b}"),
766 ConcreteTypeKind::Extension { name, parameters } => {
767 write!(f, "{name}")?;
768 write_separated(f, parameters.iter(), "<", ">", ", ")
769 }
770 ConcreteTypeKind::List(elem) => write!(f, "list<{elem}>"),
771 ConcreteTypeKind::Map { key, value } => write!(f, "map<{key}, {value}>"),
772 ConcreteTypeKind::Struct(types) => {
773 write_separated(f, types.iter(), "struct<", ">", ", ")
774 }
775 ConcreteTypeKind::NamedStruct { fields } => {
776 let kvs = fields.iter().map(|(k, v)| KeyValueDisplay(k, v, ": "));
777
778 write_separated(f, kvs, "{", "}", ", ")
779 }
780 }
781 }
782}
783
784#[derive(Clone, Debug, PartialEq)]
786pub struct ConcreteType {
787 pub kind: ConcreteTypeKind,
789 pub nullable: bool,
791}
792
793impl ConcreteType {
794 pub fn builtin(builtin_type: BasicBuiltinType, nullable: bool) -> ConcreteType {
796 ConcreteType {
797 kind: ConcreteTypeKind::Builtin(builtin_type),
798 nullable,
799 }
800 }
801
802 pub fn extension(name: String, nullable: bool) -> ConcreteType {
804 ConcreteType {
805 kind: ConcreteTypeKind::Extension {
806 name,
807 parameters: Vec::new(),
808 },
809 nullable,
810 }
811 }
812
813 pub fn extension_with_params(
815 name: String,
816 parameters: Vec<TypeParameter>,
817 nullable: bool,
818 ) -> ConcreteType {
819 ConcreteType {
820 kind: ConcreteTypeKind::Extension { name, parameters },
821 nullable,
822 }
823 }
824
825 pub fn list(element_type: ConcreteType, nullable: bool) -> ConcreteType {
827 ConcreteType {
828 kind: ConcreteTypeKind::List(Box::new(element_type)),
829 nullable,
830 }
831 }
832
833 pub fn r#struct(field_types: Vec<ConcreteType>, nullable: bool) -> ConcreteType {
835 ConcreteType {
836 kind: ConcreteTypeKind::Struct(field_types),
837 nullable,
838 }
839 }
840
841 pub fn map(key_type: ConcreteType, value_type: ConcreteType, nullable: bool) -> ConcreteType {
843 ConcreteType {
844 kind: ConcreteTypeKind::Map {
845 key: Box::new(key_type),
846 value: Box::new(value_type),
847 },
848 nullable,
849 }
850 }
851
852 pub fn named_struct(fields: IndexMap<String, ConcreteType>, nullable: bool) -> ConcreteType {
854 ConcreteType {
855 kind: ConcreteTypeKind::NamedStruct { fields },
856 nullable,
857 }
858 }
859
860 pub fn is_compatible_with(&self, other: &ConcreteType) -> bool {
869 self.kind == other.kind && (self.nullable || !other.nullable)
871 }
872}
873
874impl fmt::Display for ConcreteType {
875 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
876 write!(f, "{}", self.kind)?;
877 if self.nullable {
878 write!(f, "?")?;
879 }
880 Ok(())
881 }
882}
883
884impl From<ConcreteType> for RawType {
885 fn from(val: ConcreteType) -> Self {
886 match val.kind {
887 ConcreteTypeKind::NamedStruct { fields } => {
888 let map = Map::from_iter(
889 fields
890 .into_iter()
891 .map(|(name, ty)| (name, serde_json::Value::String(ty.to_string()))),
892 );
893 RawType::Object(map)
894 }
895 _ => RawType::String(val.to_string()),
896 }
897 }
898}
899
900fn expect_integer_param(
912 type_name: &str,
913 index: usize,
914 param: &TypeExprParam<'_>,
915 range: Option<RangeInclusive<i32>>,
916) -> Result<i32, ExtensionTypeError> {
917 let value = match param {
918 TypeExprParam::Integer(value) => {
919 i32::try_from(*value).map_err(|_| ExtensionTypeError::InvalidParameterValue {
920 type_name: type_name.to_string(),
921 index,
922 value: *value,
923 expected: "an i32",
924 })
925 }
926 _ => Err(ExtensionTypeError::InvalidParameterKind {
927 type_name: type_name.to_string(),
928 index,
929 expected: "an integer",
930 }),
931 }?;
932
933 if let Some(range) = range {
934 if range.contains(&value) {
935 return Ok(value);
936 }
937 return Err(ExtensionTypeError::InvalidParameterRange {
938 type_name: type_name.to_string(),
939 index,
940 value: i64::from(value),
941 expected: range,
942 });
943 }
944
945 Ok(value)
946}
947
948fn expect_param_len(
951 type_name: &str,
952 params: &[TypeExprParam<'_>],
953 expected: usize,
954) -> Result<(), ExtensionTypeError> {
955 if params.len() != expected {
956 return Err(ExtensionTypeError::InvalidParameterCount {
957 type_name: type_name.to_string(),
958 expected,
959 actual: params.len(),
960 });
961 }
962 Ok(())
963}
964
965fn expect_type_argument<'a>(
968 type_name: &str,
969 index: usize,
970 param: TypeExprParam<'a>,
971) -> Result<ConcreteType, ExtensionTypeError> {
972 match param {
973 TypeExprParam::Type(t) => ConcreteType::try_from(t),
974 TypeExprParam::Integer(_) => Err(ExtensionTypeError::InvalidParameterKind {
975 type_name: type_name.to_string(),
976 index,
977 expected: "a type",
978 }),
979 }
980}
981
982impl<'a> TryFrom<TypeExprParam<'a>> for TypeParameter {
983 type Error = ExtensionTypeError;
984
985 fn try_from(param: TypeExprParam<'a>) -> Result<Self, Self::Error> {
986 Ok(match param {
987 TypeExprParam::Integer(v) => TypeParameter::Integer(v),
988 TypeExprParam::Type(t) => TypeParameter::Type(ConcreteType::try_from(t)?),
989 })
990 }
991}
992
993fn parse_builtin<'a>(
997 display_name: &str,
998 lower_name: &str,
999 params: &[TypeExprParam<'a>],
1000) -> Result<Option<BasicBuiltinType>, ExtensionTypeError> {
1001 if let Some(builtin) = primitive_builtin(lower_name) {
1002 expect_param_len(display_name, params, 0)?;
1003 return Ok(Some(builtin));
1004 }
1005
1006 match lower_name {
1007 "fixedchar" => {
1009 expect_param_len(display_name, params, 1)?;
1010 let length = expect_integer_param(display_name, 0, ¶ms[0], None)?;
1011 Ok(Some(BasicBuiltinType::FixedChar { length }))
1012 }
1013 "varchar" => {
1014 expect_param_len(display_name, params, 1)?;
1015 let length = expect_integer_param(display_name, 0, ¶ms[0], None)?;
1016 Ok(Some(BasicBuiltinType::VarChar { length }))
1017 }
1018 "fixedbinary" => {
1019 expect_param_len(display_name, params, 1)?;
1020 let length = expect_integer_param(display_name, 0, ¶ms[0], None)?;
1021 Ok(Some(BasicBuiltinType::FixedBinary { length }))
1022 }
1023 "decimal" => {
1024 expect_param_len(display_name, params, 2)?;
1025 let precision = expect_integer_param(display_name, 0, ¶ms[0], Some(1..=38))?;
1026 let scale = expect_integer_param(display_name, 1, ¶ms[1], Some(0..=precision))?;
1027 Ok(Some(BasicBuiltinType::Decimal { precision, scale }))
1028 }
1029 "precisiontime" | "precision_time" => {
1030 expect_param_len(display_name, params, 1)?;
1031 let precision = expect_integer_param(display_name, 0, ¶ms[0], Some(0..=12))?;
1032 Ok(Some(BasicBuiltinType::PrecisionTime { precision }))
1033 }
1034 "precision_timestamp" => {
1035 expect_param_len(display_name, params, 1)?;
1036 let precision = expect_integer_param(display_name, 0, ¶ms[0], Some(0..=12))?;
1037 Ok(Some(BasicBuiltinType::PrecisionTimestamp { precision }))
1038 }
1039 "precision_timestamp_tz" => {
1040 expect_param_len(display_name, params, 1)?;
1041 let precision = expect_integer_param(display_name, 0, ¶ms[0], Some(0..=12))?;
1042 Ok(Some(BasicBuiltinType::PrecisionTimestampTz { precision }))
1043 }
1044 "interval_day" => {
1045 expect_param_len(display_name, params, 1)?;
1046 let precision = expect_integer_param(display_name, 0, ¶ms[0], Some(0..=9))?;
1047 Ok(Some(BasicBuiltinType::IntervalDay { precision }))
1048 }
1049 "interval_compound" => {
1050 expect_param_len(display_name, params, 1)?;
1051 let precision = expect_integer_param(display_name, 0, ¶ms[0], None)?;
1052 Ok(Some(BasicBuiltinType::IntervalCompound { precision }))
1053 }
1054 _ => Ok(None),
1055 }
1056}
1057
1058impl<'a> TryFrom<TypeExpr<'a>> for ConcreteType {
1059 type Error = ExtensionTypeError;
1060
1061 fn try_from(parsed_type: TypeExpr<'a>) -> Result<Self, Self::Error> {
1062 match parsed_type {
1063 TypeExpr::Simple(name, params, nullable) => {
1064 let lower = name.to_ascii_lowercase();
1065
1066 match lower.as_str() {
1067 "list" => {
1068 expect_param_len(name, ¶ms, 1)?;
1069 let element =
1070 expect_type_argument(name, 0, params.into_iter().next().unwrap())?;
1071 return Ok(ConcreteType::list(element, nullable));
1072 }
1073 "map" => {
1074 expect_param_len(name, ¶ms, 2)?;
1075 let mut iter = params.into_iter();
1076 let key = expect_type_argument(name, 0, iter.next().unwrap())?;
1077 let value = expect_type_argument(name, 1, iter.next().unwrap())?;
1078 return Ok(ConcreteType::map(key, value, nullable));
1079 }
1080 "struct" => {
1081 let field_types = params
1082 .into_iter()
1083 .enumerate()
1084 .map(|(idx, param)| expect_type_argument(name, idx, param))
1085 .collect::<Result<Vec<_>, _>>()?;
1086 return Ok(ConcreteType::r#struct(field_types, nullable));
1087 }
1088 _ => {}
1089 }
1090
1091 if let Some(builtin) = parse_builtin(name, lower.as_str(), ¶ms)? {
1092 return Ok(ConcreteType::builtin(builtin, nullable));
1093 }
1094
1095 Err(ExtensionTypeError::UnknownTypeName {
1098 name: name.to_string(),
1099 })
1100 }
1101 TypeExpr::UserDefined(name, params, nullable) => {
1102 let parameters = params
1103 .into_iter()
1104 .map(TypeParameter::try_from)
1105 .collect::<Result<Vec<_>, _>>()?;
1106 Ok(ConcreteType::extension_with_params(
1107 name.to_string(),
1108 parameters,
1109 nullable,
1110 ))
1111 }
1112 TypeExpr::TypeVariable(id, nullability) => {
1113 Err(ExtensionTypeError::InvalidAnyTypeVariable { id, nullability })
1114 }
1115 }
1116 }
1117}
1118
1119#[cfg(test)]
1120mod tests {
1121 use super::super::extensions::TypeContext;
1122 use super::*;
1123 use crate::parse::text::simple_extensions::TypeExpr;
1124 use crate::parse::text::simple_extensions::argument::EnumOptions as ParsedEnumOptions;
1125 use crate::text::simple_extensions;
1126 use std::iter::FromIterator;
1127
1128 fn concretize(builtin: BasicBuiltinType) -> ConcreteType {
1130 ConcreteType::builtin(builtin, false)
1131 }
1132
1133 fn parse_type(expr: &str) -> ConcreteType {
1135 let parsed = TypeExpr::parse(expr).unwrap();
1136 ConcreteType::try_from(parsed).unwrap()
1137 }
1138
1139 fn parse_type_result(expr: &str) -> Result<ConcreteType, ExtensionTypeError> {
1141 let parsed = TypeExpr::parse(expr).unwrap();
1142 ConcreteType::try_from(parsed)
1143 }
1144
1145 fn parse_simple(s: &str) -> ConcreteType {
1148 let parsed = TypeExpr::parse(s).unwrap();
1149
1150 let mut refs = Vec::new();
1151 parsed.visit_references(&mut |name| refs.push(name.to_string()));
1152 assert!(refs.is_empty(), "{s} should not add an extension reference");
1153
1154 ConcreteType::try_from(parsed).unwrap()
1155 }
1156
1157 fn type_param(expr: &str) -> TypeParameter {
1159 TypeParameter::Type(parse_type(expr))
1160 }
1161
1162 fn extension(name: &str, parameters: Vec<TypeParameter>, nullable: bool) -> ConcreteType {
1164 ConcreteType::extension_with_params(name.to_string(), parameters, nullable)
1165 }
1166
1167 fn round_trip(custom: &CustomType) {
1169 let item: simple_extensions::SimpleExtensionsTypesItem = custom.clone().into();
1170 let mut ctx = TypeContext::default();
1171 let parsed = Parse::parse(item, &mut ctx).unwrap();
1172 assert_eq!(&parsed, custom);
1173 }
1174
1175 fn raw_named_struct(fields: &[(&str, &str)]) -> RawType {
1178 let map = Map::from_iter(
1179 fields
1180 .iter()
1181 .map(|(name, ty)| ((*name).into(), serde_json::Value::String((*ty).into()))),
1182 );
1183
1184 RawType::Object(map)
1185 }
1186
1187 #[test]
1188 fn test_builtin_scalar_parsing() {
1189 let cases = vec![
1190 ("bool", Some(BasicBuiltinType::Boolean)),
1191 ("i8", Some(BasicBuiltinType::I8)),
1192 ("i16", Some(BasicBuiltinType::I16)),
1193 ("i32", Some(BasicBuiltinType::I32)),
1194 ("i64", Some(BasicBuiltinType::I64)),
1195 ("fp32", Some(BasicBuiltinType::Fp32)),
1196 ("fp64", Some(BasicBuiltinType::Fp64)),
1197 ("STRING", Some(BasicBuiltinType::String)),
1198 ("binary", Some(BasicBuiltinType::Binary)),
1199 ("uuid", Some(BasicBuiltinType::Uuid)),
1200 ("date", Some(BasicBuiltinType::Date)),
1201 ("interval_year", Some(BasicBuiltinType::IntervalYear)),
1202 ("time", Some(BasicBuiltinType::Time)),
1203 ("timestamp", Some(BasicBuiltinType::Timestamp)),
1204 ("timestamp_tz", Some(BasicBuiltinType::TimestampTz)),
1205 ("invalid", None),
1206 ];
1207
1208 for (input, expected) in cases {
1209 let result = parse_builtin(input, input.to_ascii_lowercase().as_str(), &[]).unwrap();
1210 match expected {
1211 Some(expected_type) => {
1212 assert_eq!(
1213 result,
1214 Some(expected_type),
1215 "expected builtin type for {input}"
1216 );
1217 }
1218 None => {
1219 assert!(result.is_none(), "expected parsing {input} to fail");
1220 }
1221 }
1222 }
1223 }
1224
1225 #[test]
1226 fn test_parameterized_builtin_types() {
1227 let cases = vec![
1228 (
1229 "precisiontime<2>",
1230 concretize(BasicBuiltinType::PrecisionTime { precision: 2 }),
1231 ),
1232 (
1233 "precision_timestamp<1>",
1234 concretize(BasicBuiltinType::PrecisionTimestamp { precision: 1 }),
1235 ),
1236 (
1237 "precision_timestamp_tz<5>",
1238 concretize(BasicBuiltinType::PrecisionTimestampTz { precision: 5 }),
1239 ),
1240 (
1241 "DECIMAL<10,2>",
1242 concretize(BasicBuiltinType::Decimal {
1243 precision: 10,
1244 scale: 2,
1245 }),
1246 ),
1247 (
1248 "fixedchar<12>",
1249 concretize(BasicBuiltinType::FixedChar { length: 12 }),
1250 ),
1251 (
1252 "VarChar<42>",
1253 concretize(BasicBuiltinType::VarChar { length: 42 }),
1254 ),
1255 (
1256 "fixedbinary<8>",
1257 concretize(BasicBuiltinType::FixedBinary { length: 8 }),
1258 ),
1259 (
1260 "interval_day<7>",
1261 concretize(BasicBuiltinType::IntervalDay { precision: 7 }),
1262 ),
1263 (
1264 "interval_compound<6>",
1265 concretize(BasicBuiltinType::IntervalCompound { precision: 6 }),
1266 ),
1267 ];
1268
1269 for (expr, expected) in cases {
1270 let found = parse_simple(expr);
1271 assert_eq!(found, expected, "unexpected type for {expr}");
1272 }
1273 }
1274
1275 #[test]
1276 fn test_parameterized_builtin_range_errors() {
1277 use ExtensionTypeError::InvalidParameterRange;
1278
1279 let cases = vec![
1280 ("precisiontime<13>", "precisiontime", 0, 13, 0..=12),
1281 ("precisiontime<-1>", "precisiontime", 0, -1, 0..=12),
1282 (
1283 "precision_timestamp<13>",
1284 "precision_timestamp",
1285 0,
1286 13,
1287 0..=12,
1288 ),
1289 (
1290 "precision_timestamp<-1>",
1291 "precision_timestamp",
1292 0,
1293 -1,
1294 0..=12,
1295 ),
1296 (
1297 "precision_timestamp_tz<20>",
1298 "precision_timestamp_tz",
1299 0,
1300 20,
1301 0..=12,
1302 ),
1303 ("interval_day<10>", "interval_day", 0, 10, 0..=9),
1304 ("DECIMAL<39,0>", "DECIMAL", 0, 39, 1..=38),
1305 ("DECIMAL<0,0>", "DECIMAL", 0, 0, 1..=38),
1306 ("DECIMAL<10,-1>", "DECIMAL", 1, -1, 0..=10),
1307 ("DECIMAL<10,12>", "DECIMAL", 1, 12, 0..=10),
1308 ];
1309
1310 for (expr, expected_type, expected_index, expected_value, expected_range) in cases {
1311 match parse_type_result(expr) {
1312 Ok(value) => panic!("expected error parsing {expr}, got {value:?}"),
1313 Err(InvalidParameterRange {
1314 type_name,
1315 index,
1316 value,
1317 expected,
1318 }) => {
1319 assert_eq!(type_name, expected_type, "unexpected type for {expr}");
1320 assert_eq!(index, expected_index, "unexpected index for {expr}");
1321 assert_eq!(
1322 value,
1323 i64::from(expected_value),
1324 "unexpected value for {expr}"
1325 );
1326 assert_eq!(expected, expected_range, "unexpected range for {expr}");
1327 }
1328 Err(other) => panic!("expected InvalidParameterRange for {expr}, got {other:?}"),
1329 }
1330 }
1331 }
1332
1333 #[test]
1334 fn test_container_types() {
1335 let cases = vec![
1336 (
1337 "List<i32>",
1338 ConcreteType::list(ConcreteType::builtin(BasicBuiltinType::I32, false), false),
1339 ),
1340 (
1341 "List<fp64?>",
1342 ConcreteType::list(ConcreteType::builtin(BasicBuiltinType::Fp64, true), false),
1343 ),
1344 (
1345 "Map?<i64, string?>",
1346 ConcreteType::map(
1347 ConcreteType::builtin(BasicBuiltinType::I64, false),
1348 ConcreteType::builtin(BasicBuiltinType::String, true),
1349 true,
1350 ),
1351 ),
1352 (
1353 "Struct?<i8, string?>",
1354 ConcreteType::r#struct(
1355 vec![
1356 ConcreteType::builtin(BasicBuiltinType::I8, false),
1357 ConcreteType::builtin(BasicBuiltinType::String, true),
1358 ],
1359 true,
1360 ),
1361 ),
1362 ];
1363
1364 for (expr, expected) in cases {
1365 assert_eq!(parse_type(expr), expected, "unexpected parse for {expr}");
1366 }
1367 }
1368
1369 #[test]
1370 fn test_extension_types() {
1371 let cases = vec![
1372 (
1373 "u!geo<List<i32>, 10>",
1374 extension(
1375 "geo",
1376 vec![type_param("List<i32>"), TypeParameter::Integer(10)],
1377 false,
1378 ),
1379 ),
1380 (
1381 "u!Geo?<List<i32?>>",
1382 extension("Geo", vec![type_param("List<i32?>")], true),
1383 ),
1384 (
1385 "u!Custom<string?, bool>",
1386 extension(
1387 "Custom",
1388 vec![
1389 type_param("string?"),
1390 TypeParameter::Type(ConcreteType::builtin(
1391 BasicBuiltinType::Boolean,
1392 false,
1393 )),
1394 ],
1395 false,
1396 ),
1397 ),
1398 ];
1399
1400 for (expr, expected) in cases {
1401 assert_eq!(
1402 parse_type(expr),
1403 expected,
1404 "unexpected extension for {expr}"
1405 );
1406 }
1407 }
1408
1409 #[test]
1410 fn test_parameter_type_validation() {
1411 let int_param = ParameterConstraint::Integer {
1412 min: Some(1),
1413 max: Some(10),
1414 };
1415 let enum_param = ParameterConstraint::Enumeration {
1416 options: ParsedEnumOptions::try_from(simple_extensions::EnumOptions(vec![
1417 "OVERFLOW".to_string(),
1418 "ERROR".to_string(),
1419 ]))
1420 .unwrap(),
1421 };
1422
1423 let cases = vec![
1424 (&int_param, Value::Number(5.into()), true),
1425 (&int_param, Value::Number(0.into()), false),
1426 (&int_param, Value::Number(11.into()), false),
1427 (&int_param, Value::String("not a number".into()), false),
1428 (&enum_param, Value::String("OVERFLOW".into()), true),
1429 (&enum_param, Value::String("INVALID".into()), false),
1430 ];
1431
1432 for (param, value, expected) in cases {
1433 assert_eq!(
1434 param.is_valid_value(&value),
1435 expected,
1436 "unexpected validation result for {value:?}"
1437 );
1438 }
1439 }
1440
1441 #[test]
1442 fn test_type_round_trip_display() {
1443 let cases = vec![
1445 ("i32", "i32"),
1446 ("I64?", "i64?"),
1447 ("list<string>", "list<string>"),
1448 ("List<STRING?>", "list<string?>"),
1449 ("map<i32, list<string>>", "map<i32, list<string>>"),
1450 ("struct<i8, string?>", "struct<i8, string?>"),
1451 (
1452 "Struct<List<i32>, Map<string, list<i64?>>>",
1453 "struct<list<i32>, map<string, list<i64?>>>",
1454 ),
1455 (
1456 "Map<List<I32?>, Struct<string, list<i64?>>>",
1457 "map<list<i32?>, struct<string, list<i64?>>>",
1458 ),
1459 ("u!custom<i32>", "custom<i32>"),
1460 ];
1461
1462 for (input, expected) in cases {
1463 let parsed = TypeExpr::parse(input).unwrap();
1464 let concrete = ConcreteType::try_from(parsed).unwrap();
1465 let actual = concrete.to_string();
1466
1467 assert_eq!(actual, expected, "unexpected display for {input}");
1468 }
1469 }
1470
1471 #[test]
1474 fn test_named_struct_field_order_stability() -> Result<(), ExtensionTypeError> {
1475 let mut raw_fields = Map::new();
1476 raw_fields.insert("beta".to_string(), Value::String("i32".to_string()));
1477 raw_fields.insert("alpha".to_string(), Value::String("string?".to_string()));
1478
1479 let raw = RawType::Object(raw_fields);
1480 let mut ctx = TypeContext::default();
1481 let concrete = Parse::parse(raw, &mut ctx)?;
1482
1483 let round_tripped: RawType = concrete.into();
1484 match round_tripped {
1485 RawType::Object(result_map) => {
1486 let keys: Vec<_> = result_map.keys().collect();
1487 assert_eq!(
1488 keys,
1489 vec!["beta", "alpha"],
1490 "field order should be preserved"
1491 );
1492 }
1493 other => panic!("expected Object, got {other:?}"),
1494 }
1495
1496 Ok(())
1497 }
1498
1499 #[test]
1500 fn test_integer_param_bounds_round_trip() {
1501 let cases = vec![
1502 (
1503 "bounded",
1504 simple_extensions::TypeParamDefsItem {
1505 name: Some("K".to_string()),
1506 description: None,
1507 type_: simple_extensions::TypeParamDefsItemType::Integer,
1508 min: Some(1.0),
1509 max: Some(10.0),
1510 options: None,
1511 optional: None,
1512 },
1513 Ok((Some(1), Some(10))),
1514 ),
1515 (
1516 "fractional_min",
1517 simple_extensions::TypeParamDefsItem {
1518 name: Some("K".to_string()),
1519 description: None,
1520 type_: simple_extensions::TypeParamDefsItemType::Integer,
1521 min: Some(1.5),
1522 max: None,
1523 options: None,
1524 optional: None,
1525 },
1526 Err(TypeParamError::InvalidIntegerBounds {
1527 min: Some(1.5),
1528 max: None,
1529 }),
1530 ),
1531 (
1532 "fractional_max",
1533 simple_extensions::TypeParamDefsItem {
1534 name: Some("K".to_string()),
1535 description: None,
1536 type_: simple_extensions::TypeParamDefsItemType::Integer,
1537 min: None,
1538 max: Some(9.9),
1539 options: None,
1540 optional: None,
1541 },
1542 Err(TypeParamError::InvalidIntegerBounds {
1543 min: None,
1544 max: Some(9.9),
1545 }),
1546 ),
1547 ];
1548
1549 for (label, item, expected) in cases {
1550 match (TypeParam::try_from(item), expected) {
1551 (Ok(tp), Ok((expected_min, expected_max))) => match tp.param_type {
1552 ParameterConstraint::Integer { min, max } => {
1553 assert_eq!(min, expected_min, "min mismatch for {label}");
1554 assert_eq!(max, expected_max, "max mismatch for {label}");
1555 }
1556 _ => panic!("expected integer param type for {label}"),
1557 },
1558 (Err(actual_err), Err(expected_err)) => {
1559 assert_eq!(actual_err, expected_err, "unexpected error for {label}");
1560 }
1561 (result, expected) => {
1562 panic!("unexpected result for {label}: got {result:?}, expected {expected:?}")
1563 }
1564 }
1565 }
1566 }
1567
1568 #[test]
1569 fn test_custom_type_round_trip() -> Result<(), ExtensionTypeError> {
1570 let fields = IndexMap::from_iter([
1571 (
1572 "x".to_string(),
1573 ConcreteType::builtin(BasicBuiltinType::Fp64, false),
1574 ),
1575 (
1576 "y".to_string(),
1577 ConcreteType::builtin(BasicBuiltinType::Fp64, false),
1578 ),
1579 ]);
1580
1581 let cases = vec![
1582 CustomType::new(
1583 "AliasType".to_string(),
1584 vec![],
1585 Some(ConcreteType::builtin(BasicBuiltinType::I32, false)),
1586 None,
1587 Some("a test alias type".to_string()),
1588 )?,
1589 CustomType::new(
1590 "Point".to_string(),
1591 vec![TypeParam::new(
1592 "T".to_string(),
1593 ParameterConstraint::DataType,
1594 Some("a numeric value".to_string()),
1595 )],
1596 Some(ConcreteType::named_struct(fields, false)),
1597 None,
1598 None,
1599 )?,
1600 ];
1601
1602 for custom in cases {
1603 round_trip(&custom);
1604 }
1605
1606 Ok(())
1607 }
1608
1609 #[test]
1610 fn test_invalid_type_names() {
1611 let cases = vec![
1612 ("", false),
1613 ("bad name", false),
1614 ("9bad", false),
1615 ("bad-name", false),
1616 ("bad.name", false),
1617 ("GoodName", true),
1618 ("also_good", true),
1619 ("_underscore", true),
1620 ("$dollar", true),
1621 ("CamelCase123", true),
1622 ];
1623
1624 for (name, expected_ok) in cases {
1625 let result = CustomType::validate_name(name);
1626 assert_eq!(
1627 result.is_ok(),
1628 expected_ok,
1629 "unexpected validation for {name}"
1630 );
1631 }
1632 }
1633
1634 #[test]
1635 fn test_ext_type_to_concrete_type() -> Result<(), ExtensionTypeError> {
1636 let cases = vec![
1637 (
1638 "alias",
1639 RawType::String("i32".to_string()),
1640 ConcreteType::builtin(BasicBuiltinType::I32, false),
1641 ),
1642 (
1643 "named_struct",
1644 raw_named_struct(&[("field1", "fp64"), ("field2", "i32?")]),
1645 ConcreteType::named_struct(
1646 IndexMap::from_iter([
1647 (
1648 "field1".to_string(),
1649 ConcreteType::builtin(BasicBuiltinType::Fp64, false),
1650 ),
1651 (
1652 "field2".to_string(),
1653 ConcreteType::builtin(BasicBuiltinType::I32, true),
1654 ),
1655 ]),
1656 false,
1657 ),
1658 ),
1659 ];
1660
1661 for (label, raw, expected) in cases {
1662 let mut ctx = TypeContext::default();
1663 let parsed = Parse::parse(raw, &mut ctx)?;
1664 assert_eq!(parsed, expected, "unexpected type for {label}");
1665 }
1666
1667 Ok(())
1668 }
1669
1670 #[test]
1671 fn test_custom_type_parsing() -> Result<(), ExtensionTypeError> {
1672 let cases = vec![
1673 (
1674 "alias",
1675 simple_extensions::SimpleExtensionsTypesItem {
1676 deprecated: None,
1677 name: "Alias".to_string(),
1678 description: Some("Alias type".to_string()),
1679 metadata: Default::default(),
1680 parameters: None,
1681 structure: Some(RawType::String("BINARY".to_string())),
1682 variadic: None,
1683 },
1684 "Alias",
1685 Some("Alias type"),
1686 Some(ConcreteType::builtin(BasicBuiltinType::Binary, false)),
1687 ),
1688 (
1689 "named_struct",
1690 simple_extensions::SimpleExtensionsTypesItem {
1691 deprecated: None,
1692 name: "Point".to_string(),
1693 description: Some("A 2D point".to_string()),
1694 metadata: Default::default(),
1695 parameters: None,
1696 structure: Some(raw_named_struct(&[("x", "fp64"), ("y", "fp64?")])),
1697 variadic: None,
1698 },
1699 "Point",
1700 Some("A 2D point"),
1701 Some(ConcreteType::named_struct(
1702 IndexMap::from_iter([
1703 (
1704 "x".to_string(),
1705 ConcreteType::builtin(BasicBuiltinType::Fp64, false),
1706 ),
1707 (
1708 "y".to_string(),
1709 ConcreteType::builtin(BasicBuiltinType::Fp64, true),
1710 ),
1711 ]),
1712 false,
1713 )),
1714 ),
1715 (
1716 "no_structure",
1717 simple_extensions::SimpleExtensionsTypesItem {
1718 deprecated: None,
1719 name: "Opaque".to_string(),
1720 description: None,
1721 metadata: Default::default(),
1722 parameters: None,
1723 structure: None,
1724 variadic: Some(true),
1725 },
1726 "Opaque",
1727 None,
1728 None,
1729 ),
1730 ];
1731
1732 for (label, item, expected_name, expected_description, expected_structure) in cases {
1733 let mut ctx = TypeContext::default();
1734 let parsed = Parse::parse(item, &mut ctx)?;
1735 assert_eq!(parsed.name, expected_name);
1736 assert_eq!(
1737 parsed.description.as_deref(),
1738 expected_description,
1739 "description mismatch for {label}"
1740 );
1741 assert_eq!(
1742 parsed.structure, expected_structure,
1743 "structure mismatch for {label}"
1744 );
1745 }
1746
1747 Ok(())
1748 }
1749}