clawdentity_core/registry/
registry.rs1use 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
42pub 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
75pub 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}