feat: enhance error handling and API documentation for image and label features
This commit is contained in:
parent
6213ab7a95
commit
3b16b165bb
@ -7,6 +7,12 @@ pub enum ImageError {
|
|||||||
Unexpected(anyhow::Error),
|
Unexpected(anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<String> for ImageError {
|
||||||
|
fn into(self) -> String {
|
||||||
|
format!("{}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for ImageError {
|
impl Display for ImageError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
@ -15,7 +15,7 @@ use crate::{
|
|||||||
summary = "Get image by ID",
|
summary = "Get image by ID",
|
||||||
responses (
|
responses (
|
||||||
(status = 200, body = inline(ResponseBodySchema), content_type = "image/*"),
|
(status = 200, body = inline(ResponseBodySchema), content_type = "image/*"),
|
||||||
(status = 404, description = "Image not found")
|
(status = 404, description = ImageError::NotFound),
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub async fn get_image_by_id_handler(
|
pub async fn get_image_by_id_handler(
|
||||||
@ -31,12 +31,12 @@ pub async fn get_image_by_id_handler(
|
|||||||
.body(image_response.data),
|
.body(image_response.data),
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
ImageError::NotFound => HttpResponse::NotFound().finish(),
|
ImageError::NotFound => HttpResponse::NotFound().finish(),
|
||||||
ImageError::Unexpected(e) => {
|
ImageError::UnsupportedMimeType(_) => {
|
||||||
capture_anyhow(&e);
|
capture_anyhow(&anyhow!(e));
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
}
|
}
|
||||||
_ => {
|
ImageError::Unexpected(e) => {
|
||||||
capture_anyhow(&anyhow!(e));
|
capture_anyhow(&e);
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -25,7 +25,7 @@ use crate::{
|
|||||||
),
|
),
|
||||||
responses (
|
responses (
|
||||||
(status = 201, body = ImageInfoResponseDto),
|
(status = 201, body = ImageInfoResponseDto),
|
||||||
(status = 400, description = "Unsupported MIME type or file field not found"),
|
(status = 400, description = ImageError::UnsupportedMimeType("{MIME_TYPE}".to_string())),
|
||||||
),
|
),
|
||||||
security(
|
security(
|
||||||
("oauth2" = [])
|
("oauth2" = [])
|
||||||
@ -78,12 +78,12 @@ pub async fn upload_image_handler(
|
|||||||
ImageError::UnsupportedMimeType(mime_type) => {
|
ImageError::UnsupportedMimeType(mime_type) => {
|
||||||
HttpResponse::BadRequest().body(format!("Unsupported MIME type: {}", mime_type))
|
HttpResponse::BadRequest().body(format!("Unsupported MIME type: {}", mime_type))
|
||||||
}
|
}
|
||||||
ImageError::Unexpected(e) => {
|
ImageError::NotFound => {
|
||||||
capture_anyhow(&e);
|
capture_anyhow(&anyhow!(e));
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
}
|
}
|
||||||
_ => {
|
ImageError::Unexpected(e) => {
|
||||||
capture_anyhow(&anyhow!(e));
|
capture_anyhow(&e);
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -8,11 +8,17 @@ pub enum LabelError {
|
|||||||
Unexpected(anyhow::Error),
|
Unexpected(anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<String> for LabelError {
|
||||||
|
fn into(self) -> String {
|
||||||
|
format!("{}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for LabelError {
|
impl Display for LabelError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
LabelError::NotFound => write!(f, "Label not found"),
|
LabelError::NotFound => write!(f, "Label not found"),
|
||||||
LabelError::Unauthorized => write!(f, "Unauthorized access to label"),
|
LabelError::Unauthorized => write!(f, "Unauthorized access"),
|
||||||
LabelError::DuplicatedLabelName => write!(f, "Label name already exists"),
|
LabelError::DuplicatedLabelName => write!(f, "Label name already exists"),
|
||||||
LabelError::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
LabelError::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,12 @@ use crate::{
|
|||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
post,
|
post,
|
||||||
path = "/label",
|
path = "/label",
|
||||||
tag = "post",
|
tag = "label",
|
||||||
summary = "Create a new label",
|
summary = "Create a new label",
|
||||||
responses(
|
responses(
|
||||||
(status = 201, body = LabelResponseDto),
|
(status = 201, body = LabelResponseDto),
|
||||||
|
(status = 401, description = LabelError::Unauthorized),
|
||||||
|
(status = 409, description = LabelError::DuplicatedLabelName),
|
||||||
),
|
),
|
||||||
security(
|
security(
|
||||||
("oauth2" = [])
|
("oauth2" = [])
|
||||||
|
@ -10,7 +10,7 @@ use crate::{
|
|||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
path = "/label",
|
path = "/label",
|
||||||
tag = "post",
|
tag = "label",
|
||||||
summary = "Get all labels",
|
summary = "Get all labels",
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = Vec<LabelResponseDto>)
|
(status = 200, body = Vec<LabelResponseDto>)
|
||||||
|
@ -13,13 +13,13 @@ use crate::{
|
|||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
put,
|
put,
|
||||||
path = "/label/{id}",
|
path = "/label/{id}",
|
||||||
tag = "post",
|
tag = "label",
|
||||||
summary = "Update a label by ID",
|
summary = "Update a label by ID",
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = LabelResponseDto),
|
(status = 200, body = LabelResponseDto),
|
||||||
(status = 401, description = "Unauthorized"),
|
(status = 401, description = LabelError::Unauthorized),
|
||||||
(status = 404, description = "Label not found"),
|
(status = 404, description = LabelError::NotFound),
|
||||||
(status = 409, description = "Duplicated label name"),
|
(status = 409, description = LabelError::DuplicatedLabelName),
|
||||||
),
|
),
|
||||||
security(
|
security(
|
||||||
("oauth2" = [])
|
("oauth2" = [])
|
||||||
|
@ -9,14 +9,20 @@ pub enum PostError {
|
|||||||
Unexpected(anyhow::Error),
|
Unexpected(anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<String> for PostError {
|
||||||
|
fn into(self) -> String {
|
||||||
|
format!("{}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for PostError {
|
impl Display for PostError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
PostError::NotFound => write!(f, "Post not found"),
|
PostError::NotFound => write!(f, "Post not found"),
|
||||||
PostError::Unauthorized => write!(f, "Unauthorized access to post"),
|
PostError::Unauthorized => write!(f, "Unauthorized access"),
|
||||||
PostError::InvalidSemanticId => write!(
|
PostError::InvalidSemanticId => write!(
|
||||||
f,
|
f,
|
||||||
"Semantic ID shouldn't be numeric and must conform to `^[0-9a-zA-Z_\\-]+$`"
|
"Semantic ID shouldn't be numeric and must conform to `^[0-9a-zA-Z_-]+$`"
|
||||||
),
|
),
|
||||||
PostError::DuplicatedSemanticId => write!(f, "Semantic ID already exists"),
|
PostError::DuplicatedSemanticId => write!(f, "Semantic ID already exists"),
|
||||||
PostError::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
PostError::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
||||||
|
@ -20,7 +20,7 @@ impl PostInfo {
|
|||||||
return Err(PostError::InvalidSemanticId);
|
return Err(PostError::InvalidSemanticId);
|
||||||
}
|
}
|
||||||
|
|
||||||
let re = Regex::new(r"^[0-9a-zA-Z_\-]+$").unwrap();
|
let re = Regex::new(r"^[0-9a-zA-Z_-]+$").unwrap();
|
||||||
if !re.is_match(&self.semantic_id) {
|
if !re.is_match(&self.semantic_id) {
|
||||||
return Err(PostError::InvalidSemanticId);
|
return Err(PostError::InvalidSemanticId);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,9 @@ use crate::{
|
|||||||
summary = "Create a new post",
|
summary = "Create a new post",
|
||||||
responses(
|
responses(
|
||||||
(status = 201, body = PostResponseDto),
|
(status = 201, body = PostResponseDto),
|
||||||
|
(status = 400, description = PostError::InvalidSemanticId),
|
||||||
|
(status = 401, description = PostError::Unauthorized),
|
||||||
|
(status = 409, description = PostError::DuplicatedSemanticId),
|
||||||
),
|
),
|
||||||
security(
|
security(
|
||||||
("oauth2" = [])
|
("oauth2" = [])
|
||||||
|
@ -16,7 +16,8 @@ use crate::{
|
|||||||
description = "Only authenticated users can access unpublished posts. Accepts either numeric ID or semantic ID.",
|
description = "Only authenticated users can access unpublished posts. Accepts either numeric ID or semantic ID.",
|
||||||
responses (
|
responses (
|
||||||
(status = 200, body = PostResponseDto),
|
(status = 200, body = PostResponseDto),
|
||||||
(status = 404, description = "Post not found")
|
(status = 401, description = PostError::Unauthorized),
|
||||||
|
(status = 404, description = PostError::NotFound),
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub async fn get_post_by_id_handler(
|
pub async fn get_post_by_id_handler(
|
||||||
|
@ -17,7 +17,10 @@ use crate::{
|
|||||||
summary = "Update a post by ID",
|
summary = "Update a post by ID",
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = PostResponseDto),
|
(status = 200, body = PostResponseDto),
|
||||||
(status = 404, description = "Post not found"),
|
(status = 400, description = PostError::InvalidSemanticId),
|
||||||
|
(status = 401, description = PostError::Unauthorized),
|
||||||
|
(status = 404, description = PostError::NotFound),
|
||||||
|
(status = 409, description = PostError::DuplicatedSemanticId),
|
||||||
),
|
),
|
||||||
security(
|
security(
|
||||||
("oauth2" = [])
|
("oauth2" = [])
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
use auth::framework::web::auth_api_doc;
|
use auth::framework::web::auth_api_doc;
|
||||||
use image::framework::web::image_api_doc;
|
use image::framework::web::image_api_doc;
|
||||||
|
use label::framework::web::label_api_doc;
|
||||||
use post::framework::web::post_api_doc;
|
use post::framework::web::post_api_doc;
|
||||||
use utoipa::{
|
use utoipa::{
|
||||||
OpenApi,
|
OpenApi,
|
||||||
@ -40,6 +41,7 @@ pub fn configure_api_doc_routes(cfg: &mut web::ServiceConfig) {
|
|||||||
let openapi = ApiDoc::openapi()
|
let openapi = ApiDoc::openapi()
|
||||||
.merge_from(auth_api_doc::openapi())
|
.merge_from(auth_api_doc::openapi())
|
||||||
.merge_from(image_api_doc::openapi())
|
.merge_from(image_api_doc::openapi())
|
||||||
|
.merge_from(label_api_doc::openapi())
|
||||||
.merge_from(post_api_doc::openapi());
|
.merge_from(post_api_doc::openapi());
|
||||||
|
|
||||||
cfg.service(Redoc::with_url("/redoc", openapi));
|
cfg.service(Redoc::with_url("/redoc", openapi));
|
||||||
|
@ -85,6 +85,7 @@ fn create_app(
|
|||||||
.wrap(session_middleware_builder.build())
|
.wrap(session_middleware_builder.build())
|
||||||
.app_data(web::Data::from(container.auth_controller))
|
.app_data(web::Data::from(container.auth_controller))
|
||||||
.app_data(web::Data::from(container.image_controller))
|
.app_data(web::Data::from(container.image_controller))
|
||||||
|
.app_data(web::Data::from(container.label_controller))
|
||||||
.app_data(web::Data::from(container.post_controller))
|
.app_data(web::Data::from(container.post_controller))
|
||||||
.configure(configure_api_doc_routes)
|
.configure(configure_api_doc_routes)
|
||||||
.configure(configure_auth_routes)
|
.configure(configure_auth_routes)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user