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

input-output-hk / catalyst-libs / 19153566703

06 Nov 2025 11:58PM UTC coverage: 68.421% (-0.02%) from 68.442%
19153566703

Pull #628

github

web-flow
Merge fdbbd67b6 into b703f0134
Pull Request #628: chore(general): Migrate to the 2024 rust edition

321 of 463 new or added lines in 46 files covered. (69.33%)

11 existing lines in 5 files now uncovered.

13921 of 20346 relevant lines covered (68.42%)

2897.33 hits per line

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

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

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

7
use catalyst_types::uuid::{CborContext, UuidV7};
8
use cbork_utils::{array::Array, decode_context::DecodeCtx};
9
pub use doc_locator::DocLocator;
10
pub use doc_ref::DocumentRef;
11
use minicbor::{Decode, Encode};
12
use tracing::warn;
13

14
use crate::CompatibilityPolicy;
15

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

20
impl Deref for DocumentRefs {
21
    type Target = Vec<DocumentRef>;
22

23
    fn deref(&self) -> &Self::Target {
413✔
24
        &self.0
413✔
25
    }
413✔
26
}
27

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

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

61
impl From<Vec<DocumentRef>> for DocumentRefs {
62
    fn from(value: Vec<DocumentRef>) -> Self {
75✔
63
        DocumentRefs(value)
75✔
64
    }
75✔
65
}
66

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

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

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

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

102
                        let doc_refs = arr
423✔
103
                            .iter()
423✔
104
                            .map(|bytes| minicbor::Decoder::new(bytes).decode())
431✔
105
                            .collect::<Result<_, _>>()?;
423✔
106

107
                        Ok(DocumentRefs(doc_refs))
422✔
108
                    },
109
                    // Old structure (id, ver)
110
                    minicbor::data::Type::Tag => {
111
                        match policy {
32✔
112
                            CompatibilityPolicy::Accept | CompatibilityPolicy::Warn => {
113
                                if matches!(policy, CompatibilityPolicy::Warn) {
31✔
114
                                    warn!(
1✔
NEW
115
                                        "{CONTEXT}: Conversion of document reference, id and version, to list of document reference with doc locator"
×
116
                                    );
117
                                }
30✔
118
                                if rest.len() != 1 {
31✔
119
                                    return Err(minicbor::decode::Error::message(format!(
22✔
120
                                        "{CONTEXT}: Must have exactly 2 elements inside array for document reference id and document reference version, found {}",
22✔
121
                                        rest.len().overflowing_add(1).0
22✔
122
                                    )));
22✔
123
                                }
9✔
124

125
                                let id = UuidV7::decode(
9✔
126
                                    &mut minicbor::Decoder::new(first),
9✔
127
                                    &mut CborContext::Tagged,
9✔
128
                                )
129
                                .map_err(|e| e.with_message("Invalid ID UUIDv7"))?;
9✔
130
                                let ver = rest
9✔
131
                                    .first()
9✔
132
                                    .map(|ver| UuidV7::decode(&mut minicbor::Decoder::new(ver), &mut CborContext::Tagged).map_err(|e| {
9✔
133
                                        e.with_message("Invalid Ver UUIDv7")
×
134
                                    }))
×
135
                                    .transpose()?
9✔
136
                                    .ok_or_else(|| minicbor::decode::Error::message(format!("{CONTEXT}: Missing document reference version after document reference id")))?;
9✔
137

138
                                Ok(DocumentRefs(vec![DocumentRef::new(
9✔
139
                                    id,
9✔
140
                                    ver,
9✔
141
                                    // If old implementation is used, the locator will be empty
9✔
142
                                    DocLocator::default(),
9✔
143
                                )]))
9✔
144
                            },
145
                            CompatibilityPolicy::Fail => {
146
                                Err(minicbor::decode::Error::message(format!(
1✔
147
                                    "{CONTEXT}: Conversion of document reference id and version to list of document reference with doc locator is not allowed"
1✔
148
                                )))
1✔
149
                            },
150
                        }
151
                    },
152
                    other => {
5✔
153
                        Err(minicbor::decode::Error::message(format!(
5✔
154
                            "{CONTEXT}: Expected array of document reference, or tag of version and id, found {other}",
5✔
155
                        )))
5✔
156
                    },
157
                }
158
            },
159
            _ => {
160
                Err(minicbor::decode::Error::message(format!(
1✔
161
                    "{CONTEXT}: Empty array",
1✔
162
                )))
1✔
163
            },
164
        }
165
    }
462✔
166
}
167

168
impl Encode<()> for DocumentRefs {
169
    fn encode<W: minicbor::encode::Write>(
420✔
170
        &self,
420✔
171
        e: &mut minicbor::Encoder<W>,
420✔
172
        ctx: &mut (),
420✔
173
    ) -> Result<(), minicbor::encode::Error<W::Error>> {
420✔
174
        const CONTEXT: &str = "DocumentRefs encoding";
175
        if self.0.is_empty() {
420✔
176
            return Err(minicbor::encode::Error::message(format!(
×
177
                "{CONTEXT}: DocumentRefs cannot be empty"
×
178
            )));
×
179
        }
420✔
180
        e.array(
420✔
181
            self.0
420✔
182
                .len()
420✔
183
                .try_into()
420✔
184
                .map_err(|e| minicbor::encode::Error::message(format!("{CONTEXT}, {e}")))?,
420✔
185
        )?;
×
186

187
        for doc_ref in &self.0 {
848✔
188
            doc_ref.encode(e, ctx)?;
428✔
189
        }
190
        Ok(())
420✔
191
    }
420✔
192
}
193

194
mod serde_impl {
195
    //! `serde::Deserialize` and `serde::Serialize` trait implementations
196

197
    use super::{DocumentRef, DocumentRefs};
198

199
    /// A struct to support deserializing for both the old and new version of `ref`.
200
    #[derive(serde::Deserialize)]
201
    #[serde(untagged)]
202
    enum DocRefSerde {
203
        /// Old structure of document reference.
204
        Old(DocumentRef),
205
        /// New structure of document reference.
206
        New(Vec<DocumentRef>),
207
    }
208

209
    impl serde::Serialize for DocumentRefs {
210
        fn serialize<S>(
4✔
211
            &self,
4✔
212
            serializer: S,
4✔
213
        ) -> Result<S::Ok, S::Error>
4✔
214
        where
4✔
215
            S: serde::Serializer,
4✔
216
        {
217
            self.0.serialize(serializer)
4✔
218
        }
4✔
219
    }
220

221
    impl<'de> serde::Deserialize<'de> for DocumentRefs {
222
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
341✔
223
        where D: serde::Deserializer<'de> {
341✔
224
            match DocRefSerde::deserialize(deserializer)? {
341✔
225
                DocRefSerde::Old(v) => Ok(DocumentRefs(vec![v])),
335✔
226
                DocRefSerde::New(v) => Ok(DocumentRefs(v)),
6✔
227
            }
228
        }
341✔
229
    }
230
}
231

232
#[cfg(test)]
233
mod tests {
234

235
    use minicbor::{Decoder, Encoder};
236
    use test_case::test_case;
237

238
    use super::*;
239

240
    #[test_case(
241
        CompatibilityPolicy::Accept,
242
        {
243
            Encoder::new(Vec::new())
244
        } ;
245
        "Invalid empty CBOR bytes"
246
    )]
247
    #[test_case(
248
        CompatibilityPolicy::Accept,
249
        {
250
            let mut e = Encoder::new(Vec::new());
251
            e.array(0).unwrap();
252
            e
253
        } ;
254
        "Invalid empty CBOR array"
255
    )]
256
    #[test_case(
257
        CompatibilityPolicy::Fail,
258
        {
259
            let mut e = Encoder::new(Vec::new());
260
            e.array(2)
261
                .unwrap()
262
                .encode_with(UuidV7::new(), &mut CborContext::Tagged)
263
                .unwrap()
264
                .encode_with(UuidV7::new(), &mut CborContext::Tagged)
265
                .unwrap();
266
            e
267
        } ;
268
        "Valid array of two uuid v7 (old format), fail policy"
269
    )]
270
    #[test_case(
271
        CompatibilityPolicy::Accept,
272
        {
273
            let mut e = Encoder::new(Vec::new());
274
            e.array(2)
275
                .unwrap()
276
                .encode_with(UuidV7::new(), &mut CborContext::Untagged)
277
                .unwrap()
278
                .encode_with(UuidV7::new(), &mut CborContext::Untagged)
279
                .unwrap();
280
            e
281
        } ;
282
        "Invalid untagged uuids v7 (old format)"
283
    )]
284
    #[test_case(
285
        CompatibilityPolicy::Accept,
286
        {
287
            let mut e = Encoder::new(Vec::new());
288
            e.array(1)
289
                .unwrap()
290
                .array(3)
291
                .unwrap()
292
                .encode_with(UuidV7::new(), &mut CborContext::Untagged)
293
                .unwrap()
294
                .encode_with(UuidV7::new(), &mut CborContext::Untagged)
295
                .unwrap()
296
                .encode(DocLocator::default())
297
                .unwrap();
298
            e
299
        } ;
300
        "Invalid untagged uuid uuids v7 (new format)"
301
    )]
302
    fn test_invalid_cbor_decode(
5✔
303
        mut policy: CompatibilityPolicy,
5✔
304
        e: Encoder<Vec<u8>>,
5✔
305
    ) {
5✔
306
        assert!(
5✔
307
            DocumentRefs::decode(&mut Decoder::new(e.into_writer().as_slice()), &mut policy)
5✔
308
                .is_err()
5✔
309
        );
310
    }
5✔
311

312
    #[test_case(
313
        CompatibilityPolicy::Accept,
314
        |uuid: UuidV7, _: DocLocator| {
1✔
315
            let mut e = Encoder::new(Vec::new());
1✔
316
            e.array(2)
1✔
317
                .unwrap()
1✔
318
                .encode_with(uuid, &mut CborContext::Tagged)
1✔
319
                .unwrap()
1✔
320
                .encode_with(uuid, &mut CborContext::Tagged)
1✔
321
                .unwrap();
1✔
322
            e
1✔
323
        } ;
1✔
324
        "Valid single doc ref (old format)"
325
    )]
326
    #[test_case(
327
        CompatibilityPolicy::Warn,
328
        |uuid: UuidV7, _: DocLocator| {
1✔
329
            let mut e = Encoder::new(Vec::new());
1✔
330
            e.array(2)
1✔
331
                .unwrap()
1✔
332
                .encode_with(uuid, &mut CborContext::Tagged)
1✔
333
                .unwrap()
1✔
334
                .encode_with(uuid, &mut CborContext::Tagged)
1✔
335
                .unwrap();
1✔
336
            e
1✔
337
        } ;
1✔
338
        "Valid single doc ref (old format), warn policy"
339
    )]
340
    #[test_case(
341
        CompatibilityPolicy::Accept,
342
        |uuid: UuidV7, doc_loc: DocLocator| {
1✔
343
            let mut e = Encoder::new(Vec::new());
1✔
344
            e.array(1)
1✔
345
                .unwrap()
1✔
346
                .array(3)
1✔
347
                .unwrap()
1✔
348
                .encode_with(uuid, &mut CborContext::Tagged)
1✔
349
                .unwrap()
1✔
350
                .encode_with(uuid, &mut CborContext::Tagged)
1✔
351
                .unwrap()
1✔
352
                .encode(doc_loc)
1✔
353
                .unwrap();
1✔
354
            e
1✔
355
        } ;
1✔
356
        "Array of new doc ref (new format)"
357
    )]
358
    #[test_case(
359
        CompatibilityPolicy::Fail,
360
        |uuid: UuidV7, doc_loc: DocLocator| {
1✔
361
            let mut e = Encoder::new(Vec::new());
1✔
362
            e.array(1)
1✔
363
                .unwrap()
1✔
364
                .array(3)
1✔
365
                .unwrap()
1✔
366
                .encode_with(uuid, &mut CborContext::Tagged)
1✔
367
                .unwrap()
1✔
368
                .encode_with(uuid, &mut CborContext::Tagged)
1✔
369
                .unwrap()
1✔
370
                .encode(doc_loc)
1✔
371
                .unwrap();
1✔
372
            e
1✔
373
        } ;
1✔
374
        "Array of new doc ref (new format), fail policy"
375
    )]
376
    fn test_valid_cbor_decode(
4✔
377
        mut policy: CompatibilityPolicy,
4✔
378
        e_gen: impl FnOnce(UuidV7, DocLocator) -> Encoder<Vec<u8>>,
4✔
379
    ) {
4✔
380
        let uuid = UuidV7::new();
4✔
381
        let doc_loc = DocLocator::default();
4✔
382
        let e = e_gen(uuid, doc_loc.clone());
4✔
383

384
        let doc_refs =
4✔
385
            DocumentRefs::decode(&mut Decoder::new(e.into_writer().as_slice()), &mut policy)
4✔
386
                .unwrap();
4✔
387
        assert_eq!(doc_refs.0, vec![DocumentRef::new(uuid, uuid, doc_loc)]);
4✔
388
    }
4✔
389

390
    #[test_case(
391
        serde_json::json!(
392
            {
393
                "id": UuidV7::new(),
394
                "ver": UuidV7::new(),
395
            }
396
        ) ;
397
        "Document reference type old format"
398
    )]
399
    #[test_case(
400
        serde_json::json!(
401
            [
402
                {
403
                    "id": UuidV7::new(),
404
                    "ver": UuidV7::new(),
405
                    "cid": format!("0x{}", hex::encode([1, 2, 3]))
406
                },
407
                {
408
                    "id": UuidV7::new(),
409
                    "ver": UuidV7::new(),
410
                    "cid": format!("0x{}", hex::encode([1, 2, 3]))
411
                }
412
            ]
413
        ) ;
414
        "Document reference type new format"
415
    )]
416
    fn test_json_valid_serde(json: serde_json::Value) {
2✔
417
        let refs: DocumentRefs = serde_json::from_value(json).unwrap();
2✔
418
        let json_from_refs = serde_json::to_value(&refs).unwrap();
2✔
419
        assert_eq!(refs, serde_json::from_value(json_from_refs).unwrap());
2✔
420
    }
2✔
421
}
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