acdp_types/capabilities.rs
1use serde::{Deserialize, Serialize};
2
3/// Registry capabilities document served at `GET /.well-known/acdp.json`.
4///
5/// `additionalProperties` is `true` in the schema so future versions can add
6/// capability flags without a schema bump. Unknown fields are preserved in
7/// [`Self::extensions`] for forward-compatible inspection.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct CapabilitiesDocument {
10 /// Protocol version this registry implements.
11 pub acdp_version: String,
12
13 /// Registry's Decentralized Identifier (`did:web:…`).
14 pub registry_did: String,
15
16 /// Signature algorithms accepted on publish. MUST contain `"ed25519"`.
17 pub supported_signature_algorithms: Vec<String>,
18
19 /// DID methods the registry can resolve. MUST contain `"did:web"`.
20 pub supported_did_methods: Vec<String>,
21
22 /// Profile(s) this registry claims. MUST contain `"acdp-registry-core"`.
23 pub profiles: Vec<String>,
24
25 /// Resource limits.
26 pub limits: Limits,
27
28 /// Read-authentication methods supported for non-public contexts.
29 #[serde(default, skip_serializing_if = "Vec::is_empty")]
30 pub read_authentication_methods: Vec<String>,
31
32 /// Whether anonymous reads of public contexts are permitted.
33 #[serde(default)]
34 pub anonymous_public_reads: bool,
35
36 /// Whether `Idempotency-Key` is honoured on `POST /contexts`.
37 #[serde(default)]
38 pub supports_idempotency_key: bool,
39
40 /// Forward-compatible extensions: any unknown top-level field appears
41 /// here verbatim.
42 #[serde(flatten)]
43 pub extensions: serde_json::Map<String, serde_json::Value>,
44}
45
46impl CapabilitiesDocument {
47 /// Returns `true` if this registry supports keyword search.
48 pub fn supports_discovery(&self) -> bool {
49 self.profiles.iter().any(|p| p == "acdp-registry-discovery")
50 }
51
52 /// Returns `true` if this registry supports cross-registry resolution.
53 pub fn supports_federation(&self) -> bool {
54 self.profiles.iter().any(|p| p == "acdp-registry-federated")
55 }
56
57 /// Returns `true` if this registry advertises support for the given
58 /// signature algorithm (case-sensitive match against
59 /// `supported_signature_algorithms`).
60 pub fn supports_algorithm(&self, algorithm: &str) -> bool {
61 self.supported_signature_algorithms
62 .iter()
63 .any(|a| a == algorithm)
64 }
65
66 /// Returns `true` if this registry can resolve the given DID method
67 /// (e.g. `"did:web"`).
68 pub fn supports_did_method(&self, method: &str) -> bool {
69 self.supported_did_methods.iter().any(|m| m == method)
70 }
71
72 /// Idempotency-key TTL as a [`std::time::Duration`], if the registry
73 /// advertises one. Returns `None` when `supports_idempotency_key` is
74 /// `false` or the TTL field is absent.
75 pub fn idempotency_ttl(&self) -> Option<std::time::Duration> {
76 self.limits
77 .idempotency_key_ttl_seconds
78 .map(|s| std::time::Duration::from_secs(u64::from(s)))
79 }
80
81 /// Returns `true` when the registry requires authenticated requests
82 /// even for `visibility: public` reads (i.e. `anonymous_public_reads`
83 /// is `false`).
84 pub fn requires_anonymous_auth(&self) -> bool {
85 !self.anonymous_public_reads
86 }
87}
88
89/// Resource limits declared by the registry.
90///
91/// The capabilities document is OPEN at the top level, but `limits` is a
92/// CLOSED sub-object (`additionalProperties: false`): new limit keys are
93/// added by a spec version bump, not by registries inventing fields, so
94/// `deny_unknown_fields` rejects an unknown key (RFC-ACDP-0007 §3.3.1,
95/// conformance fixture schema-010).
96#[derive(Debug, Clone, Serialize, Deserialize)]
97#[serde(deny_unknown_fields)]
98pub struct Limits {
99 /// Maximum total publish request size in bytes.
100 pub max_payload_bytes: u64,
101
102 /// Maximum size of any single embedded data reference in bytes (≤ 65536).
103 pub max_embedded_bytes: u64,
104
105 /// How long idempotency-key mappings are retained, in seconds.
106 /// MUST be present when `supports_idempotency_key` is true.
107 ///
108 /// Optional and absent-or-integer in the schema — not nullable.
109 /// `de_present` rejects an explicit `"idempotency_key_ttl_seconds": null`.
110 #[serde(
111 default,
112 skip_serializing_if = "Option::is_none",
113 deserialize_with = "crate::serde_helpers::de_present"
114 )]
115 pub idempotency_key_ttl_seconds: Option<u32>,
116
117 /// *(0.3.0)* OPTIONAL nominal per-agent publish ceiling, in
118 /// publishes per minute (integer ≥ 1). Advisory: producers SHOULD
119 /// use it to pace publishes; its presence does NOT change the
120 /// RFC-ACDP-0008 §4.3 requirements (rate limiting stays REQUIRED,
121 /// `Retry-After` stays mandatory on 429). RFC-ACDP-0007 §3.2,
122 /// fixture `caps-007`.
123 #[serde(
124 default,
125 skip_serializing_if = "Option::is_none",
126 deserialize_with = "crate::serde_helpers::de_present"
127 )]
128 pub max_publish_per_minute: Option<u64>,
129
130 /// *(0.5.0, Draft)* How long `GET /changes` entries, and cursors
131 /// pointing at them, are retained, in seconds (≥ 2592000, 30 days).
132 /// MUST be present with `acdp-registry-changes` (RFC-ACDP-0018 §5.4,
133 /// §9).
134 #[serde(
135 default,
136 skip_serializing_if = "Option::is_none",
137 deserialize_with = "crate::serde_helpers::de_present"
138 )]
139 pub change_retention_seconds: Option<u64>,
140
141 /// *(0.5.0, Draft)* Maximum total `ctx_id`/`lineage_id`/`agent_id`/
142 /// `kind` values in one `GET /changes` request or subscription filter
143 /// (≥ 100). MUST be present with `acdp-registry-changes`
144 /// (RFC-ACDP-0018 §5.1, §9).
145 #[serde(
146 default,
147 skip_serializing_if = "Option::is_none",
148 deserialize_with = "crate::serde_helpers::de_present"
149 )]
150 pub max_change_filters: Option<u64>,
151
152 /// *(0.5.0, Draft)* Largest `GET /changes` `limit` the registry
153 /// accepts (≥ 100). MUST be present with `acdp-registry-changes`
154 /// (RFC-ACDP-0018 §5.1, §9).
155 #[serde(
156 default,
157 skip_serializing_if = "Option::is_none",
158 deserialize_with = "crate::serde_helpers::de_present"
159 )]
160 pub max_changes_page: Option<u64>,
161
162 /// *(0.5.0, Draft)* Maximum change-hint subscriptions one requester
163 /// DID may hold (≥ 1). MUST be present with `acdp-registry-events`
164 /// (RFC-ACDP-0018 §8.1, §9).
165 #[serde(
166 default,
167 skip_serializing_if = "Option::is_none",
168 deserialize_with = "crate::serde_helpers::de_present"
169 )]
170 pub max_subscriptions_per_requester: Option<u64>,
171}