1use std::collections::BTreeMap;
11
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14
15#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
35pub struct ClaimSet {
36 namespaces: BTreeMap<String, BTreeMap<String, Value>>,
37}
38
39impl ClaimSet {
40 pub fn new() -> Self {
42 Self::default()
43 }
44
45 pub fn insert(&mut self, namespace: &str, name: &str, value: Value) {
47 self.namespaces
48 .entry(namespace.to_string())
49 .or_default()
50 .insert(name.to_string(), value);
51 }
52
53 pub fn get(&self, namespace: &str, name: &str) -> Option<&Value> {
55 self.namespaces.get(namespace)?.get(name)
56 }
57
58 pub fn namespaces(&self) -> impl Iterator<Item = (&str, &BTreeMap<String, Value>)> {
60 self.namespaces.iter().map(|(k, v)| (k.as_str(), v))
61 }
62
63 pub fn namespace(&self, ns: &str) -> Option<&BTreeMap<String, Value>> {
65 self.namespaces.get(ns)
66 }
67
68 pub fn len(&self) -> usize {
70 self.namespaces.values().map(|m| m.len()).sum()
71 }
72
73 pub fn is_empty(&self) -> bool {
75 self.namespaces.values().all(|m| m.is_empty())
76 }
77
78 pub fn to_json(&self) -> Value {
83 if self.namespaces.len() == 1 {
84 if let Some(claims) = self.namespaces.get("") {
85 return Value::Object(claims.iter().map(|(k, v)| (k.clone(), v.clone())).collect());
86 }
87 }
88 serde_json::to_value(&self.namespaces).unwrap_or(Value::Null)
89 }
90
91 pub fn to_json_flat(&self) -> Option<Value> {
96 let claims = self.namespaces.get("")?;
97 Some(Value::Object(
98 claims.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
99 ))
100 }
101
102 pub fn to_json_namespaced(&self) -> Value {
108 serde_json::to_value(&self.namespaces).unwrap_or(Value::Null)
109 }
110
111 pub fn from_json(value: &Value) -> Option<Self> {
113 let obj = value.as_object()?;
114 let mut cs = Self::new();
115 for (k, v) in obj {
116 cs.insert("", k, v.clone());
117 }
118 Some(cs)
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
124pub struct ClaimPath {
125 pub namespace: String,
127 pub name: String,
129}
130
131impl ClaimPath {
132 pub fn new(name: &str) -> Self {
134 Self {
135 namespace: String::new(),
136 name: name.to_string(),
137 }
138 }
139
140 pub fn with_namespace(namespace: &str, name: &str) -> Self {
142 Self {
143 namespace: namespace.to_string(),
144 name: name.to_string(),
145 }
146 }
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
151pub enum ClaimDisclosure {
152 Reveal,
154 Predicate(PredicateType),
157 Hide,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
163pub enum PredicateType {
164 GreaterThan(Value),
166 GreaterThanOrEqual(Value),
168 LessThan(Value),
170 LessThanOrEqual(Value),
172 InSet(Vec<Value>),
174 NotInSet(Vec<Value>),
176 NonRevoked,
178}
179
180#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
199pub struct DisclosureSelection {
200 selections: BTreeMap<String, BTreeMap<String, ClaimDisclosure>>,
201}
202
203impl DisclosureSelection {
204 pub fn new() -> Self {
206 Self::default()
207 }
208
209 pub fn set(mut self, name: &str, disclosure: ClaimDisclosure) -> Self {
211 self.selections
212 .entry(String::new())
213 .or_default()
214 .insert(name.to_string(), disclosure);
215 self
216 }
217
218 pub fn set_namespaced(
220 mut self,
221 namespace: &str,
222 name: &str,
223 disclosure: ClaimDisclosure,
224 ) -> Self {
225 self.selections
226 .entry(namespace.to_string())
227 .or_default()
228 .insert(name.to_string(), disclosure);
229 self
230 }
231
232 pub fn reveal(self, name: &str) -> Self {
234 self.set(name, ClaimDisclosure::Reveal)
235 }
236
237 pub fn hide(self, name: &str) -> Self {
239 self.set(name, ClaimDisclosure::Hide)
240 }
241
242 pub fn predicate(self, name: &str, predicate: PredicateType) -> Self {
244 self.set(name, ClaimDisclosure::Predicate(predicate))
245 }
246
247 pub fn get(&self, namespace: &str, name: &str) -> Option<&ClaimDisclosure> {
249 self.selections.get(namespace)?.get(name)
250 }
251
252 pub fn iter(&self) -> impl Iterator<Item = (&str, &str, &ClaimDisclosure)> {
254 self.selections.iter().flat_map(|(ns, claims)| {
255 claims
256 .iter()
257 .map(move |(name, disc)| (ns.as_str(), name.as_str(), disc))
258 })
259 }
260
261 pub fn has_predicates(&self) -> bool {
263 self.iter()
264 .any(|(_, _, d)| matches!(d, ClaimDisclosure::Predicate(_)))
265 }
266
267 pub fn revealed_claims(&self) -> Vec<&str> {
269 self.selections
270 .get("")
271 .map(|claims| {
272 claims
273 .iter()
274 .filter(|(_, d)| matches!(d, ClaimDisclosure::Reveal))
275 .map(|(name, _)| name.as_str())
276 .collect()
277 })
278 .unwrap_or_default()
279 }
280
281 pub fn hidden_claims(&self) -> Vec<&str> {
283 self.selections
284 .get("")
285 .map(|claims| {
286 claims
287 .iter()
288 .filter(|(_, d)| matches!(d, ClaimDisclosure::Hide))
289 .map(|(name, _)| name.as_str())
290 .collect()
291 })
292 .unwrap_or_default()
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use serde_json::json;
300
301 #[test]
302 fn claim_set_basic_operations() {
303 let mut cs = ClaimSet::new();
304 assert!(cs.is_empty());
305 assert_eq!(cs.len(), 0);
306
307 cs.insert("", "given_name", json!("Alice"));
308 cs.insert("", "family_name", json!("Smith"));
309 cs.insert("", "age", json!(30));
310
311 assert_eq!(cs.len(), 3);
312 assert!(!cs.is_empty());
313 assert_eq!(cs.get("", "given_name"), Some(&json!("Alice")));
314 assert_eq!(cs.get("", "age"), Some(&json!(30)));
315 assert_eq!(cs.get("", "nonexistent"), None);
316 assert_eq!(cs.get("other_ns", "given_name"), None);
317 }
318
319 #[test]
320 fn claim_set_namespaced() {
321 let mut cs = ClaimSet::new();
322 cs.insert("org.iso.18013.5.1", "family_name", json!("Smith"));
323 cs.insert("org.iso.18013.5.1", "given_name", json!("Alice"));
324 cs.insert("org.iso.18013.5.1.aamva", "DHS_compliance", json!("F"));
325
326 assert_eq!(cs.len(), 3);
327 assert_eq!(
328 cs.get("org.iso.18013.5.1", "family_name"),
329 Some(&json!("Smith"))
330 );
331 assert_eq!(
332 cs.get("org.iso.18013.5.1.aamva", "DHS_compliance"),
333 Some(&json!("F"))
334 );
335 }
336
337 #[test]
338 fn claim_set_json_roundtrip() {
339 let mut cs = ClaimSet::new();
340 cs.insert("", "name", json!("Alice"));
341 cs.insert("", "age", json!(30));
342
343 let json_val = cs.to_json();
344 let obj = json_val.as_object().unwrap();
345 assert_eq!(obj.get("name"), Some(&json!("Alice")));
346 assert_eq!(obj.get("age"), Some(&json!(30)));
347
348 let cs2 = ClaimSet::from_json(&json_val).unwrap();
349 assert_eq!(cs, cs2);
350 }
351
352 #[test]
353 fn disclosure_selection_builder() {
354 let ds = DisclosureSelection::new()
355 .reveal("given_name")
356 .reveal("family_name")
357 .predicate("birth_date", PredicateType::LessThan(json!("2008-03-01")))
358 .hide("address");
359
360 assert_eq!(ds.get("", "given_name"), Some(&ClaimDisclosure::Reveal));
361 assert_eq!(ds.get("", "family_name"), Some(&ClaimDisclosure::Reveal));
362 assert_eq!(
363 ds.get("", "birth_date"),
364 Some(&ClaimDisclosure::Predicate(PredicateType::LessThan(json!(
365 "2008-03-01"
366 ))))
367 );
368 assert_eq!(ds.get("", "address"), Some(&ClaimDisclosure::Hide));
369 assert!(ds.has_predicates());
370 }
371
372 #[test]
373 fn disclosure_selection_no_predicates() {
374 let ds = DisclosureSelection::new().reveal("name").hide("secret");
375
376 assert!(!ds.has_predicates());
377 assert_eq!(ds.revealed_claims(), vec!["name"]);
378 assert_eq!(ds.hidden_claims(), vec!["secret"]);
379 }
380
381 #[test]
382 fn disclosure_selection_namespaced() {
383 let ds = DisclosureSelection::new()
384 .set_namespaced("org.iso.18013.5.1", "family_name", ClaimDisclosure::Reveal)
385 .set_namespaced("org.iso.18013.5.1", "portrait", ClaimDisclosure::Hide);
386
387 assert_eq!(
388 ds.get("org.iso.18013.5.1", "family_name"),
389 Some(&ClaimDisclosure::Reveal)
390 );
391 assert_eq!(
392 ds.get("org.iso.18013.5.1", "portrait"),
393 Some(&ClaimDisclosure::Hide)
394 );
395 }
396
397 #[test]
398 fn predicate_types_serialize() {
399 let predicates = vec![
400 PredicateType::GreaterThan(json!(18)),
401 PredicateType::GreaterThanOrEqual(json!(18)),
402 PredicateType::LessThan(json!("2008-03-01")),
403 PredicateType::LessThanOrEqual(json!("2008-03-01")),
404 PredicateType::InSet(vec![json!("CA"), json!("US")]),
405 PredicateType::NotInSet(vec![json!("banned")]),
406 PredicateType::NonRevoked,
407 ];
408
409 for p in &predicates {
410 let serialized = serde_json::to_string(p).unwrap();
411 let deserialized: PredicateType = serde_json::from_str(&serialized).unwrap();
412 assert_eq!(p, &deserialized);
413 }
414 }
415
416 #[test]
417 fn to_json_flat_default_namespace() {
418 let mut cs = ClaimSet::new();
419 cs.insert("", "name", json!("Alice"));
420 cs.insert("", "age", json!(30));
421
422 let flat = cs.to_json_flat().unwrap();
423 let obj = flat.as_object().unwrap();
424 assert_eq!(obj.get("name"), Some(&json!("Alice")));
425 assert_eq!(obj.get("age"), Some(&json!(30)));
426 }
427
428 #[test]
429 fn to_json_flat_no_default_namespace() {
430 let mut cs = ClaimSet::new();
431 cs.insert("org.iso.18013.5.1", "family_name", json!("Smith"));
432
433 assert!(cs.to_json_flat().is_none());
434 }
435
436 #[test]
437 fn to_json_flat_ignores_other_namespaces() {
438 let mut cs = ClaimSet::new();
439 cs.insert("", "name", json!("Alice"));
440 cs.insert("org.iso.18013.5.1", "family_name", json!("Smith"));
441
442 let flat = cs.to_json_flat().unwrap();
443 let obj = flat.as_object().unwrap();
444 assert_eq!(obj.len(), 1);
445 assert_eq!(obj.get("name"), Some(&json!("Alice")));
446 }
447
448 #[test]
449 fn to_json_namespaced_always_nests() {
450 let mut cs = ClaimSet::new();
451 cs.insert("", "name", json!("Alice"));
452
453 let ns = cs.to_json_namespaced();
454 let obj = ns.as_object().unwrap();
455 assert!(obj.contains_key(""));
457 let inner = obj.get("").unwrap().as_object().unwrap();
458 assert_eq!(inner.get("name"), Some(&json!("Alice")));
459 }
460
461 #[test]
462 fn to_json_namespaced_multiple() {
463 let mut cs = ClaimSet::new();
464 cs.insert("ns1", "a", json!(1));
465 cs.insert("ns2", "b", json!(2));
466
467 let ns = cs.to_json_namespaced();
468 let obj = ns.as_object().unwrap();
469 assert!(obj.contains_key("ns1"));
470 assert!(obj.contains_key("ns2"));
471 }
472
473 #[test]
474 fn claim_path_default_namespace() {
475 let path = ClaimPath::new("given_name");
476 assert_eq!(path.namespace, "");
477 assert_eq!(path.name, "given_name");
478 }
479
480 #[test]
481 fn claim_path_with_namespace() {
482 let path = ClaimPath::with_namespace("org.iso.18013.5.1", "family_name");
483 assert_eq!(path.namespace, "org.iso.18013.5.1");
484 assert_eq!(path.name, "family_name");
485 }
486}