具有可选边界的自定义反序列化器
Custom Deserializer with Optional Bounds
我正在尝试获取 Rocket + Tracing library stack working. Rocket comes packaged with Figment, which allows the use of Serde 来创建特定于应用程序的配置文件。
使用此示例,我创建了一个通用 de/serializer 以允许我的 (yaml) 配置将 log_level: "debug"
等字符串映射到跟踪库日志 Level
。下面的代码实现这个工作正常。但是,如果修改配置文件以删除 log_level
,则会由于以下错误而中断:
thread 'main' panicked at 'Broken!: Error { tag: Tag(Default, 5), profile: Some(Profile(Uncased { string: "default" })), metadata: Some(Metadata { name: "Rocket Config", source: None, provide_location: Some(Location { file: "src/main.rs", line: 46, col: 18 }), interpolater: }), path: ["log_level"], kind: Message("error parsing level: expected one of \"error\", \"warn\", \"info\", \"debug\", \"trace\", or a number 1-5"), prev: None }', src/main.rs:53:47
这个错误清楚地表明 None
无法解析为 Level
。并不出乎意料,但我通常解决这个问题的方法是改变 serde 结构,将值更改为 Option
。在这种情况下,我收到以下错误:
error[E0277]: the trait bound `std::option::Option<tracing::Level>: FromStr` is not satisfied
--> src/main.rs:32:17
|
12 | pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
| ----------- required by a bound in this
...
15 | T: std::str::FromStr,
| ----------------- required by this bound in `from_string::deserialize`
...
32 | #[derive(Debug, Deserialize, Serialize)]
| ^^^^^^^^^^^ the trait `FromStr` is not implemented for `std::option::Option<tracing::Level>`
我不知道在这里做什么。我不认为我很明白为什么不满足这里的界限。我在其他配置代码中使用了 Option
,但我认为自定义反序列化器中的类型有一个微妙的地方我不明白。
这是我的 src/main.rs
文件:
#[macro_use]
extern crate rocket;
use rocket::figment::{Figment, providers::{Format, Yaml}};
use serde::{Deserialize, Serialize};
use tracing::Level;
pub mod from_string {
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: std::str::FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
String::deserialize(deserializer)?
.parse::<T>()
.map_err(|e| D::Error::custom(format!("{}", e)))
}
pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: std::fmt::Display,
{
format!("{}", value).serialize(serializer)
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
#[serde(with = "from_string")]
pub log_level: Level,
// This is the version that ERRORS.
// #[serde(with = "from_string")]
// pub log_level: Option<Level>,
pub name: String,
}
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
#[launch]
fn rocket() -> _ {
let config = Figment::from(rocket::Config::default())
.merge(Yaml::file("config.yaml").nested());
let default_config = config.select("default");
let cfg: Config = default_config.extract().expect("Broken!");
println!("Config name: {:#?}", cfg.name);
println!("Log level: {:#?}", cfg.log_level);
rocket::custom(default_config).mount("/", routes![index])
}
这是我的 Cargo.toml
文件:
[package]
name = "scratch-rust"
version = "0.1.0"
edition = "2018"
[dependencies]
figment = { version = "*", features = ["env", "toml", "json", "yaml"] }
rocket = "0.5.0-rc.1"
serde="*"
tracing="*"
这是一个示例 config.yaml
文件:
default:
# Remove the next line to see the 'None' error.
log_level: "debug"
name: "dev"
production:
name: "production"
使用这段代码,一个干净的开始给出了这个:
$ cargo run 17s nford 20:19:31
Compiling scratch-rust v0.1.0 (/home/nford/repos/scratch-rust/tracing-serde-bridge)
Finished dev [unoptimized + debuginfo] target(s) in 3.41s
Running `target/debug/scratch-rust`
Config name: "dev"
Log level: Level(
Debug,
)
Configured for default.
>> address: 127.0.0.1
...more startup logs
我希望看到这样的东西,除了 Level(Debug)
我希望看到:
Log level: None
任何人都可以帮助我了解打字界限是怎么回事,我应该如何解决它们?
(n.b。我只标记这个 Serde
和 Rust
因为,虽然我把它嵌入到一个比那个多一点的堆栈中,但我认为这个实际上是一个 'how do I properly write a custom deserializer for Serde' 问题。)
您的 from_string
模块只支持带有 T: std::str::FromStr,
的值。 Option
从不实现 FromStr
特性,即使内部值实现了它也不行。
有多种解决方法。
您可以将 from_string
模块更改为 return a Result<Option<T>, D::Error>
。在模块中,您反序列化 Option<String>
并根据需要解析它。要使该字段真正可选,您可能还想在该字段上添加 #[serde(default)]
,因为如果您使用 with 属性,该字段不会自动默认。
您也可以使用已经实现了这种自定义行为组合的 crate,例如使用 serde_with::DisplayFromStr
。你可以这样写:
#[serde_as]
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
#[serde_as(as = "DisplayFromStr")]
pub log_level: Level,
// #[serde_as(as = "Option<DisplayFromStr>")]
// #[serde(default)] // to make the field optional
// pub log_level: Option<Level>,
pub name: String,
}
如果 Level
结构将实现 Deserialize
,您可以跳过自定义反序列化代码。不过,这似乎不是追踪的选项。
我正在尝试获取 Rocket + Tracing library stack working. Rocket comes packaged with Figment, which allows the use of Serde 来创建特定于应用程序的配置文件。
使用此示例,我创建了一个通用 de/serializer 以允许我的 (yaml) 配置将 log_level: "debug"
等字符串映射到跟踪库日志 Level
。下面的代码实现这个工作正常。但是,如果修改配置文件以删除 log_level
,则会由于以下错误而中断:
thread 'main' panicked at 'Broken!: Error { tag: Tag(Default, 5), profile: Some(Profile(Uncased { string: "default" })), metadata: Some(Metadata { name: "Rocket Config", source: None, provide_location: Some(Location { file: "src/main.rs", line: 46, col: 18 }), interpolater: }), path: ["log_level"], kind: Message("error parsing level: expected one of \"error\", \"warn\", \"info\", \"debug\", \"trace\", or a number 1-5"), prev: None }', src/main.rs:53:47
这个错误清楚地表明 None
无法解析为 Level
。并不出乎意料,但我通常解决这个问题的方法是改变 serde 结构,将值更改为 Option
。在这种情况下,我收到以下错误:
error[E0277]: the trait bound `std::option::Option<tracing::Level>: FromStr` is not satisfied
--> src/main.rs:32:17
|
12 | pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
| ----------- required by a bound in this
...
15 | T: std::str::FromStr,
| ----------------- required by this bound in `from_string::deserialize`
...
32 | #[derive(Debug, Deserialize, Serialize)]
| ^^^^^^^^^^^ the trait `FromStr` is not implemented for `std::option::Option<tracing::Level>`
我不知道在这里做什么。我不认为我很明白为什么不满足这里的界限。我在其他配置代码中使用了 Option
,但我认为自定义反序列化器中的类型有一个微妙的地方我不明白。
这是我的 src/main.rs
文件:
#[macro_use]
extern crate rocket;
use rocket::figment::{Figment, providers::{Format, Yaml}};
use serde::{Deserialize, Serialize};
use tracing::Level;
pub mod from_string {
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: std::str::FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
String::deserialize(deserializer)?
.parse::<T>()
.map_err(|e| D::Error::custom(format!("{}", e)))
}
pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: std::fmt::Display,
{
format!("{}", value).serialize(serializer)
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
#[serde(with = "from_string")]
pub log_level: Level,
// This is the version that ERRORS.
// #[serde(with = "from_string")]
// pub log_level: Option<Level>,
pub name: String,
}
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
#[launch]
fn rocket() -> _ {
let config = Figment::from(rocket::Config::default())
.merge(Yaml::file("config.yaml").nested());
let default_config = config.select("default");
let cfg: Config = default_config.extract().expect("Broken!");
println!("Config name: {:#?}", cfg.name);
println!("Log level: {:#?}", cfg.log_level);
rocket::custom(default_config).mount("/", routes![index])
}
这是我的 Cargo.toml
文件:
[package]
name = "scratch-rust"
version = "0.1.0"
edition = "2018"
[dependencies]
figment = { version = "*", features = ["env", "toml", "json", "yaml"] }
rocket = "0.5.0-rc.1"
serde="*"
tracing="*"
这是一个示例 config.yaml
文件:
default:
# Remove the next line to see the 'None' error.
log_level: "debug"
name: "dev"
production:
name: "production"
使用这段代码,一个干净的开始给出了这个:
$ cargo run 17s nford 20:19:31
Compiling scratch-rust v0.1.0 (/home/nford/repos/scratch-rust/tracing-serde-bridge)
Finished dev [unoptimized + debuginfo] target(s) in 3.41s
Running `target/debug/scratch-rust`
Config name: "dev"
Log level: Level(
Debug,
)
Configured for default.
>> address: 127.0.0.1
...more startup logs
我希望看到这样的东西,除了 Level(Debug)
我希望看到:
Log level: None
任何人都可以帮助我了解打字界限是怎么回事,我应该如何解决它们?
(n.b。我只标记这个 Serde
和 Rust
因为,虽然我把它嵌入到一个比那个多一点的堆栈中,但我认为这个实际上是一个 'how do I properly write a custom deserializer for Serde' 问题。)
您的 from_string
模块只支持带有 T: std::str::FromStr,
的值。 Option
从不实现 FromStr
特性,即使内部值实现了它也不行。
有多种解决方法。
您可以将 from_string
模块更改为 return a Result<Option<T>, D::Error>
。在模块中,您反序列化 Option<String>
并根据需要解析它。要使该字段真正可选,您可能还想在该字段上添加 #[serde(default)]
,因为如果您使用 with 属性,该字段不会自动默认。
您也可以使用已经实现了这种自定义行为组合的 crate,例如使用 serde_with::DisplayFromStr
。你可以这样写:
#[serde_as]
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
#[serde_as(as = "DisplayFromStr")]
pub log_level: Level,
// #[serde_as(as = "Option<DisplayFromStr>")]
// #[serde(default)] // to make the field optional
// pub log_level: Option<Level>,
pub name: String,
}
如果 Level
结构将实现 Deserialize
,您可以跳过自定义反序列化代码。不过,这似乎不是追踪的选项。