common_access_token/
claims.rs

1//! # Claims for Common Access Token
2//!
3//! This module provides the claims structure and related types for Common Access Tokens.
4//!
5//! Claims in Common Access Tokens are divided into two categories:
6//!
7//! - **Registered Claims**: Standard claims defined in RFC 8392, such as issuer, subject, audience, and expiration time.
8//! - **Custom Claims**: Application-specific claims that can contain any CBOR-encodable value.
9//!
10//! Claims are used to convey information about the token subject and context, and can be used for
11//! authorization decisions by the token verifier.
12
13use crate::header::CborValue;
14use std::collections::BTreeMap;
15
16/// CWT claim keys as defined in RFC 8392
17pub mod keys {
18    use crate::constants::cwt_keys;
19
20    /// Issuer claim key
21    pub const ISS: i32 = cwt_keys::ISS;
22    /// Subject claim key
23    pub const SUB: i32 = cwt_keys::SUB;
24    /// Audience claim key
25    pub const AUD: i32 = cwt_keys::AUD;
26    /// Expiration time claim key
27    pub const EXP: i32 = cwt_keys::EXP;
28    /// Not before claim key
29    pub const NBF: i32 = cwt_keys::NBF;
30    /// Issued at claim key
31    pub const IAT: i32 = cwt_keys::IAT;
32    /// CWT ID claim key
33    pub const CTI: i32 = cwt_keys::CTI;
34}
35
36/// Type alias for claims map
37pub type ClaimsMap = BTreeMap<i32, CborValue>;
38
39/// Standard registered claims as defined in RFC 8392.
40///
41/// These claims are standardized and have well-defined meanings:
42///
43/// - **iss** (Issuer): Identifies the principal that issued the token.
44/// - **sub** (Subject): Identifies the principal that is the subject of the token.
45/// - **aud** (Audience): Identifies the recipients that the token is intended for.
46/// - **exp** (Expiration Time): Identifies the expiration time on or after which the token MUST NOT be accepted.
47/// - **nbf** (Not Before): Identifies the time before which the token MUST NOT be accepted.
48/// - **iat** (Issued At): Identifies the time at which the token was issued.
49/// - **cti** (CWT ID): Provides a unique identifier for the token.
50///
51/// # Example
52///
53/// ```
54/// use common_access_token::RegisteredClaims;
55/// use common_access_token::current_timestamp;
56///
57/// let now = current_timestamp();
58/// let claims = RegisteredClaims::new()
59///     .with_issuer("example-issuer")
60///     .with_subject("user-123")
61///     .with_audience("example-service")
62///     .with_expiration(now + 3600) // 1 hour from now
63///     .with_not_before(now)
64///     .with_issued_at(now);
65///
66/// assert_eq!(claims.iss, Some("example-issuer".to_string()));
67/// assert_eq!(claims.sub, Some("user-123".to_string()));
68/// assert_eq!(claims.exp.unwrap(), now + 3600);
69/// ```
70#[derive(Debug, Clone, Default)]
71pub struct RegisteredClaims {
72    /// Issuer - identifies the principal that issued the token
73    pub iss: Option<String>,
74    /// Subject - identifies the principal that is the subject of the token
75    pub sub: Option<String>,
76    /// Audience - identifies the recipients that the token is intended for
77    pub aud: Option<String>,
78    /// Expiration time (seconds since Unix epoch) - token must not be accepted after this time
79    pub exp: Option<u64>,
80    /// Not before (seconds since Unix epoch) - token must not be accepted before this time
81    pub nbf: Option<u64>,
82    /// Issued at (seconds since Unix epoch) - when the token was issued
83    pub iat: Option<u64>,
84    /// CWT ID - unique identifier for the token
85    pub cti: Option<Vec<u8>>,
86}
87
88impl RegisteredClaims {
89    /// Creates a new empty set of registered claims.
90    ///
91    /// # Example
92    ///
93    /// ```
94    /// use common_access_token::RegisteredClaims;
95    ///
96    /// let claims = RegisteredClaims::new();
97    /// assert!(claims.iss.is_none());
98    /// assert!(claims.sub.is_none());
99    /// assert!(claims.exp.is_none());
100    /// ```
101    pub fn new() -> Self {
102        Self::default()
103    }
104
105    /// Sets the issuer claim.
106    ///
107    /// The issuer claim identifies the principal that issued the token.
108    ///
109    /// # Example
110    ///
111    /// ```
112    /// use common_access_token::RegisteredClaims;
113    ///
114    /// let claims = RegisteredClaims::new().with_issuer("https://auth.example.com");
115    /// assert_eq!(claims.iss, Some("https://auth.example.com".to_string()));
116    /// ```
117    pub fn with_issuer<S: Into<String>>(mut self, iss: S) -> Self {
118        self.iss = Some(iss.into());
119        self
120    }
121
122    /// Sets the subject claim.
123    ///
124    /// The subject claim identifies the principal that is the subject of the token.
125    ///
126    /// # Example
127    ///
128    /// ```
129    /// use common_access_token::RegisteredClaims;
130    ///
131    /// let claims = RegisteredClaims::new().with_subject("user-123");
132    /// assert_eq!(claims.sub, Some("user-123".to_string()));
133    /// ```
134    pub fn with_subject<S: Into<String>>(mut self, sub: S) -> Self {
135        self.sub = Some(sub.into());
136        self
137    }
138
139    /// Sets the audience claim.
140    ///
141    /// The audience claim identifies the recipients that the token is intended for.
142    ///
143    /// # Example
144    ///
145    /// ```
146    /// use common_access_token::RegisteredClaims;
147    ///
148    /// let claims = RegisteredClaims::new().with_audience("https://api.example.com");
149    /// assert_eq!(claims.aud, Some("https://api.example.com".to_string()));
150    /// ```
151    pub fn with_audience<S: Into<String>>(mut self, aud: S) -> Self {
152        self.aud = Some(aud.into());
153        self
154    }
155
156    /// Sets the expiration time claim.
157    ///
158    /// The expiration time claim identifies the time on or after which the token
159    /// MUST NOT be accepted for processing. The value is a Unix timestamp (seconds
160    /// since the Unix epoch).
161    ///
162    /// # Example
163    ///
164    /// ```
165    /// use common_access_token::RegisteredClaims;
166    /// use common_access_token::current_timestamp;
167    ///
168    /// let now = current_timestamp();
169    /// let claims = RegisteredClaims::new().with_expiration(now + 3600); // 1 hour from now
170    /// assert_eq!(claims.exp, Some(now + 3600));
171    /// ```
172    pub fn with_expiration(mut self, exp: u64) -> Self {
173        self.exp = Some(exp);
174        self
175    }
176
177    /// Sets the not before claim.
178    ///
179    /// The not before claim identifies the time before which the token
180    /// MUST NOT be accepted for processing. The value is a Unix timestamp (seconds
181    /// since the Unix epoch).
182    ///
183    /// # Example
184    ///
185    /// ```
186    /// use common_access_token::RegisteredClaims;
187    /// use common_access_token::current_timestamp;
188    ///
189    /// let now = current_timestamp();
190    /// let claims = RegisteredClaims::new().with_not_before(now); // Valid from now
191    /// assert_eq!(claims.nbf, Some(now));
192    /// ```
193    pub fn with_not_before(mut self, nbf: u64) -> Self {
194        self.nbf = Some(nbf);
195        self
196    }
197
198    /// Sets the issued at claim.
199    ///
200    /// The issued at claim identifies the time at which the token was issued.
201    /// The value is a Unix timestamp (seconds since the Unix epoch).
202    ///
203    /// # Example
204    ///
205    /// ```
206    /// use common_access_token::RegisteredClaims;
207    /// use common_access_token::current_timestamp;
208    ///
209    /// let now = current_timestamp();
210    /// let claims = RegisteredClaims::new().with_issued_at(now);
211    /// assert_eq!(claims.iat, Some(now));
212    /// ```
213    pub fn with_issued_at(mut self, iat: u64) -> Self {
214        self.iat = Some(iat);
215        self
216    }
217
218    /// Sets the CWT ID claim.
219    ///
220    /// The CWT ID claim provides a unique identifier for the token.
221    ///
222    /// # Example
223    ///
224    /// ```
225    /// use common_access_token::RegisteredClaims;
226    ///
227    /// let id = vec![1, 2, 3, 4];
228    /// let claims = RegisteredClaims::new().with_cti(id.clone());
229    /// assert_eq!(claims.cti, Some(id));
230    /// ```
231    pub fn with_cti<T: Into<Vec<u8>>>(mut self, cti: T) -> Self {
232        self.cti = Some(cti.into());
233        self
234    }
235
236    /// Set token lifetime with issued-at, not-before, and expiration claims
237    ///
238    /// This is a convenience method that sets all three time-related claims:
239    /// - `iat` (issued at) is set to the current time
240    /// - `nbf` (not before) is set to the current time
241    /// - `exp` (expiration) is set to current time plus the specified seconds
242    ///
243    /// # Example
244    ///
245    /// ```
246    /// use common_access_token::{RegisteredClaims, current_timestamp};
247    ///
248    /// // Token valid for 1 hour
249    /// let claims = RegisteredClaims::new().with_lifetime_secs(3600);
250    ///
251    /// let now = current_timestamp();
252    /// assert_eq!(claims.iat, Some(now));
253    /// assert_eq!(claims.nbf, Some(now));
254    /// assert_eq!(claims.exp, Some(now + 3600));
255    /// ```
256    pub fn with_lifetime_secs(mut self, seconds: u64) -> Self {
257        let now = crate::utils::current_timestamp();
258        self.iat = Some(now);
259        self.nbf = Some(now);
260        self.exp = Some(now + seconds);
261        self
262    }
263
264    /// Set token lifetime with issued-at, not-before, and expiration claims using a Duration
265    ///
266    /// This is a convenience method that sets all three time-related claims:
267    /// - `iat` (issued at) is set to the current time
268    /// - `nbf` (not before) is set to the current time
269    /// - `exp` (expiration) is set to current time plus the specified duration
270    ///
271    /// # Example
272    ///
273    /// ```
274    /// use common_access_token::{RegisteredClaims, current_timestamp};
275    /// use std::time::Duration;
276    ///
277    /// // Token valid for 1 hour
278    /// let claims = RegisteredClaims::new().with_lifetime(Duration::from_secs(3600));
279    ///
280    /// let now = current_timestamp();
281    /// assert_eq!(claims.iat, Some(now));
282    /// assert_eq!(claims.nbf, Some(now));
283    /// assert_eq!(claims.exp, Some(now + 3600));
284    /// ```
285    pub fn with_lifetime(self, duration: std::time::Duration) -> Self {
286        self.with_lifetime_secs(duration.as_secs())
287    }
288
289    /// Check if the claims have expired
290    ///
291    /// Returns `true` if there's an expiration claim and the current time is at or after it.
292    /// Returns `false` if there's no expiration claim or it hasn't expired yet.
293    ///
294    /// # Example
295    ///
296    /// ```
297    /// use common_access_token::{RegisteredClaims, current_timestamp};
298    ///
299    /// let now = current_timestamp();
300    /// let claims = RegisteredClaims::new().with_expiration(now + 3600);
301    /// assert!(!claims.is_expired());
302    ///
303    /// let expired = RegisteredClaims::new().with_expiration(now - 100);
304    /// assert!(expired.is_expired());
305    /// ```
306    pub fn is_expired(&self) -> bool {
307        if let Some(exp) = self.exp {
308            crate::utils::current_timestamp() >= exp
309        } else {
310            false
311        }
312    }
313
314    /// Check if the claims are valid based on the not-before claim
315    ///
316    /// Returns `true` if there's no nbf claim or if the current time is at or after it.
317    /// Returns `false` if there's an nbf claim and the current time is before it.
318    ///
319    /// # Example
320    ///
321    /// ```
322    /// use common_access_token::{RegisteredClaims, current_timestamp};
323    ///
324    /// let now = current_timestamp();
325    /// let claims = RegisteredClaims::new().with_not_before(now);
326    /// assert!(claims.is_valid_yet());
327    ///
328    /// let future = RegisteredClaims::new().with_not_before(now + 3600);
329    /// assert!(!future.is_valid_yet());
330    /// ```
331    pub fn is_valid_yet(&self) -> bool {
332        if let Some(nbf) = self.nbf {
333            crate::utils::current_timestamp() >= nbf
334        } else {
335            true
336        }
337    }
338
339    /// Converts registered claims to a claims map.
340    ///
341    /// This method is primarily used internally for token encoding.
342    ///
343    /// # Example
344    ///
345    /// ```
346    /// use common_access_token::RegisteredClaims;
347    /// use common_access_token::header::CborValue;
348    /// use common_access_token::claims::keys;
349    ///
350    /// let claims = RegisteredClaims::new()
351    ///     .with_issuer("example-issuer")
352    ///     .with_subject("user-123");
353    ///
354    /// let map = claims.to_map();
355    /// assert!(matches!(map.get(&keys::ISS), Some(CborValue::Text(s)) if s == "example-issuer"));
356    /// assert!(matches!(map.get(&keys::SUB), Some(CborValue::Text(s)) if s == "user-123"));
357    /// ```
358    pub fn to_map(&self) -> ClaimsMap {
359        let mut map = ClaimsMap::new();
360
361        if let Some(iss) = &self.iss {
362            map.insert(keys::ISS, CborValue::Text(iss.clone()));
363        }
364
365        if let Some(sub) = &self.sub {
366            map.insert(keys::SUB, CborValue::Text(sub.clone()));
367        }
368
369        if let Some(aud) = &self.aud {
370            map.insert(keys::AUD, CborValue::Text(aud.clone()));
371        }
372
373        if let Some(exp) = self.exp {
374            map.insert(keys::EXP, CborValue::Integer(exp as i64));
375        }
376
377        if let Some(nbf) = self.nbf {
378            map.insert(keys::NBF, CborValue::Integer(nbf as i64));
379        }
380
381        if let Some(iat) = self.iat {
382            map.insert(keys::IAT, CborValue::Integer(iat as i64));
383        }
384
385        if let Some(cti) = &self.cti {
386            map.insert(keys::CTI, CborValue::Bytes(cti.clone()));
387        }
388
389        map
390    }
391
392    /// Extracts registered claims from a claims map.
393    ///
394    /// This method is primarily used internally for token decoding.
395    ///
396    /// # Example
397    ///
398    /// ```
399    /// use common_access_token::{RegisteredClaims, CborValue};
400    /// use common_access_token::claims::{keys, ClaimsMap};
401    ///
402    /// let mut map = ClaimsMap::new();
403    /// map.insert(keys::ISS, CborValue::Text("example-issuer".to_string()));
404    /// map.insert(keys::SUB, CborValue::Text("user-123".to_string()));
405    ///
406    /// let claims = RegisteredClaims::from_map(&map);
407    /// assert_eq!(claims.iss, Some("example-issuer".to_string()));
408    /// assert_eq!(claims.sub, Some("user-123".to_string()));
409    /// ```
410    pub fn from_map(map: &ClaimsMap) -> Self {
411        let mut claims = Self::new();
412
413        if let Some(CborValue::Text(iss)) = map.get(&keys::ISS) {
414            claims.iss = Some(iss.clone());
415        }
416
417        if let Some(CborValue::Text(sub)) = map.get(&keys::SUB) {
418            claims.sub = Some(sub.clone());
419        }
420
421        if let Some(CborValue::Text(aud)) = map.get(&keys::AUD) {
422            claims.aud = Some(aud.clone());
423        }
424
425        if let Some(CborValue::Integer(exp)) = map.get(&keys::EXP) {
426            claims.exp = Some(*exp as u64);
427        }
428
429        if let Some(CborValue::Integer(nbf)) = map.get(&keys::NBF) {
430            claims.nbf = Some(*nbf as u64);
431        }
432
433        if let Some(CborValue::Integer(iat)) = map.get(&keys::IAT) {
434            claims.iat = Some(*iat as u64);
435        }
436
437        if let Some(CborValue::Bytes(cti)) = map.get(&keys::CTI) {
438            claims.cti = Some(cti.clone());
439        }
440
441        claims
442    }
443}
444
445/// Claims for a Common Access Token.
446///
447/// This struct combines standard registered claims with custom application-specific claims.
448/// It provides a flexible way to include both standardized information and custom data
449/// in a token.
450///
451/// # Example
452///
453/// ```
454/// use common_access_token::{Claims, RegisteredClaims};
455/// use common_access_token::current_timestamp;
456///
457/// let now = current_timestamp();
458/// let registered_claims = RegisteredClaims::new()
459///     .with_issuer("example-issuer")
460///     .with_expiration(now + 3600);
461///
462/// let claims = Claims::new()
463///     .with_registered_claims(registered_claims)
464///     .with_custom_string(100, "custom-value")
465///     .with_custom_int(101, 42);
466///
467/// // Access claims
468/// assert_eq!(claims.registered.iss, Some("example-issuer".to_string()));
469/// ```
470#[derive(Debug, Clone, Default)]
471pub struct Claims {
472    /// Standard registered claims as defined in RFC 8392
473    pub registered: RegisteredClaims,
474    /// Custom application-specific claims with integer keys
475    pub custom: ClaimsMap,
476}
477
478impl Claims {
479    /// Creates a new empty claims set with no registered or custom claims.
480    ///
481    /// # Example
482    ///
483    /// ```
484    /// use common_access_token::Claims;
485    ///
486    /// let claims = Claims::new();
487    /// assert!(claims.registered.iss.is_none());
488    /// assert!(claims.custom.is_empty());
489    /// ```
490    pub fn new() -> Self {
491        Self::default()
492    }
493
494    /// Sets the registered claims.
495    ///
496    /// This method replaces any existing registered claims with the provided ones.
497    ///
498    /// # Example
499    ///
500    /// ```
501    /// use common_access_token::{Claims, RegisteredClaims};
502    ///
503    /// let registered = RegisteredClaims::new()
504    ///     .with_issuer("example-issuer")
505    ///     .with_subject("user-123");
506    ///
507    /// let claims = Claims::new().with_registered_claims(registered);
508    /// assert_eq!(claims.registered.iss, Some("example-issuer".to_string()));
509    /// ```
510    pub fn with_registered_claims(mut self, registered: RegisteredClaims) -> Self {
511        self.registered = registered;
512        self
513    }
514
515    /// Adds a custom claim with a string value.
516    ///
517    /// # Example
518    ///
519    /// ```
520    /// use common_access_token::Claims;
521    /// use common_access_token::header::CborValue;
522    ///
523    /// let claims = Claims::new().with_custom_string(100, "custom-value");
524    /// assert!(matches!(claims.custom.get(&100), Some(CborValue::Text(s)) if s == "custom-value"));
525    /// ```
526    pub fn with_custom_string<S: Into<String>>(mut self, key: i32, value: S) -> Self {
527        self.custom.insert(key, CborValue::Text(value.into()));
528        self
529    }
530
531    /// Adds a custom claim with a binary value.
532    ///
533    /// # Example
534    ///
535    /// ```
536    /// use common_access_token::Claims;
537    /// use common_access_token::header::CborValue;
538    ///
539    /// let binary_data = vec![0x01, 0x02, 0x03];
540    /// let claims = Claims::new().with_custom_binary(101, binary_data.clone());
541    /// assert!(matches!(claims.custom.get(&101), Some(CborValue::Bytes(b)) if b == &binary_data));
542    /// ```
543    pub fn with_custom_binary<B: Into<Vec<u8>>>(mut self, key: i32, value: B) -> Self {
544        self.custom.insert(key, CborValue::Bytes(value.into()));
545        self
546    }
547
548    /// Adds a custom claim with an integer value.
549    ///
550    /// # Example
551    ///
552    /// ```
553    /// use common_access_token::Claims;
554    /// use common_access_token::header::CborValue;
555    ///
556    /// let claims = Claims::new().with_custom_int(102, 42);
557    /// assert!(matches!(claims.custom.get(&102), Some(CborValue::Integer(i)) if *i == 42));
558    /// ```
559    pub fn with_custom_int(mut self, key: i32, value: i64) -> Self {
560        self.custom.insert(key, CborValue::Integer(value));
561        self
562    }
563
564    /// Adds a custom claim with a nested map value.
565    ///
566    /// This allows for complex structured data to be included in the token.
567    ///
568    /// # Example
569    ///
570    /// ```
571    /// use common_access_token::{Claims, CborValue};
572    /// use std::collections::BTreeMap;
573    ///
574    /// let mut nested_map = BTreeMap::new();
575    /// nested_map.insert(1, CborValue::Text("nested-value".to_string()));
576    ///
577    /// let claims = Claims::new().with_custom_map(103, nested_map);
578    /// if let Some(CborValue::Map(map)) = claims.custom.get(&103) {
579    ///     if let Some(CborValue::Text(value)) = map.get(&1) {
580    ///         assert_eq!(value, "nested-value");
581    ///     }
582    /// }
583    /// ```
584    pub fn with_custom_map(mut self, key: i32, value: BTreeMap<i32, CborValue>) -> Self {
585        self.custom.insert(key, CborValue::Map(value));
586        self
587    }
588
589    /// Get a custom claim as a string
590    ///
591    /// Returns `Some(&str)` if the claim exists and is a text value, `None` otherwise.
592    ///
593    /// # Example
594    ///
595    /// ```
596    /// use common_access_token::Claims;
597    ///
598    /// let claims = Claims::new().with_custom_string(100, "custom-value");
599    /// assert_eq!(claims.get_custom_string(100), Some("custom-value"));
600    /// assert_eq!(claims.get_custom_string(999), None);
601    /// ```
602    pub fn get_custom_string(&self, key: i32) -> Option<&str> {
603        match self.custom.get(&key) {
604            Some(CborValue::Text(s)) => Some(s.as_str()),
605            _ => None,
606        }
607    }
608
609    /// Get a custom claim as an integer
610    ///
611    /// Returns `Some(i64)` if the claim exists and is an integer value, `None` otherwise.
612    ///
613    /// # Example
614    ///
615    /// ```
616    /// use common_access_token::Claims;
617    ///
618    /// let claims = Claims::new().with_custom_int(100, 42);
619    /// assert_eq!(claims.get_custom_int(100), Some(42));
620    /// assert_eq!(claims.get_custom_int(999), None);
621    /// ```
622    pub fn get_custom_int(&self, key: i32) -> Option<i64> {
623        match self.custom.get(&key) {
624            Some(CborValue::Integer(i)) => Some(*i),
625            _ => None,
626        }
627    }
628
629    /// Get a custom claim as binary data
630    ///
631    /// Returns `Some(&[u8])` if the claim exists and is a bytes value, `None` otherwise.
632    ///
633    /// # Example
634    ///
635    /// ```
636    /// use common_access_token::Claims;
637    ///
638    /// let data = vec![1, 2, 3, 4];
639    /// let claims = Claims::new().with_custom_binary(100, data.clone());
640    /// assert_eq!(claims.get_custom_binary(100), Some(data.as_slice()));
641    /// assert_eq!(claims.get_custom_binary(999), None);
642    /// ```
643    pub fn get_custom_binary(&self, key: i32) -> Option<&[u8]> {
644        match self.custom.get(&key) {
645            Some(CborValue::Bytes(b)) => Some(b.as_slice()),
646            _ => None,
647        }
648    }
649
650    /// Get a reference to a custom claim value
651    ///
652    /// Returns `Some(&CborValue)` if the claim exists, `None` otherwise.
653    ///
654    /// # Example
655    ///
656    /// ```
657    /// use common_access_token::{Claims, CborValue};
658    ///
659    /// let claims = Claims::new().with_custom_string(100, "value");
660    /// if let Some(CborValue::Text(s)) = claims.get_custom_claim(100) {
661    ///     assert_eq!(s, "value");
662    /// }
663    /// ```
664    pub fn get_custom_claim(&self, key: i32) -> Option<&CborValue> {
665        self.custom.get(&key)
666    }
667
668    /// Check if a custom claim exists
669    ///
670    /// # Example
671    ///
672    /// ```
673    /// use common_access_token::Claims;
674    ///
675    /// let claims = Claims::new().with_custom_string(100, "value");
676    /// assert!(claims.has_custom_claim(100));
677    /// assert!(!claims.has_custom_claim(999));
678    /// ```
679    pub fn has_custom_claim(&self, key: i32) -> bool {
680        self.custom.contains_key(&key)
681    }
682
683    /// Converts all claims (registered and custom) to a combined claims map.
684    ///
685    /// This method is primarily used internally for token encoding.
686    ///
687    /// # Example
688    ///
689    /// ```
690    /// use common_access_token::{Claims, RegisteredClaims};
691    ///
692    /// let claims = Claims::new()
693    ///     .with_registered_claims(RegisteredClaims::new().with_issuer("example-issuer"))
694    ///     .with_custom_string(100, "custom-value");
695    ///
696    /// let map = claims.to_map();
697    /// assert_eq!(map.len(), 2); // One registered claim + one custom claim
698    /// ```
699    pub fn to_map(&self) -> ClaimsMap {
700        let mut map = self.registered.to_map();
701
702        // Add custom claims
703        for (key, value) in &self.custom {
704            map.insert(*key, value.clone());
705        }
706
707        map
708    }
709
710    /// Creates a Claims struct from a claims map.
711    ///
712    /// This method is primarily used internally for token decoding.
713    ///
714    /// # Example
715    ///
716    /// ```
717    /// use common_access_token::{Claims, CborValue};
718    /// use common_access_token::claims::{keys, ClaimsMap};
719    ///
720    /// let mut map = ClaimsMap::new();
721    /// map.insert(keys::ISS, CborValue::Text("example-issuer".to_string()));
722    /// map.insert(100, CborValue::Text("custom-value".to_string()));
723    ///
724    /// let claims = Claims::from_map(&map);
725    /// assert_eq!(claims.registered.iss, Some("example-issuer".to_string()));
726    /// assert!(claims.custom.contains_key(&100));
727    /// ```
728    pub fn from_map(map: &ClaimsMap) -> Self {
729        let registered = RegisteredClaims::from_map(map);
730
731        // Extract custom claims (all keys not in the registered claims)
732        let mut custom = ClaimsMap::new();
733        for (key, value) in map {
734            if !matches!(
735                *key,
736                keys::ISS | keys::SUB | keys::AUD | keys::EXP | keys::NBF | keys::IAT | keys::CTI
737            ) {
738                custom.insert(*key, value.clone());
739            }
740        }
741
742        Self { registered, custom }
743    }
744}