|
3 | 3 |
|
4 | 4 | use catalyst_types::{problem_report::ProblemReport, uuid::Uuid}; |
5 | 5 |
|
| 6 | +use super::{CATEGORY_DOCUMENT_UUID_TYPE, PROPOSAL_TEMPLATE_UUID_TYPE}; |
6 | 7 | use crate::{ |
7 | | - doc_types::PROPOSAL_TEMPLATE_UUID_TYPE, |
8 | 8 | error::CatalystSignedDocError, |
9 | | - validator::{ValidationRule, Validator}, |
10 | | - CatalystSignedDocument, |
| 9 | + metadata::{ContentEncoding, ContentType}, |
| 10 | + validator::{utils::validate_provided_doc, ValidationDataProvider}, |
| 11 | + CatalystSignedDocument, DocumentRef, |
11 | 12 | }; |
12 | 13 |
|
13 | 14 | /// Proposal document `UuidV4` type. |
14 | 15 | pub const PROPOSAL_DOCUMENT_UUID_TYPE: Uuid = |
15 | 16 | Uuid::from_u128(0x7808_D2BA_D511_40AF_84E8_C0D1_625F_DFDC); |
16 | 17 |
|
17 | 18 | /// 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 | +} |
19 | 25 |
|
20 | 26 | impl ProposalDocument { |
21 | 27 | /// Try to build `ProposalDocument` from `CatalystSignedDoc` doing all necessary |
22 | 28 | /// stateless verifications, |
23 | | - #[allow(dead_code)] |
24 | 29 | pub(crate) fn from_signed_doc( |
25 | | - doc: &CatalystSignedDocument, error_report: &ProblemReport, |
| 30 | + doc: &CatalystSignedDocument, report: &ProblemReport, |
26 | 31 | ) -> anyhow::Result<Self> { |
27 | | - /// Context for error messages. |
28 | | - const CONTEXT: &str = "Catalyst Signed Document to Proposal Document"; |
29 | 32 | let mut failed = false; |
30 | 33 |
|
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 | + ); |
54 | 62 | failed = true; |
55 | 63 | } |
| 64 | + } else { |
| 65 | + report.missing_field( |
| 66 | + "content-encoding", |
| 67 | + "Proposal Document must have a content-encoding field", |
| 68 | + ); |
| 69 | + failed = true; |
56 | 70 | } |
57 | 71 |
|
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 | + }; |
59 | 82 |
|
60 | 83 | if failed { |
61 | 84 | anyhow::bail!("Failed to build `ProposalDocument` from `CatalystSignedDoc`"); |
62 | 85 | } |
63 | 86 |
|
64 | | - Ok(Self) |
| 87 | + Ok(Self { template, category }) |
65 | 88 | } |
66 | 89 |
|
67 | | - /// A comprehensive validation of the `ProposalDocument` content. |
68 | | - #[allow(clippy::unused_self)] |
| 90 | + /// A comprehensive statefull validation of the `ProposalDocument` content. |
69 | 91 | pub(crate) fn validate_with_report( |
70 | | - &self, _validator: impl Validator, _error_report: &ProblemReport, |
| 92 | + &self, provider: &impl ValidationDataProvider, report: &ProblemReport, |
71 | 93 | ) { |
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 | + } |
73 | 125 | } |
74 | 126 | } |
75 | 127 |
|
76 | 128 | impl TryFrom<CatalystSignedDocument> for ProposalDocument { |
77 | 129 | type Error = CatalystSignedDocError; |
78 | 130 |
|
79 | 131 | 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"); |
81 | 133 | let res = Self::from_signed_doc(&doc, &error_report) |
82 | 134 | .map_err(|e| CatalystSignedDocError::new(error_report, e))?; |
83 | 135 | Ok(res) |
|
0 commit comments