BLOG-103 Add API documentation with Utoipa #106
@ -10,3 +10,4 @@ chrono.workspace = true
|
|||||||
log.workspace = true
|
log.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
sqlx.workspace = true
|
sqlx.workspace = true
|
||||||
|
utoipa.workspace = true
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::domain::entity::color::Color;
|
use crate::domain::entity::color::Color;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct ColorResponseDto {
|
pub struct ColorResponseDto {
|
||||||
pub red: u8,
|
pub red: u8,
|
||||||
pub green: u8,
|
pub green: u8,
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
adapter::delivery::color_response_dto::ColorResponseDto, domain::entity::label::Label,
|
adapter::delivery::color_response_dto::ColorResponseDto, domain::entity::label::Label,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct LabelResponseDto {
|
pub struct LabelResponseDto {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -2,12 +2,15 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::application::{
|
use crate::{
|
||||||
|
adapter::delivery::post_info_query_dto::PostQueryDto,
|
||||||
|
application::{
|
||||||
error::post_error::PostError,
|
error::post_error::PostError,
|
||||||
use_case::{
|
use_case::{
|
||||||
get_all_post_info_use_case::GetAllPostInfoUseCase,
|
get_all_post_info_use_case::GetAllPostInfoUseCase,
|
||||||
get_full_post_use_case::GetFullPostUseCase,
|
get_full_post_use_case::GetFullPostUseCase,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{post_info_response_dto::PostInfoResponseDto, post_response_dto::PostResponseDto};
|
use super::{post_info_response_dto::PostInfoResponseDto, post_response_dto::PostResponseDto};
|
||||||
@ -16,10 +19,10 @@ use super::{post_info_response_dto::PostInfoResponseDto, post_response_dto::Post
|
|||||||
pub trait PostController: Send + Sync {
|
pub trait PostController: Send + Sync {
|
||||||
async fn get_all_post_info(
|
async fn get_all_post_info(
|
||||||
&self,
|
&self,
|
||||||
is_published_only: bool,
|
query: PostQueryDto,
|
||||||
) -> Result<Vec<PostInfoResponseDto>, PostError>;
|
) -> Result<Vec<PostInfoResponseDto>, PostError>;
|
||||||
|
|
||||||
async fn get_full_post(&self, id: i32) -> Result<PostResponseDto, PostError>;
|
async fn get_post_by_id(&self, id: i32) -> Result<PostResponseDto, PostError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PostControllerImpl {
|
pub struct PostControllerImpl {
|
||||||
@ -43,9 +46,12 @@ impl PostControllerImpl {
|
|||||||
impl PostController for PostControllerImpl {
|
impl PostController for PostControllerImpl {
|
||||||
async fn get_all_post_info(
|
async fn get_all_post_info(
|
||||||
&self,
|
&self,
|
||||||
is_published_only: bool,
|
query: PostQueryDto,
|
||||||
) -> Result<Vec<PostInfoResponseDto>, PostError> {
|
) -> Result<Vec<PostInfoResponseDto>, PostError> {
|
||||||
let result = self.get_all_post_info_use_case.execute(is_published_only).await;
|
let result = self
|
||||||
|
.get_all_post_info_use_case
|
||||||
|
.execute(query.is_published_only.unwrap_or(true))
|
||||||
|
.await;
|
||||||
|
|
||||||
result.map(|post_info_list| {
|
result.map(|post_info_list| {
|
||||||
let post_info_response_dto_list: Vec<PostInfoResponseDto> = post_info_list
|
let post_info_response_dto_list: Vec<PostInfoResponseDto> = post_info_list
|
||||||
@ -57,7 +63,7 @@ impl PostController for PostControllerImpl {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_full_post(&self, id: i32) -> Result<PostResponseDto, PostError> {
|
async fn get_post_by_id(&self, id: i32) -> Result<PostResponseDto, PostError> {
|
||||||
let result = self.get_full_post_use_case.execute(id).await;
|
let result = self.get_full_post_use_case.execute(id).await;
|
||||||
|
|
||||||
result.map(PostResponseDto::from)
|
result.map(PostResponseDto::from)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use utoipa::IntoParams;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, IntoParams)]
|
||||||
pub struct PostQueryDto {
|
pub struct PostQueryDto {
|
||||||
pub is_published_only: Option<bool>,
|
pub is_published_only: Option<bool>,
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::domain::entity::post_info::PostInfo;
|
use crate::domain::entity::post_info::PostInfo;
|
||||||
|
|
||||||
use super::label_response_dto::LabelResponseDto;
|
use super::label_response_dto::LabelResponseDto;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct PostInfoResponseDto {
|
pub struct PostInfoResponseDto {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::domain::entity::post::Post;
|
use crate::domain::entity::post::Post;
|
||||||
|
|
||||||
use super::post_info_response_dto::PostInfoResponseDto;
|
use super::post_info_response_dto::PostInfoResponseDto;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct PostResponseDto {
|
pub struct PostResponseDto {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub info: PostInfoResponseDto,
|
pub info: PostInfoResponseDto,
|
||||||
|
@ -1 +1,5 @@
|
|||||||
|
pub mod post_api_doc;
|
||||||
pub mod post_web_routes;
|
pub mod post_web_routes;
|
||||||
|
|
||||||
|
mod get_all_post_info_handler;
|
||||||
|
mod get_post_by_id_handler;
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
|
|
||||||
|
use crate::adapter::delivery::{
|
||||||
|
post_controller::PostController, post_info_query_dto::PostQueryDto,
|
||||||
|
post_info_response_dto::PostInfoResponseDto,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/post/all",
|
||||||
|
tag = "post",
|
||||||
|
summary = "Get all post information",
|
||||||
|
params(
|
||||||
|
PostQueryDto
|
||||||
|
),
|
||||||
|
responses (
|
||||||
|
(status = 200, body = [PostInfoResponseDto])
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn get_all_post_info_handler(
|
||||||
|
post_controller: web::Data<dyn PostController>,
|
||||||
|
query: web::Query<PostQueryDto>,
|
||||||
|
) -> impl Responder {
|
||||||
|
let result = post_controller.get_all_post_info(query.into_inner()).await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(post_info_list) => HttpResponse::Ok().json(post_info_list),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
adapter::delivery::{post_controller::PostController, post_response_dto::PostResponseDto},
|
||||||
|
application::error::post_error::PostError,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/post/{id}",
|
||||||
|
tag = "post",
|
||||||
|
summary = "Get post by ID",
|
||||||
|
responses (
|
||||||
|
(status = 200, body = PostResponseDto),
|
||||||
|
(status = 404, description = "Post not found")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn get_post_by_id_handler(
|
||||||
|
post_controller: web::Data<dyn PostController>,
|
||||||
|
path: web::Path<i32>,
|
||||||
|
) -> impl Responder {
|
||||||
|
let id = path.into_inner();
|
||||||
|
let result = post_controller.get_post_by_id(id).await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(post) => HttpResponse::Ok().json(post),
|
||||||
|
Err(e) => {
|
||||||
|
if e == PostError::NotFound {
|
||||||
|
HttpResponse::NotFound().finish()
|
||||||
|
} else {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
backend/feature/post/src/framework/web/post_api_doc.rs
Normal file
16
backend/feature/post/src/framework/web/post_api_doc.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use utoipa::{OpenApi, openapi};
|
||||||
|
use crate::framework::web::{
|
||||||
|
get_all_post_info_handler,
|
||||||
|
get_post_by_id_handler
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(paths(
|
||||||
|
get_all_post_info_handler::get_all_post_info_handler,
|
||||||
|
get_post_by_id_handler::get_post_by_id_handler
|
||||||
|
))]
|
||||||
|
struct PostApiDoc;
|
||||||
|
|
||||||
|
pub fn openapi() -> openapi::OpenApi {
|
||||||
|
PostApiDoc::openapi()
|
||||||
|
}
|
@ -1,50 +1,14 @@
|
|||||||
use actix_web::{HttpResponse, Responder, web};
|
use actix_web::web;
|
||||||
|
|
||||||
use crate::{
|
use crate::framework::web::{
|
||||||
adapter::delivery::{post_controller::PostController, post_info_query_dto::PostQueryDto},
|
get_all_post_info_handler::get_all_post_info_handler,
|
||||||
application::error::post_error::PostError,
|
get_post_by_id_handler::get_post_by_id_handler,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn configure_post_routes(cfg: &mut web::ServiceConfig) {
|
pub fn configure_post_routes(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(
|
cfg.service(
|
||||||
web::scope("/post")
|
web::scope("/post")
|
||||||
.route("/all", web::get().to(get_all_post_info_handler))
|
.route("/all", web::get().to(get_all_post_info_handler))
|
||||||
.route("/{id}", web::get().to(get_full_post_handler)),
|
.route("/{id}", web::get().to(get_post_by_id_handler)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_post_info_handler(
|
|
||||||
post_controller: web::Data<dyn PostController>,
|
|
||||||
query: web::Query<PostQueryDto>,
|
|
||||||
) -> impl Responder {
|
|
||||||
let is_published_only = query.is_published_only.unwrap_or_else(|| true);
|
|
||||||
let result = post_controller.get_all_post_info(is_published_only).await;
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(post_info_list) => HttpResponse::Ok().json(post_info_list),
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("{e:?}");
|
|
||||||
HttpResponse::InternalServerError().finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_full_post_handler(
|
|
||||||
post_controller: web::Data<dyn PostController>,
|
|
||||||
path: web::Path<i32>,
|
|
||||||
) -> impl Responder {
|
|
||||||
let id = path.into_inner();
|
|
||||||
let result = post_controller.get_full_post(id).await;
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(post) => HttpResponse::Ok().json(post),
|
|
||||||
Err(e) => {
|
|
||||||
if e == PostError::NotFound {
|
|
||||||
HttpResponse::NotFound().finish()
|
|
||||||
} else {
|
|
||||||
log::error!("{e:?}");
|
|
||||||
HttpResponse::InternalServerError().finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
|
use post::framework::web::post_api_doc;
|
||||||
use utoipa::OpenApi;
|
use utoipa::OpenApi;
|
||||||
use utoipa_redoc::{Redoc, Servable};
|
use utoipa_redoc::{Redoc, Servable};
|
||||||
|
|
||||||
#[derive(OpenApi)]
|
#[derive(OpenApi)]
|
||||||
#[openapi(info(
|
#[openapi(
|
||||||
|
info(
|
||||||
title = "SquidSpirit API",
|
title = "SquidSpirit API",
|
||||||
version = env!("CARGO_PKG_VERSION")
|
version = env!("CARGO_PKG_VERSION")
|
||||||
))]
|
)
|
||||||
|
)]
|
||||||
pub struct ApiDoc;
|
pub struct ApiDoc;
|
||||||
|
|
||||||
pub fn configure_api_doc_routes(cfg: &mut web::ServiceConfig) {
|
pub fn configure_api_doc_routes(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(Redoc::with_url("/redoc", ApiDoc::openapi()));
|
let openapi = ApiDoc::openapi().merge_from(post_api_doc::openapi());
|
||||||
|
|
||||||
|
cfg.service(Redoc::with_url("/redoc", openapi));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user