1

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.

atimin
  • 499
  • 4
  • 11
  • 2
    Have you tried macros? – jthulhu Jul 20 '23 at 19:42
  • 1
    Isn't this code the same thing as `Json(self.0).into_response()`? (using [`Json`](https://docs.rs/axum/latest/axum/struct.Json.html)) – kmdreko Jul 20 '23 at 20:03
  • Good point, not yet. I'll try and answer the question if it works. – atimin Jul 20 '23 at 20:03
  • @kmdreko `Json` doesn't work for me because I have handlers with a custom error type: `async fn handler(.....) -> Result ` – atimin Jul 20 '23 at 20:06
  • I meant in these implementations, not in your handlers (that way there's less "duplication"). Regardless you can definitely do `Result, CustomError>` too. – kmdreko Jul 20 '23 at 20:09

2 Answers2

1

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.

atimin
  • 499
  • 4
  • 11
0

I would suggest you to implement derive macro, you can find in this Stack overflow post how to do it.

How to write a custom derive macro?

Rust official link to docs to implement derive macro

Once Implemented the macro

You can do it the elegant way.


#[derive(IntoResponse)]
struct BucketInfoListAxum(BucketList);

#[derive(IntoResponse)]
struct ServerInfoAxum(ServerInfo);

PXP9
  • 358
  • 1
  • 8