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}