Search code examples
rustserdetoml

Deserialize TOML string to enum using config-rs


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

Solution

  • 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.