use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum RepositoryError {
#[error("Entity not found: {0}")]
NotFound(String),
#[error("Invalid parameters: {0}")]
InvalidParams(String),
#[error("Network error: {0}")]
Network(String),
#[error("Authentication error: {0}")]
Authentication(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Repository error: {0}")]
Other(String),
}
pub type RepositoryResult<T> = Result<T, RepositoryError>;
impl From<crate::core::error::Error> for RepositoryError {
fn from(err: crate::core::error::Error) -> Self {
use crate::core::error::Error;
match err {
Error::NotFound(msg) => RepositoryError::NotFound(msg),
Error::Network(msg) => RepositoryError::Network(msg),
Error::Authentication(msg) => RepositoryError::Authentication(msg),
Error::Validation(msg) => RepositoryError::InvalidParams(msg),
Error::Http { status: 404, .. } => {
RepositoryError::NotFound("Resource not found".to_string())
}
Error::Http { message, .. } => RepositoryError::Network(message),
other => RepositoryError::Other(other.to_string()),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PaginationParams {
pub page: Option<u32>,
pub limit: Option<u32>,
pub offset: Option<u32>,
}
impl PaginationParams {
pub fn new() -> Self {
Self::default()
}
pub fn with_page(mut self, page: u32) -> Self {
self.page = Some(page);
self
}
pub fn with_limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
pub fn with_offset(mut self, offset: u32) -> Self {
self.offset = Some(offset);
self
}
pub fn to_query_params(&self) -> Vec<(String, String)> {
let mut params = vec![];
if let Some(page) = self.page {
params.push(("page".to_string(), page.to_string()));
}
if let Some(limit) = self.limit {
params.push(("limit".to_string(), limit.to_string()));
}
if let Some(offset) = self.offset {
params.push(("offset".to_string(), offset.to_string()));
}
params
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SortOrder {
Asc,
Desc,
}
impl Default for SortOrder {
fn default() -> Self {
Self::Asc
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FilterParams {
pub query: Option<String>,
pub archived: Option<bool>,
pub created_after: Option<String>,
pub created_before: Option<String>,
pub updated_after: Option<String>,
pub updated_before: Option<String>,
pub custom: Option<serde_json::Value>,
}
impl FilterParams {
pub fn new() -> Self {
Self::default()
}
pub fn with_query(mut self, query: impl Into<String>) -> Self {
self.query = Some(query.into());
self
}
pub fn with_archived(mut self, archived: bool) -> Self {
self.archived = Some(archived);
self
}
}
#[async_trait]
pub trait Repository: Send + Sync {
type Entity: Send + Sync + Debug;
type Id: Send + Sync + Debug + Clone;
async fn get(&self, id: &Self::Id) -> RepositoryResult<Self::Entity>;
async fn list(
&self,
pagination: Option<PaginationParams>,
filters: Option<FilterParams>,
) -> RepositoryResult<Vec<Self::Entity>>;
async fn create(&self, entity: &Self::Entity) -> RepositoryResult<Self::Entity>;
async fn update(&self, id: &Self::Id, entity: &Self::Entity) -> RepositoryResult<Self::Entity>;
async fn delete(&self, id: &Self::Id) -> RepositoryResult<()>;
async fn exists(&self, id: &Self::Id) -> RepositoryResult<bool> {
match self.get(id).await {
Ok(_) => Ok(true),
Err(RepositoryError::NotFound(_)) => Ok(false),
Err(e) => Err(e),
}
}
async fn count(&self, filters: Option<FilterParams>) -> RepositoryResult<u64> {
let entities = self.list(None, filters).await?;
Ok(entities.len() as u64)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginatedResponse<T> {
pub items: Vec<T>,
pub total: u64,
pub page: u32,
pub limit: u32,
pub total_pages: u32,
}
impl<T> PaginatedResponse<T> {
pub fn new(items: Vec<T>, total: u64, page: u32, limit: u32) -> Self {
let total_pages = ((total as f64) / (limit as f64)).ceil() as u32;
Self {
items,
total,
page,
limit,
total_pages,
}
}
pub fn has_next(&self) -> bool {
self.page < self.total_pages
}
pub fn has_prev(&self) -> bool {
self.page > 1
}
}