Добавлена возможность создания ResponseError с описанием ошибки.

Добавлен макрос для трансформации ErrorCode в Response, а также для имплементации треита PartialStatusCode.
This commit is contained in:
2025-03-28 15:42:45 +04:00
parent 70a7480ea3
commit 30c985a3d7
12 changed files with 296 additions and 122 deletions

View File

@@ -2,10 +2,9 @@ extern crate proc_macro;
use proc_macro::TokenStream;
mod response_error_message {
use proc_macro::TokenStream;
mod shared {
use quote::{ToTokens, quote};
use syn::Attribute;
use syn::{Attribute, DeriveInput};
pub fn find_status_code(attrs: &Vec<Attribute>) -> Option<proc_macro2::TokenStream> {
attrs
@@ -31,7 +30,7 @@ mod response_error_message {
})
}
pub fn fmt(ast: &syn::DeriveInput) -> TokenStream {
pub fn get_arms(ast: &DeriveInput) -> Vec<proc_macro2::TokenStream> {
let name = &ast.ident;
let variants = if let syn::Data::Enum(data) = &ast.data {
@@ -54,21 +53,56 @@ mod response_error_message {
if status_code_arms.len() < variants.len() {
let status_code = find_status_code(&ast.attrs)
.unwrap_or_else(|| quote! { actix_web::http::StatusCode::INTERNAL_SERVER_ERROR });
.unwrap_or_else(|| quote! { ::actix_web::http::StatusCode::INTERNAL_SERVER_ERROR });
status_code_arms.push(quote! { _ => #status_code });
}
status_code_arms
}
}
mod response_error_message {
use proc_macro::TokenStream;
use quote::quote;
pub fn fmt(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let status_code_arms = super::shared::get_arms(ast);
TokenStream::from(quote! {
impl actix_web::ResponseError for #name {
fn status_code(&self) -> actix_web::http::StatusCode {
impl ::actix_web::ResponseError for #name {
fn status_code(&self) -> ::actix_web::http::StatusCode {
match self {
#(#status_code_arms)*
}
}
fn error_response(&self) -> actix_web::HttpResponse<BoxBody> {
actix_web::HttpResponse::build(self.status_code()).json(crate::utility::error::ResponseErrorMessage::new(self.clone()))
fn error_response(&self) -> ::actix_web::HttpResponse<BoxBody> {
::actix_web::HttpResponse::build(self.status_code())
.json(crate::utility::error::ResponseErrorMessage::new(self.clone()))
}
}
})
}
}
mod status_code {
use proc_macro::TokenStream;
use quote::quote;
pub fn fmt(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let status_code_arms = super::shared::get_arms(ast);
TokenStream::from(quote! {
impl crate::routes::schema::PartialStatusCode for #name {
fn status_code(&self) -> ::actix_web::http::StatusCode {
match self {
#(#status_code_arms)*
}
}
}
})
@@ -85,13 +119,13 @@ mod responder_json {
TokenStream::from(quote! {
impl ::actix_web::Responder for #name {
type Body = ::actix_web::body::EitherBody<::actix_web::body::BoxBody>;
fn respond_to(self, _: &::actix_web::HttpRequest) -> ::actix_web::HttpResponse<Self::Body> {
match ::serde_json::to_string(&self) {
Ok(body) => ::actix_web::HttpResponse::Ok()
.json(body)
.map_into_left_body(),
Err(err) => ::actix_web::HttpResponse::from_error(
::actix_web::error::JsonPayloadError::Serialize(err),
)
@@ -103,7 +137,7 @@ mod responder_json {
}
}
mod into_iresponse {
mod into_response_error {
use proc_macro::TokenStream;
use quote::quote;
@@ -111,17 +145,37 @@ mod into_iresponse {
let name = &ast.ident;
TokenStream::from(quote! {
impl<E> ::core::convert::Into<crate::routes::schema::IResponse<#name, E>> for #name
where
E: ::serde::ser::Serialize
+ ::utoipa::PartialSchema
+ ::core::clone::Clone
+ crate::routes::schema::HttpStatusCode,
{
fn into(self) -> crate::routes::schema::IResponse<#name, E> {
crate::routes::schema::IResponse(Ok(self))
impl ::core::convert::Into<crate::routes::schema::ResponseError<#name>> for #name {
fn into(self) -> crate::routes::schema::ResponseError<#name> {
crate::routes::schema::ResponseError {
code: self,
message: ::core::option::Option::None,
}
}
}
impl<T> crate::routes::schema::IntoResponseAsError<T> for #name
where
T: ::serde::ser::Serialize + ::utoipa::PartialSchema {}
})
}
pub fn fmt_named(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
TokenStream::from(quote! {
impl ::core::convert::Into<crate::routes::schema::ResponseError<#name>> for #name {
fn into(self) -> crate::routes::schema::ResponseError<#name> {
crate::routes::schema::ResponseError {
message: ::core::option::Option::Some(format!("{}", self)),
code: self,
}
}
}
impl<T> crate::routes::schema::IntoResponseAsError<T> for #name
where
T: ::serde::ser::Serialize + ::utoipa::PartialSchema {}
})
}
}
@@ -140,9 +194,23 @@ pub fn responser_json_derive(input: TokenStream) -> TokenStream {
responder_json::fmt(&ast)
}
#[proc_macro_derive(IntoIResponse)]
pub fn into_iresponse_derive(input: TokenStream) -> TokenStream {
#[proc_macro_derive(IntoResponseError)]
pub fn into_response_error_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
into_iresponse::fmt(&ast)
into_response_error::fmt(&ast)
}
#[proc_macro_derive(IntoResponseErrorNamed)]
pub fn into_response_error_named_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
into_response_error::fmt_named(&ast)
}
#[proc_macro_derive(StatusCode, attributes(status_code))]
pub fn status_code_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
status_code::fmt(&ast)
}