Skip to content

Commit b54623a

Browse files
committed
refactor
1 parent 1ce8a3c commit b54623a

File tree

5 files changed

+166
-108
lines changed

5 files changed

+166
-108
lines changed

rust/signed_doc/src/doc_types/category_document.rs

Lines changed: 0 additions & 8 deletions
This file was deleted.

rust/signed_doc/src/doc_types/proposal_document.rs

Lines changed: 119 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ use super::{CATEGORY_DOCUMENT_UUID_TYPE, PROPOSAL_TEMPLATE_UUID_TYPE};
77
use crate::{
88
error::CatalystSignedDocError,
99
metadata::{ContentEncoding, ContentType},
10-
validator::{utils::validate_provided_doc, ValidationDataProvider},
10+
validator::{
11+
utils::validate_provided_doc, StatefullRule, StatelessRule, ValidationDataProvider,
12+
Validator,
13+
},
1114
CatalystSignedDocument, DocumentRef,
1215
};
1316

@@ -23,105 +26,139 @@ pub struct ProposalDocument {
2326
category: Option<DocumentRef>,
2427
}
2528

26-
impl ProposalDocument {
27-
/// Try to build `ProposalDocument` from `CatalystSignedDoc` doing all necessary
28-
/// stateless verifications,
29-
pub(crate) fn from_signed_doc(
30-
doc: &CatalystSignedDocument, report: &ProblemReport,
31-
) -> anyhow::Result<Self> {
32-
let mut failed = false;
29+
impl Validator for ProposalDocument {
30+
const STATEFULL_RULES: &[StatefullRule<Self>] = &[template_full_check, category_full_check];
31+
const STATELESS_RULES: &[StatelessRule] = &[
32+
type_check,
33+
content_type_check,
34+
content_encoding_check,
35+
template_check,
36+
];
37+
}
3338

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-
}
39+
/// `type` field validation
40+
fn type_check(doc: &CatalystSignedDocument, report: &ProblemReport) -> bool {
41+
if doc.doc_type().uuid() != PROPOSAL_DOCUMENT_UUID_TYPE {
42+
report.invalid_value(
43+
"type",
44+
doc.doc_type().to_string().as_str(),
45+
PROPOSAL_DOCUMENT_UUID_TYPE.to_string().as_str(),
46+
"Invalid Proposal Document type UUID value",
47+
);
48+
return false;
49+
}
50+
true
51+
}
4352

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+
/// `content-type` validation
54+
fn content_type_check(doc: &CatalystSignedDocument, report: &ProblemReport) -> bool {
55+
if doc.doc_content_type() != ContentType::Json {
56+
report.invalid_value(
57+
"content-type",
58+
doc.doc_content_type().to_string().as_str(),
59+
ContentType::Json.to_string().as_str(),
60+
"Invalid Proposal Document content-type value",
61+
);
62+
return false;
63+
}
64+
true
65+
}
5366

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-
);
62-
failed = true;
63-
}
64-
} else {
65-
report.missing_field(
67+
/// `content-encoding` validation
68+
fn content_encoding_check(doc: &CatalystSignedDocument, report: &ProblemReport) -> bool {
69+
if let Some(content_encoding) = doc.doc_content_encoding() {
70+
if content_encoding != ContentEncoding::Brotli {
71+
report.invalid_value(
6672
"content-encoding",
67-
"Proposal Document must have a content-encoding field",
73+
content_encoding.to_string().as_str(),
74+
ContentEncoding::Brotli.to_string().as_str(),
75+
"Invalid Proposal Document content-encoding value",
6876
);
69-
failed = true;
77+
return false;
7078
}
79+
} else {
80+
report.missing_field(
81+
"content-encoding",
82+
"Proposal Document must have a content-encoding field",
83+
);
84+
return false;
85+
}
86+
true
87+
}
7188

72-
let category = doc.doc_meta().category_id();
89+
/// `template` validation
90+
fn template_check(doc: &CatalystSignedDocument, report: &ProblemReport) -> bool {
91+
if doc.doc_meta().template().is_none() {
92+
report.missing_field("template", "Proposal Document must have a template field");
93+
return false;
94+
}
95+
true
96+
}
7397

74-
let Some(template) = doc.doc_meta().template() else {
75-
report.missing_field(
98+
/// `template` statefull validation
99+
fn template_full_check(
100+
doc: &ProposalDocument, provider: &dyn ValidationDataProvider, report: &ProblemReport,
101+
) -> bool {
102+
let template_validator = |template_doc: CatalystSignedDocument| {
103+
if template_doc.doc_type().uuid() != PROPOSAL_TEMPLATE_UUID_TYPE {
104+
report.invalid_value(
76105
"template",
77-
"Proposal Document must have a template
78-
field",
106+
template_doc.doc_type().to_string().as_str(),
107+
PROPOSAL_TEMPLATE_UUID_TYPE.to_string().as_str(),
108+
"Invalid referenced template document type",
79109
);
80-
anyhow::bail!("Failed to build `ProposalDocument` from `CatalystSignedDoc`");
81-
};
82-
83-
if failed {
84-
anyhow::bail!("Failed to build `ProposalDocument` from `CatalystSignedDoc`");
110+
return false;
85111
}
112+
true
113+
};
114+
validate_provided_doc(
115+
&doc.template,
116+
"Proposal Template",
117+
provider,
118+
report,
119+
template_validator,
120+
)
121+
}
86122

87-
Ok(Self { template, category })
88-
}
89-
90-
/// A comprehensive statefull validation of the `ProposalDocument` content.
91-
pub(crate) fn validate_with_report(
92-
&self, provider: &impl ValidationDataProvider, report: &ProblemReport,
93-
) {
94-
let template_validator = |template_doc: CatalystSignedDocument| {
95-
if template_doc.doc_type().uuid() != PROPOSAL_TEMPLATE_UUID_TYPE {
123+
/// `category_id` statefull validation
124+
fn category_full_check(
125+
doc: &ProposalDocument, provider: &dyn ValidationDataProvider, report: &ProblemReport,
126+
) -> bool {
127+
if let Some(category) = &doc.category {
128+
let category_validator = |category_doc: CatalystSignedDocument| -> bool {
129+
if category_doc.doc_type().uuid() != CATEGORY_DOCUMENT_UUID_TYPE {
96130
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",
131+
"category_id",
132+
category_doc.doc_type().to_string().as_str(),
133+
CATEGORY_DOCUMENT_UUID_TYPE.to_string().as_str(),
134+
"Invalid referenced category document type",
101135
);
136+
return false;
102137
}
138+
true
103139
};
104-
validate_provided_doc(
105-
&self.template,
106-
"Proposal Template",
107-
provider,
108-
report,
109-
template_validator,
110-
);
140+
return validate_provided_doc(category, "Category", provider, report, category_validator);
141+
}
142+
true
143+
}
111144

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);
145+
impl ProposalDocument {
146+
/// Try to build `ProposalDocument` from `CatalystSignedDoc` doing all necessary
147+
/// stateless verifications,
148+
pub(crate) fn from_signed_doc(
149+
doc: &CatalystSignedDocument, report: &ProblemReport,
150+
) -> anyhow::Result<Self> {
151+
if Self::stateless_validation(doc, report) {
152+
anyhow::bail!("Failed to build `ProposalDocument` from `CatalystSignedDoc`");
124153
}
154+
155+
let category = doc.doc_meta().category_id();
156+
let template = doc
157+
.doc_meta()
158+
.template()
159+
.ok_or(anyhow::anyhow!("missing `template` field"))?;
160+
161+
Ok(Self { template, category })
125162
}
126163
}
127164

rust/signed_doc/src/doc_types/proposal_template.rs

Lines changed: 0 additions & 5 deletions
This file was deleted.

rust/signed_doc/src/validator/mod.rs

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,39 @@ pub trait ValidationDataProvider {
1919
fn get_doc_ref(&self, doc_ref: &DocumentRef) -> Option<CatalystSignedDocument>;
2020
}
2121

22+
/// Stateless validation function rule type
23+
pub(crate) type StatelessRule = fn(&CatalystSignedDocument, &ProblemReport) -> bool;
24+
/// Statefull validation function rule type
25+
pub(crate) type StatefullRule<T> = fn(&T, &dyn ValidationDataProvider, &ProblemReport) -> bool;
26+
27+
/// Trait for defining a validation rules.
28+
pub trait Validator
29+
where Self: 'static
30+
{
31+
/// Stateless validation rules
32+
const STATELESS_RULES: &[StatelessRule];
33+
/// Statefull validation rules
34+
const STATEFULL_RULES: &[StatefullRule<Self>];
35+
36+
/// Perform a stateless validation, collecting a problem report
37+
fn stateless_validation(doc: &CatalystSignedDocument, report: &ProblemReport) -> bool {
38+
Self::STATELESS_RULES
39+
.iter()
40+
.map(|rule| rule(doc, report))
41+
.all(|res| res)
42+
}
43+
44+
/// Perform a statefull validation, collecting a problem report
45+
fn statefull_validation(
46+
&self, provider: &impl ValidationDataProvider, report: &ProblemReport,
47+
) -> bool {
48+
Self::STATEFULL_RULES
49+
.iter()
50+
.map(|rule| rule(self, provider, report))
51+
.all(|res| res)
52+
}
53+
}
54+
2255
/// Validation rule
2356
pub struct ValidationRule<T> {
2457
/// Name of field that is being validated
@@ -39,19 +72,19 @@ pub struct ValidationRule<T> {
3972
pub fn validate<F>(
4073
doc: &CatalystSignedDocument, doc_getter: &impl ValidationDataProvider,
4174
) -> Result<(), CatalystSignedDocError> {
42-
let error_report = ProblemReport::new("Catalyst Signed Document Validation");
75+
let report = ProblemReport::new("Catalyst Signed Document Validation");
4376

4477
let doc_type: DocumentType = match doc.doc_type().try_into() {
4578
Ok(doc_type) => doc_type,
4679
Err(e) => {
47-
error_report.invalid_value(
80+
report.invalid_value(
4881
"`type`",
4982
&doc.doc_type().to_string(),
5083
&e.to_string(),
5184
"verifying document type",
5285
);
5386
return Err(CatalystSignedDocError::new(
54-
error_report,
87+
report,
5588
anyhow::anyhow!("Validation of the Catalyst Signed Document failed"),
5689
));
5790
},
@@ -60,14 +93,14 @@ pub fn validate<F>(
6093
#[allow(clippy::match_same_arms)]
6194
match doc_type {
6295
DocumentType::ProposalDocument => {
63-
if let Ok(proposal_doc) = ProposalDocument::from_signed_doc(doc, &error_report) {
64-
proposal_doc.validate_with_report(doc_getter, &error_report);
96+
if let Ok(proposal_doc) = ProposalDocument::from_signed_doc(doc, &report) {
97+
proposal_doc.statefull_validation(doc_getter, &report);
6598
}
6699
},
67100
DocumentType::ProposalTemplate => {},
68101
DocumentType::CommentDocument => {
69-
if let Ok(comment_doc) = CommentDocument::from_signed_doc(doc, &error_report) {
70-
comment_doc.validate_with_report(doc_getter, &error_report);
102+
if let Ok(comment_doc) = CommentDocument::from_signed_doc(doc, &report) {
103+
comment_doc.validate_with_report(doc_getter, &report);
71104
}
72105
},
73106
DocumentType::CommentTemplate => {},
@@ -85,9 +118,9 @@ pub fn validate<F>(
85118
DocumentType::ImmutableLedgerBlock => {},
86119
}
87120

88-
if error_report.is_problematic() {
121+
if report.is_problematic() {
89122
return Err(CatalystSignedDocError::new(
90-
error_report,
123+
report,
91124
anyhow::anyhow!("Validation of the Catalyst Signed Document failed"),
92125
));
93126
}

rust/signed_doc/src/validator/utils.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ use crate::{CatalystSignedDocument, DocumentRef};
88
/// A helper validation document function, which validates a document from the
99
/// `ValidationDataProvider`.
1010
pub(crate) fn validate_provided_doc(
11-
doc_ref: &DocumentRef, doc_name: &str, provider: &impl ValidationDataProvider,
12-
report: &ProblemReport, validator: impl Fn(CatalystSignedDocument),
13-
) {
11+
doc_ref: &DocumentRef, doc_name: &str, provider: &dyn ValidationDataProvider,
12+
report: &ProblemReport, validator: impl Fn(CatalystSignedDocument) -> bool,
13+
) -> bool {
1414
if let Some(doc) = provider.get_doc_ref(doc_ref) {
15-
validator(doc);
15+
validator(doc)
1616
} else {
1717
report.functional_validation(
1818
format!("Cannot retrieve a {doc_name} document {doc_ref}").as_str(),
1919
"Validation data provider could not return a corresponding {doc_name}.",
2020
);
21+
false
2122
}
2223
}

0 commit comments

Comments
 (0)