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}