I need to implement the axum::IntoResponse trait for serde structures because they come from another crate:
struct BucketInfoListAxum(BucketList);
impl IntoResponse for BucketInfoListAxum {
fn into_response(self) -> Response {
let mut headers = HeaderMap::new();
headers.typed_insert(headers::ContentType::json());
(
StatusCode::OK,
headers,
serde_json::to_string(&self.0).unwrap(),
)
.into_response()
}
}
struct ServerInfoAxum(ServerInfo);
impl IntoResponse for ServerInfoAxum {
fn into_response(self) -> Response {
let mut headers = HeaderMap::new();
headers.typed_insert(headers::ContentType::json());
(
StatusCode::OK,
headers,
serde_json::to_string(&self.0).unwrap(),
)
.into_response()
}
}
All the implementations are exactly the same, and I'm wondering if there is a more elegant way in Rust to avoid the duplication rather than extracting it into a function.
As was suggested, I've solved the problem with macros. I've created a separated crate with the following code in src/lib.rs
:
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(IntoResponse)]
pub fn into_response_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_into_response(&ast)
}
fn impl_into_response(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl IntoResponse for #name {
fn into_response(self) -> Response {
let mut headers = HeaderMap::new();
headers.typed_insert(headers::ContentType::json());
(
StatusCode::OK,
headers,
serde_json::to_string(&self.0).unwrap(),
)
.into_response()
}
}
};
gen.into()
}
Now I can use it in my code:
#[derive(IntoResponse)]
struct BucketInfoListAxum(BucketList);
A little disadvantage of the approach is that I have to create a crate for the macros and use workspaces.