alibi_core/store/contracts/
transaction.rs1use super::*;
2pub type BoxedTransactionValue = Box<dyn Any + Send>;
3
4pub type TransactionFuture<'a> =
5 Pin<Box<dyn Future<Output = AuthResult<BoxedTransactionValue>> + Send + 'a>>;
6
7pub type TypedTransactionFuture<'a, T> = Pin<Box<dyn Future<Output = AuthResult<T>> + Send + 'a>>;
8
9pub type TransactionWork<S> =
10 dyn for<'tx> FnOnce(&'tx dyn AuthTransaction<S>) -> TransactionFuture<'tx> + Send;
11
12#[async_trait]
13pub trait AuthTransaction<S: AuthSchema>: Send + Sync {
14 async fn provider_verification_output(
18 &self,
19 _id: &str,
20 ) -> AuthResult<Option<serde_json::Value>> {
21 Ok(None)
22 }
23
24 async fn prepare_secondary_session_creation(
26 &self,
27 _input: CreateSession,
28 _persist: bool,
29 ) -> AuthResult<S::Session> {
30 Err(AuthError::NotImplemented(
31 "Transactional secondary session creation is unsupported".into(),
32 ))
33 }
34
35 async fn prepare_secondary_session_update(
37 &self,
38 _session: S::Session,
39 _expires_at: Option<chrono::DateTime<chrono::Utc>>,
40 _fields: crate::field_policy::FieldValues,
41 ) -> AuthResult<Option<(S::Session, crate::field_policy::FieldValues)>> {
42 Err(AuthError::NotImplemented(
43 "Transactional secondary session updates are unsupported".into(),
44 ))
45 }
46 async fn complete_secondary_session_update(
48 &self,
49 _session: S::Session,
50 _expires_at: Option<chrono::DateTime<chrono::Utc>>,
51 _fields: crate::field_policy::FieldValues,
52 _persist: bool,
53 ) -> AuthResult<Option<S::Session>> {
54 Err(AuthError::NotImplemented(
55 "Transactional secondary session updates are unsupported".into(),
56 ))
57 }
58
59 async fn list_jwks(&self) -> AuthResult<Vec<Jwk>> {
61 Err(crate::AuthError::config(
62 "Transactional JWKS reads are unsupported by this store",
63 ))
64 }
65 async fn get_jwk_by_id(&self, _id: &str) -> AuthResult<Option<Jwk>> {
67 Err(crate::AuthError::config(
68 "Transactional JWKS reads are unsupported by this store",
69 ))
70 }
71 async fn create_jwk(&self, _data: CreateJwk) -> AuthResult<Jwk> {
73 Err(crate::AuthError::config(
74 "Transactional JWKS writes are unsupported by this store",
75 ))
76 }
77
78 async fn create_user_record(
81 &self,
82 create_user: CreateUser,
83 ) -> AuthResult<crate::AdapterRecord<S::User>> {
84 crate::AdapterRecord::physical(self.create_user(create_user).await?)
85 }
86
87 async fn create_user_with_source_record(
90 &self,
91 create_user: CreateUser,
92 source: UserValidationSource,
93 ) -> AuthResult<crate::AdapterRecord<S::User>> {
94 crate::AdapterRecord::physical(self.create_user_with_source(create_user, source).await?)
95 }
96
97 async fn create_user_prepared_record(
100 &self,
101 prepared: PreparedUserCreation,
102 ) -> AuthResult<crate::AdapterRecord<S::User>> {
103 crate::AdapterRecord::physical(self.create_user_prepared(prepared).await?)
104 }
105
106 async fn get_user_by_id_record(
109 &self,
110 id: &str,
111 ) -> AuthResult<Option<crate::AdapterRecord<S::User>>> {
112 self.get_user_by_id(id)
113 .await?
114 .map(crate::AdapterRecord::physical)
115 .transpose()
116 }
117
118 async fn create_account_record(
121 &self,
122 create_account: CreateAccount,
123 ) -> AuthResult<crate::AdapterRecord<S::Account>> {
124 crate::AdapterRecord::physical(self.create_account(create_account).await?)
125 }
126
127 async fn create_session_record(
130 &self,
131 create_session: CreateSession,
132 ) -> AuthResult<crate::AdapterRecord<S::Session>> {
133 crate::AdapterRecord::physical(self.create_session(create_session).await?)
134 }
135
136 async fn update_session_active_organization_record(
139 &self,
140 token: &str,
141 organization_id: Option<&str>,
142 ) -> AuthResult<crate::AdapterRecord<S::Session>> {
143 crate::AdapterRecord::physical(
144 self.update_session_active_organization(token, organization_id)
145 .await?,
146 )
147 }
148
149 async fn update_session_active_team_record(
152 &self,
153 token: &str,
154 team_id: Option<&str>,
155 ) -> AuthResult<crate::AdapterRecord<S::Session>> {
156 crate::AdapterRecord::physical(self.update_session_active_team(token, team_id).await?)
157 }
158
159 async fn create_verification_record(
162 &self,
163 _data: VerificationCreation,
164 _publication: VerificationPublication,
165 ) -> AuthResult<Option<VerificationSnapshot>> {
166 Err(AuthError::NotImplemented(
167 "Verification publication phases are not supported by this store".into(),
168 ))
169 }
170 async fn get_team(&self, _organization_id: &str, _team_id: &str) -> AuthResult<Option<Team>> {
172 Err(AuthError::NotImplemented(
173 "Team lookup in a transaction is not supported by this store".into(),
174 ))
175 }
176 async fn add_team_member(
178 &self,
179 _team_id: &str,
180 _user_id: &str,
181 _maximum: Option<f64>,
182 ) -> AuthResult<AddTeamMemberResult> {
183 Err(AuthError::NotImplemented(
184 "Team admission in a transaction is not supported by this store".into(),
185 ))
186 }
187 async fn create_member(&self, _member: CreateMember) -> AuthResult<Member> {
188 Err(AuthError::NotImplemented(
189 "Member creation in a transaction is not supported by this store".into(),
190 ))
191 }
192 async fn update_session_active_team(
194 &self,
195 _token: &str,
196 _team_id: Option<&str>,
197 ) -> AuthResult<S::Session> {
198 Err(AuthError::NotImplemented(
199 "Active team updates in a transaction are not supported by this store".into(),
200 ))
201 }
202 async fn update_session_active_organization(
203 &self,
204 _token: &str,
205 _organization_id: Option<&str>,
206 ) -> AuthResult<S::Session> {
207 Err(AuthError::NotImplemented(
208 "Active organization updates in a transaction are not supported by this store".into(),
209 ))
210 }
211 async fn get_user_by_id(&self, _id: &str) -> AuthResult<Option<S::User>> {
213 Err(AuthError::NotImplemented(
214 "User lookup in a transaction is not supported by this store".to_owned(),
215 ))
216 }
217 async fn create_passkey(&self, _passkey: CreatePasskey) -> AuthResult<Passkey> {
219 Err(AuthError::NotImplemented(
220 "Passkey creation in a transaction is not supported by this store".to_owned(),
221 ))
222 }
223 async fn create_user(&self, create_user: CreateUser) -> AuthResult<S::User>;
224 async fn create_user_with_source(
227 &self,
228 create_user: CreateUser,
229 _source: UserValidationSource,
230 ) -> AuthResult<S::User> {
231 self.create_user(create_user).await
232 }
233 async fn create_user_prepared(&self, _prepared: PreparedUserCreation) -> AuthResult<S::User> {
236 Err(AuthError::NotImplemented(
237 "Prepared user creation is not supported by this transaction".into(),
238 ))
239 }
240 async fn create_account(&self, create_account: CreateAccount) -> AuthResult<S::Account>;
241 async fn create_session(&self, create_session: CreateSession) -> AuthResult<S::Session>;
242 async fn create_verification(
243 &self,
244 _verification: CreateVerification,
245 ) -> AuthResult<S::Verification> {
246 Err(AuthError::NotImplemented(
247 "Verification creation in a transaction is not supported by this store".to_owned(),
248 ))
249 }
250}
251
252#[async_trait]
253pub trait TransactionStore<S: AuthSchema>: Send + Sync {
254 async fn transaction_boxed(
255 &self,
256 work: Box<TransactionWork<S>>,
257 ) -> AuthResult<BoxedTransactionValue>;
258}
259
260pub async fn transaction<S, T, F>(store: &dyn AuthStore<S>, work: F) -> AuthResult<T>
265where
266 S: AuthSchema,
267 T: Send + 'static,
268 F: for<'tx> FnOnce(&'tx dyn AuthTransaction<S>) -> TypedTransactionFuture<'tx, T>
269 + Send
270 + 'static,
271{
272 let value = store
273 .transaction_boxed(Box::new(move |tx| {
274 Box::pin(async move {
275 let result: BoxedTransactionValue = Box::new(work(tx).await?);
276 Ok(result)
277 })
278 }))
279 .await?;
280
281 value
282 .downcast::<T>()
283 .map(|boxed| *boxed)
284 .map_err(|_error| AuthError::internal("store returned an invalid transaction payload"))
285}