identity_iota_core/rebased/client/
read_only.rs1use std::future::Future;
5use std::ops::Deref;
6use std::pin::Pin;
7use std::str::FromStr;
8
9use async_trait::async_trait;
10use futures::stream::FuturesUnordered;
11use futures::StreamExt as _;
12use identity_core::common::Url;
13use identity_did::DID;
14use iota_interaction::types::base_types::ObjectID;
15use iota_interaction::IotaClientTrait;
16use product_common::core_client::CoreClientReadOnly;
17use product_common::network_name::NetworkName;
18
19use crate::iota_interaction_adapter::IotaClientAdapter;
20use crate::rebased::iota;
21use crate::rebased::migration::get_alias;
22use crate::rebased::migration::get_identity;
23use crate::rebased::migration::lookup;
24use crate::rebased::migration::Identity;
25use crate::rebased::Error;
26use crate::IotaDID;
27use crate::IotaDocument;
28
29#[cfg(not(target_arch = "wasm32"))]
30use iota_interaction::IotaClient;
31
32#[cfg(target_arch = "wasm32")]
33use iota_interaction_ts::bindings::WasmIotaClient;
34
35#[derive(Clone)]
38pub struct IdentityClientReadOnly {
39 iota_client: IotaClientAdapter,
40 package_history: Vec<ObjectID>,
41 network: NetworkName,
42 chain_id: String,
43}
44
45impl Deref for IdentityClientReadOnly {
46 type Target = IotaClientAdapter;
47 fn deref(&self) -> &Self::Target {
48 &self.iota_client
49 }
50}
51
52impl IdentityClientReadOnly {
53 pub fn package_id(&self) -> ObjectID {
57 *self
58 .package_history
59 .last()
60 .expect("at least one package exists in history")
61 }
62
63 pub const fn network(&self) -> &NetworkName {
66 &self.network
67 }
68
69 pub fn chain_id(&self) -> &str {
73 &self.chain_id
74 }
75
76 pub async fn new(
86 #[cfg(target_arch = "wasm32")] iota_client: WasmIotaClient,
87 #[cfg(not(target_arch = "wasm32"))] iota_client: IotaClient,
88 ) -> Result<Self, Error> {
89 let client = IotaClientAdapter::new(iota_client);
90 let network = network_id(&client).await?;
91 Self::new_internal(client, network).await
92 }
93
94 async fn new_internal(iota_client: IotaClientAdapter, network: NetworkName) -> Result<Self, Error> {
95 let chain_id = network.as_ref().to_string();
96 let (network, package_history) = {
97 let package_registry = iota::package::identity_package_registry().await;
98 let package_history = package_registry
99 .history(&network)
100 .ok_or_else(|| {
101 Error::InvalidConfig(format!(
102 "no information for a published `iota_identity` package on network {network}; try to use `IdentityClientReadOnly::new_with_package_id`"
103 ))
104 })?
105 .to_vec();
106 let network = package_registry
107 .chain_alias(&chain_id)
108 .and_then(|alias| NetworkName::try_from(alias).ok())
109 .unwrap_or(network);
110
111 (network, package_history)
112 };
113 Ok(IdentityClientReadOnly {
114 iota_client,
115 package_history,
116 network,
117 chain_id,
118 })
119 }
120
121 pub async fn new_with_pkg_id(
125 #[cfg(target_arch = "wasm32")] iota_client: WasmIotaClient,
126 #[cfg(not(target_arch = "wasm32"))] iota_client: IotaClient,
127 package_id: ObjectID,
128 ) -> Result<Self, Error> {
129 let client = IotaClientAdapter::new(iota_client);
130 let network = network_id(&client).await?;
131
132 {
134 let mut registry = iota::package::identity_package_registry_mut().await;
135 registry.insert_new_package_version(&network, package_id);
136 }
137
138 Self::new_internal(client, network).await
139 }
140
141 pub fn set_migration_registry_id(&mut self, id: ObjectID) {
145 crate::rebased::migration::set_migration_registry_id(&self.chain_id, id);
146 }
147
148 pub async fn resolve_did(&self, did: &IotaDID) -> Result<IotaDocument, Error> {
150 let identity = self.get_identity(get_object_id_from_did(did)?).await?;
151 let did_doc = identity.did_document(self.network())?;
152
153 match identity {
154 Identity::FullFledged(identity) if identity.has_deleted_did() => {
155 Err(Error::DIDResolutionError(format!("could not find DID Document {did}")))
156 }
157 _ => Ok(did_doc),
158 }
159 }
160
161 pub async fn get_identity(&self, object_id: ObjectID) -> Result<Identity, Error> {
163 cfg_if::cfg_if! {
165 if #[cfg(feature = "send-sync")] {
168 let all_futures = FuturesUnordered::<Pin<Box<dyn Future<Output = Result<Option<Identity>, Error>> + Send>>>::new();
169 } else {
170 let all_futures = FuturesUnordered::<Pin<Box<dyn Future<Output = Result<Option<Identity>, Error>>>>>::new();
171 }
172 }
173 all_futures.push(Box::pin(resolve_new(self, object_id)));
174 all_futures.push(Box::pin(resolve_migrated(self, object_id)));
175 all_futures.push(Box::pin(resolve_unmigrated(self, object_id)));
176
177 all_futures
178 .filter_map(|res| Box::pin(async move { res.ok().flatten() }))
179 .next()
180 .await
181 .ok_or_else(|| Error::DIDResolutionError(format!("could not find DID document for {object_id}")))
182 }
183}
184
185async fn network_id(iota_client: &IotaClientAdapter) -> Result<NetworkName, Error> {
186 let network_id = iota_client
187 .read_api()
188 .get_chain_identifier()
189 .await
190 .map_err(|e| Error::RpcError(e.to_string()))?;
191 Ok(network_id.try_into().expect("chain ID is a valid network name"))
192}
193
194async fn resolve_new(client: &IdentityClientReadOnly, object_id: ObjectID) -> Result<Option<Identity>, Error> {
195 let onchain_identity = get_identity(client, object_id).await.map_err(|err| {
196 Error::DIDResolutionError(format!(
197 "could not get identity document for object id {object_id}; {err}"
198 ))
199 })?;
200 Ok(onchain_identity.map(Identity::FullFledged))
201}
202
203async fn resolve_migrated(client: &IdentityClientReadOnly, object_id: ObjectID) -> Result<Option<Identity>, Error> {
204 let onchain_identity = lookup(client, object_id).await.map_err(|err| {
205 Error::DIDResolutionError(format!(
206 "failed to look up object_id {object_id} in migration registry; {err}"
207 ))
208 })?;
209 let Some(mut onchain_identity) = onchain_identity else {
210 return Ok(None);
211 };
212 let object_id_str = object_id.to_string();
213 let queried_did = IotaDID::from_object_id(&object_id_str, &client.network);
214 let doc = onchain_identity.did_document_mut();
215 let identity_did = doc.id().clone();
216 *doc.core_document_mut().id_mut_unchecked() = queried_did.clone().into();
220 doc
223 .also_known_as_mut()
224 .replace::<Url>(&queried_did.into_url().into(), identity_did.into_url().into());
225
226 Ok(Some(Identity::FullFledged(onchain_identity)))
227}
228
229async fn resolve_unmigrated(client: &IdentityClientReadOnly, object_id: ObjectID) -> Result<Option<Identity>, Error> {
230 let unmigrated_alias = get_alias(client, object_id)
231 .await
232 .map_err(|err| Error::DIDResolutionError(format!("could no query for object id {object_id}; {err}")))?;
233 Ok(unmigrated_alias.map(Identity::Legacy))
234}
235
236pub fn get_object_id_from_did(did: &IotaDID) -> Result<ObjectID, Error> {
242 ObjectID::from_str(did.tag_str())
243 .map_err(|err| Error::DIDResolutionError(format!("could not parse object id from did {did}; {err}")))
244}
245
246#[cfg_attr(feature = "send-sync", async_trait)]
247#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
248impl CoreClientReadOnly for IdentityClientReadOnly {
249 fn package_id(&self) -> ObjectID {
250 self.package_id()
251 }
252
253 fn network_name(&self) -> &NetworkName {
254 &self.network
255 }
256
257 fn client_adapter(&self) -> &IotaClientAdapter {
258 &self.iota_client
259 }
260
261 fn package_history(&self) -> Vec<ObjectID> {
262 self.package_history.clone()
263 }
264}