-
-
Notifications
You must be signed in to change notification settings - Fork 777
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow rename with str|bool|int type for internally tagged enums #1392
Conversation
This will enable enum variants to support in addition the type int and bool
…al and untagged enum
…nally tagged enums variants
Nice, this is on the right track. Regarding the test failure, you can use cargo-expand and the command |
That Lines 2244 to 2252 in 3f0f739
The #[derive(Deserialize)]
struct Demo {
a: A,
b: B,
#[serde(flatten)]
more: HashMap<Value, Value>,
} where any map entries that are not one of the strings |
Thx @dtolnay for your I set a unit test of a internally tagged enums with a lot of variant types to help me to work on this feature: test_internally_tagged_enum_renamed. #[serde(rename=1)]
F(BTreeMap<String, String>), Can you make a review of my deserialize_identifier ? I don't know if its the correct way to modify this method and I don't know if I have to deal with Could you tell me where I have to put the unit tests for this feature (I saw that you have |
@@ -21,6 +21,7 @@ | |||
extern crate syn; | |||
|
|||
extern crate proc_macro2; | |||
extern crate quote; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is an external crate quote
needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello 😄
The quote
is used in serde_derive/src/internals/attr.rs#L153 :
impl ToTokens for VariantNameType {
fn to_tokens(&self, out: &mut TokenStream) {
match *self {
VariantNameType::Str(ref s) => s.to_tokens(out),
VariantNameType::Bool(b) => {
b.to_tokens(out);
},
VariantNameType::Int(i) => {
i.to_tokens(out);
}
}
}
}
serde_derive_internal/
has a link to ./serde_derive/src/internals/
so if we need to build
serde_derive_internal
alone that will not compile without the extern crate quote;
. If the sub crate serde_derive_internal
is not meant to be built alone I'll remove this dependency.
Looks good so far! I tried this against serde_json using the following code, but got an error during deserialization. extern crate serde;
extern crate serde_derive;
extern crate serde_json;
use serde_derive::Deserialize;
#[derive(Deserialize, Debug)]
#[serde(tag = "success")]
enum Outcome {
#[serde(rename = true)]
Success,
#[serde(rename = false)]
Failure,
}
fn main() {
let j = r#" {"success":true} "#;
match serde_json::from_str::<Outcome>(j) {
Ok(outcome) => println!("{:?}", outcome),
Err(err) => eprintln!("{}", err),
}
} invalid type: boolean `true`, expected variant identifier at line 1 column 16 Some comments inline in the generated code: impl<'de> _serde::Deserialize<'de> for Outcome {
fn deserialize<__D>(__deserializer: __D) -> _serde::export::Result<Self, __D::Error>
where
__D: _serde::Deserializer<'de>,
{
#[allow(non_camel_case_types)]
enum __Field {
__field0,
__field1,
}
struct __FieldVisitor;
// I would omit visit_u64, visit_str, visit_bytes and only keep
// visit_bool. That way we get the Visitor trait's default invalid type
// error message, since those really correspond to an invalid type
// rather than an invalid value for the variant tag.
impl<'de> _serde::de::Visitor<'de> for __FieldVisitor {
type Value = __Field;
fn expecting(
&self,
__formatter: &mut _serde::export::Formatter,
) -> _serde::export::fmt::Result {
_serde::export::Formatter::write_str(__formatter, "variant identifier")
}
fn visit_u64<__E>(self, __value: u64) -> _serde::export::Result<Self::Value, __E>
where
__E: _serde::de::Error,
{
match __value {
_ => _serde::export::Err(_serde::de::Error::invalid_value(
_serde::de::Unexpected::Unsigned(__value),
&"variant index 0 <= i < 2",
)),
}
}
fn visit_bool<__E>(self, __value: bool) -> _serde::export::Result<Self::Value, __E>
where
__E: _serde::de::Error,
{
match __value {
true => _serde::export::Ok(__Field::__field0),
false => _serde::export::Ok(__Field::__field1),
// This pattern is unreachable and should be left out.
_ => {
let __value = &_serde::export::from_bool(__value);
_serde::export::Err(_serde::de::Error::unknown_variant(__value, VARIANTS))
}
}
}
fn visit_str<__E>(self, __value: &str) -> _serde::export::Result<Self::Value, __E>
where
__E: _serde::de::Error,
{
match __value {
_ => _serde::export::Err(_serde::de::Error::unknown_variant(__value, VARIANTS)),
}
}
fn visit_bytes<__E>(self, __value: &[u8]) -> _serde::export::Result<Self::Value, __E>
where
__E: _serde::de::Error,
{
match __value {
_ => {
let __value = &_serde::export::from_utf8_lossy(__value);
_serde::export::Err(_serde::de::Error::unknown_variant(__value, VARIANTS))
}
}
}
}
impl<'de> _serde::Deserialize<'de> for __Field {
#[inline]
fn deserialize<__D>(__deserializer: __D) -> _serde::export::Result<Self, __D::Error>
where
__D: _serde::Deserializer<'de>,
{
// This line is why the serde_json snippet above is failing. JSON
// represents identifiers as strings, so we are telling it to expect a
// string but the input contains a boolean. This line should say
// Deserializer::deserialize_bool. If the user's enum mixes different
// types of tags, this should say Deserializer::deserialize_any.
_serde::Deserializer::deserialize_identifier(__deserializer, __FieldVisitor)
}
}
const VARIANTS: &'static [&'static str] = &["true", "false"];
let __tagged = try!(_serde::Deserializer::deserialize_any(
__deserializer,
_serde::private::de::TaggedContentVisitor::<__Field>::new("success"),
));
match __tagged.tag {
__Field::__field0 => {
try!(_serde::Deserializer::deserialize_any(
_serde::private::de::ContentDeserializer::<__D::Error>::new(__tagged.content,),
_serde::private::de::InternallyTaggedUnitVisitor::new("Outcome", "Success"),
));
_serde::export::Ok(Outcome::Success)
}
__Field::__field1 => {
try!(_serde::Deserializer::deserialize_any(
_serde::private::de::ContentDeserializer::<__D::Error>::new(__tagged.content,),
_serde::private::de::InternallyTaggedUnitVisitor::new("Outcome", "Failure"),
));
_serde::export::Ok(Outcome::Failure)
}
}
}
} |
Where you currently have it is fine. Those files are a bit of a mess -- at some point we will need to tidy up and organize them in a more sensible way. |
…g deserialize_identifier
Generate the true|false arms to avoid pattern true|false not covered error when the enum has only true or false arms
} | ||
} | ||
|
||
fn visit_bytes<__E>(self, __value: &[u8]) -> _serde::export::Result<Self::Value, __E> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the visit_bytes
use the constructors_strs
too so I put them together.
We verify if the len > 0 to avoid a runtime failure
Hi @dtolnay 😃 , the code you tried with
I do not understand what My second point is on the behavior of the API. What have to be done when we have 2 variants with the same
For know, the build pass 😞 and the first variant is select all the time (A). If we allow only one |
In Serde, a field identifier is a value that can be used to distinguish fields. It's used for key-value based formats that might not keep the original order, like JSON.
The attribute-checking machinery resides in https://github.com/serde-rs/serde/blob/e1edb0282ad353fd5f4539d2456f3a0ad90f7e54/serde_derive/src/internals/attr.rs where Adding new checks is done by adding new match arms in |
So I don't understand why a structure like this one (from
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct S {
#[serde(flatten)]
x: X,
#[serde(flatten)]
y: Y,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(tag = "typeX")]
enum X {
A { a: i32 },
B { b: i32 },
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(tag = "typeY")]
enum Y {
C { c: i32 },
D { d: i32 },
} This is the list of tokens expected:
This tokens list match with the ouput of NOTE: All the tests that do not pass are related to the |
@hcpl would you be able to help get this reviewed and ready to land? |
This would be a handy feature! Any chance the PR could be revived? |
I need this quite badly (API I'm writing client for uses this pattern), badly enough to volunteer for updating this PR if it would mean it getting merged. Would just need guidance on what to do, since I don't have much in the way of practical Rust experience yet. |
@kpozin @jaen, I am still interested in landing this feature but I have been bottlenecked on review bandwidth for many months, and this feature has been relatively low value for how much review effort it demanded so I haven't been able to give it more attention. Here are some things that would help drive this forward:
|
@dtolnay I actually got the PR rebased (well, squashed and rebased, it was simpler that way) and compiling, but I had problems getting tests to work. I ended up writing the impl manually instead, because I discovered #1221 will be blocking me anyway. I can publish the rebased code somewhere and/or try to fix the tests in my spare time (not much of that though, considering it's not blocking me at work for now) if that helps. |
@jaen that's awesome -- could you share your rebased code in a new PR? It doesn't have to be perfect. |
@dtolnay It's what I have right now – https://github.com/jaen/serde/pull/1/files. It compiles, but has some issues with deserialization tests – as I understand the deserializer produces a stream of tokens that are used to validate the deserializations and they do not match. These are the failing tests I didn't have time to debug yet. |
Thanks! I copied it over to #1644 so we don't lose the work if you decide to delete your fork later. |
I spent a few hours doing this so I thought it may be useful to other people in the future since this PR has not received any modification for almost a year. I generated the code for a tagged enum and adapted it to work with integers (it can also be modified to work with booleans). However I'm using two structs from private API |
I think this PR is obsolete in favor of #2056 |
This is the WIP for #745 (my work is based on #973).
I have some troubles to understand what I have to do to complete the
serde_derive/de.rs
andserde_derive/se.rs
modifications. I don't understand what I've to do in the method deserialize_identifier: serde_derive/src/de.rs#L2016 and if I have to modify otherdeserialize_*
methods inserde_derive/de.rs
. Does theserde_derive/de.rs
part seem correct ?For now the unit test doesn't compile and I get this error. Do I have to modify something in serde_derive/src/internals/ast.rs to pass the compilation ?