use crate::entities;
use crate::errors::TodoErrors;
use crate::logging::{debug, error};
use crate::traits::Controller;
use actix_web::{web, HttpResponse};
use super::AppState;
impl From<web::Query<entities::ListRequest>> for entities::ListRequest {
fn from(query: web::Query<entities::ListRequest>) -> Self {
query.into_inner()
}
}
pub struct Api<T>
where
T: Controller<
Input = entities::TodoWrite,
Output = entities::TodoRead,
Id = entities::Id,
OptionalInput = entities::TodoUpdate,
>,
{
_controller: T,
}
impl<T> Api<T>
where
T: Controller<
Input = entities::TodoWrite,
Output = entities::TodoRead,
Id = entities::Id,
OptionalInput = entities::TodoUpdate,
>,
{
pub async fn create_todo(
state: web::Data<AppState<T>>,
todo: web::Json<T::Input>,
) -> HttpResponse {
match state.controller.create(todo.into_inner()).await {
Ok(todo) => HttpResponse::Created().json(todo),
Err(TodoErrors::DatabaseError(err)) => HttpResponse::Conflict()
.content_type("text/plain")
.body(err.to_string()),
Err(err) => {
error!(state.logger, "Failed to create todo: {:?}", err);
HttpResponse::InternalServerError().finish()
}
}
}
pub async fn create_batch(
state: web::Data<AppState<T>>,
todos: web::Json<Vec<T::Input>>,
) -> HttpResponse {
match state.controller.create_batch(todos.into_inner()).await {
Ok(ids) => HttpResponse::Created().json(ids),
Err(TodoErrors::BatchTooLarge { max_size }) => HttpResponse::PayloadTooLarge()
.content_type("text/plain")
.body(format!("Batch too large, max batch size is {}", max_size)),
Err(TodoErrors::DatabaseError(err)) => HttpResponse::Conflict()
.content_type("text/plain")
.body(err.to_string()),
Err(err) => {
error!(state.logger, "Failed to create todo: {:?}", err);
HttpResponse::InternalServerError().finish()
}
}
}
pub async fn delete_todo(state: web::Data<AppState<T>>, id: web::Path<T::Id>) -> HttpResponse {
match state.controller.delete(id.into_inner()).await {
Ok(_) => HttpResponse::NoContent().finish(),
Err(TodoErrors::TodoNotFound) => HttpResponse::NotFound().finish(),
Err(err) => {
error!(state.logger, "Failed to delete todo: {:?}", err);
HttpResponse::InternalServerError().finish()
}
}
}
pub async fn get_todo(state: web::Data<AppState<T>>, id: web::Path<T::Id>) -> HttpResponse {
match state.controller.get(id.into_inner()).await {
Ok(todo) => HttpResponse::Ok().json(todo),
Err(TodoErrors::TodoNotFound) => HttpResponse::NotFound()
.content_type("text/plain")
.body("TODO not found"),
Err(err) => {
error!(state.logger, "Failed to get todo: {:?}", err);
HttpResponse::InternalServerError().finish()
}
}
}
pub async fn list_todos(
state: web::Data<AppState<T>>,
pagination: web::Query<entities::ListRequest>,
) -> HttpResponse {
debug!(state.logger, "Listing todos with request: {:?}", pagination);
match state.controller.list(pagination.into()).await {
Ok(todos) => HttpResponse::Ok().json(todos),
Err(err) => {
error!(state.logger, "Failed to list todos: {:?}", err);
HttpResponse::InternalServerError().finish()
}
}
}
pub async fn update_todo(
state: web::Data<AppState<T>>,
id: web::Path<T::Id>,
todo: web::Json<T::OptionalInput>,
) -> HttpResponse {
match state
.controller
.update(id.into_inner(), todo.into_inner())
.await
{
Ok(_) => HttpResponse::Accepted()
.content_type("text/plain")
.body("TODO updated"),
Err(TodoErrors::TodoNotFound) => HttpResponse::NotFound().finish(),
Err(TodoErrors::TitleAlreadyExists) => HttpResponse::Conflict()
.content_type("text/plain")
.body("TODO with the same title already exists"),
Err(err) => {
error!(state.logger, "Failed to update todo: {:?}", err);
HttpResponse::InternalServerError().finish()
}
}
}
}
#[allow(dead_code)]
#[utoipa::path(
post,
path = "/todos",
operation_id = "Create a new TODO",
context_path = "/api/v1",
request_body = TodoWrite,
responses(
(status = 201, content_type = "application/json", example = json!({"id": 1, "title": "Todo title", "description": "Todo description", "done": false}), body = TodoRead),
(status = 409, description = "a TODO with the same title exists", content_type = "text/plain", example = json!("Invalid input"), body = String),
(status = 500, description = "Internal server error", content_type = "text/plain", example = json!("Internal server error"), body = String),
),
tag = "todo",
)]
fn create_todo() {}
#[allow(dead_code)]
#[utoipa::path(
put,
path = "/todos",
operation_id = "Create a batch of TODOs",
context_path = "/api/v1",
request_body(content = Vec<TodoWrite>, example = json!([{"title": "Call Jack", "done": false}, {"title": "Buy milk", "done": false}, {"title": "Go to gym", "done": false}])),
responses(
(status = 201, content_type = "application/json", example = json!([{"id": 1, "title": "Call Jack", "done": false}, {"id": 2, "title": "Buy milk", "done": false}, {"id": 1, "title": "Go to gym", "done": false}]), body = Vec<TodoRead>),
(status = 409, description = "a TODO with the same title exists", content_type = "text/plain", example = json!("Todo already exists"), body = String),
(status = 500, description = "Internal server error", content_type = "text/plain", example = json!("Internal server error"), body = String),
),
)]
pub fn create_batch() {}
#[allow(dead_code)]
#[utoipa::path(
delete,
path = "/todos/{id}",
operation_id = "Delete a TODO by id",
context_path = "/api/v1",
params(
("id" = u32, description = "Todo identifier", example = json!(1), nullable = false, minimum = 1),
),
responses(
(status = 204, description = "TODO deleted", content_type = "text/plain", example = json!("TODO deleted"), body = String),
(status = 404, description = "TODO not found", content_type = "text/plain", example = json!("TODO not found"), body = String),
(status = 500, description = "Internal server error", content_type = "text/plain", example = json!("Internal server error"), body = String),
),
tag = "todo",
)]
pub fn delete_todo() {}
#[allow(dead_code)]
#[utoipa::path(
get,
path = "/todos/{id}",
operation_id = "Get a TODO by id",
context_path = "/api/v1",
params(
("id" = u32, description = "Todo identifier", example = json!(1), nullable = false, minimum = 1),
),
responses(
(status = 200, content_type = "application/json", example = json!({"id": 1, "title": "Todo title", "description": "Todo description", "done": false}), body = TodoRead),
(status = 404, description = "TODO not found", content_type = "text/plain", example = json!("TODO not found"), body = String),
),
tag = "todo",
)]
pub fn get_todo() {}
#[allow(dead_code)]
#[utoipa::path(
get,
path = "/todos",
operation_id = "List TODOs",
context_path = "/api/v1",
params(
("offset" = Option<u32>, Query, description = "Row offset", nullable = true, minimum = 0, example = 0),
("limit" = Option<u32>, Query, description = "Number of items per page", nullable = true, minimum = 1, maximum = 1000, example = 10),
),
responses(
(status = 200, content_type = "application/json", example = json!([{"id": 1, "title": "Todo title", "description": "Todo description", "done": false}]), body = Vec<TodoRead>),
(status = 500, description = "Internal server error", content_type = "text/plain", example = json!("Internal server error"), body = String),
),
tag = "todo",
)]
pub fn list_todos() {}
#[allow(dead_code)]
#[utoipa::path(
patch,
path = "/todos/{id}",
operation_id = "Update a TODO by id",
request_body = TodoUpdate,
context_path = "/api/v1",
params(
("id" = u32, description = "Todo identifier", example = json!(1), nullable = false, minimum = 1),
),
responses(
(status = 202, description = "TODO updated", content_type = "text/plain", example = json!("TODO updated"), body = String),
(status = 404, description = "TODO not found", content_type = "text/plain", example = json!("TODO not found"), body = String),
(status = 500, description = "Internal server error", content_type = "text/plain", example = json!("Internal server error"), body = String),
),
tag = "todo",
)]
pub fn update_todo() {}