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

input-output-hk / catalyst-libs / 20228814125

15 Dec 2025 10:25AM UTC coverage: 67.821% (+0.6%) from 67.194%
20228814125

Pull #709

github

web-flow
Merge 8b06de251 into 9cb603e67
Pull Request #709: chore(rust/signed-doc): Making `tests/common` module as a public mod `catalyst_signed_doc::tests_utils`

72 of 73 new or added lines in 27 files covered. (98.63%)

1 existing line in 1 file now uncovered.

14507 of 21390 relevant lines covered (67.82%)

2807.1 hits per line

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

95.4
/rust/signed_doc/src/metadata/document_refs/mod.rs
1
//! Document references.
2

3
pub(crate) mod doc_locator;
4
mod doc_ref;
5
use std::{fmt::Display, ops::Deref};
6

7
use cbork_utils::{array::Array, decode_context::DecodeCtx};
8
pub use doc_locator::DocLocator;
9
pub use doc_ref::DocumentRef;
10
use minicbor::{Decode, Encode};
11

12
use crate::CompatibilityPolicy;
13

14
/// List of document reference instance.
15
#[derive(Clone, Debug, PartialEq, Hash, Eq)]
16
pub struct DocumentRefs(Vec<DocumentRef>);
17

18
impl Deref for DocumentRefs {
19
    type Target = Vec<DocumentRef>;
20

21
    fn deref(&self) -> &Self::Target {
903✔
22
        &self.0
903✔
23
    }
903✔
24
}
25

26
impl DocumentRefs {
27
    /// Returns true if provided `cbor` bytes is a valid old format.
28
    /// ```cddl
29
    /// old_format = [id, ver]
30
    /// ```
31
    /// Returns false if provided `cbor` bytes is a valid new format.
32
    /// ```cddl
33
    /// new_format = [ +[id, ver, cid] ]
34
    /// ```
35
    pub(crate) fn is_deprecated_cbor(cbor: &[u8]) -> Result<bool, minicbor::decode::Error> {
3✔
36
        let mut d = minicbor::Decoder::new(cbor);
3✔
37
        d.array()?;
3✔
38
        match d.datatype()? {
3✔
39
            // new_format = [ +[id, ver, cid] ]
40
            minicbor::data::Type::Array => Ok(false),
3✔
41
            // old_format = [id, ver]
42
            minicbor::data::Type::Tag => Ok(true),
×
43
            ty => Err(minicbor::decode::Error::type_mismatch(ty)),
×
44
        }
45
    }
3✔
46
}
47

48
/// Document reference error.
49
#[derive(Debug, Clone, thiserror::Error)]
50
pub enum DocRefError {
51
    /// Invalid string conversion
52
    #[error("Invalid string conversion: {0}")]
53
    StringConversion(String),
54
    /// Cannot decode hex.
55
    #[error("Cannot decode hex: {0}")]
56
    HexDecode(String),
57
}
58

59
impl From<Vec<DocumentRef>> for DocumentRefs {
60
    fn from(value: Vec<DocumentRef>) -> Self {
88✔
61
        DocumentRefs(value)
88✔
62
    }
88✔
63
}
64

65
impl Display for DocumentRefs {
66
    fn fmt(
8✔
67
        &self,
8✔
68
        f: &mut std::fmt::Formatter<'_>,
8✔
69
    ) -> std::fmt::Result {
8✔
70
        let items = self
8✔
71
            .0
8✔
72
            .iter()
8✔
73
            .map(|inner| format!("{inner}"))
8✔
74
            .collect::<Vec<_>>()
8✔
75
            .join(", ");
8✔
76
        write!(f, "[{items}]")
8✔
77
    }
8✔
78
}
79

80
impl Decode<'_, CompatibilityPolicy> for DocumentRefs {
81
    fn decode(
889✔
82
        d: &mut minicbor::Decoder<'_>,
889✔
83
        _policy: &mut CompatibilityPolicy,
889✔
84
    ) -> Result<Self, minicbor::decode::Error> {
889✔
85
        const CONTEXT: &str = "DocumentRefs decoding";
86

87
        // Old: [id, ver]
88
        // New: [ 1* [id, ver, locator] ]
89
        let outer_arr = Array::decode(d, &mut DecodeCtx::ArrayDeterministic)
889✔
90
            .map_err(|e| minicbor::decode::Error::message(format!("{CONTEXT}: {e}")))?;
889✔
91

92
        match outer_arr.as_slice() {
883✔
93
            [first, rest @ ..] => {
882✔
94
                match minicbor::Decoder::new(first).datatype()? {
882✔
95
                    // New structure inner part [id, ver, locator]
96
                    minicbor::data::Type::Array => {
97
                        let mut arr = vec![first];
851✔
98
                        arr.extend(rest);
851✔
99

100
                        let doc_refs = arr
851✔
101
                            .iter()
851✔
102
                            .map(|bytes| minicbor::Decoder::new(bytes).decode())
860✔
103
                            .collect::<Result<_, _>>()?;
851✔
104

105
                        Ok(DocumentRefs(doc_refs))
850✔
106
                    },
107
                    // Old structure (id, ver) - no longer supported as DocLocator requires a valid
108
                    // CID
109
                    minicbor::data::Type::Tag => {
110
                        Err(minicbor::decode::Error::message(format!(
30✔
111
                            "{CONTEXT}: Legacy document reference format (id, ver) without CID is no longer supported. \
30✔
112
                             DocLocator now requires a valid Content Identifier (CID)."
30✔
113
                        )))
30✔
114
                    },
115
                    other => {
1✔
116
                        Err(minicbor::decode::Error::message(format!(
1✔
117
                            "{CONTEXT}: Expected array of document reference, or tag of version and id, found {other}",
1✔
118
                        )))
1✔
119
                    },
120
                }
121
            },
122
            _ => {
123
                Err(minicbor::decode::Error::message(format!(
1✔
124
                    "{CONTEXT}: Empty array",
1✔
125
                )))
1✔
126
            },
127
        }
128
    }
889✔
129
}
130

131
impl Encode<()> for DocumentRefs {
132
    fn encode<W: minicbor::encode::Write>(
847✔
133
        &self,
847✔
134
        e: &mut minicbor::Encoder<W>,
847✔
135
        ctx: &mut (),
847✔
136
    ) -> Result<(), minicbor::encode::Error<W::Error>> {
847✔
137
        const CONTEXT: &str = "DocumentRefs encoding";
138
        if self.0.is_empty() {
847✔
139
            return Err(minicbor::encode::Error::message(format!(
×
140
                "{CONTEXT}: DocumentRefs cannot be empty"
×
141
            )));
×
142
        }
847✔
143
        e.array(
847✔
144
            self.0
847✔
145
                .len()
847✔
146
                .try_into()
847✔
147
                .map_err(|e| minicbor::encode::Error::message(format!("{CONTEXT}, {e}")))?,
847✔
148
        )?;
×
149

150
        for doc_ref in &self.0 {
1,702✔
151
            doc_ref.encode(e, ctx)?;
855✔
152
        }
153
        Ok(())
847✔
154
    }
847✔
155
}
156

157
mod serde_impl {
158
    //! `serde::Deserialize` and `serde::Serialize` trait implementations
159

160
    use super::{DocumentRef, DocumentRefs};
161

162
    /// A struct to support deserializing for both the old and new version of `ref`.
163
    #[derive(serde::Deserialize)]
164
    #[serde(untagged)]
165
    enum DocRefSerde {
166
        /// Old structure of document reference.
167
        Old(DocumentRef),
168
        /// New structure of document reference.
169
        New(Vec<DocumentRef>),
170
    }
171

172
    impl serde::Serialize for DocumentRefs {
173
        fn serialize<S>(
1✔
174
            &self,
1✔
175
            serializer: S,
1✔
176
        ) -> Result<S::Ok, S::Error>
1✔
177
        where
1✔
178
            S: serde::Serializer,
1✔
179
        {
180
            self.0.serialize(serializer)
1✔
181
        }
1✔
182
    }
183

184
    impl<'de> serde::Deserialize<'de> for DocumentRefs {
185
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
754✔
186
        where D: serde::Deserializer<'de> {
754✔
187
            match DocRefSerde::deserialize(deserializer)? {
754✔
188
                DocRefSerde::Old(v) => Ok(DocumentRefs(vec![v])),
×
189
                DocRefSerde::New(v) => Ok(DocumentRefs(v)),
754✔
190
            }
191
        }
754✔
192
    }
193
}
194

195
#[cfg(test)]
196
pub(crate) mod tests {
197

198
    use catalyst_types::uuid::{CborContext, UuidV7};
199
    use minicbor::{Decoder, Encoder};
200
    use test_case::test_case;
201

202
    use super::*;
203
    use crate::tests_utils::create_dummy_doc_ref;
204

205
    #[test_case(
206
        CompatibilityPolicy::Accept,
207
        {
208
            Encoder::new(Vec::new())
209
        } ;
210
        "Invalid empty CBOR bytes"
211
    )]
212
    #[test_case(
213
        CompatibilityPolicy::Accept,
214
        {
215
            let mut e = Encoder::new(Vec::new());
216
            e.array(0).unwrap();
217
            e
218
        } ;
219
        "Invalid empty CBOR array"
220
    )]
221
    #[test_case(
222
        CompatibilityPolicy::Fail,
223
        {
224
            let mut e = Encoder::new(Vec::new());
225
            e.array(2)
226
                .unwrap()
227
                .encode_with(UuidV7::new(), &mut CborContext::Tagged)
228
                .unwrap()
229
                .encode_with(UuidV7::new(), &mut CborContext::Tagged)
230
                .unwrap();
231
            e
232
        } ;
233
        "Valid array of two uuid v7 (old format), fail policy"
234
    )]
235
    #[test_case(
236
        CompatibilityPolicy::Accept,
237
        {
238
            let mut e = Encoder::new(Vec::new());
239
            e.array(2)
240
                .unwrap()
241
                .encode_with(UuidV7::new(), &mut CborContext::Untagged)
242
                .unwrap()
243
                .encode_with(UuidV7::new(), &mut CborContext::Untagged)
244
                .unwrap();
245
            e
246
        } ;
247
        "Invalid untagged uuids v7 (old format)"
248
    )]
249
    #[test_case(
250
        CompatibilityPolicy::Accept,
251
        {
252
            let doc_ref = create_dummy_doc_ref().unwrap();
253
            let mut e = Encoder::new(Vec::new());
254
            e.array(1)
255
                .unwrap()
256
                .array(3)
257
                .unwrap()
258
                .encode_with(*doc_ref.id(), &mut CborContext::Untagged)
259
                .unwrap()
260
                .encode_with(*doc_ref.ver(), &mut CborContext::Untagged)
261
                .unwrap()
262
                .encode(doc_ref.doc_locator().clone())
263
                .unwrap();
264
            e
265
        } ;
266
        "Invalid untagged uuid uuids v7 (new format)"
267
    )]
268
    fn test_invalid_cbor_decode(
5✔
269
        mut policy: CompatibilityPolicy,
5✔
270
        e: Encoder<Vec<u8>>,
5✔
271
    ) {
5✔
272
        assert!(
5✔
273
            DocumentRefs::decode(&mut Decoder::new(e.into_writer().as_slice()), &mut policy)
5✔
274
                .is_err()
5✔
275
        );
276
    }
5✔
277

278
    #[test_case(
279
        CompatibilityPolicy::Accept,
280
        |id, ver, doc_loc| {
1✔
281
            let mut e = Encoder::new(Vec::new());
1✔
282
            e.array(1)
1✔
283
                .unwrap()
1✔
284
                .array(3)
1✔
285
                .unwrap()
1✔
286
                .encode_with(id, &mut CborContext::Tagged)
1✔
287
                .unwrap()
1✔
288
                .encode_with(ver, &mut CborContext::Tagged)
1✔
289
                .unwrap()
1✔
290
                .encode(doc_loc)
1✔
291
                .unwrap();
1✔
292
            e
1✔
293
        } ;
1✔
294
        "Array of new doc ref (new format)"
295
    )]
296
    #[test_case(
297
        CompatibilityPolicy::Fail,
298
        |id, ver, doc_loc| {
1✔
299
            let mut e = Encoder::new(Vec::new());
1✔
300
            e.array(1)
1✔
301
                .unwrap()
1✔
302
                .array(3)
1✔
303
                .unwrap()
1✔
304
                .encode_with(id, &mut CborContext::Tagged)
1✔
305
                .unwrap()
1✔
306
                .encode_with(ver, &mut CborContext::Tagged)
1✔
307
                .unwrap()
1✔
308
                .encode(doc_loc)
1✔
309
                .unwrap();
1✔
310
            e
1✔
311
        } ;
1✔
312
        "Array of new doc ref (new format), fail policy"
313
    )]
314
    fn test_valid_cbor_decode(
2✔
315
        mut policy: CompatibilityPolicy,
2✔
316
        e_gen: impl FnOnce(UuidV7, UuidV7, DocLocator) -> Encoder<Vec<u8>>,
2✔
317
    ) {
2✔
318
        let doc_ref = create_dummy_doc_ref().unwrap();
2✔
319
        let e = e_gen(*doc_ref.id(), *doc_ref.ver(), doc_ref.doc_locator().clone());
2✔
320

321
        let doc_refs =
2✔
322
            DocumentRefs::decode(&mut Decoder::new(e.into_writer().as_slice()), &mut policy)
2✔
323
                .unwrap();
2✔
324
        assert_eq!(doc_refs.0, vec![doc_ref]);
2✔
325
    }
2✔
326

327
    #[test]
328
    fn test_json_valid_serde() {
1✔
329
        let doc_ref1 = create_dummy_doc_ref().unwrap();
1✔
330
        let doc_ref2 = create_dummy_doc_ref().unwrap();
1✔
331

332
        let refs = DocumentRefs(vec![doc_ref1, doc_ref2]);
1✔
333

334
        let json = serde_json::to_value(&refs).unwrap();
1✔
335
        let refs_from_json: DocumentRefs = serde_json::from_value(json).unwrap();
1✔
336

337
        assert_eq!(refs, refs_from_json);
1✔
338
    }
1✔
339

340
    #[test]
341
    fn test_deterministic_decoding() {
1✔
342
        let mut refs = vec![
1✔
343
            create_dummy_doc_ref().unwrap(),
1✔
344
            create_dummy_doc_ref().unwrap(),
1✔
345
        ];
346
        refs.sort_by(|a, b| {
1✔
347
            let a_bytes = {
1✔
348
                let mut e = Encoder::new(Vec::new());
1✔
349
                a.encode(&mut e, &mut ()).unwrap();
1✔
350
                e.into_writer()
1✔
351
            };
352
            let b_bytes = {
1✔
353
                let mut e = Encoder::new(Vec::new());
1✔
354
                b.encode(&mut e, &mut ()).unwrap();
1✔
355
                e.into_writer()
1✔
356
            };
357

358
            match a_bytes.len().cmp(&b_bytes.len()) {
1✔
359
                std::cmp::Ordering::Equal => a_bytes.as_slice().cmp(&b_bytes),
1✔
UNCOV
360
                other => other,
×
361
            }
362
        });
1✔
363

364
        let mut e = Encoder::new(Vec::new());
1✔
365
        refs.encode(&mut e, &mut ()).unwrap();
1✔
366

367
        let result = DocumentRefs::decode(
1✔
368
            &mut Decoder::new(e.into_writer().as_slice()),
1✔
369
            &mut CompatibilityPolicy::Fail,
1✔
370
        );
371
        assert!(result.is_ok());
1✔
372

373
        let mut e = Encoder::new(Vec::new());
1✔
374
        refs.reverse();
1✔
375
        refs.encode(&mut e, &mut ()).unwrap();
1✔
376

377
        let result = DocumentRefs::decode(
1✔
378
            &mut Decoder::new(e.into_writer().as_slice()),
1✔
379
            &mut CompatibilityPolicy::Fail,
1✔
380
        );
381
        assert!(result.is_err());
1✔
382
    }
1✔
383
}
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