diff --git a/rust/catalyst-signed-doc-macro/src/types_consts.rs b/rust/catalyst-signed-doc-macro/src/types_consts.rs index 77219db71e..7a0bfff4f5 100644 --- a/rust/catalyst-signed-doc-macro/src/types_consts.rs +++ b/rust/catalyst-signed-doc-macro/src/types_consts.rs @@ -10,6 +10,9 @@ pub(crate) fn catalyst_signed_documents_types_consts_impl() -> anyhow::Result, +pub(crate) struct Copyright { + pub(crate) versions: Vec, } #[derive(serde::Deserialize)] -pub struct Version { - pub version: String, +pub(crate) struct Version { + pub(crate) version: String, } diff --git a/rust/catalyst-signed-doc-spec/src/lib.rs b/rust/catalyst-signed-doc-spec/src/lib.rs index 6e0ca1b7ae..2615a65166 100644 --- a/rust/catalyst-signed-doc-spec/src/lib.rs +++ b/rust/catalyst-signed-doc-spec/src/lib.rs @@ -7,12 +7,13 @@ pub mod doc_types; pub mod headers; pub mod is_required; pub mod metadata; +pub mod payload; use std::{collections::HashMap, fmt::Display}; use build_info as build_info_lib; -use crate::{copyright::Copyright, headers::Headers, metadata::Metadata}; +use crate::{copyright::Copyright, headers::Headers, metadata::Metadata, payload::Payload}; build_info_lib::build_info!(pub(crate) fn build_info); @@ -20,7 +21,7 @@ build_info_lib::build_info!(pub(crate) fn build_info); #[derive(serde::Deserialize)] pub struct CatalystSignedDocSpec { pub docs: HashMap, - pub copyright: Copyright, + copyright: Copyright, } // A thin wrapper over the string document name values @@ -60,10 +61,12 @@ impl DocumentName { /// Specific document type definition #[derive(serde::Deserialize)] pub struct DocSpec { + pub draft: bool, #[serde(rename = "type")] pub doc_type: String, pub headers: Headers, pub metadata: Metadata, + pub payload: Payload, } impl CatalystSignedDocSpec { diff --git a/rust/catalyst-signed-doc-spec/src/metadata/mod.rs b/rust/catalyst-signed-doc-spec/src/metadata/mod.rs index f68a6c54c1..b4368ed59d 100644 --- a/rust/catalyst-signed-doc-spec/src/metadata/mod.rs +++ b/rust/catalyst-signed-doc-spec/src/metadata/mod.rs @@ -1,11 +1,13 @@ //! `metadata` field definition pub mod doc_ref; +pub mod template; /// Document's metadata fields definition #[derive(serde::Deserialize)] #[allow(clippy::missing_docs_in_private_items)] pub struct Metadata { + pub template: template::Template, #[serde(rename = "ref")] pub doc_ref: doc_ref::Ref, } diff --git a/rust/catalyst-signed-doc-spec/src/metadata/template.rs b/rust/catalyst-signed-doc-spec/src/metadata/template.rs new file mode 100644 index 0000000000..8f97bcdc4f --- /dev/null +++ b/rust/catalyst-signed-doc-spec/src/metadata/template.rs @@ -0,0 +1,13 @@ +//! `signed_doc.json` "template" field JSON definition + +use crate::{is_required::IsRequired, DocumentName}; + +/// `signed_doc.json` "template" field JSON object +#[derive(serde::Deserialize)] +#[allow(clippy::missing_docs_in_private_items, dead_code)] +pub struct Template { + pub required: IsRequired, + #[serde(rename = "type")] + pub doc_type: Option, + pub multiple: Option, +} diff --git a/rust/catalyst-signed-doc-spec/src/payload.rs b/rust/catalyst-signed-doc-spec/src/payload.rs new file mode 100644 index 0000000000..bcf18e6341 --- /dev/null +++ b/rust/catalyst-signed-doc-spec/src/payload.rs @@ -0,0 +1,9 @@ +//! `signed_doc.json` "payload" field JSON definition + +/// `signed_doc.json` "payload" field JSON object +#[derive(serde::Deserialize)] +#[allow(clippy::missing_docs_in_private_items)] +pub struct Payload { + pub nil: bool, + pub schema: Option, +} diff --git a/rust/signed_doc/src/validator/rules/content.rs b/rust/signed_doc/src/validator/rules/content.rs index b81011deae..12b9972208 100644 --- a/rust/signed_doc/src/validator/rules/content.rs +++ b/rust/signed_doc/src/validator/rules/content.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; +use catalyst_signed_doc_spec::payload::Payload; use minicbor::Encode; use crate::{ @@ -39,6 +40,26 @@ pub(crate) enum ContentRule { } impl ContentRule { + /// Generating `ContentRule` from specs + pub(crate) fn new(spec: &Payload) -> anyhow::Result { + if spec.nil { + anyhow::ensure!( + spec.schema.is_none(), + "'schema' field could not been specified when 'nil' is 'true' for 'payload' definition" + ); + return Ok(Self::Nil); + } + + if let Some(schema) = &spec.schema { + let schema_str = schema.to_string(); + Ok(Self::StaticSchema(ContentSchema::Json( + json_schema::JsonSchema::try_from(&serde_json::from_str(&schema_str)?)?, + ))) + } else { + Ok(Self::NotNil) + } + } + /// Field validation rule #[allow(clippy::unused_async)] pub(crate) async fn check( diff --git a/rust/signed_doc/src/validator/rules/mod.rs b/rust/signed_doc/src/validator/rules/mod.rs index 2dac7ae1ef..c8b29f75c0 100644 --- a/rust/signed_doc/src/validator/rules/mod.rs +++ b/rust/signed_doc/src/validator/rules/mod.rs @@ -118,17 +118,21 @@ impl Rules { let mut doc_rules = Vec::new(); for doc_spec in spec.docs.values() { + if doc_spec.draft { + continue; + } + let rules = Self { id: IdRule, ver: VerRule, content_type: ContentTypeRule::new(&doc_spec.headers.content_type)?, content_encoding: ContentEncodingRule::new(&doc_spec.headers.content_encoding)?, - template: TemplateRule::NotSpecified, + template: TemplateRule::new(&spec.docs, &doc_spec.metadata.template)?, parameters: ParametersRule::NotSpecified, doc_ref: RefRule::new(&spec.docs, &doc_spec.metadata.doc_ref)?, reply: ReplyRule::NotSpecified, section: SectionRule::NotSpecified, - content: ContentRule::Nil, + content: ContentRule::new(&doc_spec.payload)?, kid: SignatureKidRule { exp: &[] }, signature: SignatureRule { mutlisig: false }, original_author: OriginalAuthorRule, diff --git a/rust/signed_doc/src/validator/rules/template.rs b/rust/signed_doc/src/validator/rules/template.rs index 5ff70a21a1..f1611fcd7c 100644 --- a/rust/signed_doc/src/validator/rules/template.rs +++ b/rust/signed_doc/src/validator/rules/template.rs @@ -1,5 +1,11 @@ //! `template` rule type impl. +use std::collections::HashMap; + +use catalyst_signed_doc_spec::{ + is_required::IsRequired, metadata::template::Template, DocSpec, DocumentName, +}; + use crate::{ providers::CatalystSignedDocumentProvider, validator::{ @@ -22,6 +28,39 @@ pub(crate) enum TemplateRule { } impl TemplateRule { + /// Generating `TemplateRule` from specs + pub(crate) fn new( + docs: &HashMap, + spec: &Template, + ) -> anyhow::Result { + if let IsRequired::Excluded = spec.required { + anyhow::ensure!( + spec.doc_type.is_none() && spec.multiple.is_none(), + "'type' and 'multiple' fields could not been specified when 'required' is 'excluded' for 'template' metadata definition" + ); + return Ok(Self::NotSpecified); + } + + anyhow::ensure!( + spec.multiple.is_some_and(|v| !v), + "'multiple' must be `false` for 'template' metadata definition" + ); + anyhow::ensure!( + spec.required != IsRequired::Optional, + "'required' field cannot been 'optional' for 'template' metadata definition" + ); + + let doc_name = spec.doc_type.as_ref().ok_or(anyhow::anyhow!( + "'type' field should exists for the required 'template' metadata definition" + ))?; + let docs_spec = docs.get(doc_name).ok_or(anyhow::anyhow!( + "cannot find a document definition {doc_name}" + ))?; + let allowed_type = docs_spec.doc_type.as_str().parse()?; + + Ok(Self::Specified { allowed_type }) + } + /// Field validation rule pub(crate) async fn check( &self,