Skip to content

Commit 1ce8a3c

Browse files
committed
add proposal document validation
1 parent c6f3a04 commit 1ce8a3c

File tree

8 files changed

+159
-57
lines changed

8 files changed

+159
-57
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//! Proposal Document object implementation
2+
//! <https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/catalyst_docs/proposal/#proposal-document>
3+
4+
use catalyst_types::uuid::Uuid;
5+
6+
/// Category document `UuidV4` type.
7+
pub const CATEGORY_DOCUMENT_UUID_TYPE: Uuid =
8+
Uuid::from_u128(0x48C2_0109_362A_4D32_9BBA_E0A9_CF8B_45BE);

rust/signed_doc/src/doc_types/comment_document.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
},
1111
error::CatalystSignedDocError,
1212
metadata::{ContentEncoding, ContentType},
13-
validator::{ValidationRule, Validator},
13+
validator::{ValidationDataProvider, ValidationRule},
1414
CatalystSignedDocument,
1515
};
1616

@@ -43,7 +43,7 @@ impl CommentDocument {
4343

4444
/// A comprehensive validation of the `CommentDocument` content.
4545
pub(crate) fn validate_with_report(
46-
&self, validator: &impl Validator, error_report: &ProblemReport,
46+
&self, validator: &impl ValidationDataProvider, error_report: &ProblemReport,
4747
) {
4848
let context = "Comment Document Comprehensive Validation";
4949
let doc_ref = self.0.doc_meta().doc_ref();

rust/signed_doc/src/doc_types/mod.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ pub enum DocumentType {
2323
ReviewDocument,
2424
/// A template for review documents, defining the expected structure.
2525
ReviewTemplate,
26-
/// A document defining parameters for a specific category.
27-
CategoryParametersDocument,
28-
/// A template for category parameter documents, defining the expected structure.
29-
CategoryParametersTemplate,
26+
/// A document defining for a specific category.
27+
CategoryDocument,
28+
/// A template for category documents, defining the expected structure.
29+
CategoryTemplate,
3030
/// A document containing parameters for a specific campaign.
3131
CampaignParametersDocument,
3232
/// A template for campaign parameter documents, defining the expected structure.
@@ -56,11 +56,11 @@ const COMMENT_TEMPLATE_UUID_TYPE: Uuid = Uuid::from_u128(0x0B84_24D4_EBFD_46E3_9
5656
const REVIEW_DOCUMENT_UUID_TYPE: Uuid = Uuid::from_u128(0xE4CA_F5F0_098B_45FD_94F3_0702_A457_3DB5);
5757
/// Review template `UuidV4` type.
5858
const REVIEW_TEMPLATE_UUID_TYPE: Uuid = Uuid::from_u128(0xEBE5_D0BF_5D86_4577_AF4D_008F_DDBE_2EDC);
59-
/// Category parameters document `UuidV4` type.
60-
const CATEGORY_PARAMETERS_DOCUMENT_UUID_TYPE: Uuid =
59+
/// Category document `UuidV4` type.
60+
pub const CATEGORY_DOCUMENT_UUID_TYPE: Uuid =
6161
Uuid::from_u128(0x48C2_0109_362A_4D32_9BBA_E0A9_CF8B_45BE);
62-
/// Category parameters template `UuidV4` type.
63-
const CATEGORY_PARAMETERS_TEMPLATE_UUID_TYPE: Uuid =
62+
/// Category template `UuidV4` type.
63+
const CATEGORY_TEMPLATE_UUID_TYPE: Uuid =
6464
Uuid::from_u128(0x65B1_E8B0_51F1_46A5_9970_72CD_F268_84BE);
6565
/// Campaign parameters document `UuidV4` type.
6666
const CAMPAIGN_PARAMETERS_DOCUMENT_UUID_TYPE: Uuid =
@@ -98,8 +98,8 @@ impl TryFrom<UuidV4> for DocumentType {
9898
COMMENT_TEMPLATE_UUID_TYPE => Ok(DocumentType::CommentTemplate),
9999
REVIEW_DOCUMENT_UUID_TYPE => Ok(DocumentType::ReviewDocument),
100100
REVIEW_TEMPLATE_UUID_TYPE => Ok(DocumentType::ReviewTemplate),
101-
CATEGORY_PARAMETERS_DOCUMENT_UUID_TYPE => Ok(DocumentType::CategoryParametersDocument),
102-
CATEGORY_PARAMETERS_TEMPLATE_UUID_TYPE => Ok(DocumentType::CategoryParametersTemplate),
101+
CATEGORY_DOCUMENT_UUID_TYPE => Ok(DocumentType::CategoryDocument),
102+
CATEGORY_TEMPLATE_UUID_TYPE => Ok(DocumentType::CategoryTemplate),
103103
CAMPAIGN_PARAMETERS_DOCUMENT_UUID_TYPE => Ok(DocumentType::CampaignParametersDocument),
104104
CAMPAIGN_PARAMETERS_TEMPLATE_UUID_TYPE => Ok(DocumentType::CampaignParametersTemplate),
105105
BRAND_PARAMETERS_DOCUMENT_UUID_TYPE => Ok(DocumentType::BrandParametersDocument),

rust/signed_doc/src/doc_types/proposal_document.rs

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,81 +3,133 @@
33
44
use catalyst_types::{problem_report::ProblemReport, uuid::Uuid};
55

6+
use super::{CATEGORY_DOCUMENT_UUID_TYPE, PROPOSAL_TEMPLATE_UUID_TYPE};
67
use crate::{
7-
doc_types::PROPOSAL_TEMPLATE_UUID_TYPE,
88
error::CatalystSignedDocError,
9-
validator::{ValidationRule, Validator},
10-
CatalystSignedDocument,
9+
metadata::{ContentEncoding, ContentType},
10+
validator::{utils::validate_provided_doc, ValidationDataProvider},
11+
CatalystSignedDocument, DocumentRef,
1112
};
1213

1314
/// Proposal document `UuidV4` type.
1415
pub const PROPOSAL_DOCUMENT_UUID_TYPE: Uuid =
1516
Uuid::from_u128(0x7808_D2BA_D511_40AF_84E8_C0D1_625F_DFDC);
1617

1718
/// Proposal Document struct
18-
pub struct ProposalDocument;
19+
pub struct ProposalDocument {
20+
/// `template` doc ref
21+
template: DocumentRef,
22+
/// `category` doc ref
23+
category: Option<DocumentRef>,
24+
}
1925

2026
impl ProposalDocument {
2127
/// Try to build `ProposalDocument` from `CatalystSignedDoc` doing all necessary
2228
/// stateless verifications,
23-
#[allow(dead_code)]
2429
pub(crate) fn from_signed_doc(
25-
doc: &CatalystSignedDocument, error_report: &ProblemReport,
30+
doc: &CatalystSignedDocument, report: &ProblemReport,
2631
) -> anyhow::Result<Self> {
27-
/// Context for error messages.
28-
const CONTEXT: &str = "Catalyst Signed Document to Proposal Document";
2932
let mut failed = false;
3033

31-
let rules = vec![
32-
ValidationRule {
33-
field: "type".to_string(),
34-
description: format!(
35-
"Proposal Document type UUID value is {PROPOSAL_DOCUMENT_UUID_TYPE}"
36-
),
37-
validator: |doc: &CatalystSignedDocument, _| {
38-
doc.doc_type().uuid() != PROPOSAL_DOCUMENT_UUID_TYPE
39-
},
40-
},
41-
ValidationRule {
42-
field: "template".to_string(),
43-
description: format!(
44-
"Proposal Document template UUID value is {PROPOSAL_TEMPLATE_UUID_TYPE}"
45-
),
46-
validator: |doc: &CatalystSignedDocument, _| {
47-
doc.doc_type().uuid() != PROPOSAL_TEMPLATE_UUID_TYPE
48-
},
49-
},
50-
];
51-
for rule in rules {
52-
if !(rule.validator)(doc, error_report) {
53-
error_report.functional_validation(&rule.description, "");
34+
if doc.doc_type().uuid() != PROPOSAL_DOCUMENT_UUID_TYPE {
35+
report.invalid_value(
36+
"type",
37+
doc.doc_type().to_string().as_str(),
38+
PROPOSAL_DOCUMENT_UUID_TYPE.to_string().as_str(),
39+
"Invalid Proposal Document type UUID value",
40+
);
41+
failed = true;
42+
}
43+
44+
if doc.doc_content_type() != ContentType::Json {
45+
report.invalid_value(
46+
"content-type",
47+
doc.doc_content_type().to_string().as_str(),
48+
ContentType::Json.to_string().as_str(),
49+
"Invalid Proposal Document content-type value",
50+
);
51+
failed = true;
52+
}
53+
54+
if let Some(content_encoding) = doc.doc_content_encoding() {
55+
if content_encoding != ContentEncoding::Brotli {
56+
report.invalid_value(
57+
"content-encoding",
58+
content_encoding.to_string().as_str(),
59+
ContentEncoding::Brotli.to_string().as_str(),
60+
"Invalid Proposal Document content-encoding value",
61+
);
5462
failed = true;
5563
}
64+
} else {
65+
report.missing_field(
66+
"content-encoding",
67+
"Proposal Document must have a content-encoding field",
68+
);
69+
failed = true;
5670
}
5771

58-
// TODO add other validation
72+
let category = doc.doc_meta().category_id();
73+
74+
let Some(template) = doc.doc_meta().template() else {
75+
report.missing_field(
76+
"template",
77+
"Proposal Document must have a template
78+
field",
79+
);
80+
anyhow::bail!("Failed to build `ProposalDocument` from `CatalystSignedDoc`");
81+
};
5982

6083
if failed {
6184
anyhow::bail!("Failed to build `ProposalDocument` from `CatalystSignedDoc`");
6285
}
6386

64-
Ok(Self)
87+
Ok(Self { template, category })
6588
}
6689

67-
/// A comprehensive validation of the `ProposalDocument` content.
68-
#[allow(clippy::unused_self)]
90+
/// A comprehensive statefull validation of the `ProposalDocument` content.
6991
pub(crate) fn validate_with_report(
70-
&self, _validator: impl Validator, _error_report: &ProblemReport,
92+
&self, provider: &impl ValidationDataProvider, report: &ProblemReport,
7193
) {
72-
// TODO: implement the rest of the validation
94+
let template_validator = |template_doc: CatalystSignedDocument| {
95+
if template_doc.doc_type().uuid() != PROPOSAL_TEMPLATE_UUID_TYPE {
96+
report.invalid_value(
97+
"template",
98+
template_doc.doc_type().to_string().as_str(),
99+
PROPOSAL_TEMPLATE_UUID_TYPE.to_string().as_str(),
100+
"Invalid referenced template document type",
101+
);
102+
}
103+
};
104+
validate_provided_doc(
105+
&self.template,
106+
"Proposal Template",
107+
provider,
108+
report,
109+
template_validator,
110+
);
111+
112+
if let Some(category) = &self.category {
113+
let category_validator = |category_doc: CatalystSignedDocument| {
114+
if category_doc.doc_type().uuid() != CATEGORY_DOCUMENT_UUID_TYPE {
115+
report.invalid_value(
116+
"category_id",
117+
category_doc.doc_type().to_string().as_str(),
118+
CATEGORY_DOCUMENT_UUID_TYPE.to_string().as_str(),
119+
"Invalid referenced category document type",
120+
);
121+
}
122+
};
123+
validate_provided_doc(category, "Category", provider, report, category_validator);
124+
}
73125
}
74126
}
75127

76128
impl TryFrom<CatalystSignedDocument> for ProposalDocument {
77129
type Error = CatalystSignedDocError;
78130

79131
fn try_from(doc: CatalystSignedDocument) -> Result<Self, Self::Error> {
80-
let error_report = ProblemReport::new("Proposal Document");
132+
let error_report = ProblemReport::new("Catalyst Signed Document to Proposal Document");
81133
let res = Self::from_signed_doc(&doc, &error_report)
82134
.map_err(|e| CatalystSignedDocError::new(error_report, e))?;
83135
Ok(res)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use catalyst_types::uuid::Uuid;
2+
3+
/// Proposal template `UuidV4` type.
4+
pub const PROPOSAL_TEMPLATE_UUID_TYPE: Uuid =
5+
Uuid::from_u128(0x0CE8_AB38_9258_4FBC_A62E_7FAA_6E58_318F);

rust/signed_doc/src/metadata/document_ref.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
//! Catalyst Signed Document Metadata.
2+
3+
use std::fmt::Display;
4+
25
use coset::cbor::Value;
36

47
use super::{decode_cbor_uuid, encode_cbor_uuid, UuidV7};
@@ -13,6 +16,16 @@ pub struct DocumentRef {
1316
pub ver: Option<UuidV7>,
1417
}
1518

19+
impl Display for DocumentRef {
20+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21+
if let Some(ver) = self.ver {
22+
write!(f, "id: {}, ver: {}", self.id, ver)
23+
} else {
24+
write!(f, "id: {}", self.id)
25+
}
26+
}
27+
}
28+
1629
impl DocumentRef {
1730
/// Determine if internal `UUID`s are valid.
1831
#[must_use]

rust/signed_doc/src/validator/mod.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Catalyst Signed Documents validation
22
3+
pub(crate) mod utils;
4+
35
use catalyst_types::{id_uri::IdUri, problem_report::ProblemReport};
46
use rbac_registration::cardano::cip509::SimplePublicKeyType;
57

@@ -9,11 +11,11 @@ use crate::{
911
CatalystSignedDocument, DocumentRef,
1012
};
1113

12-
/// Trait for validating Catalyst Signed Documents.
13-
pub trait Validator {
14+
/// Trait for getting a necessary data needed during the validation process.
15+
pub trait ValidationDataProvider {
1416
/// Get public keys
1517
fn get_public_key(&self, kid: &IdUri) -> Option<SimplePublicKeyType>;
16-
/// Get signed document reference
18+
/// Get signed document by document reference
1719
fn get_doc_ref(&self, doc_ref: &DocumentRef) -> Option<CatalystSignedDocument>;
1820
}
1921

@@ -35,7 +37,7 @@ pub struct ValidationRule<T> {
3537
///
3638
/// Returns a report of validation failures and the source error.
3739
pub fn validate<F>(
38-
doc: &CatalystSignedDocument, doc_getter: impl Validator,
40+
doc: &CatalystSignedDocument, doc_getter: &impl ValidationDataProvider,
3941
) -> Result<(), CatalystSignedDocError> {
4042
let error_report = ProblemReport::new("Catalyst Signed Document Validation");
4143

@@ -65,14 +67,14 @@ pub fn validate<F>(
6567
DocumentType::ProposalTemplate => {},
6668
DocumentType::CommentDocument => {
6769
if let Ok(comment_doc) = CommentDocument::from_signed_doc(doc, &error_report) {
68-
comment_doc.validate_with_report(&doc_getter, &error_report);
70+
comment_doc.validate_with_report(doc_getter, &error_report);
6971
}
7072
},
7173
DocumentType::CommentTemplate => {},
7274
DocumentType::ReviewDocument => {},
7375
DocumentType::ReviewTemplate => {},
74-
DocumentType::CategoryParametersDocument => {},
75-
DocumentType::CategoryParametersTemplate => {},
76+
DocumentType::CategoryDocument => {},
77+
DocumentType::CategoryTemplate => {},
7678
DocumentType::CampaignParametersDocument => {},
7779
DocumentType::CampaignParametersTemplate => {},
7880
DocumentType::BrandParametersDocument => {},
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//! Validation utility functions
2+
3+
use catalyst_types::problem_report::ProblemReport;
4+
5+
use super::ValidationDataProvider;
6+
use crate::{CatalystSignedDocument, DocumentRef};
7+
8+
/// A helper validation document function, which validates a document from the
9+
/// `ValidationDataProvider`.
10+
pub(crate) fn validate_provided_doc(
11+
doc_ref: &DocumentRef, doc_name: &str, provider: &impl ValidationDataProvider,
12+
report: &ProblemReport, validator: impl Fn(CatalystSignedDocument),
13+
) {
14+
if let Some(doc) = provider.get_doc_ref(doc_ref) {
15+
validator(doc);
16+
} else {
17+
report.functional_validation(
18+
format!("Cannot retrieve a {doc_name} document {doc_ref}").as_str(),
19+
"Validation data provider could not return a corresponding {doc_name}.",
20+
);
21+
}
22+
}

0 commit comments

Comments
 (0)