How to deserialize JWT with "http://url/path" as field names? - rust

I have an actix-web server that will be validating a JWT with each request and extracting claims for internal use.
I have the JWT decode-and-validate process working, based on this example by Auth0.
The problem I have is the enterprise application I need to work with has its claims structured as:
{
"https://some-org-url/domain": "some-domain",
"https://some-org-url/roles": [
"...",
],
"https://some-org-url/tag-list": [
"...",
],
"iss": "https://auth-app.com/",
"sub": "app|xxxxxxxxxxx",
"aud": [
"https://some-org-url",
"https://auth-app.com/userinfo"
],
"iat": 1660117559,
"exp": 1660203959,
"azp": "xxxxxxxxx",
"scope": "openid profile email"
}
I can extract the syntactically valid claims by defining my struct as
#[derive(Debug, Deserialize)]
pub struct Claims {
iss: String,
sub: String,
aud: Vec<String>,
iat: u32,
exp: u32,
azp: String,
scope: String
}
But I am failing to find a way to extract the remaining claims:
https://some-org-url/domain
https://some-org-url/roles
https://some-org-url/tag-list
i.e. the arbitrary claims that cannot be defined as struct fields as-is. Using their path stems (e.g. domain or roles) does not work. Any suggestions?

After stepping throuh jsonwebtoken::decode a few times, I realised that it calls Deserialize on the Claims struct you define, passing in the JWT body as data, therefore you can leverage serde field aliases to achieve the desired outcome:
#[derive(Debug, Deserialize)]
pub struct Claims {
#[serde(alias = "https://some-org-url/domain")]
domain: String,
#[serde(alias = "https://some-org-url/roles")]
roles: Vec<String>,
#[serde(alias = "https://some-org-url/tag-list")]
tag_list: Vec<String>,
iss: String,
sub: String,
aud: Vec<String>,
iat: u32,
exp: u32,
azp: String,
scope: String
}
- which works fine.

Related

Serialize a variable format field, one of two potential types

I am making a api request where one field can have to potential structures:
a string (example: "key": "24789223")
an object (example: "key": { value: "12121", "currency": "USD" })
I have tried to use an enum like this:
pub enum CustomAmount {
ComplexAmount(ComplexAmount),
SimpleAmount(String)
}
Where ComplexAmount is:
pub struct ComplexAmount {
pub value: String,
pub currency: String
}
However, when I try to deserialize from the json response I get the following error:
unknown variant `24789223 `, expected `CustomAmount` or `SimpleAmount`
I am using wasm-bindgen.
Is there a better way to allow a field to have two potential types?
Thanks
To be able to distinguish between two enum values (where there's not any extra info in the json like "__type": "ComplexAmount"), you need to add the #[serde(untagged)] macro to the enum declaration.
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum CustomAmount {
ComplexAmount(ComplexAmount),
SimpleAmount(String)
}
also make sure you've used #[derive(Deserialize)] on ComplexAmount as well.

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",
},
]

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

How to serialize a struct with a top-level key with Serde?

I'm currenlty developping a web API with Rocket that use the following struct for error responses :
#[derive(Serialize, Deserialize)]
pub struct ErrorResponse {
code: u16,
reason: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
}
Actually, serializing this struct produce json similar to this :
{
"code": 400,
"reason": "Bad Request"
}
I'm looking to serialize it this way:
{
"error": {
"code": 400,
"reason": "Bad Request"
}
}
What is the easiest way to do that without using two different sructs ?
Thank you in advance for your help!
The simplest way I can think of is to wrap your struct in an enum with a single struct variant:
#[derive(Serialize, Deserialize)]
pub enum ErrorResponseWrapper {
#[serde(rename = "error")]
ErrorResponse {
code: u16,
reason: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
}
}
The annoying thing about this is that the type and the constructor must have different names now. I.e. if a function takes an ErrorResponse it now has to take an ErrorResponseWrapper (although you probably wanna come up with a better name than that), but you have to construct such a value with the ErrorResponseWrapper::ErrorResponse variant. Fortunately you can at least add use ErrorResponseWrapper::ErrorResponse; to your imports.
Playground

How to access field from strict serde_json deserialization with tags

I get Some(Address{...}) in the Address field of data which is expected. I can see it from the debug printout.
But &data.Address = &data.Address.unwrap();
gives me static error saying: "no field Address on type Profile".
let mut data:Profile = serde_json::from_str(json_string).unwrap();
&data.Address = &data.Address.unwrap();
#[derive(Deserialize, Debug, Serialize)]
#[serde(tag = "m_tag")]
pub enum Profile {
Data{
#[serde(rename="FirstName")] // to comply with Rust coding standards
first_name:String,
LastName: String,
Age: u32,
Address: Option<Address>,
PhoneNumbers: Vec<String>
},
Data2{
#[serde(rename="FirstName")] // to comply with Rust coding standards
first_name:String,
LastName: String,
Age: u32,
},
}
#[derive(Serialize, Deserialize)]
struct Address {
Street: String,
City: String,
Country: String
}
UPDATED:
Q1) I can unwrap the value BUt I would like to update the &data.Address to it.
But i'm back to original problem where &data is a Profile type and doesn't have the Address field.
match &data {
Profile::Data {
first_name,
LastName,
Age,
Address,
PhoneNumbers,
} => data.Address = &Address.as_ref().unwrap(),
Profile::Data2 {
first_name,
LastName,
Age,
} => println!("ok"),
}
Edit:
There's no need to create an Enum if the fields overlap. It's better to declare the fields that are not guaranteed to be there as optional:
#[derive(Deserialize, Debug, Serialize)]
struct Profile {
#[serde(rename="FirstName")]
first_name:String,
LastName: String,
Age: u32,
Address: Option<Address>,
PhoneNumbers: Option<Vec<String>>
};
So that now it's guaranteed that the type Profile has an Address field:
let data:Profile = serde_json::from_str(json_string).unwrap();
println!("{:?}", data.Address);
Here's a code example working in the Rust Playground.

Resources