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

input-output-hk / catalyst-libs / 16646523141

31 Jul 2025 10:28AM UTC coverage: 67.341% (+3.8%) from 63.544%
16646523141

push

github

web-flow
feat(rust/signed-doc): Implement new Catalyst Signed Doc (#338)

* chore: add new line to open pr

Signed-off-by: bkioshn <bkioshn@gmail.com>

* chore: revert

Signed-off-by: bkioshn <bkioshn@gmail.com>

* feat(rust/signed-doc): add new type `DocType` (#339)

* feat(signed-doc): add new type DocType

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): add conversion policy

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): doc type

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): doc type error

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): seperate test

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): format

Signed-off-by: bkioshn <bkioshn@gmail.com>

---------

Signed-off-by: bkioshn <bkioshn@gmail.com>

* feat(rust/signed-doc): Add initial decoding tests for the Catalyst Signed Documents (#349)

* wip

* wip

* fix fmt

* fix spelling

* fix clippy

* fix(rust/signed-doc): Apply new `DocType` (#347)

* feat(signed-doc): add new type DocType

Signed-off-by: bkioshn <bkioshn@gmail.com>

* wip: apply doctype

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): add more function to DocType

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): map old doctype to new doctype

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): add eq to uuidv4

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): fix validator

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): minor fixes

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(catalyst-types): add hash to uuidv4

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): decoding test

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): doctype

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): minor fixes

Signed-off-by: bkioshn <bkioshn@gmail.com>

* chore(sign-doc): fix comment

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(catalyst-types): add froms... (continued)

2453 of 2675 new or added lines in 38 files covered. (91.7%)

19 existing lines in 7 files now uncovered.

11312 of 16798 relevant lines covered (67.34%)

2525.16 hits per line

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

89.66
/rust/signed_doc/src/validator/rules/content_type.rs
1
//! `content-type` rule type impl.
2

3
use crate::{metadata::ContentType, CatalystSignedDocument};
4

5
/// `content-type` field validation rule
6
#[derive(Clone, Debug, PartialEq)]
7
pub(crate) struct ContentTypeRule {
8
    /// expected `content-type` field
9
    pub(crate) exp: ContentType,
10
}
11

12
impl ContentTypeRule {
13
    /// Field validation rule
14
    #[allow(clippy::unused_async)]
15
    pub(crate) async fn check(&self, doc: &CatalystSignedDocument) -> anyhow::Result<bool> {
25✔
16
        let Ok(content_type) = doc.doc_content_type() else {
25✔
17
            doc.report().missing_field(
1✔
18
                "content-type",
1✔
19
                "Cannot get a content type field during the field validation",
1✔
20
            );
21
            return Ok(false);
1✔
22
        };
23
        if content_type != self.exp {
24✔
UNCOV
24
            doc.report().invalid_value(
×
UNCOV
25
                "content-type",
×
UNCOV
26
                content_type.to_string().as_str(),
×
UNCOV
27
                self.exp.to_string().as_str(),
×
UNCOV
28
                "Invalid Document content-type value",
×
29
            );
UNCOV
30
            return Ok(false);
×
31
        }
24✔
32
        let Ok(content) = doc.decoded_content() else {
24✔
NEW
33
            doc.report().functional_validation(
×
NEW
34
                "Invalid Document content, cannot get decoded bytes",
×
UNCOV
35
                "Cannot get a document content during the content type field validation",
×
36
            );
UNCOV
37
            return Ok(false);
×
38
        };
39
        if self.validate(&content).is_err() {
24✔
40
            doc.report().invalid_value(
8✔
41
                "payload",
8✔
42
                &hex::encode(content),
8✔
43
                &format!("Invalid Document content, should {content_type} encodable"),
8✔
44
                "Invalid Document content",
8✔
45
            );
46
            return Ok(false);
8✔
47
        }
16✔
48

49
        Ok(true)
16✔
50
    }
25✔
51

52
    /// Validates the provided `content` bytes to be a defined `ContentType`.
53
    fn validate(&self, content: &[u8]) -> anyhow::Result<()> {
24✔
54
        match self.exp {
24✔
55
            ContentType::Json => {
56
                if let Err(e) = serde_json::from_slice::<&serde_json::value::RawValue>(content) {
18✔
57
                    anyhow::bail!("Invalid {} content: {e}", self.exp)
3✔
58
                }
15✔
59
            },
60
            ContentType::Cddl => {
61
                // TODO: not implemented yet
NEW
62
                anyhow::bail!("`application/cddl` is valid but unavailable yet")
×
63
            },
64
            ContentType::Cbor => {
65
                let mut decoder = minicbor::Decoder::new(content);
6✔
66

67
                decoder.skip()?;
6✔
68

69
                if decoder.position() != content.len() {
2✔
70
                    anyhow::bail!("Unused bytes remain in the input after decoding")
1✔
71
                }
1✔
72
            },
73
            ContentType::JsonSchema => {
74
                // TODO: not implemented yet
NEW
75
                anyhow::bail!("`application/json+schema` is valid but unavailable yet")
×
76
            },
77
        }
78
        Ok(())
16✔
79
    }
24✔
80
}
81

82
#[cfg(test)]
83
mod tests {
84
    use super::*;
85
    use crate::{builder::tests::Builder, metadata::SupportedField};
86

87
    #[tokio::test]
88
    async fn cbor_with_trailing_bytes_test() {
1✔
89
        // valid cbor: {1: 2} but with trailing 0xff
90
        let mut buf = Vec::new();
1✔
91
        let mut enc = minicbor::Encoder::new(&mut buf);
1✔
92
        enc.map(1).unwrap().u8(1).unwrap().u8(2).unwrap();
1✔
93
        buf.push(0xFF); // extra byte
1✔
94

95
        let cbor_rule = ContentTypeRule {
1✔
96
            exp: ContentType::Cbor,
1✔
97
        };
1✔
98

99
        let doc = Builder::new()
1✔
100
            .with_metadata_field(SupportedField::ContentType(cbor_rule.exp))
1✔
101
            .with_content(buf)
1✔
102
            .build();
1✔
103

104
        assert!(matches!(cbor_rule.check(&doc).await, Ok(false)));
1✔
105
    }
1✔
106

107
    #[tokio::test]
108
    async fn malformed_cbor_bytes_test() {
1✔
109
        // 0xa2 means a map with 2 key-value pairs, but we only give 1 key
110
        let invalid_bytes = &[0xA2, 0x01];
1✔
111

112
        let cbor_rule = ContentTypeRule {
1✔
113
            exp: ContentType::Cbor,
1✔
114
        };
1✔
115

116
        let doc = Builder::new()
1✔
117
            .with_metadata_field(SupportedField::ContentType(cbor_rule.exp))
1✔
118
            .with_content(invalid_bytes.into())
1✔
119
            .build();
1✔
120

121
        assert!(matches!(cbor_rule.check(&doc).await, Ok(false)));
1✔
122
    }
1✔
123

124
    #[tokio::test]
125
    async fn content_type_cbor_rule_test() {
1✔
126
        let cbor_rule = ContentTypeRule {
1✔
127
            exp: ContentType::Cbor,
1✔
128
        };
1✔
129

130
        // with json bytes
131
        let doc = Builder::new()
1✔
132
            .with_metadata_field(SupportedField::ContentType(cbor_rule.exp))
1✔
133
            .with_content(serde_json::to_vec(&serde_json::json!({})).unwrap())
1✔
134
            .build();
1✔
135
        assert!(matches!(cbor_rule.check(&doc).await, Ok(false)));
1✔
136

137
        // with cbor bytes
138
        let doc = Builder::new()
1✔
139
            .with_metadata_field(SupportedField::ContentType(cbor_rule.exp))
1✔
140
            .with_content(minicbor::to_vec(minicbor::data::Token::Null).unwrap())
1✔
141
            .build();
1✔
142
        assert!(matches!(cbor_rule.check(&doc).await, Ok(true)));
1✔
143

144
        // without content
145
        let doc = Builder::new()
1✔
146
            .with_metadata_field(SupportedField::ContentType(cbor_rule.exp))
1✔
147
            .build();
1✔
148
        assert!(matches!(cbor_rule.check(&doc).await, Ok(false)));
1✔
149

150
        // with empty content
151
        let doc = Builder::new()
1✔
152
            .with_metadata_field(SupportedField::ContentType(cbor_rule.exp))
1✔
153
            .build();
1✔
154
        assert!(matches!(cbor_rule.check(&doc).await, Ok(false)));
1✔
155
    }
1✔
156

157
    #[tokio::test]
158
    async fn content_type_json_rule_test() {
1✔
159
        let json_rule = ContentTypeRule {
1✔
160
            exp: ContentType::Json,
1✔
161
        };
1✔
162

163
        // with json bytes
164
        let doc = Builder::new()
1✔
165
            .with_metadata_field(SupportedField::ContentType(json_rule.exp))
1✔
166
            .with_content(serde_json::to_vec(&serde_json::json!({})).unwrap())
1✔
167
            .build();
1✔
168
        assert!(matches!(json_rule.check(&doc).await, Ok(true)));
1✔
169

170
        // with cbor bytes
171
        let doc = Builder::new()
1✔
172
            .with_metadata_field(SupportedField::ContentType(json_rule.exp))
1✔
173
            .with_content(minicbor::to_vec(minicbor::data::Token::Null).unwrap())
1✔
174
            .build();
1✔
175
        assert!(matches!(json_rule.check(&doc).await, Ok(false)));
1✔
176

177
        // without content
178
        let doc = Builder::new()
1✔
179
            .with_metadata_field(SupportedField::ContentType(json_rule.exp))
1✔
180
            .build();
1✔
181
        assert!(matches!(json_rule.check(&doc).await, Ok(false)));
1✔
182

183
        // with empty content
184
        let doc = Builder::new()
1✔
185
            .with_metadata_field(SupportedField::ContentType(json_rule.exp))
1✔
186
            .build();
1✔
187
        assert!(matches!(json_rule.check(&doc).await, Ok(false)));
1✔
188

189
        let doc = Builder::new().build();
1✔
190
        assert!(matches!(json_rule.check(&doc).await, Ok(false)));
1✔
191
    }
1✔
192
}
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