Rust Get request with parameters - rust

I have the following asynchronous function within an implementation #[async_trait]
pub async fn get_field_value(field: web::Path<String>, value: web::Path<String>) -> HttpResponse {
let json_message = json!({
"field": field.0,
"value": value.0
});
HttpResponse::Ok().json(json_message)
}
now i have my router function
pub fn init_router(config: &mut ServiceConfig){
config.service(web::resource("/get/field-value/{field}/{value}").route(web::get().to(get_field_value)));
}
then when running the web application: localhost:3000/get/field-value/name/James
I don't get the Json if not I get the following error:
wrong number of parameters: 2 expected 1
I think I shouldn't get the error because I initialize the value in the parameters correctly.
Neither #[async_trait] allows me to use #[get("/get/field-value/{field}/{value}")]

I think the route handler is given one argument that contains all values in the route pattern, rather than two separate String arguments. You can use a tuple to get the two values:
pub async fn get_field_value(field: web::Path<(String, String)>) -> HttpResponse
Or you can use serde to deserialize the fields from the route pattern for you:
#[derive(Serialize, Deserialize)]
pub struct Field {
pub field: String,
pub value: String,
}
pub async fn get_field_value(data: web::Path<Field>) -> HttpResponse {
let json_message = json!({
"field": data.field,
"value": data.value
});
HttpResponse::Ok().json(json_message)
}

Related

Trouble with reqwest: Unable to deserialize JSON response + `match` issues

I am learning Rust and it's web api support. I am working on a simple project which does a web API call. Here's the intuition:
(It's all about getting Sprint dates from an Azure DevOps project)
In src/getlatestsprint.rs I have a struct that has fields called "date" and "sprint". They're both String type.
pub struct Sprint {
date: String,
sprint: String,
}
I am then doing an impl on it and I am expecting it to return the Sprint itself, and it's supposed to have the date and the sprint number. FYI, what it is returning in the code is just placeholder.
The trouble I am having is with the response (not this block).
impl Sprint {
pub fn get_sprint_and_date() -> Result<Sprint, reqwest::Error> {
let sprint_url: &str = "https://dev.azure.com/redactedOrg/redactedProj/redactedTeam/_apis/work/teamsettings/iterations?api-version=6.0";
let pat: &str = "redactedPAT";
let client = reqwest::blocking::Client::new();
let get_request = client.get(sprint_url)
.basic_auth(&"None", Some(pat))
.send()?;
All this works. get_request.status() is 200 and get_request.text() is Ok(JSON_respose). The trouble starts from here:
if get_request.status() == 200 {
match get_request.text() {
Ok(v) => {
let deserialized_json: HashMap<String, serde_json::Value> = serde_json::from_str(&v).unwrap(); //This I got from another SO post that I was going through to work this out.
match deserialized_json.get("value") {
Some(des_j) => des_j,
None => None, //It says here "expected `&serde_json::Value`, found enum `std::option::Option`" This is where I am lost really.
}
},
_ => (),
}
}
//Placeholder return statement (I know I don't need to write return, but I am just doing it because I am used to it in other languages)
return Ok(Sprint {
date: String::from("sdfsd"),
sprint: String::from("dsfsfsdfsdfsd"),
})
}
}
My intention is to return the latest date and sprint got from the response as a Struct.
What am I doing wrong here?
EDIT
Sample JSON response:
{
"count": 82,
"value": [
{
"id": "redactedID",
"name": "Sprint 74",
"path": "redactedProjectName\\Sprint 74",
"attributes": {
"startDate": "2018-10-22T00:00:00Z",
"finishDate": "2018-11-04T00:00:00Z",
"timeFrame": "past"
},
"url": "dev.azure.com/redactedOrg/redactedProject/redactedProj/_apis/…"
}
]
}
So I managed to get what I wanted. I wasn't aware that to work with nested JSON, I had to create a Struct for each of the nested keys. Not just that, I had to even match the case of the field that I am getting the response of. Weird, but OK. I am pretty sure there's an easier way ti do this, but I was able to resolve it myself.
Here are the Structs that I created.
#[derive(Deserialize, Serialize)]
struct Response {
count: u32,
value: Vec<Value>,
}
#[derive(Deserialize, Serialize, Debug)]
struct Value {
id: String,
name: String,
path: String,
attributes: Attributes,
url: String,
}
#[derive(Deserialize, Serialize, Debug)]
struct Attributes {
startDate: String,
finishDate: String,
timeFrame: String,
}
We can serialize and deserialize directly to a custom struct using serde.
Let's start by deriving Serialize and Deserialize for Sprint:
//This requires the serde crate with derive feature
use serde::{Serialize, Deserialize};
#[derive(Deserialize, Serialize)]
pub struct Sprint {
date: String,
sprint: String,
}
Then we can clean up the implementation:
impl Sprint {
pub fn get_sprint_and_date() -> Result<Sprint, reqwest::Error> {
let sprint_url: &str = "https://dev.azure.com/redactedOrg/redactedProj/redactedTeam/_apis/work/teamsettings/iterations?api-version=6.0";
let pat: &str = "redactedPAT";
let client = reqwest::blocking::Client::new();
let get_request = client.get(sprint_url)
.basic_auth(&"None", Some(pat))
.send()?;
//This requires the reqwest crate with the json feature
let sprint: Sprint = get_request.json()?;
Ok(sprint)
}
}

type mismatch resolving <impl std::future::Future as std::future::Future>::Output == std::result::Result<wasm_bindgen::JsValue, wasm_bindgen::JsValue>

I am trying to implement an API class using wasm_bindgen with asynchronous calls.
#![allow(non_snake_case)]
use std::future::Future;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use js_sys::Promise;
use web_sys::{Request, RequestInit, RequestMode, Response};
use wasm_bindgen_futures::future_to_promise;
use crate::fetch_example::*;
#[wasm_bindgen]
#[derive(Debug, Serialize, Deserialize)]
pub struct API {
root: String,
}
#[wasm_bindgen]
impl API {
pub fn new(root: &str) -> Self {
Self {
root: root.to_string(),
}
}
pub fn getVersion(&self) -> Promise {
let url = format!("{}/version", self.root);
future_to_promise(async move {
_request_json(&url, "GET")
})
}
// other methods ...
}
// What is the correct returned type instead of Result<JsValue, JsValue> ???
async fn _request_json(url: &str, method: &str) -> Result<JsValue, JsValue> {
let mut opts = RequestInit::new();
opts.method(method);
opts.mode(RequestMode::Cors);
let request = Request::new_with_str_and_init(&url, &opts)?;
request.headers().set("Accept", "application/json")?;
let window = web_sys::window().unwrap();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
let resp: Response = resp_value.dyn_into().unwrap();
let json = JsFuture::from(resp.json()?).await?;
Ok(json)
}
As it is seen, I took out the HTTP call into a separate private function _request_json to avoid code duplication in different API methods (they are supposed to be several, not getVersion only).
I took the base example of an HTTP call from here: https://rustwasm.github.io/wasm-bindgen/examples/fetch.html
Also I found that asynchronous calls of struct methods should be implemented with the help of future_to_promise: https://github.com/rustwasm/wasm-bindgen/issues/1858
So the issue is with the returned type by _request_json. I cannot guess it correctly.
When I compile the project I get the error:
type mismatch resolving `<impl std::future::Future as std::future::Future>::Output == std::result::Result<wasm_bindgen::JsValue, wasm_bindgen::JsValue>`
If I copy the body of _request_json inside of future_to_promise everything works fine.
What is the correct returned expression for _request_json to make the code works?
This construction:
async move {
_request_json(&url, "GET")
}
has the type impl Future<Output = impl Future<Output = Result<JsValue, JsValue>>>.
You can fix it like this:
future_to_promise(async move {
_request_json(&url, "GET").await
})
and probably also like this:
future_to_promise(_request_json(&url, "GET"))

Load .env and convert it to struct genericly with helper function

I want to convert multiple env.variables to static struct.
I can do it mannually:
Env {
is_development: env::var("IS_DEVELOPMENT")
.unwrap()
.parse::<bool>()
.unwrap(),
server: Server {
host: env::var("HOST").unwrap(),
port: env::var("PORT")
.unwrap()
.parse::<u16>()
.unwrap(),
},
}
But when there is multiple values, it's became bloated. Is there a way to make generic helper function that will give me value that i specify or panic? Something like this (or another solution):
fn get_env_var<T>(env_var_name: String) -> T {
// panic is ok here
let var = env::var(env_var_name).unwrap();
T::from(var)
}
get_env_var<u16>("PORT") // here i got u16
get_env_var<bool>("IS_DEVELOPMENT") // here is my boolean
Full example
use crate::server::logger::log_raw;
use dotenv::dotenv;
use serde::Deserialize;
use std::env;
#[derive(Deserialize, Debug, Clone)]
pub struct Server {
pub host: String,
pub port: u16,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Env {
pub is_development: bool,
pub server: Server,
}
impl Env {
pub fn init() -> Self {
dotenv().expect(".env loading fail");
// how can i specify what type i expect?
fn get_env_var<T>(env_var_name: String) -> T {
// panic is ok here
let var = env::var(env_var_name).unwrap();
T::from(var)
}
// instead this
Env {
is_development: env::var("IS_DEVELOPMENT")
.unwrap()
.parse::<bool>()
.unwrap(),
server: Server {
host: env::var("HOST").unwrap(),
port: env::var("PORT")
.unwrap()
.parse::<u16>()
.unwrap(),
},
}
// do something like this
/*
Env {
is_development: get_env_var<bool>("IS_DEVELOPMENT"),
server: Server {
host: get_env_var<String>("HOST"),
port: get_env_var<u16>("PORT"),
},
}
*/
}
}
lazy_static! {
pub static ref ENV: Env = Env::init();
}
Like in your manual version, where you use str::parse, you can have the same requirement as str::parse, which is FromStr. So if you include the T: FromStr requirement, then you'll be able to do var.parse::<T>().
use std::env;
use std::fmt::Debug;
use std::str::FromStr;
fn get_env_var<T>(env_var_name: &str) -> T
where
T: FromStr,
T::Err: Debug,
{
let var = env::var(env_var_name).unwrap();
var.parse::<T>().unwrap()
}
Then if you run the following by executing PORT=1234 IS_DEVELOPMENT=true cargo run.
fn main() {
println!("{}", get_env_var::<u16>("PORT"));
println!("{}", get_env_var::<bool>("IS_DEVELOPMENT"));
}
Then it will output:
1234
true
Alternatively, you might want to be able to handle VarError::NotPresent and fallback to a default.
use std::env::{self, VarError};
use std::fmt::Debug;
use std::str::FromStr;
fn get_env_var<T>(env_var_name: &str) -> Result<T, VarError>
where
T: FromStr,
T::Err: Debug,
{
let var = env::var(env_var_name)?;
Ok(var.parse().unwrap())
}
Now if you only executed PORT=1234 cargo run, then it would make it easier to do this:
let is_dev = get_env_var::<bool>("IS_DEVELOPMENT")
.map_err(|err| match err {
VarError::NotPresent => Ok(false),
err => Err(err),
})
.unwrap();
println!("{:?}", is_dev);
If you want to fallback to Default if VarError::NotPresent:
fn get_env_var<T>(env_var_name: &str) -> T
where
T: FromStr,
T::Err: Debug,
T: Default,
{
let var = match env::var(env_var_name) {
Err(VarError::NotPresent) => return T::default(),
res => res.unwrap(),
};
var.parse().unwrap()
}
Rust genericity, inspired by Haskell's works through traits and specifically trait bounds. This means when you write
fn get_env_var<T>(env_var_name: String) -> T
since there is no trait bound on T there are essentially no capabilities for it (this is rather unlike C++).
Therefore, as far as rustc is concerned, pretty much the only thing it can do with a T is... take one as parameter then return it as-is.
Thus to do anything useful with a T (including creating one, whether from something else or de novo) you need to use the correct trait and provide the correct trait bounds.
The From trait is entirely the wrong trait to involve here: it specifies total (never-failing) conversions e.g. converting a u16 to a u32, which can never fail.
Whether it's converting a String to a bool or a u16, the conversion is quite obviously less than total: there is an infinity of string values which are not sequences of decimal digits describing a number below 2^16.
In Rust, the signifier of failabibility tends to be Try. There is a TryFrom trait, however for historical reasons and as it documents in its signature the str::parse method is hooked on the FromStr trait.
This means in order to declare that your T can be created from a string (and use the parse method to create one), you need to bound T to FromStr. And of course indicate that it may fail, and will return whatever error T generates when it can't be parsed from a string:
fn get_env_var<T: FromStr>(env_var_name: String) -> Result<T, T::Err> {
let var = env::var(env_var_name).unwrap();
var.parse()
}
Incidentally, taking a String as input is usually avoided unless you really have to[0]. Usually you'd take an &str, that's a lot more flexible as it can be used e.g. with string literals (which are of type &'static str).
So
fn get_env_var<T: FromStr>(env_var_name: &str) -> Result<T, T::Err> {
let var = env::var(env_var_name).unwrap();
var.parse()
}
[0] or for efficiency purposes sometimes

Using a custom Rocket Responder for an error in a RequestGuard

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.

Stop Rust from enforcing the serde::Deserialize trait on an error type

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(&params)
.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(&params).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(&params)
.send()
.and_then(Response::text) //use reqwest::Response;
.map_err(MyError::WebRequestError)?;

Resources