具有可选边界的自定义反序列化器

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。我只标记这个 SerdeRust 因为,虽然我把它嵌入到一个比那个多一点的堆栈中,但我认为这个实际上是一个 '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,您可以跳过自定义反序列化代码。不过,这似乎不是追踪的选项。