1use crate::errors::DIDSCIDError;
5use affinidi_did_common::Document;
6use didwebvh_rs::{DIDWebVHState, log_entry::LogEntryMethods};
7use regex::Regex;
8use std::time::Duration;
9use tracing::{debug, error};
10
11pub mod errors;
12
13#[derive(Clone, Debug)]
14pub enum ScidMethod {
15 WebVH(String),
16
17 #[cfg(feature = "did-cheqd")]
18 Cheqd(String),
19}
20
21pub async fn resolve(
29 did: &str,
30 peer_src: Option<ScidMethod>,
31 timeout: Option<Duration>,
32) -> Result<Document, DIDSCIDError> {
33 if did.starts_with("did:scid:vh:1") {
34 match convert_scid_to_method(did, peer_src)? {
36 ScidMethod::WebVH(webvh_did) => {
37 debug!("Resolving WebVH DID: {}", webvh_did);
38 let mut method = DIDWebVHState::default();
39 match method.resolve(&webvh_did, timeout).await {
40 Ok((log_entry, _)) => {
41 Ok(serde_json::from_value(log_entry.get_did_document()?)?)
42 }
43 Err(e) => {
44 error!("Error: {:?}", e);
45 Err(DIDSCIDError::WebVHError(e))
46 }
47 }
48 }
49 #[cfg(feature = "did-cheqd")]
50 ScidMethod::Cheqd(cheqd_did) => {
51 use did_resolver_cheqd::DIDCheqd;
52 use ssi_dids_core::{DID, DIDResolver};
53
54 debug!("Resolving Cheqd DID: {}", cheqd_did);
55 match DIDCheqd::default()
56 .resolve(DID::new::<str>(&cheqd_did).unwrap())
57 .await
58 {
59 Ok(res) => {
60 let doc_value = serde_json::to_value(res.document.into_document())?;
61 Ok(serde_json::from_value(doc_value)?)
62 }
63 Err(e) => {
64 error!("Error: {:?}", e);
65 Err(DIDSCIDError::CheqdError(e.to_string()))
66 }
67 }
68 }
69 }
70 } else {
71 Err(DIDSCIDError::UnsupportedFormat)
72 }
73}
74
75fn convert_scid_to_method(
78 id: &str,
79 peer_src: Option<ScidMethod>,
80) -> Result<ScidMethod, DIDSCIDError> {
81 let re = Regex::new(r"^did:scid:vh:1:([^\?]*)(?:\?src=(.*))?$").unwrap();
82 if let Some(caps) = re.captures(id) {
83 if let Some(src) = caps.get(2).map(|m| m.as_str()) {
84 if src.starts_with("did:cheqd") {
86 let mut cheqd = String::new();
88 cheqd.push_str(src);
89 cheqd.push(':');
90 cheqd.push_str(&caps[1]);
91
92 debug!("derived cheqd DID: {cheqd}");
93 Ok(ScidMethod::Cheqd(cheqd))
94 } else if src.starts_with("did:") {
95 Err(DIDSCIDError::UnsupportedFormat)
97 } else {
98 let mut webvh = String::new();
100 webvh.push_str("did:webvh:");
101 webvh.push_str(&caps[1]);
102 webvh.push(':');
103 webvh.push_str(&src.replace("/", ":"));
104
105 debug!("derived webvh DID: {webvh}");
106 Ok(ScidMethod::WebVH(webvh))
107 }
108 } else {
109 match peer_src {
111 Some(ScidMethod::WebVH(src)) => {
112 let mut webvh = String::new();
113 webvh.push_str("did:webvh:");
114 webvh.push_str(&caps[1]);
115 webvh.push(':');
116 webvh.push_str(&src);
117
118 debug!("derived peer webvh DID: {webvh}");
119 Ok(ScidMethod::WebVH(webvh))
120 }
121 Some(ScidMethod::Cheqd(src)) => {
122 let mut cheqd = String::new();
123 cheqd.push_str("did:cheqd:");
124 cheqd.push_str(&src);
125 cheqd.push(':');
126 cheqd.push_str(&caps[1]);
127
128 debug!("derived peer cheqd DID: {cheqd}");
129 Ok(ScidMethod::Cheqd(cheqd))
130 }
131 None => Err(DIDSCIDError::MissingPeerSource),
132 }
133 }
134 } else {
135 Err(DIDSCIDError::UnsupportedFormat)
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use crate::{convert_scid_to_method, errors::DIDSCIDError, resolve};
142
143 #[test]
144 fn test_cheqd_conversion() {
145 match convert_scid_to_method("did:scid:vh:1:abcde?src=did:cheqd:mainnet", None) {
146 Ok(crate::ScidMethod::Cheqd(did)) => assert_eq!(did, "did:cheqd:mainnet:abcde"),
147 _ => panic!("Incorrect conversion"),
148 }
149 }
150
151 #[test]
152 fn test_cheqd_peer_conversion() {
153 match convert_scid_to_method(
154 "did:scid:vh:1:abcde",
155 Some(crate::ScidMethod::Cheqd("mainnet".to_string())),
156 ) {
157 Ok(crate::ScidMethod::Cheqd(did)) => assert_eq!(did, "did:cheqd:mainnet:abcde"),
158 _ => panic!("Incorrect conversion"),
159 }
160 }
161
162 #[test]
163 fn test_webvh_conversion() {
164 match convert_scid_to_method(
165 "did:scid:vh:1:abcde?src=stormer78.github.io/identity/fpp",
166 None,
167 ) {
168 Ok(crate::ScidMethod::WebVH(did)) => {
169 assert_eq!(did, "did:webvh:abcde:stormer78.github.io:identity:fpp")
170 }
171 _ => panic!("Incorrect conversion"),
172 }
173 }
174
175 #[test]
176 fn test_webvhpeer_conversion() {
177 match convert_scid_to_method(
178 "did:scid:vh:1:abcde",
179 Some(crate::ScidMethod::WebVH("stormer78.github.io".to_string())),
180 ) {
181 Ok(crate::ScidMethod::WebVH(did)) => {
182 assert_eq!(did, "did:webvh:abcde:stormer78.github.io")
183 }
184 _ => panic!("Incorrect conversion"),
185 }
186 }
187
188 #[test]
189 fn test_missing_peer() {
190 match convert_scid_to_method("did:scid:vh:1:abcde", None) {
191 Err(DIDSCIDError::MissingPeerSource) => {}
192 _ => panic!("Incorrect conversion"),
193 }
194 }
195
196 #[test]
197 fn test_bad_did_method() {
198 match convert_scid_to_method("did:scid:vh:1:abcde?src=did:example:abcd", None) {
199 Err(DIDSCIDError::UnsupportedFormat) => {}
200 _ => panic!("Incorrect conversion"),
201 }
202 }
203
204 #[test]
205 fn test_bad_id() {
206 match convert_scid_to_method("did:scid:invalid:1:abcde?src=did:example:abcd", None) {
207 Err(DIDSCIDError::UnsupportedFormat) => {}
208 _ => panic!("Incorrect conversion"),
209 }
210 }
211
212 #[tokio::test]
213 async fn test_scid_webvh_resolution() {
214 match resolve("did:scid:vh:1:Qmd1FCL9Vj2vJ433UDfC9MBstK6W6QWSQvYyeNn8va2fai?src=identity.foundation/didwebvh-implementations/implementations/affinidi-didwebvh-rs", None, None).await {
215 Ok(doc) => {
216 assert_eq!(doc.id.as_str(), "did:webvh:Qmd1FCL9Vj2vJ433UDfC9MBstK6W6QWSQvYyeNn8va2fai:identity.foundation:didwebvh-implementations:implementations:affinidi-didwebvh-rs");
217 }
218 Err(_) => panic!("Couldn't resolve SCID WebVH DID")
219 }
220 }
221
222 #[tokio::test]
223 async fn test_scid_cheqd_resolution() {
224 match resolve(
225 "did:scid:vh:1:cad53e1d-71e0-48d2-9352-39cc3d0fac99?src=did:cheqd:testnet",
226 None,
227 None,
228 )
229 .await
230 {
231 Ok(doc) => {
232 assert_eq!(
233 doc.id.as_str(),
234 "did:cheqd:testnet:cad53e1d-71e0-48d2-9352-39cc3d0fac99"
235 );
236 }
237 Err(_) => panic!("Couldn't resolve SCID Cheqd DID"),
238 }
239 }
240}