From 6802e938a799236954299439372a519970911aa6 Mon Sep 17 00:00:00 2001 From: bkioshn Date: Tue, 20 May 2025 10:32:40 +0700 Subject: [PATCH 01/18] feat(signed-doc): add new type DocType Signed-off-by: bkioshn --- rust/signed_doc/src/metadata/doc_type.rs | 181 +++++++++++++++++++++++ rust/signed_doc/src/metadata/mod.rs | 1 + 2 files changed, 182 insertions(+) create mode 100644 rust/signed_doc/src/metadata/doc_type.rs diff --git a/rust/signed_doc/src/metadata/doc_type.rs b/rust/signed_doc/src/metadata/doc_type.rs new file mode 100644 index 00000000000..6e18b6859ee --- /dev/null +++ b/rust/signed_doc/src/metadata/doc_type.rs @@ -0,0 +1,181 @@ +//! Document Type. + +use std::fmt::{Display, Formatter}; + +use catalyst_types::{ + problem_report::ProblemReport, + uuid::{CborContext, Uuid, UuidV4}, +}; +use minicbor::{Decode, Decoder, Encode}; + +/// List of `UUIDv4` document type. +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct DocType(Vec); + +impl DocType { + /// Get a list of `UUIDv4` document types. + #[allow(dead_code)] + pub fn doc_types(&self) -> &Vec { + &self.0 + } +} + +impl From for DocType { + fn from(value: UuidV4) -> Self { + DocType(vec![value]) + } +} + +impl TryFrom for DocType { + type Error = anyhow::Error; + + fn try_from(value: Uuid) -> Result { + let uuid_v4 = UuidV4::try_from(value)?; + Ok(DocType(vec![uuid_v4])) + } +} + +impl From> for DocType { + fn from(value: Vec) -> Self { + DocType(value) + } +} + +impl Display for DocType { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "[{}]", + self.0 + .iter() + .map(UuidV4::to_string) + .collect::>() + .join(", ") + ) + } +} + +// ; Document Type +// document_type = [ 1* uuid_v4 ] +// ; UUIDv4 +// uuid_v4 = #6.37(bytes .size 16) +impl Decode<'_, ProblemReport> for DocType { + fn decode( + d: &mut Decoder, report: &mut ProblemReport, + ) -> Result { + const CONTEXT: &str = "DocType decoding"; + let parse_uuid = |d: &mut Decoder| UuidV4::decode(d, &mut CborContext::Tagged); + + match d.datatype()? { + minicbor::data::Type::Array => { + let len = d.array()?.ok_or_else(|| { + report.other("Unable to decode array length", CONTEXT); + minicbor::decode::Error::message(format!( + "{CONTEXT}: Unable to decode array length" + )) + })?; + + if len == 0 { + report.invalid_value( + "array length", + "0", + "must contain at least one UUIDv4", + CONTEXT, + ); + return Err(minicbor::decode::Error::message(format!( + "{CONTEXT}: empty array" + ))); + } + + (0..len) + .map(|_| parse_uuid(d)) + .collect::, _>>() + .map(Self) + .map_err(|e| { + report.other(&format!("Invalid UUIDv4 in array: {e}"), CONTEXT); + minicbor::decode::Error::message(format!( + "{CONTEXT}: Invalid UUIDv4 in array: {e}" + )) + }) + }, + minicbor::data::Type::Tag => { + // Handle single tagged UUID + parse_uuid(d).map(|uuid| Self(vec![uuid])).map_err(|e| { + report.other(&format!("Invalid single UUIDv4: {e}"), CONTEXT); + minicbor::decode::Error::message(format!( + "{CONTEXT}: Invalid single UUIDv4: {e}" + )) + }) + }, + other => { + report.invalid_value( + "decoding type", + &format!("{other:?}"), + "array or tag cbor", + CONTEXT, + ); + Err(minicbor::decode::Error::message(format!( + "{CONTEXT}: expected array of UUIDor tagged UUIDv4, got {other:?}", + ))) + }, + } + } +} + +impl Encode for DocType { + fn encode( + &self, e: &mut minicbor::Encoder, report: &mut ProblemReport, + ) -> Result<(), minicbor::encode::Error> { + const CONTEXT: &str = "DocType encoding"; + e.array(self.0.len().try_into().map_err(|_| { + report.other("Unable to encode array length", CONTEXT); + minicbor::encode::Error::message(format!("{CONTEXT}, unable to encode array length")) + })?)?; + for id in &self.0 { + UuidV4::encode(id, e, &mut CborContext::Tagged).map_err(|_| { + report.other("Failed to encode UUIDv4", CONTEXT); + minicbor::encode::Error::message(format!("{CONTEXT}: UUIDv4 encoding failed")) + })?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + + use minicbor::Encoder; + + use super::*; + + #[test] + fn test_doc_type() { + let mut report = ProblemReport::new("test doc type"); + let uuidv4 = UuidV4::new(); + assert_eq!(DocType::from(uuidv4).0.len(), 1); + // Multiple doc types + let doc_type_list: DocType = vec![uuidv4, uuidv4].into(); + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + doc_type_list + .encode(&mut encoder, &mut report) + .expect("Failed to encode Doc Type"); + let mut decoder = Decoder::new(&buffer); + let decoded_doc_type = + DocType::decode(&mut decoder, &mut report).expect("Failed to decode Doc Type"); + assert_eq!(decoded_doc_type, doc_type_list); + + // Singer doc type + // + // 37(h'5e60e623ad024a1ba1ac406db978ee48') + let single_uuid = hex::decode("D825505E60E623AD024A1BA1AC406DB978EE48") + .expect("Failed to decode single UUID"); + let decoded_doc_type = DocType::decode(&mut Decoder::new(&single_uuid), &mut report) + .expect("Failed to decode Doc Type"); + assert_eq!(decoded_doc_type.0.len(), 1); + + // Empty doc type + let mut decoder = Decoder::new(&[]); + assert!(DocType::decode(&mut decoder, &mut report).is_err()); + } +} diff --git a/rust/signed_doc/src/metadata/mod.rs b/rust/signed_doc/src/metadata/mod.rs index bbbdb1677d7..c0b8284c848 100644 --- a/rust/signed_doc/src/metadata/mod.rs +++ b/rust/signed_doc/src/metadata/mod.rs @@ -3,6 +3,7 @@ use std::fmt::{Display, Formatter}; mod content_encoding; mod content_type; +mod doc_type; mod document_ref; mod extra_fields; mod section; From 30e66235bac029b5b370c742874c9fb3a5c2e7dc Mon Sep 17 00:00:00 2001 From: bkioshn Date: Tue, 20 May 2025 18:55:12 +0700 Subject: [PATCH 02/18] wip: apply doctype Signed-off-by: bkioshn --- rust/catalyst-types/src/uuid/mod.rs | 3 +- rust/signed_doc/src/lib.rs | 11 ++-- rust/signed_doc/src/metadata/doc_type.rs | 55 ++++++++++++++++++- rust/signed_doc/src/metadata/mod.rs | 48 ++++++++-------- rust/signed_doc/src/validator/mod.rs | 51 ++++++++--------- .../signed_doc/src/validator/rules/doc_ref.rs | 20 +++---- .../src/validator/rules/parameters.rs | 9 +-- rust/signed_doc/src/validator/rules/reply.rs | 9 +-- .../src/validator/rules/template.rs | 4 +- 9 files changed, 127 insertions(+), 83 deletions(-) diff --git a/rust/catalyst-types/src/uuid/mod.rs b/rust/catalyst-types/src/uuid/mod.rs index 3e25737e1de..8baebeaeff5 100644 --- a/rust/catalyst-types/src/uuid/mod.rs +++ b/rust/catalyst-types/src/uuid/mod.rs @@ -15,8 +15,7 @@ use minicbor::data::Tag; pub const INVALID_UUID: uuid::Uuid = uuid::Uuid::from_bytes([0x00; 16]); /// UUID CBOR tag . -#[allow(dead_code)] -const UUID_CBOR_TAG: u64 = 37; +pub const UUID_CBOR_TAG: u64 = 37; /// Uuid validation errors, which could occur during decoding or converting to /// `UuidV4` or `UuidV7` types. diff --git a/rust/signed_doc/src/lib.rs b/rust/signed_doc/src/lib.rs index 39908e7f117..0d7a0fb3f51 100644 --- a/rust/signed_doc/src/lib.rs +++ b/rust/signed_doc/src/lib.rs @@ -22,6 +22,7 @@ pub use catalyst_types::{ }; pub use content::Content; use coset::{CborSerializable, Header, TaggedCborSerializable}; +use metadata::DocType; pub use metadata::{ContentEncoding, ContentType, DocumentRef, ExtraFields, Metadata, Section}; use minicbor::{decode, encode, Decode, Decoder, Encode}; pub use signature::{CatalystId, Signatures}; @@ -84,7 +85,7 @@ impl CatalystSignedDocument { /// /// # Errors /// - Missing 'type' field. - pub fn doc_type(&self) -> anyhow::Result { + pub fn doc_type(&self) -> anyhow::Result<&DocType> { self.inner.metadata.doc_type() } @@ -221,12 +222,12 @@ impl Decode<'_, ()> for CatalystSignedDocument { minicbor::decode::Error::message(format!("Invalid COSE Sign document: {e}")) })?; - let report = ProblemReport::new(PROBLEM_REPORT_CTX); - let metadata = Metadata::from_protected_header(&cose_sign.protected, &report); - let signatures = Signatures::from_cose_sig_list(&cose_sign.signatures, &report); + let mut report = ProblemReport::new(PROBLEM_REPORT_CTX); + let metadata = Metadata::from_protected_header(&cose_sign.protected, &mut report); + let signatures = Signatures::from_cose_sig_list(&cose_sign.signatures, &mut report); let content = if let Some(payload) = cose_sign.payload { - Content::from_encoded(payload, metadata.content_encoding(), &report) + Content::from_encoded(payload, metadata.content_encoding(), &mut report) } else { report.missing_field("COSE Sign Payload", "Missing document content (payload)"); Content::default() diff --git a/rust/signed_doc/src/metadata/doc_type.rs b/rust/signed_doc/src/metadata/doc_type.rs index 6e18b6859ee..ec371d75e6c 100644 --- a/rust/signed_doc/src/metadata/doc_type.rs +++ b/rust/signed_doc/src/metadata/doc_type.rs @@ -1,15 +1,20 @@ //! Document Type. -use std::fmt::{Display, Formatter}; +use std::{ + fmt::{Display, Formatter}, + hash::Hasher, +}; use catalyst_types::{ problem_report::ProblemReport, uuid::{CborContext, Uuid, UuidV4}, }; -use minicbor::{Decode, Decoder, Encode}; +use coset::{cbor::Value, CborSerializable}; +use minicbor::{Decode, Decoder, Encode, Encoder}; +use std::hash::Hash; /// List of `UUIDv4` document type. -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Eq)] pub struct DocType(Vec); impl DocType { @@ -18,6 +23,37 @@ impl DocType { pub fn doc_types(&self) -> &Vec { &self.0 } + + pub fn to_value(&self, report: &mut ProblemReport) -> anyhow::Result { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + self.encode(&mut encoder, report)?; + Value::from_slice(&buffer) + .map_err(|e| anyhow::anyhow!("Failed to convert DocType to Value: {e}")) + } +} + +/// Returns an `DocType` from the provided argument. +/// Reduce redundant conversion. +/// This function should be used for hardcoded values, panic if conversion fail. +#[allow(clippy::expect_used)] +pub(crate) fn expect_doc_type(t: T) -> DocType +where + T: TryInto, + T::Error: std::fmt::Debug, +{ + t.try_into().expect("Failed to convert to DocType") +} + +impl Hash for DocType { + fn hash(&self, state: &mut H) { + let list = self + .0 + .iter() + .map(|uuid| uuid.to_string()) + .collect::>(); + list.hash(state); + } } impl From for DocType { @@ -144,6 +180,7 @@ impl Encode for DocType { #[cfg(test)] mod tests { + use catalyst_types::uuid::UUID_CBOR_TAG; use minicbor::Encoder; use super::*; @@ -165,6 +202,18 @@ mod tests { DocType::decode(&mut decoder, &mut report).expect("Failed to decode Doc Type"); assert_eq!(decoded_doc_type, doc_type_list); + // Convert `DocType` to `Value` + let value = doc_type_list + .to_value(&mut report) + .expect("Failed to convert to Value"); + assert_eq!(value.as_array().unwrap().len(), 2); + for v in value.as_array().unwrap() { + let (tag, v) = v.as_tag().unwrap(); + assert_eq!(tag, UUID_CBOR_TAG); + let bytes = v.as_bytes().unwrap(); + assert_eq!(bytes.len(), 16); + } + // Singer doc type // // 37(h'5e60e623ad024a1ba1ac406db978ee48') diff --git a/rust/signed_doc/src/metadata/mod.rs b/rust/signed_doc/src/metadata/mod.rs index c0b8284c848..4e6b4eb7657 100644 --- a/rust/signed_doc/src/metadata/mod.rs +++ b/rust/signed_doc/src/metadata/mod.rs @@ -9,19 +9,17 @@ mod extra_fields; mod section; pub(crate) mod utils; -use catalyst_types::{ - problem_report::ProblemReport, - uuid::{UuidV4, UuidV7}, -}; +use catalyst_types::{problem_report::ProblemReport, uuid::UuidV7}; pub use content_encoding::ContentEncoding; pub use content_type::ContentType; -use coset::{cbor::Value, iana::CoapContentFormat}; +use coset::{cbor::Value, iana::CoapContentFormat, CborSerializable}; +pub(crate) use doc_type::expect_doc_type; +pub use doc_type::{DocType}; pub use document_ref::DocumentRef; pub use extra_fields::ExtraFields; +use minicbor::{Decode, Decoder}; pub use section::Section; -use utils::{ - cose_protected_header_find, decode_document_field_from_protected_header, CborUuidV4, CborUuidV7, -}; +use utils::{cose_protected_header_find, decode_document_field_from_protected_header, CborUuidV7}; /// `content_encoding` field COSE key value const CONTENT_ENCODING_KEY: &str = "Content-Encoding"; @@ -41,9 +39,9 @@ pub struct Metadata(InnerMetadata); /// An actual representation of all metadata fields. #[derive(Clone, Debug, PartialEq, serde::Deserialize, Default)] pub(crate) struct InnerMetadata { - /// Document Type `UUIDv4`. + /// Document Type, list of `UUIDv4`. #[serde(rename = "type")] - doc_type: Option, + doc_type: Option, /// Document ID `UUIDv7`. id: Option, /// Document Version `UUIDv7`. @@ -60,13 +58,14 @@ pub(crate) struct InnerMetadata { } impl Metadata { - /// Return Document Type `UUIDv4`. + /// Return Document Type `DocType` - a list of `UUIDv4`. /// /// # Errors /// - Missing 'type' field. - pub fn doc_type(&self) -> anyhow::Result { + pub fn doc_type(&self) -> anyhow::Result<&DocType> { self.0 .doc_type + .as_ref() .ok_or(anyhow::anyhow!("Missing 'type' field")) } @@ -132,7 +131,7 @@ impl Metadata { /// Converting COSE Protected Header to Metadata. pub(crate) fn from_protected_header( - protected: &coset::ProtectedHeader, report: &ProblemReport, + protected: &coset::ProtectedHeader, report: &mut ProblemReport, ) -> Self { let metadata = InnerMetadata::from_protected_header(protected, report); Self::from_metadata_fields(metadata, report) @@ -143,7 +142,7 @@ impl InnerMetadata { /// Converting COSE Protected Header to Metadata fields, collecting decoding report /// issues. pub(crate) fn from_protected_header( - protected: &coset::ProtectedHeader, report: &ProblemReport, + protected: &coset::ProtectedHeader, report: &mut ProblemReport, ) -> Self { /// Context for problem report messages during decoding from COSE protected /// header. @@ -186,13 +185,17 @@ impl InnerMetadata { } } - metadata.doc_type = decode_document_field_from_protected_header::( + metadata.doc_type = cose_protected_header_find( protected, - TYPE_KEY, - COSE_DECODING_CONTEXT, - report, + |key| matches!(key, coset::Label::Text(label) if label.eq_ignore_ascii_case(TYPE_KEY)), ) - .map(|v| v.0); + .and_then(|value| { + DocType::decode( + &mut Decoder::new(&value.clone().to_vec().unwrap_or_default()), + report, + ) + .ok() + }); metadata.id = decode_document_field_from_protected_header::( protected, @@ -241,11 +244,10 @@ impl TryFrom<&Metadata> for coset::Header { ); } + // Dummy report, use just to pass the encoder + let mut report = ProblemReport::new("TryFrom Metadata to COSE Header"); builder = builder - .text_value( - TYPE_KEY.to_string(), - Value::try_from(CborUuidV4(meta.doc_type()?))?, - ) + .text_value(TYPE_KEY.to_string(), meta.doc_type()?.to_value(&mut report)?) .text_value( ID_KEY.to_string(), Value::try_from(CborUuidV7(meta.doc_id()?))?, diff --git a/rust/signed_doc/src/validator/mod.rs b/rust/signed_doc/src/validator/mod.rs index 0c755bdcb5a..f5234569103 100644 --- a/rust/signed_doc/src/validator/mod.rs +++ b/rust/signed_doc/src/validator/mod.rs @@ -5,7 +5,6 @@ pub(crate) mod utils; use std::{ collections::HashMap, - fmt, sync::LazyLock, time::{Duration, SystemTime}, }; @@ -14,7 +13,6 @@ use anyhow::Context; use catalyst_types::{ catalyst_id::{role_index::RoleId, CatalystId}, problem_report::ProblemReport, - uuid::{Uuid, UuidV4}, }; use coset::{CoseSign, CoseSignature}; use rules::{ @@ -28,24 +26,17 @@ use crate::{ PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, PROPOSAL_DOCUMENT_UUID_TYPE, PROPOSAL_TEMPLATE_UUID_TYPE, }, + metadata::{expect_doc_type, DocType}, providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider}, CatalystSignedDocument, ContentEncoding, ContentType, }; /// A table representing a full set or validation rules per document id. -static DOCUMENT_RULES: LazyLock> = LazyLock::new(document_rules_init); - -/// Returns an [`UuidV4`] from the provided argument, panicking if the argument is -/// invalid. -#[allow(clippy::expect_used)] -fn expect_uuidv4(t: T) -> UuidV4 -where T: TryInto { - t.try_into().expect("Must be a valid UUID V4") -} +static DOCUMENT_RULES: LazyLock> = LazyLock::new(document_rules_init); /// `DOCUMENT_RULES` initialization function #[allow(clippy::expect_used)] -fn document_rules_init() -> HashMap { +fn document_rules_init() -> HashMap { let mut document_rules_map = HashMap::new(); let proposal_document_rules = Rules { @@ -57,10 +48,10 @@ fn document_rules_init() -> HashMap { optional: false, }, content: ContentRule::Templated { - exp_template_type: expect_uuidv4(PROPOSAL_TEMPLATE_UUID_TYPE), + exp_template_type: expect_doc_type(PROPOSAL_TEMPLATE_UUID_TYPE), }, parameters: ParametersRule::Specified { - exp_parameters_type: expect_uuidv4(CATEGORY_DOCUMENT_UUID_TYPE), + exp_parameters_type: expect_doc_type(CATEGORY_DOCUMENT_UUID_TYPE), optional: true, }, doc_ref: RefRule::NotSpecified, @@ -71,7 +62,10 @@ fn document_rules_init() -> HashMap { }, }; - document_rules_map.insert(PROPOSAL_DOCUMENT_UUID_TYPE, proposal_document_rules); + document_rules_map.insert( + expect_doc_type(PROPOSAL_DOCUMENT_UUID_TYPE), + proposal_document_rules, + ); let comment_document_rules = Rules { content_type: ContentTypeRule { @@ -82,14 +76,14 @@ fn document_rules_init() -> HashMap { optional: false, }, content: ContentRule::Templated { - exp_template_type: expect_uuidv4(COMMENT_TEMPLATE_UUID_TYPE), + exp_template_type: expect_doc_type(COMMENT_TEMPLATE_UUID_TYPE), }, doc_ref: RefRule::Specified { - exp_ref_type: expect_uuidv4(PROPOSAL_DOCUMENT_UUID_TYPE), + exp_ref_type: expect_doc_type(PROPOSAL_DOCUMENT_UUID_TYPE), optional: false, }, reply: ReplyRule::Specified { - exp_reply_type: expect_uuidv4(COMMENT_DOCUMENT_UUID_TYPE), + exp_reply_type: expect_doc_type(COMMENT_DOCUMENT_UUID_TYPE), optional: true, }, section: SectionRule::Specified { optional: true }, @@ -98,7 +92,10 @@ fn document_rules_init() -> HashMap { exp: &[RoleId::Role0], }, }; - document_rules_map.insert(COMMENT_DOCUMENT_UUID_TYPE, comment_document_rules); + document_rules_map.insert( + expect_doc_type(COMMENT_DOCUMENT_UUID_TYPE), + comment_document_rules, + ); let proposal_action_json_schema = jsonschema::options() .with_draft(jsonschema::Draft::Draft7) @@ -119,11 +116,11 @@ fn document_rules_init() -> HashMap { }, content: ContentRule::Static(ContentSchema::Json(proposal_action_json_schema)), parameters: ParametersRule::Specified { - exp_parameters_type: expect_uuidv4(CATEGORY_DOCUMENT_UUID_TYPE), + exp_parameters_type: expect_doc_type(CATEGORY_DOCUMENT_UUID_TYPE), optional: true, }, doc_ref: RefRule::Specified { - exp_ref_type: expect_uuidv4(PROPOSAL_DOCUMENT_UUID_TYPE), + exp_ref_type: expect_doc_type(PROPOSAL_DOCUMENT_UUID_TYPE), optional: false, }, reply: ReplyRule::NotSpecified, @@ -134,7 +131,7 @@ fn document_rules_init() -> HashMap { }; document_rules_map.insert( - PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, + expect_doc_type(PROPOSAL_ACTION_DOCUMENT_UUID_TYPE), proposal_submission_action_rules, ); @@ -151,7 +148,9 @@ fn document_rules_init() -> HashMap { pub async fn validate( doc: &CatalystSignedDocument, provider: &Provider, ) -> anyhow::Result -where Provider: CatalystSignedDocumentProvider { +where + Provider: CatalystSignedDocumentProvider, +{ let Ok(doc_type) = doc.doc_type() else { doc.report().missing_field( "type", @@ -164,7 +163,7 @@ where Provider: CatalystSignedDocumentProvider { return Ok(false); } - let Some(rules) = DOCUMENT_RULES.get(&doc_type.uuid()) else { + let Some(rules) = DOCUMENT_RULES.get(&doc_type) else { doc.report().invalid_value( "`type`", &doc.doc_type()?.to_string(), @@ -186,7 +185,9 @@ where Provider: CatalystSignedDocumentProvider { fn validate_id_and_ver( doc: &CatalystSignedDocument, provider: &Provider, ) -> anyhow::Result -where Provider: CatalystSignedDocumentProvider { +where + Provider: CatalystSignedDocumentProvider, +{ let id = doc.doc_id().ok(); let ver = doc.doc_ver().ok(); if id.is_none() { diff --git a/rust/signed_doc/src/validator/rules/doc_ref.rs b/rust/signed_doc/src/validator/rules/doc_ref.rs index 53fec6825fe..3212985afa1 100644 --- a/rust/signed_doc/src/validator/rules/doc_ref.rs +++ b/rust/signed_doc/src/validator/rules/doc_ref.rs @@ -1,13 +1,9 @@ //! `ref` rule type impl. -use catalyst_types::{ - problem_report::ProblemReport, - uuid::{Uuid, UuidV4}, -}; +use catalyst_types::{problem_report::ProblemReport}; use crate::{ - providers::CatalystSignedDocumentProvider, validator::utils::validate_provided_doc, - CatalystSignedDocument, + metadata::DocType, providers::CatalystSignedDocumentProvider, validator::utils::validate_provided_doc, CatalystSignedDocument }; /// `ref` field validation rule @@ -16,7 +12,7 @@ pub(crate) enum RefRule { /// Is 'ref' specified Specified { /// expected `type` field of the referenced doc - exp_ref_type: UuidV4, + exp_ref_type: DocType, /// optional flag for the `ref` field optional: bool, }, @@ -28,7 +24,9 @@ impl RefRule { pub(crate) async fn check( &self, doc: &CatalystSignedDocument, provider: &Provider, ) -> anyhow::Result - where Provider: CatalystSignedDocumentProvider { + where + Provider: CatalystSignedDocumentProvider, + { if let Self::Specified { exp_ref_type, optional, @@ -36,7 +34,7 @@ impl RefRule { { if let Some(doc_ref) = doc.doc_meta().doc_ref() { let ref_validator = |ref_doc: CatalystSignedDocument| { - referenced_doc_check(&ref_doc, exp_ref_type.uuid(), "ref", doc.report()) + referenced_doc_check(&ref_doc, exp_ref_type, "ref", doc.report()) }; return validate_provided_doc(&doc_ref, provider, doc.report(), ref_validator) .await; @@ -63,13 +61,13 @@ impl RefRule { /// A generic implementation of the referenced document validation. pub(crate) fn referenced_doc_check( - ref_doc: &CatalystSignedDocument, exp_ref_type: Uuid, field_name: &str, report: &ProblemReport, + ref_doc: &CatalystSignedDocument, exp_ref_type: &DocType, field_name: &str, report: &ProblemReport, ) -> bool { let Ok(ref_doc_type) = ref_doc.doc_type() else { report.missing_field("type", "Referenced document must have type field"); return false; }; - if ref_doc_type.uuid() != exp_ref_type { + if ref_doc_type != exp_ref_type { report.invalid_value( field_name, ref_doc_type.to_string().as_str(), diff --git a/rust/signed_doc/src/validator/rules/parameters.rs b/rust/signed_doc/src/validator/rules/parameters.rs index 290d158439d..11359fa87bf 100644 --- a/rust/signed_doc/src/validator/rules/parameters.rs +++ b/rust/signed_doc/src/validator/rules/parameters.rs @@ -1,11 +1,8 @@ //! `parameters` rule type impl. -use catalyst_types::uuid::UuidV4; - use super::doc_ref::referenced_doc_check; use crate::{ - providers::CatalystSignedDocumentProvider, validator::utils::validate_provided_doc, - CatalystSignedDocument, + metadata::DocType, providers::CatalystSignedDocumentProvider, validator::utils::validate_provided_doc, CatalystSignedDocument }; /// `parameters` field validation rule @@ -14,7 +11,7 @@ pub(crate) enum ParametersRule { /// Is `parameters` specified Specified { /// expected `type` field of the parameter doc - exp_parameters_type: UuidV4, + exp_parameters_type: DocType, /// optional flag for the `parameters` field optional: bool, }, @@ -37,7 +34,7 @@ impl ParametersRule { let parameters_validator = |replied_doc: CatalystSignedDocument| { referenced_doc_check( &replied_doc, - exp_parameters_type.uuid(), + exp_parameters_type, "parameters", doc.report(), ) diff --git a/rust/signed_doc/src/validator/rules/reply.rs b/rust/signed_doc/src/validator/rules/reply.rs index 5ac256667db..16d656273d8 100644 --- a/rust/signed_doc/src/validator/rules/reply.rs +++ b/rust/signed_doc/src/validator/rules/reply.rs @@ -1,11 +1,8 @@ //! `reply` rule type impl. -use catalyst_types::uuid::UuidV4; - use super::doc_ref::referenced_doc_check; use crate::{ - providers::CatalystSignedDocumentProvider, validator::utils::validate_provided_doc, - CatalystSignedDocument, + metadata::DocType, providers::CatalystSignedDocumentProvider, validator::utils::validate_provided_doc, CatalystSignedDocument }; /// `reply` field validation rule @@ -14,7 +11,7 @@ pub(crate) enum ReplyRule { /// Is 'reply' specified Specified { /// expected `type` field of the replied doc - exp_reply_type: UuidV4, + exp_reply_type: DocType, /// optional flag for the `ref` field optional: bool, }, @@ -37,7 +34,7 @@ impl ReplyRule { let reply_validator = |replied_doc: CatalystSignedDocument| { if !referenced_doc_check( &replied_doc, - exp_reply_type.uuid(), + exp_reply_type, "reply", doc.report(), ) { diff --git a/rust/signed_doc/src/validator/rules/template.rs b/rust/signed_doc/src/validator/rules/template.rs index 0d5b0c9aaab..e3c892498da 100644 --- a/rust/signed_doc/src/validator/rules/template.rs +++ b/rust/signed_doc/src/validator/rules/template.rs @@ -6,7 +6,7 @@ use catalyst_types::uuid::UuidV4; use super::doc_ref::referenced_doc_check; use crate::{ - metadata::ContentType, providers::CatalystSignedDocumentProvider, + metadata::{ContentType, DocType}, providers::CatalystSignedDocumentProvider, validator::utils::validate_provided_doc, CatalystSignedDocument, }; @@ -23,7 +23,7 @@ pub(crate) enum ContentRule { /// Based on the 'template' field and loaded corresponding template document Templated { /// expected `type` field of the template - exp_template_type: UuidV4, + exp_template_type: DocType, }, /// Statically defined document's content schema. /// `template` field should not been specified From df05394283f10a63f37d38c80b7e203932040ace Mon Sep 17 00:00:00 2001 From: bkioshn Date: Mon, 26 May 2025 21:53:20 +0700 Subject: [PATCH 03/18] fix(signed-doc): add more function to DocType Signed-off-by: bkioshn --- rust/signed_doc/src/metadata/doc_type.rs | 57 ++++++++++++++---------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/rust/signed_doc/src/metadata/doc_type.rs b/rust/signed_doc/src/metadata/doc_type.rs index c26240df030..a3af6cdd9a3 100644 --- a/rust/signed_doc/src/metadata/doc_type.rs +++ b/rust/signed_doc/src/metadata/doc_type.rs @@ -1,24 +1,28 @@ //! Document Type. -use std::fmt::{Display, Formatter}; +use std::{ + fmt::{Display, Formatter}, + hash::{Hash, Hasher}, +}; use catalyst_types::{ problem_report::ProblemReport, uuid::{CborContext, Uuid, UuidV4}, }; -use minicbor::{Decode, Decoder, Encode}; +use coset::cbor::Value; +use minicbor::{Decode, Decoder, Encode, Encoder}; use tracing::warn; use crate::{ decode_context::{CompatibilityPolicy, DecodeContext}, doc_types::{ - COMMENT_DOCUMENT_UUID_TYPE, PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, - PROPOSAL_DOCUMENT_UUID_TYPE, SUBMISSION_ACTION, + COMMENT_DOCUMENT_UUID_TYPE, PROPOSAL_ACTION_DOC, PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, + PROPOSAL_COMMENT_DOC, PROPOSAL_DOCUMENT_UUID_TYPE, PROPOSAL_DOC_TYPE, }, }; /// List of `UUIDv4` document type. -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct DocType(Vec); /// `DocType` Errors. @@ -38,6 +42,25 @@ impl DocType { pub fn doc_types(&self) -> &Vec { &self.0 } + + /// Convert `DocType` to coset `Value`. + pub(crate) fn to_value(&self, report: &mut ProblemReport) -> anyhow::Result { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + self.encode(&mut encoder, report)?; + Ok(Value::from(buffer.clone())) + } +} + +impl Hash for DocType { + fn hash(&self, state: &mut H) { + let list = self + .0 + .iter() + .map(std::string::ToString::to_string) + .collect::>(); + list.hash(state); + } } impl From for DocType { @@ -166,12 +189,7 @@ impl Decode<'_, DecodeContext<'_>> for DocType { minicbor::decode::Error::message(format!("{CONTEXT}: {msg}")) })?; - let ids = map_doc_type(uuid.into()).map_err(|e| { - decode_context.report.other(&e.to_string(), CONTEXT); - minicbor::decode::Error::message(format!("{CONTEXT}: {e}")) - })?; - - let doc_type = ids.to_vec().try_into().map_err(|e: DocTypeError| { + let doc_type = map_doc_type(uuid.into()).map_err(|e| { decode_context.report.other(&e.to_string(), CONTEXT); minicbor::decode::Error::message(format!("{CONTEXT}: {e}")) })?; @@ -205,20 +223,11 @@ impl Decode<'_, DecodeContext<'_>> for DocType { /// Map single UUID doc type to new list of doc types /// -fn map_doc_type(uuid: Uuid) -> anyhow::Result<&'static [Uuid]> { - const PROPOSAL_DOC: &[Uuid] = &[PROPOSAL_DOCUMENT_UUID_TYPE]; - const PROPOSAL_COMMENT_DOC: &[Uuid] = - &[COMMENT_DOCUMENT_UUID_TYPE, PROPOSAL_DOCUMENT_UUID_TYPE]; - const PROPOSAL_ACTION_DOC: &[Uuid] = &[ - PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, - PROPOSAL_DOCUMENT_UUID_TYPE, - SUBMISSION_ACTION, - ]; - +fn map_doc_type(uuid: Uuid) -> anyhow::Result { match uuid { - id if id == PROPOSAL_DOCUMENT_UUID_TYPE => Ok(PROPOSAL_DOC), - id if id == COMMENT_DOCUMENT_UUID_TYPE => Ok(PROPOSAL_COMMENT_DOC), - id if id == PROPOSAL_ACTION_DOCUMENT_UUID_TYPE => Ok(PROPOSAL_ACTION_DOC), + id if id == PROPOSAL_DOCUMENT_UUID_TYPE => Ok(PROPOSAL_DOC_TYPE.clone()), + id if id == COMMENT_DOCUMENT_UUID_TYPE => Ok(PROPOSAL_COMMENT_DOC.clone()), + id if id == PROPOSAL_ACTION_DOCUMENT_UUID_TYPE => Ok(PROPOSAL_ACTION_DOC.clone()), _ => anyhow::bail!("Unknown document type: {uuid}"), } } From e89f3e1b2f3fa4e4b99156a35e0c3d9f32b6a6a2 Mon Sep 17 00:00:00 2001 From: bkioshn Date: Mon, 26 May 2025 21:54:09 +0700 Subject: [PATCH 04/18] fix(signed-doc): map old doctype to new doctype Signed-off-by: bkioshn --- rust/signed_doc/src/doc_types/mod.rs | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/rust/signed_doc/src/doc_types/mod.rs b/rust/signed_doc/src/doc_types/mod.rs index 15bcb5948f5..8946b97ec87 100644 --- a/rust/signed_doc/src/doc_types/mod.rs +++ b/rust/signed_doc/src/doc_types/mod.rs @@ -1,8 +1,12 @@ //! An implementation of different defined document types //! +use std::sync::LazyLock; + use catalyst_types::uuid::Uuid; +use crate::DocType; + /// Proposal document `UuidV4` type. pub const PROPOSAL_DOCUMENT_UUID_TYPE: Uuid = Uuid::from_u128(0x7808_D2BA_D511_40AF_84E8_C0D1_625F_DFDC); @@ -53,3 +57,36 @@ pub const IMMUTABLE_LEDGER_BLOCK_UUID_TYPE: Uuid = Uuid::from_u128(0xD9E7_E6CE_2401_4D7D_9492_F4F7_C642_41C3); /// Submission Action `UuidV4` type. pub const SUBMISSION_ACTION: Uuid = Uuid::from_u128(0x7892_7329_CFD9_4EA1_9C71_0E01_9B12_6A65); + +// -------- Mapping old document types to new document types -------- + +/// Map proposal document type to new doc type list. +#[allow(clippy::expect_used)] +pub(crate) static PROPOSAL_DOC_TYPE: LazyLock = LazyLock::new(|| { + let ids = &[PROPOSAL_DOCUMENT_UUID_TYPE]; + ids.to_vec() + .try_into() + .expect("Failed to convert proposal document Uuid to DocType") +}); + +/// Map proposal comment document type to new doc type list. +#[allow(clippy::expect_used)] +pub(crate) static PROPOSAL_COMMENT_DOC: LazyLock = LazyLock::new(|| { + let ids = &[COMMENT_DOCUMENT_UUID_TYPE, PROPOSAL_DOCUMENT_UUID_TYPE]; + ids.to_vec() + .try_into() + .expect("Failed to convert proposal comment document Uuid to DocType") +}); + +/// Map proposal action document type to new doc type list. +#[allow(clippy::expect_used)] +pub(crate) static PROPOSAL_ACTION_DOC: LazyLock = LazyLock::new(|| { + let ids = &[ + PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, + PROPOSAL_DOCUMENT_UUID_TYPE, + SUBMISSION_ACTION, + ]; + ids.to_vec() + .try_into() + .expect("Failed to convert proposal action document Uuid to DocType") +}); From 299b61bafa1e752d719221ebcd12fffc9ea1f1d5 Mon Sep 17 00:00:00 2001 From: bkioshn Date: Mon, 26 May 2025 21:54:31 +0700 Subject: [PATCH 05/18] fix(signed-doc): add eq to uuidv4 Signed-off-by: bkioshn --- rust/catalyst-types/src/uuid/uuid_v4.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/catalyst-types/src/uuid/uuid_v4.rs b/rust/catalyst-types/src/uuid/uuid_v4.rs index c7e2dbb8149..059102a15ec 100644 --- a/rust/catalyst-types/src/uuid/uuid_v4.rs +++ b/rust/catalyst-types/src/uuid/uuid_v4.rs @@ -7,7 +7,7 @@ use uuid::Uuid; use super::{decode_cbor_uuid, encode_cbor_uuid, CborContext, UuidError, INVALID_UUID}; /// Type representing a `UUIDv4`. -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Serialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, serde::Serialize)] pub struct UuidV4(Uuid); impl UuidV4 { From bfdf31f0102434ac94044f1a60d6853304c61515 Mon Sep 17 00:00:00 2001 From: bkioshn Date: Mon, 26 May 2025 21:55:19 +0700 Subject: [PATCH 06/18] fix(signed-doc): fix validator Signed-off-by: bkioshn --- rust/signed_doc/src/validator/mod.rs | 38 +++++++++++++++------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/rust/signed_doc/src/validator/mod.rs b/rust/signed_doc/src/validator/mod.rs index f5234569103..e6df5e25c1d 100644 --- a/rust/signed_doc/src/validator/mod.rs +++ b/rust/signed_doc/src/validator/mod.rs @@ -23,10 +23,10 @@ use rules::{ use crate::{ doc_types::{ CATEGORY_DOCUMENT_UUID_TYPE, COMMENT_DOCUMENT_UUID_TYPE, COMMENT_TEMPLATE_UUID_TYPE, - PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, PROPOSAL_DOCUMENT_UUID_TYPE, + PROPOSAL_ACTION_DOC, PROPOSAL_COMMENT_DOC, PROPOSAL_DOCUMENT_UUID_TYPE, PROPOSAL_DOC_TYPE, PROPOSAL_TEMPLATE_UUID_TYPE, }, - metadata::{expect_doc_type, DocType}, + metadata::DocType, providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider}, CatalystSignedDocument, ContentEncoding, ContentType, }; @@ -34,6 +34,18 @@ use crate::{ /// A table representing a full set or validation rules per document id. static DOCUMENT_RULES: LazyLock> = LazyLock::new(document_rules_init); +/// Returns an `DocType` from the provided argument. +/// Reduce redundant conversion. +/// This function should be used for hardcoded values, panic if conversion fail. +#[allow(clippy::expect_used)] +pub(crate) fn expect_doc_type(t: T) -> DocType +where + T: TryInto, + T::Error: std::fmt::Debug, +{ + t.try_into().expect("Failed to convert to DocType") +} + /// `DOCUMENT_RULES` initialization function #[allow(clippy::expect_used)] fn document_rules_init() -> HashMap { @@ -62,10 +74,7 @@ fn document_rules_init() -> HashMap { }, }; - document_rules_map.insert( - expect_doc_type(PROPOSAL_DOCUMENT_UUID_TYPE), - proposal_document_rules, - ); + document_rules_map.insert(PROPOSAL_DOC_TYPE.clone(), proposal_document_rules); let comment_document_rules = Rules { content_type: ContentTypeRule { @@ -92,10 +101,7 @@ fn document_rules_init() -> HashMap { exp: &[RoleId::Role0], }, }; - document_rules_map.insert( - expect_doc_type(COMMENT_DOCUMENT_UUID_TYPE), - comment_document_rules, - ); + document_rules_map.insert(PROPOSAL_COMMENT_DOC.clone(), comment_document_rules); let proposal_action_json_schema = jsonschema::options() .with_draft(jsonschema::Draft::Draft7) @@ -131,7 +137,7 @@ fn document_rules_init() -> HashMap { }; document_rules_map.insert( - expect_doc_type(PROPOSAL_ACTION_DOCUMENT_UUID_TYPE), + PROPOSAL_ACTION_DOC.clone(), proposal_submission_action_rules, ); @@ -148,9 +154,7 @@ fn document_rules_init() -> HashMap { pub async fn validate( doc: &CatalystSignedDocument, provider: &Provider, ) -> anyhow::Result -where - Provider: CatalystSignedDocumentProvider, -{ +where Provider: CatalystSignedDocumentProvider { let Ok(doc_type) = doc.doc_type() else { doc.report().missing_field( "type", @@ -163,7 +167,7 @@ where return Ok(false); } - let Some(rules) = DOCUMENT_RULES.get(&doc_type) else { + let Some(rules) = DOCUMENT_RULES.get(doc_type) else { doc.report().invalid_value( "`type`", &doc.doc_type()?.to_string(), @@ -185,9 +189,7 @@ where fn validate_id_and_ver( doc: &CatalystSignedDocument, provider: &Provider, ) -> anyhow::Result -where - Provider: CatalystSignedDocumentProvider, -{ +where Provider: CatalystSignedDocumentProvider { let id = doc.doc_id().ok(); let ver = doc.doc_ver().ok(); if id.is_none() { From c5d07fe0b0a6cda639bcf611c9a2a9556cde48ec Mon Sep 17 00:00:00 2001 From: bkioshn Date: Mon, 26 May 2025 21:55:40 +0700 Subject: [PATCH 07/18] fix(signed-doc): minor fixes Signed-off-by: bkioshn --- rust/signed_doc/src/lib.rs | 11 ++++-- rust/signed_doc/src/metadata/mod.rs | 33 ++++++++++-------- .../signed_doc/src/validator/rules/doc_ref.rs | 20 +++++------ .../src/validator/rules/parameters.rs | 9 ++--- rust/signed_doc/src/validator/rules/reply.rs | 16 ++++----- .../src/validator/rules/template.rs | 34 +++++++++++-------- rust/signed_doc/tests/decoding.rs | 3 +- 7 files changed, 69 insertions(+), 57 deletions(-) diff --git a/rust/signed_doc/src/lib.rs b/rust/signed_doc/src/lib.rs index 44e88d79dbb..0e9a3a3fb6d 100644 --- a/rust/signed_doc/src/lib.rs +++ b/rust/signed_doc/src/lib.rs @@ -23,6 +23,7 @@ pub use catalyst_types::{ }; pub use content::Content; use coset::{CborSerializable, Header, TaggedCborSerializable}; +use decode_context::{CompatibilityPolicy, DecodeContext}; pub use metadata::{ ContentEncoding, ContentType, DocType, DocumentRef, ExtraFields, Metadata, Section, }; @@ -83,7 +84,7 @@ impl From for CatalystSignedDocument { impl CatalystSignedDocument { // A bunch of getters to access the contents, or reason through the document, such as. - /// Return Document Type `UUIDv4`. + /// Return Document Type `DocType` - List of `UUIDv4`. /// /// # Errors /// - Missing 'type' field. @@ -225,8 +226,12 @@ impl Decode<'_, ()> for CatalystSignedDocument { })?; let mut report = ProblemReport::new(PROBLEM_REPORT_CTX); - let metadata = Metadata::from_protected_header(&cose_sign.protected, &mut report); - let signatures = Signatures::from_cose_sig_list(&cose_sign.signatures, &mut report); + let mut ctx = DecodeContext { + compatibility_policy: CompatibilityPolicy::Accept, + report: &mut report, + }; + let metadata = Metadata::from_protected_header(&cose_sign.protected, &mut ctx); + let signatures = Signatures::from_cose_sig_list(&cose_sign.signatures, &report); let content = if let Some(payload) = cose_sign.payload { Content::from_encoded(payload, metadata.content_encoding(), &mut report) diff --git a/rust/signed_doc/src/metadata/mod.rs b/rust/signed_doc/src/metadata/mod.rs index 70c8f866a2f..04b31929e32 100644 --- a/rust/signed_doc/src/metadata/mod.rs +++ b/rust/signed_doc/src/metadata/mod.rs @@ -3,7 +3,7 @@ use std::fmt::{Display, Formatter}; mod content_encoding; mod content_type; -mod doc_type; +pub(crate) mod doc_type; mod document_ref; mod extra_fields; mod section; @@ -12,14 +12,16 @@ pub(crate) mod utils; use catalyst_types::{problem_report::ProblemReport, uuid::UuidV7}; pub use content_encoding::ContentEncoding; pub use content_type::ContentType; -use coset::{cbor::Value, iana::CoapContentFormat}; -pub use doc_type::{DocType}; +use coset::{cbor::Value, iana::CoapContentFormat, CborSerializable}; +pub use doc_type::DocType; pub use document_ref::DocumentRef; pub use extra_fields::ExtraFields; use minicbor::{Decode, Decoder}; pub use section::Section; use utils::{cose_protected_header_find, decode_document_field_from_protected_header, CborUuidV7}; +use crate::decode_context::DecodeContext; + /// `content_encoding` field COSE key value const CONTENT_ENCODING_KEY: &str = "Content-Encoding"; /// `doc_type` field COSE key value @@ -130,10 +132,10 @@ impl Metadata { /// Converting COSE Protected Header to Metadata. pub(crate) fn from_protected_header( - protected: &coset::ProtectedHeader, report: &mut ProblemReport, + protected: &coset::ProtectedHeader, context: &mut DecodeContext, ) -> Self { - let metadata = InnerMetadata::from_protected_header(protected, report); - Self::from_metadata_fields(metadata, report) + let metadata = InnerMetadata::from_protected_header(protected, context); + Self::from_metadata_fields(metadata, context.report) } } @@ -141,13 +143,13 @@ impl InnerMetadata { /// Converting COSE Protected Header to Metadata fields, collecting decoding report /// issues. pub(crate) fn from_protected_header( - protected: &coset::ProtectedHeader, report: &mut ProblemReport, + protected: &coset::ProtectedHeader, context: &mut DecodeContext, ) -> Self { /// Context for problem report messages during decoding from COSE protected /// header. const COSE_DECODING_CONTEXT: &str = "COSE Protected Header to Metadata"; - let extra = ExtraFields::from_protected_header(protected, report); + let extra = ExtraFields::from_protected_header(protected, context.report); let mut metadata = Self { extra, ..Self::default() @@ -157,7 +159,7 @@ impl InnerMetadata { match ContentType::try_from(value) { Ok(ct) => metadata.content_type = Some(ct), Err(e) => { - report.conversion_error( + context.report.conversion_error( "COSE protected header content type", &format!("{value:?}"), &format!("Expected ContentType: {e}"), @@ -174,7 +176,7 @@ impl InnerMetadata { match ContentEncoding::try_from(value) { Ok(ce) => metadata.content_encoding = Some(ce), Err(e) => { - report.conversion_error( + context.report.conversion_error( "COSE protected header content encoding", &format!("{value:?}"), &format!("Expected ContentEncoding: {e}"), @@ -191,7 +193,7 @@ impl InnerMetadata { .and_then(|value| { DocType::decode( &mut Decoder::new(&value.clone().to_vec().unwrap_or_default()), - report, + context, ) .ok() }); @@ -200,7 +202,7 @@ impl InnerMetadata { protected, ID_KEY, COSE_DECODING_CONTEXT, - report, + context.report, ) .map(|v| v.0); @@ -208,7 +210,7 @@ impl InnerMetadata { protected, VER_KEY, COSE_DECODING_CONTEXT, - report, + context.report, ) .map(|v| v.0); @@ -246,7 +248,10 @@ impl TryFrom<&Metadata> for coset::Header { // Dummy report, use just to pass the encoder let mut report = ProblemReport::new("TryFrom Metadata to COSE Header"); builder = builder - .text_value(TYPE_KEY.to_string(), meta.doc_type()?.to_value(&mut report)?) + .text_value( + TYPE_KEY.to_string(), + meta.doc_type()?.to_value(&mut report)?, + ) .text_value( ID_KEY.to_string(), Value::try_from(CborUuidV7(meta.doc_id()?))?, diff --git a/rust/signed_doc/src/validator/rules/doc_ref.rs b/rust/signed_doc/src/validator/rules/doc_ref.rs index 3212985afa1..977893b0614 100644 --- a/rust/signed_doc/src/validator/rules/doc_ref.rs +++ b/rust/signed_doc/src/validator/rules/doc_ref.rs @@ -1,9 +1,10 @@ //! `ref` rule type impl. -use catalyst_types::{problem_report::ProblemReport}; +use catalyst_types::problem_report::ProblemReport; use crate::{ - metadata::DocType, providers::CatalystSignedDocumentProvider, validator::utils::validate_provided_doc, CatalystSignedDocument + metadata::DocType, providers::CatalystSignedDocumentProvider, + validator::utils::validate_provided_doc, CatalystSignedDocument, }; /// `ref` field validation rule @@ -24,9 +25,7 @@ impl RefRule { pub(crate) async fn check( &self, doc: &CatalystSignedDocument, provider: &Provider, ) -> anyhow::Result - where - Provider: CatalystSignedDocumentProvider, - { + where Provider: CatalystSignedDocumentProvider { if let Self::Specified { exp_ref_type, optional, @@ -61,7 +60,8 @@ impl RefRule { /// A generic implementation of the referenced document validation. pub(crate) fn referenced_doc_check( - ref_doc: &CatalystSignedDocument, exp_ref_type: &DocType, field_name: &str, report: &ProblemReport, + ref_doc: &CatalystSignedDocument, exp_ref_type: &DocType, field_name: &str, + report: &ProblemReport, ) -> bool { let Ok(ref_doc_type) = ref_doc.doc_type() else { report.missing_field("type", "Referenced document must have type field"); @@ -81,7 +81,7 @@ pub(crate) fn referenced_doc_check( #[cfg(test)] mod tests { - use catalyst_types::uuid::UuidV7; + use catalyst_types::uuid::{UuidV4, UuidV7}; use super::*; use crate::{providers::tests::TestCatalystSignedDocumentProvider, Builder}; @@ -135,7 +135,7 @@ mod tests { // all correct let rule = RefRule::Specified { - exp_ref_type, + exp_ref_type: exp_ref_type.into(), optional: false, }; let doc = Builder::new() @@ -148,7 +148,7 @@ mod tests { // all correct, `ref` field is missing, but its optional let rule = RefRule::Specified { - exp_ref_type, + exp_ref_type: exp_ref_type.into(), optional: true, }; let doc = Builder::new().build(); @@ -156,7 +156,7 @@ mod tests { // missing `ref` field, but its required let rule = RefRule::Specified { - exp_ref_type, + exp_ref_type: exp_ref_type.into(), optional: false, }; let doc = Builder::new().build(); diff --git a/rust/signed_doc/src/validator/rules/parameters.rs b/rust/signed_doc/src/validator/rules/parameters.rs index 11359fa87bf..41ad404df07 100644 --- a/rust/signed_doc/src/validator/rules/parameters.rs +++ b/rust/signed_doc/src/validator/rules/parameters.rs @@ -2,7 +2,8 @@ use super::doc_ref::referenced_doc_check; use crate::{ - metadata::DocType, providers::CatalystSignedDocumentProvider, validator::utils::validate_provided_doc, CatalystSignedDocument + metadata::DocType, providers::CatalystSignedDocumentProvider, + validator::utils::validate_provided_doc, CatalystSignedDocument, }; /// `parameters` field validation rule @@ -123,7 +124,7 @@ mod tests { // all correct let rule = ParametersRule::Specified { - exp_parameters_type, + exp_parameters_type: exp_parameters_type.into(), optional: false, }; let doc = Builder::new() @@ -136,7 +137,7 @@ mod tests { // all correct, `parameters` field is missing, but its optional let rule = ParametersRule::Specified { - exp_parameters_type, + exp_parameters_type: exp_parameters_type.into(), optional: true, }; let doc = Builder::new().build(); @@ -144,7 +145,7 @@ mod tests { // missing `parameters` field, but its required let rule = ParametersRule::Specified { - exp_parameters_type, + exp_parameters_type: exp_parameters_type.into(), optional: false, }; let doc = Builder::new().build(); diff --git a/rust/signed_doc/src/validator/rules/reply.rs b/rust/signed_doc/src/validator/rules/reply.rs index 16d656273d8..09b48ea28d9 100644 --- a/rust/signed_doc/src/validator/rules/reply.rs +++ b/rust/signed_doc/src/validator/rules/reply.rs @@ -2,7 +2,8 @@ use super::doc_ref::referenced_doc_check; use crate::{ - metadata::DocType, providers::CatalystSignedDocumentProvider, validator::utils::validate_provided_doc, CatalystSignedDocument + metadata::DocType, providers::CatalystSignedDocumentProvider, + validator::utils::validate_provided_doc, CatalystSignedDocument, }; /// `reply` field validation rule @@ -32,12 +33,7 @@ impl ReplyRule { { if let Some(reply) = doc.doc_meta().reply() { let reply_validator = |replied_doc: CatalystSignedDocument| { - if !referenced_doc_check( - &replied_doc, - exp_reply_type, - "reply", - doc.report(), - ) { + if !referenced_doc_check(&replied_doc, exp_reply_type, "reply", doc.report()) { return false; } let Some(doc_ref) = doc.doc_meta().doc_ref() else { @@ -162,7 +158,7 @@ mod tests { // all correct let rule = ReplyRule::Specified { - exp_reply_type, + exp_reply_type: exp_reply_type.into(), optional: false, }; let doc = Builder::new() @@ -176,7 +172,7 @@ mod tests { // all correct, `reply` field is missing, but its optional let rule = ReplyRule::Specified { - exp_reply_type, + exp_reply_type: exp_reply_type.into(), optional: true, }; let doc = Builder::new().build(); @@ -184,7 +180,7 @@ mod tests { // missing `reply` field, but its required let rule = ReplyRule::Specified { - exp_reply_type, + exp_reply_type: exp_reply_type.into(), optional: false, }; let doc = Builder::new() diff --git a/rust/signed_doc/src/validator/rules/template.rs b/rust/signed_doc/src/validator/rules/template.rs index e3c892498da..13fea3b5449 100644 --- a/rust/signed_doc/src/validator/rules/template.rs +++ b/rust/signed_doc/src/validator/rules/template.rs @@ -2,12 +2,12 @@ use std::fmt::Write; -use catalyst_types::uuid::UuidV4; - use super::doc_ref::referenced_doc_check; use crate::{ - metadata::{ContentType, DocType}, providers::CatalystSignedDocumentProvider, - validator::utils::validate_provided_doc, CatalystSignedDocument, + metadata::{ContentType, DocType}, + providers::CatalystSignedDocumentProvider, + validator::utils::validate_provided_doc, + CatalystSignedDocument, }; /// Enum represents different content schemas, against which documents content would be @@ -48,12 +48,8 @@ impl ContentRule { }; let template_validator = |template_doc: CatalystSignedDocument| { - if !referenced_doc_check( - &template_doc, - exp_template_type.uuid(), - "template", - doc.report(), - ) { + if !referenced_doc_check(&template_doc, exp_template_type, "template", doc.report()) + { return false; } @@ -181,7 +177,7 @@ fn content_schema_check(doc: &CatalystSignedDocument, schema: &ContentSchema) -> #[cfg(test)] mod tests { - use catalyst_types::uuid::UuidV7; + use catalyst_types::uuid::{UuidV4, UuidV7}; use super::*; use crate::{providers::tests::TestCatalystSignedDocumentProvider, Builder}; @@ -281,7 +277,9 @@ mod tests { } // all correct - let rule = ContentRule::Templated { exp_template_type }; + let rule = ContentRule::Templated { + exp_template_type: exp_template_type.into(), + }; let doc = Builder::new() .with_json_metadata(serde_json::json!({ "template": {"id": valid_template_doc_id.to_string(), "ver": valid_template_doc_id.to_string() } @@ -300,7 +298,9 @@ mod tests { assert!(!rule.check(&doc, &provider).await.unwrap()); // missing content - let rule = ContentRule::Templated { exp_template_type }; + let rule = ContentRule::Templated { + exp_template_type: exp_template_type.into(), + }; let doc = Builder::new() .with_json_metadata(serde_json::json!({ "template": {"id": valid_template_doc_id.to_string(), "ver": valid_template_doc_id.to_string() } @@ -310,7 +310,9 @@ mod tests { assert!(!rule.check(&doc, &provider).await.unwrap()); // content not a json encoded - let rule = ContentRule::Templated { exp_template_type }; + let rule = ContentRule::Templated { + exp_template_type: exp_template_type.into(), + }; let doc = Builder::new() .with_json_metadata(serde_json::json!({ "template": {"id": valid_template_doc_id.to_string(), "ver": valid_template_doc_id.to_string() } @@ -341,7 +343,9 @@ mod tests { assert!(!rule.check(&doc, &provider).await.unwrap()); // missing `content-type` field in the referenced doc - let rule = ContentRule::Templated { exp_template_type }; + let rule = ContentRule::Templated { + exp_template_type: exp_template_type.into(), + }; let doc = Builder::new() .with_json_metadata(serde_json::json!({ "template": {"id": missing_content_type_template_doc_id.to_string(), "ver": missing_content_type_template_doc_id.to_string() } diff --git a/rust/signed_doc/tests/decoding.rs b/rust/signed_doc/tests/decoding.rs index 38a6615aaff..2f50dfc828a 100644 --- a/rust/signed_doc/tests/decoding.rs +++ b/rust/signed_doc/tests/decoding.rs @@ -29,7 +29,8 @@ fn catalyst_signed_doc_cbor_roundtrip_test() { let decoded: CatalystSignedDocument = bytes.as_slice().try_into().unwrap(); let extra_fields: ExtraFields = serde_json::from_value(metadata_fields).unwrap(); - assert_eq!(decoded.doc_type().unwrap(), uuid_v4); + let doc_type: DocType = uuid_v4.into(); + assert_eq!(decoded.doc_type().unwrap(), &doc_type); assert_eq!(decoded.doc_id().unwrap(), uuid_v7); assert_eq!(decoded.doc_ver().unwrap(), uuid_v7); assert_eq!(decoded.doc_content().decoded_bytes().unwrap(), &content); From 1208dc7c932a7bed822bf12f9efd5f60c689f7f8 Mon Sep 17 00:00:00 2001 From: bkioshn Date: Tue, 27 May 2025 16:13:09 +0700 Subject: [PATCH 08/18] fix(catalyst-types): add hash to uuidv4 Signed-off-by: bkioshn --- rust/catalyst-types/src/uuid/uuid_v4.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/catalyst-types/src/uuid/uuid_v4.rs b/rust/catalyst-types/src/uuid/uuid_v4.rs index 059102a15ec..2298ecceefe 100644 --- a/rust/catalyst-types/src/uuid/uuid_v4.rs +++ b/rust/catalyst-types/src/uuid/uuid_v4.rs @@ -7,7 +7,7 @@ use uuid::Uuid; use super::{decode_cbor_uuid, encode_cbor_uuid, CborContext, UuidError, INVALID_UUID}; /// Type representing a `UUIDv4`. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, serde::Serialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, serde::Serialize)] pub struct UuidV4(Uuid); impl UuidV4 { From 718483c6cc8cb771b62ce24f9a9513d70d678677 Mon Sep 17 00:00:00 2001 From: bkioshn Date: Tue, 27 May 2025 16:39:41 +0700 Subject: [PATCH 09/18] fix(signed-doc): decoding test Signed-off-by: bkioshn --- rust/signed_doc/tests/common/mod.rs | 23 +++++++++++++++++++ rust/signed_doc/tests/decoding.rs | 35 +++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/rust/signed_doc/tests/common/mod.rs b/rust/signed_doc/tests/common/mod.rs index d7ea84150b0..ae3d348f8a6 100644 --- a/rust/signed_doc/tests/common/mod.rs +++ b/rust/signed_doc/tests/common/mod.rs @@ -27,6 +27,29 @@ pub fn test_metadata() -> (UuidV7, UuidV4, serde_json::Value) { (uuid_v7, uuid_v4, metadata_fields) } +pub fn test_metadata_specific_type( + uuid_v4: Option, uuid_v7: Option, +) -> (UuidV7, UuidV4, serde_json::Value) { + let uuid_v7 = uuid_v7.unwrap_or_else(UuidV7::new); + let uuid_v4 = uuid_v4.unwrap_or_else(UuidV4::new); + + let metadata_fields = serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + "type": uuid_v4.to_string(), + "id": uuid_v7.to_string(), + "ver": uuid_v7.to_string(), + "ref": {"id": uuid_v7.to_string(), "ver": uuid_v7.to_string()}, + "reply": {"id": uuid_v7.to_string(), "ver": uuid_v7.to_string()}, + "template": {"id": uuid_v7.to_string(), "ver": uuid_v7.to_string()}, + "section": "$".to_string(), + "collabs": vec!["Alex1".to_string(), "Alex2".to_string()], + "parameters": {"id": uuid_v7.to_string(), "ver": uuid_v7.to_string()}, + }); + + (uuid_v7, uuid_v4, metadata_fields) +} + pub fn create_dummy_key_pair( role_index: RoleId, ) -> anyhow::Result<( diff --git a/rust/signed_doc/tests/decoding.rs b/rust/signed_doc/tests/decoding.rs index 2f50dfc828a..824af693311 100644 --- a/rust/signed_doc/tests/decoding.rs +++ b/rust/signed_doc/tests/decoding.rs @@ -1,6 +1,6 @@ //! Integration test for COSE decoding part. -use catalyst_signed_doc::*; +use catalyst_signed_doc::{doc_types::PROPOSAL_DOCUMENT_UUID_TYPE, *}; use catalyst_types::catalyst_id::role_index::RoleId; use common::create_dummy_key_pair; use coset::TaggedCborSerializable; @@ -10,7 +10,16 @@ mod common; #[test] fn catalyst_signed_doc_cbor_roundtrip_test() { - let (uuid_v7, uuid_v4, metadata_fields) = common::test_metadata(); + catalyst_signed_doc_cbor_roundtrip(common::test_metadata()); + catalyst_signed_doc_cbor_roundtrip(common::test_metadata_specific_type( + Some(PROPOSAL_DOCUMENT_UUID_TYPE.try_into().unwrap()), + None, + )); +} + +#[allow(clippy::unwrap_used)] +fn catalyst_signed_doc_cbor_roundtrip(data: (UuidV7, UuidV4, serde_json::Value)) { + let (uuid_v7, uuid_v4, metadata_fields) = data; let (sk, _, kid) = create_dummy_key_pair(RoleId::Role0).unwrap(); let content = serde_json::to_vec(&serde_json::Value::Null).unwrap(); @@ -39,7 +48,16 @@ fn catalyst_signed_doc_cbor_roundtrip_test() { #[test] fn catalyst_signed_doc_cbor_roundtrip_kid_as_id_test() { - let (_, _, metadata_fields) = common::test_metadata(); + catalyst_signed_doc_cbor_roundtrip_kid_as_id(common::test_metadata()); + catalyst_signed_doc_cbor_roundtrip_kid_as_id(common::test_metadata_specific_type( + Some(PROPOSAL_DOCUMENT_UUID_TYPE.try_into().unwrap()), + None, + )); +} + +#[allow(clippy::unwrap_used)] +fn catalyst_signed_doc_cbor_roundtrip_kid_as_id(data: (UuidV7, UuidV4, serde_json::Value)) { + let (_, _, metadata_fields) = data; let (sk, _, kid) = create_dummy_key_pair(RoleId::Role0).unwrap(); // transform Catalyst ID URI form to the ID form let kid = kid.as_id(); @@ -59,7 +77,16 @@ fn catalyst_signed_doc_cbor_roundtrip_kid_as_id_test() { #[test] fn catalyst_signed_doc_parameters_aliases_test() { - let (_, _, metadata_fields) = common::test_metadata(); + catalyst_signed_doc_parameters_aliases(common::test_metadata()); + catalyst_signed_doc_parameters_aliases(common::test_metadata_specific_type( + Some(PROPOSAL_DOCUMENT_UUID_TYPE.try_into().unwrap()), + None, + )); +} + +#[allow(clippy::unwrap_used)] +fn catalyst_signed_doc_parameters_aliases(data: (UuidV7, UuidV4, serde_json::Value)) { + let (_, _, metadata_fields) = data; let content = serde_json::to_vec(&serde_json::Value::Null).unwrap(); From 92de7605f5d92fde13b6acc1157aa46615619320 Mon Sep 17 00:00:00 2001 From: bkioshn Date: Tue, 27 May 2025 16:43:00 +0700 Subject: [PATCH 10/18] fix(signed-doc): doctype Signed-off-by: bkioshn --- rust/signed_doc/src/doc_types/mod.rs | 6 +- rust/signed_doc/src/metadata/doc_type.rs | 196 +++++++++++++++++++++-- 2 files changed, 186 insertions(+), 16 deletions(-) diff --git a/rust/signed_doc/src/doc_types/mod.rs b/rust/signed_doc/src/doc_types/mod.rs index 8946b97ec87..19b8346d219 100644 --- a/rust/signed_doc/src/doc_types/mod.rs +++ b/rust/signed_doc/src/doc_types/mod.rs @@ -62,7 +62,7 @@ pub const SUBMISSION_ACTION: Uuid = Uuid::from_u128(0x7892_7329_CFD9_4EA1_9C71_0 /// Map proposal document type to new doc type list. #[allow(clippy::expect_used)] -pub(crate) static PROPOSAL_DOC_TYPE: LazyLock = LazyLock::new(|| { +pub static PROPOSAL_DOC_TYPE: LazyLock = LazyLock::new(|| { let ids = &[PROPOSAL_DOCUMENT_UUID_TYPE]; ids.to_vec() .try_into() @@ -71,7 +71,7 @@ pub(crate) static PROPOSAL_DOC_TYPE: LazyLock = LazyLock::new(|| { /// Map proposal comment document type to new doc type list. #[allow(clippy::expect_used)] -pub(crate) static PROPOSAL_COMMENT_DOC: LazyLock = LazyLock::new(|| { +pub static PROPOSAL_COMMENT_DOC: LazyLock = LazyLock::new(|| { let ids = &[COMMENT_DOCUMENT_UUID_TYPE, PROPOSAL_DOCUMENT_UUID_TYPE]; ids.to_vec() .try_into() @@ -80,7 +80,7 @@ pub(crate) static PROPOSAL_COMMENT_DOC: LazyLock = LazyLock::new(|| { /// Map proposal action document type to new doc type list. #[allow(clippy::expect_used)] -pub(crate) static PROPOSAL_ACTION_DOC: LazyLock = LazyLock::new(|| { +pub static PROPOSAL_ACTION_DOC: LazyLock = LazyLock::new(|| { let ids = &[ PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, PROPOSAL_DOCUMENT_UUID_TYPE, diff --git a/rust/signed_doc/src/metadata/doc_type.rs b/rust/signed_doc/src/metadata/doc_type.rs index a3af6cdd9a3..c9bb975c4d0 100644 --- a/rust/signed_doc/src/metadata/doc_type.rs +++ b/rust/signed_doc/src/metadata/doc_type.rs @@ -3,14 +3,16 @@ use std::{ fmt::{Display, Formatter}, hash::{Hash, Hasher}, + str::FromStr, }; use catalyst_types::{ problem_report::ProblemReport, - uuid::{CborContext, Uuid, UuidV4}, + uuid::{CborContext, Uuid, UuidV4, UUID_CBOR_TAG}, }; use coset::cbor::Value; -use minicbor::{Decode, Decoder, Encode, Encoder}; +use minicbor::{Decode, Decoder, Encode}; +use serde::{Deserialize, Deserializer}; use tracing::warn; use crate::{ @@ -22,7 +24,7 @@ use crate::{ }; /// List of `UUIDv4` document type. -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, serde::Serialize, Eq)] pub struct DocType(Vec); /// `DocType` Errors. @@ -34,6 +36,9 @@ pub enum DocTypeError { /// `DocType` cannot be empty. #[error("DocType cannot be empty")] Empty, + /// Invalid string conversion + #[error("Invalid string conversion: {0}")] + StringConversion(String), } impl DocType { @@ -44,11 +49,18 @@ impl DocType { } /// Convert `DocType` to coset `Value`. - pub(crate) fn to_value(&self, report: &mut ProblemReport) -> anyhow::Result { - let mut buffer = Vec::new(); - let mut encoder = Encoder::new(&mut buffer); - self.encode(&mut encoder, report)?; - Ok(Value::from(buffer.clone())) + pub(crate) fn to_value(&self) -> Value { + Value::Array( + self.0 + .iter() + .map(|uuidv4| { + Value::Tag( + UUID_CBOR_TAG, + Box::new(Value::Bytes(uuidv4.uuid().as_bytes().to_vec())), + ) + }) + .collect(), + ) } } @@ -106,6 +118,25 @@ impl TryFrom> for DocType { } } +impl TryFrom> for DocType { + type Error = DocTypeError; + + fn try_from(value: Vec) -> Result { + if value.is_empty() { + return Err(DocTypeError::Empty); + } + let converted = value + .into_iter() + .map(|s| { + let parsed = Uuid::from_str(&s).map_err(|_| DocTypeError::StringConversion(s))?; + UuidV4::try_from(parsed).map_err(|_| DocTypeError::InvalidUuid(parsed)) + }) + .collect::, DocTypeError>>()?; + + Ok(DocType(converted)) + } +} + impl Display for DocType { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { write!( @@ -259,10 +290,63 @@ impl Encode for DocType { } } +impl<'de> Deserialize<'de> for DocType { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> { + #[derive(Deserialize)] + #[serde(untagged)] + enum DocTypeInput { + /// Single UUID string. + Single(String), + /// UUID string in a list. + Multiple(Vec), + } + + let input = DocTypeInput::deserialize(deserializer)?; + let dt = match input { + DocTypeInput::Single(s) => { + let uuid = Uuid::parse_str(&s).map_err(|_| { + serde::de::Error::custom(DocTypeError::StringConversion(s.clone())) + })?; + // If there is a map from old (single uuid) to new use that list, else convert that + // single uuid to [uuid] - of type DocType + map_doc_type(uuid).unwrap_or(uuid.try_into().map_err(serde::de::Error::custom)?) + }, + DocTypeInput::Multiple(v) => v.try_into().map_err(serde::de::Error::custom)?, + }; + Ok(dt) + } +} + +impl PartialEq for DocType { + fn eq(&self, other: &Self) -> bool { + // List of special-case (single UUID) -> new DocType + let special_cases = [ + (PROPOSAL_DOCUMENT_UUID_TYPE, &*PROPOSAL_DOC_TYPE), + (COMMENT_DOCUMENT_UUID_TYPE, &*PROPOSAL_COMMENT_DOC), + (PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, &*PROPOSAL_ACTION_DOC), + ]; + for (uuid, expected) in special_cases { + match DocType::try_from(uuid) { + Ok(single) => { + if (self.0 == single.0 && other.0 == expected.0) + || (other.0 == single.0 && self.0 == expected.0) + { + return true; + } + }, + Err(_) => return false, + } + } + self.0 == other.0 + } +} + #[cfg(test)] mod tests { use minicbor::Encoder; + use serde_json::json; use super::*; @@ -273,7 +357,7 @@ mod tests { const PSA: &str = "D825505E60E623AD024A1BA1AC406DB978EE48"; #[test] - fn test_empty_doc_type() { + fn test_empty_doc_type_cbor_decode() { assert!(>>::try_from(vec![]).is_err()); let mut report = ProblemReport::new("Test empty doc type"); @@ -286,7 +370,7 @@ mod tests { } #[test] - fn test_single_uuid_doc_type_fail_policy() { + fn test_single_uuid_doc_type_fail_policy_cbor_decode() { let mut report = ProblemReport::new("Test single uuid doc type - fail"); let data = hex::decode(PSA).unwrap(); let decoder = Decoder::new(&data); @@ -298,7 +382,7 @@ mod tests { } #[test] - fn test_single_uuid_doc_type_warn_policy() { + fn test_single_uuid_doc_type_warn_policy_cbor_decode() { let mut report = ProblemReport::new("Test single uuid doc type - warn"); let data = hex::decode(PSA).unwrap(); let decoder = Decoder::new(&data); @@ -311,7 +395,7 @@ mod tests { } #[test] - fn test_single_uuid_doc_type_accept_policy() { + fn test_single_uuid_doc_type_accept_policy_cbor_decode() { let mut report = ProblemReport::new("Test single uuid doc type - accept"); let data = hex::decode(PSA).unwrap(); let decoder = Decoder::new(&data); @@ -324,7 +408,7 @@ mod tests { } #[test] - fn test_multi_uuid_doc_type() { + fn test_multi_uuid_doc_type_cbor_decode_encode() { let uuidv4 = UuidV4::new(); let mut report = ProblemReport::new("Test multi uuid doc type"); let doc_type_list: DocType = vec![uuidv4, uuidv4].try_into().unwrap(); @@ -339,4 +423,90 @@ mod tests { let decoded_doc_type = DocType::decode(&mut decoder, &mut decoded_context).unwrap(); assert_eq!(decoded_doc_type, doc_type_list); } + + #[test] + fn test_valid_vec_string() { + let uuid = Uuid::new_v4().to_string(); + let input = vec![uuid.clone()]; + let doc_type = DocType::try_from(input).expect("should succeed"); + + assert_eq!(doc_type.0.len(), 1); + assert_eq!(doc_type.0.first().unwrap().to_string(), uuid); + } + + #[test] + fn test_empty_vec_string_fails() { + let input: Vec = vec![]; + let result = DocType::try_from(input); + assert!(matches!(result, Err(DocTypeError::Empty))); + } + + #[test] + fn test_invalid_uuid_vec_string() { + let input = vec!["not-a-uuid".to_string()]; + let result = DocType::try_from(input); + assert!(matches!(result, Err(DocTypeError::StringConversion(s)) if s == "not-a-uuid")); + } + + #[test] + fn test_doc_type_to_value() { + let uuid = uuid::Uuid::new_v4(); + let doc_type = DocType(vec![UuidV4::try_from(uuid).unwrap()]); + + for d in &doc_type.to_value().into_array().unwrap() { + let t = d.clone().into_tag().unwrap(); + assert_eq!(t.0, UUID_CBOR_TAG); + assert_eq!(t.1.as_bytes().unwrap().len(), 16); + } + } + + #[test] + fn test_doctype_equal_special_cases() { + // Direct equal + let uuid = PROPOSAL_DOCUMENT_UUID_TYPE; + let dt1 = DocType::try_from(vec![uuid]).unwrap(); + let dt2 = DocType::try_from(vec![uuid]).unwrap(); + assert_eq!(dt1, dt2); + + // single -> special mapped type + let single = DocType::try_from(PROPOSAL_DOCUMENT_UUID_TYPE).unwrap(); + assert_eq!(single, *PROPOSAL_DOC_TYPE); + let single = DocType::try_from(COMMENT_DOCUMENT_UUID_TYPE).unwrap(); + assert_eq!(single, *PROPOSAL_COMMENT_DOC); + let single = DocType::try_from(PROPOSAL_ACTION_DOCUMENT_UUID_TYPE).unwrap(); + assert_eq!(single, *PROPOSAL_ACTION_DOC); + } + + #[test] + fn test_deserialize_single_uuid_normal() { + let uuid = uuid::Uuid::new_v4().to_string(); + let json = json!(uuid); + let dt: DocType = serde_json::from_value(json).unwrap(); + + assert_eq!(dt.0.len(), 1); + assert_eq!(dt.0.first().unwrap().to_string(), uuid); + } + + #[test] + fn test_deserialize_multiple_uuids() { + let uuid1 = uuid::Uuid::new_v4().to_string(); + let uuid2 = uuid::Uuid::new_v4().to_string(); + let json = json!([uuid1.clone(), uuid2.clone()]); + + let dt: DocType = serde_json::from_value(json).unwrap(); + let actual = + dt.0.iter() + .map(std::string::ToString::to_string) + .collect::>(); + assert_eq!(actual, vec![uuid1, uuid2]); + } + + #[test] + fn test_deserialize_special_case() { + let uuid = PROPOSAL_DOCUMENT_UUID_TYPE.to_string(); + let json = json!(uuid); + let dt: DocType = serde_json::from_value(json).unwrap(); + + assert_eq!(dt, *PROPOSAL_DOC_TYPE); + } } From 9ab6fe5a25b9a9946ea69046a0dc7fe1ee9c2641 Mon Sep 17 00:00:00 2001 From: bkioshn Date: Tue, 27 May 2025 16:43:12 +0700 Subject: [PATCH 11/18] fix(signed-doc): minor fixes Signed-off-by: bkioshn --- rust/signed_doc/src/lib.rs | 2 +- rust/signed_doc/src/metadata/mod.rs | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/rust/signed_doc/src/lib.rs b/rust/signed_doc/src/lib.rs index 0e9a3a3fb6d..3d69b02205a 100644 --- a/rust/signed_doc/src/lib.rs +++ b/rust/signed_doc/src/lib.rs @@ -234,7 +234,7 @@ impl Decode<'_, ()> for CatalystSignedDocument { let signatures = Signatures::from_cose_sig_list(&cose_sign.signatures, &report); let content = if let Some(payload) = cose_sign.payload { - Content::from_encoded(payload, metadata.content_encoding(), &mut report) + Content::from_encoded(payload, metadata.content_encoding(), &report) } else { report.missing_field("COSE Sign Payload", "Missing document content (payload)"); Content::default() diff --git a/rust/signed_doc/src/metadata/mod.rs b/rust/signed_doc/src/metadata/mod.rs index 04b31929e32..703b71eacc1 100644 --- a/rust/signed_doc/src/metadata/mod.rs +++ b/rust/signed_doc/src/metadata/mod.rs @@ -245,13 +245,8 @@ impl TryFrom<&Metadata> for coset::Header { ); } - // Dummy report, use just to pass the encoder - let mut report = ProblemReport::new("TryFrom Metadata to COSE Header"); builder = builder - .text_value( - TYPE_KEY.to_string(), - meta.doc_type()?.to_value(&mut report)?, - ) + .text_value(TYPE_KEY.to_string(), meta.doc_type()?.to_value()) .text_value( ID_KEY.to_string(), Value::try_from(CborUuidV7(meta.doc_id()?))?, From 993bcbb07ea9b64f6e5926c3e68376009559b10b Mon Sep 17 00:00:00 2001 From: bkioshn Date: Tue, 27 May 2025 19:15:31 +0700 Subject: [PATCH 12/18] chore(sign-doc): fix comment Signed-off-by: bkioshn --- rust/signed_doc/src/doc_types/mod.rs | 1 + rust/signed_doc/src/metadata/doc_type.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/signed_doc/src/doc_types/mod.rs b/rust/signed_doc/src/doc_types/mod.rs index 19b8346d219..1ed200d4e51 100644 --- a/rust/signed_doc/src/doc_types/mod.rs +++ b/rust/signed_doc/src/doc_types/mod.rs @@ -59,6 +59,7 @@ pub const IMMUTABLE_LEDGER_BLOCK_UUID_TYPE: Uuid = pub const SUBMISSION_ACTION: Uuid = Uuid::from_u128(0x7892_7329_CFD9_4EA1_9C71_0E01_9B12_6A65); // -------- Mapping old document types to new document types -------- +// /// Map proposal document type to new doc type list. #[allow(clippy::expect_used)] diff --git a/rust/signed_doc/src/metadata/doc_type.rs b/rust/signed_doc/src/metadata/doc_type.rs index c9bb975c4d0..57d1833641a 100644 --- a/rust/signed_doc/src/metadata/doc_type.rs +++ b/rust/signed_doc/src/metadata/doc_type.rs @@ -298,7 +298,7 @@ impl<'de> Deserialize<'de> for DocType { enum DocTypeInput { /// Single UUID string. Single(String), - /// UUID string in a list. + /// List of UUID string. Multiple(Vec), } @@ -321,6 +321,7 @@ impl<'de> Deserialize<'de> for DocType { impl PartialEq for DocType { fn eq(&self, other: &Self) -> bool { // List of special-case (single UUID) -> new DocType + // The old one should equal to the new one let special_cases = [ (PROPOSAL_DOCUMENT_UUID_TYPE, &*PROPOSAL_DOC_TYPE), (COMMENT_DOCUMENT_UUID_TYPE, &*PROPOSAL_COMMENT_DOC), From 93ba580ac6b93946e58874244c83f0e19d1d7fa7 Mon Sep 17 00:00:00 2001 From: bkioshn Date: Thu, 29 May 2025 10:44:30 +0700 Subject: [PATCH 13/18] fix(catalyst-types): add fromstr to uuidv4 and 7 Signed-off-by: bkioshn --- rust/catalyst-types/src/uuid/mod.rs | 3 +++ rust/catalyst-types/src/uuid/uuid_v4.rs | 14 +++++++++++++- rust/catalyst-types/src/uuid/uuid_v7.rs | 14 +++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/rust/catalyst-types/src/uuid/mod.rs b/rust/catalyst-types/src/uuid/mod.rs index 8baebeaeff5..c2df1c4795c 100644 --- a/rust/catalyst-types/src/uuid/mod.rs +++ b/rust/catalyst-types/src/uuid/mod.rs @@ -28,6 +28,9 @@ pub enum UuidError { /// `UUIDv7` invalid error #[error("'{0}' is not a valid UUIDv7")] InvalidUuidV7(uuid::Uuid), + /// Invalid string conversion + #[error("Invalid string conversion: {0}")] + StringConversion(String), } /// Context for `CBOR` encoding and decoding diff --git a/rust/catalyst-types/src/uuid/uuid_v4.rs b/rust/catalyst-types/src/uuid/uuid_v4.rs index 2298ecceefe..a7baf462484 100644 --- a/rust/catalyst-types/src/uuid/uuid_v4.rs +++ b/rust/catalyst-types/src/uuid/uuid_v4.rs @@ -1,5 +1,8 @@ //! `UUIDv4` Type. -use std::fmt::{Display, Formatter}; +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; use minicbor::{Decode, Decoder, Encode}; use uuid::Uuid; @@ -106,6 +109,15 @@ impl<'de> serde::Deserialize<'de> for UuidV4 { } } +impl FromStr for UuidV4 { + type Err = UuidError; + + fn from_str(s: &str) -> Result { + let uuid = Uuid::parse_str(s).map_err(|_| UuidError::StringConversion(s.to_string()))?; + UuidV4::try_from(uuid).map_err(|_| UuidError::InvalidUuidV4(uuid)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust/catalyst-types/src/uuid/uuid_v7.rs b/rust/catalyst-types/src/uuid/uuid_v7.rs index 98fbd8cda6e..1bb95e6ff90 100644 --- a/rust/catalyst-types/src/uuid/uuid_v7.rs +++ b/rust/catalyst-types/src/uuid/uuid_v7.rs @@ -1,5 +1,8 @@ //! `UUIDv7` Type. -use std::fmt::{Display, Formatter}; +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; use minicbor::{Decode, Decoder, Encode}; use uuid::Uuid; @@ -106,6 +109,15 @@ impl<'de> serde::Deserialize<'de> for UuidV7 { } } +impl FromStr for UuidV7 { + type Err = UuidError; + + fn from_str(s: &str) -> Result { + let uuid = Uuid::parse_str(s).map_err(|_| UuidError::StringConversion(s.to_string()))?; + UuidV7::try_from(uuid).map_err(|_| UuidError::InvalidUuidV7(uuid)) + } +} + #[cfg(test)] mod tests { use uuid::Uuid; From 8117da647ed4bd8a30472d93c1aef34dcabdeb62 Mon Sep 17 00:00:00 2001 From: bkioshn Date: Thu, 29 May 2025 10:46:34 +0700 Subject: [PATCH 14/18] fix(signed-doc): restructure doctypes Signed-off-by: bkioshn --- rust/signed_doc/src/doc_types/mod.rs | 139 +++++++++++++---------- rust/signed_doc/src/metadata/doc_type.rs | 34 +++--- rust/signed_doc/src/validator/mod.rs | 14 ++- rust/signed_doc/tests/comment.rs | 21 ++-- rust/signed_doc/tests/decoding.rs | 8 +- rust/signed_doc/tests/proposal.rs | 8 +- rust/signed_doc/tests/submission.rs | 16 +-- 7 files changed, 127 insertions(+), 113 deletions(-) diff --git a/rust/signed_doc/src/doc_types/mod.rs b/rust/signed_doc/src/doc_types/mod.rs index 1ed200d4e51..2a2c22735e4 100644 --- a/rust/signed_doc/src/doc_types/mod.rs +++ b/rust/signed_doc/src/doc_types/mod.rs @@ -4,90 +4,105 @@ use std::sync::LazyLock; use catalyst_types::uuid::Uuid; +use deprecated::{ + COMMENT_DOCUMENT_UUID_TYPE, PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, PROPOSAL_DOCUMENT_UUID_TYPE, +}; use crate::DocType; -/// Proposal document `UuidV4` type. -pub const PROPOSAL_DOCUMENT_UUID_TYPE: Uuid = - Uuid::from_u128(0x7808_D2BA_D511_40AF_84E8_C0D1_625F_DFDC); -/// Proposal template `UuidV4` type. -pub const PROPOSAL_TEMPLATE_UUID_TYPE: Uuid = - Uuid::from_u128(0x0CE8_AB38_9258_4FBC_A62E_7FAA_6E58_318F); -/// Comment document `UuidV4` type. -pub const COMMENT_DOCUMENT_UUID_TYPE: Uuid = - Uuid::from_u128(0xB679_DED3_0E7C_41BA_89F8_DA62_A178_98EA); -/// Comment template `UuidV4` type. -pub const COMMENT_TEMPLATE_UUID_TYPE: Uuid = - Uuid::from_u128(0x0B84_24D4_EBFD_46E3_9577_1775_A69D_290C); -/// Review document `UuidV4` type. -pub const REVIEW_DOCUMENT_UUID_TYPE: Uuid = - Uuid::from_u128(0xE4CA_F5F0_098B_45FD_94F3_0702_A457_3DB5); -/// Review template `UuidV4` type. -pub const REVIEW_TEMPLATE_UUID_TYPE: Uuid = - Uuid::from_u128(0xEBE5_D0BF_5D86_4577_AF4D_008F_DDBE_2EDC); -/// Category document `UuidV4` type. -pub const CATEGORY_DOCUMENT_UUID_TYPE: Uuid = - Uuid::from_u128(0x48C2_0109_362A_4D32_9BBA_E0A9_CF8B_45BE); -/// Category template `UuidV4` type. -pub const CATEGORY_TEMPLATE_UUID_TYPE: Uuid = - Uuid::from_u128(0x65B1_E8B0_51F1_46A5_9970_72CD_F268_84BE); -/// Campaign parameters document `UuidV4` type. -pub const CAMPAIGN_DOCUMENT_UUID_TYPE: Uuid = - Uuid::from_u128(0x0110_EA96_A555_47CE_8408_36EF_E6ED_6F7C); -/// Campaign parameters template `UuidV4` type. -pub const CAMPAIGN_TEMPLATE_UUID_TYPE: Uuid = - Uuid::from_u128(0x7E8F_5FA2_44CE_49C8_BFD5_02AF_42C1_79A3); -/// Brand parameters document `UuidV4` type. -pub const BRAND_DOCUMENT_UUID_TYPE: Uuid = - Uuid::from_u128(0x3E48_08CC_C86E_467B_9702_D60B_AA9D_1FCA); -/// Brand parameters template `UuidV4` type. -pub const BRAND_TEMPLATE_UUID_TYPE: Uuid = - Uuid::from_u128(0xFD3C_1735_80B1_4EEA_8D63_5F43_6D97_EA31); -/// Proposal action document `UuidV4` type. -pub const PROPOSAL_ACTION_DOCUMENT_UUID_TYPE: Uuid = - Uuid::from_u128(0x5E60_E623_AD02_4A1B_A1AC_406D_B978_EE48); -/// Public vote transaction v2 `UuidV4` type. -pub const PUBLIC_VOTE_TX_V2_UUID_TYPE: Uuid = - Uuid::from_u128(0x8DE5_586C_E998_4B95_8742_7BE3_C859_2803); -/// Private vote transaction v2 `UuidV4` type. -pub const PRIVATE_VOTE_TX_V2_UUID_TYPE: Uuid = - Uuid::from_u128(0xE78E_E18D_F380_44C1_A852_80AA_6ECB_07FE); -/// Immutable ledger block `UuidV4` type. -pub const IMMUTABLE_LEDGER_BLOCK_UUID_TYPE: Uuid = - Uuid::from_u128(0xD9E7_E6CE_2401_4D7D_9492_F4F7_C642_41C3); -/// Submission Action `UuidV4` type. -pub const SUBMISSION_ACTION: Uuid = Uuid::from_u128(0x7892_7329_CFD9_4EA1_9C71_0E01_9B12_6A65); - -// -------- Mapping old document types to new document types -------- -// - -/// Map proposal document type to new doc type list. +/// Proposal document type. #[allow(clippy::expect_used)] pub static PROPOSAL_DOC_TYPE: LazyLock = LazyLock::new(|| { - let ids = &[PROPOSAL_DOCUMENT_UUID_TYPE]; + let ids = &[PROPOSAL_UUID_TYPE]; ids.to_vec() .try_into() .expect("Failed to convert proposal document Uuid to DocType") }); -/// Map proposal comment document type to new doc type list. +/// Proposal comment document type. #[allow(clippy::expect_used)] pub static PROPOSAL_COMMENT_DOC: LazyLock = LazyLock::new(|| { - let ids = &[COMMENT_DOCUMENT_UUID_TYPE, PROPOSAL_DOCUMENT_UUID_TYPE]; + let ids = &[COMMENT_UUID_TYPE, PROPOSAL_UUID_TYPE]; ids.to_vec() .try_into() .expect("Failed to convert proposal comment document Uuid to DocType") }); -/// Map proposal action document type to new doc type list. +/// Proposal action document type. #[allow(clippy::expect_used)] pub static PROPOSAL_ACTION_DOC: LazyLock = LazyLock::new(|| { let ids = &[ - PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, - PROPOSAL_DOCUMENT_UUID_TYPE, - SUBMISSION_ACTION, + ACTION_UUID_TYPE, + PROPOSAL_UUID_TYPE, + SUBMISSION_ACTION_UUID_TYPE, ]; ids.to_vec() .try_into() .expect("Failed to convert proposal action document Uuid to DocType") }); + +/// Submission Action UUID type. +pub const SUBMISSION_ACTION_UUID_TYPE: Uuid = + Uuid::from_u128(0x7892_7329_CFD9_4EA1_9C71_0E01_9B12_6A65); +/// Proposal UUID type. +pub const PROPOSAL_UUID_TYPE: Uuid = PROPOSAL_DOCUMENT_UUID_TYPE; +/// Comment UUID type. +pub const COMMENT_UUID_TYPE: Uuid = COMMENT_DOCUMENT_UUID_TYPE; +/// Action UUID type. +pub const ACTION_UUID_TYPE: Uuid = PROPOSAL_ACTION_DOCUMENT_UUID_TYPE; + +/// Document type which will be deprecated. +pub mod deprecated { + use catalyst_types::uuid::Uuid; + + /// Proposal document `UuidV4` type. + pub const PROPOSAL_DOCUMENT_UUID_TYPE: Uuid = + Uuid::from_u128(0x7808_D2BA_D511_40AF_84E8_C0D1_625F_DFDC); + /// Proposal template `UuidV4` type. + pub const PROPOSAL_TEMPLATE_UUID_TYPE: Uuid = + Uuid::from_u128(0x0CE8_AB38_9258_4FBC_A62E_7FAA_6E58_318F); + /// Comment document `UuidV4` type. + pub const COMMENT_DOCUMENT_UUID_TYPE: Uuid = + Uuid::from_u128(0xB679_DED3_0E7C_41BA_89F8_DA62_A178_98EA); + /// Comment template `UuidV4` type. + pub const COMMENT_TEMPLATE_UUID_TYPE: Uuid = + Uuid::from_u128(0x0B84_24D4_EBFD_46E3_9577_1775_A69D_290C); + /// Review document `UuidV4` type. + pub const REVIEW_DOCUMENT_UUID_TYPE: Uuid = + Uuid::from_u128(0xE4CA_F5F0_098B_45FD_94F3_0702_A457_3DB5); + /// Review template `UuidV4` type. + pub const REVIEW_TEMPLATE_UUID_TYPE: Uuid = + Uuid::from_u128(0xEBE5_D0BF_5D86_4577_AF4D_008F_DDBE_2EDC); + /// Category document `UuidV4` type. + pub const CATEGORY_DOCUMENT_UUID_TYPE: Uuid = + Uuid::from_u128(0x48C2_0109_362A_4D32_9BBA_E0A9_CF8B_45BE); + /// Category template `UuidV4` type. + pub const CATEGORY_TEMPLATE_UUID_TYPE: Uuid = + Uuid::from_u128(0x65B1_E8B0_51F1_46A5_9970_72CD_F268_84BE); + /// Campaign parameters document `UuidV4` type. + pub const CAMPAIGN_DOCUMENT_UUID_TYPE: Uuid = + Uuid::from_u128(0x0110_EA96_A555_47CE_8408_36EF_E6ED_6F7C); + /// Campaign parameters template `UuidV4` type. + pub const CAMPAIGN_TEMPLATE_UUID_TYPE: Uuid = + Uuid::from_u128(0x7E8F_5FA2_44CE_49C8_BFD5_02AF_42C1_79A3); + /// Brand parameters document `UuidV4` type. + pub const BRAND_DOCUMENT_UUID_TYPE: Uuid = + Uuid::from_u128(0x3E48_08CC_C86E_467B_9702_D60B_AA9D_1FCA); + /// Brand parameters template `UuidV4` type. + pub const BRAND_TEMPLATE_UUID_TYPE: Uuid = + Uuid::from_u128(0xFD3C_1735_80B1_4EEA_8D63_5F43_6D97_EA31); + /// Proposal action document `UuidV4` type. + pub const PROPOSAL_ACTION_DOCUMENT_UUID_TYPE: Uuid = + Uuid::from_u128(0x5E60_E623_AD02_4A1B_A1AC_406D_B978_EE48); + /// Public vote transaction v2 `UuidV4` type. + pub const PUBLIC_VOTE_TX_V2_UUID_TYPE: Uuid = + Uuid::from_u128(0x8DE5_586C_E998_4B95_8742_7BE3_C859_2803); + /// Private vote transaction v2 `UuidV4` type. + pub const PRIVATE_VOTE_TX_V2_UUID_TYPE: Uuid = + Uuid::from_u128(0xE78E_E18D_F380_44C1_A852_80AA_6ECB_07FE); + /// Immutable ledger block `UuidV4` type. + pub const IMMUTABLE_LEDGER_BLOCK_UUID_TYPE: Uuid = + Uuid::from_u128(0xD9E7_E6CE_2401_4D7D_9492_F4F7_C642_41C3); + /// Submission Action `UuidV4` type. + pub const SUBMISSION_ACTION: Uuid = Uuid::from_u128(0x7892_7329_CFD9_4EA1_9C71_0E01_9B12_6A65); +} diff --git a/rust/signed_doc/src/metadata/doc_type.rs b/rust/signed_doc/src/metadata/doc_type.rs index 57d1833641a..a7c2ca205bb 100644 --- a/rust/signed_doc/src/metadata/doc_type.rs +++ b/rust/signed_doc/src/metadata/doc_type.rs @@ -3,7 +3,6 @@ use std::{ fmt::{Display, Formatter}, hash::{Hash, Hasher}, - str::FromStr, }; use catalyst_types::{ @@ -18,8 +17,8 @@ use tracing::warn; use crate::{ decode_context::{CompatibilityPolicy, DecodeContext}, doc_types::{ - COMMENT_DOCUMENT_UUID_TYPE, PROPOSAL_ACTION_DOC, PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, - PROPOSAL_COMMENT_DOC, PROPOSAL_DOCUMENT_UUID_TYPE, PROPOSAL_DOC_TYPE, + ACTION_UUID_TYPE, COMMENT_UUID_TYPE, PROPOSAL_ACTION_DOC, PROPOSAL_COMMENT_DOC, + PROPOSAL_DOC_TYPE, PROPOSAL_UUID_TYPE, }, }; @@ -128,10 +127,10 @@ impl TryFrom> for DocType { let converted = value .into_iter() .map(|s| { - let parsed = Uuid::from_str(&s).map_err(|_| DocTypeError::StringConversion(s))?; - UuidV4::try_from(parsed).map_err(|_| DocTypeError::InvalidUuid(parsed)) + s.parse::() + .map_err(|_| DocTypeError::StringConversion(s)) }) - .collect::, DocTypeError>>()?; + .collect::, _>>()?; Ok(DocType(converted)) } @@ -256,9 +255,9 @@ impl Decode<'_, DecodeContext<'_>> for DocType { /// fn map_doc_type(uuid: Uuid) -> anyhow::Result { match uuid { - id if id == PROPOSAL_DOCUMENT_UUID_TYPE => Ok(PROPOSAL_DOC_TYPE.clone()), - id if id == COMMENT_DOCUMENT_UUID_TYPE => Ok(PROPOSAL_COMMENT_DOC.clone()), - id if id == PROPOSAL_ACTION_DOCUMENT_UUID_TYPE => Ok(PROPOSAL_ACTION_DOC.clone()), + id if id == PROPOSAL_UUID_TYPE => Ok(PROPOSAL_DOC_TYPE.clone()), + id if id == COMMENT_UUID_TYPE => Ok(PROPOSAL_COMMENT_DOC.clone()), + id if id == ACTION_UUID_TYPE => Ok(PROPOSAL_ACTION_DOC.clone()), _ => anyhow::bail!("Unknown document type: {uuid}"), } } @@ -318,14 +317,15 @@ impl<'de> Deserialize<'de> for DocType { } } +// This is needed to preserve backward compatibility with the old solution. impl PartialEq for DocType { fn eq(&self, other: &Self) -> bool { // List of special-case (single UUID) -> new DocType // The old one should equal to the new one let special_cases = [ - (PROPOSAL_DOCUMENT_UUID_TYPE, &*PROPOSAL_DOC_TYPE), - (COMMENT_DOCUMENT_UUID_TYPE, &*PROPOSAL_COMMENT_DOC), - (PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, &*PROPOSAL_ACTION_DOC), + (PROPOSAL_UUID_TYPE, &*PROPOSAL_DOC_TYPE), + (COMMENT_UUID_TYPE, &*PROPOSAL_COMMENT_DOC), + (ACTION_UUID_TYPE, &*PROPOSAL_ACTION_DOC), ]; for (uuid, expected) in special_cases { match DocType::try_from(uuid) { @@ -464,17 +464,17 @@ mod tests { #[test] fn test_doctype_equal_special_cases() { // Direct equal - let uuid = PROPOSAL_DOCUMENT_UUID_TYPE; + let uuid = PROPOSAL_UUID_TYPE; let dt1 = DocType::try_from(vec![uuid]).unwrap(); let dt2 = DocType::try_from(vec![uuid]).unwrap(); assert_eq!(dt1, dt2); // single -> special mapped type - let single = DocType::try_from(PROPOSAL_DOCUMENT_UUID_TYPE).unwrap(); + let single = DocType::try_from(PROPOSAL_UUID_TYPE).unwrap(); assert_eq!(single, *PROPOSAL_DOC_TYPE); - let single = DocType::try_from(COMMENT_DOCUMENT_UUID_TYPE).unwrap(); + let single = DocType::try_from(COMMENT_UUID_TYPE).unwrap(); assert_eq!(single, *PROPOSAL_COMMENT_DOC); - let single = DocType::try_from(PROPOSAL_ACTION_DOCUMENT_UUID_TYPE).unwrap(); + let single = DocType::try_from(ACTION_UUID_TYPE).unwrap(); assert_eq!(single, *PROPOSAL_ACTION_DOC); } @@ -504,7 +504,7 @@ mod tests { #[test] fn test_deserialize_special_case() { - let uuid = PROPOSAL_DOCUMENT_UUID_TYPE.to_string(); + let uuid = PROPOSAL_UUID_TYPE.to_string(); let json = json!(uuid); let dt: DocType = serde_json::from_value(json).unwrap(); diff --git a/rust/signed_doc/src/validator/mod.rs b/rust/signed_doc/src/validator/mod.rs index e6df5e25c1d..da1b4604d2a 100644 --- a/rust/signed_doc/src/validator/mod.rs +++ b/rust/signed_doc/src/validator/mod.rs @@ -22,9 +22,11 @@ use rules::{ use crate::{ doc_types::{ - CATEGORY_DOCUMENT_UUID_TYPE, COMMENT_DOCUMENT_UUID_TYPE, COMMENT_TEMPLATE_UUID_TYPE, - PROPOSAL_ACTION_DOC, PROPOSAL_COMMENT_DOC, PROPOSAL_DOCUMENT_UUID_TYPE, PROPOSAL_DOC_TYPE, - PROPOSAL_TEMPLATE_UUID_TYPE, + deprecated::{ + CATEGORY_DOCUMENT_UUID_TYPE, COMMENT_TEMPLATE_UUID_TYPE, PROPOSAL_TEMPLATE_UUID_TYPE, + }, + COMMENT_UUID_TYPE, PROPOSAL_ACTION_DOC, PROPOSAL_COMMENT_DOC, PROPOSAL_DOC_TYPE, + PROPOSAL_UUID_TYPE, }, metadata::DocType, providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider}, @@ -88,11 +90,11 @@ fn document_rules_init() -> HashMap { exp_template_type: expect_doc_type(COMMENT_TEMPLATE_UUID_TYPE), }, doc_ref: RefRule::Specified { - exp_ref_type: expect_doc_type(PROPOSAL_DOCUMENT_UUID_TYPE), + exp_ref_type: expect_doc_type(PROPOSAL_UUID_TYPE), optional: false, }, reply: ReplyRule::Specified { - exp_reply_type: expect_doc_type(COMMENT_DOCUMENT_UUID_TYPE), + exp_reply_type: expect_doc_type(COMMENT_UUID_TYPE), optional: true, }, section: SectionRule::Specified { optional: true }, @@ -126,7 +128,7 @@ fn document_rules_init() -> HashMap { optional: true, }, doc_ref: RefRule::Specified { - exp_ref_type: expect_doc_type(PROPOSAL_DOCUMENT_UUID_TYPE), + exp_ref_type: expect_doc_type(PROPOSAL_UUID_TYPE), optional: false, }, reply: ReplyRule::NotSpecified, diff --git a/rust/signed_doc/tests/comment.rs b/rust/signed_doc/tests/comment.rs index 1c746e589c7..e41849ccc34 100644 --- a/rust/signed_doc/tests/comment.rs +++ b/rust/signed_doc/tests/comment.rs @@ -8,16 +8,16 @@ mod common; #[tokio::test] async fn test_valid_comment_doc() { let (proposal_doc, proposal_doc_id, proposal_doc_ver) = - common::create_dummy_doc(doc_types::PROPOSAL_DOCUMENT_UUID_TYPE).unwrap(); + common::create_dummy_doc(doc_types::PROPOSAL_UUID_TYPE).unwrap(); let (template_doc, template_doc_id, template_doc_ver) = - common::create_dummy_doc(doc_types::COMMENT_TEMPLATE_UUID_TYPE).unwrap(); + common::create_dummy_doc(doc_types::deprecated::COMMENT_TEMPLATE_UUID_TYPE).unwrap(); let uuid_v7 = UuidV7::new(); let (doc, ..) = common::create_dummy_signed_doc( serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::COMMENT_DOCUMENT_UUID_TYPE, + "type": doc_types::COMMENT_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "template": { @@ -48,9 +48,9 @@ async fn test_valid_comment_doc_with_reply() { let empty_json = serde_json::to_vec(&serde_json::json!({})).unwrap(); let (proposal_doc, proposal_doc_id, proposal_doc_ver) = - common::create_dummy_doc(doc_types::PROPOSAL_DOCUMENT_UUID_TYPE).unwrap(); + common::create_dummy_doc(doc_types::PROPOSAL_UUID_TYPE).unwrap(); let (template_doc, template_doc_id, template_doc_ver) = - common::create_dummy_doc(doc_types::COMMENT_TEMPLATE_UUID_TYPE).unwrap(); + common::create_dummy_doc(doc_types::deprecated::COMMENT_TEMPLATE_UUID_TYPE).unwrap(); let comment_doc_id = UuidV7::new(); let comment_doc_ver = UuidV7::new(); @@ -58,7 +58,7 @@ async fn test_valid_comment_doc_with_reply() { .with_json_metadata(serde_json::json!({ "id": comment_doc_id, "ver": comment_doc_ver, - "type": doc_types::COMMENT_DOCUMENT_UUID_TYPE, + "type": doc_types::COMMENT_UUID_TYPE, "content-type": ContentType::Json.to_string(), "template": { "id": template_doc_id.to_string(), "ver": template_doc_ver.to_string() }, "ref": { @@ -75,7 +75,7 @@ async fn test_valid_comment_doc_with_reply() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::COMMENT_DOCUMENT_UUID_TYPE, + "type": doc_types::COMMENT_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "template": { @@ -108,17 +108,16 @@ async fn test_valid_comment_doc_with_reply() { #[tokio::test] async fn test_invalid_comment_doc() { - let (proposal_doc, ..) = - common::create_dummy_doc(doc_types::PROPOSAL_DOCUMENT_UUID_TYPE).unwrap(); + let (proposal_doc, ..) = common::create_dummy_doc(doc_types::PROPOSAL_UUID_TYPE).unwrap(); let (template_doc, template_doc_id, template_doc_ver) = - common::create_dummy_doc(doc_types::COMMENT_TEMPLATE_UUID_TYPE).unwrap(); + common::create_dummy_doc(doc_types::deprecated::COMMENT_TEMPLATE_UUID_TYPE).unwrap(); let uuid_v7 = UuidV7::new(); let (doc, ..) = common::create_dummy_signed_doc( serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::COMMENT_DOCUMENT_UUID_TYPE, + "type": doc_types::COMMENT_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "template": { diff --git a/rust/signed_doc/tests/decoding.rs b/rust/signed_doc/tests/decoding.rs index 6518bd23d15..795183e6990 100644 --- a/rust/signed_doc/tests/decoding.rs +++ b/rust/signed_doc/tests/decoding.rs @@ -1,8 +1,6 @@ //! Integration test for COSE decoding part. -use catalyst_signed_doc::{ - doc_types::PROPOSAL_DOCUMENT_UUID_TYPE, providers::tests::TestVerifyingKeyProvider, *, -}; +use catalyst_signed_doc::{providers::tests::TestVerifyingKeyProvider, *}; use catalyst_types::catalyst_id::role_index::RoleId; use common::create_dummy_key_pair; use coset::TaggedCborSerializable; @@ -14,7 +12,7 @@ mod common; fn catalyst_signed_doc_cbor_roundtrip_kid_as_id_test() { catalyst_signed_doc_cbor_roundtrip_kid_as_id(common::test_metadata()); catalyst_signed_doc_cbor_roundtrip_kid_as_id(common::test_metadata_specific_type( - Some(PROPOSAL_DOCUMENT_UUID_TYPE.try_into().unwrap()), + Some(doc_types::PROPOSAL_UUID_TYPE.try_into().unwrap()), None, )); } @@ -43,7 +41,7 @@ fn catalyst_signed_doc_cbor_roundtrip_kid_as_id(data: (UuidV7, UuidV4, serde_jso async fn catalyst_signed_doc_parameters_aliases_test() { catalyst_signed_doc_parameters_aliases(common::test_metadata()).await; catalyst_signed_doc_parameters_aliases(common::test_metadata_specific_type( - Some(PROPOSAL_DOCUMENT_UUID_TYPE.try_into().unwrap()), + Some(doc_types::PROPOSAL_UUID_TYPE.try_into().unwrap()), None, )) .await; diff --git a/rust/signed_doc/tests/proposal.rs b/rust/signed_doc/tests/proposal.rs index 50ce1799e4a..ea256442a5e 100644 --- a/rust/signed_doc/tests/proposal.rs +++ b/rust/signed_doc/tests/proposal.rs @@ -8,14 +8,14 @@ mod common; #[tokio::test] async fn test_valid_proposal_doc() { let (template_doc, template_doc_id, template_doc_ver) = - common::create_dummy_doc(doc_types::PROPOSAL_TEMPLATE_UUID_TYPE).unwrap(); + common::create_dummy_doc(doc_types::deprecated::PROPOSAL_TEMPLATE_UUID_TYPE).unwrap(); let uuid_v7 = UuidV7::new(); let (doc, ..) = common::create_dummy_signed_doc( serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_DOCUMENT_UUID_TYPE, + "type": doc_types::PROPOSAL_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "template": { @@ -47,7 +47,7 @@ async fn test_valid_proposal_doc_with_empty_provider() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_DOCUMENT_UUID_TYPE, + "type": doc_types::PROPOSAL_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "template": { @@ -74,7 +74,7 @@ async fn test_invalid_proposal_doc() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_DOCUMENT_UUID_TYPE, + "type": doc_types::PROPOSAL_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), // without specifying template id diff --git a/rust/signed_doc/tests/submission.rs b/rust/signed_doc/tests/submission.rs index d10c6c3952f..5b31ea9e187 100644 --- a/rust/signed_doc/tests/submission.rs +++ b/rust/signed_doc/tests/submission.rs @@ -8,14 +8,14 @@ mod common; #[tokio::test] async fn test_valid_submission_action() { let (proposal_doc, proposal_doc_id, proposal_doc_ver) = - common::create_dummy_doc(doc_types::PROPOSAL_DOCUMENT_UUID_TYPE).unwrap(); + common::create_dummy_doc(doc_types::PROPOSAL_UUID_TYPE).unwrap(); let uuid_v7 = UuidV7::new(); let (doc, ..) = common::create_dummy_signed_doc( serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, + "type": doc_types::ACTION_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "ref": { @@ -47,7 +47,7 @@ async fn test_valid_submission_action_with_empty_provider() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, + "type": doc_types::ACTION_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "ref": { @@ -78,7 +78,7 @@ async fn test_invalid_submission_action() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, + "type": doc_types::ACTION_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), // without specifying ref @@ -98,13 +98,13 @@ async fn test_invalid_submission_action() { // corrupted JSON let (proposal_doc, proposal_doc_id, proposal_doc_ver) = - common::create_dummy_doc(doc_types::PROPOSAL_DOCUMENT_UUID_TYPE).unwrap(); + common::create_dummy_doc(doc_types::PROPOSAL_UUID_TYPE).unwrap(); let uuid_v7 = UuidV7::new(); let (doc, ..) = common::create_dummy_signed_doc( serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, + "type": doc_types::ACTION_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "ref": { @@ -124,13 +124,13 @@ async fn test_invalid_submission_action() { // empty content let (proposal_doc, proposal_doc_id, proposal_doc_ver) = - common::create_dummy_doc(doc_types::PROPOSAL_DOCUMENT_UUID_TYPE).unwrap(); + common::create_dummy_doc(doc_types::PROPOSAL_UUID_TYPE).unwrap(); let uuid_v7 = UuidV7::new(); let (doc, ..) = common::create_dummy_signed_doc( serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, + "type": doc_types::ACTION_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "ref": { From ca38e3806ad29193212fab3e04971519d14de5bb Mon Sep 17 00:00:00 2001 From: bkioshn Date: Thu, 29 May 2025 11:00:30 +0700 Subject: [PATCH 15/18] fix(signed-doc): test Signed-off-by: bkioshn --- rust/signed_doc/tests/comment.rs | 45 +++++++++++++++++++++++++++-- rust/signed_doc/tests/proposal.rs | 36 +++++++++++++++++++++-- rust/signed_doc/tests/submission.rs | 39 +++++++++++++++++++++++-- 3 files changed, 112 insertions(+), 8 deletions(-) diff --git a/rust/signed_doc/tests/comment.rs b/rust/signed_doc/tests/comment.rs index e41849ccc34..effacf3fc21 100644 --- a/rust/signed_doc/tests/comment.rs +++ b/rust/signed_doc/tests/comment.rs @@ -17,6 +17,45 @@ async fn test_valid_comment_doc() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_COMMENT_DOC.clone(), + "id": uuid_v7.to_string(), + "ver": uuid_v7.to_string(), + "template": { + "id": template_doc_id, + "ver": template_doc_ver + }, + "ref": { + "id": proposal_doc_id, + "ver": proposal_doc_ver + } + }), + serde_json::to_vec(&serde_json::Value::Null).unwrap(), + RoleId::Role0, + ) + .unwrap(); + + let mut provider = TestCatalystSignedDocumentProvider::default(); + provider.add_document(template_doc).unwrap(); + provider.add_document(proposal_doc).unwrap(); + + let is_valid = validator::validate(&doc, &provider).await.unwrap(); + + assert!(is_valid); +} + +#[tokio::test] +async fn test_valid_comment_doc_old_type() { + let (proposal_doc, proposal_doc_id, proposal_doc_ver) = + common::create_dummy_doc(doc_types::PROPOSAL_UUID_TYPE).unwrap(); + let (template_doc, template_doc_id, template_doc_ver) = + common::create_dummy_doc(doc_types::deprecated::COMMENT_TEMPLATE_UUID_TYPE).unwrap(); + + let uuid_v7 = UuidV7::new(); + let (doc, ..) = common::create_dummy_signed_doc( + serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + // Using old (single uuid) "type": doc_types::COMMENT_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), @@ -58,7 +97,7 @@ async fn test_valid_comment_doc_with_reply() { .with_json_metadata(serde_json::json!({ "id": comment_doc_id, "ver": comment_doc_ver, - "type": doc_types::COMMENT_UUID_TYPE, + "type": doc_types::PROPOSAL_COMMENT_DOC.clone(), "content-type": ContentType::Json.to_string(), "template": { "id": template_doc_id.to_string(), "ver": template_doc_ver.to_string() }, "ref": { @@ -75,7 +114,7 @@ async fn test_valid_comment_doc_with_reply() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::COMMENT_UUID_TYPE, + "type": doc_types::PROPOSAL_COMMENT_DOC.clone(), "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "template": { @@ -117,7 +156,7 @@ async fn test_invalid_comment_doc() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::COMMENT_UUID_TYPE, + "type": doc_types::PROPOSAL_COMMENT_DOC.clone(), "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "template": { diff --git a/rust/signed_doc/tests/proposal.rs b/rust/signed_doc/tests/proposal.rs index ea256442a5e..4980e334735 100644 --- a/rust/signed_doc/tests/proposal.rs +++ b/rust/signed_doc/tests/proposal.rs @@ -15,6 +15,38 @@ async fn test_valid_proposal_doc() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_DOC_TYPE.clone(), + "id": uuid_v7.to_string(), + "ver": uuid_v7.to_string(), + "template": { + "id": template_doc_id, + "ver": template_doc_ver + }, + }), + serde_json::to_vec(&serde_json::Value::Null).unwrap(), + RoleId::Proposer, + ) + .unwrap(); + + let mut provider = TestCatalystSignedDocumentProvider::default(); + provider.add_document(template_doc).unwrap(); + + let is_valid = validator::validate(&doc, &provider).await.unwrap(); + + assert!(is_valid); +} + +#[tokio::test] +async fn test_valid_proposal_doc_old_type() { + let (template_doc, template_doc_id, template_doc_ver) = + common::create_dummy_doc(doc_types::deprecated::PROPOSAL_TEMPLATE_UUID_TYPE).unwrap(); + + let uuid_v7 = UuidV7::new(); + let (doc, ..) = common::create_dummy_signed_doc( + serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + // Using old (single uuid) "type": doc_types::PROPOSAL_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), @@ -47,7 +79,7 @@ async fn test_valid_proposal_doc_with_empty_provider() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_UUID_TYPE, + "type": doc_types::PROPOSAL_DOC_TYPE.clone(), "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "template": { @@ -74,7 +106,7 @@ async fn test_invalid_proposal_doc() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::PROPOSAL_UUID_TYPE, + "type": doc_types::PROPOSAL_DOC_TYPE.clone(), "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), // without specifying template id diff --git a/rust/signed_doc/tests/submission.rs b/rust/signed_doc/tests/submission.rs index 5b31ea9e187..2e404a9082f 100644 --- a/rust/signed_doc/tests/submission.rs +++ b/rust/signed_doc/tests/submission.rs @@ -15,6 +15,39 @@ async fn test_valid_submission_action() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), + "type": doc_types::PROPOSAL_ACTION_DOC.clone(), + "id": uuid_v7.to_string(), + "ver": uuid_v7.to_string(), + "ref": { + "id": proposal_doc_id, + "ver": proposal_doc_ver + }, + }), + serde_json::to_vec(&serde_json::json!({ + "action": "final" + })) + .unwrap(), + RoleId::Proposer, + ) + .unwrap(); + + let mut provider = TestCatalystSignedDocumentProvider::default(); + provider.add_document(proposal_doc).unwrap(); + let is_valid = validator::validate(&doc, &provider).await.unwrap(); + assert!(is_valid, "{:?}", doc.problem_report()); +} + +#[tokio::test] +async fn test_valid_submission_action_old_type() { + let (proposal_doc, proposal_doc_id, proposal_doc_ver) = + common::create_dummy_doc(doc_types::PROPOSAL_UUID_TYPE).unwrap(); + + let uuid_v7 = UuidV7::new(); + let (doc, ..) = common::create_dummy_signed_doc( + serde_json::json!({ + "content-type": ContentType::Json.to_string(), + "content-encoding": ContentEncoding::Brotli.to_string(), + // Using old (single uuid) "type": doc_types::ACTION_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), @@ -47,7 +80,7 @@ async fn test_valid_submission_action_with_empty_provider() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::ACTION_UUID_TYPE, + "type": doc_types::PROPOSAL_ACTION_DOC.clone(), "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "ref": { @@ -78,7 +111,7 @@ async fn test_invalid_submission_action() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::ACTION_UUID_TYPE, + "type": doc_types::PROPOSAL_ACTION_DOC.clone(), "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), // without specifying ref @@ -130,7 +163,7 @@ async fn test_invalid_submission_action() { serde_json::json!({ "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), - "type": doc_types::ACTION_UUID_TYPE, + "type": doc_types::PROPOSAL_ACTION_DOC.clone(), "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "ref": { From bce6a2947db2908ee889d0b3d3f6369bde1eabe1 Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Thu, 29 May 2025 13:38:55 +0900 Subject: [PATCH 16/18] Update rust/signed_doc/tests/proposal.rs --- rust/signed_doc/tests/proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/signed_doc/tests/proposal.rs b/rust/signed_doc/tests/proposal.rs index 4980e334735..7e6f4f21d7c 100644 --- a/rust/signed_doc/tests/proposal.rs +++ b/rust/signed_doc/tests/proposal.rs @@ -47,7 +47,7 @@ async fn test_valid_proposal_doc_old_type() { "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), // Using old (single uuid) - "type": doc_types::PROPOSAL_UUID_TYPE, + "type": doc_types::deprecated::PROPOSAL_DOCUMENT_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "template": { From e309ec3065ce66766da4144614db1583b31877f6 Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Thu, 29 May 2025 13:39:04 +0900 Subject: [PATCH 17/18] Update rust/signed_doc/tests/comment.rs --- rust/signed_doc/tests/comment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/signed_doc/tests/comment.rs b/rust/signed_doc/tests/comment.rs index effacf3fc21..5725e3080c7 100644 --- a/rust/signed_doc/tests/comment.rs +++ b/rust/signed_doc/tests/comment.rs @@ -56,7 +56,7 @@ async fn test_valid_comment_doc_old_type() { "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), // Using old (single uuid) - "type": doc_types::COMMENT_UUID_TYPE, + "type": doc_types::deprecated::COMMENT_DOCUMENT_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "template": { From 0480884f1f0e2f21687fb34d7fd8af44566fc073 Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Thu, 29 May 2025 13:39:13 +0900 Subject: [PATCH 18/18] Update rust/signed_doc/tests/submission.rs --- rust/signed_doc/tests/submission.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/signed_doc/tests/submission.rs b/rust/signed_doc/tests/submission.rs index 2e404a9082f..dc2ea5d56a4 100644 --- a/rust/signed_doc/tests/submission.rs +++ b/rust/signed_doc/tests/submission.rs @@ -48,7 +48,7 @@ async fn test_valid_submission_action_old_type() { "content-type": ContentType::Json.to_string(), "content-encoding": ContentEncoding::Brotli.to_string(), // Using old (single uuid) - "type": doc_types::ACTION_UUID_TYPE, + "type": doc_types::deprecated::PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, "id": uuid_v7.to_string(), "ver": uuid_v7.to_string(), "ref": {