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

input-output-hk / catalyst-libs / 19403595944

16 Nov 2025 09:31AM UTC coverage: 68.206% (-0.09%) from 68.298%
19403595944

push

github

web-flow
feat(rust/signed-doc): CatalystSignedDocument has CIDv1 (#621)

* feat(rust/signed-doc): CatalystSignedDocument has CIDv1

* feat(rust/signed-doc): Cid newtype

* break(rust/signed-doc): DocLocator(Cid) must be valid

* Breaking change that removes compatibility with old versions

* fix(rust/signed-doc): fix unit tests to support DocLocator

* removes support for old version

* fix(rust/signed-doc): fix integration tests

* chore(rust/signed-doc): fmt fix

* chore(rust/signed-doc): fix spelling

* chore(rust/signed-doc): fix doc comment

* chore(rust/signed-doc): Add `doc_ref` method for `CatalystSignedDoc`. (#640)

* update API

* wip

* wip

* wip

* fix test

* wip

* wip

* fix fmt

* fix(rust/signed-doc): remove stale example

---------

Co-authored-by: JoaquĆ­n Rosales <joaquin.rosales@iohk.io>

---------

Co-authored-by: Alex Pozhylenkov <leshiy12345678@gmail.com>

525 of 561 new or added lines in 12 files covered. (93.58%)

10 existing lines in 5 files now uncovered.

13841 of 20293 relevant lines covered (68.21%)

2725.97 hits per line

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

86.49
/rust/signed_doc/src/metadata/document_refs/doc_locator.rs
1
//! Document Locator, where a document can be located.
2
//! A [CBOR Encoded IPLD Content Identifier](https://github.com/ipld/cid-cbor/)
3
//! or also known as [IPFS CID](https://docs.ipfs.tech/concepts/content-addressing/#what-is-a-cid).
4

5
use std::{fmt::Display, ops::Deref, str::FromStr};
6

7
use cbork_utils::{decode_context::DecodeCtx, map::Map};
8
use minicbor::{Decode, Decoder, Encode};
9

10
use crate::{
11
    cid_v1::{Cid, CidError},
12
    metadata::document_refs::DocRefError,
13
};
14

15
/// CID map key.
16
const CID_MAP_KEY: &str = "cid";
17

18
/// Document locator number of map item.
19
const DOC_LOC_MAP_ITEM: u64 = 1;
20

21
/// Document locator wrapping a CID (Content Identifier).
22
#[derive(Clone, Debug, PartialEq, Hash, Eq)]
23
pub struct DocLocator(Cid);
24

25
impl Deref for DocLocator {
26
    type Target = Cid;
27

28
    fn deref(&self) -> &Self::Target {
1✔
29
        &self.0
1✔
30
    }
1✔
31
}
32

33
impl From<Cid> for DocLocator {
34
    fn from(cid: Cid) -> Self {
774✔
35
        Self(cid)
774✔
36
    }
774✔
37
}
38

39
impl TryFrom<&[u8]> for DocLocator {
40
    type Error = CidError;
41

NEW
42
    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
×
NEW
43
        let cid = Cid::try_from(bytes)?;
×
NEW
44
        Ok(Self(cid))
×
UNCOV
45
    }
×
46
}
47

48
impl Display for DocLocator {
49
    fn fmt(
373✔
50
        &self,
373✔
51
        f: &mut std::fmt::Formatter<'_>,
373✔
52
    ) -> std::fmt::Result {
373✔
53
        write!(f, "{}", self.0)
373✔
54
    }
373✔
55
}
56

57
impl FromStr for DocLocator {
58
    type Err = DocRefError;
59

60
    fn from_str(s: &str) -> Result<Self, Self::Err> {
336✔
61
        let cid = Cid::from_str(s).map_err(|e| DocRefError::StringConversion(e.to_string()))?;
336✔
62
        Ok(Self(cid))
336✔
63
    }
336✔
64
}
65

66
impl<'de> serde::Deserialize<'de> for DocLocator {
67
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
335✔
68
    where D: serde::Deserializer<'de> {
335✔
69
        let s = String::deserialize(deserializer)?;
335✔
70
        s.parse::<DocLocator>().map_err(serde::de::Error::custom)
335✔
71
    }
335✔
72
}
73

74
impl serde::Serialize for DocLocator {
75
    fn serialize<S>(
335✔
76
        &self,
335✔
77
        serializer: S,
335✔
78
    ) -> Result<S::Ok, S::Error>
335✔
79
    where
335✔
80
        S: serde::Serializer,
335✔
81
    {
82
        serializer.serialize_str(&self.to_string())
335✔
83
    }
335✔
84
}
85

86
// document_locator = { "cid" => tag(42)(cid_bytes) }
87
impl Decode<'_, ()> for DocLocator {
88
    fn decode(
439✔
89
        d: &mut Decoder,
439✔
90
        _ctx: &mut (),
439✔
91
    ) -> Result<Self, minicbor::decode::Error> {
439✔
92
        const CONTEXT: &str = "DocLocator decoding";
93

94
        let entries = Map::decode(d, &mut DecodeCtx::Deterministic)?;
439✔
95

96
        match entries.as_slice() {
439✔
97
            [entry] => {
439✔
98
                let key = minicbor::Decoder::new(&entry.key_bytes)
439✔
99
                    .str()
439✔
100
                    .map_err(|e| e.with_message(format!("{CONTEXT}: expected string")))?;
439✔
101

102
                if key != "cid" {
439✔
103
                    return Err(minicbor::decode::Error::message(format!(
×
104
                        "{CONTEXT}: expected key 'cid', found '{key}'"
×
105
                    )));
×
106
                }
439✔
107

108
                let mut value_decoder = minicbor::Decoder::new(&entry.value);
439✔
109

110
                // Decode the Cid, which validates tag(42) and CID format
111
                let cid = Cid::decode(&mut value_decoder, &mut ()).map_err(|e| {
439✔
NEW
112
                    let msg = format!("{CONTEXT}: {e}");
×
NEW
113
                    e.with_message(msg)
×
NEW
114
                })?;
×
115

116
                Ok(DocLocator(cid))
439✔
117
            },
118
            _ => {
119
                Err(minicbor::decode::Error::message(format!(
×
120
                    "{CONTEXT}: expected map length {DOC_LOC_MAP_ITEM}, found {}",
×
121
                    entries.len()
×
122
                )))
×
123
            },
124
        }
125
    }
439✔
126
}
127

128
impl Encode<()> for DocLocator {
129
    fn encode<W: minicbor::encode::Write>(
465✔
130
        &self,
465✔
131
        e: &mut minicbor::Encoder<W>,
465✔
132
        ctx: &mut (),
465✔
133
    ) -> Result<(), minicbor::encode::Error<W::Error>> {
465✔
134
        e.map(DOC_LOC_MAP_ITEM)?;
465✔
135
        e.str(CID_MAP_KEY)?;
465✔
136
        // Delegate Cid encoding which handles tag(42) and CID bytes
137
        self.0.encode(e, ctx)?;
465✔
138
        Ok(())
465✔
139
    }
465✔
140
}
141

142
#[cfg(test)]
143
mod tests {
144

145
    use minicbor::{Decoder, Encoder};
146

147
    use super::*;
148
    use crate::{
149
        Builder, ContentType, UuidV7, metadata::document_refs::tests::create_dummy_doc_ref,
150
    };
151

152
    #[test]
153
    fn test_doc_locator_encode_decode() {
1✔
154
        let locator = create_dummy_doc_ref().doc_locator().clone();
1✔
155
        let mut buffer = Vec::new();
1✔
156
        let mut encoder = Encoder::new(&mut buffer);
1✔
157
        locator.encode(&mut encoder, &mut ()).unwrap();
1✔
158
        let mut decoder = Decoder::new(&buffer);
1✔
159
        let decoded_doc_loc = DocLocator::decode(&mut decoder, &mut ()).unwrap();
1✔
160
        assert_eq!(locator, decoded_doc_loc);
1✔
161
    }
1✔
162

163
    #[test]
164
    fn test_doc_locator_display() {
1✔
165
        let locator = create_dummy_doc_ref().doc_locator().clone();
1✔
166
        let display_str = locator.to_string();
1✔
167
        assert!(
1✔
168
            display_str.starts_with('b'),
1✔
NEW
169
            "Should use multibase format starting with 'b'"
×
170
        );
171
    }
1✔
172

173
    #[test]
174
    fn test_doc_locator_from_str() {
1✔
175
        let locator = create_dummy_doc_ref().doc_locator().clone();
1✔
176
        let display_str = locator.to_string();
1✔
177
        let parsed = display_str
1✔
178
            .parse::<DocLocator>()
1✔
179
            .expect("Should parse multibase string");
1✔
180
        assert_eq!(locator, parsed);
1✔
181
    }
1✔
182

183
    #[test]
184
    fn test_doc_locator_from_cid() {
1✔
185
        use crate::UuidV4;
186

187
        let id = UuidV7::new();
1✔
188
        let ver = UuidV7::new();
1✔
189
        let doc = Builder::new()
1✔
190
            .with_json_metadata(serde_json::json!({
1✔
191
                "id": id.to_string(),
1✔
192
                "ver": ver.to_string(),
1✔
193
                "type": UuidV4::new().to_string(),
1✔
194
                "content-type": ContentType::Json,
1✔
195
            }))
196
            .expect("Should create metadata")
1✔
197
            .with_json_content(&serde_json::json!({"test": "content"}))
1✔
198
            .expect("Should set content")
1✔
199
            .build()
1✔
200
            .expect("Should build document");
1✔
201

202
        let cid = doc.to_cid_v1().expect("Should generate CID");
1✔
203
        let locator = DocLocator::from(cid);
1✔
204

205
        assert_eq!(&*locator, &cid);
1✔
206
    }
1✔
207
}
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