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
24pub 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#[derive(Debug, Eq, PartialEq, Hash, Clone)]
43pub struct DidUrl {
44 method: DidMethod,
45 s: Utf8Bytes,
47 method_specific_id: std::ops::RangeFrom<usize>,
49}
50
51impl DidUrl {
52 pub fn as_str(&self) -> &str {
54 self.s.as_str()
55 }
56
57 pub fn as_slice(&self) -> &[u8] {
59 self.s.as_slice()
60 }
61
62 pub fn as_utf8_bytes(&self) -> &Utf8Bytes {
65 &self.s
66 }
67
68 pub fn method(&self) -> DidMethod {
70 self.method
71 }
72
73 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}