1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use std::convert::TryFrom;
3use std::fmt::{Display, Formatter};
4use thiserror::Error;
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub struct CidV0(String);
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct CidV1(String);
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub enum Cid {
18 V0(CidV0),
20 V1(CidV1),
22}
23
24#[derive(Error, Debug)]
25pub enum CidError {
26 #[error("invalid CID: empty string")]
27 EmptyString,
28 #[error("invalid CID format: unrecognized version or encoding")]
29 InvalidFormat,
30 #[error("invalid CIDv0: must start with 'Qm' and be 46 characters")]
31 InvalidV0,
32}
33
34impl CidV0 {
35 pub fn new(cid: String) -> Result<Self, CidError> {
38 if cid.starts_with("Qm") && cid.len() == 46 {
39 Ok(CidV0(cid))
40 } else {
41 Err(CidError::InvalidV0)
42 }
43 }
44
45 pub fn as_str(&self) -> &str {
47 &self.0
48 }
49
50 pub fn into_inner(self) -> String {
52 self.0
53 }
54}
55
56impl TryFrom<String> for CidV0 {
57 type Error = CidError;
58
59 fn try_from(value: String) -> Result<Self, Self::Error> {
60 CidV0::new(value)
61 }
62}
63
64impl TryFrom<&str> for CidV0 {
65 type Error = CidError;
66
67 fn try_from(value: &str) -> Result<Self, Self::Error> {
68 CidV0::new(value.to_string())
69 }
70}
71
72impl Display for CidV0 {
73 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
74 write!(f, "{}", self.0)
75 }
76}
77
78impl CidV1 {
79 pub fn new(cid: String) -> Self {
82 CidV1(cid)
83 }
84
85 pub fn as_str(&self) -> &str {
87 &self.0
88 }
89
90 pub fn into_inner(self) -> String {
92 self.0
93 }
94}
95
96impl From<String> for CidV1 {
97 fn from(value: String) -> Self {
98 CidV1(value)
99 }
100}
101
102impl Display for CidV1 {
103 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
104 write!(f, "{}", self.0)
105 }
106}
107
108impl Cid {
109 pub fn v0(cid: CidV0) -> Self {
111 Cid::V0(cid)
112 }
113
114 pub fn v1(cid: CidV1) -> Self {
116 Cid::V1(cid)
117 }
118
119 pub fn as_str(&self) -> &str {
121 match self {
122 Cid::V0(cid) => cid.as_str(),
123 Cid::V1(cid) => cid.as_str(),
124 }
125 }
126
127 pub fn is_v0(&self) -> bool {
129 matches!(self, Cid::V0(_))
130 }
131
132 pub fn is_v1(&self) -> bool {
134 matches!(self, Cid::V1(_))
135 }
136}
137
138impl TryFrom<String> for Cid {
139 type Error = CidError;
140
141 fn try_from(value: String) -> Result<Self, Self::Error> {
142 if value.is_empty() {
143 return Err(CidError::EmptyString);
144 }
145
146 if value.starts_with("Qm") && value.len() == 46 {
148 return Ok(Cid::V0(CidV0(value)));
149 }
150
151 if value.len() >= 40 {
156 let first_char = value.chars().next().unwrap();
157 if matches!(
159 first_char,
160 'b' | 'B' | 'z' | 'f' | 'F' | 'm' | 'M' | 'u' | 'U'
161 ) {
162 return Ok(Cid::V1(CidV1(value)));
163 }
164 }
165
166 Err(CidError::InvalidFormat)
167 }
168}
169
170impl TryFrom<&str> for Cid {
171 type Error = CidError;
172
173 fn try_from(value: &str) -> Result<Self, Self::Error> {
174 Cid::try_from(value.to_string())
175 }
176}
177
178impl Display for Cid {
179 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
180 write!(f, "{}", self.as_str())
181 }
182}
183
184impl From<CidV0> for Cid {
185 fn from(value: CidV0) -> Self {
186 Cid::V0(value)
187 }
188}
189
190impl From<CidV1> for Cid {
191 fn from(value: CidV1) -> Self {
192 Cid::V1(value)
193 }
194}
195
196impl Serialize for Cid {
198 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
199 where
200 S: Serializer,
201 {
202 serializer.serialize_str(self.as_str())
203 }
204}
205
206impl<'de> Deserialize<'de> for Cid {
208 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
209 where
210 D: Deserializer<'de>,
211 {
212 let s = String::deserialize(deserializer)?;
213 Cid::try_from(s).map_err(serde::de::Error::custom)
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_cidv0_new() {
223 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8".to_string();
224 let cid = CidV0::new(cid_str.clone()).unwrap();
225 assert_eq!(cid.as_str(), cid_str);
226 }
227
228 #[test]
229 fn test_cidv0_try_from() {
230 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
231 let cid = CidV0::try_from(cid_str).unwrap();
232 assert_eq!(cid.as_str(), cid_str);
233 }
234
235 #[test]
236 fn test_cidv0_invalid_length() {
237 let cid_str = "QmYULJo".to_string();
238 let result = CidV0::new(cid_str);
239 assert!(result.is_err());
240 assert!(matches!(result.unwrap_err(), CidError::InvalidV0));
241 }
242
243 #[test]
244 fn test_cidv0_invalid_prefix() {
245 let cid_str = "XmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8".to_string();
246 let result = CidV0::new(cid_str);
247 assert!(result.is_err());
248 }
249
250 #[test]
251 fn test_cidv0_display() {
252 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
253 let cid = CidV0::try_from(cid_str).unwrap();
254 assert_eq!(format!("{}", cid), cid_str);
255 }
256
257 #[test]
258 fn test_cidv1_new() {
259 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi".to_string();
260 let cid = CidV1::new(cid_str.clone());
261 assert_eq!(cid.as_str(), cid_str);
262 }
263
264 #[test]
265 fn test_cidv1_from_string() {
266 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi".to_string();
267 let cid = CidV1::from(cid_str.clone());
268 assert_eq!(cid.as_str(), cid_str);
269 }
270
271 #[test]
272 fn test_cidv1_display() {
273 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
274 let cid = CidV1::new(cid_str.to_string());
275 assert_eq!(format!("{}", cid), cid_str);
276 }
277
278 #[test]
279 fn test_cid_from_cidv0() {
280 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
281 let cidv0 = CidV0::try_from(cid_str).unwrap();
282 let cid = Cid::from(cidv0);
283 assert!(cid.is_v0());
284 assert_eq!(cid.as_str(), cid_str);
285 }
286
287 #[test]
288 fn test_cid_from_cidv1() {
289 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
290 let cidv1 = CidV1::new(cid_str.to_string());
291 let cid = Cid::from(cidv1);
292 assert!(cid.is_v1());
293 assert_eq!(cid.as_str(), cid_str);
294 }
295
296 #[test]
297 fn test_cid_try_from_v0_string() {
298 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
299 let cid = Cid::try_from(cid_str).unwrap();
300 assert!(cid.is_v0());
301 assert_eq!(cid.as_str(), cid_str);
302 }
303
304 #[test]
305 fn test_cid_try_from_v1_base32() {
306 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
307 let cid = Cid::try_from(cid_str).unwrap();
308 assert!(cid.is_v1());
309 assert_eq!(cid.as_str(), cid_str);
310 }
311
312 #[test]
313 fn test_cid_try_from_v1_base58btc() {
314 let cid_str = "zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7";
315 let cid = Cid::try_from(cid_str).unwrap();
316 assert!(cid.is_v1());
317 assert_eq!(cid.as_str(), cid_str);
318 }
319
320 #[test]
321 fn test_cid_empty_string() {
322 let result = Cid::try_from("");
323 assert!(result.is_err());
324 assert!(matches!(result.unwrap_err(), CidError::EmptyString));
325 }
326
327 #[test]
328 fn test_cid_invalid_format() {
329 let result = Cid::try_from("invalid_cid_format");
330 assert!(result.is_err());
331 assert!(matches!(result.unwrap_err(), CidError::InvalidFormat));
332 }
333
334 #[test]
335 fn test_cid_display() {
336 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
337 let cid = Cid::try_from(cid_str).unwrap();
338 assert_eq!(format!("{}", cid), cid_str);
339 }
340
341 #[test]
342 fn test_cidv0_serde() {
343 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
344 let cid = CidV0::try_from(cid_str).unwrap();
345
346 let json = serde_json::to_string(&cid).unwrap();
347 assert_eq!(json, format!("\"{}\"", cid_str));
348
349 let deserialized: CidV0 = serde_json::from_str(&json).unwrap();
350 assert_eq!(cid, deserialized);
351 }
352
353 #[test]
354 fn test_cidv1_serde() {
355 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
356 let cid = CidV1::new(cid_str.to_string());
357
358 let json = serde_json::to_string(&cid).unwrap();
359 assert_eq!(json, format!("\"{}\"", cid_str));
360
361 let deserialized: CidV1 = serde_json::from_str(&json).unwrap();
362 assert_eq!(cid, deserialized);
363 }
364
365 #[test]
366 fn test_cid_serde_v0() {
367 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
368 let cid = Cid::try_from(cid_str).unwrap();
369
370 let json = serde_json::to_string(&cid).unwrap();
371 assert_eq!(json, format!("\"{}\"", cid_str));
372
373 let deserialized: Cid = serde_json::from_str(&json).unwrap();
374 assert_eq!(cid, deserialized);
375 assert!(deserialized.is_v0());
376 }
377
378 #[test]
379 fn test_cid_serde_v1() {
380 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
381 let cid = Cid::try_from(cid_str).unwrap();
382
383 let json = serde_json::to_string(&cid).unwrap();
384 assert_eq!(json, format!("\"{}\"", cid_str));
385
386 let deserialized: Cid = serde_json::from_str(&json).unwrap();
387 assert_eq!(cid, deserialized);
388 assert!(deserialized.is_v1());
389 }
390
391 #[test]
392 fn test_cidv0_into_inner() {
393 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8".to_string();
394 let cid = CidV0::new(cid_str.clone()).unwrap();
395 assert_eq!(cid.into_inner(), cid_str);
396 }
397
398 #[test]
399 fn test_cidv1_into_inner() {
400 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi".to_string();
401 let cid = CidV1::new(cid_str.clone());
402 assert_eq!(cid.into_inner(), cid_str);
403 }
404}