问题
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