Skip to content

Commit b573bff

Browse files
vlopes11Mr-Leshiyno30bit
authored
refactor(rust/signed-doc): add parameters field (#317)
* refactor(rust/signed-doc): add parameters field This commit replaces the ad-hoc attributes (category_id, election_id, campaign_id, brand_id) for a single `parameters` field, that is expected to hold document-specific validation rules. It is in accordance to the architecture specs https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/signed_doc/spec/ Closes #307 * Update rust/signed_doc/src/validator/mod.rs * wip * wip * wip * fix spelling * fix clippy * wip * pedantic suggestion (#327) --------- Co-authored-by: Alex Pozhylenkov <[email protected]> Co-authored-by: Artur Helmanau <[email protected]>
1 parent 0221c37 commit b573bff

File tree

7 files changed

+466
-295
lines changed

7 files changed

+466
-295
lines changed

rust/signed_doc/src/metadata/extra_fields.rs

Lines changed: 38 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
//! Catalyst Signed Document Extra Fields.
22
3-
use catalyst_types::{problem_report::ProblemReport, uuid::UuidV4};
3+
use catalyst_types::problem_report::ProblemReport;
44
use coset::{cbor::Value, Label, ProtectedHeader};
55

66
use super::{
7-
cose_protected_header_find,
8-
utils::{decode_document_field_from_protected_header, CborUuidV4},
9-
DocumentRef, Section,
7+
cose_protected_header_find, utils::decode_document_field_from_protected_header, DocumentRef,
8+
Section,
109
};
1110

1211
/// `ref` field COSE key value
@@ -19,13 +18,13 @@ const REPLY_KEY: &str = "reply";
1918
const SECTION_KEY: &str = "section";
2019
/// `collabs` field COSE key value
2120
const COLLABS_KEY: &str = "collabs";
22-
/// `brand_id` field COSE key value
21+
/// `parameters` field COSE key value
22+
const PARAMETERS_KEY: &str = "parameters";
23+
/// `brand_id` field COSE key value (alias of the `parameters` field)
2324
const BRAND_ID_KEY: &str = "brand_id";
24-
/// `campaign_id` field COSE key value
25+
/// `campaign_id` field COSE key value (alias of the `parameters` field)
2526
const CAMPAIGN_ID_KEY: &str = "campaign_id";
26-
/// `election_id` field COSE key value
27-
const ELECTION_ID_KEY: &str = "election_id";
28-
/// `category_id` field COSE key value
27+
/// `category_id` field COSE key value (alias of the `parameters` field)
2928
const CATEGORY_ID_KEY: &str = "category_id";
3029

3130
/// Extra Metadata Fields.
@@ -48,18 +47,9 @@ pub struct ExtraFields {
4847
/// Reference to the document collaborators. Collaborator type is TBD.
4948
#[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")]
5049
collabs: Vec<String>,
51-
/// Unique identifier for the brand that is running the voting.
50+
/// Reference to the parameters document.
5251
#[serde(skip_serializing_if = "Option::is_none")]
53-
brand_id: Option<DocumentRef>,
54-
/// Unique identifier for the campaign of voting.
55-
#[serde(skip_serializing_if = "Option::is_none")]
56-
campaign_id: Option<DocumentRef>,
57-
/// Unique identifier for the election.
58-
#[serde(skip_serializing_if = "Option::is_none")]
59-
election_id: Option<UuidV4>,
60-
/// Unique identifier for the voting category as a collection of proposals.
61-
#[serde(skip_serializing_if = "Option::is_none")]
62-
category_id: Option<DocumentRef>,
52+
parameters: Option<DocumentRef>,
6353
}
6454

6555
impl ExtraFields {
@@ -93,28 +83,10 @@ impl ExtraFields {
9383
&self.collabs
9484
}
9585

96-
/// Return `brand_id` field.
97-
#[must_use]
98-
pub fn brand_id(&self) -> Option<DocumentRef> {
99-
self.brand_id
100-
}
101-
102-
/// Return `campaign_id` field.
103-
#[must_use]
104-
pub fn campaign_id(&self) -> Option<DocumentRef> {
105-
self.campaign_id
106-
}
107-
108-
/// Return `election_id` field.
86+
/// Return `parameters` field.
10987
#[must_use]
110-
pub fn election_id(&self) -> Option<UuidV4> {
111-
self.election_id
112-
}
113-
114-
/// Return `category_id` field.
115-
#[must_use]
116-
pub fn category_id(&self) -> Option<DocumentRef> {
117-
self.category_id
88+
pub fn parameters(&self) -> Option<DocumentRef> {
89+
self.parameters
11890
}
11991

12092
/// Fill the COSE header `ExtraFields` data into the header builder.
@@ -141,26 +113,11 @@ impl ExtraFields {
141113
Value::Array(self.collabs.iter().cloned().map(Value::Text).collect()),
142114
);
143115
}
144-
if let Some(brand_id) = &self.brand_id {
145-
builder = builder.text_value(BRAND_ID_KEY.to_string(), Value::try_from(*brand_id)?);
146-
}
147116

148-
if let Some(campaign_id) = &self.campaign_id {
149-
builder =
150-
builder.text_value(CAMPAIGN_ID_KEY.to_string(), Value::try_from(*campaign_id)?);
117+
if let Some(parameters) = &self.parameters {
118+
builder = builder.text_value(PARAMETERS_KEY.to_string(), Value::try_from(*parameters)?);
151119
}
152120

153-
if let Some(election_id) = &self.election_id {
154-
builder = builder.text_value(
155-
ELECTION_ID_KEY.to_string(),
156-
Value::try_from(CborUuidV4(*election_id))?,
157-
);
158-
}
159-
160-
if let Some(category_id) = &self.category_id {
161-
builder =
162-
builder.text_value(CATEGORY_ID_KEY.to_string(), Value::try_from(*category_id)?);
163-
}
164121
Ok(builder)
165122
}
166123

@@ -196,41 +153,38 @@ impl ExtraFields {
196153
COSE_DECODING_CONTEXT,
197154
error_report,
198155
);
199-
let brand_id = decode_document_field_from_protected_header(
200-
protected,
156+
157+
// process `parameters` field and all its aliases
158+
let (parameters, has_multiple_fields) = [
159+
PARAMETERS_KEY,
201160
BRAND_ID_KEY,
202-
COSE_DECODING_CONTEXT,
203-
error_report,
204-
);
205-
let campaign_id = decode_document_field_from_protected_header(
206-
protected,
207161
CAMPAIGN_ID_KEY,
208-
COSE_DECODING_CONTEXT,
209-
error_report,
210-
);
211-
let election_id = decode_document_field_from_protected_header::<CborUuidV4>(
212-
protected,
213-
ELECTION_ID_KEY,
214-
COSE_DECODING_CONTEXT,
215-
error_report,
216-
)
217-
.map(|v| v.0);
218-
let category_id = decode_document_field_from_protected_header(
219-
protected,
220162
CATEGORY_ID_KEY,
221-
COSE_DECODING_CONTEXT,
222-
error_report,
223-
);
163+
]
164+
.iter()
165+
.filter_map(|field_name| -> Option<DocumentRef> {
166+
decode_document_field_from_protected_header(
167+
protected,
168+
field_name,
169+
COSE_DECODING_CONTEXT,
170+
error_report,
171+
)
172+
})
173+
.fold((None, false), |(res, _), v| (Some(v), res.is_some()));
174+
if has_multiple_fields {
175+
error_report.duplicate_field(
176+
"brand_id, campaign_id, category_id",
177+
"Only value at the same time is allowed parameters, brand_id, campaign_id, category_id",
178+
"Validation of parameters field aliases"
179+
);
180+
}
224181

225182
let mut extra = ExtraFields {
226183
doc_ref,
227184
template,
228185
reply,
229186
section,
230-
brand_id,
231-
campaign_id,
232-
election_id,
233-
category_id,
187+
parameters,
234188
..Default::default()
235189
};
236190

rust/signed_doc/src/validator/mod.rs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub(crate) mod utils;
55

66
use std::{
77
collections::HashMap,
8+
fmt,
89
sync::LazyLock,
910
time::{Duration, SystemTime},
1011
};
@@ -13,18 +14,19 @@ use anyhow::Context;
1314
use catalyst_types::{
1415
catalyst_id::{role_index::RoleId, CatalystId},
1516
problem_report::ProblemReport,
16-
uuid::Uuid,
17+
uuid::{Uuid, UuidV4},
1718
};
1819
use coset::{CoseSign, CoseSignature};
1920
use rules::{
20-
CategoryRule, ContentEncodingRule, ContentRule, ContentSchema, ContentTypeRule, RefRule,
21+
ContentEncodingRule, ContentRule, ContentSchema, ContentTypeRule, ParametersRule, RefRule,
2122
ReplyRule, Rules, SectionRule, SignatureKidRule,
2223
};
2324

2425
use crate::{
2526
doc_types::{
26-
COMMENT_DOCUMENT_UUID_TYPE, COMMENT_TEMPLATE_UUID_TYPE, PROPOSAL_ACTION_DOCUMENT_UUID_TYPE,
27-
PROPOSAL_DOCUMENT_UUID_TYPE, PROPOSAL_TEMPLATE_UUID_TYPE,
27+
CATEGORY_DOCUMENT_UUID_TYPE, COMMENT_DOCUMENT_UUID_TYPE, COMMENT_TEMPLATE_UUID_TYPE,
28+
PROPOSAL_ACTION_DOCUMENT_UUID_TYPE, PROPOSAL_DOCUMENT_UUID_TYPE,
29+
PROPOSAL_TEMPLATE_UUID_TYPE,
2830
},
2931
providers::{CatalystSignedDocumentProvider, VerifyingKeyProvider},
3032
CatalystSignedDocument, ContentEncoding, ContentType,
@@ -33,6 +35,14 @@ use crate::{
3335
/// A table representing a full set or validation rules per document id.
3436
static DOCUMENT_RULES: LazyLock<HashMap<Uuid, Rules>> = LazyLock::new(document_rules_init);
3537

38+
/// Returns an [`UuidV4`] from the provided argument, panicking if the argument is
39+
/// invalid.
40+
#[allow(clippy::expect_used)]
41+
fn expect_uuidv4<T>(t: T) -> UuidV4
42+
where T: TryInto<UuidV4, Error: fmt::Debug> {
43+
t.try_into().expect("Must be a valid UUID V4")
44+
}
45+
3646
/// `DOCUMENT_RULES` initialization function
3747
#[allow(clippy::expect_used)]
3848
fn document_rules_init() -> HashMap<Uuid, Rules> {
@@ -47,18 +57,20 @@ fn document_rules_init() -> HashMap<Uuid, Rules> {
4757
optional: false,
4858
},
4959
content: ContentRule::Templated {
50-
exp_template_type: PROPOSAL_TEMPLATE_UUID_TYPE
51-
.try_into()
52-
.expect("Must be a valid UUID V4"),
60+
exp_template_type: expect_uuidv4(PROPOSAL_TEMPLATE_UUID_TYPE),
61+
},
62+
parameters: ParametersRule::Specified {
63+
exp_parameters_type: expect_uuidv4(CATEGORY_DOCUMENT_UUID_TYPE),
64+
optional: true,
5365
},
54-
category: CategoryRule::Specified { optional: true },
5566
doc_ref: RefRule::NotSpecified,
5667
reply: ReplyRule::NotSpecified,
5768
section: SectionRule::NotSpecified,
5869
kid: SignatureKidRule {
5970
exp: &[RoleId::Proposer],
6071
},
6172
};
73+
6274
document_rules_map.insert(PROPOSAL_DOCUMENT_UUID_TYPE, proposal_document_rules);
6375

6476
let comment_document_rules = Rules {
@@ -70,24 +82,18 @@ fn document_rules_init() -> HashMap<Uuid, Rules> {
7082
optional: false,
7183
},
7284
content: ContentRule::Templated {
73-
exp_template_type: COMMENT_TEMPLATE_UUID_TYPE
74-
.try_into()
75-
.expect("Must be a valid UUID V4"),
85+
exp_template_type: expect_uuidv4(COMMENT_TEMPLATE_UUID_TYPE),
7686
},
7787
doc_ref: RefRule::Specified {
78-
exp_ref_type: PROPOSAL_DOCUMENT_UUID_TYPE
79-
.try_into()
80-
.expect("Must be a valid UUID V4"),
88+
exp_ref_type: expect_uuidv4(PROPOSAL_DOCUMENT_UUID_TYPE),
8189
optional: false,
8290
},
8391
reply: ReplyRule::Specified {
84-
exp_reply_type: COMMENT_DOCUMENT_UUID_TYPE
85-
.try_into()
86-
.expect("Must be a valid UUID V4"),
92+
exp_reply_type: expect_uuidv4(COMMENT_DOCUMENT_UUID_TYPE),
8793
optional: true,
8894
},
8995
section: SectionRule::Specified { optional: true },
90-
category: CategoryRule::NotSpecified,
96+
parameters: ParametersRule::NotSpecified,
9197
kid: SignatureKidRule {
9298
exp: &[RoleId::Role0],
9399
},
@@ -112,11 +118,12 @@ fn document_rules_init() -> HashMap<Uuid, Rules> {
112118
optional: false,
113119
},
114120
content: ContentRule::Static(ContentSchema::Json(proposal_action_json_schema)),
115-
category: CategoryRule::Specified { optional: true },
121+
parameters: ParametersRule::Specified {
122+
exp_parameters_type: expect_uuidv4(CATEGORY_DOCUMENT_UUID_TYPE),
123+
optional: true,
124+
},
116125
doc_ref: RefRule::Specified {
117-
exp_ref_type: PROPOSAL_DOCUMENT_UUID_TYPE
118-
.try_into()
119-
.expect("Must be a valid UUID V4"),
126+
exp_ref_type: expect_uuidv4(PROPOSAL_DOCUMENT_UUID_TYPE),
120127
optional: false,
121128
},
122129
reply: ReplyRule::NotSpecified,

0 commit comments

Comments
 (0)