1use std::fmt::Debug;
5use std::fmt::Display;
6use std::str::FromStr;
7
8use identity_jose::jwk::Jwk;
9use identity_jose::jwu::decode_b64_json;
10
11use crate::CoreDID;
12use crate::Error;
13use crate::DID;
14
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
16#[repr(transparent)]
17#[serde(into = "CoreDID", try_from = "CoreDID")]
18pub struct DIDJwk(CoreDID);
20
21impl DIDJwk {
22 pub const METHOD: &'static str = "jwk";
24
25 pub fn parse(s: &str) -> Result<Self, Error> {
27 s.parse()
28 }
29
30 pub fn jwk(&self) -> Jwk {
32 decode_b64_json(self.method_id()).expect("did:jwk encodes a valid JWK")
33 }
34}
35
36impl AsRef<CoreDID> for DIDJwk {
37 fn as_ref(&self) -> &CoreDID {
38 &self.0
39 }
40}
41
42impl From<DIDJwk> for CoreDID {
43 fn from(value: DIDJwk) -> Self {
44 value.0
45 }
46}
47
48impl<'a> TryFrom<&'a str> for DIDJwk {
49 type Error = Error;
50 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
51 value.parse()
52 }
53}
54
55impl Display for DIDJwk {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 write!(f, "{}", self.0)
58 }
59}
60
61impl FromStr for DIDJwk {
62 type Err = Error;
63 fn from_str(s: &str) -> Result<Self, Self::Err> {
64 s.parse::<CoreDID>().and_then(TryFrom::try_from)
65 }
66}
67
68impl From<DIDJwk> for String {
69 fn from(value: DIDJwk) -> Self {
70 value.to_string()
71 }
72}
73
74impl TryFrom<CoreDID> for DIDJwk {
75 type Error = Error;
76 fn try_from(value: CoreDID) -> Result<Self, Self::Error> {
77 let Self::METHOD = value.method() else {
78 return Err(Error::InvalidMethodName);
79 };
80 decode_b64_json::<Jwk>(value.method_id())
81 .map(|_| Self(value))
82 .map_err(|_| Error::InvalidMethodId)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use identity_core::convert::FromJson;
89
90 use super::*;
91
92 #[test]
93 fn test_valid_deserialization() -> Result<(), Error> {
94 "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9".parse::<DIDJwk>()?;
95 "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9".parse::<DIDJwk>()?;
96
97 Ok(())
98 }
99
100 #[test]
101 fn test_jwk() {
102 let did = DIDJwk::parse("did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9").unwrap();
103 let target_jwk = Jwk::from_json_value(serde_json::json!({
104 "kty":"OKP","crv":"X25519","use":"enc","x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"
105 }))
106 .unwrap();
107
108 assert_eq!(did.jwk(), target_jwk);
109 }
110
111 #[test]
112 fn test_invalid_deserialization() {
113 assert!(
114 "did:iota:0xf4d6f08f5a1b80dd578da7dc1b49c886d580acd4cf7d48119dfeb82b538ad88a"
115 .parse::<DIDJwk>()
116 .is_err()
117 );
118 assert!("did:jwk:".parse::<DIDJwk>().is_err());
119 assert!("did:jwk:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
120 .parse::<DIDJwk>()
121 .is_err());
122 }
123}