Skip to main content

uselesskey_core_jwks_order/
lib.rs

1#![forbid(unsafe_code)]
2
3//! Stable kid-based ordering helper for JWKS-like collections.
4//!
5//! [`KidSorted`] collects items that implement [`HasKid`] and returns them
6//! sorted lexicographically by `kid`, with ties broken by insertion order.
7//! This guarantees deterministic JWKS output regardless of the order keys
8//! are generated.
9//!
10//! # Examples
11//!
12//! ```
13//! use uselesskey_core_jwks_order::{HasKid, KidSorted};
14//!
15//! struct Key { kid: String }
16//! impl HasKid for Key {
17//!     fn kid(&self) -> &str { &self.kid }
18//! }
19//!
20//! let mut sorter = KidSorted::new();
21//! sorter.push(Key { kid: "c".into() });
22//! sorter.push(Key { kid: "a".into() });
23//! sorter.push(Key { kid: "b".into() });
24//!
25//! let keys = sorter.build();
26//! let kids: Vec<&str> = keys.iter().map(|k| k.kid()).collect();
27//! assert_eq!(kids, ["a", "b", "c"]);
28//! ```
29
30use core::fmt;
31
32/// A minimal trait for items with a stable key-id used for ordering.
33pub trait HasKid {
34    /// Return the sort key for the item.
35    fn kid(&self) -> &str;
36}
37
38/// Store items and return them sorted by `kid` with deterministic
39/// tie-breakers based on insertion order.
40#[derive(Clone)]
41pub struct KidSorted<T: HasKid> {
42    entries: Vec<Entry<T>>,
43}
44
45impl<T: HasKid> Default for KidSorted<T> {
46    fn default() -> Self {
47        Self {
48            entries: Vec::new(),
49        }
50    }
51}
52
53#[derive(Clone)]
54struct Entry<T: HasKid> {
55    kid: String,
56    index: usize,
57    value: T,
58}
59
60impl<T: HasKid + fmt::Debug> fmt::Debug for KidSorted<T> {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        f.debug_struct("KidSorted")
63            .field("entries", &self.entries.len())
64            .finish_non_exhaustive()
65    }
66}
67
68impl<T: HasKid> KidSorted<T> {
69    /// Construct an empty ordered collection.
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    /// Push a value into the collection.
75    pub fn push(&mut self, value: T) {
76        let index = self.entries.len();
77        let kid = value.kid().to_string();
78        self.entries.push(Entry { kid, index, value });
79    }
80
81    /// Build the final vector, sorted by `kid`, stable on insertion order.
82    pub fn build(mut self) -> Vec<T> {
83        self.entries
84            .sort_by(|a, b| a.kid.cmp(&b.kid).then(a.index.cmp(&b.index)));
85        self.entries.into_iter().map(|e| e.value).collect()
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::{HasKid, KidSorted};
92
93    #[derive(Debug, Clone)]
94    struct TestItem {
95        kid: &'static str,
96        payload: &'static str,
97    }
98
99    impl HasKid for TestItem {
100        fn kid(&self) -> &str {
101            self.kid
102        }
103    }
104
105    #[test]
106    fn orders_items_by_kid() {
107        let mut sorter = KidSorted::new();
108        sorter.push(TestItem {
109            kid: "b",
110            payload: "second",
111        });
112        sorter.push(TestItem {
113            kid: "a",
114            payload: "first",
115        });
116        let items = sorter.build();
117        let order: Vec<_> = items.iter().map(|item| item.payload).collect();
118
119        assert_eq!(order, vec!["first", "second"]);
120    }
121
122    #[test]
123    fn preserves_insertion_for_equal_kids() {
124        let mut sorter = KidSorted::new();
125        sorter.push(TestItem {
126            kid: "dup",
127            payload: "one",
128        });
129        sorter.push(TestItem {
130            kid: "dup",
131            payload: "two",
132        });
133        sorter.push(TestItem {
134            kid: "dup",
135            payload: "three",
136        });
137
138        let items = sorter.build();
139        let order: Vec<_> = items.iter().map(|item| item.payload).collect();
140
141        assert_eq!(order, vec!["one", "two", "three"]);
142    }
143}