Skip to main content

uselesskey_core_jwk_builder/
lib.rs

1#![forbid(unsafe_code)]
2
3//! JWKS composition with deterministic ordering semantics.
4//!
5//! This crate centralizes JWKS assembly behavior that is shared across JWK-producing
6//! key fixtures. Entries are sorted by `kid` and preserve insertion order for duplicate
7//! `kid` values.
8
9use uselesskey_core_jwk_shape::{AnyJwk, Jwks, PrivateJwk, PublicJwk};
10use uselesskey_core_jwks_order::{HasKid, KidSorted};
11
12/// Incrementally assembles a [`Jwks`] set with deterministic `kid`-based ordering.
13///
14/// Keys are sorted lexicographically by `kid`; duplicate `kid` values
15/// preserve insertion order.
16#[derive(Clone, Default)]
17pub struct JwksBuilder {
18    entries: KidSorted<OrderedJwk>,
19}
20
21#[derive(Clone)]
22struct OrderedJwk(AnyJwk);
23
24impl HasKid for OrderedJwk {
25    fn kid(&self) -> &str {
26        self.0.kid()
27    }
28}
29
30impl JwksBuilder {
31    /// Create an empty builder.
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// Append a public JWK and return `self` for chaining.
37    pub fn add_public(mut self, jwk: PublicJwk) -> Self {
38        self.push_public(jwk);
39        self
40    }
41
42    /// Append a private JWK and return `self` for chaining.
43    pub fn add_private(mut self, jwk: PrivateJwk) -> Self {
44        self.push_private(jwk);
45        self
46    }
47
48    /// Append any JWK variant and return `self` for chaining.
49    pub fn add_any(mut self, jwk: AnyJwk) -> Self {
50        self.push_any(jwk);
51        self
52    }
53
54    /// Append a public JWK by mutable reference.
55    pub fn push_public(&mut self, jwk: PublicJwk) -> &mut Self {
56        self.push_any(AnyJwk::from(jwk))
57    }
58
59    /// Append a private JWK by mutable reference.
60    pub fn push_private(&mut self, jwk: PrivateJwk) -> &mut Self {
61        self.push_any(AnyJwk::from(jwk))
62    }
63
64    /// Append any JWK variant by mutable reference.
65    pub fn push_any(&mut self, jwk: AnyJwk) -> &mut Self {
66        self.entries.push(OrderedJwk(jwk));
67        self
68    }
69
70    /// Consume the builder and return the sorted [`Jwks`] set.
71    pub fn build(self) -> Jwks {
72        Jwks {
73            keys: self.entries.build().into_iter().map(|jwk| jwk.0).collect(),
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    fn sample_rsa_public(kid: &str, n: &str) -> PublicJwk {
83        PublicJwk::Rsa(uselesskey_core_jwk_shape::RsaPublicJwk {
84            kty: "RSA",
85            use_: "sig",
86            alg: "RS256",
87            kid: kid.to_string(),
88            n: n.to_string(),
89            e: "AQAB".to_string(),
90        })
91    }
92
93    fn sample_oct_private(kid: &str, k: &str) -> PrivateJwk {
94        PrivateJwk::Oct(uselesskey_core_jwk_shape::OctJwk {
95            kty: "oct",
96            use_: "sig",
97            alg: "HS256",
98            kid: kid.to_string(),
99            k: k.to_string(),
100        })
101    }
102
103    #[test]
104    fn jwks_builder_orders_by_kid() {
105        let jwk1 = PublicJwk::Rsa(uselesskey_core_jwk_shape::RsaPublicJwk {
106            kty: "RSA",
107            use_: "sig",
108            alg: "RS256",
109            kid: "b".to_string(),
110            n: "n".to_string(),
111            e: "e".to_string(),
112        });
113        let jwk2 = PublicJwk::Ec(uselesskey_core_jwk_shape::EcPublicJwk {
114            kty: "EC",
115            use_: "sig",
116            alg: "ES256",
117            crv: "P-256",
118            kid: "a".to_string(),
119            x: "x".to_string(),
120            y: "y".to_string(),
121        });
122
123        let jwks = JwksBuilder::new().add_public(jwk1).add_public(jwk2).build();
124
125        assert_eq!(jwks.keys.len(), 2);
126        assert_eq!(jwks.keys[0].kid(), "a");
127        assert_eq!(jwks.keys[1].kid(), "b");
128    }
129
130    #[test]
131    fn jwks_builder_stable_for_same_kid() {
132        let jwk1 = PublicJwk::Rsa(uselesskey_core_jwk_shape::RsaPublicJwk {
133            kty: "RSA",
134            use_: "sig",
135            alg: "RS256",
136            kid: "same".to_string(),
137            n: "n1".to_string(),
138            e: "e1".to_string(),
139        });
140        let jwk2 = PublicJwk::Rsa(uselesskey_core_jwk_shape::RsaPublicJwk {
141            kty: "RSA",
142            use_: "sig",
143            alg: "RS256",
144            kid: "same".to_string(),
145            n: "n2".to_string(),
146            e: "e2".to_string(),
147        });
148
149        let jwks = JwksBuilder::new().add_public(jwk1).add_public(jwk2).build();
150
151        assert_eq!(jwks.keys[0].kid(), "same");
152        assert_eq!(jwks.keys[1].kid(), "same");
153        let first = jwks.keys[0].to_value();
154        let second = jwks.keys[1].to_value();
155        assert_eq!(first["n"], "n1");
156        assert_eq!(second["n"], "n2");
157    }
158
159    #[test]
160    fn jwks_builder_push_methods_and_display() {
161        let jwk_pub = sample_rsa_public("kid-b", "nb");
162        let jwk_priv = sample_oct_private("kid-a", "ka");
163
164        let mut builder = JwksBuilder::new();
165        builder.push_public(jwk_pub.clone());
166        builder.push_private(jwk_priv.clone());
167        builder.push_any(AnyJwk::from(jwk_pub.clone()));
168
169        let jwks = builder.build();
170        let json = jwks.to_string();
171        let v: serde_json::Value = serde_json::from_str(&json).expect("valid JSON");
172
173        let keys = v["keys"].as_array().expect("keys array");
174        assert_eq!(keys.len(), 3);
175        assert_eq!(jwks.keys.len(), 3);
176    }
177
178    #[test]
179    fn jwks_builder_add_methods_work() {
180        let jwk_priv = sample_oct_private("kid-a", "ka");
181        let jwk_any = AnyJwk::from(sample_rsa_public("kid-b", "nb"));
182
183        let jwks = JwksBuilder::new()
184            .add_private(jwk_priv)
185            .add_any(jwk_any)
186            .build();
187
188        assert_eq!(jwks.keys.len(), 2);
189    }
190}