Skip to content

Commit d723d47

Browse files
committed
break(rust/signed-doc): DocLocator(Cid) must be valid
* Breaking change that removes compatibility with old versions
1 parent a579dad commit d723d47

File tree

4 files changed

+124
-187
lines changed

4 files changed

+124
-187
lines changed

rust/signed_doc/src/metadata/document_refs/doc_locator.rs

Lines changed: 94 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,39 @@ use std::{fmt::Display, ops::Deref, str::FromStr};
77
use cbork_utils::{decode_context::DecodeCtx, map::Map};
88
use minicbor::{Decode, Decoder, Encode};
99

10+
use crate::cid_v1::{Cid, CidError};
1011
use crate::metadata::document_refs::DocRefError;
1112

12-
/// CBOR tag of IPLD content identifiers (CIDs).
13-
const CID_TAG: u64 = 42;
14-
1513
/// CID map key.
1614
const CID_MAP_KEY: &str = "cid";
1715

1816
/// Document locator number of map item.
1917
const DOC_LOC_MAP_ITEM: u64 = 1;
2018

21-
/// Document locator, no size limit.
22-
#[derive(Clone, Debug, Default, PartialEq, Hash, Eq)]
23-
pub struct DocLocator(Vec<u8>);
19+
/// Document locator wrapping a CID (Content Identifier).
20+
#[derive(Clone, Debug, PartialEq, Hash, Eq)]
21+
pub struct DocLocator(Cid);
2422

2523
impl Deref for DocLocator {
26-
type Target = Vec<u8>;
24+
type Target = Cid;
2725

2826
fn deref(&self) -> &Self::Target {
2927
&self.0
3028
}
3129
}
3230

33-
impl From<Vec<u8>> for DocLocator {
34-
fn from(value: Vec<u8>) -> Self {
35-
Self(value)
31+
impl From<Cid> for DocLocator {
32+
fn from(cid: Cid) -> Self {
33+
Self(cid)
34+
}
35+
}
36+
37+
impl TryFrom<&[u8]> for DocLocator {
38+
type Error = CidError;
39+
40+
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
41+
let cid = Cid::try_from(bytes)?;
42+
Ok(Self(cid))
3643
}
3744
}
3845

@@ -41,19 +48,17 @@ impl Display for DocLocator {
4148
&self,
4249
f: &mut std::fmt::Formatter<'_>,
4350
) -> std::fmt::Result {
44-
write!(f, "0x{}", hex::encode(self.0.as_slice()))
51+
write!(f, "{}", self.0)
4552
}
4653
}
4754

4855
impl FromStr for DocLocator {
4956
type Err = DocRefError;
5057

5158
fn from_str(s: &str) -> Result<Self, Self::Err> {
52-
s.strip_prefix("0x")
53-
.map(hex::decode)
54-
.ok_or(DocRefError::HexDecode("missing 0x prefix".to_string()))?
55-
.map(Self)
56-
.map_err(|e| DocRefError::HexDecode(e.to_string()))
59+
let cid = Cid::from_str(s)
60+
.map_err(|e| DocRefError::StringConversion(e.to_string()))?;
61+
Ok(Self(cid))
5762
}
5863
}
5964

@@ -77,7 +82,7 @@ impl serde::Serialize for DocLocator {
7782
}
7883
}
7984

80-
// document_locator = { "cid" => cid }
85+
// document_locator = { "cid" => tag(42)(cid_bytes) }
8186
impl Decode<'_, ()> for DocLocator {
8287
fn decode(
8388
d: &mut Decoder,
@@ -101,22 +106,14 @@ impl Decode<'_, ()> for DocLocator {
101106

102107
let mut value_decoder = minicbor::Decoder::new(&entry.value);
103108

104-
let tag = value_decoder
105-
.tag()
106-
.map_err(|e| e.with_message(format!("{CONTEXT}: expected tag")))?;
107-
108-
if tag.as_u64() != CID_TAG {
109-
return Err(minicbor::decode::Error::message(format!(
110-
"{CONTEXT}: expected tag {CID_TAG}, found {tag}",
111-
)));
112-
}
109+
// Decode the Cid, which validates tag(42) and CID format
110+
let cid = Cid::decode(&mut value_decoder, &mut ())
111+
.map_err(|e| {
112+
let msg = format!("{CONTEXT}: {e}");
113+
e.with_message(msg)
114+
})?;
113115

114-
// No length limit
115-
let cid_bytes = value_decoder
116-
.bytes()
117-
.map_err(|e| e.with_message(format!("{CONTEXT}: expected bytes")))?;
118-
119-
Ok(DocLocator(cid_bytes.to_vec()))
116+
Ok(DocLocator(cid))
120117
},
121118
_ => {
122119
Err(minicbor::decode::Error::message(format!(
@@ -132,26 +129,49 @@ impl Encode<()> for DocLocator {
132129
fn encode<W: minicbor::encode::Write>(
133130
&self,
134131
e: &mut minicbor::Encoder<W>,
135-
(): &mut (),
132+
ctx: &mut (),
136133
) -> Result<(), minicbor::encode::Error<W::Error>> {
137134
e.map(DOC_LOC_MAP_ITEM)?;
138135
e.str(CID_MAP_KEY)?;
139-
e.tag(minicbor::data::Tag::new(CID_TAG))?;
140-
e.bytes(&self.0)?;
136+
// Delegate Cid encoding which handles tag(42) and CID bytes
137+
self.0.encode(e, ctx)?;
141138
Ok(())
142139
}
143140
}
144141

145142
#[cfg(test)]
146-
mod tests {
143+
pub(crate) mod tests {
147144

148145
use minicbor::{Decoder, Encoder};
149146

150147
use super::*;
148+
use crate::{Builder, ContentType, UuidV7};
149+
150+
pub(crate) fn create_dummy_doc_locator() -> DocLocator {
151+
use crate::UuidV4;
152+
153+
let id = UuidV7::new();
154+
let ver = UuidV7::new();
155+
let doc = Builder::new()
156+
.with_json_metadata(serde_json::json!({
157+
"id": id.to_string(),
158+
"ver": ver.to_string(),
159+
"type": UuidV4::new().to_string(),
160+
"content-type": ContentType::Json,
161+
}))
162+
.expect("Should create metadata")
163+
.with_json_content(&serde_json::json!({"test": "content"}))
164+
.expect("Should set content")
165+
.build()
166+
.expect("Should build document");
167+
168+
let cid = doc.to_cid_v1().expect("Should generate CID");
169+
DocLocator::from(cid)
170+
}
151171

152172
#[test]
153173
fn test_doc_locator_encode_decode() {
154-
let locator = DocLocator(vec![1, 2, 3, 4]);
174+
let locator = create_dummy_doc_locator();
155175
let mut buffer = Vec::new();
156176
let mut encoder = Encoder::new(&mut buffer);
157177
locator.encode(&mut encoder, &mut ()).unwrap();
@@ -160,15 +180,43 @@ mod tests {
160180
assert_eq!(locator, decoded_doc_loc);
161181
}
162182

163-
// Empty doc locator should not fail
164183
#[test]
165-
fn test_doc_locator_encode_decode_empty() {
166-
let locator = DocLocator(vec![]);
167-
let mut buffer = Vec::new();
168-
let mut encoder = Encoder::new(&mut buffer);
169-
locator.encode(&mut encoder, &mut ()).unwrap();
170-
let mut decoder = Decoder::new(&buffer);
171-
let decoded_doc_loc = DocLocator::decode(&mut decoder, &mut ()).unwrap();
172-
assert_eq!(locator, decoded_doc_loc);
184+
fn test_doc_locator_display() {
185+
let locator = create_dummy_doc_locator();
186+
let display_str = locator.to_string();
187+
assert!(display_str.starts_with('b'), "Should use multibase format starting with 'b'");
188+
}
189+
190+
#[test]
191+
fn test_doc_locator_from_str() {
192+
let locator = create_dummy_doc_locator();
193+
let display_str = locator.to_string();
194+
let parsed = display_str.parse::<DocLocator>().expect("Should parse multibase string");
195+
assert_eq!(locator, parsed);
196+
}
197+
198+
#[test]
199+
fn test_doc_locator_from_cid() {
200+
use crate::UuidV4;
201+
202+
let id = UuidV7::new();
203+
let ver = UuidV7::new();
204+
let doc = Builder::new()
205+
.with_json_metadata(serde_json::json!({
206+
"id": id.to_string(),
207+
"ver": ver.to_string(),
208+
"type": UuidV4::new().to_string(),
209+
"content-type": ContentType::Json,
210+
}))
211+
.expect("Should create metadata")
212+
.with_json_content(&serde_json::json!({"test": "content"}))
213+
.expect("Should set content")
214+
.build()
215+
.expect("Should build document");
216+
217+
let cid = doc.to_cid_v1().expect("Should generate CID");
218+
let locator = DocLocator::from(cid);
219+
220+
assert_eq!(&*locator, &cid);
173221
}
174222
}

rust/signed_doc/src/metadata/document_refs/doc_ref.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub struct DocumentRef {
2020
/// Reference to the Document Ver
2121
ver: UuidV7,
2222
/// Document locator
23-
#[serde(rename = "cid", default)]
23+
#[serde(rename = "cid")]
2424
doc_locator: DocLocator,
2525
}
2626

@@ -62,10 +62,11 @@ impl TryFrom<&CatalystSignedDocument> for DocumentRef {
6262
type Error = anyhow::Error;
6363

6464
fn try_from(value: &CatalystSignedDocument) -> Result<Self, Self::Error> {
65+
let cid = value.to_cid_v1()?;
6566
Ok(Self::new(
6667
value.doc_id()?,
6768
value.doc_ver()?,
68-
DocLocator::default(),
69+
DocLocator::from(cid),
6970
))
7071
}
7172
}

0 commit comments

Comments
 (0)