diff --git a/rust/catalyst-signed-doc-macro/src/rules/doc_ref.rs b/rust/catalyst-signed-doc-macro/src/rules/doc_ref.rs new file mode 100644 index 0000000000..8d460e0af6 --- /dev/null +++ b/rust/catalyst-signed-doc-macro/src/rules/doc_ref.rs @@ -0,0 +1,38 @@ +//! `RefRule` generation + +use proc_macro2::TokenStream; +use quote::quote; + +use crate::signed_doc_spec::{self, IsRequired}; + +/// Generating `RefRule` instantiation +pub(crate) fn ref_rule(ref_spec: &signed_doc_spec::doc_ref::Ref) -> anyhow::Result { + let optional = match ref_spec.required { + IsRequired::Yes => false, + IsRequired::Optional => true, + IsRequired::Excluded => { + return Ok(quote! { + crate::validator::rules::RefRule::NotSpecified + }); + }, + }; + + anyhow::ensure!(!ref_spec.doc_type.is_empty(), "'type' field should exists and has at least one entry for the required 'ref' metadata definition"); + + let const_type_name_idents = ref_spec.doc_type.iter().map(|doc_name| { + let const_type_name_ident = doc_name.ident(); + quote! { + crate::doc_types::#const_type_name_ident + } + }); + let multiple = ref_spec.multiple.ok_or(anyhow::anyhow!( + "'multiple' field should exists for the required 'ref' metadata definition" + ))?; + Ok(quote! { + crate::validator::rules::RefRule::Specified { + exp_ref_types: vec![ #(#const_type_name_idents,)* ], + multiple: #multiple, + optional: #optional, + } + }) +} diff --git a/rust/catalyst-signed-doc-macro/src/rules/mod.rs b/rust/catalyst-signed-doc-macro/src/rules/mod.rs index a8f2d619a0..a8fa819ade 100644 --- a/rust/catalyst-signed-doc-macro/src/rules/mod.rs +++ b/rust/catalyst-signed-doc-macro/src/rules/mod.rs @@ -1,5 +1,7 @@ //! `catalyst_signed_documents_rules!` macro implementation +pub(crate) mod doc_ref; + use proc_macro2::TokenStream; use quote::quote; @@ -10,9 +12,10 @@ pub(crate) fn catalyst_signed_documents_rules_impl() -> anyhow::Result anyhow::Result, +} diff --git a/rust/catalyst-signed-doc-macro/src/signed_doc_spec.rs b/rust/catalyst-signed-doc-macro/src/signed_doc_spec/mod.rs similarity index 51% rename from rust/catalyst-signed-doc-macro/src/signed_doc_spec.rs rename to rust/catalyst-signed-doc-macro/src/signed_doc_spec/mod.rs index 48eadb18a1..a2cba1d84b 100644 --- a/rust/catalyst-signed-doc-macro/src/signed_doc_spec.rs +++ b/rust/catalyst-signed-doc-macro/src/signed_doc_spec/mod.rs @@ -1,8 +1,9 @@ //! Catalyst Signed Document spec type -use std::collections::HashMap; +pub(crate) mod doc_ref; + +use std::{collections::HashMap, ops::Deref}; -use anyhow::Context; use proc_macro2::Ident; use quote::format_ident; @@ -43,15 +44,66 @@ pub(crate) struct DocSpec { /// Document type UUID v4 value #[serde(rename = "type")] pub(crate) doc_type: String, + + /// Document type metadata definitions + pub(crate) metadata: Metadata, +} + +/// Document's metadata fields definition +#[derive(serde::Deserialize)] +#[allow(clippy::missing_docs_in_private_items)] +pub(crate) struct Metadata { + #[serde(rename = "ref")] + pub(crate) doc_ref: doc_ref::Ref, +} + +/// "required" field definition +#[derive(serde::Deserialize)] +#[serde(rename_all = "lowercase")] +#[allow(clippy::missing_docs_in_private_items)] +pub(crate) enum IsRequired { + Yes, + Excluded, + Optional, +} + +/// A helper type for deserialization "type" metadata field +pub(crate) struct DocTypes(Vec); + +impl Deref for DocTypes { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'de> serde::Deserialize<'de> for DocTypes { + #[allow(clippy::missing_docs_in_private_items)] + fn deserialize(deserializer: D) -> Result + where D: serde::Deserializer<'de> { + #[derive(serde::Deserialize)] + #[serde(untagged)] + enum SingleOrVec { + Single(DocumentName), + Multiple(Vec), + } + let value = Option::::deserialize(deserializer)?; + let result = match value { + Some(SingleOrVec::Single(item)) => vec![item], + Some(SingleOrVec::Multiple(items)) => items, + None => vec![], + }; + Ok(Self(result)) + } } impl CatalystSignedDocSpec { /// Loading a Catalyst Signed Documents spec from the `signed_doc.json` // #[allow(dependency_on_unit_never_type_fallback)] pub(crate) fn load_signed_doc_spec() -> anyhow::Result { - let signed_doc_str = include_str!("../../../specs/signed_doc.json"); - let signed_doc_spec = serde_json::from_str(signed_doc_str) - .context("Catalyst Signed Documents spec must be a JSON object")?; + let signed_doc_str = include_str!("../../../../specs/signed_doc.json"); + let signed_doc_spec = serde_json::from_str(signed_doc_str)?; Ok(signed_doc_spec) } }