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

input-output-hk / catalyst-libs / 19934578682

04 Dec 2025 03:37PM UTC coverage: 67.785% (-0.05%) from 67.835%
19934578682

Pull #680

github

web-flow
Merge d7f554c56 into f08d044b7
Pull Request #680: feat(rust/signed-doc): Catalyst Signed Documents extended validation capabilities

252 of 259 new or added lines in 27 files covered. (97.3%)

7 existing lines in 2 files now uncovered.

13997 of 20649 relevant lines covered (67.79%)

2708.18 hits per line

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

86.73
/rust/signed_doc/src/validator/rules/content_encoding.rs
1
//! `content-encoding` rule type impl.
2

3
use std::string::ToString;
4

5
use catalyst_signed_doc_spec::is_required::IsRequired;
6

7
use crate::{
8
    CatalystSignedDocument, metadata::ContentEncoding,
9
    providers::CatalystSignedDocumentAndCatalystIdProvider,
10
    validator::CatalystSignedDocumentValidationRule,
11
};
12

13
/// `content-encoding` field validation rule.
14
#[derive(Debug)]
15
pub(crate) enum ContentEncodingRule {
16
    /// Content Encoding field is optionally present in the document.
17
    Specified {
18
        /// expected `content-encoding` field.
19
        exp: Vec<ContentEncoding>,
20
        /// optional flag for the `content-encoding` field.
21
        optional: bool,
22
    },
23
    /// Content Encoding field must not be present in the document.
24
    NotSpecified,
25
}
26

27
#[async_trait::async_trait]
28
impl CatalystSignedDocumentValidationRule for ContentEncodingRule {
29
    async fn check(
30
        &self,
31
        doc: &CatalystSignedDocument,
32
        _provider: &dyn CatalystSignedDocumentAndCatalystIdProvider,
33
    ) -> anyhow::Result<bool> {
105✔
34
        Ok(self.check_inner(doc))
35
    }
105✔
36
}
37

38
impl ContentEncodingRule {
39
    /// Create a new rule from specs.
40
    pub(crate) fn new(
2,247✔
41
        spec: &catalyst_signed_doc_spec::headers::content_encoding::ContentEncoding
2,247✔
42
    ) -> anyhow::Result<Self> {
2,247✔
43
        if let IsRequired::Excluded = spec.required {
2,247✔
44
            anyhow::ensure!(
×
45
                spec.value.is_none(),
×
46
                "'content type' field must not exist when 'required' is 'excluded'"
×
47
            );
48
            return Ok(Self::NotSpecified);
×
49
        }
2,247✔
50

51
        let optional = IsRequired::Optional == spec.required;
2,247✔
52

53
        let exp = spec
2,247✔
54
            .value
2,247✔
55
            .as_ref()
2,247✔
56
            .ok_or(anyhow::anyhow!("'content-encoding' field must have value "))?
2,247✔
57
            .iter()
2,247✔
58
            .flat_map(|encoding| encoding.parse())
2,247✔
59
            .collect();
2,247✔
60

61
        Ok(Self::Specified { exp, optional })
2,247✔
62
    }
2,247✔
63

64
    /// Field validation rule
65
    fn check_inner(
111✔
66
        &self,
111✔
67
        doc: &CatalystSignedDocument,
111✔
68
    ) -> bool {
111✔
69
        let context = "Content Encoding Rule check";
111✔
70
        match self {
111✔
71
            Self::NotSpecified => {
72
                if let Some(content_encoding) = doc.doc_content_encoding() {
2✔
73
                    doc.report().unknown_field(
1✔
74
                        "content-encoding",
1✔
75
                        &content_encoding.to_string(),
1✔
76
                        &format!(
1✔
77
                            "{context}, document does not expect to have a content-encoding field"
1✔
78
                        ),
1✔
79
                    );
80
                    return false;
1✔
81
                }
1✔
82
            },
83
            Self::Specified { exp, optional } => {
109✔
84
                if let Some(content_encoding) = doc.doc_content_encoding() {
109✔
85
                    if !exp.contains(&content_encoding) {
93✔
86
                        doc.report().invalid_value(
×
87
                            "content-encoding",
×
88
                            content_encoding.to_string().as_str(),
×
89
                            &exp.iter()
×
90
                                .map(ToString::to_string)
×
91
                                .collect::<Vec<_>>()
×
92
                                .join(", "),
×
93
                            "Invalid document content-encoding value",
×
94
                        );
NEW
95
                        return false;
×
96
                    }
93✔
97
                    if content_encoding.decode(doc.encoded_content()).is_err() {
93✔
98
                        doc.report().invalid_value(
16✔
99
                            "payload",
16✔
100
                            &hex::encode(doc.encoded_content()),
16✔
101
                            content_encoding.to_string().as_str(),
16✔
102
                            "Document content is not decodable with the expected content-encoding",
16✔
103
                        );
104
                        return false;
16✔
105
                    }
77✔
106
                } else if !optional {
16✔
107
                    doc.report().missing_field(
1✔
108
                        "content-encoding",
1✔
109
                        "Document must have a content-encoding field",
1✔
110
                    );
111
                    return false;
1✔
112
                }
15✔
113
            },
114
        }
115
        true
93✔
116
    }
111✔
117
}
118

119
#[cfg(test)]
120
mod tests {
121
    use super::*;
122
    use crate::{builder::tests::Builder, metadata::SupportedField};
123

124
    #[test]
125
    fn content_encoding_is_specified_rule_test() {
1✔
126
        let content_encoding = ContentEncoding::Brotli;
1✔
127

128
        let rule = ContentEncodingRule::Specified {
1✔
129
            exp: vec![content_encoding],
1✔
130
            optional: true,
1✔
131
        };
1✔
132

133
        let doc = Builder::new()
1✔
134
            .with_metadata_field(SupportedField::ContentEncoding(content_encoding))
1✔
135
            .with_content(content_encoding.encode(&[1, 2, 3]).unwrap())
1✔
136
            .build();
1✔
137
        assert!(rule.check_inner(&doc));
1✔
138

139
        // empty content (empty bytes) could not be brotli decoded
140
        let doc = Builder::new()
1✔
141
            .with_metadata_field(SupportedField::ContentEncoding(content_encoding))
1✔
142
            .build();
1✔
143
        assert!(!rule.check_inner(&doc));
1✔
144

145
        let doc = Builder::new().build();
1✔
146
        assert!(rule.check_inner(&doc));
1✔
147

148
        let rule = ContentEncodingRule::Specified {
1✔
149
            exp: vec![content_encoding],
1✔
150
            optional: false,
1✔
151
        };
1✔
152
        assert!(!rule.check_inner(&doc));
1✔
153
    }
1✔
154

155
    #[test]
156
    fn content_encoding_is_not_specified_rule_test() {
1✔
157
        let content_encoding = ContentEncoding::Brotli;
1✔
158

159
        let rule = ContentEncodingRule::NotSpecified;
1✔
160

161
        // With Brotli content encoding
162
        let doc = Builder::new()
1✔
163
            .with_metadata_field(SupportedField::ContentEncoding(content_encoding))
1✔
164
            .build();
1✔
165
        assert!(!rule.check_inner(&doc));
1✔
166

167
        // No content encoding
168
        let doc = Builder::new().build();
1✔
169
        assert!(rule.check_inner(&doc));
1✔
170
    }
1✔
171
}
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