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.
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
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
Related
I'm new to Rust, and wanted to test it out with something simple. The code basically queries an external API and returns the response. In this case, the response is an array of objects.
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use] extern crate rocket;
extern crate serde;
extern crate serde_json;
#[derive(Deserialize, Debug)]
struct InboundAddress {
chain: String,
pub_key: String,
address: String,
halted: bool,
gas_rate: String,
}
#[get("/addresses")]
fn addresses() -> Result<Vec<InboundAddress>, reqwest::Error> {
let url = "https://midgard.thorchain.info/v2/thorchain/inbound_addresses";
let addresses: Vec<InboundAddress> = reqwest::blocking::get(url)?.json()?;
println!("First address chain is: {}", addresses[0].chain);
Ok(addresses)
}
fn main() {
rocket::ignite().mount("/", routes![addresses]).launch();
}
The error is coming from what I'm trying to return, Result<Vec<InboundAddress>, reqwest::Error>, saying "the trait rocket::response::Responder<'_> is not implemented for std::result::Result<std::vec::Vec<InboundAddress>, reqwest::Error>"
The json is parsing correctly, logging out details from some of the addresses work. How can I return the array of objects queried from an external API in Rocket?
The key was wrapping the Vec in Json.
#[get("/addresses")]
fn addresses() -> Result<Json<Vec<InboundAddress>>, reqwest::Error> {
let url = "https://testnet.midgard.thorchain.info/v2/thorchain/inbound_addresses";
let addresses: Vec<InboundAddress> = reqwest::blocking::get(url)?.json()?;
Ok(Json(addresses))
}
In a web server application using rocket.rs, I am using an Error type that implement Responder throughout my API. This error type ensures all errors are uniformly rendered (as RFC 7807 json).
However, I can't find a way to use these Error responses in RequestGuards. It seems that the from_request function results in an Outcome which uses an entirely different model, returning Outcome::Failure((Status, T)) on errors.
How can I ensure that errors in these request guards are rendered in the same JSON format? Is it even customizable?
I have tried to use a catcher, but this does not seem to retrieve any error information whatsoever.
The docs for FromRequest's Outcome state:
Note that users can request types of Result<S, E> and Option<S> to catch Failures and retrieve the error value.
At the start of your FromRequest implementation, define type Error = JsonValue;
In the from_request function, make sure it returns request::Outcome<S, Self::Error> where S is what you're implementing for.
In the from_request function, when you want to return a failure do something like Outcome::Failure((Status::Unauthorized, json!({"error": "unauthorised"}))), or whatever it is you want to return.
In your route's function use Result<S, JsonValue> as the type of the request guard, where S is what you where implementing for. In your route, use match to match it to Ok(S) or Err(json_error) for example.
There is probably a way to pass on the status of Outcome::Failure, but the solution I've described means if you're using a custom responder you would set the status in the responder, not based on Outcome::Failure - for example the code below.
Here's an example applied to the ApiKey request guard example from the docs, with an example custom responder called ApiResponse that sets its own status:
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate rocket_contrib;
#[macro_use]
extern crate serde_derive;
use rocket::Outcome;
use rocket::http::{ContentType, Status};
use rocket::request::{self, Request, FromRequest};
use rocket::response::{self, Responder, Response};
use rocket_contrib::json::{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()
}
}
#[derive(Debug, Deserialize, Serialize)]
struct ApiKey(String);
/// Returns true if `key` is a valid API key string.
fn is_valid(key: &str) -> bool {
key == "valid_api_key"
}
impl<'a, 'r> FromRequest<'a, 'r> for ApiKey {
type Error = JsonValue;
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let keys: Vec<_> = request.headers().get("x-api-key").collect();
match keys.len() {
0 => Outcome::Failure((Status::BadRequest, json!({ "error": "api key missing" }))),
1 if is_valid(keys[0]) => Outcome::Success(ApiKey(keys[0].to_string())),
1 => Outcome::Failure((Status::BadRequest, json!({ "error": "api key invalid" }))),
_ => Outcome::Failure((Status::BadRequest, json!({ "error": "bad api key count" }))),
}
}
}
#[get("/sensitive")]
fn sensitive(key: Result<ApiKey, JsonValue>) -> ApiResponse {
match key {
Ok(_ApiKey) => ApiResponse {
json: json!({ "data": "sensitive data." }),
status: Status::Ok
},
Err(json_error) => ApiResponse {
json: json_error,
status: Status::BadRequest
}
}
}
I'm new to Rust and Rocket, so this might not be the best solution.
The code below is the beginnings of a small library I'm writing to talk to a web API. Users of the library will instantiate a client MyClient and access the web API through it. Here, I'm trying to get an access token from the API before making requests to it.
In get_new_access() I'm able to make the request and receive the JSON response. I then try to use serde to turn the response into an Access struct, and this is where the problems start.
I've created a library specific error enum MyError which can represent the JSON deserializing and reqwest errors that could occur within get_new_access(). However, when I go to compile I get the trait serde::Deserialize<'_> is not implemented for MyError. My understanding is that this is happening because in the case that I get one of the aforementioned errors, serde does not know how to deserialize it into an Access struct. Of course, I don't want it to do that at all, so my question is what should I do?
I've looked at various serde deserialize examples, but all of them seem to assume that they are running in a main function that can only return a serde error. If I put #[derive(Deserialize)] above MyError's declaration, then I get the same error, but it shifts to reqwest::Error and serde_json::Error instead.
use std::error;
use std::fmt;
extern crate chrono;
extern crate reqwest;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
use chrono::prelude::*;
use reqwest::Client;
pub struct MyClient {
access: Access,
token_expires: DateTime<Utc>,
}
#[derive(Deserialize, Debug)]
struct Access {
access_token: String,
expires_in: i64,
token_type: String,
}
fn main() {
let sc: MyClient = MyClient::new();
println!("{:?}", &sc.access);
}
impl MyClient {
pub fn new() -> MyClient {
let a: Access = MyClient::get_new_access().expect("Couldn't get Access");
let e: DateTime<Utc> = chrono::Utc::now(); //TODO
MyClient {
access: a,
token_expires: e,
}
}
fn get_new_access() -> Result<Access, MyError> {
let params = ["test"];
let client = Client::new();
let json = client
.post(&[""].concat())
.form(¶ms)
.send()?
.text()
.expect("Couldn't get JSON Response");
println!("{}", &json);
serde_json::from_str(&json)?
//let a = Access {access_token: "Test".to_string(), expires_in: 3600, token_type: "Test".to_string() };
//serde_json::from_str(&json)?
}
}
#[derive(Debug)]
pub enum MyError {
WebRequestError(reqwest::Error),
ParseError(serde_json::Error),
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "eRROR")
}
}
impl error::Error for MyError {
fn description(&self) -> &str {
"API internal error"
}
fn cause(&self) -> Option<&error::Error> {
// Generic error, underlying cause isn't tracked.
None
}
}
impl From<serde_json::Error> for MyError {
fn from(e: serde_json::Error) -> Self {
MyError::ParseError(e)
}
}
impl From<reqwest::Error> for MyError {
fn from(e: reqwest::Error) -> Self {
MyError::WebRequestError(e)
}
}
Playground link here.
Your first problem is that your fn get_new_access() -> Result<Access, MyError> expects a Result. But in here:
//...
serde_json::from_str(&json)?
}
because of using ?(try macro), you are trying to return Result's unwrapped value which is a subtype of serde::Deserialize<'_>. The compiler warns you about this Deserialize is not a Result. What you should do is just return the result without unwrapping it:
//...
serde_json::from_str(&json)
}
Or
//...
let access = serde_json::from_str(&json)?; // gets access or propagates error
Ok(access) //if no error return access in a Result
}
Then you will have a second problem because your function expects MyError in the Result while you are returning serde_json::Error with this call serde_json::from_str(&json). Luckily Result has the function map_err which maps the actual error type to your custom error type.
This code will solve your problem:
//...
serde_json::from_str(&json).map_err(MyError::ParseError)
}
For the request in the comment :
For example, if I change the web request line to let json = client.post("").form(¶ms).send().map_err(MyError::WebRequestError)?.text()?;,
is that better practice at all?
Yes but since text() returns a Result you need to map it's error as MyError too. Since both send and text has same error type(reqwest::Error) you can combine the results with and_then :
let json = client
.post(&[""].concat())
.form(¶ms)
.send()
.and_then(Response::text) //use reqwest::Response;
.map_err(MyError::WebRequestError)?;
I am trying to create a REST server using hyper. For robust error handling, I would prefer to have the service return a future with a custom error type that wraps hyper, Diesel, and other errors. Unfortunately, hyper::Response seems to hard-code a stream with error type hyper::error::Error, which conflicts with the error type I've defined for my service. I see a couple possible solutions:
Make my service return my custom error type by modifying hyper::Response, which seems hard.
Wrap non-hyper errors in a hyper::error::Error. This seems hacky.
Something else. It seems like I'm missing the "right" way to do this.
The following code shows what I think I want to do:
extern crate diesel;
extern crate futures;
extern crate hyper;
use futures::future::{ok, Future};
use hyper::StatusCode;
use hyper::server::{Request, Response, Service};
fn main() {
let address = "127.0.0.1:8080".parse().unwrap();
let server = hyper::server::Http::new()
.bind(&address, move || Ok(ApiService {}))
.unwrap();
server.run().unwrap();
}
pub struct ApiService;
impl Service for ApiService {
type Request = Request;
type Response = Response;
type Error = Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, request: Request) -> Self::Future {
Box::new(ok(Response::new().with_status(StatusCode::Ok)))
}
}
#[derive(Debug)]
pub enum Error {
Request(hyper::Error),
DatabaseResult(diesel::result::Error),
DatabaseConnection(diesel::ConnectionError),
Other(String),
}
// omitted impl of Display, std::error::Error for brevity
This code results in a compiler error which I believe is because the bind function requires that the response type have a body that is a stream with error type hyper::error::Error:
error[E0271]: type mismatch resolving `<ApiService as hyper::client::Service>::Error == hyper::Error`
--> src/main.rs:14:10
|
14 | .bind(&address, move || Ok(ApiService {}))
| ^^^^ expected enum `Error`, found enum `hyper::Error`
|
= note: expected type `Error`
found type `hyper::Error`
Because the ultimate goal of the server is to return a response to the user, I found an acceptable solution to be to create a finalize function that converts errors encountered while processing a request into correctly formed responses and treats those errors as non-errors from hyper's perspective. I will need to flesh this idea out some (e.g. By passing along hyper errors as errors), but I believe the basic idea is sound.
The following code modifies the code in the question to do this:
extern crate diesel;
extern crate futures;
extern crate hyper;
#[macro_use]
extern crate serde_derive;
use futures::future::{ok, Future};
use hyper::StatusCode;
use hyper::server::{Request, Response, Service};
fn main() {
let address = "127.0.0.1:8080".parse().unwrap();
let server = hyper::server::Http::new()
.bind(&address, move || Ok(ApiService {}))
.unwrap();
server.run().unwrap();
}
fn finalize(result: Result<Response, Error>) -> FutureResult<Response, hyper::Error> {
match result {
Ok(response) => ok(response),
Err(error) => {
let response_body =
json!({"status": 500, "description": error.description()}).to_string();
ok(Response::new()
.with_status(StatusCode::InternalServerError)
.with_header(ContentLength(response_body.len() as u64))
.with_body(response_body))
}
}
}
pub struct ApiService;
impl Service for ApiService {
type Request = Request;
type Response = Response;
type Error = hyper::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, request: Request) -> Self::Future {
let response = Ok(Response::new().with_status(StatusCode::Ok));
Box::new(finalize(response))
}
}
#[derive(Debug)]
pub enum Error {
Request(hyper::Error),
DatabaseResult(diesel::result::Error),
DatabaseConnection(diesel::ConnectionError),
Other(String),
}
// omitted impl of Display, std::error::Error for brevity
You can implement the std::convert::From trait for your Error type. E.g. for the hyper::Error case:
impl From<hyper::Error> for Error {
fn from(error: hyper::Error) -> Self {
Error::Request(error)
}
}
I am using iron. Most of time like 99.* % all is good. But sometimes I get error like Error was: ErrorImpl { code: EofWhileParsingString/List/Object, line: 1, column: 8186 } or InvalidUnicodeCodePoint. I am printing request in log and when i try that request every thing goes well. I also have server written in Golang receiving same request and they never have parsing or json to MyStruct conversion problem.Please note Code would not compile as it is, missing imports, error::from and structure definition. Can not provide reproducible request logs as it only happens when serving lots on concurrent request but if single request is taken it works fine.
I have tried serde_json::from_reader, bodyparser crate and all have same issue.
extern crate serde;
extern crate serde_json;
extern crate iron;
use self::iron;
use self::iron::prelude::*;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MyStruct {
}
struct ResponseTime;
impl typemap::Key for ResponseTime {
type Value = u64;
}
#[derive(Debug)]
struct RequestBody;
impl typemap::Key for RequestBody {
type Value = RefCell<Vec<u8>>;
}
impl BeforeMiddleware for ResponseTime {
fn before(&self, req: &mut Request) -> IronResult<()> {
req.extensions.insert::<RequestBody>(RefCell::new(Vec::new()));
req.extensions.insert::<ResponseTime>(precise_time_ns());
Ok(())
}
}
impl AfterMiddleware for ResponseTime {
fn after(&self, req: &mut Request, res: Response) -> IronResult<Response> {
Ok(res)
}
fn catch(&self, req : &mut Request, err : IronError) -> IronResult<Response> {
let ref byte_req = *req.extensions.get::<RequestBody>()
.unwrap()
.borrow();
//just to make sure uft8 is not causing some issue.
let payload = unsafe {
str::from_utf8_unchecked(&byte_req)
};
//but when i send request body all comes good
error!("Error {} for Body {}", err, payload);
Err(err)
}
}
fn iron_handler(req : &mut Request) -> Result<Response, CustomError>{
let mut buffer = req.extensions.get::<server::RequestBody>()
.unwrap()
.borrow_mut();
req.body.read_to_end(&mut buffer)?;
// not seeing InvalidUnicodeCodePoint after this.
let payload = String::from_utf8_lossy(&buffer);
//some request throw error
let my_struct_obj : MyStruct = serde_json::from_str(&payload)?;
Ok(Response::with((iron::status::Ok, "Final Response")))
}
Need help to figure out how to identify problem. Intent of posting here is to see if someone had same issue or can see obvious problem with this. Appreciate everyone'e time do not expect to build and run with examples as can not provide them because of privacy.