2

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)

  1. 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,
}
  1. 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.
  2. 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()?;
  1. 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/…"
        } 
    ] 
}
Anonymous Person
  • 1,437
  • 8
  • 26
  • 47

2 Answers2

2

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,
}
Anonymous Person
  • 1,437
  • 8
  • 26
  • 47
  • 1
    Check out my comment on your question to clean this up even more. Good job though, take an upvote for figuring it out mostly on your own :) – MeetTitan Dec 13 '21 at 19:52
1

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)
    }
}
MeetTitan
  • 3,383
  • 1
  • 13
  • 26
  • Unfortunately, this doesn't help. This is what I get when I do exactly this way `Err(reqwest::Error { kind: Decode, source: Error("missing field `date`", line: 1, column: 31215) })` What should I be aware of before doing this? I should say I get a nested JSON as response. It's not flat JSON, so, i don't think putting them into the `sprint` Struct as you suggested would work? I could be wrong though. – Anonymous Person Dec 11 '21 at 06:25
  • Could you give an example of your expected json? It seems like your server response doesn't contain date as a top level field. – MeetTitan Dec 11 '21 at 16:50
  • That's correct. The response does not contain `date` as the top level field. It contains `count` and `value`. Does that mean I'd have to change the names of the Stuct fields to that? Then what about the nested JSON data inside of the field? Like, `value` has the actual data that I am interested it. How's that going to be interpreted? My trouble lies because I am comparing all this to how Python's `Request` module functions, which is normal I guess, as I am more used to that. – Anonymous Person Dec 12 '21 at 03:28
  • Example: ``` { "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": "https://dev.azure.com/redactedOrg/redactedProject/redactedProj/_apis/work/teamsettings/iterations/redactedIDofSomethingIDoNotKnow" } ] } ``` Just image more records under `value` – Anonymous Person Dec 12 '21 at 03:41
  • Thanks for looking into this. I managed to resolve it. – Anonymous Person Dec 12 '21 at 16:52
  • @AnonymousPerson, You can de-structure the nested json by using [`serde_json::Value`](https://docs.rs/serde_json/1.0.72/serde_json/value/enum.Value.html) to navigate to the json entry you'd like, then deserialize to a concrete struct. So you can get away with only the `Sprint` struct, but you'd have to navigate to the inner json object whose only values are a `String` and a `Date`, then deserialize to `Sprint`. Also check out [serde's field attributes](https://serde.rs/field-attrs.html) if you'd like json field names to potentially differ from struct field names. – MeetTitan Dec 13 '21 at 19:48