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

input-output-hk / catalyst-libs / 17602891511

10 Sep 2025 04:08AM UTC coverage: 68.637%. First build
17602891511

Pull #547

github

web-flow
Merge 3b5b47ab4 into 6e806ce0d
Pull Request #547: feat(rust/signed-doc): Add a new separate crate `catalyst-signed-doc-spec`

144 of 160 new or added lines in 7 files covered. (90.0%)

13608 of 19826 relevant lines covered (68.64%)

2680.28 hits per line

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

87.13
/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::{metadata::ContentEncoding, CatalystSignedDocument};
8

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

23
impl ContentEncodingRule {
24
    /// Create a new rule from specs.
25
    pub(crate) fn new(
336✔
26
        spec: &catalyst_signed_doc_spec::headers::content_encoding::ContentEncoding
336✔
27
    ) -> anyhow::Result<Self> {
336✔
28
        if let IsRequired::Excluded = spec.required {
336✔
29
            anyhow::ensure!(
16✔
30
                spec.value.is_none(),
16✔
NEW
31
                "'content type' field must not exist when 'required' is 'excluded'"
×
32
            );
33
            return Ok(Self::NotSpecified);
16✔
34
        }
320✔
35

36
        if let IsRequired::Yes = spec.required {
320✔
NEW
37
            anyhow::ensure!(
×
NEW
38
                spec.value.is_some(),
×
NEW
39
                "'content-encoding' field must have value when 'required' is 'yes'"
×
40
            );
41
        }
320✔
42

43
        let optional = IsRequired::Optional == spec.required;
320✔
44

45
        let exp = spec
320✔
46
            .value
320✔
47
            .as_ref()
320✔
48
            .ok_or(anyhow::anyhow!("'content-encoding' field must have value "))?
320✔
49
            .iter()
320✔
50
            .flat_map(|encoding| encoding.parse())
320✔
51
            .collect();
320✔
52

53
        Ok(Self::Specified { exp, optional })
320✔
54
    }
336✔
55

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

112
#[cfg(test)]
113
mod tests {
114
    use super::*;
115
    use crate::{builder::tests::Builder, metadata::SupportedField};
116

117
    #[tokio::test]
118
    async fn content_encoding_is_specified_rule_test() {
1✔
119
        let content_encoding = ContentEncoding::Brotli;
1✔
120

121
        let rule = ContentEncodingRule::Specified {
1✔
122
            exp: vec![content_encoding],
1✔
123
            optional: true,
1✔
124
        };
1✔
125

126
        let doc = Builder::new()
1✔
127
            .with_metadata_field(SupportedField::ContentEncoding(content_encoding))
1✔
128
            .with_content(content_encoding.encode(&[1, 2, 3]).unwrap())
1✔
129
            .build();
1✔
130
        assert!(rule.check(&doc).await.unwrap());
1✔
131

132
        // empty content (empty bytes) could not be brotli decoded
133
        let doc = Builder::new()
1✔
134
            .with_metadata_field(SupportedField::ContentEncoding(content_encoding))
1✔
135
            .build();
1✔
136
        assert!(!rule.check(&doc).await.unwrap());
1✔
137

138
        let doc = Builder::new().build();
1✔
139
        assert!(rule.check(&doc).await.unwrap());
1✔
140

141
        let rule = ContentEncodingRule::Specified {
1✔
142
            exp: vec![content_encoding],
1✔
143
            optional: false,
1✔
144
        };
1✔
145
        assert!(!rule.check(&doc).await.unwrap());
1✔
146
    }
1✔
147

148
    #[tokio::test]
149
    async fn content_encoding_is_not_specified_rule_test() {
1✔
150
        let content_encoding = ContentEncoding::Brotli;
1✔
151

152
        let rule = ContentEncodingRule::NotSpecified;
1✔
153

154
        // With Brotli content encoding
155
        let doc = Builder::new()
1✔
156
            .with_metadata_field(SupportedField::ContentEncoding(content_encoding))
1✔
157
            .build();
1✔
158
        assert!(!rule.check(&doc).await.unwrap());
1✔
159

160
        // No content encoding
161
        let doc = Builder::new().build();
1✔
162
        assert!(rule.check(&doc).await.unwrap());
1✔
163
    }
1✔
164
}
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