diff --git a/rust/Cargo.toml b/rust/Cargo.toml index df963adefc..0b84127235 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -15,6 +15,7 @@ members = [ "vote-tx-v1", "vote-tx-v2", "signed_doc", + "catalyst-signed-doc-macro", "rbac-registration", ] diff --git a/rust/Earthfile b/rust/Earthfile index 3f14f17454..7b9e3f9c5b 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -17,6 +17,7 @@ COPY_SRC: cbork cbork-abnf-parser cbork-cddl-parser cbork-utils \ hermes-ipfs \ signed_doc \ + catalyst-signed-doc-macro \ rbac-registration \ immutable-ledger . @@ -63,7 +64,7 @@ build: --args1="--libs=c509-certificate --libs=cardano-blockchain-types --libs=cardano-chain-follower --libs=hermes-ipfs" \ --args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser --libs=cbork-utils --libs=catalyst-types" \ --args3="--libs=catalyst-voting --libs=immutable-ledger --libs=vote-tx-v1 --libs=vote-tx-v2" \ - --args4="--bins=cbork/cbork --libs=rbac-registration --libs=catalyst-signed-doc" \ + --args4="--bins=cbork/cbork --libs=rbac-registration --libs=catalyst-signed-doc --libs=catalyst-signed-doc-macro" \ --args5="--cov_report=$HOME/build/coverage-report.info" \ --output="release/[^\./]+" \ --junit="cat-libs.junit-report.xml" \ diff --git a/rust/catalyst-signed-doc-macro/Cargo.toml b/rust/catalyst-signed-doc-macro/Cargo.toml new file mode 100644 index 0000000000..f4e583bb02 --- /dev/null +++ b/rust/catalyst-signed-doc-macro/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "catalyst-signed-doc-macro" +version = "0.0.1" +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[lints] +workspace = true + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0", features = ["full"] } +quote = "1.0" +proc-macro2 = "1.0" +serde_json = "1.0.142" +anyhow = "1.0.99" diff --git a/rust/catalyst-signed-doc-macro/src/error.rs b/rust/catalyst-signed-doc-macro/src/error.rs new file mode 100644 index 0000000000..930649cfba --- /dev/null +++ b/rust/catalyst-signed-doc-macro/src/error.rs @@ -0,0 +1,14 @@ +//! Error definition + +use proc_macro2::TokenStream; +use quote::quote; + +/// Processes the provided `anyhow::Error` and parses it into the `TokenStream` +pub(crate) fn process_error(err: anyhow::Error) -> TokenStream { + let err_str = err.to_string(); + if let Ok(err) = err.downcast::() { + err.into_compile_error() + } else { + quote!(#err_str) + } +} diff --git a/rust/catalyst-signed-doc-macro/src/lib.rs b/rust/catalyst-signed-doc-macro/src/lib.rs new file mode 100644 index 0000000000..9527fe8b94 --- /dev/null +++ b/rust/catalyst-signed-doc-macro/src/lib.rs @@ -0,0 +1,72 @@ +//! Catalyst Signed Documents code generation macro's from the defined `signed_doc.json` +//! spec. + +mod error; + +use anyhow::Context; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::error::process_error; + +/// Defines consts for all Catalyst Signed Documents types values +/// e.g. +/// ```ignore +/// pub const PROPOSAL: DocType = DocType::try_from_uuid(catalyst_types::uuid::uuid!( +/// "7808d2ba-d511-40af-84e8-c0d1625fdfdc" +/// )); +/// ``` +#[proc_macro] +pub fn catalyst_signed_documents_types_consts( + _: proc_macro::TokenStream +) -> proc_macro::TokenStream { + catalyst_signed_documents_types_consts_impl() + .unwrap_or_else(process_error) + .into() +} + +/// `catalyst_signed_documents_types_consts` macro implementation +fn catalyst_signed_documents_types_consts_impl() -> anyhow::Result { + let signed_doc_spec = load_signed_doc_spec()?; + + let docs = signed_doc_spec["docs"] + .as_object() + .ok_or(anyhow::anyhow!("`docs` field must be a JSON object"))?; + + let mut consts_definitions = Vec::new(); + for (doc_name, doc_spec) in docs { + let const_type_name = doc_name + .split_whitespace() + .map(|word| word.to_uppercase()) + .collect::>() + .join("_"); + let const_type_name_ident = format_ident!("{const_type_name}",); + let type_uuid = doc_spec["type"] + .as_str() + .ok_or(anyhow::anyhow!("`type` field must be a string literal"))?; + + let const_definition = quote! { + /// Catalyst Signed Document type constant definition. + pub const #const_type_name_ident: crate::DocType = match crate::DocType::try_from_uuid(catalyst_types::uuid::uuid!(#type_uuid)) { + Ok(v) => v, + Err(_) => panic!("invalid uuid v4 value"), + }; + }; + consts_definitions.push(const_definition); + } + + Ok(quote! { + #(#consts_definitions)* + }) +} + +/// Loading a Catalyst Signed Documents spec from the `signed_doc.json` as a JSON object +fn load_signed_doc_spec() -> anyhow::Result> { + let signed_doc_str = include_str!("../../../specs/signed_doc.json"); + let signed_doc_spec: serde_json::Value = serde_json::from_str(signed_doc_str) + .context("Catalyst Signed Documents spec must be a JSON object")?; + match signed_doc_spec { + serde_json::Value::Object(obj) => Ok(obj), + _ => anyhow::bail!("Catalyst Signed Documents spec must be a JSON object"), + } +} diff --git a/rust/signed_doc/Cargo.toml b/rust/signed_doc/Cargo.toml index 9c6cc54d9e..f52635f3a0 100644 --- a/rust/signed_doc/Cargo.toml +++ b/rust/signed_doc/Cargo.toml @@ -14,6 +14,8 @@ workspace = true catalyst-types = { version = "0.0.6", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "catalyst-types/v0.0.6" } cbork-utils = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "cbork-utils-v0.0.2" } +catalyst-signed-doc-macro = { path = "../catalyst-signed-doc-macro" } + anyhow = "1.0.95" serde = { version = "1.0.217", features = ["derive"] } serde_json = { version = "1.0.134", features = ["raw_value"] } diff --git a/rust/signed_doc/src/doc_types/mod.rs b/rust/signed_doc/src/doc_types/mod.rs index 902f322294..60da5f6728 100644 --- a/rust/signed_doc/src/doc_types/mod.rs +++ b/rust/signed_doc/src/doc_types/mod.rs @@ -1,42 +1,4 @@ -//! An implementation of different defined document types +//! Catalyst Signed Documents types constants //! -use crate::DocType; - -/// helper macro by evaluating `DocType::try_from_uuid(catalyst_types::uuid::uuid!())` -/// expression -macro_rules! doc_type_init { - ($s:literal) => { - match DocType::try_from_uuid(catalyst_types::uuid::uuid!($s)) { - Ok(v) => v, - Err(_) => panic!("invalid uuid v4 value"), - } - }; -} - -/// -------------- Document Types -------------- -/// Brand document type. -pub const BRAND_PARAMETERS: DocType = doc_type_init!("3e4808cc-c86e-467b-9702-d60baa9d1fca"); - -/// Campaign Parameters document type. -pub const CAMPAIGN_PARAMETERS: DocType = doc_type_init!("0110ea96-a555-47ce-8408-36efe6ed6f7c"); - -/// Category Parameters document type. -pub const CATEGORY_PARAMETERS: DocType = doc_type_init!("48c20109-362a-4d32-9bba-e0a9cf8b45be"); - -/// Proposal document type. -pub const PROPOSAL: DocType = doc_type_init!("7808d2ba-d511-40af-84e8-c0d1625fdfdc"); - -/// Proposal comment document type. -pub const PROPOSAL_COMMENT: DocType = doc_type_init!("b679ded3-0e7c-41ba-89f8-da62a17898ea"); - -/// Proposal action document type. -pub const PROPOSAL_SUBMISSION_ACTION: DocType = - doc_type_init!("5e60e623-ad02-4a1b-a1ac-406db978ee48"); - -/// Proposal Comment Template document type. -pub const PROPOSAL_COMMENT_FORM_TEMPLATE: DocType = - doc_type_init!("0b8424d4-ebfd-46e3-9577-1775a69d290c"); - -/// Proposal Template document type. -pub const PROPOSAL_FORM_TEMPLATE: DocType = doc_type_init!("0ce8ab38-9258-4fbc-a62e-7faa6e58318f"); +catalyst_signed_doc_macro::catalyst_signed_documents_types_consts!();