did_simple/
url.rs

1use std::{fmt::Display, str::FromStr};
2
3use crate::utf8bytes::Utf8Bytes;
4
5#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
6pub enum DidMethod {
7	Key,
8	Web,
9}
10
11impl FromStr for DidMethod {
12	type Err = ParseError;
13
14	fn from_str(s: &str) -> Result<Self, Self::Err> {
15		Ok(match s {
16			"key" => Self::Key,
17			"web" => Self::Web,
18			"" => return Err(ParseError::MissingMethod),
19			_ => return Err(ParseError::UnknownMethod),
20		})
21	}
22}
23
24/// Helper type to access data in the method-specific-id of a [`DidUrl`].
25pub struct MethodSpecificId<'a>(&'a DidUrl);
26
27impl MethodSpecificId<'_> {
28	pub fn as_str(&self) -> &str {
29		&(self.0.as_str()[self.0.method_specific_id.clone()])
30	}
31
32	pub fn as_slice(&self) -> &[u8] {
33		&(self.0.s.as_slice()[self.0.method_specific_id.clone()])
34	}
35
36	pub fn utf8_bytes(&self) -> Utf8Bytes {
37		self.0.s.clone().split_off(self.0.method_specific_id.start)
38	}
39}
40
41/// A Decentralized Identifier, including any path information, as a url.
42#[derive(Debug, Eq, PartialEq, Hash, Clone)]
43pub struct DidUrl {
44	method: DidMethod,
45	/// The string representation of the DID.
46	s: Utf8Bytes,
47	/// The substring for method-specific-id. This is a range index into `s`.
48	method_specific_id: std::ops::RangeFrom<usize>,
49}
50
51impl DidUrl {
52	/// Gets the buffer representing the url as a str.
53	pub fn as_str(&self) -> &str {
54		self.s.as_str()
55	}
56
57	/// Gets the buffer representing the url as a byte slice.
58	pub fn as_slice(&self) -> &[u8] {
59		self.s.as_slice()
60	}
61
62	/// Gets the buffer representing the url as a reference counted slice that
63	/// is guaranteed to be utf8.
64	pub fn as_utf8_bytes(&self) -> &Utf8Bytes {
65		&self.s
66	}
67
68	/// The method of the did.
69	pub fn method(&self) -> DidMethod {
70		self.method
71	}
72
73	/// Method-specific identity info.
74	pub fn method_specific_id(&self) -> MethodSpecificId {
75		MethodSpecificId(self)
76	}
77}
78
79impl FromStr for DidUrl {
80	type Err = ParseError;
81
82	fn from_str(s: &str) -> Result<Self, Self::Err> {
83		let (method, remaining) = s
84			.strip_prefix("did:")
85			.ok_or(ParseError::InvalidScheme)?
86			.split_once(':')
87			.ok_or(ParseError::MissingMethod)?;
88		let method = DidMethod::from_str(method)?;
89		let start_idx = s.len() - remaining.len();
90
91		Ok(DidUrl {
92			method,
93			s: Utf8Bytes::from(s.to_owned()),
94			method_specific_id: (start_idx..),
95		})
96	}
97}
98
99impl TryFrom<String> for DidUrl {
100	type Error = ParseError;
101
102	fn try_from(s: String) -> Result<Self, Self::Error> {
103		let (method, remaining) = s
104			.strip_prefix("did:")
105			.ok_or(ParseError::InvalidScheme)?
106			.split_once(':')
107			.ok_or(ParseError::MissingMethod)?;
108		let method = DidMethod::from_str(method)?;
109		let start_idx = s.len() - remaining.len();
110
111		Ok(DidUrl {
112			method,
113			s: Utf8Bytes::from(s),
114			method_specific_id: (start_idx..),
115		})
116	}
117}
118
119#[derive(Debug, thiserror::Error)]
120pub enum ParseError {
121	#[error("expected the did: scheme")]
122	InvalidScheme,
123	#[error("expected did:method, but method was not present")]
124	MissingMethod,
125	#[error("encountered unknown did:method")]
126	UnknownMethod,
127}
128
129impl Display for DidUrl {
130	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131		self.as_str().fmt(f)
132	}
133}
134
135#[cfg(test)]
136mod test {
137	use super::*;
138	use eyre::{Result, WrapErr};
139
140	fn common_test_cases() -> Vec<DidUrl> {
141		vec![DidUrl {
142			method: DidMethod::Key,
143			s: String::from("did:key:123456").into(),
144			method_specific_id: (8..),
145		}]
146	}
147
148	#[test]
149	fn test_parse() -> Result<()> {
150		for expected in common_test_cases() {
151			let s = expected.s.as_str().to_owned();
152			let from_str = DidUrl::from_str(&s).wrap_err("failed to from_str")?;
153			let try_from = DidUrl::try_from(s).wrap_err("failed to try_from")?;
154			assert_eq!(from_str, try_from);
155			assert_eq!(from_str, expected);
156		}
157		Ok(())
158	}
159
160	#[test]
161	fn test_display() {
162		for example in common_test_cases() {
163			assert_eq!(example.as_str(), format!("{example}"));
164		}
165	}
166}