1use std::{fmt::Display, str::FromStr};
2
3use anyhow::bail;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7 did::Did,
8 uri::{Segment, is_segment},
9};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct DidUrl {
13 pub did: Did,
14 pub path_abempty: String,
17 pub query: Option<String>,
20 pub fragment: Option<String>,
23}
24
25impl Serialize for DidUrl {
26 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
27 where
28 S: serde::Serializer,
29 {
30 let v = self.to_string();
31 serializer.serialize_str(&v)
32 }
33}
34
35impl<'de> Deserialize<'de> for DidUrl {
36 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
37 where
38 D: serde::Deserializer<'de>,
39 {
40 let s = String::deserialize(deserializer)?;
41 Self::from_str(&s).map_err(|_| serde::de::Error::custom("parse err"))
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct RelativeDidUrl {
47 pub path: RelativeDidUrlPath,
48 pub query: Option<String>,
50 pub fragment: Option<String>,
52}
53
54impl Display for RelativeDidUrl {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 let path = self.path.to_string();
57 let query = match &self.query {
58 Some(q) => format!("?{q}"),
59 None => String::new(),
60 };
61 let fragment = match &self.fragment {
62 Some(f) => format!("#{f}"),
63 None => String::new(),
64 };
65 f.write_fmt(format_args!("{path}{query}{fragment}"))
66 }
67}
68
69impl FromStr for RelativeDidUrl {
70 type Err = anyhow::Error;
71
72 fn from_str(s: &str) -> Result<Self, Self::Err> {
73 let (path, query, fragment) = match s.split_once('?') {
74 Some((path, rest)) => match rest.split_once('#') {
75 Some((query, fragment)) => (path, Some(query), Some(fragment)),
76 None => (path, Some(rest), None),
77 },
78 None => match s.split_once('#') {
79 Some((path, fragment)) => (path, None, Some(fragment)),
80 None => (s, None, None),
81 },
82 };
83
84 Ok(Self {
85 path: RelativeDidUrlPath::from_str(path)?,
86 query: query.map(|s| s.to_string()),
87 fragment: fragment.map(|s| s.to_string()),
88 })
89 }
90}
91
92impl Serialize for RelativeDidUrl {
93 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
94 where
95 S: serde::Serializer,
96 {
97 let v = self.to_string();
98 serializer.serialize_str(&v)
99 }
100}
101
102impl<'de> Deserialize<'de> for RelativeDidUrl {
103 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
104 where
105 D: serde::Deserializer<'de>,
106 {
107 let s = String::deserialize(deserializer)?;
108 Self::from_str(&s).map_err(|_| serde::de::Error::custom("parse err"))
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub enum RelativeDidUrlPath {
114 Absolute(String),
116 NoScheme(String),
118 Empty,
120}
121
122impl Display for RelativeDidUrlPath {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 let data = match self {
125 Self::Absolute(s) | Self::NoScheme(s) => s.as_str(),
126 Self::Empty => "",
127 };
128 f.write_str(data)
129 }
130}
131
132impl FromStr for RelativeDidUrlPath {
133 type Err = anyhow::Error;
134
135 fn from_str(path: &str) -> Result<Self, Self::Err> {
136 if path.is_empty() {
137 return Ok(Self::Empty);
138 }
139 if path.starts_with('/') {
140 if path.len() >= 2 && path.chars().nth(1) == Some('/') {
142 bail!("double slash at start")
143 }
144
145 if !path
146 .split('/')
147 .skip(1)
148 .all(|v| is_segment(v, Segment::Base))
149 {
150 bail!("invalid segment")
151 }
152
153 Ok(Self::Absolute(path.to_string()))
154 } else {
155 if !path.split('/').all(|v| is_segment(v, Segment::NzNc)) {
157 bail!("invalid segment")
158 }
159
160 Ok(Self::NoScheme(path.to_string()))
161 }
162 }
163}
164
165impl DidUrl {
166 pub fn to_relative(&self) -> Option<RelativeDidUrl> {
168 Some(RelativeDidUrl {
169 path: match RelativeDidUrlPath::from_str(&self.path_abempty) {
170 Ok(v) => v,
171 Err(_) => return None,
172 },
173 fragment: self.fragment.clone(),
174 query: self.query.clone(),
175 })
176 }
177}
178
179impl Display for DidUrl {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 let mut url = format!("{}{}", self.did, self.path_abempty);
182
183 if let Some(ref query) = self.query {
184 url.push('?');
185 url.push_str(query);
186 }
187
188 if let Some(ref fragment) = self.fragment {
189 url.push('#');
190 url.push_str(fragment);
191 }
192
193 f.write_str(&url)
194 }
195}
196
197impl FromStr for DidUrl {
198 type Err = anyhow::Error;
199
200 fn from_str(s: &str) -> Result<Self, Self::Err> {
201 let (did_str, _) = s.split_once('/').unwrap_or_else(|| {
202 s.split_once('?')
203 .unwrap_or_else(|| s.split_once('#').unwrap_or((s, "")))
204 });
205
206 let did = Did::from_str(did_str)?;
207
208 let mut path_abempty = String::new();
209 let mut query = None;
210 let mut fragment = None;
211
212 let mut rest = s.strip_prefix(did_str).unwrap();
213 if let Some((before_fragment, frag)) = rest.split_once('#') {
214 fragment = Some(frag.to_string());
215 rest = before_fragment;
216 }
217
218 if let Some((before_query, qry)) = rest.split_once('?') {
219 query = Some(qry.to_string());
220 rest = before_query;
221 }
222
223 path_abempty.push_str(rest);
224
225 if !path_abempty.is_empty() {
227 if !path_abempty.starts_with('/') {
228 bail!("path_abempty does not start with slash")
229 }
230
231 if !path_abempty
232 .split('/')
233 .all(|v| is_segment(v, Segment::Base))
234 {
235 bail!("invalid path_abempty segment")
236 }
237 }
238
239 Ok(DidUrl {
240 did,
241 path_abempty,
242 query,
243 fragment,
244 })
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_did_url_full() {
254 let did_url = DidUrl {
255 did: Did::from_str("did:example:123").unwrap(),
256 path_abempty: "/path/to/resource".to_string(),
257 query: Some("key=value".to_string()),
258 fragment: Some("section".to_string()),
259 };
260
261 let serialized = did_url.to_string();
262 assert_eq!(
263 serialized,
264 "did:example:123/path/to/resource?key=value#section"
265 );
266
267 let deserialized = DidUrl::from_str(&serialized).expect("deserialize failed");
268 assert_eq!(deserialized, did_url);
269 }
270
271 #[test]
272 fn test_did_url_no_path() {
273 let did_url = DidUrl {
274 did: Did::from_str("did:example:123").unwrap(),
275 path_abempty: "".to_string(),
276 query: Some("key=value".to_string()),
277 fragment: Some("section".to_string()),
278 };
279
280 let serialized = did_url.to_string();
281 assert_eq!(serialized, "did:example:123?key=value#section");
282
283 let deserialized = DidUrl::from_str(&serialized).expect("deserialize failed");
284 assert_eq!(deserialized, did_url);
285 }
286
287 #[test]
288 fn test_did_url_no_query() {
289 let did_url = DidUrl {
290 did: Did::from_str("did:example:123").unwrap(),
291 path_abempty: "/path/to/resource".to_string(),
292 query: None,
293 fragment: Some("section".to_string()),
294 };
295
296 let serialized = did_url.to_string();
297 assert_eq!(serialized, "did:example:123/path/to/resource#section");
298
299 let deserialized = DidUrl::from_str(&serialized).expect("deserialize failed");
300 assert_eq!(deserialized, did_url);
301 }
302
303 #[test]
304 fn test_did_url_no_fragment() {
305 let did_url = DidUrl {
306 did: Did::from_str("did:example:123").unwrap(),
307 path_abempty: "/path/to/resource".to_string(),
308 query: Some("key=value".to_string()),
309 fragment: None,
310 };
311
312 let serialized = did_url.to_string();
313 assert_eq!(serialized, "did:example:123/path/to/resource?key=value");
314
315 let deserialized = DidUrl::from_str(&serialized).expect("deserialize failed");
316 assert_eq!(deserialized, did_url);
317 }
318
319 #[test]
320 fn test_did_url_none() {
321 let did_url = DidUrl {
322 did: Did::from_str("did:example:123").unwrap(),
323 path_abempty: "".to_string(),
324 query: None,
325 fragment: None,
326 };
327
328 let serialized = did_url.to_string();
329 assert_eq!(serialized, "did:example:123");
330
331 let deserialized = DidUrl::from_str(&serialized).expect("deserialize failed");
332 assert_eq!(deserialized, did_url);
333 }
334}