1use serde::{Deserialize, Serialize};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum Capability {
33 WireV2,
36 JwsAgentCard,
39 CardEtag,
42 A2ABridge,
45 EtereCitizenTrust,
49 SafeHttp,
53 ClockSkew60s,
56 StreamingTransfer,
59 DeferredDecision,
73}
74
75impl Capability {
76 pub const ALL: &'static [Capability] = &[
78 Capability::WireV2,
79 Capability::JwsAgentCard,
80 Capability::CardEtag,
81 Capability::A2ABridge,
82 Capability::EtereCitizenTrust,
83 Capability::SafeHttp,
84 Capability::ClockSkew60s,
85 Capability::StreamingTransfer,
86 Capability::DeferredDecision,
87 ];
88
89 pub const fn as_bit(self) -> u8 {
91 match self {
92 Capability::WireV2 => 0,
93 Capability::JwsAgentCard => 1,
94 Capability::CardEtag => 2,
95 Capability::A2ABridge => 3,
96 Capability::EtereCitizenTrust => 4,
97 Capability::SafeHttp => 5,
98 Capability::ClockSkew60s => 6,
99 Capability::StreamingTransfer => 7,
100 Capability::DeferredDecision => 8,
101 }
102 }
103
104 pub const fn as_str(self) -> &'static str {
106 match self {
107 Capability::WireV2 => "wire-v2",
108 Capability::JwsAgentCard => "jws-agent-card",
109 Capability::CardEtag => "card-etag",
110 Capability::A2ABridge => "a2a-bridge",
111 Capability::EtereCitizenTrust => "etere-citizen-trust",
112 Capability::SafeHttp => "safe-http",
113 Capability::ClockSkew60s => "clock-skew-60s",
114 Capability::StreamingTransfer => "streaming-transfer",
115 Capability::DeferredDecision => "deferred-decision",
116 }
117 }
118
119 pub fn parse(s: &str) -> Option<Self> {
124 Self::ALL.iter().copied().find(|c| c.as_str() == s)
125 }
126}
127
128#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
133pub struct CapabilitySet(u64);
134
135impl CapabilitySet {
136 pub const fn empty() -> Self {
138 Self(0)
139 }
140
141 pub fn with(mut self, cap: Capability) -> Self {
143 self.0 |= 1u64 << cap.as_bit();
144 self
145 }
146
147 pub fn has(self, cap: Capability) -> bool {
149 (self.0 & (1u64 << cap.as_bit())) != 0
150 }
151
152 pub fn iter(self) -> impl Iterator<Item = Capability> {
155 Capability::ALL
156 .iter()
157 .copied()
158 .filter(move |c| self.has(*c))
159 }
160
161 pub fn to_string_array(self) -> Vec<&'static str> {
164 self.iter().map(Capability::as_str).collect()
165 }
166
167 pub fn from_string_array<I, S>(items: I) -> Self
171 where
172 I: IntoIterator<Item = S>,
173 S: AsRef<str>,
174 {
175 let mut set = Self::empty();
176 for item in items {
177 if let Some(cap) = Capability::parse(item.as_ref()) {
178 set = set.with(cap);
179 }
180 }
181 set
182 }
183
184 pub const fn bits(self) -> u64 {
186 self.0
187 }
188}
189
190impl Serialize for CapabilitySet {
191 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
192 self.to_string_array().serialize(s)
197 }
198}
199
200impl<'de> Deserialize<'de> for CapabilitySet {
201 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
202 let v: Vec<String> = Vec::deserialize(d)?;
203 Ok(Self::from_string_array(v))
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn empty_set_has_no_caps() {
213 let set = CapabilitySet::empty();
214 for cap in Capability::ALL {
215 assert!(!set.has(*cap), "empty set should not have {:?}", cap);
216 }
217 }
218
219 #[test]
220 fn add_and_query() {
221 let set = CapabilitySet::empty()
222 .with(Capability::WireV2)
223 .with(Capability::JwsAgentCard);
224 assert!(set.has(Capability::WireV2));
225 assert!(set.has(Capability::JwsAgentCard));
226 assert!(!set.has(Capability::A2ABridge));
227 }
228
229 #[test]
230 fn bits_are_stable() {
231 assert_eq!(Capability::WireV2.as_bit(), 0);
235 assert_eq!(Capability::JwsAgentCard.as_bit(), 1);
236 assert_eq!(Capability::CardEtag.as_bit(), 2);
237 assert_eq!(Capability::A2ABridge.as_bit(), 3);
238 assert_eq!(Capability::EtereCitizenTrust.as_bit(), 4);
239 assert_eq!(Capability::SafeHttp.as_bit(), 5);
240 assert_eq!(Capability::ClockSkew60s.as_bit(), 6);
241 assert_eq!(Capability::StreamingTransfer.as_bit(), 7);
242 assert_eq!(Capability::DeferredDecision.as_bit(), 8);
243 }
244
245 #[test]
246 fn names_are_stable() {
247 assert_eq!(Capability::WireV2.as_str(), "wire-v2");
248 assert_eq!(Capability::JwsAgentCard.as_str(), "jws-agent-card");
249 assert_eq!(Capability::CardEtag.as_str(), "card-etag");
250 assert_eq!(Capability::A2ABridge.as_str(), "a2a-bridge");
251 assert_eq!(
252 Capability::EtereCitizenTrust.as_str(),
253 "etere-citizen-trust"
254 );
255 assert_eq!(Capability::SafeHttp.as_str(), "safe-http");
256 assert_eq!(Capability::ClockSkew60s.as_str(), "clock-skew-60s");
257 assert_eq!(Capability::StreamingTransfer.as_str(), "streaming-transfer");
258 assert_eq!(Capability::DeferredDecision.as_str(), "deferred-decision");
259 }
260
261 #[test]
262 fn parse_roundtrip() {
263 for cap in Capability::ALL {
264 let parsed = Capability::parse(cap.as_str()).unwrap();
265 assert_eq!(parsed, *cap);
266 }
267 assert!(Capability::parse("does-not-exist").is_none());
268 }
269
270 #[test]
271 fn iter_in_canonical_order() {
272 let set = CapabilitySet::empty()
273 .with(Capability::StreamingTransfer)
274 .with(Capability::WireV2)
275 .with(Capability::JwsAgentCard);
276 let order: Vec<_> = set.iter().collect();
277 assert_eq!(
278 order,
279 vec![
280 Capability::WireV2,
281 Capability::JwsAgentCard,
282 Capability::StreamingTransfer
283 ]
284 );
285 }
286
287 #[test]
288 fn serde_roundtrip_via_string_array() {
289 let set = CapabilitySet::empty()
290 .with(Capability::WireV2)
291 .with(Capability::JwsAgentCard)
292 .with(Capability::CardEtag);
293 let json = serde_json::to_string(&set).unwrap();
294 assert_eq!(json, r#"["wire-v2","jws-agent-card","card-etag"]"#);
295 let back: CapabilitySet = serde_json::from_str(&json).unwrap();
296 assert_eq!(set, back);
297 }
298
299 #[test]
300 fn deserialize_skips_unknown_names() {
301 let json = r#"["wire-v2","post-quantum-sig","jws-agent-card"]"#;
304 let set: CapabilitySet = serde_json::from_str(json).unwrap();
305 assert!(set.has(Capability::WireV2));
306 assert!(set.has(Capability::JwsAgentCard));
307 assert_eq!(set.to_string_array().len(), 2);
309 }
310
311 #[test]
312 fn duplicate_names_idempotent() {
313 let set = CapabilitySet::from_string_array(["wire-v2", "wire-v2", "wire-v2"]);
314 assert!(set.has(Capability::WireV2));
315 assert_eq!(set.to_string_array().len(), 1);
316 }
317
318 #[test]
319 fn empty_array_is_empty_set() {
320 let set: CapabilitySet = serde_json::from_str("[]").unwrap();
321 assert_eq!(set, CapabilitySet::empty());
322 assert_eq!(set.bits(), 0);
323 }
324
325 #[test]
326 fn all_caps_set() {
327 let mut set = CapabilitySet::empty();
328 for cap in Capability::ALL {
329 set = set.with(*cap);
330 }
331 for cap in Capability::ALL {
332 assert!(set.has(*cap));
333 }
334 }
335}