Skip to main content

substrait/parse/text/simple_extensions/
types.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Owned type expressions used by the extension registry.
4
5use super::TypeExpr;
6use super::argument::{
7    EnumOptions as ParsedEnumOptions, EnumOptionsError as ParsedEnumOptionsError,
8};
9use super::extensions::TypeContext;
10use super::type_ast::TypeExprParam;
11use crate::parse::Parse;
12use crate::parse::text::simple_extensions::type_ast::TypeParseError;
13use crate::text::simple_extensions::{
14    EnumOptions as RawEnumOptions, SimpleExtensionsTypesItem, Type as RawType, TypeParamDefs,
15    TypeParamDefsItem, TypeParamDefsItemType,
16};
17use indexmap::IndexMap;
18use serde_json::Value;
19use std::convert::TryFrom;
20use std::fmt;
21use std::ops::RangeInclusive;
22use thiserror::Error;
23
24/// Write a sequence of items separated by a separator, with a start and end
25/// delimiter.
26///
27/// Start and end are only included in the output if there is at least one item.
28fn write_separated<I, T>(
29    f: &mut fmt::Formatter<'_>,
30    iter: I,
31    start: &str,
32    end: &str,
33    sep: &str,
34) -> fmt::Result
35where
36    I: IntoIterator<Item = T>,
37    T: fmt::Display,
38{
39    let mut it = iter.into_iter();
40    if let Some(first) = it.next() {
41        f.write_str(start)?;
42        write!(f, "{first}")?;
43        for item in it {
44            f.write_str(sep)?;
45            write!(f, "{item}")?;
46        }
47        f.write_str(end)
48    } else {
49        Ok(())
50    }
51}
52
53/// A pair of a key and a value, separated by a separator. For display purposes.
54struct KeyValueDisplay<K, V>(K, V, &'static str);
55
56impl<K, V> fmt::Display for KeyValueDisplay<K, V>
57where
58    K: fmt::Display,
59    V: fmt::Display,
60{
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        write!(f, "{}{}{}", self.0, self.2, self.1)
63    }
64}
65
66/// The structure of a type expression, without its nullability.
67#[derive(Clone, Debug, PartialEq)]
68pub enum TypeKind {
69    /// Boolean type - `bool`
70    Boolean,
71    /// 8-bit signed integer - `i8`
72    I8,
73    /// 16-bit signed integer - `i16`
74    I16,
75    /// 32-bit signed integer - `i32`
76    I32,
77    /// 64-bit signed integer - `i64`
78    I64,
79    /// 32-bit floating point - `fp32`
80    Fp32,
81    /// 64-bit floating point - `fp64`
82    Fp64,
83    /// Variable-length string - `string`
84    String,
85    /// Variable-length binary data - `binary`
86    Binary,
87    /// Calendar date - `date`
88    Date,
89    /// Year-month interval - `interval_year`
90    IntervalYear,
91    /// 128-bit UUID - `uuid`
92    Uuid,
93    /// Fixed-length character string: `fixedchar<L>`
94    FixedChar {
95        /// Length (number of characters), must be >= 1
96        length: IntegerParameter,
97    },
98    /// Variable-length character string: `varchar<L>`
99    VarChar {
100        /// Maximum length (number of characters), must be >= 1
101        length: IntegerParameter,
102    },
103    /// Fixed-length binary data: `fixedbinary<L>`
104    FixedBinary {
105        /// Length (number of bytes), must be >= 1
106        length: IntegerParameter,
107    },
108    /// Fixed-point decimal: `decimal<P, S>`
109    Decimal {
110        /// Precision (total digits), <= 38
111        precision: IntegerParameter,
112        /// Scale (digits after decimal point), 0 <= S <= P
113        scale: IntegerParameter,
114    },
115    /// Time with sub-second precision: `precision_time<P>`
116    PrecisionTime {
117        /// Sub-second precision digits (0-12: seconds to picoseconds)
118        precision: IntegerParameter,
119    },
120    /// Timestamp with sub-second precision: `precision_timestamp<P>`
121    PrecisionTimestamp {
122        /// Sub-second precision digits (0-12: seconds to picoseconds)
123        precision: IntegerParameter,
124    },
125    /// Timezone-aware timestamp with precision: `precision_timestamp_tz<P>`
126    PrecisionTimestampTz {
127        /// Sub-second precision digits (0-12: seconds to picoseconds)
128        precision: IntegerParameter,
129    },
130    /// Day-time interval: `interval_day<P>`
131    IntervalDay {
132        /// Sub-second precision digits (0-9: seconds to nanoseconds)
133        precision: IntegerParameter,
134    },
135    /// Compound interval: `interval_compound<P>`
136    IntervalCompound {
137        /// Sub-second precision digits
138        precision: IntegerParameter,
139    },
140    /// Extension type with optional parameters
141    Extension {
142        /// Extension type name
143        name: String,
144        /// Type parameters
145        parameters: Vec<TypeParameter>,
146    },
147    /// List type with element type
148    List(Box<Type>),
149    /// Map type with key and value types
150    Map {
151        /// Key type
152        key: Box<Type>,
153        /// Value type
154        value: Box<Type>,
155    },
156    /// Struct type (ordered fields without names)
157    Struct(Vec<Type>),
158    /// Named struct type (nstruct - ordered fields with names)
159    NamedStruct {
160        /// Ordered field names and types. They are in the order they should
161        /// appear in the struct - hence the use of [`IndexMap`].
162        fields: IndexMap<String, Type>,
163    },
164    /// A function type: `func<(P1, P2) -> R>`
165    Func {
166        /// Parameter types in declaration order
167        parameters: Vec<Type>,
168        /// The return type
169        return_type: Box<Type>,
170    },
171    /// An unnumbered wildcard, such as `any`.
172    Any,
173    /// A numbered type variable, such as `any1`.
174    Variable {
175        /// The variable identifier shared within a function signature.
176        id: u32,
177    },
178}
179
180impl TypeKind {
181    /// Check if a string is a valid name for a builtin scalar type
182    pub fn is_name(name: &str) -> bool {
183        let lower = name.to_ascii_lowercase();
184        primitive_builtin(&lower).is_some()
185            || matches!(
186                lower.as_str(),
187                "fixedchar"
188                    | "varchar"
189                    | "fixedbinary"
190                    | "decimal"
191                    | "precision_time"
192                    | "precision_timestamp"
193                    | "precision_timestamp_tz"
194                    | "interval_day"
195                    | "interval_compound"
196            )
197    }
198
199    fn fmt_with_nullability(&self, f: &mut fmt::Formatter<'_>, nullable: bool) -> fmt::Result {
200        let nullability = if nullable { "?" } else { "" };
201        let name = match self {
202            Self::Boolean => "bool",
203            Self::I8 => "i8",
204            Self::I16 => "i16",
205            Self::I32 => "i32",
206            Self::I64 => "i64",
207            Self::Fp32 => "fp32",
208            Self::Fp64 => "fp64",
209            Self::String => "string",
210            Self::Binary => "binary",
211            Self::Date => "date",
212            Self::IntervalYear => "interval_year",
213            Self::Uuid => "uuid",
214            Self::FixedChar { .. } => "fixedchar",
215            Self::VarChar { .. } => "varchar",
216            Self::FixedBinary { .. } => "fixedbinary",
217            Self::Decimal { .. } => "decimal",
218            Self::PrecisionTime { .. } => "precision_time",
219            Self::PrecisionTimestamp { .. } => "precision_timestamp",
220            Self::PrecisionTimestampTz { .. } => "precision_timestamp_tz",
221            Self::IntervalDay { .. } => "interval_day",
222            Self::IntervalCompound { .. } => "interval_compound",
223            Self::List(_) => "list",
224            Self::Map { .. } => "map",
225            Self::Struct(_) => "struct",
226            Self::Func { .. } => "func",
227            Self::Extension { name, parameters } => {
228                write!(f, "u!{name}{nullability}")?;
229                return write_separated(f, parameters.iter(), "<", ">", ", ");
230            }
231            Self::NamedStruct { fields } => {
232                let fields = fields
233                    .iter()
234                    .map(|(name, field)| KeyValueDisplay(name, field, ": "));
235                write_separated(f, fields, "{", "}", ", ")?;
236                return f.write_str(nullability);
237            }
238            Self::Any => return write!(f, "any{nullability}"),
239            Self::Variable { id } => return write!(f, "any{id}{nullability}"),
240        };
241        write!(f, "{name}{nullability}")?;
242        match self {
243            Self::FixedChar { length }
244            | Self::VarChar { length }
245            | Self::FixedBinary { length } => {
246                write!(f, "<{length}>")
247            }
248            Self::Decimal { precision, scale } => write!(f, "<{precision}, {scale}>"),
249            Self::PrecisionTime { precision }
250            | Self::PrecisionTimestamp { precision }
251            | Self::PrecisionTimestampTz { precision }
252            | Self::IntervalDay { precision }
253            | Self::IntervalCompound { precision } => write!(f, "<{precision}>"),
254            Self::List(element) => write!(f, "<{element}>"),
255            Self::Map { key, value } => write!(f, "<{key}, {value}>"),
256            Self::Struct(fields) => {
257                f.write_str("<")?;
258                write_separated(f, fields.iter(), "", "", ", ")?;
259                f.write_str(">")
260            }
261            Self::Func {
262                parameters,
263                return_type,
264            } => {
265                if parameters.len() == 1 {
266                    write!(f, "<{} -> {return_type}>", parameters[0])
267                } else {
268                    f.write_str("<(")?;
269                    write_separated(f, parameters.iter(), "", "", ", ")?;
270                    write!(f, ") -> {return_type}>")
271                }
272            }
273            _ => Ok(()),
274        }
275    }
276}
277
278/// Formats the full type spelling used in extension YAML, not the short name
279/// used in function signatures (e.g., `precision_timestamp<3>`, not `pts`).
280impl fmt::Display for TypeKind {
281    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282        self.fmt_with_nullability(f, false)
283    }
284}
285
286/// An integer literal or a named parameter in a type expression.
287#[derive(Clone, Debug, PartialEq)]
288pub enum IntegerParameter {
289    /// A known integer value.
290    Literal(i64),
291    /// A name whose value depends on a function call.
292    Variable(String),
293}
294
295impl IntegerParameter {
296    /// Returns whether this parameter is a literal rather than a variable.
297    pub fn is_concrete(&self) -> bool {
298        matches!(self, Self::Literal(_))
299    }
300}
301
302impl fmt::Display for IntegerParameter {
303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304        match self {
305            Self::Literal(value) => write!(f, "{value}"),
306            Self::Variable(name) => f.write_str(name),
307        }
308    }
309}
310
311/// A parameter, used in parameterized types
312#[derive(Clone, Debug, PartialEq)]
313pub enum TypeParameter {
314    /// Integer parameter (e.g., precision, scale)
315    Integer(IntegerParameter),
316    /// Type parameter (nested type)
317    Type(Type),
318    /// A name whose parameter kind depends on the user-defined type declaration.
319    Name(String),
320    // TODO: Add support for other type parameters, as described in
321    // https://github.com/substrait-io/substrait/blob/35101020d961eda48f8dd1aafbc794c9e5cac077/proto/substrait/type.proto#L250-L265
322}
323
324impl TypeParameter {
325    /// Returns whether this parameter contains no unresolved variables.
326    pub fn is_concrete(&self) -> bool {
327        match self {
328            Self::Integer(parameter) => parameter.is_concrete(),
329            Self::Type(type_) => type_.is_concrete(),
330            Self::Name(_) => false,
331        }
332    }
333}
334
335impl fmt::Display for TypeParameter {
336    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337        match self {
338            TypeParameter::Integer(i) => write!(f, "{i}"),
339            TypeParameter::Type(t) => write!(f, "{t}"),
340            TypeParameter::Name(name) => f.write_str(name),
341        }
342    }
343}
344
345/// Parse a primitive (no type parameters) builtin type name
346fn primitive_builtin(lower_name: &str) -> Option<TypeKind> {
347    match lower_name {
348        "bool" | "boolean" => Some(TypeKind::Boolean),
349        "i8" => Some(TypeKind::I8),
350        "i16" => Some(TypeKind::I16),
351        "i32" => Some(TypeKind::I32),
352        "i64" => Some(TypeKind::I64),
353        "fp32" => Some(TypeKind::Fp32),
354        "fp64" => Some(TypeKind::Fp64),
355        "string" => Some(TypeKind::String),
356        "binary" => Some(TypeKind::Binary),
357        "date" => Some(TypeKind::Date),
358        "interval_year" => Some(TypeKind::IntervalYear),
359        "uuid" => Some(TypeKind::Uuid),
360        _ => None,
361    }
362}
363
364/// Parameter type information for type definitions
365#[derive(Clone, Debug, PartialEq)]
366pub enum ParameterConstraint {
367    /// Data type parameter
368    DataType,
369    /// Integer parameter with range constraints
370    Integer {
371        /// Minimum value (inclusive), if specified
372        min: Option<i64>,
373        /// Maximum value (inclusive), if specified
374        max: Option<i64>,
375    },
376    /// Enumeration parameter
377    Enumeration {
378        /// Valid enumeration values (validated, deduplicated)
379        options: ParsedEnumOptions,
380    },
381    /// Boolean parameter
382    Boolean,
383    /// String parameter
384    String,
385}
386
387impl ParameterConstraint {
388    /// Convert back to raw TypeParamDefsItemType
389    fn raw_type(&self) -> TypeParamDefsItemType {
390        match self {
391            ParameterConstraint::DataType => TypeParamDefsItemType::DataType,
392            ParameterConstraint::Boolean => TypeParamDefsItemType::Boolean,
393            ParameterConstraint::Integer { .. } => TypeParamDefsItemType::Integer,
394            ParameterConstraint::Enumeration { .. } => TypeParamDefsItemType::Enumeration,
395            ParameterConstraint::String => TypeParamDefsItemType::String,
396        }
397    }
398
399    /// Extract raw bounds for integer parameters (min, max)
400    fn raw_bounds(&self) -> (Option<f64>, Option<f64>) {
401        match self {
402            ParameterConstraint::Integer { min, max } => {
403                (min.map(|i| i as f64), max.map(|i| i as f64))
404            }
405            _ => (None, None),
406        }
407    }
408
409    /// Extract raw enum options for enumeration parameters
410    fn raw_options(&self) -> Option<RawEnumOptions> {
411        match self {
412            ParameterConstraint::Enumeration { options } => Some(options.clone().into()),
413            _ => None,
414        }
415    }
416
417    /// Check if a parameter value is valid for this parameter type
418    pub fn is_valid_value(&self, value: &Value) -> bool {
419        match (self, value) {
420            (ParameterConstraint::DataType, Value::String(_)) => true,
421            (ParameterConstraint::Integer { min, max }, Value::Number(n)) => {
422                if let Some(i) = n.as_i64() {
423                    min.is_none_or(|min_val| i >= min_val) && max.is_none_or(|max_val| i <= max_val)
424                } else {
425                    false
426                }
427            }
428            (ParameterConstraint::Enumeration { options }, Value::String(s)) => options.contains(s),
429            (ParameterConstraint::Boolean, Value::Bool(_)) => true,
430            (ParameterConstraint::String, Value::String(_)) => true,
431            _ => false,
432        }
433    }
434
435    fn from_raw(
436        t: TypeParamDefsItemType,
437        opts: Option<RawEnumOptions>,
438        min: Option<f64>,
439        max: Option<f64>,
440    ) -> Result<Self, TypeParamError> {
441        Ok(match t {
442            TypeParamDefsItemType::DataType => Self::DataType,
443            TypeParamDefsItemType::Boolean => Self::Boolean,
444            TypeParamDefsItemType::Integer => {
445                match (min, max) {
446                    (Some(min_f), _) if min_f.fract() != 0.0 => {
447                        return Err(TypeParamError::InvalidIntegerBounds { min, max });
448                    }
449                    (_, Some(max_f)) if max_f.fract() != 0.0 => {
450                        return Err(TypeParamError::InvalidIntegerBounds { min, max });
451                    }
452                    _ => (),
453                }
454
455                let min_i = min.map(|v| v as i64);
456                let max_i = max.map(|v| v as i64);
457                Self::Integer {
458                    min: min_i,
459                    max: max_i,
460                }
461            }
462            TypeParamDefsItemType::Enumeration => {
463                let options: ParsedEnumOptions =
464                    opts.ok_or(TypeParamError::MissingEnumOptions)?.try_into()?;
465                Self::Enumeration { options }
466            }
467            TypeParamDefsItemType::String => Self::String,
468        })
469    }
470}
471
472/// A validated type parameter with name and constraints
473#[derive(Clone, Debug, PartialEq)]
474pub struct TypeParam {
475    /// Parameter name (e.g., "K" for a type variable)
476    pub name: String,
477    /// Parameter type constraints
478    pub param_type: ParameterConstraint,
479    /// Human-readable description
480    pub description: Option<String>,
481}
482
483impl TypeParam {
484    /// Create a new type parameter
485    pub fn new(name: String, param_type: ParameterConstraint, description: Option<String>) -> Self {
486        Self {
487            name,
488            param_type,
489            description,
490        }
491    }
492
493    /// Check if a parameter value is valid
494    pub fn is_valid_value(&self, value: &Value) -> bool {
495        self.param_type.is_valid_value(value)
496    }
497}
498
499impl TryFrom<TypeParamDefsItem> for TypeParam {
500    type Error = TypeParamError;
501
502    fn try_from(item: TypeParamDefsItem) -> Result<Self, Self::Error> {
503        let name = item.name.ok_or(TypeParamError::MissingName)?;
504        let param_type =
505            ParameterConstraint::from_raw(item.type_, item.options, item.min, item.max)?;
506
507        Ok(Self {
508            name,
509            param_type,
510            description: item.description,
511        })
512    }
513}
514
515/// Error types for extension type validation
516#[derive(Debug, Error, PartialEq)]
517pub enum ExtensionTypeError {
518    /// Extension type name is invalid
519    #[error("{0}")]
520    InvalidName(#[from] InvalidTypeName),
521    /// Any type variable is invalid for concrete types
522    #[error("Any type variable is invalid for concrete types: any{}{}", id, nullability.then_some("?").unwrap_or(""))]
523    InvalidAnyTypeVariable {
524        /// The type variable name
525        id: u32,
526        /// Whether the type variable is nullable
527        nullability: bool,
528    },
529    /// Type variables or named numeric parameters are not supported in this context.
530    #[error("Type '{type_string}' must be concrete in this context")]
531    ExpectedConcreteType {
532        /// The type expression containing unresolved parameters.
533        type_string: String,
534    },
535    /// Unknown type name (not a builtin, missing u! prefix for extension types)
536    #[error(
537        "Unknown type name: '{}'. Extension types must use the u! prefix (e.g., u!{})",
538        name,
539        name
540    )]
541    UnknownTypeName {
542        /// The unknown type name
543        name: String,
544    },
545    /// Parameter validation failed
546    #[error("Invalid parameter: {0}")]
547    InvalidParameter(#[from] TypeParamError),
548    /// Field type is invalid
549    #[error("Invalid structure field type: {0}")]
550    InvalidFieldType(String),
551    /// Duplicate struct field name
552    #[error("Duplicate struct field '{field_name}'")]
553    DuplicateFieldName {
554        /// The duplicated field name
555        field_name: String,
556    },
557    /// Type parameter count is invalid for the given type name
558    #[error("Type '{type_name}' expects {expected} parameters, got {actual}")]
559    InvalidParameterCount {
560        /// The type name being validated
561        type_name: String,
562        /// Expected number of parameters
563        expected: usize,
564        /// The actual number of parameters provided
565        actual: usize,
566    },
567    /// Type parameter is of the wrong kind for the given position
568    #[error("Type '{type_name}' parameter {index} must be {expected}")]
569    InvalidParameterKind {
570        /// The type name being validated
571        type_name: String,
572        /// Zero-based index of the offending parameter
573        index: usize,
574        /// Expected parameter kind (e.g., integer, type)
575        expected: &'static str,
576    },
577    /// Provided parameter value does not fit within the expected bounds
578    #[error("Type '{type_name}' parameter {index} value {value} is not within {expected}")]
579    InvalidParameterValue {
580        /// The type name being validated
581        type_name: String,
582        /// Zero-based index of the offending parameter
583        index: usize,
584        /// Provided parameter value
585        value: i64,
586        /// Description of the expected range or type
587        expected: &'static str,
588    },
589    /// Provided parameter value does not fit within the expected bounds
590    #[error("Type '{type_name}' parameter {index} value {value} is out of range {expected:?}")]
591    InvalidParameterRange {
592        /// The type name being validated
593        type_name: String,
594        /// Zero-based index of the offending parameter
595        index: usize,
596        /// Provided parameter value
597        value: i64,
598        /// Description of the expected range or type
599        expected: RangeInclusive<i32>,
600    },
601    /// Structure representation cannot be nullable
602    #[error("Structure representation cannot be nullable: {type_string}")]
603    StructureCannotBeNullable {
604        /// The type string that was nullable
605        type_string: String,
606    },
607    /// Error parsing type
608    #[error("Error parsing type: {0}")]
609    ParseType(#[from] TypeParseError),
610}
611
612/// Error types for TypeParam validation
613#[derive(Debug, Error, PartialEq)]
614pub enum TypeParamError {
615    /// Parameter name is missing
616    #[error("Parameter name is required")]
617    MissingName,
618    /// Integer parameter has non-integer min/max values
619    #[error("Integer parameter has invalid min/max values: min={min:?}, max={max:?}")]
620    InvalidIntegerBounds {
621        /// The invalid minimum value
622        min: Option<f64>,
623        /// The invalid maximum value
624        max: Option<f64>,
625    },
626    /// Enumeration parameter is missing options
627    #[error("Enumeration parameter is missing options")]
628    MissingEnumOptions,
629    /// Enumeration parameter has invalid options
630    #[error("Enumeration parameter has invalid options: {0}")]
631    InvalidEnumOptions(#[from] ParsedEnumOptionsError),
632}
633
634/// A validated Simple Extension type definition
635#[derive(Clone, Debug, PartialEq)]
636pub struct CustomType {
637    /// Type name
638    pub name: String,
639    /// Type parameters (e.g., for generic types)
640    pub parameters: Vec<TypeParam>,
641    /// Structure definition, if any
642    pub structure: Option<Type>,
643    /// Whether this type can have variadic parameters
644    pub variadic: Option<bool>,
645    /// Human-readable description
646    pub description: Option<String>,
647}
648
649impl CustomType {
650    /// Check if this type name is valid according to Substrait naming rules
651    /// (see the `Identifier` rule in `substrait/grammar/SubstraitLexer.g4`).
652    /// Identifiers are case-insensitive and must start with a an ASCII letter,
653    /// `_`, or `$`, followed by ASCII letters, digits, `_`, or `$`.
654    //
655    // Note: I'm not sure if `$` is actually something we want to allow, or if
656    // `_` is, but it's in the grammar so I'm allowing it here.
657    pub fn validate_name(name: &str) -> Result<(), InvalidTypeName> {
658        let mut chars = name.chars();
659        let first = chars
660            .next()
661            .ok_or_else(|| InvalidTypeName(name.to_string()))?;
662        if !(first.is_ascii_alphabetic() || first == '_' || first == '$') {
663            return Err(InvalidTypeName(name.to_string()));
664        }
665
666        if !chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$') {
667            return Err(InvalidTypeName(name.to_string()));
668        }
669
670        Ok(())
671    }
672
673    /// Create a new custom type with validation
674    pub fn new(
675        name: String,
676        parameters: Vec<TypeParam>,
677        structure: Option<Type>,
678        variadic: Option<bool>,
679        description: Option<String>,
680    ) -> Result<Self, ExtensionTypeError> {
681        Self::validate_name(&name)?;
682
683        Ok(Self {
684            name,
685            parameters,
686            structure,
687            variadic,
688            description,
689        })
690    }
691}
692
693impl From<CustomType> for SimpleExtensionsTypesItem {
694    fn from(value: CustomType) -> Self {
695        // Convert parameters back to TypeParamDefs if any
696        let parameters = if value.parameters.is_empty() {
697            None
698        } else {
699            Some(TypeParamDefs(
700                value
701                    .parameters
702                    .into_iter()
703                    .map(|param| {
704                        let (min, max) = param.param_type.raw_bounds();
705                        TypeParamDefsItem {
706                            name: Some(param.name),
707                            description: param.description,
708                            type_: param.param_type.raw_type(),
709                            min,
710                            max,
711                            options: param.param_type.raw_options(),
712                            // TODO: add this to TypeParamDefsItem parsing, and
713                            // follow it through here. I'm not entirely sure
714                            // when/if it is used.
715                            optional: None,
716                        }
717                    })
718                    .collect(),
719            ))
720        };
721
722        // Convert structure back to Type if any
723        let structure = value.structure.map(Into::into);
724
725        SimpleExtensionsTypesItem {
726            name: value.name,
727            description: value.description,
728            metadata: Default::default(),
729            parameters,
730            structure,
731            variadic: value.variadic,
732            // Not tracked by `CustomType`; default to no deprecation.
733            deprecated: None,
734        }
735    }
736}
737
738impl Parse<TypeContext> for SimpleExtensionsTypesItem {
739    type Parsed = CustomType;
740    type Error = ExtensionTypeError;
741
742    fn parse(self, ctx: &mut TypeContext) -> Result<Self::Parsed, Self::Error> {
743        let name = self.name;
744        CustomType::validate_name(&name)?;
745
746        // Register this type as found
747        ctx.found(&name);
748
749        let parameters = if let Some(param_defs) = self.parameters {
750            param_defs
751                .0
752                .into_iter()
753                .map(TypeParam::try_from)
754                .collect::<Result<Vec<_>, _>>()?
755        } else {
756            Vec::new()
757        };
758
759        // Parse structure with context, so referenced extension types are recorded as linked
760        let structure = match self.structure {
761            Some(structure_data) => {
762                let parsed = Parse::parse(structure_data, ctx)?;
763                // TODO: check that the structure is valid. The `Type::Object`
764                // form of `structure_data` is by definition a non-nullable `NSTRUCT`; however,
765                // what types allowed under the `Type::String` form is less clear in the spec:
766                // See https://github.com/substrait-io/substrait/issues/920.
767                Some(parsed)
768            }
769            None => None,
770        };
771
772        Ok(CustomType {
773            name,
774            parameters,
775            structure,
776            variadic: self.variadic,
777            description: self.description,
778        })
779    }
780}
781
782impl Parse<TypeContext> for RawType {
783    type Parsed = Type;
784    type Error = ExtensionTypeError;
785
786    fn parse(self, ctx: &mut TypeContext) -> Result<Self::Parsed, Self::Error> {
787        match self {
788            RawType::String(type_string) => {
789                let parsed_type = TypeExpr::parse(&type_string)?;
790                let mut link = |name: &str| ctx.linked(name);
791                parsed_type.visit_references(&mut link);
792                expect_concrete_type(parsed_type)
793            }
794            RawType::Object(field_map) => {
795                // Type structure in Substrait must preserve field order (see
796                // substrait-io/substrait#915). The typify generation uses
797                // IndexMap to retain the YAML order so that the order of the
798                // fields in the structure matches that of the extensions file.
799                let mut fields = IndexMap::new();
800
801                for (field_name, field_type_value) in field_map {
802                    let type_string = match field_type_value {
803                        serde_json::Value::String(s) => s,
804                        _ => {
805                            return Err(ExtensionTypeError::InvalidFieldType(
806                                "Struct field types must be strings".to_string(),
807                            ));
808                        }
809                    };
810
811                    let parsed_field_type = TypeExpr::parse(&type_string)?;
812                    let mut link = |name: &str| ctx.linked(name);
813                    parsed_field_type.visit_references(&mut link);
814                    let field_type = expect_concrete_type(parsed_field_type)?;
815
816                    if fields.insert(field_name.clone(), field_type).is_some() {
817                        return Err(ExtensionTypeError::DuplicateFieldName { field_name });
818                    }
819                }
820
821                Ok(Type {
822                    kind: TypeKind::NamedStruct { fields },
823                    nullable: false,
824                })
825            }
826        }
827    }
828}
829
830/// Invalid type name error
831#[derive(Debug, Error, PartialEq)]
832#[error("invalid type name `{0}`")]
833pub struct InvalidTypeName(String);
834
835/// A type expression with nullability.
836#[derive(Clone, Debug, PartialEq)]
837pub struct Type {
838    /// The type expression
839    pub kind: TypeKind,
840    /// Whether this type is nullable
841    pub nullable: bool,
842}
843
844impl Type {
845    /// Create a type expression with the given nullability.
846    pub fn new(kind: TypeKind, nullable: bool) -> Type {
847        Type { kind, nullable }
848    }
849
850    /// Create a new extension type reference (without parameters)
851    pub fn extension(name: String, nullable: bool) -> Type {
852        Type {
853            kind: TypeKind::Extension {
854                name,
855                parameters: Vec::new(),
856            },
857            nullable,
858        }
859    }
860
861    /// Create a new parameterized extension type
862    pub fn extension_with_params(
863        name: String,
864        parameters: Vec<TypeParameter>,
865        nullable: bool,
866    ) -> Type {
867        Type {
868            kind: TypeKind::Extension { name, parameters },
869            nullable,
870        }
871    }
872
873    /// Create a new list type
874    pub fn list(element_type: Type, nullable: bool) -> Type {
875        Type {
876            kind: TypeKind::List(Box::new(element_type)),
877            nullable,
878        }
879    }
880
881    /// Create a new struct type (ordered fields without names)
882    pub fn r#struct(field_types: Vec<Type>, nullable: bool) -> Type {
883        Type {
884            kind: TypeKind::Struct(field_types),
885            nullable,
886        }
887    }
888
889    /// Create a new map type
890    pub fn map(key_type: Type, value_type: Type, nullable: bool) -> Type {
891        Type {
892            kind: TypeKind::Map {
893                key: Box::new(key_type),
894                value: Box::new(value_type),
895            },
896            nullable,
897        }
898    }
899
900    /// Create a new named struct type (nstruct - ordered fields with names)
901    pub fn named_struct(fields: IndexMap<String, Type>, nullable: bool) -> Type {
902        Type {
903            kind: TypeKind::NamedStruct { fields },
904            nullable,
905        }
906    }
907
908    /// Returns whether this type contains no unresolved variables.
909    pub fn is_concrete(&self) -> bool {
910        match &self.kind {
911            TypeKind::Any | TypeKind::Variable { .. } => false,
912            TypeKind::FixedChar { length }
913            | TypeKind::VarChar { length }
914            | TypeKind::FixedBinary { length } => length.is_concrete(),
915            TypeKind::Decimal { precision, scale } => {
916                precision.is_concrete() && scale.is_concrete()
917            }
918            TypeKind::PrecisionTime { precision }
919            | TypeKind::PrecisionTimestamp { precision }
920            | TypeKind::PrecisionTimestampTz { precision }
921            | TypeKind::IntervalDay { precision }
922            | TypeKind::IntervalCompound { precision } => precision.is_concrete(),
923            TypeKind::List(element) => element.is_concrete(),
924            TypeKind::Map { key, value } => key.is_concrete() && value.is_concrete(),
925            TypeKind::Struct(fields) => fields.iter().all(Type::is_concrete),
926            TypeKind::NamedStruct { fields } => fields.values().all(Type::is_concrete),
927            TypeKind::Func {
928                parameters,
929                return_type,
930            } => parameters.iter().all(Type::is_concrete) && return_type.is_concrete(),
931            TypeKind::Extension { parameters, .. } => {
932                parameters.iter().all(TypeParameter::is_concrete)
933            }
934            _ => true,
935        }
936    }
937
938    /// Check if this type (as a function argument) is compatible with another
939    /// type (as an input).
940    ///
941    /// Does not bind type variables or symbolic parameters.
942    /// Mainly checks nullability:
943    ///   - `i64?` is compatible with `i64` and `i64?` - both can be passed as
944    ///     arguments
945    ///   - `i64` is compatible with `i64` but NOT `i64?` - you can't pass a
946    ///     nullable type to a function that only accepts non-nullable arguments
947    pub fn is_compatible_with(&self, other: &Type) -> bool {
948        // Types must match exactly, but nullable types can accept non-nullable values
949        self.kind == other.kind && (self.nullable || !other.nullable)
950    }
951}
952
953/// Formats full extension-YAML spellings, not function-signature short names.
954impl fmt::Display for Type {
955    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
956        self.kind.fmt_with_nullability(f, self.nullable)
957    }
958}
959
960impl From<Type> for RawType {
961    fn from(val: Type) -> Self {
962        match val.kind {
963            TypeKind::NamedStruct { fields } => RawType::Object(
964                fields
965                    .into_iter()
966                    .map(|(name, ty)| (name, serde_json::Value::String(ty.to_string())))
967                    .collect(),
968            ),
969            _ => RawType::String(val.to_string()),
970        }
971    }
972}
973
974/// Parse an integer literal or parameter name for a built-in type.
975/// Validate literals against the supplied range.
976///
977/// - `type_name`: Type being validated (for error messages, e.g., "DECIMAL")
978/// - `index`: Parameter position (0-based, e.g., 0 for precision, 1 for scale);
979///   needed for error messages
980/// - `param`: The parameter to validate
981/// - `range`: Optional bounds to enforce (e.g., `Some(1..=38)` for precision)
982fn parse_integer_parameter(
983    type_name: &str,
984    index: usize,
985    param: &TypeExprParam<'_>,
986    range: Option<RangeInclusive<i32>>,
987) -> Result<IntegerParameter, ExtensionTypeError> {
988    let value = match param {
989        TypeExprParam::Type(TypeExpr::Simple(name, parameters, false))
990            if parameters.is_empty() && CustomType::validate_name(name).is_ok() =>
991        {
992            return Ok(IntegerParameter::Variable(name.to_string()));
993        }
994        TypeExprParam::Integer(value) => {
995            i32::try_from(*value).map_err(|_| ExtensionTypeError::InvalidParameterValue {
996                type_name: type_name.to_string(),
997                index,
998                value: *value,
999                expected: "an i32",
1000            })
1001        }
1002        _ => Err(ExtensionTypeError::InvalidParameterKind {
1003            type_name: type_name.to_string(),
1004            index,
1005            expected: "an integer",
1006        }),
1007    }?;
1008
1009    if let Some(range) = range {
1010        if range.contains(&value) {
1011            return Ok(IntegerParameter::Literal(i64::from(value)));
1012        }
1013        return Err(ExtensionTypeError::InvalidParameterRange {
1014            type_name: type_name.to_string(),
1015            index,
1016            value: i64::from(value),
1017            expected: range,
1018        });
1019    }
1020
1021    Ok(IntegerParameter::Literal(i64::from(value)))
1022}
1023
1024/// Helper function - checks that param length matches expectations, returns
1025/// error if not. Assumes a fixed number of expected parameters.
1026fn expect_param_len(
1027    type_name: &str,
1028    params: &[TypeExprParam<'_>],
1029    expected: usize,
1030) -> Result<(), ExtensionTypeError> {
1031    if params.len() != expected {
1032        return Err(ExtensionTypeError::InvalidParameterCount {
1033            type_name: type_name.to_string(),
1034            expected,
1035            actual: params.len(),
1036        });
1037    }
1038    Ok(())
1039}
1040
1041/// Parse a concrete type with all parameters fully defined, without type variables
1042/// or named numeric parameters.
1043fn expect_concrete_type(expression: TypeExpr<'_>) -> Result<Type, ExtensionTypeError> {
1044    match expression {
1045        TypeExpr::TypeVariable(id, nullability) => {
1046            Err(ExtensionTypeError::InvalidAnyTypeVariable { id, nullability })
1047        }
1048        expression => {
1049            let parsed = Type::try_from(expression)?;
1050            if parsed.is_concrete() {
1051                Ok(parsed)
1052            } else {
1053                Err(ExtensionTypeError::ExpectedConcreteType {
1054                    type_string: parsed.to_string(),
1055                })
1056            }
1057        }
1058    }
1059}
1060
1061/// Helper function - expect a type parameter, and return the [Type] if it is a [TypeExpr]
1062/// or an error if not.
1063fn expect_type_argument<'a>(
1064    type_name: &str,
1065    index: usize,
1066    param: TypeExprParam<'a>,
1067) -> Result<Type, ExtensionTypeError> {
1068    match param {
1069        TypeExprParam::Type(t) => Type::try_from(t),
1070        TypeExprParam::Integer(_) => Err(ExtensionTypeError::InvalidParameterKind {
1071            type_name: type_name.to_string(),
1072            index,
1073            expected: "a type",
1074        }),
1075    }
1076}
1077
1078impl<'a> TryFrom<TypeExprParam<'a>> for TypeParameter {
1079    type Error = ExtensionTypeError;
1080
1081    fn try_from(param: TypeExprParam<'a>) -> Result<Self, Self::Error> {
1082        Ok(match param {
1083            TypeExprParam::Integer(v) => TypeParameter::Integer(IntegerParameter::Literal(v)),
1084            TypeExprParam::Type(expression) => {
1085                let is_bare_name = matches!(
1086                    &expression,
1087                    TypeExpr::Simple(_, parameters, false) if parameters.is_empty()
1088                );
1089                match Type::try_from(expression) {
1090                    Err(ExtensionTypeError::UnknownTypeName { name }) if is_bare_name => {
1091                        CustomType::validate_name(&name)?;
1092                        TypeParameter::Name(name)
1093                    }
1094                    result => TypeParameter::Type(result?),
1095                }
1096            }
1097        })
1098    }
1099}
1100
1101/// The tightest provable upper bound for a decimal scale, per the
1102/// [decimal type constraints](https://substrait.io/types/type_classes/#compound-types).
1103fn max_literal_scale(precision: &IntegerParameter) -> i32 {
1104    match precision {
1105        IntegerParameter::Literal(precision) => *precision as i32,
1106        // A named precision is at most the maximum decimal precision.
1107        IntegerParameter::Variable(_) => 38,
1108    }
1109}
1110
1111/// Parse a builtin type. Returns an `ExtensionTypeError` if the type name is
1112/// matched, but parameters are incorrect; returns `Some(None)` if the type is
1113/// not known.
1114fn parse_builtin<'a>(
1115    display_name: &str,
1116    lower_name: &str,
1117    params: &[TypeExprParam<'a>],
1118) -> Result<Option<TypeKind>, ExtensionTypeError> {
1119    if let Some(builtin) = primitive_builtin(lower_name) {
1120        expect_param_len(display_name, params, 0)?;
1121        return Ok(Some(builtin));
1122    }
1123
1124    match lower_name {
1125        // Parameterized builtins
1126        "fixedchar" => {
1127            expect_param_len(display_name, params, 1)?;
1128            let length = parse_integer_parameter(display_name, 0, &params[0], None)?;
1129            Ok(Some(TypeKind::FixedChar { length }))
1130        }
1131        "varchar" => {
1132            expect_param_len(display_name, params, 1)?;
1133            let length = parse_integer_parameter(display_name, 0, &params[0], None)?;
1134            Ok(Some(TypeKind::VarChar { length }))
1135        }
1136        "fixedbinary" => {
1137            expect_param_len(display_name, params, 1)?;
1138            let length = parse_integer_parameter(display_name, 0, &params[0], None)?;
1139            Ok(Some(TypeKind::FixedBinary { length }))
1140        }
1141        "decimal" => {
1142            expect_param_len(display_name, params, 2)?;
1143            let precision = parse_integer_parameter(display_name, 0, &params[0], Some(1..=38))?;
1144            let scale = parse_integer_parameter(
1145                display_name,
1146                1,
1147                &params[1],
1148                Some(0..=max_literal_scale(&precision)),
1149            )?;
1150            Ok(Some(TypeKind::Decimal { precision, scale }))
1151        }
1152        "precision_time" => {
1153            expect_param_len(display_name, params, 1)?;
1154            let precision = parse_integer_parameter(display_name, 0, &params[0], Some(0..=12))?;
1155            Ok(Some(TypeKind::PrecisionTime { precision }))
1156        }
1157        "precision_timestamp" => {
1158            expect_param_len(display_name, params, 1)?;
1159            let precision = parse_integer_parameter(display_name, 0, &params[0], Some(0..=12))?;
1160            Ok(Some(TypeKind::PrecisionTimestamp { precision }))
1161        }
1162        "precision_timestamp_tz" => {
1163            expect_param_len(display_name, params, 1)?;
1164            let precision = parse_integer_parameter(display_name, 0, &params[0], Some(0..=12))?;
1165            Ok(Some(TypeKind::PrecisionTimestampTz { precision }))
1166        }
1167        "interval_day" => {
1168            expect_param_len(display_name, params, 1)?;
1169            let precision = parse_integer_parameter(display_name, 0, &params[0], Some(0..=9))?;
1170            Ok(Some(TypeKind::IntervalDay { precision }))
1171        }
1172        "interval_compound" => {
1173            expect_param_len(display_name, params, 1)?;
1174            let precision = parse_integer_parameter(display_name, 0, &params[0], None)?;
1175            Ok(Some(TypeKind::IntervalCompound { precision }))
1176        }
1177        _ => Ok(None),
1178    }
1179}
1180
1181impl<'a> TryFrom<TypeExpr<'a>> for Type {
1182    type Error = ExtensionTypeError;
1183
1184    fn try_from(parsed_type: TypeExpr<'a>) -> Result<Self, Self::Error> {
1185        match parsed_type {
1186            TypeExpr::Func(parameters, return_type, nullable) => {
1187                let parameters = parameters
1188                    .into_iter()
1189                    .enumerate()
1190                    .map(|(index, parameter)| expect_type_argument("func", index, parameter))
1191                    .collect::<Result<Vec<_>, _>>()?;
1192                let return_type = Box::new(Type::try_from(*return_type)?);
1193                Ok(Type::new(
1194                    TypeKind::Func {
1195                        parameters,
1196                        return_type,
1197                    },
1198                    nullable,
1199                ))
1200            }
1201            TypeExpr::Simple(name, params, nullable) => {
1202                let lower = name.to_ascii_lowercase();
1203
1204                match lower.as_str() {
1205                    "any" => {
1206                        expect_param_len(name, &params, 0)?;
1207                        return Ok(Type::new(TypeKind::Any, nullable));
1208                    }
1209                    "list" => {
1210                        expect_param_len(name, &params, 1)?;
1211                        let element =
1212                            expect_type_argument(name, 0, params.into_iter().next().unwrap())?;
1213                        return Ok(Type::list(element, nullable));
1214                    }
1215                    "map" => {
1216                        expect_param_len(name, &params, 2)?;
1217                        let mut iter = params.into_iter();
1218                        let key = expect_type_argument(name, 0, iter.next().unwrap())?;
1219                        let value = expect_type_argument(name, 1, iter.next().unwrap())?;
1220                        return Ok(Type::map(key, value, nullable));
1221                    }
1222                    "struct" => {
1223                        let field_types = params
1224                            .into_iter()
1225                            .enumerate()
1226                            .map(|(idx, param)| expect_type_argument(name, idx, param))
1227                            .collect::<Result<Vec<_>, _>>()?;
1228                        return Ok(Type::r#struct(field_types, nullable));
1229                    }
1230                    _ => {}
1231                }
1232
1233                if let Some(builtin) = parse_builtin(name, lower.as_str(), &params)? {
1234                    return Ok(Type::new(builtin, nullable));
1235                }
1236
1237                // Simple types that aren't builtins are unknown
1238                // Extension types MUST use the u! prefix
1239                Err(ExtensionTypeError::UnknownTypeName {
1240                    name: name.to_string(),
1241                })
1242            }
1243            TypeExpr::UserDefined(name, params, nullable) => {
1244                let parameters = params
1245                    .into_iter()
1246                    .map(TypeParameter::try_from)
1247                    .collect::<Result<Vec<_>, _>>()?;
1248                Ok(Type::extension_with_params(
1249                    name.to_string(),
1250                    parameters,
1251                    nullable,
1252                ))
1253            }
1254            TypeExpr::TypeVariable(id, nullable) => {
1255                Ok(Type::new(TypeKind::Variable { id }, nullable))
1256            }
1257        }
1258    }
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263    use super::super::extensions::TypeContext;
1264    use super::*;
1265    use crate::parse::text::simple_extensions::TypeExpr;
1266    use crate::parse::text::simple_extensions::argument::EnumOptions as ParsedEnumOptions;
1267    use crate::text::simple_extensions;
1268    use std::iter::FromIterator;
1269
1270    /// Wrap a type kind without a nullable annotation.
1271    fn required_type(kind: TypeKind) -> Type {
1272        Type::new(kind, false)
1273    }
1274
1275    /// Parse a string into a [Type]
1276    fn parse_type(expr: &str) -> Type {
1277        let parsed = TypeExpr::parse(expr).unwrap();
1278        Type::try_from(parsed).unwrap()
1279    }
1280
1281    /// Parse a string into a [Type], returning the result
1282    fn parse_type_result(expr: &str) -> Result<Type, ExtensionTypeError> {
1283        let parsed = TypeExpr::parse(expr).unwrap();
1284        Type::try_from(parsed)
1285    }
1286
1287    /// Parse a string into a builtin [Type], with no unresolved
1288    /// extension references
1289    fn parse_simple(s: &str) -> Type {
1290        let parsed = TypeExpr::parse(s).unwrap();
1291
1292        let mut refs = Vec::new();
1293        parsed.visit_references(&mut |name| refs.push(name.to_string()));
1294        assert!(refs.is_empty(), "{s} should not add an extension reference");
1295
1296        Type::try_from(parsed).unwrap()
1297    }
1298
1299    #[test]
1300    fn test_literal_and_symbolic_lengths_share_a_type_kind() {
1301        for (length, expected, is_concrete) in [
1302            (IntegerParameter::Literal(20), "varchar<20>", true),
1303            (IntegerParameter::Variable("L".into()), "varchar<L>", false),
1304        ] {
1305            let type_ = Type::new(
1306                TypeKind::VarChar {
1307                    length: length.clone(),
1308                },
1309                false,
1310            );
1311            assert_eq!(type_.to_string(), expected);
1312            assert_eq!(type_.is_concrete(), is_concrete);
1313
1314            let extension = Type::extension_with_params(
1315                "sized".into(),
1316                vec![TypeParameter::Integer(length)],
1317                false,
1318            );
1319            assert_eq!(extension.is_concrete(), is_concrete);
1320        }
1321    }
1322
1323    #[test]
1324    fn test_concreteness_is_recursive() {
1325        let variable = parse_type("any1?");
1326        assert_eq!(variable.to_string(), "any1?");
1327        for field in [
1328            parse_type("i32"),
1329            variable,
1330            Type::new(
1331                TypeKind::VarChar {
1332                    length: IntegerParameter::Variable("L".into()),
1333                },
1334                false,
1335            ),
1336        ] {
1337            let expected = field.is_concrete();
1338            for nested in [
1339                Type::list(field.clone(), false),
1340                Type::map(parse_type("string"), field.clone(), false),
1341                Type::r#struct(vec![field.clone()], false),
1342                Type::named_struct(IndexMap::from([("field".into(), field.clone())]), false),
1343                Type::extension_with_params(
1344                    "wrapper".into(),
1345                    vec![TypeParameter::Type(field)],
1346                    false,
1347                ),
1348            ] {
1349                assert_eq!(
1350                    nested.is_concrete(),
1351                    expected,
1352                    "unexpected concreteness for {nested}"
1353                );
1354            }
1355        }
1356    }
1357
1358    #[test]
1359    fn test_concreteness_does_not_replace_value_validation() {
1360        let type_ = Type::new(
1361            TypeKind::Decimal {
1362                precision: IntegerParameter::Literal(10),
1363                scale: IntegerParameter::Literal(20),
1364            },
1365            false,
1366        );
1367        assert!(type_.is_concrete());
1368        assert!(matches!(
1369            parse_type_result(&type_.to_string()),
1370            Err(ExtensionTypeError::InvalidParameterRange { .. })
1371        ));
1372    }
1373
1374    #[test]
1375    fn test_structure_parsing_still_requires_concrete_types() {
1376        for raw in [
1377            RawType::String("any1?".into()),
1378            RawType::Object(IndexMap::from([(
1379                "field".into(),
1380                Value::String("any1?".into()),
1381            )])),
1382        ] {
1383            assert!(matches!(
1384                raw.parse(&mut TypeContext::default()),
1385                Err(ExtensionTypeError::InvalidAnyTypeVariable {
1386                    id: 1,
1387                    nullability: true
1388                })
1389            ));
1390        }
1391    }
1392
1393    #[test]
1394    fn test_named_numeric_parameters_remain_unsupported_in_type_structures() {
1395        for raw in [
1396            RawType::String("varchar<L>".into()),
1397            RawType::Object(IndexMap::from([(
1398                "field".into(),
1399                Value::String("decimal<P, S>".into()),
1400            )])),
1401        ] {
1402            assert!(matches!(
1403                raw.parse(&mut TypeContext::default()),
1404                Err(ExtensionTypeError::ExpectedConcreteType { .. })
1405            ));
1406        }
1407    }
1408
1409    #[test]
1410    fn test_extension_integer_parameters_preserve_i64_range() {
1411        let input = format!("u!wide<{}>", i64::MAX);
1412        let parsed = parse_type(&input);
1413        assert!(parsed.is_concrete());
1414        assert_eq!(parsed.to_string(), input);
1415        assert!(matches!(
1416            parse_type_result(&format!("varchar<{}>", i64::MAX)),
1417            Err(ExtensionTypeError::InvalidParameterValue { .. })
1418        ));
1419    }
1420
1421    /// Create a type parameter from a type expression string
1422    fn type_param(expr: &str) -> TypeParameter {
1423        TypeParameter::Type(parse_type(expr))
1424    }
1425
1426    /// Create an extension type
1427    fn extension(name: &str, parameters: Vec<TypeParameter>, nullable: bool) -> Type {
1428        Type::extension_with_params(name.to_string(), parameters, nullable)
1429    }
1430
1431    /// Convert a custom type to raw and back, ensuring round-trip consistency
1432    fn round_trip(custom: &CustomType) {
1433        let item: simple_extensions::SimpleExtensionsTypesItem = custom.clone().into();
1434        let mut ctx = TypeContext::default();
1435        let parsed = Parse::parse(item, &mut ctx).unwrap();
1436        assert_eq!(&parsed, custom);
1437    }
1438
1439    /// Create a raw named struct (e.g. straight from YAML) from field name and
1440    /// type pairs
1441    fn raw_named_struct(fields: &[(&str, &str)]) -> RawType {
1442        RawType::Object(
1443            fields
1444                .iter()
1445                .map(|(name, ty)| ((*name).into(), serde_json::Value::String((*ty).into())))
1446                .collect(),
1447        )
1448    }
1449
1450    #[test]
1451    fn test_builtin_scalar_parsing() {
1452        let cases = vec![
1453            ("bool", Some(TypeKind::Boolean)),
1454            ("i8", Some(TypeKind::I8)),
1455            ("i16", Some(TypeKind::I16)),
1456            ("i32", Some(TypeKind::I32)),
1457            ("i64", Some(TypeKind::I64)),
1458            ("fp32", Some(TypeKind::Fp32)),
1459            ("fp64", Some(TypeKind::Fp64)),
1460            ("STRING", Some(TypeKind::String)),
1461            ("binary", Some(TypeKind::Binary)),
1462            ("uuid", Some(TypeKind::Uuid)),
1463            ("date", Some(TypeKind::Date)),
1464            ("interval_year", Some(TypeKind::IntervalYear)),
1465            ("invalid", None),
1466        ];
1467
1468        for (input, expected) in cases {
1469            let result = parse_builtin(input, input.to_ascii_lowercase().as_str(), &[]).unwrap();
1470            match expected {
1471                Some(expected_type) => {
1472                    assert_eq!(
1473                        result,
1474                        Some(expected_type),
1475                        "expected builtin type for {input}"
1476                    );
1477                }
1478                None => {
1479                    assert!(result.is_none(), "expected parsing {input} to fail");
1480                }
1481            }
1482        }
1483    }
1484
1485    #[test]
1486    fn test_nested_symbolic_types() {
1487        assert_eq!(
1488            parse_type("List<any>"),
1489            Type::list(required_type(TypeKind::Any), false)
1490        );
1491        assert_eq!(
1492            parse_type("Map<string, any?>"),
1493            Type::map(parse_type("string"), Type::new(TypeKind::Any, true), false)
1494        );
1495        assert_eq!(
1496            parse_type("List?<DECIMAL<P, S>>"),
1497            Type::list(parse_type("decimal<P, S>"), true)
1498        );
1499        assert_eq!(
1500            parse_type("Map<string, precision_timestamp<P>>"),
1501            Type::map(
1502                parse_type("string"),
1503                parse_type("precision_timestamp<P>"),
1504                false
1505            )
1506        );
1507        assert_eq!(
1508            parse_type("Struct?<any1, List<Map<string, VARCHAR<L>>>>"),
1509            Type::r#struct(
1510                vec![
1511                    parse_type("any1"),
1512                    parse_type("List<Map<string, VARCHAR<L>>>")
1513                ],
1514                true
1515            )
1516        );
1517        assert_eq!(parse_type("any").to_string(), "any");
1518        assert_eq!(parse_type("Any?").to_string(), "any?");
1519    }
1520
1521    #[test]
1522    fn test_nested_type_concreteness_follows_elements() {
1523        assert!(parse_type("list<decimal<10, 2>>").is_concrete());
1524        assert!(!parse_type("list<any>").is_concrete());
1525    }
1526
1527    #[test]
1528    fn test_function_types() {
1529        for (input, expected) in [
1530            ("func<i32 -> i64>", "func<i32 -> i64>"),
1531            ("FUNC?<any1 -> any2>", "func?<any1 -> any2>"),
1532            (
1533                "func<(i32, list<any1>) -> i64?>",
1534                "func<(i32, list<any1>) -> i64?>",
1535            ),
1536            (
1537                "func<i32 -> func<i64 -> boolean?>>",
1538                "func<i32 -> func<i64 -> bool?>>",
1539            ),
1540        ] {
1541            let ty = parse_type(input);
1542            assert_eq!(ty.to_string(), expected, "unexpected spelling for {input}");
1543            assert_eq!(
1544                parse_type(&ty.to_string()),
1545                ty,
1546                "round trip failed for {input}"
1547            );
1548        }
1549
1550        assert!(parse_type("func<i32 -> i64>").is_concrete());
1551        assert!(!parse_type("func<any1 -> i64>").is_concrete());
1552        assert!(!parse_type("func<i32 -> any1>").is_concrete());
1553    }
1554
1555    #[test]
1556    fn test_function_parameter_can_be_a_function() {
1557        assert_eq!(
1558            parse_type("func<func<i32 -> i64> -> bool>"),
1559            required_type(TypeKind::Func {
1560                parameters: vec![parse_type("func<i32 -> i64>")],
1561                return_type: Box::new(parse_type("bool")),
1562            }),
1563        );
1564    }
1565
1566    #[test]
1567    fn test_list_elements_can_be_functions() {
1568        assert_eq!(
1569            parse_type("list<func<i32 -> i64>>"),
1570            Type::list(parse_type("func<i32 -> i64>"), false),
1571        );
1572    }
1573
1574    #[test]
1575    fn test_function_type_validation() {
1576        for input in ["func<5 -> i64>", "func<i32 -> missing>"] {
1577            assert!(
1578                parse_type_result(input).is_err(),
1579                "{input} should not be accepted as a signature type"
1580            );
1581        }
1582    }
1583
1584    #[test]
1585    fn test_symbolic_user_defined_type_parameters() {
1586        let ty = parse_type("u!Box?<List<decimal<P, S>>, 5, N>");
1587        assert_eq!(
1588            ty,
1589            Type::extension_with_params(
1590                "Box".to_string(),
1591                vec![
1592                    TypeParameter::Type(parse_type("List<decimal<P, S>>")),
1593                    TypeParameter::Integer(IntegerParameter::Literal(5)),
1594                    TypeParameter::Name("N".to_string()),
1595                ],
1596                true,
1597            )
1598        );
1599        assert!(!ty.is_concrete());
1600        assert!(!parse_type("u!Box<5, N>").is_concrete());
1601        assert_eq!(ty.to_string(), "u!Box?<list<decimal<P, S>>, 5, N>");
1602    }
1603
1604    #[test]
1605    fn test_invalid_nested_signature_types() {
1606        for input in [
1607            "List<any1, any2>",
1608            "Map<any1>",
1609            "Struct<1, any1>",
1610            "List<missing_type?>",
1611            "List<decimal<any1>>",
1612            "List<i32<P>>",
1613        ] {
1614            assert!(
1615                Type::try_from(TypeExpr::parse(input).unwrap()).is_err(),
1616                "{input} should not be accepted as a signature type"
1617            );
1618        }
1619    }
1620
1621    #[test]
1622    fn test_named_integer_parameters() {
1623        let parameter = IntegerParameter::Variable("N".to_string());
1624        for (name, expected) in [
1625            (
1626                "fixedchar",
1627                TypeKind::FixedChar {
1628                    length: parameter.clone(),
1629                },
1630            ),
1631            (
1632                "varchar",
1633                TypeKind::VarChar {
1634                    length: parameter.clone(),
1635                },
1636            ),
1637            (
1638                "fixedbinary",
1639                TypeKind::FixedBinary {
1640                    length: parameter.clone(),
1641                },
1642            ),
1643            (
1644                "precision_time",
1645                TypeKind::PrecisionTime {
1646                    precision: parameter.clone(),
1647                },
1648            ),
1649            (
1650                "precision_timestamp",
1651                TypeKind::PrecisionTimestamp {
1652                    precision: parameter.clone(),
1653                },
1654            ),
1655            (
1656                "precision_timestamp_tz",
1657                TypeKind::PrecisionTimestampTz {
1658                    precision: parameter.clone(),
1659                },
1660            ),
1661            (
1662                "interval_day",
1663                TypeKind::IntervalDay {
1664                    precision: parameter.clone(),
1665                },
1666            ),
1667            (
1668                "interval_compound",
1669                TypeKind::IntervalCompound {
1670                    precision: parameter,
1671                },
1672            ),
1673        ] {
1674            let input = format!("{}?<N>", name.to_ascii_uppercase());
1675            let expression = Type::try_from(TypeExpr::parse(&input).unwrap()).unwrap();
1676            assert_eq!(
1677                expression,
1678                Type::new(expected, true),
1679                "unexpected type for {input}"
1680            );
1681        }
1682
1683        for (input, precision, scale) in [
1684            (
1685                "decimal<P, S>",
1686                IntegerParameter::Variable("P".into()),
1687                IntegerParameter::Variable("S".into()),
1688            ),
1689            (
1690                "decimal<10, S>",
1691                IntegerParameter::Literal(10),
1692                IntegerParameter::Variable("S".into()),
1693            ),
1694            (
1695                "decimal<P, 2>",
1696                IntegerParameter::Variable("P".into()),
1697                IntegerParameter::Literal(2),
1698            ),
1699        ] {
1700            assert_eq!(
1701                Type::try_from(TypeExpr::parse(input).unwrap()).unwrap(),
1702                Type::new(TypeKind::Decimal { precision, scale }, false)
1703            );
1704        }
1705    }
1706
1707    #[test]
1708    fn test_named_numeric_parameters_preserve_builtin_validation() {
1709        for input in [
1710            "i32<P>",
1711            "varchar<L, 2>",
1712            "decimal<any1, S>",
1713            "decimal<P?, S>",
1714            "decimal<P<1>, S>",
1715            "decimal<u!P, S>",
1716            "decimal<39, S>",
1717            "decimal<P, 39>",
1718            "precisiontime<P>",
1719            "missing<P>",
1720        ] {
1721            assert!(
1722                Type::try_from(TypeExpr::parse(input).unwrap()).is_err(),
1723                "{input} should not be accepted as a signature type"
1724            );
1725        }
1726    }
1727
1728    #[test]
1729    fn test_parameterized_builtin_types() {
1730        let cases = vec![
1731            (
1732                "precision_time<2>",
1733                required_type(TypeKind::PrecisionTime {
1734                    precision: IntegerParameter::Literal(2),
1735                }),
1736            ),
1737            (
1738                "precision_timestamp<1>",
1739                required_type(TypeKind::PrecisionTimestamp {
1740                    precision: IntegerParameter::Literal(1),
1741                }),
1742            ),
1743            (
1744                "precision_timestamp_tz<5>",
1745                required_type(TypeKind::PrecisionTimestampTz {
1746                    precision: IntegerParameter::Literal(5),
1747                }),
1748            ),
1749            (
1750                "DECIMAL<10,2>",
1751                required_type(TypeKind::Decimal {
1752                    precision: IntegerParameter::Literal(10),
1753                    scale: IntegerParameter::Literal(2),
1754                }),
1755            ),
1756            (
1757                "fixedchar<12>",
1758                required_type(TypeKind::FixedChar {
1759                    length: IntegerParameter::Literal(12),
1760                }),
1761            ),
1762            (
1763                "VarChar<42>",
1764                required_type(TypeKind::VarChar {
1765                    length: IntegerParameter::Literal(42),
1766                }),
1767            ),
1768            (
1769                "fixedbinary<8>",
1770                required_type(TypeKind::FixedBinary {
1771                    length: IntegerParameter::Literal(8),
1772                }),
1773            ),
1774            (
1775                "interval_day<7>",
1776                required_type(TypeKind::IntervalDay {
1777                    precision: IntegerParameter::Literal(7),
1778                }),
1779            ),
1780            (
1781                "interval_compound<6>",
1782                required_type(TypeKind::IntervalCompound {
1783                    precision: IntegerParameter::Literal(6),
1784                }),
1785            ),
1786        ];
1787
1788        for (expr, expected) in cases {
1789            let found = parse_simple(expr);
1790            assert_eq!(found, expected, "unexpected type for {expr}");
1791        }
1792    }
1793
1794    #[test]
1795    fn test_parameterized_builtin_range_errors() {
1796        use ExtensionTypeError::InvalidParameterRange;
1797
1798        let cases = vec![
1799            ("precision_time<13>", "precision_time", 0, 13, 0..=12),
1800            ("precision_time<-1>", "precision_time", 0, -1, 0..=12),
1801            (
1802                "precision_timestamp<13>",
1803                "precision_timestamp",
1804                0,
1805                13,
1806                0..=12,
1807            ),
1808            (
1809                "precision_timestamp<-1>",
1810                "precision_timestamp",
1811                0,
1812                -1,
1813                0..=12,
1814            ),
1815            (
1816                "precision_timestamp_tz<20>",
1817                "precision_timestamp_tz",
1818                0,
1819                20,
1820                0..=12,
1821            ),
1822            ("interval_day<10>", "interval_day", 0, 10, 0..=9),
1823            ("DECIMAL<39,0>", "DECIMAL", 0, 39, 1..=38),
1824            ("DECIMAL<0,0>", "DECIMAL", 0, 0, 1..=38),
1825            ("DECIMAL<10,-1>", "DECIMAL", 1, -1, 0..=10),
1826            ("DECIMAL<10,12>", "DECIMAL", 1, 12, 0..=10),
1827        ];
1828
1829        for (expr, expected_type, expected_index, expected_value, expected_range) in cases {
1830            match parse_type_result(expr) {
1831                Ok(value) => panic!("expected error parsing {expr}, got {value:?}"),
1832                Err(InvalidParameterRange {
1833                    type_name,
1834                    index,
1835                    value,
1836                    expected,
1837                }) => {
1838                    assert_eq!(type_name, expected_type, "unexpected type for {expr}");
1839                    assert_eq!(index, expected_index, "unexpected index for {expr}");
1840                    assert_eq!(
1841                        value,
1842                        i64::from(expected_value),
1843                        "unexpected value for {expr}"
1844                    );
1845                    assert_eq!(expected, expected_range, "unexpected range for {expr}");
1846                }
1847                Err(other) => panic!("expected InvalidParameterRange for {expr}, got {other:?}"),
1848            }
1849        }
1850    }
1851
1852    #[test]
1853    fn test_container_types() {
1854        let cases = vec![
1855            (
1856                "List<i32>",
1857                Type::list(Type::new(TypeKind::I32, false), false),
1858            ),
1859            (
1860                "List<fp64?>",
1861                Type::list(Type::new(TypeKind::Fp64, true), false),
1862            ),
1863            (
1864                "Map?<i64, string?>",
1865                Type::map(
1866                    Type::new(TypeKind::I64, false),
1867                    Type::new(TypeKind::String, true),
1868                    true,
1869                ),
1870            ),
1871            (
1872                "Struct?<i8, string?>",
1873                Type::r#struct(
1874                    vec![
1875                        Type::new(TypeKind::I8, false),
1876                        Type::new(TypeKind::String, true),
1877                    ],
1878                    true,
1879                ),
1880            ),
1881        ];
1882
1883        for (expr, expected) in cases {
1884            assert_eq!(parse_type(expr), expected, "unexpected parse for {expr}");
1885        }
1886    }
1887
1888    #[test]
1889    fn test_extension_types() {
1890        let cases = vec![
1891            (
1892                "u!geo<List<i32>, 10>",
1893                extension(
1894                    "geo",
1895                    vec![
1896                        type_param("List<i32>"),
1897                        TypeParameter::Integer(IntegerParameter::Literal(10)),
1898                    ],
1899                    false,
1900                ),
1901            ),
1902            (
1903                "u!Geo?<List<i32?>>",
1904                extension("Geo", vec![type_param("List<i32?>")], true),
1905            ),
1906            (
1907                "u!Custom<string?, bool>",
1908                extension(
1909                    "Custom",
1910                    vec![
1911                        type_param("string?"),
1912                        TypeParameter::Type(Type::new(TypeKind::Boolean, false)),
1913                    ],
1914                    false,
1915                ),
1916            ),
1917        ];
1918
1919        for (expr, expected) in cases {
1920            assert_eq!(
1921                parse_type(expr),
1922                expected,
1923                "unexpected extension for {expr}"
1924            );
1925        }
1926    }
1927
1928    #[test]
1929    fn test_parameter_type_validation() {
1930        let int_param = ParameterConstraint::Integer {
1931            min: Some(1),
1932            max: Some(10),
1933        };
1934        let enum_param = ParameterConstraint::Enumeration {
1935            options: ParsedEnumOptions::try_from(simple_extensions::EnumOptions(vec![
1936                "OVERFLOW".to_string(),
1937                "ERROR".to_string(),
1938            ]))
1939            .unwrap(),
1940        };
1941
1942        let cases = vec![
1943            (&int_param, Value::Number(5.into()), true),
1944            (&int_param, Value::Number(0.into()), false),
1945            (&int_param, Value::Number(11.into()), false),
1946            (&int_param, Value::String("not a number".into()), false),
1947            (&enum_param, Value::String("OVERFLOW".into()), true),
1948            (&enum_param, Value::String("INVALID".into()), false),
1949        ];
1950
1951        for (param, value, expected) in cases {
1952            assert_eq!(
1953                param.is_valid_value(&value),
1954                expected,
1955                "unexpected validation result for {value:?}"
1956            );
1957        }
1958    }
1959
1960    #[test]
1961    fn test_builtin_type_display_lowercase() {
1962        for input in [
1963            "I32",
1964            "FIXEDCHAR<5>",
1965            "VARCHAR<10>",
1966            "FIXEDBINARY<8>",
1967            "DeCiMaL<10, 2>",
1968            "PRECISION_TIME<3>",
1969            "PRECISION_TIMESTAMP<3>",
1970            "PRECISION_TIMESTAMP_TZ<3>",
1971            "INTERVAL_DAY<3>",
1972            "INTERVAL_COMPOUND<3>",
1973        ] {
1974            let concrete = parse_type(input);
1975            assert_eq!(parse_type(&input.to_ascii_lowercase()), concrete);
1976
1977            let output = concrete.to_string();
1978            assert_eq!(output, output.to_ascii_lowercase(), "display for {input}");
1979        }
1980    }
1981
1982    #[test]
1983    fn test_precision_types_require_underscores() {
1984        for name in [
1985            "precisiontime",
1986            "precisiontimestamp",
1987            "precisiontimestamptz",
1988        ] {
1989            for spelling in [name.to_string(), name.to_ascii_uppercase()] {
1990                assert!(!TypeKind::is_name(&spelling));
1991                assert_eq!(
1992                    parse_type_result(&format!("{spelling}<3>")),
1993                    Err(ExtensionTypeError::UnknownTypeName { name: spelling })
1994                );
1995            }
1996        }
1997    }
1998
1999    #[test]
2000    fn test_precision_type_round_trip_display() {
2001        for (input, expected) in [
2002            ("PRECISION_TIME<3>", "precision_time<3>"),
2003            ("PRECISION_TIMESTAMP<3>", "precision_timestamp<3>"),
2004            ("PRECISION_TIMESTAMP_TZ<3>", "precision_timestamp_tz<3>"),
2005        ] {
2006            let concrete = parse_type(input);
2007            let output = concrete.to_string();
2008            assert_eq!(output, expected);
2009            assert_eq!(parse_type(&output), concrete);
2010
2011            let raw: RawType = concrete.clone().into();
2012            let mut ctx = TypeContext::default();
2013            assert_eq!(raw.parse(&mut ctx).unwrap(), concrete);
2014        }
2015    }
2016
2017    #[test]
2018    fn test_type_round_trip_display() {
2019        // (example, canonical form)
2020        let cases = vec![
2021            ("i32", "i32"),
2022            ("I64?", "i64?"),
2023            ("list<string>", "list<string>"),
2024            ("List<STRING?>", "list<string?>"),
2025            ("map<i32, list<string>>", "map<i32, list<string>>"),
2026            ("struct<i8, string?>", "struct<i8, string?>"),
2027            (
2028                "Struct<List<i32>, Map<string, list<i64?>>>",
2029                "struct<list<i32>, map<string, list<i64?>>>",
2030            ),
2031            (
2032                "Map<List<I32?>, Struct<string, list<i64?>>>",
2033                "map<list<i32?>, struct<string, list<i64?>>>",
2034            ),
2035            ("List?<i32>", "list?<i32>"),
2036            ("Map?<string, List?<i32?>>", "map?<string, list?<i32?>>"),
2037            ("Struct?<i32, string?>", "struct?<i32, string?>"),
2038            ("struct<>", "struct<>"),
2039            ("FIXEDCHAR?<5>", "fixedchar?<5>"),
2040            ("VARCHAR?<10>", "varchar?<10>"),
2041            ("FIXEDBINARY?<8>", "fixedbinary?<8>"),
2042            ("DeCiMaL?<10,2>", "decimal?<10, 2>"),
2043            ("precision_time?<3>", "precision_time?<3>"),
2044            ("precision_timestamp?<3>", "precision_timestamp?<3>"),
2045            ("precision_timestamp_tz?<3>", "precision_timestamp_tz?<3>"),
2046            ("InTeRvAl_DaY?<3>", "interval_day?<3>"),
2047            ("INTERVAL_COMPOUND?<3>", "interval_compound?<3>"),
2048            ("u!geometry?", "u!geometry?"),
2049            ("u!CustomType?<i32>", "u!CustomType?<i32>"),
2050            ("u!custom<i32>", "u!custom<i32>"),
2051            ("u!custom?<i32, 5>", "u!custom?<i32, 5>"),
2052            ("list?<u!geometry?>", "list?<u!geometry?>"),
2053        ];
2054
2055        for (input, expected) in cases {
2056            let parsed = TypeExpr::parse(input).unwrap();
2057            let concrete = Type::try_from(parsed).unwrap();
2058            let actual = concrete.to_string();
2059
2060            assert_eq!(actual, expected, "unexpected display for {input}");
2061            assert_eq!(
2062                parse_type(&actual),
2063                concrete,
2064                "round trip failed for {input}"
2065            );
2066
2067            let raw: RawType = concrete.clone().into();
2068            let mut ctx = TypeContext::default();
2069            assert_eq!(
2070                raw.parse(&mut ctx).unwrap(),
2071                concrete,
2072                "raw type round trip failed for {input}"
2073            );
2074        }
2075    }
2076
2077    /// Test that named struct field order preserves the structure order when
2078    /// round-tripping through RawType (Substrait #915).
2079    #[test]
2080    fn test_named_struct_field_order_stability() -> Result<(), ExtensionTypeError> {
2081        let raw = RawType::Object(
2082            [("beta", "i32"), ("alpha", "string?")]
2083                .into_iter()
2084                .map(|(name, ty)| (name.to_string(), Value::String(ty.to_string())))
2085                .collect(),
2086        );
2087        let mut ctx = TypeContext::default();
2088        let concrete = Parse::parse(raw, &mut ctx)?;
2089
2090        let round_tripped: RawType = concrete.into();
2091        match round_tripped {
2092            RawType::Object(result_map) => {
2093                let keys: Vec<_> = result_map.keys().collect();
2094                assert_eq!(
2095                    keys,
2096                    vec!["beta", "alpha"],
2097                    "field order should be preserved"
2098                );
2099            }
2100            other => panic!("expected Object, got {other:?}"),
2101        }
2102
2103        Ok(())
2104    }
2105
2106    #[test]
2107    fn test_integer_param_bounds_round_trip() {
2108        let cases = vec![
2109            (
2110                "bounded",
2111                simple_extensions::TypeParamDefsItem {
2112                    name: Some("K".to_string()),
2113                    description: None,
2114                    type_: simple_extensions::TypeParamDefsItemType::Integer,
2115                    min: Some(1.0),
2116                    max: Some(10.0),
2117                    options: None,
2118                    optional: None,
2119                },
2120                Ok((Some(1), Some(10))),
2121            ),
2122            (
2123                "fractional_min",
2124                simple_extensions::TypeParamDefsItem {
2125                    name: Some("K".to_string()),
2126                    description: None,
2127                    type_: simple_extensions::TypeParamDefsItemType::Integer,
2128                    min: Some(1.5),
2129                    max: None,
2130                    options: None,
2131                    optional: None,
2132                },
2133                Err(TypeParamError::InvalidIntegerBounds {
2134                    min: Some(1.5),
2135                    max: None,
2136                }),
2137            ),
2138            (
2139                "fractional_max",
2140                simple_extensions::TypeParamDefsItem {
2141                    name: Some("K".to_string()),
2142                    description: None,
2143                    type_: simple_extensions::TypeParamDefsItemType::Integer,
2144                    min: None,
2145                    max: Some(9.9),
2146                    options: None,
2147                    optional: None,
2148                },
2149                Err(TypeParamError::InvalidIntegerBounds {
2150                    min: None,
2151                    max: Some(9.9),
2152                }),
2153            ),
2154        ];
2155
2156        for (label, item, expected) in cases {
2157            match (TypeParam::try_from(item), expected) {
2158                (Ok(tp), Ok((expected_min, expected_max))) => match tp.param_type {
2159                    ParameterConstraint::Integer { min, max } => {
2160                        assert_eq!(min, expected_min, "min mismatch for {label}");
2161                        assert_eq!(max, expected_max, "max mismatch for {label}");
2162                    }
2163                    _ => panic!("expected integer param type for {label}"),
2164                },
2165                (Err(actual_err), Err(expected_err)) => {
2166                    assert_eq!(actual_err, expected_err, "unexpected error for {label}");
2167                }
2168                (result, expected) => {
2169                    panic!("unexpected result for {label}: got {result:?}, expected {expected:?}")
2170                }
2171            }
2172        }
2173    }
2174
2175    #[test]
2176    fn test_custom_type_round_trip() -> Result<(), ExtensionTypeError> {
2177        let fields = IndexMap::from_iter([
2178            ("x".to_string(), Type::new(TypeKind::Fp64, false)),
2179            ("y".to_string(), Type::new(TypeKind::Fp64, false)),
2180        ]);
2181
2182        let cases = vec![
2183            CustomType::new(
2184                "AliasType".to_string(),
2185                vec![],
2186                Some(Type::new(TypeKind::I32, false)),
2187                None,
2188                Some("a test alias type".to_string()),
2189            )?,
2190            CustomType::new(
2191                "Point".to_string(),
2192                vec![TypeParam::new(
2193                    "T".to_string(),
2194                    ParameterConstraint::DataType,
2195                    Some("a numeric value".to_string()),
2196                )],
2197                Some(Type::named_struct(fields, false)),
2198                None,
2199                None,
2200            )?,
2201        ];
2202
2203        for custom in cases {
2204            round_trip(&custom);
2205        }
2206
2207        Ok(())
2208    }
2209
2210    #[test]
2211    fn test_invalid_type_names() {
2212        let cases = vec![
2213            ("", false),
2214            ("bad name", false),
2215            ("9bad", false),
2216            ("bad-name", false),
2217            ("bad.name", false),
2218            ("GoodName", true),
2219            ("also_good", true),
2220            ("_underscore", true),
2221            ("$dollar", true),
2222            ("CamelCase123", true),
2223        ];
2224
2225        for (name, expected_ok) in cases {
2226            let result = CustomType::validate_name(name);
2227            assert_eq!(
2228                result.is_ok(),
2229                expected_ok,
2230                "unexpected validation for {name}"
2231            );
2232        }
2233    }
2234
2235    #[test]
2236    fn test_ext_type_to_concrete_type() -> Result<(), ExtensionTypeError> {
2237        let cases = vec![
2238            (
2239                "alias",
2240                RawType::String("i32".to_string()),
2241                Type::new(TypeKind::I32, false),
2242            ),
2243            (
2244                "named_struct",
2245                raw_named_struct(&[("field1", "fp64"), ("field2", "i32?")]),
2246                Type::named_struct(
2247                    IndexMap::from_iter([
2248                        ("field1".to_string(), Type::new(TypeKind::Fp64, false)),
2249                        ("field2".to_string(), Type::new(TypeKind::I32, true)),
2250                    ]),
2251                    false,
2252                ),
2253            ),
2254        ];
2255
2256        for (label, raw, expected) in cases {
2257            let mut ctx = TypeContext::default();
2258            let parsed = Parse::parse(raw, &mut ctx)?;
2259            assert_eq!(parsed, expected, "unexpected type for {label}");
2260        }
2261
2262        Ok(())
2263    }
2264
2265    #[test]
2266    fn test_custom_type_parsing() -> Result<(), ExtensionTypeError> {
2267        let cases = vec![
2268            (
2269                "alias",
2270                simple_extensions::SimpleExtensionsTypesItem {
2271                    deprecated: None,
2272                    name: "Alias".to_string(),
2273                    description: Some("Alias type".to_string()),
2274                    metadata: Default::default(),
2275                    parameters: None,
2276                    structure: Some(RawType::String("BINARY".to_string())),
2277                    variadic: None,
2278                },
2279                "Alias",
2280                Some("Alias type"),
2281                Some(Type::new(TypeKind::Binary, false)),
2282            ),
2283            (
2284                "named_struct",
2285                simple_extensions::SimpleExtensionsTypesItem {
2286                    deprecated: None,
2287                    name: "Point".to_string(),
2288                    description: Some("A 2D point".to_string()),
2289                    metadata: Default::default(),
2290                    parameters: None,
2291                    structure: Some(raw_named_struct(&[("x", "fp64"), ("y", "fp64?")])),
2292                    variadic: None,
2293                },
2294                "Point",
2295                Some("A 2D point"),
2296                Some(Type::named_struct(
2297                    IndexMap::from_iter([
2298                        ("x".to_string(), Type::new(TypeKind::Fp64, false)),
2299                        ("y".to_string(), Type::new(TypeKind::Fp64, true)),
2300                    ]),
2301                    false,
2302                )),
2303            ),
2304            (
2305                "no_structure",
2306                simple_extensions::SimpleExtensionsTypesItem {
2307                    deprecated: None,
2308                    name: "Opaque".to_string(),
2309                    description: None,
2310                    metadata: Default::default(),
2311                    parameters: None,
2312                    structure: None,
2313                    variadic: Some(true),
2314                },
2315                "Opaque",
2316                None,
2317                None,
2318            ),
2319        ];
2320
2321        for (label, item, expected_name, expected_description, expected_structure) in cases {
2322            let mut ctx = TypeContext::default();
2323            let parsed = Parse::parse(item, &mut ctx)?;
2324            assert_eq!(parsed.name, expected_name);
2325            assert_eq!(
2326                parsed.description.as_deref(),
2327                expected_description,
2328                "description mismatch for {label}"
2329            );
2330            assert_eq!(
2331                parsed.structure, expected_structure,
2332                "structure mismatch for {label}"
2333            );
2334        }
2335
2336        Ok(())
2337    }
2338}