eth_avatars/modules/ipfs/
mod.rs1use std::str::FromStr;
2use strum::{Display, EnumString};
3
4use crate::{Locator, LocatorError, Resource};
5
6pub mod gateway;
7pub use gateway::IpfsGateway;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)]
10#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
11pub enum IpfsSchema {
12 Ipfs,
13 Ipns,
14}
15
16#[derive(Debug, PartialEq, Eq)]
17pub struct Ipfs {
18 pub schema: IpfsSchema,
19 pub cid: String,
20 pub path: Option<String>,
21}
22
23impl Locator for Ipfs {
24 fn of(resource: &Resource) -> Option<&Self> {
25 match resource {
26 Resource::Ipfs(ipfs) => Some(ipfs),
27 _ => None,
28 }
29 }
30}
31
32impl FromStr for Ipfs {
33 type Err = LocatorError;
34
35 fn from_str(s: &str) -> Result<Self, Self::Err> {
36 let (schema, rest) = s.split_once(':').ok_or(LocatorError::IpfsSchema)?;
37 let schema: IpfsSchema = schema.parse().map_err(|_| LocatorError::IpfsSchema)?;
38 let rest = rest.trim_start_matches('/');
39
40 let (cid, path) = match rest.split_once('/') {
41 Some((cid, path)) => (cid, Some(path)),
42 None => (rest, None),
43 };
44
45 if cid.is_empty() {
46 return Err(LocatorError::IpfsEmptyCid);
47 }
48
49 Ok(Self {
50 schema,
51 cid: cid.to_string(),
52 path: path.filter(|path| !path.is_empty()).map(str::to_string),
53 })
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[tokio::test]
62 async fn ipfs_url_no_path() {
63 let input: Ipfs = "ipfs://bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
64 .parse()
65 .unwrap();
66 assert_eq!(input.schema, IpfsSchema::Ipfs);
67 assert_eq!(
68 input.cid,
69 "bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
70 );
71 assert_eq!(input.path, None);
72 }
73
74 #[tokio::test]
75 async fn ipfs_url_short_schema() {
76 let input: Ipfs = "ipfs:/bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
77 .parse()
78 .unwrap();
79 assert_eq!(input.schema, IpfsSchema::Ipfs);
80 assert_eq!(
81 input.cid,
82 "bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
83 );
84 assert_eq!(input.path, None);
85 }
86
87 #[tokio::test]
88 async fn ipfs_url_ipns() {
89 let input: Ipfs = "ipns://bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
90 .parse()
91 .unwrap();
92 assert_eq!(input.schema, IpfsSchema::Ipns);
93 assert_eq!(
94 input.cid,
95 "bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
96 );
97 assert_eq!(input.path, None);
98 }
99
100 #[tokio::test]
101 async fn ipfs_url_path() {
102 let input: Ipfs =
103 "ipfs://bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu/hello/world.txt"
104 .parse()
105 .unwrap();
106 assert_eq!(input.schema, IpfsSchema::Ipfs);
107 assert_eq!(
108 input.cid,
109 "bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
110 );
111 assert_eq!(input.path.unwrap(), "hello/world.txt");
112 }
113}