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