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
Related
I'm trying to construct a JSON in rust using serde using structs.
{
"parent": { "database_id": "123" },
"properties": {
"title": {
"title": [
{
"text": {
"content": "Yurts in Big Sur, 123"
}
}
]
}
}
}
There's a lot of nested structures. Should I create a struct for each nest? In the case of the two "title" properties. How do I handle that?
I tried to create a struct for each nest.
Here's how it looks like
#[derive(Serialize)]
struct DBRow {
parent: Parent,
properties: Properties,
}
#[derive(Serialize)]
struct Parent {
database_id: String
}
#[derive(Serialize)]
struct Properties {
title: PropTitle
}
#[derive(Serialize)]
struct PropTitle {
title: Vec<Text>
}
#[derive(Serialize)]
struct Text {
content: String
}
#[derive(Deserialize)]
struct CreateUser {
username: String,
}
You are missing the dezeralization for the text field
"text": {
"content": "Yurts in Big Sur, 123"
}
Instead of having a Vec<Text> in PropTitle, you need another struct in between that has the text field.
#[derive(Deserialize, Serialize)]
struct PropTitle {
title: Vec<TextEntry>,
}
#[derive(Deserialize, Serialize)]
struct TextEntry {
text: Text,
}
#[derive(Deserialize, Serialize)]
struct Text {
content: String,
}
In general, yes if you want to stick with using derive, you have little choice besides defining a rust struct for each level and "type" of structure in your JSON. I only have a few mitigating points:
You could abbreviate the title thing a bit by having a #[derive(Serialize)] struct Titled<T> { title: T } and then using it as struct DBRow { properties: Titled<Titled<Vec<Text>>>, … }. Playground. But I doubt it's of much use because:
You probably have more than one property, so I suspect you'll end up with something like struct DBRow { properties: HashMap<String, Property>, … } and #[derive(Serialize)] #[serde(rename_all = "snake_case"))] enum Property { Title (Vec<Text>) } anyway.
If you don't like working with the deeply nested set of structs in your rust code, you can make serde automatically convert structs after de-/serialization with from and into.
I made a crate to make this kind of definition easier to read, structstruck, but it still requires defining a lot of structs
You can of course define custom serializers, but that will likely end up being more work than defining the rust structs.
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.
I have an enum similar to:
#[derive(Debug, Deserialize, Serialize)]
pub enum Gender {
Male,
Female,
NonBinary
}
and I a have an axum handler function which expects it to be passed in as Query(gender): Query<Gender>
But when I request the endpoint like this:
http://localhost:3000/greet?gender=Male
The page returns the following error message:
Failed to deserialize query string: invalid type: map, expected enum Gender
If I switch the params to use a struct instead everything works fine, which is why I know this is specifically an issue with enum serialization.
Any ideas how to fix?
You should use a struct as your query parameter type:
#[derive(Debug, Deserialize, Serialize)]
pub enum Gender {
Male,
Female,
NonBinary
}
#[derive(Debug, Deserialize)]
pub struct GreetParams {
gender: Gender,
}
async fn greet_handler(Query(params): Query<GreetParams>) {}
The reason you get an error is because ?gender=Male is parsed as a mapping of key-value pairs. Therefore, you need to use a mapped data structure that has named keys: something like BTreeMap<String, _>, HashMap<String, _>, or a struct.
If I switch the params to use a struct instead everything works fine, which is why I know this is specifically an issue with enum serialization. Any ideas how to fix?
There is nothing to fix; this is working as intended.
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)
}
}
I'm using rust + rocket + diesel (orm) + serde_derive to make a rest api. Currently, I'm dealing with error handling for the api if diesel fails to insert a user for whatever reason. It looks like this:
pub fn create(user: InsertableUser, connection: &MysqlConnection) -> ApiResponse {
let result = diesel::insert_into(users::table)
.values(&InsertableUser::hashed_user(user))
.execute(connection);
match result {
Ok(_) => ApiResponse {
json: json!({"success": true, "error": null}),
status: Status::Ok,
},
Err(error) => {
println!("Cannot create the recipe: {:?}", error);
ApiResponse {
json: json!({"success": false, "error": error}),
status: Status::UnprocessableEntity,
}
}
}
}
However, json: json!({"success": false, "error": error}), gives me this error:
the trait bound `diesel::result::Error: user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not satisfied
the trait `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not implemented for `diesel::result::Error`
note: required because of the requirements on the impl of `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` for `&diesel::result::Error`
note: required by `serde_json::value::to_value`rustc(E0277)
<::serde_json::macros::json_internal macros>(123, 27): the trait `user::_IMPL_DESERIALIZE_FOR_User::_serde::Serialize` is not implemented for `diesel::result::Error`
By the sounds of it, diesel::result::Error does not #[derive(Serialize)], and so cannot be serialized with the json! macro. Thus, I need some way to make the diesel::result::Error implement/derive Serialize.
Thanks in advance for any help.
BTW the ApiResponse looks like:
use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response;
use rocket::response::{Responder, Response};
use rocket_contrib::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()
}
}
Serde provides a workaround for deriving serialization implementations for external crates - see the section Derive for remote crates in their documentation.
You have to define an enum with the same definition as the one you are trying to serialize (diesel::result::Error in your case), and then identify that as a kind of proxy for the type you are trying to serialize, like this:
#[derive(Serialize, Deserialize)]
#[serde(remote = "diesel::result::Error")]
struct ErrorDef {
// Definition in here the same as the enum diesel::result::Error
// ...
}
Of course you would have to do the same for all of the types enclosed within the Error type as well (or at least any types that don't already implement Serialize).
The documentation states that Serde checks the definition you provide against the one in the 'remote' crate, and throws an error if they differ, which would help keep them in sync.
Also note that this does not result in diesel::result::Error implementing Serialize - rather you now have a stand-in type that you can use like this:
struct JsonErrorRespone {
pub success: bool,
#[serde(with = "ErrorDef")]
pub error: diesel::result::Error,
}
You would then serialize an instance of the above struct instead of your existing json! macro call.
Alternatively the document linked above also gives some tips for manually calling the correct Serialize / Deserialize implementations.
Disclaimer: I haven't used this facility yet, the above was gleaned from the documentation only.
The short answer: yes you can. Personally I've always found it a bit difficult.
As a compromise, you could pull out just the parts of the error that are relevant to you, or even do:
ApiResponse {
json: json!({"success": false, "error": error.to_string() }),
status: Status::UnprocessableEntity,
}
If you're content with just a textual representation of the error.