metabase_api_rs/repository/
traits.rs

1//! Common repository traits and types
2//!
3//! This module defines the base traits and common types used across all repositories.
4
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use std::fmt::Debug;
8use thiserror::Error;
9
10/// Repository-specific error type
11#[derive(Debug, Error)]
12pub enum RepositoryError {
13    /// Entity not found
14    #[error("Entity not found: {0}")]
15    NotFound(String),
16
17    /// Invalid parameters
18    #[error("Invalid parameters: {0}")]
19    InvalidParams(String),
20
21    /// Network error
22    #[error("Network error: {0}")]
23    Network(String),
24
25    /// Authentication error
26    #[error("Authentication error: {0}")]
27    Authentication(String),
28
29    /// Serialization/Deserialization error
30    #[error("Serialization error: {0}")]
31    Serialization(String),
32
33    /// Other error
34    #[error("Repository error: {0}")]
35    Other(String),
36}
37
38/// Repository result type
39pub type RepositoryResult<T> = Result<T, RepositoryError>;
40
41/// Convert from core Error to RepositoryError
42impl From<crate::core::error::Error> for RepositoryError {
43    fn from(err: crate::core::error::Error) -> Self {
44        use crate::core::error::Error;
45        match err {
46            Error::NotFound(msg) => RepositoryError::NotFound(msg),
47            Error::Network(msg) => RepositoryError::Network(msg),
48            Error::Authentication(msg) => RepositoryError::Authentication(msg),
49            Error::Validation(msg) => RepositoryError::InvalidParams(msg),
50            Error::Http { status: 404, .. } => {
51                RepositoryError::NotFound("Resource not found".to_string())
52            }
53            Error::Http { message, .. } => RepositoryError::Network(message),
54            other => RepositoryError::Other(other.to_string()),
55        }
56    }
57}
58
59/// Pagination parameters
60#[derive(Debug, Clone, Default, Serialize, Deserialize)]
61pub struct PaginationParams {
62    /// Page number (1-based)
63    pub page: Option<u32>,
64    /// Items per page
65    pub limit: Option<u32>,
66    /// Offset (alternative to page)
67    pub offset: Option<u32>,
68}
69
70impl PaginationParams {
71    /// Create new pagination params
72    pub fn new() -> Self {
73        Self::default()
74    }
75
76    /// Set page number
77    pub fn with_page(mut self, page: u32) -> Self {
78        self.page = Some(page);
79        self
80    }
81
82    /// Set limit
83    pub fn with_limit(mut self, limit: u32) -> Self {
84        self.limit = Some(limit);
85        self
86    }
87
88    /// Set offset
89    pub fn with_offset(mut self, offset: u32) -> Self {
90        self.offset = Some(offset);
91        self
92    }
93
94    /// Convert to query parameters
95    pub fn to_query_params(&self) -> Vec<(String, String)> {
96        let mut params = vec![];
97        if let Some(page) = self.page {
98            params.push(("page".to_string(), page.to_string()));
99        }
100        if let Some(limit) = self.limit {
101            params.push(("limit".to_string(), limit.to_string()));
102        }
103        if let Some(offset) = self.offset {
104            params.push(("offset".to_string(), offset.to_string()));
105        }
106        params
107    }
108}
109
110/// Sort order
111#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
112#[serde(rename_all = "lowercase")]
113pub enum SortOrder {
114    /// Ascending order
115    Asc,
116    /// Descending order
117    Desc,
118}
119
120impl Default for SortOrder {
121    fn default() -> Self {
122        Self::Asc
123    }
124}
125
126/// Generic filter parameters
127#[derive(Debug, Clone, Default, Serialize, Deserialize)]
128pub struct FilterParams {
129    /// Search query
130    pub query: Option<String>,
131    /// Filter by active/archived status
132    pub archived: Option<bool>,
133    /// Filter by creation date (ISO 8601)
134    pub created_after: Option<String>,
135    /// Filter by creation date (ISO 8601)
136    pub created_before: Option<String>,
137    /// Filter by update date (ISO 8601)
138    pub updated_after: Option<String>,
139    /// Filter by update date (ISO 8601)
140    pub updated_before: Option<String>,
141    /// Additional custom filters
142    pub custom: Option<serde_json::Value>,
143}
144
145impl FilterParams {
146    /// Create new filter params
147    pub fn new() -> Self {
148        Self::default()
149    }
150
151    /// Set search query
152    pub fn with_query(mut self, query: impl Into<String>) -> Self {
153        self.query = Some(query.into());
154        self
155    }
156
157    /// Set archived filter
158    pub fn with_archived(mut self, archived: bool) -> Self {
159        self.archived = Some(archived);
160        self
161    }
162}
163
164/// Base repository trait
165///
166/// This trait defines common operations that all repositories should support.
167#[async_trait]
168pub trait Repository: Send + Sync {
169    /// The entity type this repository manages
170    type Entity: Send + Sync + Debug;
171
172    /// The ID type for entities
173    type Id: Send + Sync + Debug + Clone;
174
175    /// Get an entity by ID
176    async fn get(&self, id: &Self::Id) -> RepositoryResult<Self::Entity>;
177
178    /// List entities with optional pagination and filters
179    async fn list(
180        &self,
181        pagination: Option<PaginationParams>,
182        filters: Option<FilterParams>,
183    ) -> RepositoryResult<Vec<Self::Entity>>;
184
185    /// Create a new entity
186    async fn create(&self, entity: &Self::Entity) -> RepositoryResult<Self::Entity>;
187
188    /// Update an existing entity
189    async fn update(&self, id: &Self::Id, entity: &Self::Entity) -> RepositoryResult<Self::Entity>;
190
191    /// Delete an entity
192    async fn delete(&self, id: &Self::Id) -> RepositoryResult<()>;
193
194    /// Check if an entity exists
195    async fn exists(&self, id: &Self::Id) -> RepositoryResult<bool> {
196        match self.get(id).await {
197            Ok(_) => Ok(true),
198            Err(RepositoryError::NotFound(_)) => Ok(false),
199            Err(e) => Err(e),
200        }
201    }
202
203    /// Count entities matching filters
204    async fn count(&self, filters: Option<FilterParams>) -> RepositoryResult<u64> {
205        let entities = self.list(None, filters).await?;
206        Ok(entities.len() as u64)
207    }
208}
209
210/// Paginated response
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct PaginatedResponse<T> {
213    /// The items in this page
214    pub items: Vec<T>,
215    /// Total number of items
216    pub total: u64,
217    /// Current page (1-based)
218    pub page: u32,
219    /// Items per page
220    pub limit: u32,
221    /// Total number of pages
222    pub total_pages: u32,
223}
224
225impl<T> PaginatedResponse<T> {
226    /// Create a new paginated response
227    pub fn new(items: Vec<T>, total: u64, page: u32, limit: u32) -> Self {
228        let total_pages = ((total as f64) / (limit as f64)).ceil() as u32;
229        Self {
230            items,
231            total,
232            page,
233            limit,
234            total_pages,
235        }
236    }
237
238    /// Check if there's a next page
239    pub fn has_next(&self) -> bool {
240        self.page < self.total_pages
241    }
242
243    /// Check if there's a previous page
244    pub fn has_prev(&self) -> bool {
245        self.page > 1
246    }
247}