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

input-output-hk / catalyst-libs / 15831300667

23 Jun 2025 05:44PM UTC coverage: 67.405%. First build
15831300667

Pull #380

github

web-flow
Merge 5ef5e6461 into 6a1be0bd9
Pull Request #380: chore(rust/signed-doc): Cleanup Catalyst Signed Document `Builder`, make it type safe, add special test builder (2).

479 of 488 new or added lines in 14 files covered. (98.16%)

12604 of 18699 relevant lines covered (67.4%)

2197.73 hits per line

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

92.96
/rust/signed_doc/src/builder.rs
1
//! Catalyst Signed Document Builder.
2
use catalyst_types::{catalyst_id::CatalystId, problem_report::ProblemReport};
3

4
use crate::{
5
    signature::{tbs_data, Signature},
6
    CatalystSignedDocument, Content, ContentType, Metadata, Signatures,
7
};
8

9
/// Catalyst Signed Document Builder.
10
#[derive(Debug)]
11
pub struct Builder {
12
    /// metadata
13
    metadata: Metadata,
14
    /// content
15
    content: Content,
16
    /// signatures
17
    signatures: Signatures,
18
}
19

20
impl Default for Builder {
NEW
21
    fn default() -> Self {
×
NEW
22
        Self::new()
×
NEW
23
    }
×
24
}
25

26
impl Builder {
27
    /// Start building a signed document
28
    #[must_use]
29
    pub fn new() -> Self {
154✔
30
        Self {
154✔
31
            metadata: Metadata::default(),
154✔
32
            content: Content::default(),
154✔
33
            signatures: Signatures::default(),
154✔
34
        }
154✔
35
    }
154✔
36

37
    /// Set document metadata in JSON format
38
    /// Collect problem report if some fields are missing.
39
    ///
40
    /// # Errors
41
    /// - Fails if it is invalid metadata fields JSON object.
42
    pub fn with_json_metadata(mut self, json: serde_json::Value) -> anyhow::Result<Self> {
138✔
43
        self.metadata = Metadata::from_json(json, &ProblemReport::new(""));
138✔
44
        Ok(self)
138✔
45
    }
138✔
46

47
    /// Set the provided JSON content, applying already set `content-encoding`.
48
    ///
49
    /// # Errors
50
    ///  - Verifies that `content-type` field is set to JSON
51
    ///  - Cannot serialize provided JSON
52
    ///  - Compression failure
53
    pub fn with_json_content(mut self, json: &serde_json::Value) -> anyhow::Result<Self> {
55✔
54
        anyhow::ensure!(
55✔
55
            self.metadata.content_type()? == ContentType::Json,
55✔
NEW
56
            "Already set metadata field `content-type` is not JSON value"
×
57
        );
58

59
        let content = serde_json::to_vec(&json)?;
55✔
60
        if let Some(encoding) = self.metadata.content_encoding() {
55✔
61
            self.content = encoding.encode(&content)?.into();
30✔
62
        } else {
25✔
63
            self.content = content.into();
25✔
64
        }
25✔
65

66
        Ok(self)
55✔
67
    }
55✔
68

69
    /// Set decoded (original) document content bytes
70
    ///
71
    /// # Errors
72
    ///  - Compression failure
73
    pub fn with_decoded_content(mut self, decoded: Vec<u8>) -> anyhow::Result<Self> {
30✔
74
        if let Some(encoding) = self.metadata.content_encoding() {
30✔
75
            self.content = encoding.encode(&decoded)?.into();
3✔
76
        } else {
27✔
77
            self.content = decoded.into();
27✔
78
        }
27✔
79
        Ok(self)
30✔
80
    }
30✔
81

82
    /// Add a signature to the document
83
    ///
84
    /// # Errors
85
    ///
86
    /// Fails if a `CatalystSignedDocument` cannot be created due to missing metadata or
87
    /// content, due to malformed data, or when the signed document cannot be
88
    /// converted into `coset::CoseSign`.
89
    pub fn add_signature(
12✔
90
        mut self, sign_fn: impl FnOnce(Vec<u8>) -> Vec<u8>, kid: CatalystId,
12✔
91
    ) -> anyhow::Result<Self> {
12✔
92
        if kid.is_id() {
12✔
93
            anyhow::bail!("Provided kid should be in a uri format, kid: {kid}");
×
94
        }
12✔
95
        let data_to_sign = tbs_data(&kid, &self.metadata, &self.content)?;
12✔
96
        let sign_bytes = sign_fn(data_to_sign);
12✔
97
        self.signatures.push(Signature::new(kid, sign_bytes));
12✔
98

12✔
99
        Ok(self)
12✔
100
    }
12✔
101

102
    /// Build a signed document with the collected error report.
103
    /// Could provide an invalid document.
104
    ///
105
    /// # Panics
106
    ///  Should not panic
107
    #[must_use]
108
    #[allow(
109
        clippy::unwrap_used,
110
        reason = "At this point all the data MUST be correctly encodable, and the final prepared bytes MUST be correctly decodable as a CatalystSignedDocument object."
111
    )]
112
    pub fn build(self) -> CatalystSignedDocument {
161✔
113
        let mut e = minicbor::Encoder::new(Vec::new());
161✔
114
        // COSE_Sign tag
161✔
115
        // <!https://datatracker.ietf.org/doc/html/rfc8152#page-9>
161✔
116
        e.tag(minicbor::data::Tag::new(98)).unwrap();
161✔
117
        e.array(4).unwrap();
161✔
118
        // protected headers (metadata fields)
161✔
119
        e.bytes(minicbor::to_vec(&self.metadata).unwrap().as_slice())
161✔
120
            .unwrap();
161✔
121
        // empty unprotected headers
161✔
122
        e.map(0).unwrap();
161✔
123
        // content
161✔
124
        e.encode(&self.content).unwrap();
161✔
125
        // signatures
161✔
126
        e.encode(self.signatures).unwrap();
161✔
127

161✔
128
        CatalystSignedDocument::try_from(e.into_writer().as_slice()).unwrap()
161✔
129
    }
161✔
130
}
131

132
impl From<&CatalystSignedDocument> for Builder {
133
    fn from(value: &CatalystSignedDocument) -> Self {
7✔
134
        Self {
7✔
135
            metadata: value.inner.metadata.clone(),
7✔
136
            content: value.inner.content.clone(),
7✔
137
            signatures: value.inner.signatures.clone(),
7✔
138
        }
7✔
139
    }
7✔
140
}
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