How to set_default() using config-rs crate
(Jin Qing's Column, Nov., 2024)
config is a configuration system for Rust applications.
Example of examples\hierarchical-env:
rust
#[derive(Debug, Deserialize)]
#[allow(unused)]
struct Database {
url: String,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub(crate) struct Settings {
debug: bool,
database: Database,
}
impl Settings {
pub(crate) fn new() -> Result<Self, ConfigError> {
let s = Config::builder()
.add_source(File::with_name("examples/hierarchical-env/config/default"))
.build()?;
// You can deserialize (and thus freeze) the entire configuration as
s.try_deserialize()
}
}
ConfigBuilder::set_default
sets a default value at key:
rust
pub fn set_default<S, T>(self, key: S, value: T) -> Result<Self, ConfigError>
where
S: AsRef<str>,
T: Into<Value>,
Such as:
rust
let s = Config::builder()
.set_default("debug", true)?
...
But how to set a member like Database
struct?
rust
let s = Config::builder()
.set_default("database", Database{url: "".into()})?
...
fails:
error[E0277]: the trait bound `ValueKind: From<Database>` is not satisfied
--> examples\hierarchical-env\settings.rs:50:38
|
50 | .set_default("database", Database{url: "".into()})?
| ----------- ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<Database>` is not implemented for `ValueKind`, which is required by `Database: Into<Value>`
| |
| required by a bound introduced by this call
|
= help: the following other types implement trait `From<T>`:
<ValueKind as From<&'a str>>
...
<ValueKind as From<i32>>
and 9 others
= note: required for `Database` to implement `Into<ValueKind>`
= note: required for `Value` to implement `From<Database>`
= note: 1 redundant requirement hidden
= note: required for `Database` to implement `Into<Value>`
note: required by a bound in `ConfigBuilder::<St>::set_default`
--> D:\github\config-rs\src\builder.rs:156:12
|
153 | pub fn set_default<S, T>(mut self, key: S, value: T) -> Result<Self>
| ----------- required by a bound in this associated function
...
156 | T: Into<Value>,
| ^^^^^^^^^^^ required by this bound in `ConfigBuilder::<St>::set_default`
- https://www.reddit.com/r/rust/comments/xgbtl0/how_to_properly_use_set_default_method_in/?rdt=37431
- https://www.reddit.com/r/rust/comments/v7744h/rust_crate_configrs_set_custom_struct_as_default/
The above links suggest to use ValueKind
:
let s = Config::builder()
.set_default(
"database",
ValueKind::Table(HashMap::<String, Value>::from([("url".into(), "".into())])),
)?
...
The ergonomic way is to implement From<Database>
for ValueKind
:
rust
impl From<Database> for ValueKind {
fn from(value: Database) -> Self {
let t = HashMap::<String, Value>::from([("url".into(), value.url.into())]);
ValueKind::Table(t)
}
}
rust
let s = Config::builder()
.set_default("database", Database { url: "".into() })?
...
For nested struct:
rust
#[derive(Debug, Default, Deserialize)]
#[allow(unused)]
struct Database {
url: String,
other: Other,
}
#[derive(Debug, Default, Deserialize)]
#[allow(unused)]
struct Other {}
impl From<Database> for ValueKind {
fn from(value: Database) -> Self {
let t = HashMap::<String, Value>::from([
("url".into(), value.url.into()),
("other".into(), value.other.into()),
]);
ValueKind::Table(t)
}
}
impl From<Other> for ValueKind {
fn from(_value: Other) -> Self {
ValueKind::Table(HashMap::new())
}
}
rust
let s = Config::builder()
.set_default("database", Database::default())?
...