Deserialize TOML string to enum using config-rs

倖福魔咒の 提交于 2019-12-10 18:54:37

问题


I'm using config-rs to load the configuration from a TOML file and I want to deserialize a string to an enum. I tried to solve it using the deserialize_with feature of serde_derive but I don't know how to return a suitable Error to satisfy the function signature. How can I achieve it?

My dependencies:

config = "0.7"
serde_derive = "1.0"
serde = "1.0"
toml = "0.4"

Example code where it's intended to deserialize the enum RotationPolicyType:

extern crate config;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate toml;

use std::env;
use config::{Config, Environment, File};
use std::path::PathBuf;
use serde;
use serde::de::Deserializer;
use serde::Deserialize;

#[derive(Debug, Deserialize, Clone)]
pub enum RotationPolicyType {
    ByDuration,
    ByDay,
}

#[derive(Debug, Deserialize, Clone)]
pub struct FileConfig {
    pub rotations: i32,
    #[serde(deserialize_with="deserialize_with")]
    pub rotation_policy_type: RotationPolicyType,
}

#[derive(Debug, Deserialize, Clone)]
pub struct Settings {
    pub threads: usize,
    pub file_writer: FileConfig,
}

impl Settings {
    pub fn new() -> Self {
        let mut s = Config::new();
        s.merge(File::with_name("config/default")).unwrap();
        s.merge(File::with_name("config/local").required(false))
            .unwrap();
        s.merge(Environment::with_prefix("app")).unwrap();
        s.deserialize().unwrap()
    }
}


fn deserialize_with<D>(deserializer: D) -> Result<RotationPolicyType, D::Error> where D: Deserializer {
    let s = String::deserialize(deserializer)?;

    match s.as_ref() {
        "ByDuration" => Ok(RotationPolicyType::ByDuration),
        "ByDay" => Ok(RotationPolicyType::ByDay),
        _ => Err(serde::de::Error::custom("error trying to deserialize rotation policy config"))
    }
}



fn deserialize_with2<'de, D>(deserializer: &'de mut D) -> Result<RotationPolicyType, D::Error> where &'de mut D: Deserializer<'de> {
    let s = String::deserialize(deserializer)?;

    match s.as_ref() {
        "ByDuration" => Ok(RotationPolicyType::ByDuration),
        "ByDay" => Ok(RotationPolicyType::ByDay),
        _ => Err(serde::de::Error::custom("error trying to deserialize rotation policy config"))
    }
}

Compilation error with deserialize_with:

error[E0106]: missing lifetime specifier
  --> src/settings.rs:30:94
   |
30 |     fn deserialize_with<D>(deserializer: D) -> Result<RotationPolicyType, D::Error> where D: Deserializer {
   |                                                                                              ^^^^^^^^^^^^ expected lifetime parameter

error: aborting due to previous error

Compilation error with deserialize_with2:

error[E0220]: associated type `Error` not found for `D`
  --> src/settings.rs:42:90
   |
42 |     fn deserialize_with2<'de, D>(deserializer: &'de mut D) -> Result<RotationPolicyType, D::Error> where &'de mut D: Deserializer<'de> {
   |                                                                                          ^^^^^^^^ associated type `Error` not found

error: aborting due to previous error

回答1:


First of all, your example does not compile far enough to get to the errors you are describing. Please take care to produce an MCVE next time. Bonus points for getting it to work on https://play.rust-lang.org (which is possible, the extern crate config is entirely unnecessary in your example).

After fixing up all the compilation issues, your first error is simply fixed by changing the function API to match the one suggested in the serde docs

-fn deserialize_with<     D>(deserializer: D) -> Result<RotationPolicyType, D::Error> where D: Deserializer
+fn deserialize_with<'de, D>(deserializer: D) -> Result<RotationPolicyType, D::Error> where D: Deserializer<'de>

The error tried to help you there. It told you that Deserializer is missing a lifetime parameter.

The second function is telling you that D has no associated type Error. Which it can only have if D would implement Deserializer<'de>. But you specified that &'de mut D implements Deserializer<'de>. Finding the solution to this problem is left as an exercise to the reader.




回答2:


Following the advice of @oli-obk-ker the solution was quite simple:

use std::env;
use config::{Config, File, Environment};
use std::path::PathBuf;
use serde;
use serde::de::Deserializer;
use serde::Deserialize;

pub trait DeserializeWith: Sized {
    fn deserialize_with<'de, D>(de: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>;
}

#[derive(Debug, Deserialize, Eq, PartialEq, Clone)]
pub enum RotationPolicyType {
    ByDuration,
    ByDay
}

impl DeserializeWith for RotationPolicyType {
    fn deserialize_with<'de, D>(de: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
        let s = String::deserialize(de)?;

        match s.as_ref() {
            "ByDuration" => Ok(RotationPolicyType::ByDuration),
            "ByDay" => Ok(RotationPolicyType::ByDay),
            _ => Err(serde::de::Error::custom("error trying to deserialize rotation policy config"))
        }
    }
}

#[derive(Debug, Deserialize, Clone)]
pub struct FileConfig {
    pub rotations: i32,
    #[serde(deserialize_with="RotationPolicyType::deserialize_with")]
    pub rotation_policy_type: RotationPolicyType,
}

#[derive(Debug, Deserialize, Clone)]
pub struct Settings {
    pub threads: i32,
    pub file_writer: FileConfig,
}

impl Settings {
    pub fn new() -> Self {
        let mut s = Config::new();
        s.merge(File::with_name("config/default")).unwrap();
        s.merge(File::with_name("config/local").required(false)).unwrap();
        s.merge(Environment::with_prefix("app")).unwrap();
        s.deserialize().unwrap()
    }
}


来源:https://stackoverflow.com/questions/47785720/deserialize-toml-string-to-enum-using-config-rs

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!