Search code examples
rustserderust-dieselrust-rocket

Can one implement/derive Serialize on an enum without #[derive(Serialize)]?


I'm using rust + rocket + diesel (orm) + serde_derive to make a rest api. Currently, I'm dealing with error handling for the api if diesel fails to insert a user for whatever reason. It looks like this:

pub fn create(user: InsertableUser, connection: &MysqlConnection) -> ApiResponse {
    let result = diesel::insert_into(users::table)
        .values(&InsertableUser::hashed_user(user))
        .execute(connection);
    match result {
        Ok(_) => ApiResponse {
            json: json!({"success": true, "error": null}),
            status: Status::Ok,
        },
        Err(error) => {
            println!("Cannot create the recipe: {:?}", error);
            ApiResponse {
                json: json!({"success": false, "error": error}),
                status: Status::UnprocessableEntity,
            }
        }
    }
}

However, json: json!({"success": false, "error": error}), gives me this error:

the trait bound `diesel::result::Error: user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not satisfied

the trait `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not implemented for `diesel::result::Error`

note: required because of the requirements on the impl of `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` for `&diesel::result::Error`
note: required by `serde_json::value::to_value`rustc(E0277)
<::serde_json::macros::json_internal macros>(123, 27): the trait `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not implemented for `diesel::result::Error`

By the sounds of it, diesel::result::Error does not #[derive(Serialize)], and so cannot be serialized with the json! macro. Thus, I need some way to make the diesel::result::Error implement/derive Serialize.

Thanks in advance for any help.

BTW the ApiResponse looks like:

use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response;
use rocket::response::{Responder, Response};
use rocket_contrib::json::JsonValue;

#[derive(Debug)]
pub struct ApiResponse {
    pub json: JsonValue,
    pub status: Status,
}

impl<'r> Responder<'r> for ApiResponse {
    fn respond_to(self, req: &Request) -> response::Result<'r> {
        Response::build_from(self.json.respond_to(&req).unwrap())
            .status(self.status)
            .header(ContentType::JSON)
            .ok()
    }
}

Solution

  • Serde provides a workaround for deriving serialization implementations for external crates - see the section Derive for remote crates in their documentation.

    You have to define an enum with the same definition as the one you are trying to serialize (diesel::result::Error in your case), and then identify that as a kind of proxy for the type you are trying to serialize, like this:

    #[derive(Serialize, Deserialize)]
    #[serde(remote = "diesel::result::Error")]
    struct ErrorDef {
        // Definition in here the same as the enum diesel::result::Error
        // ...
    }
    

    Of course you would have to do the same for all of the types enclosed within the Error type as well (or at least any types that don't already implement Serialize).

    The documentation states that Serde checks the definition you provide against the one in the 'remote' crate, and throws an error if they differ, which would help keep them in sync.

    Also note that this does not result in diesel::result::Error implementing Serialize - rather you now have a stand-in type that you can use like this:

    struct JsonErrorRespone {
        pub success: bool,
        #[serde(with = "ErrorDef")]
        pub error: diesel::result::Error,
    }
    

    You would then serialize an instance of the above struct instead of your existing json! macro call.

    Alternatively the document linked above also gives some tips for manually calling the correct Serialize / Deserialize implementations.

    Disclaimer: I haven't used this facility yet, the above was gleaned from the documentation only.