0

I try to return json body with errors in Rust's Rocket.

pub fn error_status(error: Error) -> Status {
    match error {
        Error::NotFound => Status::NotFound,
        _ => Status::InternalServerError
    }
}

#[get("/user/<id>")]
pub fn get_user(id: i32, connection: DbConn) -> Result<Json<User>, Status> {

    UserService::show_user(id, &connection)
        .map(|u| Json(u))
        .map_err(|err| core::error_status(err))
}

When error occours it returns Status::NotFound but with html body, I need json body. I tried with Return JSON with an HTTP status other than 200 in Rocket but without success. In that topic author uses JsonValue I need Json(T) for dynamic json body. I couldn't create Response with success :/ I could use errorCatcher but I don't want to use it in all responses, I need json only in api respnses.

How to return Errors with json body? Thank you in advance.

nicram
  • 353
  • 3
  • 7
  • 24
  • I tried the same [solution](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b091084ba7c86becc1ead6ec517cc9e1) you linked, but replacing the `Json` with `Json` and it just works. – rodrigo Mar 08 '21 at 16:16
  • @rodrigo `Json` is explicit json data from Thing struct, I need generic type `Json`. – nicram Mar 08 '21 at 17:31
  • 1
    Something like [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e8b4d63ff102ff46b1c8678e60a42ab1)? `ApiResponse` is generic, but any single call returns a concrete type? – rodrigo Mar 08 '21 at 17:34
  • Yes, something like that but with generic I had problems with building response. I'll try later – nicram Mar 08 '21 at 17:41
  • It works, thanks. The error I did was that I omited `serde::Serialize` to generic type. – nicram Mar 08 '21 at 18:30

2 Answers2

3

This is how to do it

#![feature(proc_macro_hygiene)] 
#![feature(decl_macro)]

#[macro_use]
extern crate rocket;
#[macro_use]
extern crate rocket_contrib;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

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

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Thing {
    pub name: String,
}

#[derive(Debug)]
struct ApiResponse<T> {
    json: Json<T>,
    status: Status,
}

impl<'r, T: serde::Serialize> Responder<'r> for ApiResponse<T> {
    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()
    }
}

#[post("/create/thing", format = "application/json", data = "<thing>")]
fn put(thing: Json<Thing>) -> ApiResponse<Thing> {
    match thing.name.len() {
        0...3 => ApiResponse {
            json: thing,
            status: Status::UnprocessableEntity,
        },
        _ => ApiResponse {
            json: thing,
            status: Status::Ok,
        },
    }
}
fn main() {
    rocket::ignite().mount("/", routes![put]).launch();
} 

It even works with 0.5.0-rc.1

source: comments section of this question

0

This can be done from the client side by providing the appropriate Accept header in the request:

Accept: application/json

To receive the error as html, simply omit the Accept header.

See Built-In Catcher

ant1fact
  • 31
  • 1
  • 4