diff --git a/confik/CHANGELOG.md b/confik/CHANGELOG.md index eafc205..fa00a5c 100644 --- a/confik/CHANGELOG.md +++ b/confik/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Minimum supported Rust version (MSRV) is now 1.67 due to `toml_edit` dependency. +- Add `Json5` source support under `Json5Source` ## 0.11.7 diff --git a/confik/Cargo.toml b/confik/Cargo.toml index bb1b5c2..5c32733 100644 --- a/confik/Cargo.toml +++ b/confik/Cargo.toml @@ -26,6 +26,7 @@ default = ["env", "toml"] # Source types env = ["dep:envious"] json = ["dep:serde_json"] +json5 = ["dep:json5"] toml = ["dep:toml"] # Destination types @@ -48,6 +49,7 @@ serde = { version = "1", default-features = false, features = ["std", "derive"] thiserror = "1" envious = { version = "0.2", optional = true } +json5 = { version = "0.4", optional = true } serde_json = { version = "1", optional = true } toml = { version = "0.8", optional = true, default-features = false, features = ["parse"] } diff --git a/confik/src/lib.rs b/confik/src/lib.rs index 0dd50c5..310b105 100644 --- a/confik/src/lib.rs +++ b/confik/src/lib.rs @@ -36,6 +36,8 @@ mod third_party; #[cfg(feature = "env")] pub use self::sources::env_source::EnvSource; +#[cfg(feature = "json5")] +pub use self::sources::json5_source::Json5Source; #[cfg(feature = "json")] pub use self::sources::json_source::JsonSource; #[cfg(feature = "toml")] diff --git a/confik/src/sources/json5_source.rs b/confik/src/sources/json5_source.rs new file mode 100644 index 0000000..4deddba --- /dev/null +++ b/confik/src/sources/json5_source.rs @@ -0,0 +1,93 @@ +use std::{borrow::Cow, error::Error, fmt}; + +use crate::{ConfigurationBuilder, Source}; + +/// A [`Source`] containing raw JSON data. +#[derive(Clone)] +pub struct Json5Source<'a> { + contents: Cow<'a, str>, + allow_secrets: bool, +} + +impl<'a> Json5Source<'a> { + /// Creates a [`Source`] containing raw JSON data. + pub fn new(contents: impl Into>) -> Self { + Self { + contents: contents.into(), + allow_secrets: false, + } + } + + /// Allows this source to contain secrets. + pub fn allow_secrets(mut self) -> Self { + self.allow_secrets = true; + self + } +} + +impl<'a> Source for Json5Source<'a> { + fn allows_secrets(&self) -> bool { + self.allow_secrets + } + + fn provide(&self) -> Result> { + Ok(json5::from_str(&self.contents)?) + } +} + +impl<'a> fmt::Debug for Json5Source<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Json5Source") + .field("allow_secrets", &self.allow_secrets) + .finish_non_exhaustive() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Configuration; + + #[test] + fn defaults() { + let source = Json5Source::new("{}"); + assert!(!source.allows_secrets()); + } + + #[test] + fn clone() { + let source = Json5Source::new("{}").allow_secrets(); + assert!(source.allows_secrets()); + assert!(source.clone().allow_secrets); + } + + #[test] + fn json5() { + #[derive(Configuration, Debug, PartialEq)] + struct Config { + message: String, + n: i32, + } + + let config = " + { + // A traditional message. + message: 'hello world', + + // A number for some reason. + n: 42, + } + "; + + assert_eq!( + Config::builder() + .override_with(Json5Source::new(config)) + .try_build() + .expect("Failed to build config"), + Config { + message: "hello world".to_string(), + n: 42, + }, + ); + } +} diff --git a/confik/src/sources/mod.rs b/confik/src/sources/mod.rs index 75b77ca..317f07c 100644 --- a/confik/src/sources/mod.rs +++ b/confik/src/sources/mod.rs @@ -56,6 +56,9 @@ pub(crate) mod file_source; #[cfg(feature = "toml")] pub(crate) mod toml_source; +#[cfg(feature = "json5")] +pub(crate) mod json5_source; + #[cfg(feature = "json")] pub(crate) mod json_source;