使用“#[derive(Debug)]”覆盖结构的单个字段的默认实现

Override the default implementation for a single field of a struct using `#[derive(Debug)]`

我有一个包含 Web 应用程序一般配置数据的结构:

#[derive(Debug)]
pub struct AppConfig {
  pub db_host: String,
  pub timeout: Duration,
  pub jwt_signing_key: String,
  // more fields
}

jwt_signing_key是一个对称的jwt secret,所以要保密/

这工作正常,但我希望能够记录 AppConfig 的内容以进行调试,而 JWT 机密不会出现在日志中。理想情况下,它会 return 类似于:

AppConfig {
  db_host: "https://blah"
  jwt_signing_key: "**********"  // key obfuscated
}

一个选项是创建一个薄包装器:

pub struct AppConfig {
  // ...
  pub jwt_signing_key: JwtSigningKey,
}

pub struct JwtSigningKey(String);
// custom implementation of Debug

但这需要进行大规模重构并添加一个不必要的(在我看来)间接层。 (我不担心性能,但更多的是代码噪音)

AppConfig 相对较大,维护手动 Debug 实施是额外的工作,我不想每次添加字段时都必须这样做。

有没有更简单的解决方案?

查看 this thread. Essentially, the best way to do it right now is to use the derivative crate,它允许你做这样的事情:

use derivative::Derivative;
use std::fmt;

fn obfuscated_formatter(val: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "{:?}", "*".repeat(val.len()))
}

#[derive(Derivative)]
#[derivative(Debug)]
struct Foo {
    normal: String,
    #[derivative(Debug(format_with = "obfuscated_formatter"))]
    secret: String
}

fn main() {
    let foo = Foo {
        normal: "asdf".into(),
        secret: "foobar".into()
    };
    println!("{:?}", foo);
}

Playground link

One option is to create a thin wrapper:

pub struct AppConfig {
  // ...
  pub jwt_signing_key: JwtSigningKey,
}

pub struct JwtSigningKey(String);
// custom implementation of Debug

我认为你应该选择这个选项。它的优点是 无论值在您的应用程序中的哪个位置结束, 它永远不会被意外打印 — 或以任何其他方式使用 — 而不是显式展开它,而替换 [= AppConfig 结构的 11=] 将仅协助处理该特定情况。它对重构时犯的错误也很稳健。

类型不必像JwtSigningKey那样具体;它可能只是 pub struct Secret<T>(T); 并支持您最终使用的任何类型的秘密。甚至 libraries to provide such a type (see this thread for comments comparing two of them) 添加了更多功能,例如在掉落时将内存归零(这样内存内容的任何意外泄漏都不太可能泄露秘密)。