diff --git a/bindings/wasm/identity_wasm/src/common/types.rs b/bindings/wasm/identity_wasm/src/common/types.rs index 04d9793519..47d1abec5e 100644 --- a/bindings/wasm/identity_wasm/src/common/types.rs +++ b/bindings/wasm/identity_wasm/src/common/types.rs @@ -42,7 +42,7 @@ extern "C" { #[wasm_bindgen(typescript_type = "VerificationMethod[]")] pub type ArrayVerificationMethod; - #[wasm_bindgen(typescript_type = "Array")] + #[wasm_bindgen(typescript_type = "Array")] pub type ArrayCoreMethodRef; #[wasm_bindgen(typescript_type = "DIDUrl | string")] diff --git a/bindings/wasm/identity_wasm/src/did/mod.rs b/bindings/wasm/identity_wasm/src/did/mod.rs index 427b4be863..d8c1993c6b 100644 --- a/bindings/wasm/identity_wasm/src/did/mod.rs +++ b/bindings/wasm/identity_wasm/src/did/mod.rs @@ -9,6 +9,7 @@ mod wasm_core_did; mod wasm_core_document; mod wasm_did_jwk_document_ext; mod wasm_did_url; +mod wasm_method_ref; pub use self::jws_verification_options::*; pub use self::service::IService; @@ -23,5 +24,6 @@ pub use self::wasm_core_document::PromiseJws; pub use self::wasm_core_document::PromiseJwt; pub use self::wasm_core_document::WasmCoreDocument; pub use self::wasm_did_url::WasmDIDUrl; +pub use self::wasm_method_ref::WasmMethodRef; pub use did_compositejwk::*; pub use did_jwk::*; diff --git a/bindings/wasm/identity_wasm/src/did/wasm_core_document.rs b/bindings/wasm/identity_wasm/src/did/wasm_core_document.rs index 8748f03878..614f23cc34 100644 --- a/bindings/wasm/identity_wasm/src/did/wasm_core_document.rs +++ b/bindings/wasm/identity_wasm/src/did/wasm_core_document.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use super::WasmCoreDID; use super::WasmJwsVerificationOptions; +use super::WasmMethodRef; use crate::common::ArrayCoreMethodRef; use crate::common::ArrayService; use crate::common::ArrayString; @@ -58,7 +59,6 @@ use identity_iota::storage::key_storage::KeyType; use identity_iota::storage::storage::JwkDocumentExt; use identity_iota::storage::storage::JwsSignatureOptions; use identity_iota::verification::jose::jws::JwsAlgorithm; -use identity_iota::verification::MethodRef; use identity_iota::verification::MethodScope; use identity_iota::verification::VerificationMethod; @@ -220,10 +220,7 @@ impl WasmCoreDocument { .authentication() .iter() .cloned() - .map(|method_ref| match method_ref { - MethodRef::Embed(verification_method) => JsValue::from(WasmVerificationMethod(verification_method)), - MethodRef::Refer(did_url) => JsValue::from(WasmDIDUrl(did_url)), - }) + .map(|method_ref| JsValue::from(WasmMethodRef(method_ref))) .collect::() .unchecked_into::(), ) @@ -239,10 +236,7 @@ impl WasmCoreDocument { .assertion_method() .iter() .cloned() - .map(|method_ref| match method_ref { - MethodRef::Embed(verification_method) => JsValue::from(WasmVerificationMethod(verification_method)), - MethodRef::Refer(did_url) => JsValue::from(WasmDIDUrl(did_url)), - }) + .map(|method_ref| JsValue::from(WasmMethodRef(method_ref))) .collect::() .unchecked_into::(), ) @@ -258,10 +252,7 @@ impl WasmCoreDocument { .key_agreement() .iter() .cloned() - .map(|method_ref| match method_ref { - MethodRef::Embed(verification_method) => JsValue::from(WasmVerificationMethod(verification_method)), - MethodRef::Refer(did_url) => JsValue::from(WasmDIDUrl(did_url)), - }) + .map(|method_ref| JsValue::from(WasmMethodRef(method_ref))) .collect::() .unchecked_into::(), ) @@ -277,10 +268,7 @@ impl WasmCoreDocument { .capability_delegation() .iter() .cloned() - .map(|method_ref| match method_ref { - MethodRef::Embed(verification_method) => JsValue::from(WasmVerificationMethod(verification_method)), - MethodRef::Refer(did_url) => JsValue::from(WasmDIDUrl(did_url)), - }) + .map(|method_ref| JsValue::from(WasmMethodRef(method_ref))) .collect::() .unchecked_into::(), ) @@ -296,10 +284,7 @@ impl WasmCoreDocument { .capability_invocation() .iter() .cloned() - .map(|method_ref| match method_ref { - MethodRef::Embed(verification_method) => JsValue::from(WasmVerificationMethod(verification_method)), - MethodRef::Refer(did_url) => JsValue::from(WasmDIDUrl(did_url)), - }) + .map(|method_ref| JsValue::from(WasmMethodRef(method_ref))) .collect::() .unchecked_into::(), ) @@ -417,10 +402,7 @@ impl WasmCoreDocument { .try_read()? .verification_relationships() .cloned() - .map(|method_ref| match method_ref { - MethodRef::Embed(verification_method) => JsValue::from(WasmVerificationMethod(verification_method)), - MethodRef::Refer(did_url) => JsValue::from(WasmDIDUrl(did_url)), - }) + .map(|method_ref| JsValue::from(WasmMethodRef(method_ref))) .collect::() .unchecked_into::(), ) diff --git a/bindings/wasm/identity_wasm/src/did/wasm_method_ref.rs b/bindings/wasm/identity_wasm/src/did/wasm_method_ref.rs new file mode 100644 index 0000000000..3388223c49 --- /dev/null +++ b/bindings/wasm/identity_wasm/src/did/wasm_method_ref.rs @@ -0,0 +1,75 @@ +// Copyright 2020-2026 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::did::WasmDIDUrl; +use crate::error::Result; +use crate::error::WasmResult; +use crate::verification::WasmVerificationMethod; +use identity_iota::verification::MethodRef; +use wasm_bindgen::prelude::*; + +/// A reference held inside a verification relationship: either an embedded +/// {@link VerificationMethod}, an absolute DID URL (`"refer"`), or a relative +/// DID URL resolved against the document DID (`"relativeRefer"`). +#[wasm_bindgen(js_name = MethodRef, inspectable)] +pub struct WasmMethodRef(pub(crate) MethodRef); + +#[wasm_bindgen(js_class = MethodRef)] +impl WasmMethodRef { + /// Returns the variant: `"embedded"`, `"refer"`, or `"relativeRefer"`. + #[wasm_bindgen(getter, js_name = type)] + pub fn kind(&self) -> String { + match &self.0 { + MethodRef::Embed(_) => "embedded", + MethodRef::Refer(_) => "refer", + MethodRef::RelativeRefer(_) => "relativeRefer", + } + .to_owned() + } + + /// Returns the embedded {@link VerificationMethod}, or `undefined` for references. + #[wasm_bindgen(js_name = asVerificationMethod)] + pub fn as_verification_method(&self) -> Option { + if let MethodRef::Embed(method) = &self.0 { + Some(WasmVerificationMethod(method.clone())) + } else { + None + } + } + + /// Returns the resolved absolute {@link DIDUrl} for `"refer"` and `"relativeRefer"`, + /// or `undefined` for embedded methods. + /// + /// Note: for `"relativeRefer"` this is the *resolved* URL (e.g. `did:example:123#key-1`). + /// Use {@link MethodRef#toString} to obtain the original relative string (e.g. `"#key-1"`). + #[wasm_bindgen(js_name = asDIDUrl)] + pub fn as_did_url(&self) -> Option { + match &self.0 { + MethodRef::Refer(did_url) | MethodRef::RelativeRefer(did_url) => Some(WasmDIDUrl(did_url.clone())), + MethodRef::Embed(_) => None, + } + } + + /// Serializes as the document JSON representation: + /// - `"embedded"` → the full verification method object + /// - `"refer"` → the absolute DID URL string + /// - `"relativeRefer"` → the relative reference string (e.g. `"#key-1"`) + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + JsValue::from_serde(&self.0).wasm_result() + } + + /// Returns the string form consistent with the document JSON: + /// the relative string for `"relativeRefer"`, the full DID URL string for all others. + #[allow(clippy::inherent_to_string)] + #[wasm_bindgen(js_name = toString)] + pub fn to_string(&self) -> String { + match &self.0 { + MethodRef::Embed(method) => method.id().to_string(), + MethodRef::Refer(did_url) => did_url.to_string(), + MethodRef::RelativeRefer(did_url) => did_url.url().to_string(), + } + } +} + +impl_wasm_clone!(WasmMethodRef, MethodRef); diff --git a/bindings/wasm/identity_wasm/tests/core.ts b/bindings/wasm/identity_wasm/tests/core.ts index 9bde0e335d..8d1d76f7be 100644 --- a/bindings/wasm/identity_wasm/tests/core.ts +++ b/bindings/wasm/identity_wasm/tests/core.ts @@ -8,6 +8,7 @@ import { EdCurve, Jwk, JwkType, + MethodRef, MethodRelationship, MethodScope, MethodType, @@ -314,6 +315,66 @@ describe("CoreDocument", function() { assert.deepStrictEqual(doc.service().length, 0); }); }); + describe("#fromJSON / MethodRef types", function() { + const JSON_DOCUMENT = { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2020/v1", + ], + "id": "did:example:123", + "verificationMethod": [{ + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J", + }], + "authentication": [ + { + "id": "did:example:123#embedded-key", + "controller": "did:example:123", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J", + }, + "did:example:1234#key1", + "#key-1", + ], + }; + + it("deserializes all three MethodRef types", () => { + const auth = CoreDocument.fromJSON(JSON_DOCUMENT).authentication(); + + assert.strictEqual(auth[0].type, "embedded"); + assert.strictEqual(auth[0].asVerificationMethod()!.id().toString(), "did:example:123#embedded-key"); + + assert.strictEqual(auth[1].type, "refer"); + assert.strictEqual(auth[1].toString(), "did:example:1234#key1"); + + assert.strictEqual(auth[2].type, "relativeRefer"); + assert.strictEqual(auth[2].toString(), "#key-1"); + assert.strictEqual(auth[2].asDIDUrl()!.toString(), "did:example:123#key-1"); + }); + + it("extracts relative reference string from RelativeRefer", () => { + const auth: MethodRef[] = CoreDocument.fromJSON(JSON_DOCUMENT).authentication(); + + assert.strictEqual(auth[0].asVerificationMethod()!.id().toString(), "did:example:123#embedded-key"); + assert.strictEqual(auth[1].asDIDUrl()!.toString(), "did:example:1234#key1"); + // asDIDUrl() returns the resolved absolute URL; toString() returns the original relative reference + assert.strictEqual(auth[2].asDIDUrl()!.toString(), "did:example:123#key-1"); + assert.strictEqual(auth[2].toString(), "#key-1"); + }); + + it("serializes all three MethodRef types correctly", () => { + const auth = CoreDocument.fromJSON(JSON_DOCUMENT).toJSON().authentication; + + assert.strictEqual(typeof auth[0], "object"); + assert.strictEqual(auth[0].id, "did:example:123#embedded-key"); + + assert.strictEqual(auth[1], "did:example:1234#key1"); + + assert.strictEqual(auth[2], "#key-1"); + }); + }); describe("#properties", function() { it("should work", () => { const doc = new CoreDocument({ diff --git a/identity_document/Cargo.toml b/identity_document/Cargo.toml index f6875ae32d..46868c69dd 100644 --- a/identity_document/Cargo.toml +++ b/identity_document/Cargo.toml @@ -17,12 +17,12 @@ identity_did = { version = "=1.9.10-beta.1", path = "../identity_did" } identity_verification = { version = "=1.9.10-beta.1", path = "../identity_verification", default-features = false } indexmap = { version = "2.0", default-features = false, features = ["std", "serde"] } serde.workspace = true +serde_json.workspace = true strum.workspace = true thiserror.workspace = true [dev-dependencies] criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] } -serde_json.workspace = true [[bench]] name = "deserialize_document" diff --git a/identity_document/src/document/core_document.rs b/identity_document/src/document/core_document.rs index 8887eeea72..d69383f771 100644 --- a/identity_document/src/document/core_document.rs +++ b/identity_document/src/document/core_document.rs @@ -36,7 +36,7 @@ use identity_verification::MethodRelationship; use identity_verification::MethodScope; use identity_verification::VerificationMethod; -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] #[rustfmt::skip] pub(crate) struct CoreDocumentData { @@ -90,6 +90,7 @@ impl CoreDocumentData { .map(|method_ref| match method_ref { MethodRef::Embed(_) => (method_ref.id(), true), MethodRef::Refer(_) => (method_ref.id(), false), + MethodRef::RelativeRefer(_) => (method_ref.id(), false), }) { if let Some(previous) = method_identifiers.insert(id, is_embedded) { @@ -223,6 +224,73 @@ impl CoreDocumentData { properties: current_data.properties, }) } + + /// Try parsing given [`serde_json::Value`] into set of [`MethodRef`](identity_verification::MethodRef). + fn try_parse_method_ref_set( + id: &CoreDID, + value: &Option, + ) -> std::result::Result, E> + where + E: serde::de::Error, + { + let set = match value { + // array with values (expected format if present) + Some(serde_json::Value::Array(array_value)) => array_value + .iter() + .map(|entry| MethodRef::try_from_value(entry, id).map_err(serde::de::Error::custom)) + .collect::>()?, + // rely on serde_json to generate error message for other formats + Some(non_array_value) => serde_json::from_value(non_array_value.clone()).map_err(serde::de::Error::custom)?, + // fallback if omitted + None => OrderedSet::default(), + }; + Ok(set) + } +} + +impl<'de> serde::Deserialize<'de> for CoreDocumentData { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + /// Helper struct, that resembles [`CoreDocumentData`] structure except that + /// `OrderedSet` fields are represented as [`serde_json::Value`] for parsing. + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub(crate) struct CoreDocumentDataInner { + pub(crate) id: CoreDID, + pub(crate) controller: Option>, + #[serde(default = "Default::default")] + pub(crate) also_known_as: OrderedSet, + #[serde(default = "Default::default")] + pub(crate) verification_method: OrderedSet, + pub(crate) authentication: Option, + pub(crate) assertion_method: Option, + pub(crate) key_agreement: Option, + pub(crate) capability_delegation: Option, + pub(crate) capability_invocation: Option, + #[serde(default = "Default::default")] + pub(crate) service: OrderedSet, + #[serde(flatten)] + pub(crate) properties: Object, + } + + let data = CoreDocumentDataInner::deserialize(deserializer)?; + + Ok(CoreDocumentData { + id: data.id.clone(), + controller: data.controller, + also_known_as: data.also_known_as, + verification_method: data.verification_method, + authentication: Self::try_parse_method_ref_set(&data.id, &data.authentication)?, + assertion_method: Self::try_parse_method_ref_set(&data.id, &data.assertion_method)?, + key_agreement: Self::try_parse_method_ref_set(&data.id, &data.key_agreement)?, + capability_delegation: Self::try_parse_method_ref_set(&data.id, &data.capability_delegation)?, + capability_invocation: Self::try_parse_method_ref_set(&data.id, &data.capability_invocation)?, + service: data.service, + properties: data.properties, + }) + } } /// A DID Document. @@ -236,7 +304,7 @@ pub struct CoreDocument pub(crate) data: CoreDocumentData, } -//Forward serialization to inner +// Forward serialization to inner impl Serialize for CoreDocument { fn serialize(&self, serializer: S) -> Result where @@ -252,6 +320,7 @@ macro_rules! method_ref_mut_helper { match $doc.data.$method.query_mut($query.into())? { MethodRef::Embed(method) => Some(method), MethodRef::Refer(ref did) => $doc.data.verification_method.query_mut(did), + MethodRef::RelativeRefer(ref did) => $doc.data.verification_method.query_mut(did), } }; } @@ -655,6 +724,7 @@ impl CoreDocument { match method { MethodRef::Embed(method) => Some(method), MethodRef::Refer(_) => None, + MethodRef::RelativeRefer(_) => None, } } @@ -738,7 +808,7 @@ impl CoreDocument { /// # Warning /// /// Incorrect use of this method can lead to distinct document resources being identified by the same DID URL. - // NOTE: This method demonstrates unexpected behaviour in the edge cases where the document contains methods + // NOTE: This method demonstrates unexpected behavior in the edge cases where the document contains methods // whose ids are of the form #. pub fn resolve_method_mut<'query, 'me, Q>( &'me mut self, @@ -786,6 +856,7 @@ impl CoreDocument { match method_ref { MethodRef::Embed(method) => Some(method), MethodRef::Refer(did) => self.data.verification_method.query(did), + MethodRef::RelativeRefer(did) => self.data.verification_method.query(did), } } @@ -815,6 +886,7 @@ impl CoreDocument { match method { Some(MethodRef::Embed(method)) => Some(method), Some(MethodRef::Refer(did)) => self.data.verification_method.query(&did.to_string()), + Some(MethodRef::RelativeRefer(did)) => self.data.verification_method.query(&did.to_string()), None => self.data.verification_method.query(query), } } @@ -845,6 +917,7 @@ impl CoreDocument { match method { Some(MethodRef::Embed(method)) => Some(method), Some(MethodRef::Refer(did)) => self.data.verification_method.query_mut(&did.to_string()), + Some(MethodRef::RelativeRefer(did)) => self.data.verification_method.query_mut(&did.to_string()), None => self.data.verification_method.query_mut(query), } } @@ -1080,6 +1153,8 @@ impl CoreDocument { #[cfg(test)] mod tests { + use std::str::FromStr; + use identity_core::convert::FromJson; use identity_core::convert::ToJson; use identity_did::DID; @@ -1570,6 +1645,84 @@ mod tests { assert!(doc.is_ok()); } + mod method_ref { + use super::*; + + // The verification method types here are really Ed25519VerificationKey2020, changed to be + // compatible with the current version of this library. + const JSON_DOCUMENT: &str = r###"{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "did:example:123", + "verificationMethod": [ + { + "id": "did:example:1234#key1", + "controller": "did:example:1234", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + } + ], + "authentication": [ + { + "id": "did:example:123#embedded-key", + "controller": "did:example:123", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" + }, + "did:example:1234#key1", + "#key-1" + ] + }"###; + + #[test] + fn deserialize_method_ref_types() { + let deserialized: CoreDocument = CoreDocument::from_json(JSON_DOCUMENT).unwrap(); + let auth: Vec<&MethodRef> = deserialized.authentication().iter().collect(); + + assert!(matches!( + auth[0], + MethodRef::Embed(m) if m.id() == &DIDUrl::from_str("did:example:123#embedded-key").unwrap() + )); + assert_eq!( + auth[1], + &MethodRef::Refer(DIDUrl::from_str("did:example:1234#key1").unwrap()) + ); + assert_eq!( + auth[2], + &MethodRef::RelativeRefer(DIDUrl::from_str("did:example:123#key-1").unwrap()) + ); + } + + #[test] + fn relative_refer_to_string() { + let doc: CoreDocument = CoreDocument::from_json(JSON_DOCUMENT).unwrap(); + let auth: Vec<&MethodRef> = doc.authentication().iter().collect(); + + assert_eq!(auth[0].id().to_string(), "did:example:123#embedded-key"); + assert_eq!(auth[1].id().to_string(), "did:example:1234#key1"); + // url() strips the DID prefix, recovering the original relative reference + let MethodRef::RelativeRefer(did_url) = auth[2] else { + panic!("expected RelativeRefer"); + }; + assert_eq!(did_url.to_string(), "did:example:123#key-1"); + assert_eq!(did_url.url().to_string(), "#key-1"); + } + + #[test] + fn serialize_method_ref_types() { + let deserialized: CoreDocument = CoreDocument::from_json(JSON_DOCUMENT).unwrap(); + let serialized: serde_json::Value = serde_json::from_str(&serde_json::to_string(&deserialized).unwrap()).unwrap(); + let auth = &serialized["authentication"]; + + assert!(auth[0].is_object()); + assert_eq!(auth[0]["id"].as_str(), Some("did:example:123#embedded-key")); + assert_eq!(auth[1].as_str(), Some("did:example:1234#key1")); + assert_eq!(auth[2].as_str(), Some("#key-1")); + } + } + #[test] fn deserialize_duplicate_method_different_scopes() { const JSON_VERIFICATION_METHOD_KEY_AGREEMENT: &str = r#"{ diff --git a/identity_verification/src/error.rs b/identity_verification/src/error.rs index 2c9f2a3534..d5108bdd01 100644 --- a/identity_verification/src/error.rs +++ b/identity_verification/src/error.rs @@ -42,4 +42,7 @@ pub enum Error { /// Caused by key material that is not a Composite Public Key. #[error("verification material format is not compositePublicKey")] NotCompositePublicKey, + /// Failed to deserialize [`MethodRef`](crate::MethodRef) + #[error("invalid method ref; {0}")] + InvalidMethodRef(#[from] serde_json::Error), } diff --git a/identity_verification/src/verification_method/method_ref.rs b/identity_verification/src/verification_method/method_ref.rs index 29ec574df7..568758aefe 100644 --- a/identity_verification/src/verification_method/method_ref.rs +++ b/identity_verification/src/verification_method/method_ref.rs @@ -5,19 +5,25 @@ use core::fmt::Debug; use core::fmt::Formatter; use identity_core::common::KeyComparable; +use identity_did::DID; +use serde::Serialize; +use serde::Serializer; use crate::verification_method::VerificationMethod; +use crate::Error; use identity_did::CoreDID; use identity_did::DIDUrl; /// A reference to a verification method, either a `DID` or embedded `Method`. -#[derive(Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, PartialEq, Eq, Deserialize)] #[serde(untagged)] pub enum MethodRef { /// A [`VerificationMethod`] embedded in a verification relationship. Embed(VerificationMethod), /// A reference to a [`VerificationMethod`] in a verification relationship. Refer(DIDUrl), + /// A relative reference to a [`VerificationMethod`] in current document + RelativeRefer(DIDUrl), } impl MethodRef { @@ -26,6 +32,7 @@ impl MethodRef { match self { Self::Embed(inner) => inner.id(), Self::Refer(inner) => inner, + Self::RelativeRefer(inner) => inner, } } @@ -36,6 +43,7 @@ impl MethodRef { match self { Self::Embed(inner) => Some(inner.controller()), Self::Refer(_) => None, + Self::RelativeRefer(_) => None, } } @@ -61,6 +69,7 @@ impl MethodRef { match self { MethodRef::Embed(method) => MethodRef::Embed(method.map(f)), MethodRef::Refer(id) => MethodRef::Refer(id.map(f)), + MethodRef::RelativeRefer(id) => MethodRef::Refer(id.map(f)), } } @@ -72,6 +81,7 @@ impl MethodRef { Ok(match self { MethodRef::Embed(method) => MethodRef::Embed(method.try_map(f)?), MethodRef::Refer(id) => MethodRef::Refer(id.try_map(f)?), + MethodRef::RelativeRefer(id) => MethodRef::Refer(id.try_map(f)?), }) } @@ -86,6 +96,7 @@ impl MethodRef { match self { Self::Embed(inner) => Ok(inner), Self::Refer(_) => Err(self.into()), + Self::RelativeRefer(_) => Err(self.into()), } } @@ -100,8 +111,27 @@ impl MethodRef { match self { Self::Embed(_) => Err(self.into()), Self::Refer(inner) => Ok(inner), + Self::RelativeRefer(inner) => Ok(inner), } } + + /// Try to build instance from [`serde_json::Value`]. + pub fn try_from_value(value: &serde_json::Value, id: &CoreDID) -> Result { + let parsed = match value { + // relative references will be joined with document id + serde_json::Value::String(string_value) => { + if !string_value.starts_with("did:") { + MethodRef::RelativeRefer(id.clone().join(string_value).map_err(Error::DIDUrlConstructionError)?) + } else { + serde_json::from_value(value.clone())? + } + } + // otherwise parse as usual + _ => serde_json::from_value(value.clone())?, + }; + + Ok(parsed) + } } impl Debug for MethodRef { @@ -109,6 +139,7 @@ impl Debug for MethodRef { match self { Self::Embed(inner) => Debug::fmt(inner, f), Self::Refer(inner) => Debug::fmt(inner, f), + Self::RelativeRefer(inner) => Debug::fmt(inner, f), } } } @@ -142,3 +173,16 @@ impl KeyComparable for MethodRef { self.id() } } + +impl Serialize for MethodRef { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::Embed(value) => value.serialize(serializer), + Self::Refer(value) => value.serialize(serializer), + Self::RelativeRefer(value) => serializer.serialize_str(&value.url().to_string()), + } + } +}