How to remove a struct's field? - rust

I make a request to a CouchDB database and get a response. Here's the struct for it:
use couch_rs::{types::{document::DocumentId}, CouchDocument, error::CouchResult};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, CouchDocument, Default, Debug)]
pub struct GuildEntry {
#[serde(skip_serializing_if = "String::is_empty")]
pub _id: DocumentId,
#[serde(skip_serializing_if = "String::is_empty")]
pub _rev: String,
pub embedColor: String
}
I want to remove _id and _rev fields, or at least create a struct instance with all of the fields, except for those two. I can't afford creating a new struct without those fields and transform it from the response, because there's going to be a lot of fields in the future.
What have I tried/looked into so far:
I've looked into utilizing conversion to serde_json Value and then back, but I'll need to declare 2 identical structs, but with one has 2 fields missing.
I've also seen people mentioning composition, but it's kinda ugly and I'd like to avoid using it.
I can't use Option because CouchDocument requires that _id and _rev must be String and not Option<String>.
How can I accomplish this? Is this even possible?

Composition is really the only way. Though you don't have to make two separate structures for each; you can use one generic structure:
extern crate serde;
#[derive(serde::Deserialize)]
struct WithID<T> {
id: String,
#[serde(flatten)]
inner: T,
}
#[derive(serde::Deserialize)]
struct Foo {
bar: String,
baz: String,
}
type FooWithId = WithID<Foo>;
Implementing Deref and DerefMut for WithID with Target = T would make things easier, as you won't have to reference inner as much.

Related

How to create an arbitrary HashMap to use in rust rocket for a web API

I try to make a web API with rocket to try out the framework. I managed to return paginated results with a special struct that implements serializable.
However, the API I try to build depends on arbitrary values in a special dictionary. The received values may be strings, integers, bools, or other complex objects. The problem now is, that I'm not able to create a struct that contains "any" since Any is not serializable.
The basic idea would be something like this:
#[derive(Debug, Serialize, Deserialize)]
pub struct Foobar<'a> {
pub id: Uuid,
pub data: HashMap<&'a str, ??????>,
}
Even with enums, the problem remains since there is an infinite count of variations. Let's say, I use an enum to determine strings, bools, integers. When the containing type is another type, I need the json representation of that specific type. Basically another map with string -> any.
The current idea would be to use:
#[derive(Debug, Serialize, Deserialize)]
pub struct Foobar {
pub id: Uuid,
pub data: HashMap<String, rocket::serde::json::Value>,
}
But I don't know how the API will fare when there are non json values (e.g. msgpack).
Has somebody accomplished such a feat with rust/rocket?
After several tries with traits and std::any::Any, I ended up with:
pub type DataObject = HashMap<String, DataValue>;
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub enum DataValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Date(String),
Time(String),
DateTime(String),
Reference(DataObject),
ArrayReference(Vec<DataObject>),
}
This is - more or less - the same as serde_json::Value. It helps to further check the values and validate them against a possible type description of the objects.

How to use enums as a query parameter with Axum

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.

Best way to populate a struct from a similar struct?

I am new to Rust, and am attempting to take a struct returned from a library (referred to as source struct) and convert it into protobuf message using prost. The goal is to take the source struct, map the source struct types to the protobuf message types (or rather, the appropriate types for prost-generated struct, referred to as message struct), and populate the message struct fields using fields from the source struct. The source struct fields are a subset of message struct fields. For example:
pub struct Message {
pub id: i32,
pub email: String,
pub name: String,
}
pub struct Source {
pub email: Email,
pub name: Name,
}
So, I would like to take fields from from Source, map the types to corresponding types in Message, and populate the fields of Message using Source (fields have the same name). Currently, I am manually assigning the values by creating a Message struct and doing something like message_struct.email = source_struct.email.to_string();. Except I have multiple Message structs based on protobuf, some having 20+ fields, so I'm really hoping to find a more efficient way of doing this.
If I understand you correctly you want to generate define new struct based on fields from another. In that case you have to use macros.
https://doc.rust-lang.org/book/ch19-06-macros.html
Also this question (and answer) could be useful for you Is it possible to generate a struct with a macro?
To convert struct values from one to another struct best way is to use From<T> or Into<T> trait.
https://doc.rust-lang.org/std/convert/trait.From.html
This is called FRU (functional record update).
This currently works only for structs with the same type and structs with the same type modulo generic parameters.
The RFC-2528 talks about a generalization that would make this work:
struct Foo {
field1: &'static str,
field2: i32,
}
struct Bar {
field1: f64,
field2: i32,
}
let foo = Foo { field1: "hi", field2: 1 };
let bar = Bar { field1: 3.14, ..foo };
Unfortunately, this has not yet been implemented.
You could make a method to create a Message from a Source like this.
impl Message {
pub fn from_source(source: &Source, id: i32) -> Self {
Message {
id: id,
email: source.email.to_string(),
name: source.name.to_string(),
}
}
}
And then,
let source = // todo
let id = // todo
let message = Message::from_source(&source, id);

How to only allow one field or the other with Serde?

Say I have this struct:
use serde::{Serialize, Deserialize};
#[derive(Deserialize)]
struct MyStruct {
field_1: Option<usize>, // should only have field_1 or field_2
field_2: Option<usize>, // should only have field_1 or field_2
other_field: String,
}
How can I deserialize this but only allow one of these fields to exist?
The suggestions in the comments to use an enum are likely your best bet. You don't need to replace your struct with an enum, instead you'd add a separate enum type to represent this constraint, e.g.:
use serde::{Serialize, Deserialize};
#[derive(Deserialize)]
enum OneOf {
F1(usize), F2(usize)
}
#[derive(Deserialize)]
struct MyStruct {
one_of_field: OneOf,
other_field: String,
}
Now MyStruct's one_of_field can be initialized with either an F1 or an F2.
#dimo414's answer is correct about the enum being necessary, but the code sample will not function in the way the question is described. This is caused by a couple factors related to how enums are deserialized. Mainly, it will not enforce mutual exclusion of the two variants and will silently pick the first variant that matches and ignore extraneous fields. Another issue is the enum will be treated as a separate structure within MyStruct (Ex: {"one_of_field":{"F1":123},"other_field":"abc"} in JSON).
Solution
For anyone wanting an easy solution, here it is. However keep in mind that variants of the mutually exclusive type can not contain #[serde(flatten)] fields (more information in the issue section). To accommodate neither field_1 or field_2, Option<MutuallyExclusive> can be used in MyStruct.
/// Enum containing mutually exclusive fields. Variants names will be used as the
/// names of fields unless annotated with `#[serde(rename = "field_name")]`.
#[derive(Deserialize)]
enum MutuallyExclusive {
field_1(usize),
field_2(usize),
}
#[derive(Deserialize)]
/// `deny_unknown_fields` is required. If not included, it will not error when both
/// `field_1` and `field_2` are both present.
#[serde(deny_unknown_fields)]
struct MyStruct {
/// Flatten makes it so the variants of MutuallyExclusive are seen as fields of
/// this struct. Without it, foo would be treated as a separate struct/object held
/// within this struct.
#[serde(flatten)]
foo: MutuallyExclusive,
other_field: String,
}
The Issue
TL;DR: It should be fine to use deny_unknown_fields with flatten in this way so long as types used in MutuallyExclusive do not use flatten.
If you read the serde documentation, you may notice it warns that using deny_unknown_fields in conjunction with flatten is unsupported. This is problematic as it throws the long-term reliability of the above code into question. As of writing this, serde will not produce any errors or warnings about this configuration and is able to handle it as intended.
The pull request adding this warning cited 3 issues when doing so:
#[serde(flatten)] error behavior is different to normal one
deny_unknown_fields incorrectly fails with flattened untagged enum
Structs with nested flattens cannot be deserialized if deny_unknown_fields is set
To be honest, I don't really care about the first one. I personally feel it is a bit overly pedantic. It simply states that the error message is not exactly identical between a type and a wrapper for that type using flatten for errors triggered by deny_unknown_fields. This should not have any effect on the functionality of the code above.
However, the other two errors are relevant. They both relate to nested flatten types within a deny_unknown_fields type. Technically the second issue uses untagged for the second layer of nesting, but it has the same effect as flatten in this context. The main idea is that deny_unknown_fields is unable to handle more than a single level of nesting without causing issues. The use case is in any way at fault, but the way deny_unknown_fields and flattened are handled makes it difficult to implement a workaround.
Alternative
However, if anyone still feels uncomfortable with using the above code, you can use this version instead. It will be a pain to work with if there are a lot of other fields, but sidesteps the warning in the documentation.
#[derive(Debug, Deserialize)]
#[serde(untagged, deny_unknown_fields)]
enum MyStruct {
WithField1 {
field_1: usize,
other_field: String,
},
WithField2 {
field_2: usize,
other_field: String,
},
}
You can deserialize your struct and then verify that all the invariants your type should uphold. You can implement Deserialize for your type to this while also relying on the derive macro to do the heavy lifting.
use serde::{Deserialize, Deserializer};
#[derive(Debug, Deserialize)]
#[serde(remote = "Self")]
struct MyStruct {
field_1: Option<usize>, // should only have field_1 or field_2
field_2: Option<usize>, // should only have field_1 or field_2
other_field: String,
}
impl<'de> Deserialize<'de> for MyStruct {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use serde::de::Error;
let s = Self::deserialize(deserializer)?;
if s.field_1.is_some() && s.field_2.is_some() {
return Err(D::Error::custom("should only have field_1 or field_2"));
}
Ok(s)
}
}
fn main() -> () {
dbg!(serde_json::from_value::<MyStruct>(serde_json::json!({
"field_1": 123,
"other_field": "abc"
})));
dbg!(serde_json::from_value::<MyStruct>(serde_json::json!({
"field_2": 456,
"other_field": "abc"
})));
dbg!(serde_json::from_value::<MyStruct>(serde_json::json!({
"field_1": 123,
"field_2": 456,
"other_field": "abc"
})));
}
Playground

Implement convert From trait with additional argument

I have a situation where I have 2 structs
struct A {
pub one: number;
pub two: number;
}
struct AWithThree {
pub one: number;
pub two: number;
pub three: number;
}
And I want to often convert A structs into AWithThree structs after calculating the three field. Note that in my actual code, both these structs have many more fields than shown here.
It becomes tedious though to always manually type each field that I need in the conversion in a function that takes in an A struct and return an AWithThree struct.
AWithThree {
one: a.one,
two: a.two,
three: calculateThree(a),
}
I'd ideally like to be able to just do
AWithThree {
..a,
three: calculateThree(a),
}
This is something that Typescript supports which makes struct mappings really easy since the required fields are automatically checked. But it seems that Rust doesn't support this.
So that's why I turned to implementing the From trait, but the required method of the From trait is only allowed to take in the type we're converting from
fn from(T) -> Self
I instead though wanted to be able to do
fn from(T, additional_arg: S) -> Self
where in my example above, additional_arg would be the value for three that I would then use to constuct the AWithThree struct from the A struct. I don't want to calculate the value of three inside the From since I want the calling function to pass it in.
Is there a better way to do easy struct conversion in Rust?

Resources