did-resolver-cheqd 1.0.1

DID Resolver Rust implementation for did:cheqd method, using the ssi crate and Cheqd GRPC APIs
Documentation
//! 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.
//! The implementation resolves DIDs via gRPC network requests to the configured nodes. Default nodes for cheqd's `mainnet` & `testnet` can be used,
//! or custom nodes can be opt-in by supplying a different gRPC URL configuration.
//!
//! This crate uses gRPC types and clients generated using [tonic](https://github.com/hyperium/tonic).
//! The generated rust code is checked-in to this repository for monitoring, [see here](./src/proto/mod.rs).
//! These generated rust files are checked-in alongside the V2 cheqd proto files & dependencies.
//! which are sourced from [cheqd's Buf registry](https://buf.build/cheqd/proto/docs).
//!
//! 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.
//! The crate exports the `DIDCheqd` type which implements the
//! [`ssi_dids_core::DIDMethod`] and
//! [`ssi_dids_core::resolution::DIDMethodResolver`] traits. This crate is
//! uses cheqd network's GRPC
//!
//! # Example
//!
//! The example below is intentionally minimal and self-contained so it can be
//! executed as a doc-test (no network calls, no async runtime). It verifies the
//! public associated constant and basic construction of the type. This keeps
//! `cargo test --doc` and tools like `cargo-rdme` reliable.
//!
//! ```
//! use did_resolver_cheqd::DIDCheqd;
//! use ssi_dids_core::DIDMethod;
//! // Confirm the API constant and that we can construct the value
//! assert_eq!(DIDCheqd::DID_METHOD_NAME, "cheqd");
//! let _ = DIDCheqd::default();
//! let _ = DIDCheqd::new(None);
//! let _ = DIDCheqd::new(Some(DidCheqdResolverConfiguration {
//!     networks: vec![
//!         NetworkConfiguration {
//!             grpc_url: "https://grpc.cheqd.net:443".to_string(),
//!             namespace: "mainnet".to_string(),
//!         },
//!     ],
//! }));
//! ```
//!
//! # Library features
//!
//! - Implements a `DIDMethodResolver` for the `did:cheqd` DID method.
//! - Exposes `resolution`, `proto` and `error` modules for integration.

use crate::resolution::parser::DidCheqdParser;
use crate::resolution::resolver::{DidCheqdResolver, DidCheqdResolverConfiguration};
use crate::resolution::transformer::cheqd_diddoc_to_json;
use serde_json::to_vec;
use ssi_dids_core::{
    DIDMethod, DIDResolver,
    document::{self, representation::MediaType},
    resolution::{Error, Metadata as ResolutionMetadata, Options, Output},
};

pub mod error;
pub mod proto;
pub mod resolution;

pub struct DIDCheqd {
    /// Resolver configuration used when resolving DIDs/resources.
    pub config: DidCheqdResolverConfiguration,
}

impl DIDCheqd {
    /// Create a resolver using an optional custom configuration.
    /// If `None` is provided, it defaults to `DidCheqdResolverConfiguration::default()`.
    pub fn new(config: Option<DidCheqdResolverConfiguration>) -> Self {
        Self {
            config: config.unwrap_or_default(),
        }
    }
}

impl Default for DIDCheqd {
    fn default() -> Self {
        Self::new(None)
    }
}

impl DIDMethod for DIDCheqd {
    const DID_METHOD_NAME: &'static str = "cheqd";
}

impl DIDResolver for DIDCheqd {
    async fn resolve_representation<'a>(
        &'a self,
        did: &'a ssi_dids_core::DID,
        options: Options,
    ) -> Result<Output<Vec<u8>>, Error> {
        // Try parse as a DID URL (resource) first, otherwise as a DID
        // We will use the internal cheqd resolver to fetch a DidDocument or a resource and
        // then convert it into bytes (JSON-LD) to match the did:key style Output.
        // Use the resolver configuration provided to this DIDCheqd instance.
        let cfg = self.config.clone();
        let resolver = DidCheqdResolver::new(cfg);

        // Check if it's a DidUrl (resource)
        let parsed = DidCheqdParser::parse(did.as_str())
            .map_err(|e| Error::InvalidMethodSpecificId(e.to_string()))?;

        if parsed.query.is_some() {
            // treat as a full did URL
            match resolver.query_resource_by_str(did.as_str(), parsed).await {
                Ok((content_bytes, media_type)) => {
                    return Ok(Output::new(
                        content_bytes,
                        document::Metadata::default(),
                        ResolutionMetadata::from_content_type(media_type),
                    ));
                }
                Err(e) => return Err(Error::internal(format!("cheqd resolver error: {e:?}"))),
            }
        }

        match resolver.query_did_doc_by_str(did.as_str(), parsed).await {
            Ok((proto_doc, metadata)) => {
                // convert proto DIDDoc to a JSON representation and serialize
                let json_value = cheqd_diddoc_to_json(proto_doc)
                    .map_err(|e| Error::internal(format!("cheqd transform error: {e:?}")))?;
                let json = to_vec(&json_value).map_err(|e| {
                    Error::internal(format!("failed to serialize DID document: {e}"))
                })?;

                let content_type = options.accept.unwrap_or(MediaType::JsonLd);

                Ok(Output::new(
                    json,
                    match metadata {
                        Some(meta) => document::Metadata {
                            deactivated: Some(meta.deactivated),
                        },
                        None => document::Metadata { deactivated: None },
                    },
                    ResolutionMetadata::from_content_type(Some(content_type.to_string())),
                ))
            }
            Err(e) => Err(Error::internal(format!("cheqd resolver error: {e:?}"))),
        }
    }
}