diff --git a/rust/signed_doc/src/validator/mod.rs b/rust/signed_doc/src/validator/mod.rs index c2f7f2ba6b5..b5e07737ec0 100644 --- a/rust/signed_doc/src/validator/mod.rs +++ b/rust/signed_doc/src/validator/mod.rs @@ -91,6 +91,7 @@ fn proposal_comment_rule() -> Rules { }, doc_ref: RefRule::Specified { exp_ref_types: vec![PROPOSAL.clone()], + multiple: false, optional: false, }, reply: ReplyRule::Specified { @@ -148,6 +149,7 @@ fn proposal_submission_action_rule() -> Rules { }, doc_ref: RefRule::Specified { exp_ref_types: vec![PROPOSAL.clone()], + multiple: false, optional: false, }, reply: ReplyRule::NotSpecified, diff --git a/rust/signed_doc/src/validator/rules/content.rs b/rust/signed_doc/src/validator/rules/content.rs index 8b9c8ac24fb..ab24b2f864e 100644 --- a/rust/signed_doc/src/validator/rules/content.rs +++ b/rust/signed_doc/src/validator/rules/content.rs @@ -91,6 +91,7 @@ impl ContentRule { return doc_refs_check( template_ref, std::slice::from_ref(exp_template_type), + false, "template", provider, doc.report(), diff --git a/rust/signed_doc/src/validator/rules/doc_ref.rs b/rust/signed_doc/src/validator/rules/doc_ref.rs index 938f19bf02c..a84859030c5 100644 --- a/rust/signed_doc/src/validator/rules/doc_ref.rs +++ b/rust/signed_doc/src/validator/rules/doc_ref.rs @@ -14,6 +14,8 @@ pub(crate) enum RefRule { Specified { /// expected `type` field of the referenced doc exp_ref_types: Vec, + /// allows multiple document references or only one + multiple: bool, /// optional flag for the `ref` field optional: bool, }, @@ -33,6 +35,7 @@ impl RefRule { let context: &str = "Ref rule check"; if let Self::Specified { exp_ref_types, + multiple, optional, } = self { @@ -40,6 +43,7 @@ impl RefRule { return doc_refs_check( doc_refs, exp_ref_types, + *multiple, "ref", provider, doc.report(), @@ -73,6 +77,7 @@ impl RefRule { pub(crate) async fn doc_refs_check( doc_refs: &DocumentRefs, exp_ref_types: &[DocType], + multiple: bool, field_name: &str, provider: &Provider, report: &ProblemReport, @@ -84,6 +89,18 @@ where { let mut all_valid = true; + if !multiple && doc_refs.len() > 1 { + report.other( + format!( + "Only ONE document reference is allowed, found {} document references", + doc_refs.len() + ) + .as_str(), + &format!("Referenced document validation for the `{field_name}` field"), + ); + return Ok(false); + } + for dr in doc_refs.iter() { if let Some(ref ref_doc) = provider.try_get_doc(dr).await? { let is_valid = referenced_doc_type_check(ref_doc, exp_ref_types, field_name, report) @@ -396,7 +413,109 @@ mod tests { "valid reference to the missing one document" )] #[tokio::test] - async fn ref_specified_test( + async fn ref_multiple_specified_test( + doc_gen: impl FnOnce(&[DocType; 2], &mut TestCatalystProvider) -> CatalystSignedDocument + ) -> bool { + let mut provider = TestCatalystProvider::default(); + + let exp_types: [DocType; 2] = [UuidV4::new().into(), UuidV4::new().into()]; + + let doc = doc_gen(&exp_types, &mut provider); + + let non_optional_res = RefRule::Specified { + exp_ref_types: exp_types.to_vec(), + multiple: true, + optional: false, + } + .check(&doc, &provider) + .await + .unwrap(); + + let optional_res = RefRule::Specified { + exp_ref_types: exp_types.to_vec(), + multiple: true, + optional: true, + } + .check(&doc, &provider) + .await + .unwrap(); + + assert_eq!(non_optional_res, optional_res); + non_optional_res + } + + #[test_case( + |exp_types, provider| { + let ref_doc = Builder::new() + .with_metadata_field(SupportedField::Id(UuidV7::new())) + .with_metadata_field(SupportedField::Ver(UuidV7::new())) + .with_metadata_field(SupportedField::Type(exp_types[0].clone())) + .build(); + provider.add_document(None, &ref_doc).unwrap(); + + Builder::new() + .with_metadata_field(SupportedField::Ref( + vec![DocumentRef::new( + ref_doc.doc_id().unwrap(), + ref_doc.doc_ver().unwrap(), + DocLocator::default(), + )] + .into(), + )) + .build() + } + => true + ; + "valid document with a single reference" + )] + #[test_case( + |exp_types, provider| { + let ref_doc_1 = Builder::new() + .with_metadata_field(SupportedField::Id(UuidV7::new())) + .with_metadata_field(SupportedField::Ver(UuidV7::new())) + .with_metadata_field(SupportedField::Type(exp_types[0].clone())) + .build(); + provider.add_document(None, &ref_doc_1).unwrap(); + let ref_doc_2 = Builder::new() + .with_metadata_field(SupportedField::Id(UuidV7::new())) + .with_metadata_field(SupportedField::Ver(UuidV7::new())) + .with_metadata_field(SupportedField::Type(exp_types[1].clone())) + .build(); + provider.add_document(None, &ref_doc_2).unwrap(); + let ref_doc_3 = Builder::new() + .with_metadata_field(SupportedField::Id(UuidV7::new())) + .with_metadata_field(SupportedField::Ver(UuidV7::new())) + .with_metadata_field(SupportedField::Type(exp_types[0].clone())) + .build(); + provider.add_document(None, &ref_doc_3).unwrap(); + + Builder::new() + .with_metadata_field(SupportedField::Ref( + vec![DocumentRef::new( + ref_doc_1.doc_id().unwrap(), + ref_doc_1.doc_ver().unwrap(), + DocLocator::default(), + ), + DocumentRef::new( + ref_doc_2.doc_id().unwrap(), + ref_doc_2.doc_ver().unwrap(), + DocLocator::default(), + ), + DocumentRef::new( + ref_doc_3.doc_id().unwrap(), + ref_doc_3.doc_ver().unwrap(), + DocLocator::default(), + )] + .into(), + )) + .build() + } + => false + ; + "valid document with multiple references" + )] + #[tokio::test] + async fn ref_non_multiple_specified_test( doc_gen: impl FnOnce(&[DocType; 2], &mut TestCatalystProvider) -> CatalystSignedDocument ) -> bool { let mut provider = TestCatalystProvider::default(); @@ -407,6 +526,7 @@ mod tests { let non_optional_res = RefRule::Specified { exp_ref_types: exp_types.to_vec(), + multiple: false, optional: false, } .check(&doc, &provider) @@ -415,6 +535,7 @@ mod tests { let optional_res = RefRule::Specified { exp_ref_types: exp_types.to_vec(), + multiple: false, optional: true, } .check(&doc, &provider) @@ -430,6 +551,7 @@ mod tests { let provider = TestCatalystProvider::default(); let rule = RefRule::Specified { exp_ref_types: vec![UuidV4::new().into()], + multiple: true, optional: true, }; @@ -439,6 +561,7 @@ mod tests { let provider = TestCatalystProvider::default(); let rule = RefRule::Specified { exp_ref_types: vec![UuidV4::new().into()], + multiple: true, optional: false, }; diff --git a/rust/signed_doc/src/validator/rules/parameters.rs b/rust/signed_doc/src/validator/rules/parameters.rs index 19dfc9188ff..af5d2411d36 100644 --- a/rust/signed_doc/src/validator/rules/parameters.rs +++ b/rust/signed_doc/src/validator/rules/parameters.rs @@ -43,6 +43,7 @@ impl ParametersRule { let parameters_check = doc_refs_check( parameters_ref, exp_parameters_type, + false, "parameters", provider, doc.report(), diff --git a/rust/signed_doc/src/validator/rules/reply.rs b/rust/signed_doc/src/validator/rules/reply.rs index edb49a4bbc3..fe634ecc99d 100644 --- a/rust/signed_doc/src/validator/rules/reply.rs +++ b/rust/signed_doc/src/validator/rules/reply.rs @@ -66,6 +66,7 @@ impl ReplyRule { return doc_refs_check( reply_ref, std::slice::from_ref(exp_reply_type), + false, "reply", provider, doc.report(),