help-rust/README.md

4.4 KiB
Raw Permalink Blame History

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:

#[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:

pub type ModuleArgs = HashMap<String, Value>;

The deserialization process is programmed in the config module. Here is what I first described:

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:

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, Im 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: