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() > 1 {
155 let first_char = value.chars().next().unwrap();
156 if matches!(
158 first_char,
159 'b' | 'B' | 'z' | 'f' | 'F' | 'm' | 'M' | 'u' | 'U'
160 ) {
161 return Ok(Cid::V1(CidV1(value)));
162 }
163 }
164
165 Err(CidError::InvalidFormat)
166 }
167}
168
169impl TryFrom<&str> for Cid {
170 type Error = CidError;
171
172 fn try_from(value: &str) -> Result<Self, Self::Error> {
173 Cid::try_from(value.to_string())
174 }
175}
176
177impl Display for Cid {
178 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
179 write!(f, "{}", self.as_str())
180 }
181}
182
183impl From<CidV0> for Cid {
184 fn from(value: CidV0) -> Self {
185 Cid::V0(value)
186 }
187}
188
189impl From<CidV1> for Cid {
190 fn from(value: CidV1) -> Self {
191 Cid::V1(value)
192 }
193}
194
195impl Serialize for Cid {
197 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
198 where
199 S: Serializer,
200 {
201 serializer.serialize_str(self.as_str())
202 }
203}
204
205impl<'de> Deserialize<'de> for Cid {
207 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
208 where
209 D: Deserializer<'de>,
210 {
211 let s = String::deserialize(deserializer)?;
212 Cid::try_from(s).map_err(serde::de::Error::custom)
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_cidv0_new() {
222 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8".to_string();
223 let cid = CidV0::new(cid_str.clone()).unwrap();
224 assert_eq!(cid.as_str(), cid_str);
225 }
226
227 #[test]
228 fn test_cidv0_try_from() {
229 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
230 let cid = CidV0::try_from(cid_str).unwrap();
231 assert_eq!(cid.as_str(), cid_str);
232 }
233
234 #[test]
235 fn test_cidv0_invalid_length() {
236 let cid_str = "QmYULJo".to_string();
237 let result = CidV0::new(cid_str);
238 assert!(result.is_err());
239 assert!(matches!(result.unwrap_err(), CidError::InvalidV0));
240 }
241
242 #[test]
243 fn test_cidv0_invalid_prefix() {
244 let cid_str = "XmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8".to_string();
245 let result = CidV0::new(cid_str);
246 assert!(result.is_err());
247 }
248
249 #[test]
250 fn test_cidv0_display() {
251 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
252 let cid = CidV0::try_from(cid_str).unwrap();
253 assert_eq!(format!("{}", cid), cid_str);
254 }
255
256 #[test]
257 fn test_cidv1_new() {
258 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi".to_string();
259 let cid = CidV1::new(cid_str.clone());
260 assert_eq!(cid.as_str(), cid_str);
261 }
262
263 #[test]
264 fn test_cidv1_from_string() {
265 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi".to_string();
266 let cid = CidV1::from(cid_str.clone());
267 assert_eq!(cid.as_str(), cid_str);
268 }
269
270 #[test]
271 fn test_cidv1_display() {
272 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
273 let cid = CidV1::new(cid_str.to_string());
274 assert_eq!(format!("{}", cid), cid_str);
275 }
276
277 #[test]
278 fn test_cid_from_cidv0() {
279 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
280 let cidv0 = CidV0::try_from(cid_str).unwrap();
281 let cid = Cid::from(cidv0);
282 assert!(cid.is_v0());
283 assert_eq!(cid.as_str(), cid_str);
284 }
285
286 #[test]
287 fn test_cid_from_cidv1() {
288 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
289 let cidv1 = CidV1::new(cid_str.to_string());
290 let cid = Cid::from(cidv1);
291 assert!(cid.is_v1());
292 assert_eq!(cid.as_str(), cid_str);
293 }
294
295 #[test]
296 fn test_cid_try_from_v0_string() {
297 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
298 let cid = Cid::try_from(cid_str).unwrap();
299 assert!(cid.is_v0());
300 assert_eq!(cid.as_str(), cid_str);
301 }
302
303 #[test]
304 fn test_cid_try_from_v1_base32() {
305 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
306 let cid = Cid::try_from(cid_str).unwrap();
307 assert!(cid.is_v1());
308 assert_eq!(cid.as_str(), cid_str);
309 }
310
311 #[test]
312 fn test_cid_try_from_v1_base58btc() {
313 let cid_str = "zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7";
314 let cid = Cid::try_from(cid_str).unwrap();
315 assert!(cid.is_v1());
316 assert_eq!(cid.as_str(), cid_str);
317 }
318
319 #[test]
320 fn test_cid_empty_string() {
321 let result = Cid::try_from("");
322 assert!(result.is_err());
323 assert!(matches!(result.unwrap_err(), CidError::EmptyString));
324 }
325
326 #[test]
327 fn test_cid_invalid_format() {
328 let result = Cid::try_from("invalid_cid_format");
329 assert!(result.is_err());
330 assert!(matches!(result.unwrap_err(), CidError::InvalidFormat));
331 }
332
333 #[test]
334 fn test_cid_display() {
335 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
336 let cid = Cid::try_from(cid_str).unwrap();
337 assert_eq!(format!("{}", cid), cid_str);
338 }
339
340 #[test]
341 fn test_cidv0_serde() {
342 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
343 let cid = CidV0::try_from(cid_str).unwrap();
344
345 let json = serde_json::to_string(&cid).unwrap();
346 assert_eq!(json, format!("\"{}\"", cid_str));
347
348 let deserialized: CidV0 = serde_json::from_str(&json).unwrap();
349 assert_eq!(cid, deserialized);
350 }
351
352 #[test]
353 fn test_cidv1_serde() {
354 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
355 let cid = CidV1::new(cid_str.to_string());
356
357 let json = serde_json::to_string(&cid).unwrap();
358 assert_eq!(json, format!("\"{}\"", cid_str));
359
360 let deserialized: CidV1 = serde_json::from_str(&json).unwrap();
361 assert_eq!(cid, deserialized);
362 }
363
364 #[test]
365 fn test_cid_serde_v0() {
366 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8";
367 let cid = Cid::try_from(cid_str).unwrap();
368
369 let json = serde_json::to_string(&cid).unwrap();
370 assert_eq!(json, format!("\"{}\"", cid_str));
371
372 let deserialized: Cid = serde_json::from_str(&json).unwrap();
373 assert_eq!(cid, deserialized);
374 assert!(deserialized.is_v0());
375 }
376
377 #[test]
378 fn test_cid_serde_v1() {
379 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
380 let cid = Cid::try_from(cid_str).unwrap();
381
382 let json = serde_json::to_string(&cid).unwrap();
383 assert_eq!(json, format!("\"{}\"", cid_str));
384
385 let deserialized: Cid = serde_json::from_str(&json).unwrap();
386 assert_eq!(cid, deserialized);
387 assert!(deserialized.is_v1());
388 }
389
390 #[test]
391 fn test_cidv0_into_inner() {
392 let cid_str = "QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8".to_string();
393 let cid = CidV0::new(cid_str.clone()).unwrap();
394 assert_eq!(cid.into_inner(), cid_str);
395 }
396
397 #[test]
398 fn test_cidv1_into_inner() {
399 let cid_str = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi".to_string();
400 let cid = CidV1::new(cid_str.clone());
401 assert_eq!(cid.into_inner(), cid_str);
402 }
403}