confik
confik copied to clipboard
Tagged enums require tag set in both TOML and Env sources
Say you have a serde tagged enum by #[confik(forward_serde(tag = "type"))] in the configuration:
use confik::Configuration;
#[derive(Debug, Clone, Configuration)]
#[confik(forward_serde(tag = "type", rename_all = "snake_case"))]
enum Config {
Foo(Foo),
Bar(Bar),
}
#[derive(Debug, Clone, Configuration)]
struct Foo {
foo: String,
}
#[derive(Debug, Clone, Configuration)]
struct Bar {
bar: String,
}
fn main() {
let config = Config::builder()
.override_with(confik::FileSource::new("config.toml"))
.override_with(confik::EnvSource::new().allow_secrets())
.try_build()
.unwrap();
}
with config.toml
type = "foo"
foo = "hello"
and environment variable
TYPE=foo
The above works.
However, remove either of type = "foo" from config.toml or TYPE=foo from the environment, and this fails with an error:
(...)
missing field `type`
(...)
There are some limitations to confik that make this harder to avoid, but I think the actual bug is probably in serde. E.g.
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize, Default)]
struct ConfigOuter(#[serde(default)] Config);
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(tag = "type", rename_all = "snake_case")]
enum Config {
Foo(Foo),
Bar(Bar),
#[default]
Unknown,
}
#[derive(Debug, Clone, Deserialize)]
struct Foo {
foo: String,
}
#[derive(Debug, Clone, Deserialize)]
struct Bar {
bar: String,
}
#[test]
fn test() {
// This fails with:
//
// ---- test stdout ----
// thread 'test' panicked at src/main.rs:27:58:
// called `Result::unwrap()` on an `Err` value: Error("missing field `type`", line: 1, column: 2)
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
//
// However it should work and produce `ConfigOuter(Config::Unknown)`
let config: ConfigOuter = serde_json::from_str("{}").unwrap();
}
Similarity, serde(other) also fails for your example Config:
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum Config {
Foo(Foo),
Bar(Bar),
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Deserialize)]
struct Foo {
foo: String,
}
#[derive(Debug, Clone, Deserialize)]
struct Bar {
bar: String,
}
#[test]
fn test() {
// This fails with:
//
// ---- test stdout ----
// thread 'test' panicked at src/main.rs:27:58:
// called `Result::unwrap()` on an `Err` value: Error("missing field `type`", line: 1, column: 2)
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
//
// However it should work and produce `Config::Unknown`
let config: Config = serde_json::from_str("{}").unwrap();
}