1pub mod file;
4pub mod memory;
5
6pub use file::FileStore;
7pub use memory::MemoryStore;
8
9use crate::{
11 _prelude::*,
12 auth::{ScopeSet, TokenFamily, TokenRecord},
13};
14
15pub type StoreFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T, StoreError>> + 'a + Send>>;
17
18pub trait BrokerStore
20where
21 Self: Send + Sync,
22{
23 fn save(&self, record: TokenRecord) -> StoreFuture<'_, ()>;
25
26 fn fetch<'a>(
28 &'a self,
29 family: &'a TokenFamily,
30 scope: &'a ScopeSet,
31 ) -> StoreFuture<'a, Option<TokenRecord>>;
32
33 fn compare_and_swap_refresh<'a>(
35 &'a self,
36 family: &'a TokenFamily,
37 scope: &'a ScopeSet,
38 expected_refresh: Option<&'a str>,
39 replacement: TokenRecord,
40 ) -> StoreFuture<'a, CompareAndSwapOutcome>;
41
42 fn revoke<'a>(
44 &'a self,
45 family: &'a TokenFamily,
46 scope: &'a ScopeSet,
47 instant: OffsetDateTime,
48 ) -> StoreFuture<'a, Option<TokenRecord>>;
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
53pub enum CompareAndSwapOutcome {
54 Updated,
56 RefreshMismatch,
58 Missing,
60}
61
62#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, ThisError)]
64pub enum StoreError {
65 #[error("Serialization error: {message}.")]
67 Serialization {
68 message: String,
70 },
71 #[error("Backend failure: {message}.")]
73 Backend {
74 message: String,
76 },
77}
78
79#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
81pub struct StoreKey {
82 pub family: TokenFamily,
84 pub scope_fingerprint: String,
86}
87impl StoreKey {
88 pub fn new(family: &TokenFamily, scope: &ScopeSet) -> Self {
90 Self { family: family.clone(), scope_fingerprint: scope.fingerprint() }
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
98 use crate::{
99 auth::{PrincipalId, ScopeSet, TenantId},
100 error::Error,
101 };
102 use std::error::Error as StdError;
103
104 #[test]
105 fn store_error_converts_into_broker_error_with_source() {
106 let store_error = StoreError::Backend { message: "database unreachable".into() };
107 let broker_error: Error = store_error.clone().into();
108
109 assert!(matches!(broker_error, Error::Storage(_)));
110 assert!(broker_error.to_string().contains("database unreachable"));
111
112 let source = StdError::source(&broker_error)
113 .expect("Broker error should expose the original store error as its source.");
114
115 assert_eq!(source.to_string(), store_error.to_string());
116 }
117
118 #[test]
119 fn store_key_uses_scope_fingerprint() {
120 let tenant = TenantId::new("tenant-1").expect("Tenant fixture should be valid.");
121 let principal =
122 PrincipalId::new("principal-1").expect("Principal fixture should be valid.");
123 let family = TokenFamily::new(tenant, principal);
124 let scope_a =
125 ScopeSet::new(["profile", "email"]).expect("First scope fixture should be valid.");
126 let scope_b =
127 ScopeSet::new(["email", "profile"]).expect("Second scope fixture should be valid.");
128 let key_a = StoreKey::new(&family, &scope_a);
129 let key_b = StoreKey::new(&family, &scope_b);
130
131 assert_eq!(key_a.scope_fingerprint, key_b.scope_fingerprint);
132 assert_eq!(key_a.family, key_b.family);
133 assert_eq!(key_a, key_b);
134 }
135
136 #[test]
137 fn compare_and_swap_outcome_can_be_serialized() {
138 let payload = serde_json::to_string(&CompareAndSwapOutcome::Updated)
139 .expect("CompareAndSwapOutcome should serialize to JSON.");
140
141 assert_eq!(payload, "\"Updated\"");
142
143 let round_trip: CompareAndSwapOutcome = serde_json::from_str(&payload)
144 .expect("Serialized outcome should deserialize from JSON.");
145
146 assert_eq!(round_trip, CompareAndSwapOutcome::Updated);
147 }
148}