1use crate::error::CoreError;
8use async_trait::async_trait;
9use once_cell::sync::OnceCell;
10use serde_json::{Map, Value};
11
12#[derive(Debug, Clone)]
14pub struct FilterClause {
15 pub field: String,
16 pub op: FilterOp,
17 pub value: String,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum FilterOp {
23 Eq,
25 Contains,
27 Gte,
29 Lte,
31}
32
33#[derive(Debug, Clone)]
35pub struct QueryOptions {
36 pub page: u64,
37 pub per_page: u64,
38 pub sort_by: Option<String>,
39 pub sort_desc: bool,
40 pub filters: Vec<FilterClause>,
42}
43
44impl QueryOptions {
45 pub fn offset(&self) -> u64 {
46 (self.page.max(1) - 1) * self.per_page
47 }
48}
49
50#[derive(Debug, Clone)]
52pub struct ListPage {
53 pub rows: Vec<Value>,
54 pub total: u64,
55}
56
57#[derive(Debug, Clone, Default)]
60pub struct CreateOutcome {
61 pub last_insert_id: Option<String>,
62}
63
64#[derive(Debug, Clone)]
65pub enum StorageError {
66 NotFound,
67 Backend(String),
68}
69
70impl std::fmt::Display for StorageError {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 match self {
73 StorageError::NotFound => f.write_str("not found"),
74 StorageError::Backend(m) => write!(f, "storage backend error: {m}"),
75 }
76 }
77}
78
79impl std::error::Error for StorageError {}
82
83impl From<StorageError> for CoreError {
84 fn from(e: StorageError) -> Self {
85 match e {
86 StorageError::NotFound => CoreError::NotFound,
87 StorageError::Backend(m) => CoreError::Internal(m),
88 }
89 }
90}
91
92#[async_trait]
93pub trait Storage: Send + Sync {
94 async fn list(&self, table: &str, opts: &QueryOptions) -> Result<ListPage, StorageError>;
95
96 async fn get(&self, table: &str, pk: &str, id: &str) -> Result<Option<Value>, StorageError>;
97
98 async fn find_one_by(
102 &self,
103 _table: &str,
104 _column: &str,
105 _value: &str,
106 ) -> Result<Option<Value>, StorageError> {
107 Err(StorageError::Backend(
108 "find_one_by not supported by this storage backend".into(),
109 ))
110 }
111
112 async fn create(
113 &self,
114 table: &str,
115 data: Map<String, Value>,
116 ) -> Result<CreateOutcome, StorageError>;
117
118 async fn update(
120 &self,
121 table: &str,
122 pk: &str,
123 id: &str,
124 data: Map<String, Value>,
125 ) -> Result<u64, StorageError>;
126
127 async fn delete(
130 &self,
131 table: &str,
132 pk: &str,
133 id: &str,
134 soft: bool,
135 ) -> Result<u64, StorageError>;
136
137 async fn execute_raw(&self, _statement: &str) -> Result<u64, StorageError> {
143 Err(StorageError::Backend(
144 "execute_raw not supported by this storage backend".into(),
145 ))
146 }
147
148 async fn health(&self) -> bool;
149}
150
151static STORAGE: OnceCell<Box<dyn Storage>> = OnceCell::new();
152
153pub fn set_storage(storage: Box<dyn Storage>) {
155 if STORAGE.set(storage).is_err() {
156 tracing::warn!("adminx storage backend was already initialized; ignoring reset");
157 }
158}
159
160pub fn storage() -> &'static dyn Storage {
162 STORAGE
163 .get()
164 .expect("adminx storage backend not initialized; call set_storage() first")
165 .as_ref()
166}
167
168pub async fn seed(statements: &[&str]) -> Result<(), StorageError> {
185 let s = storage();
186 for stmt in statements {
187 s.execute_raw(stmt).await?;
188 }
189 Ok(())
190}