Skip to content

Commit b6a93f0

Browse files
feat(confik): add js_option crate support (#221)
1 parent e2ec8ba commit b6a93f0

File tree

5 files changed

+143
-0
lines changed

5 files changed

+143
-0
lines changed

Cargo.lock

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

confik/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Implement `Configuration` for [`js_option::JsOption`](https://docs.rs/js_option/0.1.1/js_option/enum.JsOption.html)
6+
57
## 0.13.0
68

79
- Update `bytesize` dependency to `2`.

confik/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ camino = ["dep:camino"]
3535
chrono = ["dep:chrono"]
3636
common = []
3737
ipnetwork = ["dep:ipnetwork"]
38+
js_option = ["dep:js_option"]
3839
rust_decimal = ["dep:rust_decimal"]
3940
secrecy = ["dep:secrecy"]
4041
url = ["dep:url"]
@@ -56,6 +57,7 @@ bytesize = { version = "2", optional = true, features = ["serde"] }
5657
camino = { version = "1", optional = true, features = ["serde1"] }
5758
chrono = { version = "0.4.40", optional = true, default-features = false, features = ["serde"] }
5859
ipnetwork = { version = "0.21", optional = true, features = ["serde"] }
60+
js_option = { version = "0.1", optional = true, features = ["serde"] }
5961
rust_decimal = { version = "1", optional = true, features = ["serde"] }
6062
secrecy = { version = "0.10", optional = true, features = ["serde"] }
6163
url = { version = "2", optional = true, features = ["serde"] }

confik/src/third_party.rs

+56
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,62 @@ mod ipnetwork {
119119
}
120120
}
121121

122+
#[cfg(feature = "js_option")]
123+
mod js_option {
124+
use js_option::JsOption;
125+
use serde::de::DeserializeOwned;
126+
127+
use crate::{Configuration, ConfigurationBuilder};
128+
129+
impl<T> Configuration for JsOption<T>
130+
where
131+
T: DeserializeOwned + Configuration,
132+
{
133+
type Builder = JsOption<<T as Configuration>::Builder>;
134+
}
135+
136+
impl<T> ConfigurationBuilder for JsOption<T>
137+
where
138+
T: DeserializeOwned + ConfigurationBuilder,
139+
{
140+
type Target = JsOption<<T as ConfigurationBuilder>::Target>;
141+
142+
fn merge(self, other: Self) -> Self {
143+
match (self, other) {
144+
// If both `Some` then merge the contained builders
145+
(Self::Some(us), Self::Some(other)) => Self::Some(us.merge(other)),
146+
// If we don't have a value then always take the other
147+
(Self::Undefined, other) => other,
148+
// Either:
149+
// - We're explicitly `Null`
150+
// - We're explicitly `Some` and the other is `Undefined` or `Null`
151+
//
152+
// In either case, just take our value, which should be preferred to other.
153+
(us, _) => us,
154+
}
155+
}
156+
157+
fn try_build(self) -> Result<Self::Target, crate::Error> {
158+
match self {
159+
Self::Undefined => Ok(Self::Target::Undefined),
160+
Self::Null => Ok(Self::Target::Null),
161+
Self::Some(val) => Ok(Self::Target::Some(val.try_build()?)),
162+
}
163+
}
164+
165+
fn contains_non_secret_data(&self) -> Result<bool, crate::UnexpectedSecret> {
166+
match self {
167+
Self::Some(data) => data.contains_non_secret_data(),
168+
169+
// An explicit `Null` is counted as data, overriding any default.
170+
Self::Null => Ok(true),
171+
172+
Self::Undefined => Ok(false),
173+
}
174+
}
175+
}
176+
}
177+
122178
#[cfg(feature = "secrecy")]
123179
mod secrecy {
124180
use secrecy::SecretString;

confik/tests/third_party.rs

+73
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,76 @@ mod bigdecimal {
126126
}
127127
}
128128
}
129+
130+
#[cfg(feature = "js_option")]
131+
mod js_option {
132+
use confik::{Configuration, TomlSource};
133+
use js_option::JsOption;
134+
135+
#[derive(Configuration, Debug)]
136+
struct Config {
137+
opt: JsOption<usize>,
138+
}
139+
140+
#[test]
141+
fn undefined() {
142+
let config = Config::builder()
143+
.try_build()
144+
.expect("Should be valid without config");
145+
assert_eq!(config.opt, JsOption::Undefined);
146+
}
147+
148+
#[cfg(feature = "json")]
149+
#[test]
150+
fn null() {
151+
let json = r#"{ "opt": null }"#;
152+
153+
let config = Config::builder()
154+
.override_with(confik::JsonSource::new(json))
155+
.try_build()
156+
.expect("Failed to parse config");
157+
assert_eq!(config.opt, JsOption::Null);
158+
}
159+
160+
#[test]
161+
fn present() {
162+
let toml = "opt = 5";
163+
164+
let config = Config::builder()
165+
.override_with(TomlSource::new(toml))
166+
.try_build()
167+
.expect("Should be valid without config");
168+
assert_eq!(config.opt, JsOption::Some(5));
169+
}
170+
171+
#[cfg(feature = "json")]
172+
#[test]
173+
fn merge() {
174+
#[derive(Debug, Configuration, PartialEq, Eq)]
175+
struct Config {
176+
one: JsOption<usize>,
177+
two: JsOption<usize>,
178+
three: JsOption<usize>,
179+
four: JsOption<usize>,
180+
}
181+
182+
let base = r#"{ "two": null, "three": 5 }"#;
183+
let merge = r#"{ "one": 1, "two": 2, "three": 3}"#;
184+
185+
let config = Config::builder()
186+
.override_with(confik::JsonSource::new(merge))
187+
.override_with(confik::JsonSource::new(base))
188+
.try_build()
189+
.expect("Failed to parse config");
190+
191+
assert_eq!(
192+
config,
193+
Config {
194+
one: JsOption::Some(1),
195+
two: JsOption::Null,
196+
three: JsOption::Some(5),
197+
four: JsOption::Undefined,
198+
}
199+
);
200+
}
201+
}

0 commit comments

Comments
 (0)