164 lines
4.4 KiB
Markdown
164 lines
4.4 KiB
Markdown
## My issue
|
||
|
||
I want to deserialize data into Rust types using serde.
|
||
The data format already exists and I want to remain compatible. It is currently JSON, but I want to specify deserializers independently of the format (so that later I can use eg. YAML).
|
||
|
||
My file format is documented here: https://yalis.fr/git/yves/pyruse/src/branch/master/doc/conffile.md
|
||
|
||
In module `common`, I have:
|
||
|
||
```rust
|
||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||
pub enum Value {
|
||
Bool(bool),
|
||
Str(String),
|
||
Int(isize),
|
||
Date(DateTime<chrono::Utc>),
|
||
Map(HashMap<String, Value>),
|
||
List(Vec<Value>)
|
||
}
|
||
```
|
||
|
||
NOTE: No data from the file will ever become a `Date` variant.
|
||
|
||
In module `modules`, I have:
|
||
|
||
```rust
|
||
pub type ModuleArgs = HashMap<String, Value>;
|
||
```
|
||
|
||
The deserialization process is programmed in the `config` module. Here is what I first described:
|
||
|
||
```rust
|
||
pub struct Config {
|
||
actions: Vec<Chain>,
|
||
options: HashMap<String, Value>
|
||
}
|
||
|
||
pub struct Chain {
|
||
name: String,
|
||
steps: Vec<Step>
|
||
}
|
||
|
||
pub struct Step {
|
||
module_name: String,
|
||
args: ModuleArgs,
|
||
then_dest: Option<String>,
|
||
else_dest: Option<String>
|
||
}
|
||
```
|
||
|
||
(Subsidiary question: If `args` above is not specified in the file, is it OK to want the `args` field to be an empty Hashmap, or is it worth the extra work to make it an `Option<ModuleArgs>`?)
|
||
|
||
I tried to follow the documentation, and misc. web pages, and ended up with that, so far:
|
||
|
||
```rust
|
||
use serde::de::{self,Deserializer,MapAccess,SeqAccess,Visitor};
|
||
use serde::Deserialize;
|
||
use std::collections::HashMap;
|
||
use std::fmt;
|
||
use crate::common::Value;
|
||
use crate::modules::ModuleArgs;
|
||
|
||
#[derive(Deserialize)]
|
||
pub struct Config {
|
||
actions: Vec<Chain>,
|
||
|
||
#[serde(flatten)]
|
||
options: HashMap<String, Value>
|
||
}
|
||
|
||
pub struct Chain {
|
||
name: String,
|
||
steps: Vec<Step>
|
||
}
|
||
|
||
pub struct Step {
|
||
module_name: String,
|
||
args: ModuleArgs,
|
||
then_dest: Option<String>,
|
||
else_dest: Option<String>
|
||
}
|
||
|
||
struct ValueVisitor;
|
||
|
||
impl<'de> Visitor<'de> for ValueVisitor {
|
||
type Value = Value;
|
||
|
||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||
formatter.write_str("a boolean, string, integer, array or object")
|
||
}
|
||
|
||
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> where E: de::Error {
|
||
Ok(Value::Bool(v))
|
||
}
|
||
|
||
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E> where E: de::Error {
|
||
Ok(Value::Int(v as isize))
|
||
}
|
||
|
||
// … and so on …
|
||
|
||
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E> where E: de::Error {
|
||
Ok(Value::Int(v as isize))
|
||
}
|
||
|
||
// … and so on …
|
||
|
||
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E> where E: de::Error {
|
||
Ok(Value::Int(v as isize))
|
||
}
|
||
|
||
// … and so on …
|
||
|
||
fn visit_char<E>(self, v: char) -> Result<Self::Value, E> where E: de::Error {
|
||
Ok(Value::Str(v.to_string()))
|
||
}
|
||
|
||
// … and so on …
|
||
|
||
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> where E: de::Error {
|
||
Ok(Value::Str(std::str::from_utf8(v).expect("Strings in the configuration must be UTF-8").to_string()))
|
||
}
|
||
|
||
// … and so on …
|
||
|
||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error> where A: SeqAccess<'de> {
|
||
let list = Vec::with_capacity(seq.size_hint().unwrap_or(0));
|
||
while let Some(v) = seq.next_element()? {
|
||
list.append(v);
|
||
}
|
||
Ok(Value::List(list))
|
||
}
|
||
|
||
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error> where A: MapAccess<'de> {
|
||
let map = HashMap::with_capacity(map.size_hint().unwrap_or(0));
|
||
while let Some((k, v)) = map.next_entry()? {
|
||
map.insert(k, v);
|
||
}
|
||
Ok(Value::Map(map))
|
||
}
|
||
}
|
||
|
||
impl<'de> Deserialize<'de> for Value {
|
||
fn deserialize<D>(deserializer: D) -> Result<Value, D::Error> where D: Deserializer<'de> {
|
||
deserializer.deserialize_any(ValueVisitor)
|
||
}
|
||
}
|
||
```
|
||
|
||
And here, I’m stuck…
|
||
|
||
1. Both recursive visitors (`visit_seq` and `visit_map`) fail to compile. Basically, I just cannot find anywhere on the Internet how to deserialize a sub-structure from a Visitor…
|
||
|
||
Here is the documentation I found, where I try to find inspiration:
|
||
|
||
* https://serde.rs/impl-deserialize.html (obviously)
|
||
* https://serde.rs/enum-representations.html
|
||
* https://github.com/serde-rs/json/issues/144
|
||
* https://github.com/serde-rs/serde/issues/1470
|
||
* https://github.com/serde-rs/serde/issues/1131
|
||
* https://github.com/serde-rs/serde/issues/1364
|
||
* https://stackoverflow.com/a/48293152/6751571
|
||
* https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=31881ff3984a8600861f2f4ee32faf23 (Thank you GreenJello)
|