Как сделать поле config.rs, которое можно десериализовать в один из нескольких типов? - PullRequest
0 голосов
/ 29 сентября 2018

Я использую config в качестве средства для загрузки внешних данных в мою программу, которая использует serde в фоновом режиме для десериализации, но мне нужна возможность, чтобы конкретное поле могло бытьодин из нескольких типов.Поскольку я совершенно новичок в Rust, найденная мною документация не имеет большого смысла.

Как сделать так, чтобы initial_value мог быть одного из нескольких типов:

  • String
  • i32
  • bool
  • [i32,i32]

Я понимаю, что могу использовать атрибут deserialize_with, чтобы перейти к пользовательскому декодеру, но за этим я немного растерялся.

Вот мой код:

use std::collections::HashMap;
use std::fs;

use config_rs::{Config, ConfigError, File};

#[derive(Debug, Deserialize)]
enum InitialValues {
    String,
    i32,
    bool,
}

#[derive(Debug, Deserialize)]
struct Component {
    component: String,
    initial_value: InitialValues,
}

#[derive(Debug, Deserialize)]
struct Template {
    feature_packs: Vec<String>,
    components: Vec<Component>,
}

type Name = String;
type Type = String;

#[derive(Debug, Deserialize)]
pub struct Templates {
    templates: HashMap<Name, Template>,
}

impl Templates {
    pub fn new(file: &str) -> Result<Self, ConfigError> {
        let mut templates = Config::new();
        templates.merge(File::with_name(file)).unwrap();
        templates.try_into()
    }
}

#[derive(Debug)]
pub struct Manager {
    pub templates: HashMap<Type, Templates>,
}

impl Manager {
    pub fn new(dir: &str) -> Self {
        let mut manager = Self {
            templates: HashMap::new(),
        };
        'paths: for raw_path in fs::read_dir(dir).unwrap() {
            let path = raw_path.unwrap().path();
            let file_path = path.clone();
            let type_name = path.clone();
            let templates = match Templates::new(type_name.to_str().unwrap()) {
                Ok(templates) => templates,
                Err(message) => {
                    println!("TemplateManager: file({:?}), {}", &file_path, message);
                    continue 'paths;
                }
            };
            manager.templates.insert(
                file_path.file_stem().unwrap().to_str().unwrap().to_owned(),
                templates,
            );
        }
        manager
    }
}

Пример файла конфигурации:

---
templates:
  orc:
    feature_packs:
      - physical
      - basic_identifiers_mob
    components:
      - component: char
        initial_value: T
  goblin:
    feature_packs:
      - physical
      - basic_identifiers_mob
    components:
      - component: char
        initial_value: t

Ответы [ 2 ]

0 голосов
/ 29 сентября 2018

Я бы десериализовал приведенный вами пример конфигурации следующим образом.


const Y: &str = r#"
---
orc:
  feature_packs:
    - physical
    - basic_identifiers_mob
  components:
    - component: char
      initial_value: T
goblin:
  feature_packs:
    - physical
    - basic_identifiers_mob
  components:
    - component: char
      initial_value: t
"#;

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_yaml;

use std::collections::HashMap as Map;

type Templates = Map<String, Template>;

#[derive(Deserialize, Debug)]
struct Template {
    feature_packs: Vec<String>,
    components: Vec<Component>,
}

#[derive(Deserialize, Debug)]
#[serde(
    tag = "component",
    content = "initial_value",
    rename_all = "lowercase",
)]
enum Component {
    Char(char),
    String(String),
    Int(i32),
    Float(f32),
    Bool(bool),
    Range(Range<i32>),
}

#[derive(Deserialize, Debug)]
struct Range<T> {
    start: T,
    end: T,
}

fn main() {
    println!("{:#?}", serde_yaml::from_str::<Templates>(Y).unwrap());
}
0 голосов
/ 29 сентября 2018

Изменение InitialValues enum на:

#[derive(Debug, Deserialize)]
#[serde(
    rename_all = "lowercase",
    untagged
)]
pub enum InitialValue {
    Char(char),
    String(String),
    Int(i32),
    Float(f32),
    Bool(bool),
    Range(Range<i32>)
}

Означает, что теперь работает следующее:

---
example:
  feature_packs:
    - example
    - example
  components:
    - component: char
      initial_value: T
    - component: string
      initial_value: 'asdasdasd'
    - component: int
      initial_value: 2
    - component: float
      initial_value: 3.2
    - component: bool
      initial_value: true
    - component: range
      initial_value:
        start: 0
        end: 9

Это немного более многословно, чем хотелось бы, нопо крайней мере, это выполнимо.


РЕДАКТИРОВАТЬ: Благодаря ответу @dtolnay я смог улучшить этот вопрос, и теперь он читает данные в точности так, как я изначально планировал!

Код выше ужебыл изменен, чтобы отразить это.


Кроме того, #[serde(flatten)] до Templates примерно так:

#[derive(Debug, Deserialize)]
pub struct Templates {
    #[serde(flatten)]
    templates: HashMap<Name,Template>,
}

Мне удалось убрать необходимость добавить templates: вначало файла.

...