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

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)
}
}

Related

Deserialise dynamic externally tagged values with serde?

I've come across some data I'm trying to translate into a struct to use with serde. It's a nested dictionary where each subsequent entry's key is the version number of the piece of data. I can't wrap my head around how to translate this into a serde struct that will read a dynamic number of these versions. I attached a terrible example of what I came up with but this is definitely is wrong.
I'm not sure exactly what key terms I can use to search for information on this. The one thing I did find in the documentation were externally tagged enums however I'm not quite sure this will help me.
{
"v0001": {
"id": "a"
// ...
},
"v0002": {
"id": "b"
// ...
}
"v0003": {
"id": "c"
// ...
}
// ...
}
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Data {
pub v0001: Version,
pub v0002: Version,
pub v0003: Version,
// ??!
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Version {
id: String,
}
What I'm ultimately trying to do is, load the json, append a new version and rewrite it back to json.
Any help/thoughts/feedback would be amazing! :)
After #cdknight's suggestion to use a HashMap I realized that the root data type didn't have to be a struct. Here's a working solution I came up with. I used a BTreeMap instead so the keys would be in order.
type Data = BTreeMap<String, Version>;
#[derive(Debug, Serialize, Deserialize)]
struct Version {
pub id: String,
}
fn main() {
let input = r#"
{
"v0001": {
"id": "a"
},
"v0002": {
"id": "b"
},
"v0003": {
"id": "c"
}
}"#;
let data: Data = serde_json::from_str(input).unwrap();
dbg!(&data);
let vec: Vec<Version> = data.into_values().collect();
dbg!(&vec);
}
Ouputs:
[src/main.rs:106] &data = {
"v0001": Version {
id: "a",
},
"v0002": Version {
id: "b",
},
"v0003": Version {
id: "c",
},
}
[src/main.rs:109] &vec = [
Version {
id: "a",
},
Version {
id: "b",
},
Version {
id: "c",
},
]

Can I declare an enum value that takes a String or &str without needing additional functions?

I have an enum with a String:
enum MyLovelyEnum {
Thing(String),
}
For tests, I would like to be able to pass in a &'static str to avoid MyLovelyEnum::Thing("abc".to_string) over and over.
I found that you can do this nicely with structs with a constructor:
// From: https://hermanradtke.com/2015/05/06/creating-a-rust-function-that-accepts-string-or-str.html
struct Person {
name: String,
}
impl Person {
fn new<S: Into<String>>(name: S) -> Person {
Person { name: name.into() }
}
}
fn main() {
let person = Person::new("Herman");
let person = Person::new("Herman".to_string());
}
I know I can use lifetimes or Cow as described in What's the best practice for str/String values in Rust enums? or I can make my own function.
Is there something close to the example in the blog post for enums? e.g.
// this is the kind of thing I am after but this specifically is not correct syntax
enum MyLovelyEnum {
Thing<S: Into<String>>(S)
}
You can create a generic enum:
enum MyLovelyEnum<S>
where
S: Into<String>,
{
Thing(S),
}
MyLovelyEnum::Thing("a");
MyLovelyEnum::Thing("b".to_string());
I likely wouldn't do that in my code, instead opting to create a constructor, much like the blog post you linked:
enum MyLovelyEnum {
Thing(String),
}
impl MyLovelyEnum {
fn thing(s: impl Into<String>) -> Self {
MyLovelyEnum::Thing(s.into())
}
}
MyLovelyEnum::thing("a");
MyLovelyEnum::thing("b".to_string());

Rust Get request with parameters

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)
}

Avoid string field cloning in rust

I've MyObjManager that holds an array of MyObj (more than 1500 elements). MyObj has a lot of String fields and an option integer.
The serialize function takes a manager and a price for serializing the array with the new price.
Something like this:
use serde::{Serialize};
use serde_json;
#[derive(Serialize, Clone)]
pub struct MyObj {
id: String,
name: String,
price: Option<u32>
}
pub struct MyObjManager {
my_objs: Vec<MyObj>
}
fn get (manager: &MyObjManager, price: u32) -> Vec<MyObj> {
manager.my_objs.iter()
.map(|my_obj| {
MyObj {
price: Some(price),
..my_obj.clone() // string allocation
}
})
.collect()
}
pub fn serialize (manager: &MyObjManager, price: u32) -> String {
let my_objs = get(manager, price);
serde_json::to_string(&my_objs).unwrap()
}
With this implementation, every serialize call allocates a lot of string. It's possible to avoid that?
NB: the array of MyObj can be consider static and doesn't change in the time

How do I read attributes programatically in Rust

I'd like to read attributes programatically. For example, I have a struct which has attributes attached to each field:
#[derive(Clone, Debug, PartialEq, Message)]
pub struct Person {
#[prost(string, tag="1")]
pub name: String,
/// Unique ID number for this person.
#[prost(int32, tag="2")]
pub id: i32,
#[prost(string, tag="3")]
pub email: String,
#[prost(message, repeated, tag="4")]
pub phones: Vec<person::PhoneNumber>,
}
(source)
I'd like to find the tag associated with the email field.
I expect there is some code like this to get the tag at runtime:
let tag = Person::email::prost::tag;
Since attributes are read only at compile time you need to write a procedural macro to solve such issue.
You can find information with following designators in your macro.
Field name with ident
Attribute contents with meta
After you find your field name and your meta, then you can match the stringified result with given input parameter in macro like following:
macro_rules! my_macro {
(struct $name:ident {
$(#[$field_attribute:meta] $field_name:ident: $field_type:ty,)*
}) => {
struct $name {
$(#[$field_attribute] $field_name: $field_type,)*
}
impl $name {
fn get_field_attribute(field_name_prm : &str) -> &'static str {
let fields = vec![$(stringify!($field_name,$field_attribute)),*];
let mut ret_val = "Field Not Found";
fields.iter().for_each(|field_str| {
let parts : Vec<&str> = field_str.split(' ').collect();
if parts[0] == field_name_prm{
ret_val = parts[2];
}
});
ret_val
}
}
}
}
my_macro! {
struct S {
#[serde(default)]
field1: String,
#[serde(default)]
field2: String,
}
}
Please note that it assumes that every field in the struct has an attribute. And every field declaration is ending with , including last field. But with some modification on regex you can make it available for optional attributes as well.
Here working solution in Playground
For further info about designators here is the reference
Also you can take a quick look for procedural macros here

Resources