diff --git a/identity_iota_core/Cargo.toml b/identity_iota_core/Cargo.toml index ea8d43a845..c5fbfa1f89 100644 --- a/identity_iota_core/Cargo.toml +++ b/identity_iota_core/Cargo.toml @@ -45,9 +45,11 @@ rustdoc-args = ["--cfg", "docsrs"] default = ["client", "iota-client", "revocation-bitmap", "send-sync-client-ext"] # Exposes the IotaIdentityClient and IotaIdentityClientExt traits. client = ["dep:async-trait", "iota-sdk"] -# Enbales the implementation of the extension traits on the iota-sdk's Client. +# Enables the implementation of the extension traits on the iota-sdk's Client. iota-client = ["client", "iota-sdk/client", "iota-sdk/tls"] # Enables revocation with `RevocationBitmap2022`. revocation-bitmap = ["identity_credential/revocation-bitmap"] # Adds Send bounds on the futures produces by the client extension traits. send-sync-client-ext = [] +# Disables the blanket implementation of `IotaIdentityClientExt`. +test = ["client"] diff --git a/identity_iota_core/src/client/identity_client.rs b/identity_iota_core/src/client/identity_client.rs index 94b0cf88b2..34df1fd5f0 100644 --- a/identity_iota_core/src/client/identity_client.rs +++ b/identity_iota_core/src/client/identity_client.rs @@ -1,7 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::block::protocol::ProtocolParameters; +#[cfg(feature = "test")] +use iota_sdk::client::Client; use crate::block::address::Address; use crate::block::output::feature::SenderFeature; @@ -14,6 +15,7 @@ use crate::block::output::Feature; use crate::block::output::OutputId; use crate::block::output::RentStructure; use crate::block::output::UnlockCondition; +use crate::block::protocol::ProtocolParameters; use crate::Error; use crate::IotaDID; use crate::IotaDocument; @@ -192,7 +194,10 @@ pub trait IotaIdentityClientExt: IotaIdentityClient { } } +#[cfg(not(feature = "test"))] impl IotaIdentityClientExt for T where T: IotaIdentityClient {} +#[cfg(feature = "test")] +impl IotaIdentityClientExt for Client {} pub(super) async fn validate_network(client: &T, did: &IotaDID) -> Result<()> where diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index 60bda28350..bd28b248ad 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -31,6 +31,8 @@ features = ["send-sync-client-ext", "iota-client"] optional = true [dev-dependencies] +identity_iota_core = { version = "=1.1.1", path = "../identity_iota_core", features = ["test"] } +iota-sdk = { version = "1.0.2" } tokio = { version = "1.29.0", default-features = false, features = ["rt-multi-thread", "macros"] } [features] diff --git a/identity_resolver/src/error.rs b/identity_resolver/src/error.rs index 5a8c3c63f4..d72a78fd4a 100644 --- a/identity_resolver/src/error.rs +++ b/identity_resolver/src/error.rs @@ -68,4 +68,7 @@ pub enum ErrorCause { /// The method that is unsupported. method: String, }, + /// No client attached to the specific network. + #[error("none of the attached clients support the network {0}")] + UnsupportedNetwork(String), } diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index 10d5359db8..b8ceffbc7f 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -249,12 +249,14 @@ impl Resolver> { #[cfg(feature = "iota")] mod iota_handler { + use crate::ErrorCause; + use super::Resolver; use identity_document::document::CoreDocument; - use identity_iota_core::IotaClientExt; use identity_iota_core::IotaDID; use identity_iota_core::IotaDocument; use identity_iota_core::IotaIdentityClientExt; + use std::collections::HashMap; use std::sync::Arc; impl Resolver @@ -266,7 +268,7 @@ mod iota_handler { /// See also [`attach_handler`](Self::attach_handler). pub fn attach_iota_handler(&mut self, client: CLI) where - CLI: IotaClientExt + Send + Sync + 'static, + CLI: IotaIdentityClientExt + Send + Sync + 'static, { let arc_client: Arc = Arc::new(client); @@ -277,6 +279,58 @@ mod iota_handler { self.attach_handler(IotaDID::METHOD.to_owned(), handler); } + + /// Convenience method for attaching multiple handlers responsible for resolving IOTA DIDs + /// on multiple networks. + /// + /// + /// # Arguments + /// + /// * `clients` - A collection of tuples where each tuple contains the name of the network name and its + /// corresponding client. + /// + /// # Examples + /// + /// ```ignore + /// // Assume `smr_client` and `iota_client` are instances IOTA clients `iota_sdk::client::Client`. + /// attach_multiple_iota_handlers(vec![("smr", smr_client), ("iota", iota_client)]); + /// ``` + /// + /// # See Also + /// - [`attach_handler`](Self::attach_handler). + /// + /// # Note + /// + /// - Using `attach_iota_handler` or `attach_handler` for the IOTA method would override all + /// previously added clients. + /// - This function does not validate the provided configuration. Ensure that the provided + /// network name corresponds with the client, possibly by using `client.network_name()`. + pub fn attach_multiple_iota_handlers(&mut self, clients: I) + where + CLI: IotaIdentityClientExt + Send + Sync + 'static, + I: IntoIterator, + { + let arc_clients = Arc::new(clients.into_iter().collect::>()); + + let handler = move |did: IotaDID| { + let future_client = arc_clients.clone(); + async move { + let did_network = did.network_str(); + let client: &CLI = + future_client + .get(did_network) + .ok_or(crate::Error::new(ErrorCause::UnsupportedNetwork( + did_network.to_string(), + )))?; + client + .resolve_did(&did) + .await + .map_err(|err| crate::Error::new(ErrorCause::HandlerError { source: Box::new(err) })) + } + }; + + self.attach_handler(IotaDID::METHOD.to_owned(), handler); + } } } @@ -301,3 +355,63 @@ where .finish() } } + +#[cfg(test)] +mod tests { + use identity_iota_core::block::output::AliasId; + use identity_iota_core::block::output::AliasOutput; + use identity_iota_core::block::output::OutputId; + use identity_iota_core::block::protocol::ProtocolParameters; + use identity_iota_core::IotaDID; + use identity_iota_core::IotaDocument; + use identity_iota_core::IotaIdentityClient; + use identity_iota_core::IotaIdentityClientExt; + + use super::*; + + struct DummyClient(IotaDocument); + + #[async_trait::async_trait] + impl IotaIdentityClient for DummyClient { + async fn get_alias_output(&self, _id: AliasId) -> identity_iota_core::Result<(OutputId, AliasOutput)> { + unreachable!() + } + async fn get_protocol_parameters(&self) -> identity_iota_core::Result { + unreachable!() + } + } + + #[async_trait::async_trait] + impl IotaIdentityClientExt for DummyClient { + async fn resolve_did(&self, did: &IotaDID) -> identity_iota_core::Result { + if self.0.id().as_str() == did.as_str() { + Ok(self.0.clone()) + } else { + Err(identity_iota_core::Error::DIDResolutionError( + iota_sdk::client::error::Error::NoOutput(did.to_string()), + )) + } + } + } + + #[tokio::test] + async fn test_multiple_handlers() { + let did1 = + IotaDID::parse("did:iota:smr:0x0101010101010101010101010101010101010101010101010101010101010101").unwrap(); + let document = IotaDocument::new_with_id(did1.clone()); + let dummy_smr_client = DummyClient(document); + + let did2 = IotaDID::parse("did:iota:0x0101010101010101010101010101010101010101010101010101010101010101").unwrap(); + let document = IotaDocument::new_with_id(did2.clone()); + let dummy_iota_client = DummyClient(document); + + let mut resolver = Resolver::::new(); + resolver.attach_multiple_iota_handlers(vec![("iota", dummy_iota_client), ("smr", dummy_smr_client)]); + + let doc = resolver.resolve(&did1).await.unwrap(); + assert_eq!(doc.id(), &did1); + + let doc = resolver.resolve(&did2).await.unwrap(); + assert_eq!(doc.id(), &did2); + } +}