sdf_metadata/metadata/operator/
step_invocation.rs

1use std::collections::BTreeSet;
2
3use anyhow::Result;
4use sdf_common::render::wit_name_case;
5use wit_encoder::{Interface, Params, Result_, StandaloneFunc, Type, Use};
6
7use sdf_common::{constants::ROW_VALUE_WIT_TYPE, render::map_wit_keyword};
8use sdf_common::constants::{DF_VALUE_WIT_TYPE, I32_LIST_VALUE_WIT_TYPE, I32_VALUE_WIT_TYPE};
9
10use crate::wit::io::TypeRef;
11use crate::{
12    metadata::metadata::sdf_type::hashable_primitives_list,
13    util::{
14        sdf_types_map::{is_imported_type, SdfTypesMap},
15        validation_failure::ValidationFailure,
16    },
17    wit::{
18        dataflow::PackageImport,
19        metadata::{NamedParameter, OutputType, Parameter, ParameterKind, SdfType},
20        operator::{CodeInfo, ImportedFunctionMetadata, OperatorType, StepInvocation, StepState},
21        states::StateTyped,
22    },
23};
24
25impl StepInvocation {
26    pub fn set_inputs(&mut self, inputs: Vec<NamedParameter>) {
27        self.inputs = inputs;
28    }
29
30    pub fn set_output(&mut self, output: Option<Parameter>) {
31        self.output = output
32    }
33
34    pub fn set_imported_function_metadata(&mut self, meta: Option<ImportedFunctionMetadata>) {
35        self.imported_function_metadata = meta
36    }
37
38    pub fn set_states(&mut self, states: Vec<StepState>) {
39        self.states = states;
40    }
41
42    pub fn is_imported(&self, imports: &[PackageImport]) -> bool {
43        let name = &self.uses;
44
45        imports.iter().any(|import| {
46            import.functions.iter().any(|function| {
47                if let Some(alias) = &function.alias {
48                    *alias == *name
49                } else {
50                    function.name == *name
51                }
52            })
53        })
54    }
55
56    pub fn resolve_states(&mut self, states: &[StateTyped]) -> Result<()> {
57        for state in &mut self.states {
58            state.resolve(states)?;
59        }
60
61        Ok(())
62    }
63
64    pub fn requires_key_param(&self) -> bool {
65        self.inputs
66            .first()
67            .map(|input| matches!(input.kind, ParameterKind::Key))
68            .unwrap_or_default()
69    }
70
71    pub fn has_key_in_output(&self) -> bool {
72        self.output
73            .as_ref()
74            .map(|output| matches!(output.type_, OutputType::KeyValue(_)))
75            .unwrap_or_default()
76    }
77
78    #[cfg(feature = "parser")]
79    fn validate_code(&self) -> Result<(), ValidationFailure> {
80        use crate::util::sdf_function_parser::SDFFunctionParser;
81        let mut errors = ValidationFailure::new();
82
83        if let Some(ref code) = self.code_info.code {
84            if code.is_empty() {
85                errors.push_str("Code block is empty");
86            }
87
88            match SDFFunctionParser::parse(&self.code_info.lang, code) {
89                Ok((uses, inputs, output)) => {
90                    // must match with self.attributes
91                    if self.uses != uses {
92                        errors.push_str(&format!(
93                            "function name on parsed code does not match. Got {uses}, expected: {}",
94                            self.uses
95                        ))
96                    }
97                    if self.output != output {
98                        errors.push_str(&format!(
99                            "function output on parsed code does not match. Got {:?}, expected: {:?}", output, self.output,
100                        ))
101                    }
102
103                    if self.inputs != inputs {
104                        errors.push_str(&format!(
105                            "function input on parsed code does not match. Got {:?}, expected: {:?}", inputs, self.inputs,
106                        ))
107                    }
108                }
109                Err(err) => {
110                    errors.push_str(&err.to_string());
111                }
112            }
113        }
114
115        if errors.any() {
116            Err(errors)
117        } else {
118            Ok(())
119        }
120    }
121
122    pub fn validate_map(&self, types: &SdfTypesMap) -> Result<(), ValidationFailure> {
123        let mut errors = ValidationFailure::new();
124
125        if let Err(err) = self.validate_n_value_input(1, "map") {
126            errors.concat(&err);
127        }
128
129        if let Err(err) = self.validate_output_present("map") {
130            errors.concat(&err);
131        }
132
133        if let Err(err) = self.validate_inputs_in_scope(types) {
134            errors.concat(&err);
135        }
136
137        if let Err(err) = self.validate_output_in_scope(types) {
138            errors.concat(&err);
139        }
140
141        #[cfg(feature = "parser")]
142        if let Err(err) = self.validate_code() {
143            errors.concat(&err);
144        }
145
146        if errors.any() {
147            Err(errors)
148        } else {
149            Ok(())
150        }
151    }
152
153    pub fn validate_filter_map(&self, types: &SdfTypesMap) -> Result<(), ValidationFailure> {
154        let mut errors = ValidationFailure::new();
155
156        if let Err(err) = self.validate_n_value_input(1, "filter-map") {
157            errors.concat(&err);
158        }
159
160        if let Err(err) = self.validate_output_present("filter-map") {
161            errors.concat(&err);
162        }
163
164        if let Err(err) = self.validate_output_is_optional("filter-map", types) {
165            errors.concat(&err);
166        }
167
168        if let Err(err) = self.validate_inputs_in_scope(types) {
169            errors.concat(&err);
170        }
171
172        if let Err(err) = self.validate_output_in_scope(types) {
173            errors.concat(&err);
174        }
175
176        #[cfg(feature = "parser")]
177        if let Err(err) = self.validate_code() {
178            errors.concat(&err);
179        }
180
181        if errors.any() {
182            Err(errors)
183        } else {
184            Ok(())
185        }
186    }
187
188    pub fn validate_filter(&self, types: &SdfTypesMap) -> Result<(), ValidationFailure> {
189        let mut errors = ValidationFailure::new();
190
191        if let Err(err) = self.validate_n_value_input(1, "filter") {
192            errors.concat(&err);
193        }
194
195        if !self
196            .output
197            .as_ref()
198            .map(|p| p.is_bool())
199            .unwrap_or_default()
200        {
201            errors.push_str(&format!(
202                "filter type function `{}` requires an output type of `bool`, but found {}",
203                self.uses,
204                self.output
205                    .as_ref()
206                    .map(|p| format!("`{}`", p.type_.value_type_name()))
207                    .unwrap_or("no type".to_string())
208            ));
209        }
210
211        if let Err(err) = self.validate_inputs_in_scope(types) {
212            errors.concat(&err);
213        }
214
215        if let Err(err) = self.validate_output_in_scope(types) {
216            errors.concat(&err);
217        }
218
219        #[cfg(feature = "parser")]
220        if let Err(err) = self.validate_code() {
221            errors.concat(&err);
222        }
223
224        if errors.any() {
225            Err(errors)
226        } else {
227            Ok(())
228        }
229    }
230
231    pub fn validate_flat_map(&self, types: &SdfTypesMap) -> Result<(), ValidationFailure> {
232        let mut errors = ValidationFailure::new();
233
234        if let Err(err) = self.validate_n_value_input(1, "flat-map") {
235            errors.concat(&err);
236        }
237
238        if let Err(err) = self.validate_output_present("flat-map") {
239            errors.concat(&err);
240        }
241        if let Err(err) = self.validate_inputs_in_scope(types) {
242            errors.concat(&err);
243        }
244
245        if let Err(err) = self.validate_output_in_scope(types) {
246            errors.concat(&err);
247        }
248
249        #[cfg(feature = "parser")]
250        if let Err(err) = self.validate_code() {
251            errors.concat(&err);
252        }
253
254        if errors.any() {
255            Err(errors)
256        } else {
257            Ok(())
258        }
259    }
260
261    pub fn validate_update_state(&self, types: &SdfTypesMap) -> Result<(), ValidationFailure> {
262        let mut errors = ValidationFailure::new();
263
264        if let Err(err) = self.validate_n_value_input(1, "update-state") {
265            errors.concat(&err);
266        }
267
268        if let Err(err) = self.validate_inputs_in_scope(types) {
269            errors.concat(&err);
270        }
271
272        if let Some(output) = &self.output {
273            errors.push_str(&format!(
274                "update-state type function `{}` should have no output, but found `{}`",
275                self.uses,
276                output.type_.value_type_name()
277            ));
278        }
279
280        #[cfg(feature = "parser")]
281        if let Err(err) = self.validate_code() {
282            errors.concat(&err);
283        }
284
285        if errors.any() {
286            Err(errors)
287        } else {
288            Ok(())
289        }
290    }
291
292    pub fn validate_window_aggregate(&self, types: &SdfTypesMap) -> Result<(), ValidationFailure> {
293        let mut errors = ValidationFailure::new();
294
295        if !self.inputs.is_empty() {
296            errors.push_str(&format!(
297                "window-aggregate type function `{}` should have no input type, but found {}",
298                self.uses,
299                self.inputs
300                    .iter()
301                    .map(|p| format!("[{}: {}]", p.name, p.type_.name))
302                    .collect::<Vec<String>>()
303                    .join(", ")
304            ));
305        }
306
307        if let Err(err) = self.validate_output_present("window-aggregate") {
308            errors.concat(&err);
309        }
310
311        if let Err(err) = self.validate_output_in_scope(types) {
312            errors.concat(&err);
313        }
314
315        #[cfg(feature = "parser")]
316        if let Err(err) = self.validate_code() {
317            errors.concat(&err);
318        }
319
320        if errors.any() {
321            Err(errors)
322        } else {
323            Ok(())
324        }
325    }
326
327    pub fn validate_assign_key(&self, types: &SdfTypesMap) -> Result<(), ValidationFailure> {
328        let mut errors = ValidationFailure::new();
329
330        if let Err(err) = self.validate_n_value_input(1, "assign-key") {
331            errors.concat(&err);
332        }
333
334        if let Err(err) = self.validate_output_present("assign-key") {
335            errors.concat(&err);
336        }
337
338        if let Err(err) = self.validate_inputs_in_scope(types) {
339            errors.concat(&err);
340        }
341
342        if let Err(err) = self.validate_output_in_scope(types) {
343            errors.concat(&err);
344        }
345
346        if let Some(output) = &self.output {
347            let output_type_name = output.type_.value_type_name();
348
349            if let Some((output_type, _)) = types.get(output_type_name) {
350                if !output_type.is_hashable(types) {
351                    errors.push_str(&format!(
352                        "output type for assign-key type function `{}` must be hashable, or a reference to a hashable type. found `{}`.\n hashable types: [{}]",
353                        self.uses, output_type_name, hashable_primitives_list()
354                    ));
355                }
356            }
357        }
358
359        #[cfg(feature = "parser")]
360        if let Err(err) = self.validate_code() {
361            errors.concat(&err);
362        }
363
364        if errors.any() {
365            Err(errors)
366        } else {
367            Ok(())
368        }
369    }
370
371    pub fn validate_assign_timestamp(&self, types: &SdfTypesMap) -> Result<(), ValidationFailure> {
372        let mut errors = ValidationFailure::new();
373
374        if let Err(err) = self.validate_n_value_input(2, "assign-timestamp") {
375            errors.concat(&err);
376        }
377
378        if let Err(err) = self.validate_output_present("assign-timestamp") {
379            errors.concat(&err);
380        }
381
382        if let Err(err) = self.validate_inputs_in_scope(types) {
383            errors.concat(&err);
384        }
385
386        if let Err(err) = self.validate_output_in_scope(types) {
387            errors.concat(&err);
388        }
389
390        if let Some(second_input) = self.inputs.last() {
391            if types.is_s64(&second_input.type_.name) {
392            } else {
393                errors.push_str(
394                    format!(
395                        "second input type for assign-timestamp type function `{}` must be a signed 64-bit int or an alias for one, found: `{}`",
396                        self.uses, second_input.type_.name
397                    )
398                    .as_str(),
399                );
400            }
401        }
402
403        if let Some(output) = &self.output {
404            let output_type_name = &output.type_.value_type_name();
405
406            if !types.is_s64(output_type_name) {
407                errors.push_str(&format!(
408                        "output type for assign-timestamp type function `{}` must be a signed 64-bit int or an alias for one, found: `{}`",
409                        self.uses, output_type_name
410                    ));
411            }
412        }
413
414        #[cfg(feature = "parser")]
415        if let Err(err) = self.validate_code() {
416            errors.concat(&err);
417        }
418
419        if errors.any() {
420            Err(errors)
421        } else {
422            Ok(())
423        }
424    }
425
426    fn validate_n_value_input(
427        &self,
428        n: usize,
429        function_type: &str,
430    ) -> Result<(), ValidationFailure> {
431        let Some(first_input) = self.inputs.first() else {
432            return Err(ValidationFailure::from(
433                format!(
434                    "{} type function `{}` should have exactly {} input type, found 0",
435                    function_type, self.uses, n
436                )
437                .as_str(),
438            ));
439        };
440
441        let expected_types = n + (first_input.kind == ParameterKind::Key) as usize;
442
443        if self.inputs.len() != expected_types {
444            return Err(ValidationFailure::from(
445                format!(
446                    "{} type function `{}` should have exactly {} input type, found {}",
447                    function_type,
448                    self.uses,
449                    expected_types,
450                    self.inputs.len()
451                )
452                .as_str(),
453            ));
454        }
455
456        Ok(())
457    }
458
459    fn validate_output_present(&self, function_type: &str) -> Result<(), ValidationFailure> {
460        if self.output.is_none() {
461            return Err(ValidationFailure::from(
462                format!(
463                    "{} type function `{}` requires an output type",
464                    function_type, self.uses
465                )
466                .as_str(),
467            ));
468        }
469
470        Ok(())
471    }
472
473    fn validate_output_is_optional(
474        &self,
475        function_type: &str,
476        types_map: &SdfTypesMap,
477    ) -> Result<(), ValidationFailure> {
478        if let Some(output) = &self.output {
479            if output.optional {
480                return Ok(());
481            }
482
483            if let OutputType::Ref(ref ty) = output.type_ {
484                if let Some(resolved_type) = types_map.inner_type_name(&ty.name) {
485                    if let Some((ty, _)) = types_map.get(&resolved_type) {
486                        if matches!(ty, SdfType::Option(_)) {
487                            return Ok(());
488                        }
489                    }
490                }
491            }
492        }
493
494        Err(ValidationFailure::from(
495            format!(
496                "{} type function `{}` requires an optional output type",
497                function_type, self.uses
498            )
499            .as_str(),
500        ))
501    }
502
503    fn validate_inputs_in_scope(&self, types: &SdfTypesMap) -> Result<(), ValidationFailure> {
504        let mut errors = ValidationFailure::new();
505
506        for input in &self.inputs {
507            if !types.contains_key(&input.type_.name) {
508                errors.push_str(
509                    format!(
510                        "function `{}` has invalid input type, {}",
511                        self.uses,
512                        ref_type_error(&input.type_.name)
513                    )
514                    .as_str(),
515                );
516            }
517        }
518
519        if errors.any() {
520            Err(errors)
521        } else {
522            Ok(())
523        }
524    }
525
526    fn validate_output_in_scope(&self, types: &SdfTypesMap) -> Result<(), ValidationFailure> {
527        if let Some(output) = &self.output {
528            if !types.contains_key(output.type_.value_type_name()) {
529                return Err(ValidationFailure::from(
530                    format!(
531                        "function `{}` has invalid output type, {}",
532                        self.uses,
533                        ref_type_error(output.type_.value_type_name())
534                    )
535                    .as_str(),
536                ));
537            }
538        }
539
540        Ok(())
541    }
542
543    #[cfg(feature = "parser")]
544    pub fn update_signature_from_code(&mut self) -> Result<()> {
545        use crate::util::sdf_function_parser::SDFFunctionParser;
546        let mut errors = ValidationFailure::new();
547
548        if let Some(ref code) = self.code_info.code {
549            if code.is_empty() {
550                errors.push_str("Code block is empty");
551            }
552
553            match SDFFunctionParser::parse(&self.code_info.lang, code) {
554                Ok((uses, inputs, output)) => {
555                    self.output = output;
556                    self.inputs = inputs;
557                    self.uses = uses;
558                }
559                Err(err) => {
560                    errors.push_str(&err.to_string());
561                }
562            }
563        }
564
565        if errors.any() {
566            Err(anyhow::anyhow!("{}", errors))
567        } else {
568            Ok(())
569        }
570    }
571
572    fn wit_function(&self, op_type: &OperatorType) -> StandaloneFunc {
573        let mut operator_fn = StandaloneFunc::new(self.uses.to_owned(), false);
574
575        let mut params = Params::empty();
576
577        for input in &self.inputs {
578            let ty = input.type_.wit_type();
579            let ty = if input.optional || matches!(input.kind, ParameterKind::Key) {
580                Type::Option(Box::new(ty))
581            } else {
582                ty
583            };
584
585            params.item(input.name.to_string(), ty);
586        }
587        operator_fn.set_params(params);
588
589        let results = match self.output.as_ref() {
590            None => Some(Type::Result(Box::new(Result_::err(Type::String)))),
591            Some(output) => {
592                let ty = output.type_.wit_type();
593                let ty = if output.optional {
594                    Type::Option(Box::new(ty))
595                } else if matches!(op_type, OperatorType::FlatMap) {
596                    Type::List(Box::new(ty))
597                } else {
598                    ty
599                };
600
601                Some(Type::Result(Box::new(Result_::both(ty, Type::String))))
602            }
603        };
604
605        operator_fn.set_result(results);
606
607        operator_fn
608    }
609
610    pub fn wit_interface(&self, op_type: &OperatorType) -> Interface {
611        let mut interface = Interface::new(format!("{}-service", self.uses));
612
613        if let Some(imported_types) = self.imported_types() {
614            interface.use_(imported_types);
615        }
616
617        for import in self.state_imports(op_type) {
618            interface.use_(import);
619        }
620
621        let wit_fn = self.wit_function(op_type);
622
623        interface.function(wit_fn);
624
625        interface
626    }
627
628    pub fn state_imports(&self, op_type: &OperatorType) -> Vec<Use> {
629        self.states
630            .iter()
631            .filter_map(|s| match s {
632                StepState::Resolved(state) => match &state.type_.value {
633                    crate::wit::metadata::SdfKeyedStateValue::ArrowRow(_) => {
634                        if let OperatorType::WindowAggregate = op_type {
635                            let mut use_value = Use::new("sdf:df/lazy");
636                            use_value.item(DF_VALUE_WIT_TYPE, None);
637
638                            Some(use_value)
639                        } else {
640                            let mut use_value = Use::new("sdf:row-state/row");
641                            use_value.item(ROW_VALUE_WIT_TYPE, None);
642
643                            Some(use_value)
644                        }
645                    }
646                    crate::wit::metadata::SdfKeyedStateValue::U32 => {
647                        let mut use_value = Use::new("sdf:value-state/values");
648
649                        if let OperatorType::WindowAggregate = op_type {
650                            use_value.item(I32_LIST_VALUE_WIT_TYPE, None);
651                            Some(use_value)
652                        } else {
653                            use_value.item(I32_VALUE_WIT_TYPE, None);
654                            Some(use_value)
655                        }
656                    }
657                    crate::wit::metadata::SdfKeyedStateValue::Unresolved(_) => None,
658                },
659                StepState::Unresolved(_) => None,
660            })
661            .collect::<BTreeSet<_>>()
662            .into_iter()
663            .collect()
664    }
665
666    pub fn imported_types(&self) -> Option<Use> {
667        let types_to_import = self
668            .inputs
669            .iter()
670            .map(|iter| iter.type_.name.clone())
671            .chain(
672                self.output
673                    .as_ref()
674                    .map(|output| output.type_.value_type_name().to_owned()),
675            )
676            .chain(
677                self.output
678                    .as_ref()
679                    .and_then(|output| output.type_.key_type_name().map(|s| s.to_owned())),
680            )
681            .chain(self.states.iter().filter_map(|s| match s {
682                StepState::Resolved(state) => Some(state.name.to_owned()),
683                StepState::Unresolved(_) => None,
684            }))
685            .collect::<BTreeSet<_>>();
686
687        let items = types_to_import
688            .into_iter()
689            .filter(|t| is_imported_type(t))
690            .map(|t| map_wit_keyword(&t))
691            .collect::<Vec<_>>();
692
693        if items.is_empty() {
694            return None;
695        }
696
697        let mut uses = Use::new("types");
698        for item in items {
699            uses.item(wit_name_case(&item), None);
700        }
701
702        Some(uses)
703    }
704
705    pub fn deserialize_input_wit_interface(&self) -> Interface {
706        let mut iface = Interface::new(format!("deserialize-{}", self.uses));
707
708        let mut deserialize_key_fn = StandaloneFunc::new("deserialize-key", false);
709        let mut params = Params::empty();
710        params.item("key", Type::option(Type::String));
711        deserialize_key_fn.set_params(params);
712
713        let mut imported_types = BTreeSet::new();
714
715        if let Some(key_type) = self.input_key_type() {
716            deserialize_key_fn.set_result(Some(Type::result_both(
717                Type::option(key_type.wit_type()),
718                Type::String,
719            )));
720
721            if is_imported_type(&key_type.name) {
722                imported_types.insert(map_wit_keyword(&key_type.name));
723            }
724        } else {
725            deserialize_key_fn.set_result(Some(Type::result_both(
726                Type::option(Type::list(Type::U8)),
727                Type::String,
728            )));
729        }
730
731        iface.function(deserialize_key_fn);
732
733        let mut deserialize_value_fn = StandaloneFunc::new("deserialize-input", false);
734        let mut params = Params::empty();
735        params.item("value", Type::String);
736        deserialize_value_fn.set_params(params);
737
738        if let Some(value_type) = self.input_value_type() {
739            deserialize_value_fn
740                .set_result(Some(Type::result_both(value_type.wit_type(), Type::String)));
741            if is_imported_type(&value_type.name) {
742                imported_types.insert(map_wit_keyword(&value_type.name));
743            }
744        } else {
745            deserialize_value_fn
746                .set_result(Some(Type::result_both(Type::list(Type::U8), Type::String)));
747        }
748
749        if !imported_types.is_empty() {
750            let mut uses = Use::new("types");
751            for ty in imported_types {
752                uses.item(wit_name_case(&ty), None);
753            }
754
755            iface.use_(uses);
756        }
757        iface.function(deserialize_value_fn);
758
759        iface
760    }
761
762    pub fn serialize_output_wit_interface(&self) -> Option<Interface> {
763        let output_value = self.output_value_type()?;
764
765        let mut iface = Interface::new(format!("serialize-{}", self.uses));
766        let mut imported_types = BTreeSet::new();
767
768        let mut serialize_key_fn = StandaloneFunc::new("serialize-key", false);
769        let mut params = Params::empty();
770        if let Some(output_key) = self.output_key_type() {
771            params.item("input", Type::option(output_key.wit_type()));
772            if is_imported_type(&output_key.name) {
773                imported_types.insert(map_wit_keyword(&output_key.name));
774            }
775        } else {
776            params.item("input", Type::option(Type::list(Type::U8)));
777        }
778        serialize_key_fn.set_params(params);
779
780        serialize_key_fn.set_result(Some(Type::result_both(
781            Type::option(Type::list(Type::U8)),
782            Type::String,
783        )));
784
785        iface.function(serialize_key_fn);
786        let mut serialize_value_fn = StandaloneFunc::new("serialize-output", false);
787        let mut params = Params::empty();
788
789        if is_imported_type(&output_value.name) {
790            imported_types.insert(map_wit_keyword(&output_value.name));
791        }
792        params.item("input", output_value.wit_type());
793        serialize_value_fn.set_params(params);
794        serialize_value_fn.set_result(Some(Type::result_both(Type::list(Type::U8), Type::String)));
795        iface.function(serialize_value_fn);
796        if !imported_types.is_empty() {
797            let mut uses = Use::new("types");
798            for ty in imported_types {
799                uses.item(wit_name_case(&ty), None);
800            }
801
802            iface.use_(uses);
803        }
804        Some(iface)
805    }
806
807    fn input_key_type(&self) -> Option<TypeRef> {
808        self.inputs
809            .iter()
810            .find(|input| matches!(input.kind, ParameterKind::Key))
811            .map(|input| input.type_.clone())
812    }
813
814    fn input_value_type(&self) -> Option<TypeRef> {
815        self.inputs
816            .iter()
817            .find(|input| matches!(input.kind, ParameterKind::Value))
818            .map(|input| input.type_.clone())
819    }
820
821    fn output_key_type(&self) -> Option<TypeRef> {
822        self.output
823            .as_ref()
824            .and_then(|output| output.type_.key_type())
825            .cloned()
826    }
827
828    fn output_value_type(&self) -> Option<TypeRef> {
829        self.output
830            .as_ref()
831            .map(|output| output.type_.value_type())
832            .cloned()
833    }
834}
835
836fn ref_type_error(name: &str) -> String {
837    format!(
838        "Referenced type `{}` not found in config or imported types",
839        name
840    )
841}
842
843#[allow(clippy::derivable_impls)]
844impl Default for StepInvocation {
845    fn default() -> Self {
846        StepInvocation {
847            uses: String::new(),
848            inputs: Vec::new(),
849            output: None,
850            states: Vec::new(),
851            imported_function_metadata: None,
852            code_info: CodeInfo::default(),
853            system: false,
854            params: None,
855        }
856    }
857}
858
859#[cfg(test)]
860mod test {
861    use sdf_common::display::WitInterfaceDisplay;
862
863    use crate::{
864        metadata::operator::step_invocation::ref_type_error,
865        util::{sdf_types_map::SdfTypesMap, validation_error::ValidationError},
866        wit::{
867            io::TypeRef,
868            metadata::{
869                NamedParameter, Parameter, ParameterKind, SdfArrowRow, SdfKeyValue, SdfType,
870            },
871            operator::{OperatorType, StepInvocation},
872        },
873    };
874    #[cfg(feature = "parser")]
875    use crate::wit::operator::{CodeInfo, CodeLang};
876
877    fn types() -> SdfTypesMap {
878        SdfTypesMap::default()
879    }
880
881    #[test]
882    fn test_validate_filter_requires_one_input() {
883        let function = StepInvocation {
884            uses: "my-filter".to_string(),
885            inputs: vec![],
886            ..Default::default()
887        };
888
889        let res = function
890            .validate_filter(&types())
891            .expect_err("should error for missing input type");
892
893        assert!(res.errors.contains(&ValidationError::new(
894            "filter type function `my-filter` should have exactly 1 input type, found 0"
895        )));
896
897        let function = StepInvocation {
898            uses: "my-filter".to_string(),
899            inputs: vec![
900                NamedParameter {
901                    name: "input".to_string(),
902                    type_: TypeRef {
903                        name: "u8".to_string(),
904                    },
905                    optional: false,
906                    kind: ParameterKind::Value,
907                },
908                NamedParameter {
909                    name: "other-input".to_string(),
910                    type_: TypeRef {
911                        name: "u8".to_string(),
912                    },
913                    optional: false,
914                    kind: ParameterKind::Value,
915                },
916            ],
917            ..Default::default()
918        };
919
920        let res = function
921            .validate_filter(&types())
922            .expect_err("should error for too many inputs");
923
924        assert!(res.errors.contains(&ValidationError::new(
925            "filter type function `my-filter` should have exactly 1 input type, found 2"
926        )));
927    }
928
929    #[test]
930    fn test_validate_filter_requires_bool_output() {
931        let function = StepInvocation {
932            uses: "my-filter".to_string(),
933            output: None,
934            ..Default::default()
935        };
936
937        let res = function
938            .validate_filter(&types())
939            .expect_err("should error for missing output");
940
941        assert!(res.errors.contains(&ValidationError::new(
942            "filter type function `my-filter` requires an output type of `bool`, but found no type"
943        )));
944
945        let function = StepInvocation {
946            uses: "my-filter".to_string(),
947            output: Some(Parameter {
948                type_: TypeRef {
949                    name: "string".to_string(),
950                }
951                .into(),
952                ..Default::default()
953            }),
954            ..Default::default()
955        };
956
957        let res = function
958            .validate_filter(&types())
959            .expect_err("should error for missing output");
960
961        assert!(res.errors.contains(&ValidationError::new(
962            "filter type function `my-filter` requires an output type of `bool`, but found `string`"
963        )));
964    }
965
966    #[test]
967    fn test_validate_filter_requires_input_type_is_in_scope() {
968        let function = StepInvocation {
969            uses: "my-filter".to_string(),
970            inputs: vec![NamedParameter {
971                name: "input".to_string(),
972                type_: TypeRef {
973                    name: "foobar".to_string(),
974                },
975                optional: false,
976                kind: ParameterKind::Value,
977            }],
978            output: Some(Parameter {
979                type_: TypeRef {
980                    name: "string".to_string(),
981                }
982                .into(),
983                ..Default::default()
984            }),
985            ..Default::default()
986        };
987
988        let res = function
989            .validate_filter(&types())
990            .expect_err("should error for input type not in scope");
991
992        assert!(res.errors.contains(&ValidationError::new(&format!(
993            "function `my-filter` has invalid input type, {}",
994            &ref_type_error("foobar")
995        ))));
996    }
997
998    #[test]
999    fn test_validate_filter_requires_output_type_is_in_scope() {
1000        let function = StepInvocation {
1001            uses: "my-filter".to_string(),
1002            inputs: vec![NamedParameter {
1003                name: "input".to_string(),
1004                type_: TypeRef {
1005                    name: "string".to_string(),
1006                },
1007                optional: false,
1008                kind: ParameterKind::Value,
1009            }],
1010            output: Some(Parameter {
1011                type_: TypeRef {
1012                    name: "foobar".to_string(),
1013                }
1014                .into(),
1015                ..Default::default()
1016            }),
1017            ..Default::default()
1018        };
1019
1020        let res = function
1021            .validate_filter(&types())
1022            .expect_err("should error for output type not in scope");
1023
1024        assert!(res.errors.contains(&ValidationError::new(&format!(
1025            "function `my-filter` has invalid output type, {}",
1026            &ref_type_error("foobar")
1027        ))));
1028    }
1029
1030    #[test]
1031    fn test_validate_filter_accepts_valid_functions() {
1032        let function = StepInvocation {
1033            uses: "my-filter".to_string(),
1034            inputs: vec![NamedParameter {
1035                name: "input".to_string(),
1036                type_: TypeRef {
1037                    name: "string".to_string(),
1038                },
1039                optional: false,
1040                kind: ParameterKind::Value,
1041            }],
1042            output: Some(Parameter {
1043                type_: TypeRef {
1044                    name: "bool".to_string(),
1045                }
1046                .into(),
1047                ..Default::default()
1048            }),
1049            ..Default::default()
1050        };
1051
1052        function.validate_filter(&types()).expect("should validate");
1053    }
1054
1055    #[test]
1056    fn test_validate_map_requires_one_input() {
1057        let function = StepInvocation {
1058            uses: "my-map".to_string(),
1059            inputs: vec![],
1060            ..Default::default()
1061        };
1062
1063        let res = function
1064            .validate_map(&types())
1065            .expect_err("should error for missing input type");
1066
1067        assert!(res.errors.contains(&ValidationError::new(
1068            "map type function `my-map` should have exactly 1 input type, found 0"
1069        )));
1070
1071        let function = StepInvocation {
1072            uses: "my-map".to_string(),
1073            inputs: vec![
1074                NamedParameter {
1075                    name: "input".to_string(),
1076                    type_: TypeRef {
1077                        name: "u8".to_string(),
1078                    },
1079                    optional: false,
1080                    kind: ParameterKind::Value,
1081                },
1082                NamedParameter {
1083                    name: "other-input".to_string(),
1084                    type_: TypeRef {
1085                        name: "u8".to_string(),
1086                    },
1087                    optional: false,
1088                    kind: ParameterKind::Value,
1089                },
1090            ],
1091            ..Default::default()
1092        };
1093
1094        let res = function
1095            .validate_map(&types())
1096            .expect_err("should error for too many inputs");
1097
1098        assert!(res.errors.contains(&ValidationError::new(
1099            "map type function `my-map` should have exactly 1 input type, found 2"
1100        )));
1101    }
1102
1103    #[test]
1104    fn test_validate_map_requires_one_output() {
1105        let function = StepInvocation {
1106            uses: "my-map".to_string(),
1107            output: None,
1108            ..Default::default()
1109        };
1110
1111        let res = function
1112            .validate_map(&types())
1113            .expect_err("should error for missing output");
1114
1115        assert!(res.errors.contains(&ValidationError::new(
1116            "map type function `my-map` requires an output type"
1117        )));
1118    }
1119
1120    #[test]
1121    fn test_validate_map_requires_input_type_is_in_scope() {
1122        let function = StepInvocation {
1123            uses: "my-map".to_string(),
1124            inputs: vec![NamedParameter {
1125                name: "input".to_string(),
1126                type_: TypeRef {
1127                    name: "foobar".to_string(),
1128                },
1129                optional: false,
1130                kind: ParameterKind::Value,
1131            }],
1132            output: Some(Parameter {
1133                type_: TypeRef {
1134                    name: "string".to_string(),
1135                }
1136                .into(),
1137                ..Default::default()
1138            }),
1139            ..Default::default()
1140        };
1141
1142        let res = function
1143            .validate_map(&types())
1144            .expect_err("should error for input type not in scope");
1145
1146        assert!(res.errors.contains(&ValidationError::new(&format!(
1147            "function `my-map` has invalid input type, {}",
1148            &ref_type_error("foobar")
1149        ))));
1150    }
1151
1152    #[test]
1153    fn test_validate_map_requires_output_type_is_in_scope() {
1154        let function = StepInvocation {
1155            uses: "my-map".to_string(),
1156            inputs: vec![NamedParameter {
1157                name: "input".to_string(),
1158                type_: TypeRef {
1159                    name: "string".to_string(),
1160                },
1161                optional: false,
1162                kind: ParameterKind::Value,
1163            }],
1164            output: Some(Parameter {
1165                type_: TypeRef {
1166                    name: "foobar".to_string(),
1167                }
1168                .into(),
1169                ..Default::default()
1170            }),
1171            ..Default::default()
1172        };
1173
1174        let res = function
1175            .validate_map(&types())
1176            .expect_err("should error for output type not in scope");
1177
1178        assert!(res.errors.contains(&ValidationError::new(&format!(
1179            "function `my-map` has invalid output type, {}",
1180            &ref_type_error("foobar")
1181        ))));
1182    }
1183
1184    #[test]
1185    fn test_validate_map_accepts_valid_functions() {
1186        let function = StepInvocation {
1187            uses: "my-map".to_string(),
1188            inputs: vec![NamedParameter {
1189                name: "input".to_string(),
1190                type_: TypeRef {
1191                    name: "string".to_string(),
1192                },
1193                optional: false,
1194                kind: ParameterKind::Value,
1195            }],
1196            output: Some(Parameter {
1197                type_: TypeRef {
1198                    name: "u8".to_string(),
1199                }
1200                .into(),
1201                ..Default::default()
1202            }),
1203            ..Default::default()
1204        };
1205
1206        function.validate_map(&types()).expect("should validate");
1207    }
1208
1209    #[test]
1210    fn test_validate_flat_map_requires_one_input() {
1211        let function = StepInvocation {
1212            uses: "my-flat-map".to_string(),
1213            inputs: vec![],
1214            ..Default::default()
1215        };
1216
1217        let res = function
1218            .validate_flat_map(&types())
1219            .expect_err("should error for missing input type");
1220
1221        assert!(res.errors.contains(&ValidationError::new(
1222            "flat-map type function `my-flat-map` should have exactly 1 input type, found 0"
1223        )));
1224
1225        let function = StepInvocation {
1226            uses: "my-flat-map".to_string(),
1227            inputs: vec![
1228                NamedParameter {
1229                    name: "input".to_string(),
1230                    type_: TypeRef {
1231                        name: "u8".to_string(),
1232                    },
1233                    optional: false,
1234                    kind: ParameterKind::Value,
1235                },
1236                NamedParameter {
1237                    name: "other-input".to_string(),
1238                    type_: TypeRef {
1239                        name: "u8".to_string(),
1240                    },
1241                    optional: false,
1242                    kind: ParameterKind::Value,
1243                },
1244            ],
1245            ..Default::default()
1246        };
1247
1248        let res = function
1249            .validate_flat_map(&types())
1250            .expect_err("should error for too many inputs");
1251
1252        assert!(res.errors.contains(&ValidationError::new(
1253            "flat-map type function `my-flat-map` should have exactly 1 input type, found 2"
1254        )));
1255    }
1256
1257    #[test]
1258    fn test_validate_flat_map_requires_one_output() {
1259        let function = StepInvocation {
1260            uses: "my-flat-map".to_string(),
1261            output: None,
1262            ..Default::default()
1263        };
1264
1265        let res = function
1266            .validate_flat_map(&types())
1267            .expect_err("should error for missing output");
1268
1269        assert!(res.errors.contains(&ValidationError::new(
1270            "flat-map type function `my-flat-map` requires an output type"
1271        )));
1272    }
1273
1274    #[test]
1275    fn test_validate_flat_map_requires_input_type_is_in_scope() {
1276        let function = StepInvocation {
1277            uses: "my-flat-map".to_string(),
1278            inputs: vec![NamedParameter {
1279                name: "input".to_string(),
1280                type_: TypeRef {
1281                    name: "foobar".to_string(),
1282                },
1283                optional: false,
1284                kind: ParameterKind::Value,
1285            }],
1286            output: Some(Parameter {
1287                type_: TypeRef {
1288                    name: "string".to_string(),
1289                }
1290                .into(),
1291                ..Default::default()
1292            }),
1293            ..Default::default()
1294        };
1295
1296        let res = function
1297            .validate_flat_map(&types())
1298            .expect_err("should error for input type not in scope");
1299
1300        assert!(res.errors.contains(&ValidationError::new(&format!(
1301            "function `my-flat-map` has invalid input type, {}",
1302            &ref_type_error("foobar")
1303        ))));
1304    }
1305
1306    #[test]
1307    fn test_validate_flat_map_requires_output_type_is_in_scope() {
1308        let function = StepInvocation {
1309            uses: "my-flat-map".to_string(),
1310            inputs: vec![NamedParameter {
1311                name: "input".to_string(),
1312                type_: TypeRef {
1313                    name: "string".to_string(),
1314                },
1315                optional: false,
1316                kind: ParameterKind::Value,
1317            }],
1318            output: Some(Parameter {
1319                type_: TypeRef {
1320                    name: "foobar".to_string(),
1321                }
1322                .into(),
1323                ..Default::default()
1324            }),
1325            ..Default::default()
1326        };
1327
1328        let res = function
1329            .validate_flat_map(&types())
1330            .expect_err("should error for output type not in scope");
1331
1332        assert!(res.errors.contains(&ValidationError::new(&format!(
1333            "function `my-flat-map` has invalid output type, {}",
1334            &ref_type_error("foobar")
1335        ))));
1336    }
1337
1338    #[test]
1339    fn test_validate_flat_map_accepts_valid_functions() {
1340        let function = StepInvocation {
1341            uses: "my-flat-map".to_string(),
1342            inputs: vec![NamedParameter {
1343                name: "input".to_string(),
1344                type_: TypeRef {
1345                    name: "string".to_string(),
1346                },
1347                optional: false,
1348                kind: ParameterKind::Value,
1349            }],
1350            output: Some(Parameter {
1351                type_: TypeRef {
1352                    name: "u8".to_string(),
1353                }
1354                .into(),
1355                ..Default::default()
1356            }),
1357            ..Default::default()
1358        };
1359
1360        function
1361            .validate_flat_map(&types())
1362            .expect("should validate");
1363    }
1364
1365    #[test]
1366    fn test_validate_filter_map_requires_one_input() {
1367        let function = StepInvocation {
1368            uses: "my-filter-map".to_string(),
1369            inputs: vec![],
1370            ..Default::default()
1371        };
1372
1373        let res = function
1374            .validate_filter_map(&types())
1375            .expect_err("should error for missing input type");
1376
1377        assert!(res.errors.contains(&ValidationError::new(
1378            "filter-map type function `my-filter-map` should have exactly 1 input type, found 0"
1379        )));
1380
1381        let function = StepInvocation {
1382            uses: "my-filter-map".to_string(),
1383            inputs: vec![
1384                NamedParameter {
1385                    name: "input".to_string(),
1386                    type_: TypeRef {
1387                        name: "u8".to_string(),
1388                    },
1389                    optional: false,
1390                    kind: ParameterKind::Value,
1391                },
1392                NamedParameter {
1393                    name: "other-input".to_string(),
1394                    type_: TypeRef {
1395                        name: "u8".to_string(),
1396                    },
1397                    optional: false,
1398                    kind: ParameterKind::Value,
1399                },
1400            ],
1401            ..Default::default()
1402        };
1403
1404        let res = function
1405            .validate_filter_map(&types())
1406            .expect_err("should error for too many inputs");
1407
1408        assert!(res.errors.contains(&ValidationError::new(
1409            "filter-map type function `my-filter-map` should have exactly 1 input type, found 2"
1410        )));
1411    }
1412
1413    #[test]
1414    fn test_validate_filter_map_requires_one_output() {
1415        let function = StepInvocation {
1416            uses: "my-filter-map".to_string(),
1417            output: None,
1418            ..Default::default()
1419        };
1420
1421        let res = function
1422            .validate_filter_map(&types())
1423            .expect_err("should error for missing output");
1424
1425        assert!(res.errors.contains(&ValidationError::new(
1426            "filter-map type function `my-filter-map` requires an output type"
1427        )));
1428    }
1429
1430    #[test]
1431    fn test_validate_filter_map_requires_optional_output() {
1432        let function = StepInvocation {
1433            uses: "my-filter-map".to_string(),
1434            output: Some(Parameter {
1435                type_: TypeRef {
1436                    name: "string".to_string(),
1437                }
1438                .into(),
1439                ..Default::default()
1440            }),
1441            ..Default::default()
1442        };
1443
1444        let res = function
1445            .validate_filter_map(&types())
1446            .expect_err("should error for missing output");
1447
1448        assert!(res.errors.contains(&ValidationError::new(
1449            "filter-map type function `my-filter-map` requires an optional output type"
1450        )));
1451    }
1452
1453    #[test]
1454    fn test_validate_filter_map_requires_input_type_is_in_scope() {
1455        let function = StepInvocation {
1456            uses: "my-filter-map".to_string(),
1457            inputs: vec![NamedParameter {
1458                name: "input".to_string(),
1459                type_: TypeRef {
1460                    name: "foobar".to_string(),
1461                },
1462                optional: false,
1463                kind: ParameterKind::Value,
1464            }],
1465            output: Some(Parameter {
1466                type_: TypeRef {
1467                    name: "string".to_string(),
1468                }
1469                .into(),
1470                optional: true,
1471            }),
1472            ..Default::default()
1473        };
1474
1475        let res = function
1476            .validate_filter_map(&types())
1477            .expect_err("should error for input type not in scope");
1478
1479        assert!(res.errors.contains(&ValidationError::new(&format!(
1480            "function `my-filter-map` has invalid input type, {}",
1481            &ref_type_error("foobar")
1482        ))));
1483    }
1484
1485    #[test]
1486    fn test_validate_filter_map_requires_output_type_is_in_scope() {
1487        let function = StepInvocation {
1488            uses: "my-filter-map".to_string(),
1489            inputs: vec![NamedParameter {
1490                name: "input".to_string(),
1491                type_: TypeRef {
1492                    name: "string".to_string(),
1493                },
1494                optional: false,
1495                kind: ParameterKind::Value,
1496            }],
1497            output: Some(Parameter {
1498                type_: TypeRef {
1499                    name: "foobar".to_string(),
1500                }
1501                .into(),
1502                ..Default::default()
1503            }),
1504            ..Default::default()
1505        };
1506
1507        let res = function
1508            .validate_filter_map(&types())
1509            .expect_err("should error for output type not in scope");
1510
1511        assert!(res.errors.contains(&ValidationError::new(&format!(
1512            "function `my-filter-map` has invalid output type, {}",
1513            &ref_type_error("foobar")
1514        ))));
1515    }
1516
1517    #[test]
1518    fn test_validate_filter_map_accepts_valid_functions() {
1519        let function = StepInvocation {
1520            uses: "my-filter-map".to_string(),
1521            inputs: vec![NamedParameter {
1522                name: "input".to_string(),
1523                type_: TypeRef {
1524                    name: "string".to_string(),
1525                },
1526                optional: false,
1527                kind: ParameterKind::Value,
1528            }],
1529            output: Some(Parameter {
1530                type_: TypeRef {
1531                    name: "u8".to_string(),
1532                }
1533                .into(),
1534                optional: true,
1535            }),
1536            ..Default::default()
1537        };
1538
1539        function
1540            .validate_filter_map(&types())
1541            .expect("should validate");
1542    }
1543
1544    #[test]
1545    fn test_validate_update_state_requires_one_input() {
1546        let function = StepInvocation {
1547            uses: "my-update-state".to_string(),
1548            inputs: vec![],
1549            ..Default::default()
1550        };
1551
1552        let res = function
1553            .validate_update_state(&types())
1554            .expect_err("should error for missing input type");
1555
1556        assert!(res.errors.contains(&ValidationError::new(
1557            "update-state type function `my-update-state` should have exactly 1 input type, found 0"
1558        )));
1559
1560        let function = StepInvocation {
1561            uses: "my-update-state".to_string(),
1562            inputs: vec![
1563                NamedParameter {
1564                    name: "input".to_string(),
1565                    type_: TypeRef {
1566                        name: "u8".to_string(),
1567                    },
1568                    optional: false,
1569                    kind: ParameterKind::Value,
1570                },
1571                NamedParameter {
1572                    name: "other-input".to_string(),
1573                    type_: TypeRef {
1574                        name: "u8".to_string(),
1575                    },
1576                    optional: false,
1577                    kind: ParameterKind::Value,
1578                },
1579            ],
1580            ..Default::default()
1581        };
1582
1583        let res = function
1584            .validate_update_state(&types())
1585            .expect_err("should error for too many inputs");
1586
1587        assert!(res.errors.contains(&ValidationError::new(
1588            "update-state type function `my-update-state` should have exactly 1 input type, found 2"
1589        )));
1590    }
1591
1592    #[test]
1593    fn test_validate_update_state_requires_no_output() {
1594        let function = StepInvocation {
1595            uses: "my-update-state".to_string(),
1596            output: Some(Parameter {
1597                type_: TypeRef {
1598                    name: "string".to_string(),
1599                }
1600                .into(),
1601                ..Default::default()
1602            }),
1603            ..Default::default()
1604        };
1605
1606        let res = function
1607            .validate_update_state(&types())
1608            .expect_err("should error for unexpected output type");
1609
1610        assert!(res.errors.contains(&ValidationError::new(
1611            "update-state type function `my-update-state` should have no output, but found `string`"
1612        )));
1613    }
1614
1615    #[test]
1616    fn test_validate_update_state_requires_input_type_is_in_scope() {
1617        let function = StepInvocation {
1618            uses: "my-update-state".to_string(),
1619            inputs: vec![NamedParameter {
1620                name: "input".to_string(),
1621                type_: TypeRef {
1622                    name: "foobar".to_string(),
1623                },
1624                optional: false,
1625                kind: ParameterKind::Value,
1626            }],
1627            output: Some(Parameter {
1628                type_: TypeRef {
1629                    name: "string".to_string(),
1630                }
1631                .into(),
1632                ..Default::default()
1633            }),
1634            ..Default::default()
1635        };
1636
1637        let res = function
1638            .validate_update_state(&types())
1639            .expect_err("should error for input type not in scope");
1640
1641        assert!(res.errors.contains(&ValidationError::new(&format!(
1642            "function `my-update-state` has invalid input type, {}",
1643            &ref_type_error("foobar")
1644        ))));
1645    }
1646
1647    #[test]
1648    fn test_validate_update_state_accepts_valid_function() {
1649        let function = StepInvocation {
1650            uses: "my-update-state".to_string(),
1651            inputs: vec![NamedParameter {
1652                name: "input".to_string(),
1653                type_: TypeRef {
1654                    name: "string".to_string(),
1655                },
1656                optional: false,
1657                kind: ParameterKind::Value,
1658            }],
1659            ..Default::default()
1660        };
1661
1662        function
1663            .validate_update_state(&types())
1664            .expect("should validate");
1665    }
1666
1667    #[test]
1668    fn test_validate_window_aggregate_requires_no_input() {
1669        let function = StepInvocation {
1670            uses: "my-window-aggregate".to_string(),
1671            inputs: vec![NamedParameter {
1672                name: "input".to_string(),
1673                type_: TypeRef {
1674                    name: "string".to_string(),
1675                },
1676                optional: false,
1677                kind: ParameterKind::Value,
1678            }],
1679            ..Default::default()
1680        };
1681
1682        let res = function
1683            .validate_window_aggregate(&types())
1684            .expect_err("should error for unexpected input type");
1685
1686        assert!(res.errors.contains(&ValidationError::new(
1687            "window-aggregate type function `my-window-aggregate` should have no input type, but found [input: string]"
1688        )));
1689    }
1690
1691    #[test]
1692    fn test_validate_window_aggregate_requires_one_output() {
1693        let function = StepInvocation {
1694            uses: "my-window-aggregate".to_string(),
1695            output: None,
1696            ..Default::default()
1697        };
1698
1699        let res = function
1700            .validate_window_aggregate(&types())
1701            .expect_err("should error for missing output");
1702
1703        assert!(res.errors.contains(&ValidationError::new(
1704            "window-aggregate type function `my-window-aggregate` requires an output type"
1705        )));
1706    }
1707
1708    #[test]
1709    fn test_validate_window_aggregate_requires_output_type_is_in_scope() {
1710        let function = StepInvocation {
1711            uses: "my-window-aggregate".to_string(),
1712            inputs: vec![NamedParameter {
1713                name: "input".to_string(),
1714                type_: TypeRef {
1715                    name: "string".to_string(),
1716                },
1717                optional: false,
1718                kind: ParameterKind::Value,
1719            }],
1720            output: Some(Parameter {
1721                type_: TypeRef {
1722                    name: "foobar".to_string(),
1723                }
1724                .into(),
1725                ..Default::default()
1726            }),
1727            ..Default::default()
1728        };
1729
1730        let res = function
1731            .validate_window_aggregate(&types())
1732            .expect_err("should error for output type not in scope");
1733
1734        assert!(res.errors.contains(&ValidationError::new(&format!(
1735            "function `my-window-aggregate` has invalid output type, {}",
1736            &ref_type_error("foobar")
1737        ))));
1738    }
1739
1740    #[test]
1741    fn test_validate_window_aggregate_accepts_valid_functions() {
1742        let function = StepInvocation {
1743            uses: "my-window-aggregate".to_string(),
1744            output: Some(Parameter {
1745                type_: TypeRef {
1746                    name: "string".to_string(),
1747                }
1748                .into(),
1749                ..Default::default()
1750            }),
1751            ..Default::default()
1752        };
1753
1754        function
1755            .validate_window_aggregate(&types())
1756            .expect("should validate");
1757    }
1758
1759    #[test]
1760    fn test_validate_assign_key_requires_one_input() {
1761        let function = StepInvocation {
1762            uses: "my-assign-key".to_string(),
1763            inputs: vec![],
1764            ..Default::default()
1765        };
1766
1767        let res = function
1768            .validate_assign_key(&types())
1769            .expect_err("should error for missing input type");
1770
1771        assert!(res.errors.contains(&ValidationError::new(
1772            "assign-key type function `my-assign-key` should have exactly 1 input type, found 0"
1773        )));
1774
1775        let function = StepInvocation {
1776            uses: "my-assign-key".to_string(),
1777            inputs: vec![
1778                NamedParameter {
1779                    name: "input".to_string(),
1780                    type_: TypeRef {
1781                        name: "u8".to_string(),
1782                    },
1783                    optional: false,
1784                    kind: ParameterKind::Value,
1785                },
1786                NamedParameter {
1787                    name: "other-input".to_string(),
1788                    type_: TypeRef {
1789                        name: "u8".to_string(),
1790                    },
1791                    optional: false,
1792                    kind: ParameterKind::Value,
1793                },
1794            ],
1795            ..Default::default()
1796        };
1797
1798        let res = function
1799            .validate_assign_key(&types())
1800            .expect_err("should error for too many inputs");
1801
1802        assert!(res.errors.contains(&ValidationError::new(
1803            "assign-key type function `my-assign-key` should have exactly 1 input type, found 2"
1804        )));
1805    }
1806
1807    #[test]
1808    fn test_validate_assign_key_requires_one_output() {
1809        let function = StepInvocation {
1810            uses: "my-assign-key".to_string(),
1811            output: None,
1812            ..Default::default()
1813        };
1814
1815        let res = function
1816            .validate_assign_key(&types())
1817            .expect_err("should error for missing output");
1818
1819        assert!(res.errors.contains(&ValidationError::new(
1820            "assign-key type function `my-assign-key` requires an output type"
1821        )));
1822    }
1823
1824    #[test]
1825    fn test_validate_assign_key_requires_input_type_is_in_scope() {
1826        let function = StepInvocation {
1827            uses: "my-assign-key".to_string(),
1828            inputs: vec![NamedParameter {
1829                name: "input".to_string(),
1830                type_: TypeRef {
1831                    name: "foobar".to_string(),
1832                },
1833                optional: false,
1834                kind: ParameterKind::Value,
1835            }],
1836            output: Some(Parameter {
1837                type_: TypeRef {
1838                    name: "string".to_string(),
1839                }
1840                .into(),
1841                ..Default::default()
1842            }),
1843            ..Default::default()
1844        };
1845
1846        let res = function
1847            .validate_assign_key(&types())
1848            .expect_err("should error for input type not in scope");
1849
1850        assert!(res.errors.contains(&ValidationError::new(&format!(
1851            "function `my-assign-key` has invalid input type, {}",
1852            &ref_type_error("foobar")
1853        ))));
1854    }
1855
1856    #[test]
1857    fn test_validate_assign_key_requires_output_type_is_in_scope() {
1858        let function = StepInvocation {
1859            uses: "my-assign-key".to_string(),
1860            inputs: vec![NamedParameter {
1861                name: "input".to_string(),
1862                type_: TypeRef {
1863                    name: "string".to_string(),
1864                },
1865                optional: false,
1866                kind: ParameterKind::Value,
1867            }],
1868
1869            output: Some(Parameter {
1870                type_: TypeRef {
1871                    name: "foobar".to_string(),
1872                }
1873                .into(),
1874                ..Default::default()
1875            }),
1876            ..Default::default()
1877        };
1878
1879        let res = function
1880            .validate_assign_key(&types())
1881            .expect_err("should error for output type not in scope");
1882
1883        assert!(res.errors.contains(&ValidationError::new(&format!(
1884            "function `my-assign-key` has invalid output type, {}",
1885            &ref_type_error("foobar")
1886        ))));
1887    }
1888
1889    #[test]
1890    fn test_validate_assign_key_requires_output_type_is_hashable() {
1891        let mut types = types();
1892        types.insert_local(
1893            "my-state-value".to_string(),
1894            SdfType::ArrowRow(SdfArrowRow::default()),
1895        );
1896
1897        let function = StepInvocation {
1898            uses: "my-assign-key".to_string(),
1899            inputs: vec![NamedParameter {
1900                name: "input".to_string(),
1901                type_: TypeRef {
1902                    name: "string".to_string(),
1903                },
1904                optional: false,
1905                kind: ParameterKind::Value,
1906            }],
1907            output: Some(Parameter {
1908                type_: TypeRef {
1909                    name: "my-state-value".to_string(),
1910                }
1911                .into(),
1912                ..Default::default()
1913            }),
1914            ..Default::default()
1915        };
1916
1917        let res = function
1918            .validate_assign_key(&types)
1919            .expect_err("should error for output type not hashable");
1920
1921        assert!(res.errors.contains(&ValidationError::new(r#"output type for assign-key type function `my-assign-key` must be hashable, or a reference to a hashable type. found `my-state-value`.
1922 hashable types: [u8, u16, u32, u64, s8, s16, s32, s64, bool, string, f32, f64]"#)));
1923    }
1924
1925    #[test]
1926    fn test_validate_assign_key_accepts_valid_function() {
1927        let function = StepInvocation {
1928            uses: "my-assign-key".to_string(),
1929            inputs: vec![NamedParameter {
1930                name: "input".to_string(),
1931                type_: TypeRef {
1932                    name: "string".to_string(),
1933                },
1934                optional: false,
1935                kind: ParameterKind::Value,
1936            }],
1937            output: Some(Parameter {
1938                type_: TypeRef {
1939                    name: "string".to_string(),
1940                }
1941                .into(),
1942                ..Default::default()
1943            }),
1944            ..Default::default()
1945        };
1946
1947        function
1948            .validate_assign_key(&types())
1949            .expect("should be valid");
1950    }
1951
1952    #[test]
1953    fn test_validate_assign_timestamp_requires_two_inputs() {
1954        let function = StepInvocation {
1955            uses: "my-assign-timestamp".to_string(),
1956            inputs: vec![NamedParameter {
1957                name: "input".to_string(),
1958                type_: TypeRef {
1959                    name: "i64".to_string(),
1960                },
1961                optional: false,
1962                kind: ParameterKind::Value,
1963            }],
1964            output: Some(Parameter {
1965                type_: TypeRef {
1966                    name: "i64".to_string(),
1967                }
1968                .into(),
1969                ..Default::default()
1970            }),
1971            ..Default::default()
1972        };
1973
1974        let res = function
1975            .validate_assign_timestamp(&types())
1976            .expect_err("should error for too few inputs");
1977
1978        assert!(res.errors.contains(&ValidationError::new(
1979            "assign-timestamp type function `my-assign-timestamp` should have exactly 2 input type, found 1"
1980        )));
1981
1982        let function = StepInvocation {
1983            uses: "my-assign-timestamp".to_string(),
1984            inputs: vec![
1985                NamedParameter {
1986                    name: "input".to_string(),
1987                    type_: TypeRef {
1988                        name: "u8".to_string(),
1989                    },
1990                    optional: false,
1991                    kind: ParameterKind::Value,
1992                },
1993                NamedParameter {
1994                    name: "other-input".to_string(),
1995                    type_: TypeRef {
1996                        name: "u8".to_string(),
1997                    },
1998                    optional: false,
1999                    kind: ParameterKind::Value,
2000                },
2001                NamedParameter {
2002                    name: "third-input".to_string(),
2003                    type_: TypeRef {
2004                        name: "u8".to_string(),
2005                    },
2006                    optional: false,
2007                    kind: ParameterKind::Value,
2008                },
2009            ],
2010            ..Default::default()
2011        };
2012
2013        let res = function
2014            .validate_assign_timestamp(&types())
2015            .expect_err("should error for too many inputs");
2016
2017        assert!(res.errors.contains(&ValidationError::new(
2018            "assign-timestamp type function `my-assign-timestamp` should have exactly 2 input type, found 3"
2019        )));
2020    }
2021
2022    #[test]
2023    fn test_validate_assign_timestamp_requires_one_output() {
2024        let function = StepInvocation {
2025            uses: "my-assign-timestamp".to_string(),
2026            output: None,
2027            ..Default::default()
2028        };
2029
2030        let res = function
2031            .validate_assign_timestamp(&types())
2032            .expect_err("should error for missing output");
2033
2034        assert!(res.errors.contains(&ValidationError::new(
2035            "assign-timestamp type function `my-assign-timestamp` requires an output type"
2036        )));
2037    }
2038
2039    #[test]
2040    fn test_validate_assign_timestamp_requires_input_types_are_in_scope() {
2041        let function = StepInvocation {
2042            uses: "my-assign-timestamp".to_string(),
2043            inputs: vec![
2044                NamedParameter {
2045                    name: "input".to_string(),
2046                    type_: TypeRef {
2047                        name: "foobar".to_string(),
2048                    },
2049                    optional: false,
2050                    kind: ParameterKind::Value,
2051                },
2052                NamedParameter {
2053                    name: "other-input".to_string(),
2054                    type_: TypeRef {
2055                        name: "s64".to_string(),
2056                    },
2057                    optional: false,
2058                    kind: ParameterKind::Value,
2059                },
2060            ],
2061            output: Some(Parameter {
2062                type_: TypeRef {
2063                    name: "string".to_string(),
2064                }
2065                .into(),
2066                ..Default::default()
2067            }),
2068            ..Default::default()
2069        };
2070
2071        let res = function
2072            .validate_assign_timestamp(&types())
2073            .expect_err("should error for input type not in scope");
2074
2075        assert!(res.errors.contains(&ValidationError::new(&format!(
2076            "function `my-assign-timestamp` has invalid input type, {}",
2077            &ref_type_error("foobar")
2078        ))));
2079
2080        let function = StepInvocation {
2081            uses: "my-assign-timestamp".to_string(),
2082            inputs: vec![
2083                NamedParameter {
2084                    name: "input".to_string(),
2085                    type_: TypeRef {
2086                        name: "s64".to_string(),
2087                    },
2088                    optional: false,
2089                    kind: ParameterKind::Value,
2090                },
2091                NamedParameter {
2092                    name: "other-input".to_string(),
2093                    type_: TypeRef {
2094                        name: "foobar".to_string(),
2095                    },
2096                    optional: false,
2097                    kind: ParameterKind::Value,
2098                },
2099            ],
2100            output: Some(Parameter {
2101                type_: TypeRef {
2102                    name: "string".to_string(),
2103                }
2104                .into(),
2105                ..Default::default()
2106            }),
2107            ..Default::default()
2108        };
2109
2110        let res = function
2111            .validate_assign_timestamp(&types())
2112            .expect_err("should error for input type not in scope");
2113
2114        assert!(res.errors.contains(&ValidationError::new(&format!(
2115            "function `my-assign-timestamp` has invalid input type, {}",
2116            &ref_type_error("foobar")
2117        ))));
2118    }
2119
2120    #[test]
2121    fn test_validate_assign_timestamp_requires_output_type_is_in_scope() {
2122        let function = StepInvocation {
2123            uses: "my-assign-timestamp".to_string(),
2124            inputs: vec![NamedParameter {
2125                name: "input".to_string(),
2126                type_: TypeRef {
2127                    name: "string".to_string(),
2128                },
2129                optional: false,
2130                kind: ParameterKind::Value,
2131            }],
2132            output: Some(Parameter {
2133                type_: TypeRef {
2134                    name: "foobar".to_string(),
2135                }
2136                .into(),
2137                ..Default::default()
2138            }),
2139            ..Default::default()
2140        };
2141
2142        let res = function
2143            .validate_assign_timestamp(&types())
2144            .expect_err("should error for output type not in scope");
2145
2146        assert!(res.errors.contains(&ValidationError::new(&format!(
2147            "function `my-assign-timestamp` has invalid output type, {}",
2148            &ref_type_error("foobar")
2149        ))));
2150    }
2151
2152    #[test]
2153    fn test_validate_assign_timestamp_requires_second_input_type_is_s64() {
2154        let function = StepInvocation {
2155            uses: "my-assign-timestamp".to_string(),
2156            inputs: vec![
2157                NamedParameter {
2158                    name: "input".to_string(),
2159                    type_: TypeRef {
2160                        name: "string".to_string(),
2161                    },
2162                    optional: false,
2163                    kind: ParameterKind::Value,
2164                },
2165                NamedParameter {
2166                    name: "other-input".to_string(),
2167                    type_: TypeRef {
2168                        name: "string".to_string(),
2169                    },
2170                    optional: false,
2171                    kind: ParameterKind::Value,
2172                },
2173            ],
2174            output: Some(Parameter {
2175                type_: TypeRef {
2176                    name: "string".to_string(),
2177                }
2178                .into(),
2179                ..Default::default()
2180            }),
2181            ..Default::default()
2182        };
2183
2184        let res = function
2185            .validate_assign_timestamp(&types())
2186            .expect_err("should error for second input type not s64");
2187
2188        assert!(res.errors.contains(&ValidationError::new("second input type for assign-timestamp type function `my-assign-timestamp` must be a signed 64-bit int or an alias for one, found: `string`")));
2189    }
2190
2191    #[test]
2192    fn test_validate_assign_timestamp_requires_output_type_is_s64() {
2193        let function = StepInvocation {
2194            uses: "my-assign-timestamp".to_string(),
2195            inputs: vec![
2196                NamedParameter {
2197                    name: "input".to_string(),
2198                    type_: TypeRef {
2199                        name: "string".to_string(),
2200                    },
2201                    optional: false,
2202                    kind: ParameterKind::Value,
2203                },
2204                NamedParameter {
2205                    name: "other-input".to_string(),
2206                    type_: TypeRef {
2207                        name: "string".to_string(),
2208                    },
2209                    optional: false,
2210                    kind: ParameterKind::Value,
2211                },
2212            ],
2213            output: Some(Parameter {
2214                type_: TypeRef {
2215                    name: "string".to_string(),
2216                }
2217                .into(),
2218                ..Default::default()
2219            }),
2220            ..Default::default()
2221        };
2222
2223        let res = function
2224            .validate_assign_timestamp(&types())
2225            .expect_err("should error for second input type not s64");
2226
2227        assert!(res.errors.contains(&ValidationError::new("output type for assign-timestamp type function `my-assign-timestamp` must be a signed 64-bit int or an alias for one, found: `string`")));
2228    }
2229
2230    #[test]
2231    fn test_validate_assign_timestamp_accepts_valid_fn() {
2232        let function = StepInvocation {
2233            uses: "my-assign-timestamp".to_string(),
2234            inputs: vec![
2235                NamedParameter {
2236                    name: "input".to_string(),
2237                    type_: TypeRef {
2238                        name: "string".to_string(),
2239                    },
2240                    optional: false,
2241                    kind: ParameterKind::Value,
2242                },
2243                NamedParameter {
2244                    name: "other-input".to_string(),
2245                    type_: TypeRef {
2246                        name: "i64".to_string(),
2247                    },
2248                    optional: false,
2249                    kind: ParameterKind::Value,
2250                },
2251            ],
2252            output: Some(Parameter {
2253                type_: TypeRef {
2254                    name: "i64".to_string(),
2255                }
2256                .into(),
2257                ..Default::default()
2258            }),
2259            ..Default::default()
2260        };
2261
2262        function
2263            .validate_assign_timestamp(&types())
2264            .expect("should accept valid function");
2265    }
2266
2267    #[cfg(feature = "yaml")]
2268    #[test]
2269    fn test_validate_code_block() {
2270        let function = StepInvocation {
2271            uses: "my-code-block".to_string(),
2272            inputs: vec![NamedParameter {
2273                name: "input".to_string(),
2274                type_: TypeRef {
2275                    name: "string".to_string(),
2276                },
2277                optional: false,
2278                kind: ParameterKind::Value,
2279            }],
2280            output: Some(Parameter {
2281                type_: TypeRef {
2282                    name: "u8".to_string(),
2283                }
2284                .into(),
2285                ..Default::default()
2286            }),
2287            code_info: CodeInfo {
2288                code: Some("fn my_code_block(input: string) -> Result<u8> { 1 }".to_string()),
2289                lang: CodeLang::Rust,
2290                extra_deps: vec![],
2291            },
2292            ..Default::default()
2293        };
2294
2295        function.validate_code().expect("should validate");
2296    }
2297
2298    #[cfg(feature = "yaml")]
2299    #[test]
2300    fn test_validate_code_block_checks_syntax() {
2301        let function = StepInvocation {
2302            uses: "my-code-block".to_string(),
2303            inputs: vec![NamedParameter {
2304                name: "input".to_string(),
2305                type_: TypeRef {
2306                    name: "string".to_string(),
2307                },
2308                optional: false,
2309                kind: ParameterKind::Value,
2310            }],
2311            output: Some(Parameter {
2312                type_: TypeRef {
2313                    name: "u8".to_string(),
2314                }
2315                .into(),
2316                ..Default::default()
2317            }),
2318            code_info: CodeInfo {
2319                code: Some("fn my_code_block(input: string) -> Result<u8> {".to_string()),
2320                lang: CodeLang::Rust,
2321                extra_deps: vec![],
2322            },
2323            ..Default::default()
2324        };
2325
2326        let err = function.validate_code().expect_err("should fail");
2327
2328        assert!(
2329            err.errors.contains(&ValidationError::new(
2330               "Failed to parse code. Is this valid Rust syntax for a function?:\n fn my_code_block(input: string) -> Result<u8> {"
2331            )),
2332            "{:?}",
2333            err
2334        );
2335    }
2336
2337    #[cfg(feature = "yaml")]
2338    #[test]
2339    fn test_update_signature() {
2340        let mut step = StepInvocation {
2341            uses: "my-step".to_string(),
2342            inputs: vec![NamedParameter {
2343                name: "old-name".to_string(),
2344                type_: TypeRef {
2345                    name: "string".to_string(),
2346                },
2347                optional: false,
2348                kind: ParameterKind::Value,
2349            }],
2350            output: Some(Parameter {
2351                type_: TypeRef {
2352                    name: "u8".to_string(),
2353                }
2354                .into(),
2355                ..Default::default()
2356            }),
2357            code_info: CodeInfo {
2358                code: Some(
2359                    "fn my_code_block(input: MyInput) -> Result<MyOutput> { 1 }".to_string(),
2360                ),
2361                lang: CodeLang::Rust,
2362                extra_deps: vec![],
2363            },
2364            ..Default::default()
2365        };
2366        step.update_signature_from_code().expect("failed to update");
2367        assert_eq!(step.uses, "my-code-block");
2368        assert_eq!(step.inputs.len(), 1);
2369        assert_eq!(step.inputs[0].name, "input");
2370        assert_eq!(step.inputs[0].type_.name, "my-input");
2371        assert!(step.output.is_some());
2372        assert_eq!(step.output.unwrap().type_.value_type().name, "my-output");
2373    }
2374
2375    #[test]
2376    fn test_wit_interface_map() {
2377        let step = StepInvocation {
2378            uses: "my-map".to_string(),
2379            inputs: vec![NamedParameter {
2380                name: "input".to_string(),
2381                type_: TypeRef {
2382                    name: "string".to_string(),
2383                },
2384                optional: false,
2385                kind: ParameterKind::Value,
2386            }],
2387            output: Some(Parameter {
2388                type_: TypeRef {
2389                    name: "u8".to_string(),
2390                }
2391                .into(),
2392                ..Default::default()
2393            }),
2394            ..Default::default()
2395        };
2396
2397        let interface = step.wit_interface(&OperatorType::Map);
2398
2399        let expected_interface =
2400            "interface my-map-service {\n  my-map: func(input: string) -> result<u8, string>;\n}\n";
2401
2402        assert_eq!(
2403            expected_interface,
2404            WitInterfaceDisplay(interface).to_string()
2405        )
2406    }
2407
2408    #[test]
2409    fn test_wit_filter_map() {
2410        let step = StepInvocation {
2411            uses: "my-filter-map".to_string(),
2412            inputs: vec![NamedParameter {
2413                name: "input".to_string(),
2414                type_: TypeRef {
2415                    name: "string".to_string(),
2416                },
2417                optional: false,
2418                kind: ParameterKind::Value,
2419            }],
2420            output: Some(Parameter {
2421                type_: TypeRef {
2422                    name: "u8".to_string(),
2423                }
2424                .into(),
2425                optional: true,
2426            }),
2427            ..Default::default()
2428        };
2429
2430        let interface = step.wit_interface(&OperatorType::FilterMap);
2431
2432        let expected_interface = "interface my-filter-map-service {\n  my-filter-map: func(input: string) -> result<option<u8>, string>;\n}\n";
2433
2434        assert_eq!(
2435            expected_interface,
2436            WitInterfaceDisplay(interface).to_string()
2437        )
2438    }
2439
2440    #[test]
2441    fn test_wit_flat_map() {
2442        let step = StepInvocation {
2443            uses: "my-flat-map".to_string(),
2444            inputs: vec![NamedParameter {
2445                name: "input".to_string(),
2446                type_: TypeRef {
2447                    name: "string".to_string(),
2448                },
2449                optional: false,
2450                kind: ParameterKind::Value,
2451            }],
2452            output: Some(Parameter {
2453                type_: TypeRef {
2454                    name: "u8".to_string(),
2455                }
2456                .into(),
2457                ..Default::default()
2458            }),
2459            ..Default::default()
2460        };
2461
2462        let interface = step.wit_interface(&OperatorType::FlatMap);
2463
2464        let expected_interface = "interface my-flat-map-service {\n  my-flat-map: func(input: string) -> result<list<u8>, string>;\n}\n";
2465
2466        assert_eq!(
2467            expected_interface,
2468            WitInterfaceDisplay(interface).to_string()
2469        )
2470    }
2471
2472    #[test]
2473    fn test_wit_filter() {
2474        let step = StepInvocation {
2475            uses: "my-filter".to_string(),
2476            inputs: vec![NamedParameter {
2477                name: "input".to_string(),
2478                type_: TypeRef {
2479                    name: "string".to_string(),
2480                },
2481                optional: false,
2482                kind: ParameterKind::Value,
2483            }],
2484            output: Some(Parameter {
2485                type_: TypeRef {
2486                    name: "bool".to_string(),
2487                }
2488                .into(),
2489                ..Default::default()
2490            }),
2491            ..Default::default()
2492        };
2493
2494        let interface = step.wit_interface(&OperatorType::Filter);
2495
2496        let expected_interface = "interface my-filter-service {\n  my-filter: func(input: string) -> result<bool, string>;\n}\n";
2497
2498        assert_eq!(
2499            expected_interface,
2500            WitInterfaceDisplay(interface).to_string()
2501        )
2502    }
2503
2504    #[test]
2505    fn test_wit_update_state() {
2506        let step = StepInvocation {
2507            uses: "my-update".to_string(),
2508            inputs: vec![NamedParameter {
2509                name: "input".to_string(),
2510                type_: TypeRef {
2511                    name: "string".to_string(),
2512                },
2513                optional: false,
2514                kind: ParameterKind::Value,
2515            }],
2516            output: None,
2517            ..Default::default()
2518        };
2519
2520        let interface = step.wit_interface(&OperatorType::UpdateState);
2521
2522        let expected_interface = "interface my-update-service {\n  my-update: func(input: string) -> result<_, string>;\n}\n";
2523
2524        assert_eq!(
2525            expected_interface,
2526            WitInterfaceDisplay(interface).to_string()
2527        )
2528    }
2529
2530    #[test]
2531    fn test_wit_assign_timestamp() {
2532        let step = StepInvocation {
2533            uses: "my-assign-timestamp".to_string(),
2534            inputs: vec![
2535                NamedParameter {
2536                    name: "input".to_string(),
2537                    type_: TypeRef {
2538                        name: "string".to_string(),
2539                    },
2540                    optional: false,
2541                    kind: ParameterKind::Value,
2542                },
2543                NamedParameter {
2544                    name: "timestamp".to_string(),
2545                    type_: TypeRef {
2546                        name: "s64".to_string(),
2547                    },
2548                    optional: false,
2549                    kind: ParameterKind::Value,
2550                },
2551            ],
2552            output: Some(Parameter {
2553                type_: TypeRef {
2554                    name: "s64".to_string(),
2555                }
2556                .into(),
2557                ..Default::default()
2558            }),
2559            ..Default::default()
2560        };
2561
2562        let interface = step.wit_interface(&OperatorType::AssignTimestamp);
2563
2564        let expected_interface = "interface my-assign-timestamp-service {\n  my-assign-timestamp: func(input: string, timestamp: s64) -> result<s64, string>;\n}\n";
2565
2566        assert_eq!(
2567            expected_interface,
2568            WitInterfaceDisplay(interface).to_string()
2569        )
2570    }
2571
2572    #[test]
2573    fn test_map_with_key() {
2574        let step = StepInvocation {
2575            uses: "my-map".to_string(),
2576            inputs: vec![
2577                NamedParameter {
2578                    name: "key".to_string(),
2579                    type_: TypeRef {
2580                        name: "string".to_string(),
2581                    },
2582                    optional: true,
2583                    kind: ParameterKind::Key,
2584                },
2585                NamedParameter {
2586                    name: "input".to_string(),
2587                    type_: TypeRef {
2588                        name: "string".to_string(),
2589                    },
2590                    optional: false,
2591                    kind: ParameterKind::Value,
2592                },
2593            ],
2594            output: Some(Parameter {
2595                type_: crate::wit::metadata::OutputType::KeyValue(SdfKeyValue {
2596                    key: TypeRef {
2597                        name: "string".to_string(),
2598                    },
2599                    value: TypeRef {
2600                        name: "u8".to_string(),
2601                    },
2602                }),
2603                ..Default::default()
2604            }),
2605            ..Default::default()
2606        };
2607
2608        let interface = step.wit_interface(&OperatorType::Map);
2609
2610        let expected_interface = "interface my-map-service {\n  my-map: func(key: option<string>, input: string) -> result<tuple<option<string>, u8>, string>;\n}\n";
2611
2612        assert_eq!(
2613            expected_interface,
2614            WitInterfaceDisplay(interface).to_string()
2615        )
2616    }
2617}