Have spent some time learning Rust and have now moved to looking at web based applications. Using the Rocket crate in rust I have explored the GET request method and understood this well. Now have a POST request in my code and it runs, but I am unsure how to actually POST a new book into my dummy database? How can I test the code to check that a received REQUEST is adding a new book?
I am working on MacOS.
Sorry am very new to this - so grateful for any help!
#![feature(decl_macro)]
#[macro_use] extern crate rocket;
use rocket::response::content::Json;
use rocket::request::Form;
#[get("/hello")]
fn hello() -> Json<&'static str> {
Json("{
'status': 'success',
'message': 'Hello API!'
'server': 'live'
}")
}
#[derive(FromForm, Debug)]
struct Book {
title: String,
author: String,
isbn: String
}
#[post("/book", data = "<book_form>")]
fn new_book(book_form: Form<Book>) -> String {
let book: Book = book_form.into_inner();
let mut dummy_db: Vec<Book> = Vec::new();
dummy_db.push(book);
format!("Book added successfully: {:?}", dummy_db)
}
fn main() {
rocket::ignite()
.mount("/api", routes![hello])
.mount("/book", routes![new_book])
.launch();
}
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))
}
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
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 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.
I'm following an Iron web framework tutorial, which seemed pretty simple, but I can't seem to encode a struct as JSON.
extern crate iron;
extern crate rustc_serialize;
use iron::prelude::*;
use iron::status;
use rustc_serialize::json;
struct Greeting {
msg: String,
}
fn main() {
fn hello_world(_: &mut Request) -> IronResult<Response> {
let greeting = Greeting { msg: "hello_world".to_string() };
let payload = json::encode(&greeting).unwrap();
// Ok(Response::with((status::Ok,payload)))
}
// Iron::new(hello_world).http("localhost:3000").unwrap();
}
My Cargo.toml
[package]
name = "iron_init"
version = "0.1.0"
authors = ["mazbaig"]
[dependencies]
iron = "*"
rustc-serialize = "*"
And my error:
error: the trait bound `Greeting: rustc_serialize::Encodable` is not satisfied [E0277]
let payload = json::encode(&greeting).unwrap();
^~~~~~~~~~~~
help: run `rustc --explain E0277` to see a detailed explanation
note: required by `rustc_serialize::json::encode`
I kinda get that the right types aren't getting passed into the json.encode() function, but I'm having trouble figuring out what it wants from me. I'm probably missing something really basic.
You didn't provide the actual tutorial that you are using, but it appears to match this one from brson.
extern crate iron;
extern crate rustc_serialize;
use iron::prelude::*;
use iron::status;
use rustc_serialize::json;
#[derive(RustcEncodable)]
struct Greeting {
msg: String
}
fn main() {
fn hello_world(_: &mut Request) -> IronResult<Response> {
let greeting = Greeting { msg: "Hello, World".to_string() };
let payload = json::encode(&greeting).unwrap();
Ok(Response::with((status::Ok, payload)))
}
Iron::new(hello_world).http("localhost:3000").unwrap();
println!("On 3000");
}
Notice anything different between the two?
#[derive(RustcEncodable)]
struct Greeting {
msg: String
}
You have to specify that the Encodable trait is implemented. In this case, you can do so by deriving RustcEncodable.