untagged enums

This commit is contained in:
Robin Appelman 2023-12-18 18:06:19 +01:00
commit 08e7d35905
5 changed files with 99 additions and 9 deletions

View file

@ -3,3 +3,42 @@
A parser for Valve's Data Format v1 (VDF) also known as [KeyValues](https://developer.valvesoftware.com/wiki/KeyValues). A parser for Valve's Data Format v1 (VDF) also known as [KeyValues](https://developer.valvesoftware.com/wiki/KeyValues).
The parser focuses on being able to deal with all the various weird forms vdf takes in the wild and providing access to the data stream instead of always requiring parsing the file in full. The parser focuses on being able to deal with all the various weird forms vdf takes in the wild and providing access to the data stream instead of always requiring parsing the file in full.
## Serde
This crate implements a deserializer for serde, but because VDF doesn't map that well only the serde data model not every type might deserialize properly.
### Limitations
- Because the boolean values `0` and `1` can't be distinguished from numbers, it is not possible to use booleans in untagged enums.
### Tagged enum root
To help deserialize some common vdf formats, you can use a tagged enum as the root element instead of a struct.
```vdf
"Variant1" {
content 1
}
```
or
```vdf
"Variant2" {
other foo
}
```
can be deserialized into a
```rust
enum Data {
Variant1 {
content: bool,
},
Variant2 {
other: String,
}
}
```

View file

@ -94,14 +94,36 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
V: Visitor<'de>, V: Visitor<'de>,
{ {
let source = self.source(); let source = self.source();
let peek = self.peek().expect_token(VALUE_TOKEN, source)?; let token = self.next().expect_token(VALUE_TOKEN, source)?;
match peek.token { let span = token.span.clone();
Token::Item | Token::QuotedItem | Token::Statement | Token::QuotedStatement => self match token.token {
.deserialize_str(visitor) Token::Item | Token::QuotedItem | Token::Statement | Token::QuotedStatement => {
.ensure_span(peek.span, self.source()), let str = token.string(self.source());
Token::GroupStart => self // note: we don't check for bool as we can't distinguish those from numbers
.deserialize_map(visitor) if let Ok(int) = i64::from_str(str.as_ref()) {
.ensure_span(peek.span, self.source()), return visitor.visit_i64(int).ensure_span(span, self.source());
}
if let Ok(float) = f64::from_str(str.as_ref()) {
return visitor.visit_f64(float).ensure_span(span, self.source());
}
if str.starts_with('[') && str.ends_with(']') {
self.push_peeked(token);
return self
.deserialize_seq(visitor)
.ensure_span(span, self.source());
}
match str {
Cow::Borrowed(str) => visitor
.visit_borrowed_str(str)
.ensure_span(span, self.source()),
Cow::Owned(str) => visitor.visit_string(str).ensure_span(span, self.source()),
}
}
Token::GroupStart => {
self.push_peeked(token);
self.deserialize_map(visitor)
.ensure_span(span, self.source())
}
_ => unreachable!(), _ => unreachable!(),
} }
} }
@ -780,4 +802,17 @@ mod tests {
let expected = E::Struct2 { a: 1 }; let expected = E::Struct2 { a: 1 };
assert_eq!(expected, unwrap_err(from_str(j))); assert_eq!(expected, unwrap_err(from_str(j)));
} }
#[test]
fn test_untagged_enum() {
#[derive(Deserialize, PartialEq, Debug)]
#[serde(untagged)]
enum E {
Int(u8),
Float(f32),
}
let j = r#"1.1"#;
assert_eq!(E::Float(1.1), unwrap_err(from_str(j)));
}
} }

View file

@ -2,4 +2,7 @@
fixed_array "[1 2 3]" fixed_array "[1 2 3]"
flex_array "[1.0 2.2]" flex_array "[1.0 2.2]"
tuple "[1 57]" tuple "[1 57]"
single 1.2
triple "[1.2 1.3 1.4]"
single_int 2
} }

View file

@ -11,6 +11,9 @@ enum Expected {
fixed_array: [u8; 3], fixed_array: [u8; 3],
flex_array: Vec<f32>, flex_array: Vec<f32>,
tuple: (bool, u8), tuple: (bool, u8),
single: SingleOrTriple<f32>,
triple: SingleOrTriple<f32>,
single_int: SingleOrTriple<f32>,
}, },
LightmappedGeneric { LightmappedGeneric {
#[serde(rename = "$baseTexture")] #[serde(rename = "$baseTexture")]
@ -52,6 +55,13 @@ enum Expected {
}, },
} }
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum SingleOrTriple<T> {
Single(T),
Triple([T; 3]),
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct UserConfigDataSteam { struct UserConfigDataSteam {
cached: UserConfigDataSteamCached, cached: UserConfigDataSteamCached,

View file

@ -9,4 +9,7 @@ Types(
2.2, 2.2,
], ],
tuple: (true, 57), tuple: (true, 57),
single: 1.2,
triple: (1.2, 1.3, 1.4),
single_int: 2.0,
) )