Skip to main content

did_resolver_cheqd/
lib.rs

1//! This crate contains a resolver for DIDs of the [did:cheqd](https://docs.cheqd.io/product/architecture/adr-list/adr-001-cheqd-did-method) method.
2//! The implementation resolves DIDs via gRPC network requests to the configured nodes. Default nodes for cheqd's `mainnet` & `testnet` can be used,
3//! or custom nodes can be opt-in by supplying a different gRPC URL configuration.
4//!
5//! This crate uses gRPC types and clients generated using [tonic](https://github.com/hyperium/tonic).
6//! The generated rust code is checked-in to this repository for monitoring, [see here](./src/proto/mod.rs).
7//! These generated rust files are checked-in alongside the V2 cheqd proto files & dependencies.
8//! which are sourced from [cheqd's Buf registry](https://buf.build/cheqd/proto/docs).
9//!
10//! Since the generated code & proto files are not relatively large nor overwhelming in content, they are checked-in rather than pulled and/or generated at build time. The benefit is that the contents of the files can be monitored with each update, making supply-chain attacks obvious. It also reduces the build time complexity for consumers - such as reducing requirements for any 3rd party build tools to be installed (`protobuf`). The drawback is that it introduces some more manual maintainence.
11//! The crate exports the `DIDCheqd` type which implements the
12//! [`ssi_dids_core::DIDMethod`] and
13//! [`ssi_dids_core::resolution::DIDMethodResolver`] traits. This crate is
14//! uses cheqd network's GRPC
15//!
16//! # Example
17//!
18//! The example below is intentionally minimal and self-contained so it can be
19//! executed as a doc-test (no network calls, no async runtime). It verifies the
20//! public associated constant and basic construction of the type. This keeps
21//! `cargo test --doc` and tools like `cargo-rdme` reliable.
22//!
23//! ```
24//! use did_resolver_cheqd::DIDCheqd;
25//! use ssi_dids_core::DIDMethod;
26//! // Confirm the API constant and that we can construct the value
27//! assert_eq!(DIDCheqd::DID_METHOD_NAME, "cheqd");
28//! let _ = DIDCheqd::default();
29//! let _ = DIDCheqd::new(None);
30//! let _ = DIDCheqd::new(Some(DidCheqdResolverConfiguration {
31//!     networks: vec![
32//!         NetworkConfiguration {
33//!             grpc_url: "https://grpc.cheqd.net:443".to_string(),
34//!             namespace: "mainnet".to_string(),
35//!         },
36//!     ],
37//! }));
38//! ```
39//!
40//! # Library features
41//!
42//! - Implements a `DIDMethodResolver` for the `did:cheqd` DID method.
43//! - Exposes `resolution`, `proto` and `error` modules for integration.
44
45use crate::resolution::parser::DidCheqdParser;
46use crate::resolution::resolver::{DidCheqdResolver, DidCheqdResolverConfiguration};
47use crate::resolution::transformer::cheqd_diddoc_to_json;
48use serde_json::to_vec;
49use ssi_dids_core::{
50    DIDMethod, DIDResolver,
51    document::{self, representation::MediaType},
52    resolution::{Error, Metadata as ResolutionMetadata, Options, Output},
53};
54
55pub mod error;
56pub mod proto;
57pub mod resolution;
58
59pub struct DIDCheqd {
60    /// Resolver configuration used when resolving DIDs/resources.
61    pub config: DidCheqdResolverConfiguration,
62}
63
64impl DIDCheqd {
65    /// Create a resolver using an optional custom configuration.
66    /// If `None` is provided, it defaults to `DidCheqdResolverConfiguration::default()`.
67    pub fn new(config: Option<DidCheqdResolverConfiguration>) -> Self {
68        Self {
69            config: config.unwrap_or_default(),
70        }
71    }
72}
73
74impl Default for DIDCheqd {
75    fn default() -> Self {
76        Self::new(None)
77    }
78}
79
80impl DIDMethod for DIDCheqd {
81    const DID_METHOD_NAME: &'static str = "cheqd";
82}
83
84impl DIDResolver for DIDCheqd {
85    async fn resolve_representation<'a>(
86        &'a self,
87        did: &'a ssi_dids_core::DID,
88        options: Options,
89    ) -> Result<Output<Vec<u8>>, Error> {
90        // Try parse as a DID URL (resource) first, otherwise as a DID
91        // We will use the internal cheqd resolver to fetch a DidDocument or a resource and
92        // then convert it into bytes (JSON-LD) to match the did:key style Output.
93        // Use the resolver configuration provided to this DIDCheqd instance.
94        let cfg = self.config.clone();
95        let resolver = DidCheqdResolver::new(cfg);
96
97        // Check if it's a DidUrl (resource)
98        let parsed = DidCheqdParser::parse(did.as_str())
99            .map_err(|e| Error::InvalidMethodSpecificId(e.to_string()))?;
100
101        if parsed.query.is_some() {
102            // treat as a full did URL
103            match resolver.query_resource_by_str(did.as_str(), parsed).await {
104                Ok((content_bytes, media_type)) => {
105                    return Ok(Output::new(
106                        content_bytes,
107                        document::Metadata::default(),
108                        ResolutionMetadata::from_content_type(media_type),
109                    ));
110                }
111                Err(e) => return Err(Error::internal(format!("cheqd resolver error: {e:?}"))),
112            }
113        }
114
115        match resolver.query_did_doc_by_str(did.as_str(), parsed).await {
116            Ok((proto_doc, metadata)) => {
117                // convert proto DIDDoc to a JSON representation and serialize
118                let json_value = cheqd_diddoc_to_json(proto_doc)
119                    .map_err(|e| Error::internal(format!("cheqd transform error: {e:?}")))?;
120                let json = to_vec(&json_value).map_err(|e| {
121                    Error::internal(format!("failed to serialize DID document: {e}"))
122                })?;
123
124                let content_type = options.accept.unwrap_or(MediaType::JsonLd);
125
126                Ok(Output::new(
127                    json,
128                    match metadata {
129                        Some(meta) => document::Metadata {
130                            deactivated: Some(meta.deactivated),
131                        },
132                        None => document::Metadata { deactivated: None },
133                    },
134                    ResolutionMetadata::from_content_type(Some(content_type.to_string())),
135                ))
136            }
137            Err(e) => Err(Error::internal(format!("cheqd resolver error: {e:?}"))),
138        }
139    }
140}