did_utils/methods/
utils.rs

1use std::collections::HashMap;
2
3use url::Url;
4
5use super::errors::DIDResolutionError;
6
7pub type ParsedDIDUrl = (String, HashMap<String, String>, Option<String>);
8
9/// Parses DID URL into (did, query, fragment)
10pub(super) fn parse_did_url(did_url: &str) -> Result<ParsedDIDUrl, DIDResolutionError> {
11    if !did_url.starts_with("did:") {
12        return Err(DIDResolutionError::InvalidDidUrlPrefix);
13    }
14
15    if did_url.contains("%%") {
16        return Err(DIDResolutionError::InvalidDidUrlFormat);
17    }
18
19    if did_url.split(':').filter(|x| !x.is_empty()).count() < 3 {
20        return Err(DIDResolutionError::DidUrlPartLengthTooShort);
21    }
22
23    let url = format!("scheme://{}", did_url.replace(':', "%%"));
24    let url = Url::parse(&url).map_err(|_| DIDResolutionError::InvalidDidUrlFormat)?;
25    let domain = url.domain().ok_or(DIDResolutionError::InvalidDidUrlFormat)?;
26
27    let did = domain.replace("%%", ":");
28    if did.split(':').filter(|x| !x.is_empty()).count() < 3 {
29        return Err(DIDResolutionError::DidUrlPartLengthTooShort);
30    }
31
32    let query = url.query_pairs().map(|(key, val)| (key.to_string(), val.to_string())).collect();
33    let fragment = url.fragment().map(|x| x.to_string());
34
35    Ok((did, query, fragment))
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn test_did_url_parsing() {
44        let (did, query, fragment) = parse_did_url("did:key:abcd?x=a&y=b#f").unwrap();
45        assert_eq!(did, "did:key:abcd");
46        assert_eq!(query.get("x").unwrap(), "a");
47        assert_eq!(query.get("y").unwrap(), "b");
48        assert_eq!(fragment.unwrap(), "f");
49
50        let (did, query, fragment) = parse_did_url("did:web:example.com:a:b?m=hello+world").unwrap();
51        assert_eq!(did, "did:web:example.com:a:b");
52        assert_eq!(query.get("m").unwrap(), "hello world");
53        assert!(fragment.is_none());
54    }
55
56    #[test]
57    fn test_did_url_parsing_fails_as_expected() {
58        let entries = [
59            ("dxd:key:abcd", DIDResolutionError::InvalidDidUrlPrefix),
60            ("did:key:a%%d", DIDResolutionError::InvalidDidUrlFormat),
61            ("did:key", DIDResolutionError::DidUrlPartLengthTooShort),
62            ("did:key:", DIDResolutionError::DidUrlPartLengthTooShort),
63            ("did:key:?k=v", DIDResolutionError::DidUrlPartLengthTooShort),
64            ("did:key:ab\\cd", DIDResolutionError::InvalidDidUrlFormat),
65            ("did:key:abcd|80", DIDResolutionError::InvalidDidUrlFormat),
66            ("did:key:abcd[80]", DIDResolutionError::InvalidDidUrlFormat),
67        ];
68
69        for (did_url, err) in entries {
70            assert_eq!(parse_did_url(did_url).unwrap_err(), err);
71        }
72    }
73}