sdf_metadata/metadata/package_interface/
package_definition.rs

1use std::{
2    collections::{BTreeMap, HashSet},
3    fmt::Display,
4};
5
6use anyhow::Result;
7use wit_encoder::{Package, Use};
8
9use sdf_common::{render::wit_name_case, version::ApiVersion};
10
11use crate::{
12    importer::resolver::DependencyResolver,
13    metadata::metadata::header::HeaderValidationError,
14    util::{
15        config_error::{ConfigError, INDENT},
16        merge::merge_types_and_states,
17        sdf_types_map::{is_imported_type, SdfTypesMap},
18        validate::{validate_all, MetadataTypeValidationFailure},
19        validation_failure::ValidationFailure,
20    },
21    wit::{
22        metadata::{MetadataType, SdfType, SdfTypeOrigin},
23        operator::OperatorType,
24        package_interface::{PackageDefinition, StepInvocation},
25    },
26};
27
28#[derive(Debug, Clone, Eq, PartialEq)]
29pub struct PackageDefinitionValidationFailure {
30    pub errors: Vec<PackageDefinitionValidationError>,
31}
32
33impl Display for PackageDefinitionValidationFailure {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        writeln!(f, "Package Config failed validation\n")?;
36
37        for error in &self.errors {
38            writeln!(f, "{}", error.readable(1))?;
39        }
40
41        Ok(())
42    }
43}
44
45#[derive(Debug, Clone, Eq, PartialEq)]
46pub enum PackageDefinitionValidationError {
47    Meta(Vec<HeaderValidationError>),
48    Type(MetadataTypeValidationFailure),
49    State(ValidationFailure),
50    Function(ValidationFailure),
51}
52
53impl ConfigError for PackageDefinitionValidationError {
54    fn readable(&self, indents: usize) -> String {
55        let indent = INDENT.repeat(indents);
56
57        match self {
58            PackageDefinitionValidationError::Meta(errors) => {
59                let mut res = format!("{}Header is invalid:\n", indent);
60
61                for error in errors {
62                    res.push_str(&error.readable(indents + 1));
63                }
64
65                res
66            }
67            PackageDefinitionValidationError::Type(failure) => failure.readable(indents),
68            PackageDefinitionValidationError::State(failure) => {
69                format!(
70                    "{}State is invalid:\n{}",
71                    indent,
72                    failure.readable(indents + 1)
73                )
74            }
75            PackageDefinitionValidationError::Function(failure) => failure.readable(indents),
76        }
77    }
78}
79
80impl PackageDefinition {
81    pub fn name(&self) -> &str {
82        &self.meta.name
83    }
84
85    pub fn api_version(&self) -> Result<ApiVersion> {
86        ApiVersion::from(&self.api_version)
87    }
88
89    pub fn has_custom_types(&self) -> bool {
90        !self.types.is_empty() && !self.states.is_empty()
91    }
92
93    pub fn namespace(&self) -> &str {
94        &self.meta.namespace
95    }
96    pub fn resolve_imports(&mut self, packages: Vec<PackageDefinition>, debug: bool) -> Result<()> {
97        let dependency_resolver =
98            DependencyResolver::build(self.imports.clone(), packages.clone(), debug)?;
99        let package_configs = dependency_resolver.packages()?;
100
101        self.merge_dependencies(&package_configs)?;
102
103        Ok(())
104    }
105    pub fn merge_dependencies(&mut self, package_configs: &[PackageDefinition]) -> Result<()> {
106        let mut all_types = self.types_map();
107        let mut all_states = self
108            .states
109            .iter()
110            .map(|ty| (ty.name.clone(), ty.clone()))
111            .collect::<BTreeMap<_, _>>();
112
113        merge_types_and_states(
114            &mut all_types,
115            &mut all_states,
116            &self.imports,
117            package_configs,
118        )?;
119
120        self.types = all_types
121            .iter()
122            .map(|(name, (ty, origin))| MetadataType {
123                name: name.to_owned(),
124                type_: ty.to_owned(),
125                origin: origin.to_owned(),
126            })
127            .collect();
128        self.states = all_states.into_values().collect();
129
130        self.resolve_function_states()?;
131
132        Ok(())
133    }
134
135    pub fn resolve_function_states(&mut self) -> Result<()> {
136        for (function, _operator) in self.functions.iter_mut() {
137            function.resolve_states(&self.states)?;
138        }
139
140        Ok(())
141    }
142
143    pub fn get_function(&self, name: &str) -> Option<&(StepInvocation, OperatorType)> {
144        self.functions
145            .iter()
146            .find(|(function, _operator)| function.uses == name)
147    }
148
149    pub fn types_map(&self) -> SdfTypesMap {
150        SdfTypesMap {
151            map: self
152                .types
153                .iter()
154                .map(|ty| (ty.name.clone(), (ty.type_.clone(), ty.origin)))
155                .chain(self.states.iter().map(|state| {
156                    (
157                        state.name.clone(),
158                        (
159                            SdfType::KeyedState(state.type_.clone()),
160                            SdfTypeOrigin::Local,
161                        ),
162                    )
163                }))
164                .collect(),
165        }
166    }
167
168    pub fn validate(&self) -> Result<(), PackageDefinitionValidationFailure> {
169        let mut errors: Vec<PackageDefinitionValidationError> = vec![];
170
171        if let Err(err) = self.meta.validate() {
172            errors.push(PackageDefinitionValidationError::Meta(err));
173        }
174
175        let types_map = self.types_map();
176
177        for metadata_type in &self.types {
178            if let Err(type_validation_failure) = metadata_type.validate(&types_map) {
179                errors.push(PackageDefinitionValidationError::Type(
180                    type_validation_failure,
181                ));
182            }
183        }
184
185        if let Err(err) = self.validate_states() {
186            errors.push(PackageDefinitionValidationError::State(err));
187        }
188
189        if let Err(err) = self.validate_functions(&types_map) {
190            errors.push(PackageDefinitionValidationError::Function(err));
191        }
192
193        if errors.is_empty() {
194            Ok(())
195        } else {
196            Err(PackageDefinitionValidationFailure { errors })
197        }
198    }
199
200    fn validate_states(&self) -> Result<(), ValidationFailure> {
201        validate_all(&self.states)
202    }
203
204    fn validate_functions(&self, types: &SdfTypesMap) -> Result<(), ValidationFailure> {
205        let mut errors = ValidationFailure::new();
206
207        for (function, operator) in &self.functions {
208            match operator {
209                OperatorType::AssignKey => {
210                    if let Err(failures) = function.validate_assign_key(types) {
211                        errors.concat(&failures);
212                    }
213                }
214                OperatorType::Map => {
215                    if let Err(failures) = function.validate_map(types) {
216                        errors.concat(&failures);
217                    }
218                }
219                OperatorType::FilterMap => {
220                    if let Err(failures) = function.validate_filter_map(types) {
221                        errors.concat(&failures);
222                    }
223                }
224                OperatorType::Filter => {
225                    if let Err(failures) = function.validate_filter(types) {
226                        errors.concat(&failures);
227                    }
228                }
229                OperatorType::FlatMap => {
230                    if let Err(failures) = function.validate_flat_map(types) {
231                        errors.concat(&failures);
232                    }
233                }
234                OperatorType::UpdateState => {
235                    if let Err(failures) = function.validate_update_state(types) {
236                        errors.concat(&failures);
237                    }
238                }
239                OperatorType::WindowAggregate => {
240                    if let Err(failures) = function.validate_window_aggregate(types) {
241                        errors.concat(&failures);
242                    }
243                }
244                OperatorType::AssignTimestamp => {
245                    if let Err(failures) = function.validate_assign_timestamp(types) {
246                        errors.concat(&failures);
247                    }
248                }
249            }
250        }
251
252        if errors.any() {
253            Err(errors)
254        } else {
255            Ok(())
256        }
257    }
258
259    pub fn types_wit_package(&self) -> Result<Package> {
260        let name = wit_encoder::PackageName::new(
261            self.meta.namespace.clone(),
262            self.meta.name.clone(),
263            None,
264        );
265
266        let mut package = Package::new(name);
267
268        let api_version = self.api_version()?;
269
270        let types_map = self.types_map();
271
272        let imports = self
273            .imports
274            .iter()
275            .filter_map(|import| {
276                let types = import
277                    .types
278                    .iter()
279                    .cloned()
280                    .chain(import.states.iter().cloned())
281                    .collect::<Vec<_>>();
282                if types.is_empty() {
283                    None
284                } else {
285                    let types_iface = format!(
286                        "{}:{}/types",
287                        import.metadata.namespace, import.metadata.name
288                    );
289
290                    let mut uses = Use::new(types_iface);
291
292                    let mut needed_types = HashSet::new();
293
294                    for t in types {
295                        if !is_imported_type(&t) {
296                            continue;
297                        }
298                        needed_types.insert(t.clone());
299
300                        let typedeps = types_map.get_type_tree(&t);
301                        for (key, _) in typedeps {
302                            needed_types.insert(key);
303                        }
304                    }
305                    for ty in needed_types {
306                        uses.item(wit_name_case(&ty), None);
307                    }
308                    Some(uses)
309                }
310            })
311            .collect::<Vec<_>>();
312
313        let wit_interface = types_map.wit_interface(&api_version, imports);
314
315        package.interface(wit_interface);
316        Ok(package)
317    }
318}
319
320#[cfg(test)]
321mod test {
322
323    use sdf_common::constants::DATAFLOW_STABLE_VERSION;
324
325    use crate::{
326        metadata::{
327            metadata::{header::HeaderValidationError, sdf_type::SdfTypeValidationError},
328            package_interface::package_definition::PackageDefinitionValidationError,
329        },
330        util::{
331            validate::{MetadataTypeValidationError, MetadataTypeValidationFailure},
332            validation_error::ValidationError,
333            validation_failure::ValidationFailure,
334        },
335        wit::{
336            metadata::{
337                MetadataType, NamedParameter, Parameter, ParameterKind, SdfKeyedState,
338                SdfKeyedStateValue, SdfObject, SdfType, SdfTypeOrigin, TypeRef,
339            },
340            operator::StepInvocation,
341            package_interface::{
342                FunctionImport, Header, OperatorType, PackageDefinition, PackageImport, StateTyped,
343            },
344        },
345    };
346
347    fn package() -> PackageDefinition {
348        PackageDefinition {
349            api_version: DATAFLOW_STABLE_VERSION.to_string(),
350            meta: Header {
351                namespace: "example".to_string(),
352                name: "core".to_string(),
353                version: "0.1.0".to_string(),
354            },
355            types: vec![],
356            states: vec![],
357            imports: vec![
358                PackageImport {
359                    metadata: Header {
360                        namespace: "example".to_string(),
361                        name: "bank-update".to_string(),
362                        version: "0.1.0".to_string(),
363                    },
364                    functions: vec![FunctionImport {
365                        name: "filter-positive-events".to_string(),
366                        alias: None,
367                    }],
368                    path: None,
369                    types: vec!["account-balance".to_string()],
370                    states: vec![],
371                },
372                PackageImport {
373                    metadata: Header {
374                        namespace: "example".to_string(),
375                        name: "bank".to_string(),
376                        version: "0.1.0".to_string(),
377                    },
378                    types: vec!["bank-event".to_string()],
379                    functions: vec![],
380                    states: vec![],
381                    path: None,
382                },
383            ],
384            functions: vec![],
385            dev: None,
386        }
387    }
388
389    #[test]
390    fn test_validate_validates_metadata() {
391        let mut pkg = package();
392        pkg.meta.name = "".to_string();
393
394        let res = pkg.validate().expect_err("should error for empty name");
395
396        assert!(res
397            .errors
398            .contains(&PackageDefinitionValidationError::Meta(vec![
399                HeaderValidationError::new("Name cannot be empty\n")
400            ])));
401        assert_eq!(
402            res.to_string(),
403            r#"Package Config failed validation
404
405    Header is invalid:
406        Name cannot be empty
407
408"#,
409        )
410    }
411
412    #[test]
413    fn test_validate_rejects_empty_type_names() {
414        let mut pkg = package();
415
416        pkg.types = vec![MetadataType {
417            name: "".to_string(),
418            type_: SdfType::Object(SdfObject { fields: vec![] }),
419            origin: SdfTypeOrigin::Local,
420        }];
421
422        let res = pkg
423            .validate()
424            .expect_err("should error for empty type name");
425
426        assert!(res.errors.contains(&PackageDefinitionValidationError::Type(
427            MetadataTypeValidationFailure {
428                name: "".to_string(),
429                errors: vec![MetadataTypeValidationError::EmptyName],
430            }
431        )));
432        assert_eq!(
433            res.to_string(),
434            r#"Package Config failed validation
435
436    Defined type `` is invalid:
437        Name cannot be empty
438
439"#,
440        )
441    }
442
443    #[test]
444    fn test_validate_validates_types() {
445        let mut pkg = package();
446
447        pkg.types.push(MetadataType {
448            name: "my-type".to_string(),
449            type_: SdfType::Named(TypeRef {
450                name: "foobar".to_string(),
451            }),
452            origin: SdfTypeOrigin::Local,
453        });
454
455        let res = pkg
456            .validate()
457            .expect_err("should error for invalid type reference");
458
459        assert!(res.errors.contains(&PackageDefinitionValidationError::Type(
460            MetadataTypeValidationFailure {
461                name: "my-type".to_string(),
462                errors: vec![MetadataTypeValidationError::SdfType(
463                    SdfTypeValidationError::InvalidRef("foobar".to_string())
464                )],
465            }
466        )));
467        assert_eq!(
468            res.to_string(),
469            r#"Package Config failed validation
470
471    Defined type `my-type` is invalid:
472        Referenced type `foobar` not found in config or imported types
473
474"#,
475        )
476    }
477
478    #[test]
479    fn test_validate_validate_states() {
480        let mut pkg = package();
481
482        pkg.states.push(StateTyped {
483            name: "state".to_string(),
484            type_: SdfKeyedState {
485                key: TypeRef {
486                    name: "string".to_string(),
487                },
488                value: SdfKeyedStateValue::Unresolved(TypeRef {
489                    name: "my-state-value".to_string(),
490                }),
491            },
492        });
493
494        let res = pkg
495            .validate()
496            .expect_err("should error for invalid state type");
497
498        assert!(
499            res.errors.contains(&PackageDefinitionValidationError::State(
500                ValidationFailure {
501                    errors: vec![ValidationError::new("Internal Error: typed state value should be resolved before validation. Please contact support")],
502                }
503            ))
504        );
505        assert_eq!(
506            res.to_string(),
507            r#"Package Config failed validation
508
509    State is invalid:
510        Internal Error: typed state value should be resolved before validation. Please contact support
511
512"#,
513        )
514    }
515
516    #[test]
517    fn test_validate_validate_functions() {
518        let mut pkg = package();
519
520        pkg.functions.push((
521            StepInvocation {
522                uses: "my-filter".to_string(),
523                inputs: vec![NamedParameter {
524                    name: "first-input".to_string(),
525                    type_: TypeRef {
526                        name: "u16".to_string(),
527                    },
528                    optional: false,
529                    kind: ParameterKind::Value,
530                }],
531                output: Some(Parameter {
532                    type_: TypeRef {
533                        name: "string".to_string(),
534                    }
535                    .into(),
536                    ..Default::default()
537                }),
538                ..Default::default()
539            },
540            OperatorType::Filter,
541        ));
542
543        let res = pkg
544            .validate()
545            .expect_err("should error for invalid state type");
546
547        assert!(res.errors.contains(&PackageDefinitionValidationError::Function(
548            ValidationFailure {
549                errors: vec![
550                    ValidationError::new("filter type function `my-filter` requires an output type of `bool`, but found `string`")
551                ]
552            }
553        )));
554
555        assert_eq!(
556            res.to_string(),
557            r#"Package Config failed validation
558
559    filter type function `my-filter` requires an output type of `bool`, but found `string`
560
561"#,
562        )
563    }
564
565    #[test]
566    fn test_validate_passes_valid_config() {
567        let pkg = package();
568        pkg.validate().expect("failed to validate");
569    }
570
571    #[test]
572    fn test_types_wit_package() {
573        let mut pkg = package();
574
575        pkg.types = vec![MetadataType {
576            name: "my-type".to_string(),
577            type_: SdfType::Named(TypeRef {
578                name: "foobar".to_string(),
579            }),
580            origin: SdfTypeOrigin::Local,
581        }];
582
583        let package = pkg
584            .types_wit_package()
585            .expect("failed to generate wit package");
586        let expected_wit = "package example:core;
587
588interface types {
589  use example:bank-update/types.{ account-balance };
590  use example:bank/types.{ bank-event };
591  type bytes = list<u8>;
592  type my-type = foobar;
593}
594";
595        assert_eq!(package.to_string(), expected_wit,);
596    }
597}