oslquery_petite/
types.rs

1//! Type-safe OSL parameter system where types and values are unified.
2//!
3//! This module provides the most type-safe representation where it's impossible
4//! to have a mismatch between a parameter's type and its default value.
5
6use std::fmt;
7use ustr::Ustr;
8
9/// A typed parameter that unifies type information with its potential value.
10///
11/// This design makes it impossible to have type mismatches - you can't accidentally
12/// assign an integer default to a color parameter, for example.
13#[derive(Debug, Clone, PartialEq)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub enum TypedParameter {
16    // ============= Scalar Types =============
17    /// Integer parameter
18    Int { default: Option<i32> },
19    /// Float parameter
20    Float { default: Option<f32> },
21    /// String parameter
22    String { default: Option<String> },
23
24    // ============= Geometric Types =============
25    // These are always 3 floats in OSL
26    /// RGB color (3 floats)
27    Color {
28        default: Option<[f32; 3]>,
29        space: Option<Ustr>, // Color space (e.g., "rgb", "hsv")
30    },
31    /// 3D point (3 floats)
32    Point {
33        default: Option<[f32; 3]>,
34        space: Option<Ustr>, // Coordinate space (e.g., "world", "object")
35    },
36    /// 3D vector (3 floats)
37    Vector {
38        default: Option<[f32; 3]>,
39        space: Option<Ustr>,
40    },
41    /// Surface normal (3 floats)
42    Normal {
43        default: Option<[f32; 3]>,
44        space: Option<Ustr>,
45    },
46    /// 4x4 transformation matrix (16 floats)
47    Matrix { default: Option<[f32; 16]> },
48
49    // ============= Fixed-Size Array Types =============
50    /// Fixed-size array of integers
51    IntArray {
52        size: usize,
53        default: Option<Vec<i32>>,
54    },
55    /// Fixed-size array of floats
56    FloatArray {
57        size: usize,
58        default: Option<Vec<f32>>,
59    },
60    /// Fixed-size array of strings
61    StringArray {
62        size: usize,
63        default: Option<Vec<String>>,
64    },
65    /// Fixed-size array of colors
66    ColorArray {
67        size: usize,
68        default: Option<Vec<[f32; 3]>>,
69        space: Option<Ustr>,
70    },
71    /// Fixed-size array of points
72    PointArray {
73        size: usize,
74        default: Option<Vec<[f32; 3]>>,
75        space: Option<Ustr>,
76    },
77    /// Fixed-size array of vectors
78    VectorArray {
79        size: usize,
80        default: Option<Vec<[f32; 3]>>,
81        space: Option<Ustr>,
82    },
83    /// Fixed-size array of normals
84    NormalArray {
85        size: usize,
86        default: Option<Vec<[f32; 3]>>,
87        space: Option<Ustr>,
88    },
89    /// Fixed-size array of matrices
90    MatrixArray {
91        size: usize,
92        default: Option<Vec<[f32; 16]>>,
93    },
94
95    // ============= Dynamic Array Types =============
96    /// Dynamic (unsized) array of integers
97    IntDynamicArray { default: Option<Vec<i32>> },
98    /// Dynamic array of floats
99    FloatDynamicArray { default: Option<Vec<f32>> },
100    /// Dynamic array of strings
101    StringDynamicArray { default: Option<Vec<String>> },
102    /// Dynamic array of colors
103    ColorDynamicArray {
104        default: Option<Vec<[f32; 3]>>,
105        space: Option<Ustr>,
106    },
107    /// Dynamic array of points
108    PointDynamicArray {
109        default: Option<Vec<[f32; 3]>>,
110        space: Option<Ustr>,
111    },
112    /// Dynamic array of vectors
113    VectorDynamicArray {
114        default: Option<Vec<[f32; 3]>>,
115        space: Option<Ustr>,
116    },
117    /// Dynamic array of normals
118    NormalDynamicArray {
119        default: Option<Vec<[f32; 3]>>,
120        space: Option<Ustr>,
121    },
122    /// Dynamic array of matrices
123    MatrixDynamicArray { default: Option<Vec<[f32; 16]>> },
124
125    // ============= Special Types =============
126    /// Closure (BSDF, etc.) - no default values
127    Closure { closure_type: Ustr },
128}
129
130impl TypedParameter {
131    /// Check if this parameter has a default value.
132    pub fn has_default(&self) -> bool {
133        match self {
134            TypedParameter::Int { default } => default.is_some(),
135            TypedParameter::Float { default } => default.is_some(),
136            TypedParameter::String { default } => default.is_some(),
137            TypedParameter::Color { default, .. } => default.is_some(),
138            TypedParameter::Point { default, .. } => default.is_some(),
139            TypedParameter::Vector { default, .. } => default.is_some(),
140            TypedParameter::Normal { default, .. } => default.is_some(),
141            TypedParameter::Matrix { default } => default.is_some(),
142
143            TypedParameter::IntArray { default, .. } => default.is_some(),
144            TypedParameter::FloatArray { default, .. } => default.is_some(),
145            TypedParameter::StringArray { default, .. } => default.is_some(),
146            TypedParameter::ColorArray { default, .. } => default.is_some(),
147            TypedParameter::PointArray { default, .. } => default.is_some(),
148            TypedParameter::VectorArray { default, .. } => default.is_some(),
149            TypedParameter::NormalArray { default, .. } => default.is_some(),
150            TypedParameter::MatrixArray { default, .. } => default.is_some(),
151
152            TypedParameter::IntDynamicArray { default } => default.is_some(),
153            TypedParameter::FloatDynamicArray { default } => default.is_some(),
154            TypedParameter::StringDynamicArray { default } => default.is_some(),
155            TypedParameter::ColorDynamicArray { default, .. } => default.is_some(),
156            TypedParameter::PointDynamicArray { default, .. } => default.is_some(),
157            TypedParameter::VectorDynamicArray { default, .. } => default.is_some(),
158            TypedParameter::NormalDynamicArray { default, .. } => default.is_some(),
159            TypedParameter::MatrixDynamicArray { default } => default.is_some(),
160
161            TypedParameter::Closure { .. } => false, // Closures never have defaults
162        }
163    }
164
165    /// Check if this is an array type.
166    pub fn is_array(&self) -> bool {
167        !matches!(
168            self,
169            TypedParameter::Int { .. }
170                | TypedParameter::Float { .. }
171                | TypedParameter::String { .. }
172                | TypedParameter::Color { .. }
173                | TypedParameter::Point { .. }
174                | TypedParameter::Vector { .. }
175                | TypedParameter::Normal { .. }
176                | TypedParameter::Matrix { .. }
177                | TypedParameter::Closure { .. }
178        )
179    }
180
181    /// Check if this is a dynamic (unsized) array.
182    pub fn is_dynamic_array(&self) -> bool {
183        matches!(
184            self,
185            TypedParameter::IntDynamicArray { .. }
186                | TypedParameter::FloatDynamicArray { .. }
187                | TypedParameter::StringDynamicArray { .. }
188                | TypedParameter::ColorDynamicArray { .. }
189                | TypedParameter::PointDynamicArray { .. }
190                | TypedParameter::VectorDynamicArray { .. }
191                | TypedParameter::NormalDynamicArray { .. }
192                | TypedParameter::MatrixDynamicArray { .. }
193        )
194    }
195
196    /// Check if this is a closure type.
197    pub fn is_closure(&self) -> bool {
198        matches!(self, TypedParameter::Closure { .. })
199    }
200
201    /// Get the type name as a string.
202    pub fn type_name(&self) -> &'static str {
203        match self {
204            TypedParameter::Int { .. } => "int",
205            TypedParameter::Float { .. } => "float",
206            TypedParameter::String { .. } => "string",
207            TypedParameter::Color { .. } => "color",
208            TypedParameter::Point { .. } => "point",
209            TypedParameter::Vector { .. } => "vector",
210            TypedParameter::Normal { .. } => "normal",
211            TypedParameter::Matrix { .. } => "matrix",
212
213            TypedParameter::IntArray { .. } => "int[]",
214            TypedParameter::FloatArray { .. } => "float[]",
215            TypedParameter::StringArray { .. } => "string[]",
216            TypedParameter::ColorArray { .. } => "color[]",
217            TypedParameter::PointArray { .. } => "point[]",
218            TypedParameter::VectorArray { .. } => "vector[]",
219            TypedParameter::NormalArray { .. } => "normal[]",
220            TypedParameter::MatrixArray { .. } => "matrix[]",
221
222            TypedParameter::IntDynamicArray { .. } => "int[]",
223            TypedParameter::FloatDynamicArray { .. } => "float[]",
224            TypedParameter::StringDynamicArray { .. } => "string[]",
225            TypedParameter::ColorDynamicArray { .. } => "color[]",
226            TypedParameter::PointDynamicArray { .. } => "point[]",
227            TypedParameter::VectorDynamicArray { .. } => "vector[]",
228            TypedParameter::NormalDynamicArray { .. } => "normal[]",
229            TypedParameter::MatrixDynamicArray { .. } => "matrix[]",
230
231            TypedParameter::Closure { .. } => "closure",
232        }
233    }
234}
235
236impl fmt::Display for TypedParameter {
237    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238        match self {
239            TypedParameter::IntArray { size, .. } => write!(f, "int[{}]", size),
240            TypedParameter::FloatArray { size, .. } => write!(f, "float[{}]", size),
241            TypedParameter::StringArray { size, .. } => write!(f, "string[{}]", size),
242            TypedParameter::ColorArray { size, .. } => write!(f, "color[{}]", size),
243            TypedParameter::PointArray { size, .. } => write!(f, "point[{}]", size),
244            TypedParameter::VectorArray { size, .. } => write!(f, "vector[{}]", size),
245            TypedParameter::NormalArray { size, .. } => write!(f, "normal[{}]", size),
246            TypedParameter::MatrixArray { size, .. } => write!(f, "matrix[{}]", size),
247
248            TypedParameter::Closure { closure_type } => write!(f, "closure {}", closure_type),
249
250            other => write!(f, "{}", other.type_name()),
251        }
252    }
253}
254
255/// Metadata attached to parameters.
256#[derive(Debug, Clone, PartialEq)]
257#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
258pub struct Metadata {
259    pub name: Ustr,
260    pub value: MetadataValue,
261}
262
263/// Metadata values are simpler - they're always scalar or string arrays.
264#[derive(Debug, Clone, PartialEq)]
265#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
266pub enum MetadataValue {
267    Int(i32),
268    Float(f32),
269    String(String),
270    IntArray(Vec<i32>),
271    FloatArray(Vec<f32>),
272    StringArray(Vec<String>),
273}
274
275/// A parameter with its direction (input/output).
276#[derive(Debug, Clone, PartialEq)]
277#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
278pub enum ParameterKind {
279    /// Input parameter with potential default value
280    Input(TypedParameter),
281    /// Output parameter (never has defaults)
282    Output(TypedParameter),
283}
284
285impl ParameterKind {
286    /// Check if this is an output parameter.
287    pub fn is_output(&self) -> bool {
288        matches!(self, ParameterKind::Output(_))
289    }
290
291    /// Get the inner typed parameter.
292    pub fn typed_param(&self) -> &TypedParameter {
293        match self {
294            ParameterKind::Input(p) | ParameterKind::Output(p) => p,
295        }
296    }
297}
298
299/// Complete parameter with name and metadata.
300#[derive(Debug, Clone, PartialEq)]
301#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
302pub struct Parameter {
303    /// Parameter name
304    pub name: Ustr,
305    /// Parameter kind and type
306    pub kind: ParameterKind,
307    /// Associated metadata
308    pub metadata: Vec<Metadata>,
309}
310
311impl Parameter {
312    /// Create a new input parameter.
313    pub fn new_input(name: impl Into<Ustr>, typed_param: TypedParameter) -> Self {
314        Parameter {
315            name: name.into(),
316            kind: ParameterKind::Input(typed_param),
317            metadata: Vec::new(),
318        }
319    }
320
321    /// Create a new output parameter (strips any default values).
322    pub fn new_output(name: impl Into<Ustr>, mut typed_param: TypedParameter) -> Self {
323        // Output parameters can't have defaults, so strip them
324        match &mut typed_param {
325            TypedParameter::Int { default } => *default = None,
326            TypedParameter::Float { default } => *default = None,
327            TypedParameter::String { default } => *default = None,
328            TypedParameter::Color { default, .. } => *default = None,
329            TypedParameter::Point { default, .. } => *default = None,
330            TypedParameter::Vector { default, .. } => *default = None,
331            TypedParameter::Normal { default, .. } => *default = None,
332            TypedParameter::Matrix { default } => *default = None,
333
334            TypedParameter::IntArray { default, .. } => *default = None,
335            TypedParameter::FloatArray { default, .. } => *default = None,
336            TypedParameter::StringArray { default, .. } => *default = None,
337            TypedParameter::ColorArray { default, .. } => *default = None,
338            TypedParameter::PointArray { default, .. } => *default = None,
339            TypedParameter::VectorArray { default, .. } => *default = None,
340            TypedParameter::NormalArray { default, .. } => *default = None,
341            TypedParameter::MatrixArray { default, .. } => *default = None,
342
343            TypedParameter::IntDynamicArray { default } => *default = None,
344            TypedParameter::FloatDynamicArray { default } => *default = None,
345            TypedParameter::StringDynamicArray { default } => *default = None,
346            TypedParameter::ColorDynamicArray { default, .. } => *default = None,
347            TypedParameter::PointDynamicArray { default, .. } => *default = None,
348            TypedParameter::VectorDynamicArray { default, .. } => *default = None,
349            TypedParameter::NormalDynamicArray { default, .. } => *default = None,
350            TypedParameter::MatrixDynamicArray { default } => *default = None,
351
352            TypedParameter::Closure { .. } => {} // Already has no defaults
353        }
354
355        Parameter {
356            name: name.into(),
357            kind: ParameterKind::Output(typed_param),
358            metadata: Vec::new(),
359        }
360    }
361
362    /// Check if this is an output parameter.
363    pub fn is_output(&self) -> bool {
364        self.kind.is_output()
365    }
366
367    /// Get the typed parameter.
368    pub fn typed_param(&self) -> &TypedParameter {
369        self.kind.typed_param()
370    }
371
372    /// Find metadata by name.
373    pub fn find_metadata(&self, name: &str) -> Option<&Metadata> {
374        self.metadata.iter().find(|m| m.name.as_str() == name)
375    }
376
377    /// Add metadata to this parameter.
378    pub fn add_metadata(&mut self, name: impl Into<Ustr>, value: MetadataValue) {
379        self.metadata.push(Metadata {
380            name: name.into(),
381            value,
382        });
383    }
384}
385
386// Conversion from ParsedParameter to typed parameters
387impl TryFrom<crate::parser::types::ParsedParameter> for Parameter {
388    type Error = String;
389
390    fn try_from(old: crate::parser::types::ParsedParameter) -> Result<Self, Self::Error> {
391        use crate::parser::types::BaseType;
392
393        // Convert the type and value together
394        let typed_param = match old.type_desc.basetype {
395            BaseType::Int => {
396                if old.type_desc.is_array() {
397                    if old.type_desc.arraylen == -1 {
398                        TypedParameter::IntDynamicArray {
399                            default: if old.valid_default && !old.idefault.is_empty() {
400                                Some(old.idefault)
401                            } else {
402                                None
403                            },
404                        }
405                    } else {
406                        TypedParameter::IntArray {
407                            size: old.type_desc.arraylen as usize,
408                            default: if old.valid_default && !old.idefault.is_empty() {
409                                Some(old.idefault)
410                            } else {
411                                None
412                            },
413                        }
414                    }
415                } else {
416                    TypedParameter::Int {
417                        default: if old.valid_default && !old.idefault.is_empty() {
418                            Some(old.idefault[0])
419                        } else {
420                            None
421                        },
422                    }
423                }
424            }
425            BaseType::Float => {
426                if old.type_desc.is_array() {
427                    if old.type_desc.arraylen == -1 {
428                        TypedParameter::FloatDynamicArray {
429                            default: if old.valid_default && !old.fdefault.is_empty() {
430                                Some(old.fdefault)
431                            } else {
432                                None
433                            },
434                        }
435                    } else {
436                        TypedParameter::FloatArray {
437                            size: old.type_desc.arraylen as usize,
438                            default: if old.valid_default && !old.fdefault.is_empty() {
439                                Some(old.fdefault)
440                            } else {
441                                None
442                            },
443                        }
444                    }
445                } else {
446                    TypedParameter::Float {
447                        default: if old.valid_default && !old.fdefault.is_empty() {
448                            Some(old.fdefault[0])
449                        } else {
450                            None
451                        },
452                    }
453                }
454            }
455            BaseType::String => {
456                if old.type_desc.is_array() {
457                    if old.type_desc.arraylen == -1 {
458                        TypedParameter::StringDynamicArray {
459                            default: if old.valid_default && !old.sdefault.is_empty() {
460                                Some(old.sdefault)
461                            } else {
462                                None
463                            },
464                        }
465                    } else {
466                        TypedParameter::StringArray {
467                            size: old.type_desc.arraylen as usize,
468                            default: if old.valid_default && !old.sdefault.is_empty() {
469                                Some(old.sdefault)
470                            } else {
471                                None
472                            },
473                        }
474                    }
475                } else {
476                    TypedParameter::String {
477                        default: if old.valid_default && !old.sdefault.is_empty() {
478                            Some(old.sdefault[0].clone())
479                        } else {
480                            None
481                        },
482                    }
483                }
484            }
485            BaseType::Color => {
486                let space = old.spacename.first().map(|s| Ustr::from(s.as_str()));
487                if old.type_desc.is_array() {
488                    // Convert flat array to array of [f32; 3]
489                    let arrays = if old.valid_default && !old.fdefault.is_empty() {
490                        Some(
491                            old.fdefault
492                                .chunks_exact(3)
493                                .map(|chunk| [chunk[0], chunk[1], chunk[2]])
494                                .collect(),
495                        )
496                    } else {
497                        None
498                    };
499
500                    if old.type_desc.arraylen == -1 {
501                        TypedParameter::ColorDynamicArray {
502                            default: arrays,
503                            space,
504                        }
505                    } else {
506                        TypedParameter::ColorArray {
507                            size: old.type_desc.arraylen as usize,
508                            default: arrays,
509                            space,
510                        }
511                    }
512                } else {
513                    TypedParameter::Color {
514                        default: if old.valid_default && old.fdefault.len() >= 3 {
515                            Some([old.fdefault[0], old.fdefault[1], old.fdefault[2]])
516                        } else {
517                            None
518                        },
519                        space,
520                    }
521                }
522            }
523            BaseType::Point => {
524                let space = old.spacename.first().map(|s| Ustr::from(s.as_str()));
525                if old.type_desc.is_array() {
526                    let arrays = if old.valid_default && !old.fdefault.is_empty() {
527                        Some(
528                            old.fdefault
529                                .chunks_exact(3)
530                                .map(|chunk| [chunk[0], chunk[1], chunk[2]])
531                                .collect(),
532                        )
533                    } else {
534                        None
535                    };
536
537                    if old.type_desc.arraylen == -1 {
538                        TypedParameter::PointDynamicArray {
539                            default: arrays,
540                            space,
541                        }
542                    } else {
543                        TypedParameter::PointArray {
544                            size: old.type_desc.arraylen as usize,
545                            default: arrays,
546                            space,
547                        }
548                    }
549                } else {
550                    TypedParameter::Point {
551                        default: if old.valid_default && old.fdefault.len() >= 3 {
552                            Some([old.fdefault[0], old.fdefault[1], old.fdefault[2]])
553                        } else {
554                            None
555                        },
556                        space,
557                    }
558                }
559            }
560            BaseType::Vector => {
561                let space = old.spacename.first().map(|s| Ustr::from(s.as_str()));
562                if old.type_desc.is_array() {
563                    let arrays = if old.valid_default && !old.fdefault.is_empty() {
564                        Some(
565                            old.fdefault
566                                .chunks_exact(3)
567                                .map(|chunk| [chunk[0], chunk[1], chunk[2]])
568                                .collect(),
569                        )
570                    } else {
571                        None
572                    };
573
574                    if old.type_desc.arraylen == -1 {
575                        TypedParameter::VectorDynamicArray {
576                            default: arrays,
577                            space,
578                        }
579                    } else {
580                        TypedParameter::VectorArray {
581                            size: old.type_desc.arraylen as usize,
582                            default: arrays,
583                            space,
584                        }
585                    }
586                } else {
587                    TypedParameter::Vector {
588                        default: if old.valid_default && old.fdefault.len() >= 3 {
589                            Some([old.fdefault[0], old.fdefault[1], old.fdefault[2]])
590                        } else {
591                            None
592                        },
593                        space,
594                    }
595                }
596            }
597            BaseType::Normal => {
598                let space = old.spacename.first().map(|s| Ustr::from(s.as_str()));
599                if old.type_desc.is_array() {
600                    let arrays = if old.valid_default && !old.fdefault.is_empty() {
601                        Some(
602                            old.fdefault
603                                .chunks_exact(3)
604                                .map(|chunk| [chunk[0], chunk[1], chunk[2]])
605                                .collect(),
606                        )
607                    } else {
608                        None
609                    };
610
611                    if old.type_desc.arraylen == -1 {
612                        TypedParameter::NormalDynamicArray {
613                            default: arrays,
614                            space,
615                        }
616                    } else {
617                        TypedParameter::NormalArray {
618                            size: old.type_desc.arraylen as usize,
619                            default: arrays,
620                            space,
621                        }
622                    }
623                } else {
624                    TypedParameter::Normal {
625                        default: if old.valid_default && old.fdefault.len() >= 3 {
626                            Some([old.fdefault[0], old.fdefault[1], old.fdefault[2]])
627                        } else {
628                            None
629                        },
630                        space,
631                    }
632                }
633            }
634            BaseType::Matrix => {
635                if old.type_desc.is_array() {
636                    let arrays = if old.valid_default && !old.fdefault.is_empty() {
637                        Some(
638                            old.fdefault
639                                .chunks_exact(16)
640                                .map(|chunk| {
641                                    let mut arr = [0.0; 16];
642                                    arr.copy_from_slice(chunk);
643                                    arr
644                                })
645                                .collect(),
646                        )
647                    } else {
648                        None
649                    };
650
651                    if old.type_desc.arraylen == -1 {
652                        TypedParameter::MatrixDynamicArray { default: arrays }
653                    } else {
654                        TypedParameter::MatrixArray {
655                            size: old.type_desc.arraylen as usize,
656                            default: arrays,
657                        }
658                    }
659                } else {
660                    TypedParameter::Matrix {
661                        default: if old.valid_default && old.fdefault.len() >= 16 {
662                            let mut arr = [0.0; 16];
663                            arr.copy_from_slice(&old.fdefault[..16]);
664                            Some(arr)
665                        } else {
666                            None
667                        },
668                    }
669                }
670            }
671            BaseType::None => {
672                if old.type_desc.is_closure {
673                    TypedParameter::Closure {
674                        closure_type: old.structname.unwrap_or_else(|| Ustr::from("closure")),
675                    }
676                } else {
677                    return Err("Cannot convert BaseType::None that isn't a closure".to_string());
678                }
679            }
680        };
681
682        // Create the parameter
683        let mut param = if old.is_output {
684            Parameter::new_output(old.name, typed_param)
685        } else {
686            Parameter::new_input(old.name, typed_param)
687        };
688
689        // Convert metadata
690        for meta in old.metadata {
691            let meta_value = if !meta.idefault.is_empty() {
692                if meta.idefault.len() == 1 {
693                    MetadataValue::Int(meta.idefault[0])
694                } else {
695                    MetadataValue::IntArray(meta.idefault)
696                }
697            } else if !meta.fdefault.is_empty() {
698                if meta.fdefault.len() == 1 {
699                    MetadataValue::Float(meta.fdefault[0])
700                } else {
701                    MetadataValue::FloatArray(meta.fdefault)
702                }
703            } else if !meta.sdefault.is_empty() {
704                if meta.sdefault.len() == 1 {
705                    MetadataValue::String(meta.sdefault[0].clone())
706                } else {
707                    MetadataValue::StringArray(meta.sdefault)
708                }
709            } else {
710                continue;
711            };
712            param.add_metadata(meta.name, meta_value);
713        }
714
715        Ok(param)
716    }
717}
718
719#[cfg(test)]
720mod tests {
721    use super::*;
722
723    #[test]
724    fn test_typed_parameter_creation() {
725        // Simple float with default
726        let param = TypedParameter::Float { default: Some(0.5) };
727        assert!(param.has_default());
728        assert!(!param.is_array());
729        assert_eq!(param.type_name(), "float");
730
731        // Color without default
732        let param = TypedParameter::Color {
733            default: None,
734            space: Some(Ustr::from("rgb")),
735        };
736        assert!(!param.has_default());
737        assert!(!param.is_array());
738        assert_eq!(param.type_name(), "color");
739
740        // Fixed array
741        let param = TypedParameter::FloatArray {
742            size: 5,
743            default: Some(vec![1.0, 2.0, 3.0, 4.0, 5.0]),
744        };
745        assert!(param.has_default());
746        assert!(param.is_array());
747        assert!(!param.is_dynamic_array());
748        assert_eq!(param.to_string(), "float[5]");
749
750        // Dynamic array
751        let param = TypedParameter::StringDynamicArray {
752            default: Some(vec!["hello".to_string(), "world".to_string()]),
753        };
754        assert!(param.has_default());
755        assert!(param.is_array());
756        assert!(param.is_dynamic_array());
757        assert_eq!(param.type_name(), "string[]");
758    }
759
760    #[test]
761    fn test_output_parameter_strips_defaults() {
762        let typed_param = TypedParameter::Color {
763            default: Some([1.0, 0.0, 0.0]),
764            space: None,
765        };
766
767        let output = Parameter::new_output("result", typed_param);
768        assert!(output.is_output());
769
770        // Check that default was stripped
771        match output.typed_param() {
772            TypedParameter::Color { default, .. } => {
773                assert!(
774                    default.is_none(),
775                    "Output parameter should not have default"
776                );
777            }
778            _ => panic!("Wrong type"),
779        }
780    }
781
782    #[test]
783    fn test_type_safety() {
784        // This design makes it impossible to have mismatched types and values
785        // You can't create a Color with an int default - it's enforced at compile time!
786
787        let color = TypedParameter::Color {
788            default: Some([1.0, 0.5, 0.0]),
789            space: None,
790        };
791
792        // Can't accidentally treat it as an int
793        match color {
794            TypedParameter::Color {
795                default: Some(rgb), ..
796            } => {
797                assert_eq!(rgb[0], 1.0);
798                assert_eq!(rgb[1], 0.5);
799                assert_eq!(rgb[2], 0.0);
800            }
801            TypedParameter::Int { .. } => {
802                panic!("This branch is impossible - type safety!");
803            }
804            _ => {}
805        }
806    }
807}