use crate::error::CoreError;
use async_trait::async_trait;
use once_cell::sync::OnceCell;
use serde_json::{Map, Value};
#[derive(Debug, Clone)]
pub struct FilterClause {
pub field: String,
pub op: FilterOp,
pub value: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterOp {
Eq,
Contains,
Gte,
Lte,
}
#[derive(Debug, Clone)]
pub struct QueryOptions {
pub page: u64,
pub per_page: u64,
pub sort_by: Option<String>,
pub sort_desc: bool,
pub filters: Vec<FilterClause>,
}
impl QueryOptions {
pub fn offset(&self) -> u64 {
(self.page.max(1) - 1) * self.per_page
}
}
#[derive(Debug, Clone)]
pub struct ListPage {
pub rows: Vec<Value>,
pub total: u64,
}
#[derive(Debug, Clone, Default)]
pub struct CreateOutcome {
pub last_insert_id: Option<String>,
}
#[derive(Debug, Clone)]
pub enum StorageError {
NotFound,
Backend(String),
}
impl std::fmt::Display for StorageError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StorageError::NotFound => f.write_str("not found"),
StorageError::Backend(m) => write!(f, "storage backend error: {m}"),
}
}
}
impl std::error::Error for StorageError {}
impl From<StorageError> for CoreError {
fn from(e: StorageError) -> Self {
match e {
StorageError::NotFound => CoreError::NotFound,
StorageError::Backend(m) => CoreError::Internal(m),
}
}
}
#[async_trait]
pub trait Storage: Send + Sync {
async fn list(&self, table: &str, opts: &QueryOptions) -> Result<ListPage, StorageError>;
async fn get(&self, table: &str, pk: &str, id: &str) -> Result<Option<Value>, StorageError>;
async fn find_one_by(
&self,
_table: &str,
_column: &str,
_value: &str,
) -> Result<Option<Value>, StorageError> {
Err(StorageError::Backend(
"find_one_by not supported by this storage backend".into(),
))
}
async fn create(
&self,
table: &str,
data: Map<String, Value>,
) -> Result<CreateOutcome, StorageError>;
async fn update(
&self,
table: &str,
pk: &str,
id: &str,
data: Map<String, Value>,
) -> Result<u64, StorageError>;
async fn delete(
&self,
table: &str,
pk: &str,
id: &str,
soft: bool,
) -> Result<u64, StorageError>;
async fn execute_raw(&self, _statement: &str) -> Result<u64, StorageError> {
Err(StorageError::Backend(
"execute_raw not supported by this storage backend".into(),
))
}
async fn health(&self) -> bool;
}
static STORAGE: OnceCell<Box<dyn Storage>> = OnceCell::new();
pub fn set_storage(storage: Box<dyn Storage>) {
if STORAGE.set(storage).is_err() {
tracing::warn!("adminx storage backend was already initialized; ignoring reset");
}
}
pub fn storage() -> &'static dyn Storage {
STORAGE
.get()
.expect("adminx storage backend not initialized; call set_storage() first")
.as_ref()
}
pub async fn seed(statements: &[&str]) -> Result<(), StorageError> {
let s = storage();
for stmt in statements {
s.execute_raw(stmt).await?;
}
Ok(())
}