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 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}