clawdentity-core 0.1.7

Core Rust library for Clawdentity identity, registry auth, relay, connector, and provider flows.
Documentation
use serde::{Deserialize, Serialize};

use crate::error::{CoreError, Result};
use crate::identity::LocalIdentity;

const REGISTRY_METADATA_PATH: &str = "/v1/metadata";

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegistryMetadata {
    pub registry_url: String,
    pub proxy_url: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterIdentityResult {
    pub registry_url: String,
    pub status: String,
    pub message: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MetadataPayload {
    registry_url: Option<String>,
    proxy_url: Option<String>,
}

fn join_url(base: &str, path: &str) -> Result<String> {
    let base_url = url::Url::parse(base).map_err(|_| CoreError::InvalidUrl {
        context: "registryUrl",
        value: base.to_string(),
    })?;
    let joined = base_url.join(path).map_err(|_| CoreError::InvalidUrl {
        context: "registryUrl",
        value: base.to_string(),
    })?;
    Ok(joined.to_string())
}

/// TODO(clawdentity): document `fetch_registry_metadata`.
pub async fn fetch_registry_metadata(
    client: &reqwest::Client,
    registry_url: &str,
) -> Result<RegistryMetadata> {
    let url = join_url(registry_url, REGISTRY_METADATA_PATH)?;
    let response = client
        .get(url)
        .send()
        .await
        .map_err(|error| CoreError::Http(error.to_string()))?;
    if !response.status().is_success() {
        let status = response.status().as_u16();
        let message = response
            .text()
            .await
            .unwrap_or_else(|_| "metadata request failed".to_string());
        return Err(CoreError::HttpStatus { status, message });
    }

    let payload = response
        .json::<MetadataPayload>()
        .await
        .map_err(|error| CoreError::Http(error.to_string()))?;

    Ok(RegistryMetadata {
        registry_url: payload
            .registry_url
            .unwrap_or_else(|| registry_url.to_string()),
        proxy_url: payload.proxy_url.unwrap_or_default(),
    })
}

/// TODO(clawdentity): document `register_identity`.
pub async fn register_identity(
    _client: &reqwest::Client,
    registry_url: &str,
    _identity: &LocalIdentity,
) -> Result<RegisterIdentityResult> {
    Ok(RegisterIdentityResult {
        registry_url: registry_url.to_string(),
        status: "not_supported".to_string(),
        message: "Identity registration is challenge-based via `agent create`.".to_string(),
    })
}

#[cfg(test)]
mod tests {
    use wiremock::matchers::{method, path};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    use crate::identity::LocalIdentity;

    use super::{fetch_registry_metadata, register_identity};

    #[tokio::test]
    async fn fetch_registry_metadata_parses_registry_and_proxy_urls() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/metadata"))
            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
                "registryUrl": server.uri(),
                "proxyUrl": format!("{}/proxy", server.uri()),
            })))
            .mount(&server)
            .await;

        let client = crate::http::client().expect("client");
        let metadata = fetch_registry_metadata(&client, &server.uri())
            .await
            .expect("metadata");
        assert_eq!(metadata.registry_url, server.uri());
        assert_eq!(metadata.proxy_url, format!("{}/proxy", server.uri()));
    }

    #[tokio::test]
    async fn register_identity_returns_not_supported_for_legacy_flow() {
        let client = crate::http::client().expect("client");
        let identity = LocalIdentity {
            did: "did:cdi:registry.clawdentity.com:human:01ARZ3NDEKTSV4RRFFQ69G5FAV".to_string(),
            public_key: "abc".to_string(),
            secret_key: "def".to_string(),
            registry_url: "https://registry.clawdentity.com".to_string(),
        };

        let result = register_identity(&client, "https://registry.clawdentity.com", &identity)
            .await
            .expect("register");
        assert_eq!(result.status, "not_supported");
    }
}