Skip to main content

clawdentity_core/registry/
registry.rs

1use serde::{Deserialize, Serialize};
2
3use crate::error::{CoreError, Result};
4use crate::identity::LocalIdentity;
5
6const REGISTRY_METADATA_PATH: &str = "/v1/metadata";
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct RegistryMetadata {
11    pub registry_url: String,
12    pub proxy_url: String,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
16#[serde(rename_all = "camelCase")]
17pub struct RegisterIdentityResult {
18    pub registry_url: String,
19    pub status: String,
20    pub message: String,
21}
22
23#[derive(Debug, Deserialize)]
24#[serde(rename_all = "camelCase")]
25struct MetadataPayload {
26    registry_url: Option<String>,
27    proxy_url: Option<String>,
28}
29
30fn join_url(base: &str, path: &str) -> Result<String> {
31    let base_url = url::Url::parse(base).map_err(|_| CoreError::InvalidUrl {
32        context: "registryUrl",
33        value: base.to_string(),
34    })?;
35    let joined = base_url.join(path).map_err(|_| CoreError::InvalidUrl {
36        context: "registryUrl",
37        value: base.to_string(),
38    })?;
39    Ok(joined.to_string())
40}
41
42/// TODO(clawdentity): document `fetch_registry_metadata`.
43pub async fn fetch_registry_metadata(
44    client: &reqwest::Client,
45    registry_url: &str,
46) -> Result<RegistryMetadata> {
47    let url = join_url(registry_url, REGISTRY_METADATA_PATH)?;
48    let response = client
49        .get(url)
50        .send()
51        .await
52        .map_err(|error| CoreError::Http(error.to_string()))?;
53    if !response.status().is_success() {
54        let status = response.status().as_u16();
55        let message = response
56            .text()
57            .await
58            .unwrap_or_else(|_| "metadata request failed".to_string());
59        return Err(CoreError::HttpStatus { status, message });
60    }
61
62    let payload = response
63        .json::<MetadataPayload>()
64        .await
65        .map_err(|error| CoreError::Http(error.to_string()))?;
66
67    Ok(RegistryMetadata {
68        registry_url: payload
69            .registry_url
70            .unwrap_or_else(|| registry_url.to_string()),
71        proxy_url: payload.proxy_url.unwrap_or_default(),
72    })
73}
74
75/// TODO(clawdentity): document `register_identity`.
76pub async fn register_identity(
77    _client: &reqwest::Client,
78    registry_url: &str,
79    _identity: &LocalIdentity,
80) -> Result<RegisterIdentityResult> {
81    Ok(RegisterIdentityResult {
82        registry_url: registry_url.to_string(),
83        status: "not_supported".to_string(),
84        message: "Identity registration is challenge-based via `agent create`.".to_string(),
85    })
86}
87
88#[cfg(test)]
89mod tests {
90    use wiremock::matchers::{method, path};
91    use wiremock::{Mock, MockServer, ResponseTemplate};
92
93    use crate::identity::LocalIdentity;
94
95    use super::{fetch_registry_metadata, register_identity};
96
97    #[tokio::test]
98    async fn fetch_registry_metadata_parses_registry_and_proxy_urls() {
99        let server = MockServer::start().await;
100        Mock::given(method("GET"))
101            .and(path("/v1/metadata"))
102            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
103                "registryUrl": server.uri(),
104                "proxyUrl": format!("{}/proxy", server.uri()),
105            })))
106            .mount(&server)
107            .await;
108
109        let client = crate::http::client().expect("client");
110        let metadata = fetch_registry_metadata(&client, &server.uri())
111            .await
112            .expect("metadata");
113        assert_eq!(metadata.registry_url, server.uri());
114        assert_eq!(metadata.proxy_url, format!("{}/proxy", server.uri()));
115    }
116
117    #[tokio::test]
118    async fn register_identity_returns_not_supported_for_legacy_flow() {
119        let client = crate::http::client().expect("client");
120        let identity = LocalIdentity {
121            did: "did:cdi:registry.clawdentity.com:human:01ARZ3NDEKTSV4RRFFQ69G5FAV".to_string(),
122            public_key: "abc".to_string(),
123            secret_key: "def".to_string(),
124            registry_url: "https://registry.clawdentity.com".to_string(),
125        };
126
127        let result = register_identity(&client, "https://registry.clawdentity.com", &identity)
128            .await
129            .expect("register");
130        assert_eq!(result.status, "not_supported");
131    }
132}