scufflecloud_core/
operations.rs1use std::sync::Arc;
2
3use core_cedar::CedarEntity;
4use diesel_async::TransactionManager;
5use ext_traits::ResultExt;
6
7use crate::cedar::{self, Action};
8use crate::http_ext::CoreRequestExt;
9
10pub(crate) mod login;
11pub(crate) mod organization_invitations;
12pub(crate) mod organizations;
13pub(crate) mod user_session_requests;
14pub(crate) mod user_sessions;
15pub(crate) mod users;
16
17pub(crate) struct OperationDriver<'a, G: core_traits::Global> {
18 global: &'a Arc<G>,
19 conn: Option<G::Connection<'a>>,
20}
21
22impl<'a, G: core_traits::Global> OperationDriver<'a, G> {
23 const fn new(global: &'a Arc<G>) -> Self {
24 OperationDriver { global, conn: None }
25 }
26
27 pub(crate) async fn conn(&mut self) -> Result<&mut G::Connection<'a>, tonic::Status> {
28 let conn = &mut self.conn;
29 if let Some(conn) = conn {
30 return Ok(conn);
31 }
32
33 let mut db = self
34 .global
35 .db()
36 .await
37 .into_tonic_internal_err("failed to connect to database")?;
38 <G::Connection<'a> as diesel_async::AsyncConnection>::TransactionManager::begin_transaction(&mut db)
39 .await
40 .into_tonic_internal_err("failed to begin transaction")?;
41 Ok(conn.insert(db))
42 }
43
44 async fn abort(self) -> Result<(), tonic::Status> {
45 let Some(mut conn) = self.conn else {
46 return Ok(());
47 };
48
49 <G::Connection<'a> as diesel_async::AsyncConnection>::TransactionManager::rollback_transaction(&mut conn)
50 .await
51 .into_tonic_internal_err("failed to rollback transaction")
52 }
53
54 async fn commit(self) -> Result<(), tonic::Status> {
55 let Some(mut conn) = self.conn else {
56 return Ok(());
57 };
58
59 <G::Connection<'a> as diesel_async::AsyncConnection>::TransactionManager::commit_transaction(&mut conn)
60 .await
61 .into_tonic_internal_err("failed to commit transaction")
62 }
63}
64
65pub(crate) trait Operation<G: core_traits::Global>: ext_traits::RequestExt + Sized + Send {
77 type Principal: CedarEntity + Send + Sync;
79 type Resource: CedarEntity + Send + Sync;
81 type Response: Send;
83
84 const ACTION: Action;
86
87 async fn validate(&mut self) -> Result<(), tonic::Status> {
91 Ok(())
92 }
93
94 fn load_principal(
95 &mut self,
96 driver: &mut OperationDriver<'_, G>,
97 ) -> impl Future<Output = Result<Self::Principal, tonic::Status>> + Send;
98
99 fn load_resource(
100 &mut self,
101 driver: &mut OperationDriver<'_, G>,
102 ) -> impl Future<Output = Result<Self::Resource, tonic::Status>> + Send;
103
104 fn execute(
105 self,
106 driver: &mut OperationDriver<'_, G>,
107 principal: Self::Principal,
108 resource: Self::Resource,
109 ) -> impl Future<Output = Result<Self::Response, tonic::Status>> + Send;
110
111 async fn run(mut self) -> Result<Self::Response, tonic::Status> {
112 self.validate().await?;
113
114 let global = self.global::<G>()?;
115 let mut driver = OperationDriver::new(&global);
116
117 let fut = async {
118 let principal = self.load_principal(&mut driver).await?;
119 let resource = self.load_resource(&mut driver).await?;
120
121 cedar::is_authorized(&global, self.session(), &principal, &Self::ACTION, &resource).await?;
122
123 self.execute(&mut driver, principal, resource).await
124 };
125
126 match fut.await {
127 Ok(resp) => {
128 driver.commit().await?;
129 Ok(resp)
130 }
131 Err(e) => {
132 driver.abort().await?;
133 Err(e)
134 }
135 }
136 }
137}