1

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()
    }
}
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
SRugina
  • 129
  • 9
  • or, you can implement a custom error type and `From` for it. From there, you are quite literally free to do anything. As the error type for diesel is contained in the crate, there is no way you can add serialize to it short of modifying the crate itself. – Sébastien Renauld Sep 18 '19 at 00:34

2 Answers2

3

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.

harmic
  • 28,606
  • 5
  • 67
  • 91
  • This is the correct answer for the question I asked, so I've marked it as correct - though in the code I have I would have to somehow get diesel's .execute() to return the proxy struct (correct me if I'm wrong). so @Alistair 's answer is what I ended up using in my code – SRugina Sep 20 '19 at 07:16
1

The short answer: yes you can. Personally I've always found it a bit difficult.

As a compromise, you could pull out just the parts of the error that are relevant to you, or even do:

ApiResponse {
    json: json!({"success": false, "error": error.to_string() }),
    status: Status::UnprocessableEntity,
}

If you're content with just a textual representation of the error.

Alistair
  • 8,066
  • 14
  • 39
  • 43