• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

input-output-hk / catalyst-libs / 17868036982

19 Sep 2025 07:34PM UTC coverage: 68.918% (+0.2%) from 68.74%
17868036982

Pull #567

github

web-flow
Merge d49c96af1 into b1136bb78
Pull Request #567: feat(rust/signed-doc): implement DocumentOwnershipRule

209 of 221 new or added lines in 11 files covered. (94.57%)

107 existing lines in 2 files now uncovered.

13907 of 20179 relevant lines covered (68.92%)

2656.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.71
/rust/signed_doc/src/validator/mod.rs
1
//! Catalyst Signed Documents validation logic
2

3
pub(crate) mod json_schema;
4
pub(crate) mod rules;
5

6
use std::{collections::HashMap, sync::LazyLock};
7

8
use catalyst_types::catalyst_id::role_index::RoleId;
9
use rules::{
10
    ContentEncodingRule, ContentRule, ContentSchema, ContentTypeRule, DocumentOwnershipRule,
11
    IdRule, ParametersRule, RefRule, ReplyRule, Rules, SectionRule, SignatureKidRule, VerRule,
12
};
13

14
use crate::{
15
    doc_types::{
16
        BRAND_PARAMETERS, CAMPAIGN_PARAMETERS, CATEGORY_PARAMETERS, PROPOSAL, PROPOSAL_COMMENT,
17
        PROPOSAL_COMMENT_FORM_TEMPLATE, PROPOSAL_FORM_TEMPLATE, PROPOSAL_SUBMISSION_ACTION,
18
    },
19
    metadata::DocType,
20
    providers::{CatalystIdProvider, CatalystSignedDocumentProvider},
21
    validator::rules::{CollaboratorsRule, SignatureRule, TemplateRule},
22
    CatalystSignedDocument, ContentEncoding, ContentType,
23
};
24

25
/// A table representing a full set or validation rules per document id.
26
static DOCUMENT_RULES: LazyLock<HashMap<DocType, Rules>> = LazyLock::new(document_rules_init);
27

28
/// Proposal
29
/// Require field: type, id, ver, template, parameters
30
/// <https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/signed_doc/docs/proposal/>
31
fn proposal_rule() -> Rules {
15✔
32
    // Parameter can be either brand, campaign or category
33
    let parameters = vec![
15✔
34
        BRAND_PARAMETERS.clone(),
15✔
35
        CAMPAIGN_PARAMETERS.clone(),
15✔
36
        CATEGORY_PARAMETERS.clone(),
15✔
37
    ];
38
    Rules {
15✔
39
        id: IdRule,
15✔
40
        ver: VerRule,
15✔
41
        content_type: ContentTypeRule::Specified {
15✔
42
            exp: ContentType::Json,
15✔
43
        },
15✔
44
        content_encoding: ContentEncodingRule::Specified {
15✔
45
            exp: vec![ContentEncoding::Brotli],
15✔
46
            optional: false,
15✔
47
        },
15✔
48
        template: TemplateRule::Specified {
15✔
49
            allowed_type: PROPOSAL_FORM_TEMPLATE.clone(),
15✔
50
        },
15✔
51
        parameters: ParametersRule::Specified {
15✔
52
            allowed_type: parameters.clone(),
15✔
53
            optional: false,
15✔
54
        },
15✔
55
        doc_ref: RefRule::NotSpecified,
15✔
56
        reply: ReplyRule::NotSpecified,
15✔
57
        section: SectionRule::NotSpecified,
15✔
58
        collaborators: CollaboratorsRule::NotSpecified,
15✔
59
        content: ContentRule::NotNil,
15✔
60
        kid: SignatureKidRule {
15✔
61
            allowed_roles: [RoleId::Proposer].into_iter().collect(),
15✔
62
        },
15✔
63
        signature: SignatureRule { mutlisig: false },
15✔
64
        ownership: DocumentOwnershipRule {
15✔
65
            allow_collaborators: false,
15✔
66
        },
15✔
67
    }
15✔
68
}
15✔
69

70
/// Proposal Comment
71
/// Require field: type, id, ver, ref, template, parameters
72
/// <https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/signed_doc/docs/proposal_comment_template/>
73
fn proposal_comment_rule() -> Rules {
15✔
74
    // Parameter can be either brand, campaign or category
75
    let parameters = vec![
15✔
76
        BRAND_PARAMETERS.clone(),
15✔
77
        CAMPAIGN_PARAMETERS.clone(),
15✔
78
        CATEGORY_PARAMETERS.clone(),
15✔
79
    ];
80
    Rules {
15✔
81
        id: IdRule,
15✔
82
        ver: VerRule,
15✔
83
        content_type: ContentTypeRule::Specified {
15✔
84
            exp: ContentType::Json,
15✔
85
        },
15✔
86
        content_encoding: ContentEncodingRule::Specified {
15✔
87
            exp: vec![ContentEncoding::Brotli],
15✔
88
            optional: false,
15✔
89
        },
15✔
90
        template: TemplateRule::Specified {
15✔
91
            allowed_type: PROPOSAL_COMMENT_FORM_TEMPLATE.clone(),
15✔
92
        },
15✔
93
        doc_ref: RefRule::Specified {
15✔
94
            allowed_type: vec![PROPOSAL.clone()],
15✔
95
            multiple: false,
15✔
96
            optional: false,
15✔
97
        },
15✔
98
        reply: ReplyRule::Specified {
15✔
99
            allowed_type: PROPOSAL_COMMENT.clone(),
15✔
100
            optional: true,
15✔
101
        },
15✔
102
        section: SectionRule::NotSpecified,
15✔
103
        parameters: ParametersRule::Specified {
15✔
104
            allowed_type: parameters.clone(),
15✔
105
            optional: false,
15✔
106
        },
15✔
107
        collaborators: CollaboratorsRule::NotSpecified,
15✔
108
        content: ContentRule::NotNil,
15✔
109
        kid: SignatureKidRule {
15✔
110
            allowed_roles: [RoleId::Role0].into_iter().collect(),
15✔
111
        },
15✔
112
        signature: SignatureRule { mutlisig: false },
15✔
113
        ownership: DocumentOwnershipRule {
15✔
114
            allow_collaborators: false,
15✔
115
        },
15✔
116
    }
15✔
117
}
15✔
118

119
/// Proposal Submission Action
120
/// Require fields: type, id, ver, ref, parameters
121
/// <https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/signed_doc/docs/proposal_submission_action/>
122
#[allow(clippy::expect_used)]
123
fn proposal_submission_action_rule() -> Rules {
15✔
124
    // Parameter can be either brand, campaign or category
125
    let parameters = vec![
15✔
126
        BRAND_PARAMETERS.clone(),
15✔
127
        CAMPAIGN_PARAMETERS.clone(),
15✔
128
        CATEGORY_PARAMETERS.clone(),
15✔
129
    ];
130

131
    let proposal_action_json_schema_content = &serde_json::from_str(include_str!(
15✔
132
        "./../../../../specs/definitions/signed_docs/docs/payload_schemas/proposal_submission_action.schema.json"
15✔
133
    ))
15✔
134
    .expect("Must be a valid json file");
15✔
135

136
    let proposal_action_json_schema =
15✔
137
        json_schema::JsonSchema::try_from(proposal_action_json_schema_content)
15✔
138
            .expect("Must be a valid json scheme file");
15✔
139

140
    Rules {
15✔
141
        id: IdRule,
15✔
142
        ver: VerRule,
15✔
143
        content_type: ContentTypeRule::Specified {
15✔
144
            exp: ContentType::Json,
15✔
145
        },
15✔
146
        content_encoding: ContentEncodingRule::Specified {
15✔
147
            exp: vec![ContentEncoding::Brotli],
15✔
148
            optional: false,
15✔
149
        },
15✔
150
        template: TemplateRule::NotSpecified,
15✔
151
        parameters: ParametersRule::Specified {
15✔
152
            allowed_type: parameters,
15✔
153
            optional: false,
15✔
154
        },
15✔
155
        doc_ref: RefRule::Specified {
15✔
156
            allowed_type: vec![PROPOSAL.clone()],
15✔
157
            multiple: false,
15✔
158
            optional: false,
15✔
159
        },
15✔
160
        reply: ReplyRule::NotSpecified,
15✔
161
        section: SectionRule::NotSpecified,
15✔
162
        collaborators: CollaboratorsRule::NotSpecified,
15✔
163
        content: ContentRule::StaticSchema(ContentSchema::Json(proposal_action_json_schema)),
15✔
164
        kid: SignatureKidRule {
15✔
165
            allowed_roles: [RoleId::Proposer].into_iter().collect(),
15✔
166
        },
15✔
167
        signature: SignatureRule { mutlisig: false },
15✔
168
        ownership: DocumentOwnershipRule {
15✔
169
            allow_collaborators: false,
15✔
170
        },
15✔
171
    }
15✔
172
}
15✔
173

174
/// `DOCUMENT_RULES` initialization function
175
#[allow(clippy::expect_used)]
176
fn document_rules_init() -> HashMap<DocType, Rules> {
15✔
177
    let mut document_rules_map: HashMap<DocType, Rules> = Rules::documents_rules()
15✔
178
        .expect("cannot fail to initialize validation rules")
15✔
179
        .collect();
15✔
180

181
    // TODO: remove this redefinitions of the validation rules after
182
    // `catalyst_signed_documents_rules!` macro would be fully finished
183
    document_rules_map.insert(PROPOSAL.clone(), proposal_rule());
15✔
184
    document_rules_map.insert(PROPOSAL_COMMENT.clone(), proposal_comment_rule());
15✔
185
    document_rules_map.insert(
15✔
186
        PROPOSAL_SUBMISSION_ACTION.clone(),
15✔
187
        proposal_submission_action_rule(),
15✔
188
    );
189

190
    document_rules_map
15✔
191
}
15✔
192

193
/// A comprehensive document type based validation of the `CatalystSignedDocument`.
194
/// Includes time based validation of the `id` and `ver` fields based on the provided
195
/// `future_threshold` and `past_threshold` threshold values (in seconds).
196
/// Return true if it is valid, otherwise return false.
197
///
198
/// # Errors
199
/// If `provider` returns error, fails fast throwing that error.
200
pub async fn validate<Provider>(
14✔
201
    doc: &CatalystSignedDocument,
14✔
202
    provider: &Provider,
14✔
203
) -> anyhow::Result<bool>
14✔
204
where
14✔
205
    Provider: CatalystSignedDocumentProvider + CatalystIdProvider,
14✔
206
{
14✔
207
    let Ok(doc_type) = doc.doc_type() else {
14✔
UNCOV
208
        doc.report().missing_field(
×
UNCOV
209
            "type",
×
210
            "Can't get a document type during the validation process",
×
211
        );
212
        return Ok(false);
×
213
    };
214

215
    let Some(rules) = DOCUMENT_RULES.get(doc_type) else {
14✔
216
        doc.report().invalid_value(
×
UNCOV
217
            "`type`",
×
UNCOV
218
            &doc.doc_type()?.to_string(),
×
UNCOV
219
            "Must be a known document type value",
×
UNCOV
220
            "Unsupported document type",
×
221
        );
UNCOV
222
        return Ok(false);
×
223
    };
224
    rules.check(doc, provider).await
14✔
225
}
14✔
226

227
#[cfg(test)]
228
mod tests {
229
    use crate::validator::document_rules_init;
230

231
    #[test]
232
    fn document_rules_init_test() {
1✔
233
        document_rules_init();
1✔
234
    }
1✔
235
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc