Parsing JSON from POST request

First confessions...I am an absolute Rust beginner, and only a marginal C++ developer. Most of my career has been around JVM-centric languages.

I set myself an intial learning task to hit an access control endpoint, log in, and get an oauth token back.

I can't shake the feeling that my implementation is awkward and too complicated, particularly the multiple uses of "unwrap()". Any advice greatly appreciated.

extern crate reqwest;
extern crate serde;
extern crate serde_derive;
extern crate serde_json;

use std::collections::HashMap;
use serde::{Deserialize};
use reqwest::blocking::Client;

#[derive(Deserialize)]
struct Tokens {
    access_token: String,
    refresh_token: String,
}

fn main() {
    let mut map = HashMap::new();

    map.insert("grant_type", "password");
    map.insert("client_id", "id");
    map.insert("client_secret", "secret");
    map.insert("username", "username");
    map.insert("password", "password");

    let client = Client::new();

    let res = client.post("https://some-url/apis/private/tokens")
        .json(&map)
        .send();

    let response = res.unwrap().json::<Tokens>().unwrap();

    println!("{:?}", response.access_token);
}

I'm assuming there is a more elegant way to handle the response as json straight into the struct?

The usual alternative to unwrap is to use the ? operator for error handling:

use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // ...
    let response = client.post("https://some-url/apis/private/tokens")
        .json(&map)
        .send()?
        .json::<Tokens>()?;
    // ...
}
2 Likes

Ah, it really was as simple as that. Can '?' also be used to unwrap a "Some()", or does that always need an explicit call to "unwrap ()"?

You can use ? to unwrap an option, however you need to be in a function that returns option to do that. You can also use ok_or or ok_or_else to convert your option into a result and use ? on that result.

1 Like

So the way I would approach error handling is to first understand that Result and Option are sum types - like tagged unions in C (and called enum in rust). To find out which variant of a sum type you have, the canonical way is to destructure using match

let test = Ok(42);

match test {
    Ok(val) => // handle the Ok case
    Err(e) => // handle the error case
}

When you just want to push an error up the stack, this can become verbose, so some syntax was introduced to help:

fn example_try_op() -> Result<i32, String> {
    let x = Ok(42);

    do_something_with_inner_value(x?); // this will return early if x is Err
}

unwrap does something different - it basically bails out of the program if the value is Err or None. Try to avoid unwrap unless

  1. You know that the value will never be Err.
  2. You're prototyping and want to defer error handling till later

as @alice says there are methods on Result and Option that make it possible to recover from errors (e.g. by using a default value) or changing the value of the Ok or Err case.

The book is the best place to learn all this and will explain it better than me

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.