From d8d7a48621ae57b3bcc825a3c3a6c9e76cf0f3e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20Rosales?= Date: Tue, 2 Sep 2025 09:46:09 -0600 Subject: [PATCH] feat(rust/signed-doc): add 'NotSpecified' variant to Content Encoding validation rule --- .../src/rules/mod.rs | 2 +- rust/signed_doc/src/validator/mod.rs | 6 +- .../src/validator/rules/content_encoding.rs | 115 ++++++++++++------ 3 files changed, 83 insertions(+), 40 deletions(-) diff --git a/rust/catalyst-signed-doc-macro/src/rules/mod.rs b/rust/catalyst-signed-doc-macro/src/rules/mod.rs index 40ca2353c8..a8f2d619a0 100644 --- a/rust/catalyst-signed-doc-macro/src/rules/mod.rs +++ b/rust/catalyst-signed-doc-macro/src/rules/mod.rs @@ -21,7 +21,7 @@ pub(crate) fn catalyst_signed_documents_rules_impl() -> anyhow::Result Rules { content_type: ContentTypeRule { exp: ContentType::Json, }, - content_encoding: ContentEncodingRule { + content_encoding: ContentEncodingRule::Specified { exp: ContentEncoding::Brotli, optional: false, }, @@ -82,7 +82,7 @@ fn proposal_comment_rule() -> Rules { content_type: ContentTypeRule { exp: ContentType::Json, }, - content_encoding: ContentEncodingRule { + content_encoding: ContentEncodingRule::Specified { exp: ContentEncoding::Brotli, optional: false, }, @@ -138,7 +138,7 @@ fn proposal_submission_action_rule() -> Rules { content_type: ContentTypeRule { exp: ContentType::Json, }, - content_encoding: ContentEncodingRule { + content_encoding: ContentEncodingRule::Specified { exp: ContentEncoding::Brotli, optional: false, }, diff --git a/rust/signed_doc/src/validator/rules/content_encoding.rs b/rust/signed_doc/src/validator/rules/content_encoding.rs index 9ebfb15432..657f24e3a9 100644 --- a/rust/signed_doc/src/validator/rules/content_encoding.rs +++ b/rust/signed_doc/src/validator/rules/content_encoding.rs @@ -2,13 +2,19 @@ use crate::{metadata::ContentEncoding, CatalystSignedDocument}; -/// `content-encoding` field validation rule +/// `content-encoding` field validation rule. #[derive(Debug)] -pub(crate) struct ContentEncodingRule { - /// expected `content-encoding` field - pub(crate) exp: ContentEncoding, - /// optional flag for the `content-encoding` field - pub(crate) optional: bool, +pub(crate) enum ContentEncodingRule { + /// Content Encoding field is optionally present in the document. + Specified { + /// expected `content-encoding` field. + exp: ContentEncoding, + /// optional flag for the `content-encoding` field. + optional: bool, + }, + /// Content Encoding field must not be present in the document. + #[allow(dead_code)] + NotSpecified, } impl ContentEncodingRule { @@ -18,33 +24,50 @@ impl ContentEncodingRule { &self, doc: &CatalystSignedDocument, ) -> anyhow::Result { - if let Some(content_encoding) = doc.doc_content_encoding() { - if content_encoding != self.exp { - doc.report().invalid_value( - "content-encoding", - content_encoding.to_string().as_str(), - self.exp.to_string().as_str(), - "Invalid Document content-encoding value", - ); - return Ok(false); - } - if content_encoding.decode(doc.encoded_content()).is_err() { - doc.report().invalid_value( - "payload", - &hex::encode(doc.encoded_content()), - &format!( - "Document content (payload) must decodable by the set content encoding type: {content_encoding}" - ), - "Invalid Document content value", - ); - return Ok(false); - } - } else if !self.optional { - doc.report().missing_field( - "content-encoding", - "Document must have a content-encoding field", - ); - return Ok(false); + let context = "Content Encoding Rule check"; + match self { + Self::NotSpecified => { + if let Some(content_encoding) = doc.doc_content_encoding() { + doc.report().unknown_field( + "content-encoding", + &content_encoding.to_string(), + &format!( + "{context}, document does not expect to have a content-encoding field" + ), + ); + return Ok(false); + } + }, + Self::Specified { exp, optional } => { + if let Some(content_encoding) = doc.doc_content_encoding() { + if content_encoding != *exp { + doc.report().invalid_value( + "content-encoding", + content_encoding.to_string().as_str(), + exp.to_string().as_str(), + "Invalid Document content-encoding value", + ); + return Ok(false); + } + if content_encoding.decode(doc.encoded_content()).is_err() { + doc.report().invalid_value( + "payload", + &hex::encode(doc.encoded_content()), + &format!( + "Document content (payload) must decodable by the set content encoding type: {content_encoding}" + ), + "Invalid Document content value", + ); + return Ok(false); + } + } else if !optional { + doc.report().missing_field( + "content-encoding", + "Document must have a content-encoding field", + ); + return Ok(false); + } + }, } Ok(true) } @@ -56,10 +79,10 @@ mod tests { use crate::{builder::tests::Builder, metadata::SupportedField}; #[tokio::test] - async fn content_encoding_rule_test() { + async fn content_encoding_is_specified_rule_test() { let content_encoding = ContentEncoding::Brotli; - let mut rule = ContentEncodingRule { + let rule = ContentEncodingRule::Specified { exp: content_encoding, optional: true, }; @@ -79,7 +102,27 @@ mod tests { let doc = Builder::new().build(); assert!(rule.check(&doc).await.unwrap()); - rule.optional = false; + let rule = ContentEncodingRule::Specified { + exp: content_encoding, + optional: false, + }; assert!(!rule.check(&doc).await.unwrap()); } + + #[tokio::test] + async fn content_encoding_is_not_specified_rule_test() { + let content_encoding = ContentEncoding::Brotli; + + let rule = ContentEncodingRule::NotSpecified; + + // With Brotli content encoding + let doc = Builder::new() + .with_metadata_field(SupportedField::ContentEncoding(content_encoding)) + .build(); + assert!(!rule.check(&doc).await.unwrap()); + + // No content encoding + let doc = Builder::new().build(); + assert!(rule.check(&doc).await.unwrap()); + } }