use std::future::Future;
use super::error::ApiError;
use super::query::ListQuery;
use super::response::{ItemResponse, ListResponse};
pub trait CollectionHandler<Id, Entity, CreateDto, UpdateDto>: Send + Sync {
fn list(
&self,
query: ListQuery,
) -> impl Future<Output = Result<ListResponse<Entity>, ApiError>> + Send;
fn get(&self, id: &Id) -> impl Future<Output = Result<ItemResponse<Entity>, ApiError>> + Send;
fn create(
&self,
dto: CreateDto,
) -> impl Future<Output = Result<ItemResponse<Entity>, ApiError>> + Send;
fn update(
&self,
id: &Id,
dto: UpdateDto,
) -> impl Future<Output = Result<ItemResponse<Entity>, ApiError>> + Send;
fn delete(&self, id: &Id) -> impl Future<Output = Result<(), ApiError>> + Send;
}
pub trait SoftDeleteHandler<Id, Entity, CreateDto, UpdateDto>:
CollectionHandler<Id, Entity, CreateDto, UpdateDto>
{
fn soft_delete(&self, id: &Id) -> impl Future<Output = Result<(), ApiError>> + Send;
fn restore(
&self,
id: &Id,
) -> impl Future<Output = Result<ItemResponse<Entity>, ApiError>> + Send;
fn list_with_deleted(
&self,
query: ListQuery,
) -> impl Future<Output = Result<ListResponse<Entity>, ApiError>> + Send;
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone)]
struct MockId(String);
#[derive(Debug, Clone)]
struct MockEntity {
id: String,
name: String,
}
#[derive(Debug, Clone)]
struct MockCreate {
name: String,
}
#[derive(Debug, Clone)]
struct MockUpdate {
name: Option<String>,
}
struct MockHandler;
impl CollectionHandler<MockId, MockEntity, MockCreate, MockUpdate> for MockHandler {
async fn list(&self, query: ListQuery) -> Result<ListResponse<MockEntity>, ApiError> {
let data = vec![MockEntity {
id: "1".to_string(),
name: "Test".to_string(),
}];
let pagination = super::super::response::PaginationMeta::new(
query.page_number(),
query.items_per_page(),
1,
);
Ok(ListResponse::new(data, pagination))
}
async fn get(&self, id: &MockId) -> Result<ItemResponse<MockEntity>, ApiError> {
Ok(ItemResponse::new(MockEntity {
id: id.0.clone(),
name: "Test".to_string(),
}))
}
async fn create(&self, dto: MockCreate) -> Result<ItemResponse<MockEntity>, ApiError> {
Ok(ItemResponse::new(MockEntity {
id: "new".to_string(),
name: dto.name,
}))
}
async fn update(
&self,
id: &MockId,
dto: MockUpdate,
) -> Result<ItemResponse<MockEntity>, ApiError> {
Ok(ItemResponse::new(MockEntity {
id: id.0.clone(),
name: dto.name.unwrap_or_else(|| "unchanged".to_string()),
}))
}
async fn delete(&self, _id: &MockId) -> Result<(), ApiError> {
Ok(())
}
}
impl SoftDeleteHandler<MockId, MockEntity, MockCreate, MockUpdate> for MockHandler {
async fn soft_delete(&self, _id: &MockId) -> Result<(), ApiError> {
Ok(())
}
async fn restore(&self, id: &MockId) -> Result<ItemResponse<MockEntity>, ApiError> {
Ok(ItemResponse::new(MockEntity {
id: id.0.clone(),
name: "Restored".to_string(),
}))
}
async fn list_with_deleted(
&self,
query: ListQuery,
) -> Result<ListResponse<MockEntity>, ApiError> {
self.list(query).await
}
}
#[tokio::test]
async fn test_mock_handler_list() {
let handler = MockHandler;
let query = ListQuery::new().with_page(1).with_per_page(20);
let result = handler.list(query).await;
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(response.data.len(), 1);
assert_eq!(response.pagination.total, 1);
}
#[tokio::test]
async fn test_mock_handler_get() {
let handler = MockHandler;
let id = MockId("123".to_string());
let result = handler.get(&id).await;
assert!(result.is_ok());
assert_eq!(result.unwrap().data.id, "123");
}
#[tokio::test]
async fn test_mock_handler_create() {
let handler = MockHandler;
let dto = MockCreate {
name: "New Entity".to_string(),
};
let result = handler.create(dto).await;
assert!(result.is_ok());
assert_eq!(result.unwrap().data.name, "New Entity");
}
#[tokio::test]
async fn test_mock_handler_update() {
let handler = MockHandler;
let id = MockId("123".to_string());
let dto = MockUpdate {
name: Some("Updated".to_string()),
};
let result = handler.update(&id, dto).await;
assert!(result.is_ok());
assert_eq!(result.unwrap().data.name, "Updated");
}
#[tokio::test]
async fn test_mock_handler_delete() {
let handler = MockHandler;
let id = MockId("123".to_string());
let result = handler.delete(&id).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_mock_soft_delete_handler() {
let handler = MockHandler;
let id = MockId("123".to_string());
let result = handler.soft_delete(&id).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_mock_restore_handler() {
let handler = MockHandler;
let id = MockId("123".to_string());
let result = handler.restore(&id).await;
assert!(result.is_ok());
assert_eq!(result.unwrap().data.name, "Restored");
}
#[tokio::test]
async fn test_mock_list_with_deleted() {
let handler = MockHandler;
let query = ListQuery::default();
let result = handler.list_with_deleted(query).await;
assert!(result.is_ok());
}
}