diff --git a/CHANGELOG.md b/CHANGELOG.md index 51961389..e29af64f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,15 @@ - Refactored the internals of how validation errors work. - Removed `Config::META` and `ConfigError::META`. Use `Schematic::schema_name()` instead. +- Removed `url` as a default Cargo feature. +- Renamed `valid_*` Cargo features to `validate_*`. - Renamed some error enum variants. #### 🚀 Updates +- Added a `env` Cargo feature for toggling environment variable functionality. Enabled by default. +- Added a `extends` Cargo feature for config extending functionality. Enabled by default. +- Added a `validate` Cargo feature for toggling validation functionality. Enabled by default. - Reworked how parser and validator errors are rendered in the terminal. #### ⚙️ Internal diff --git a/Cargo.lock b/Cargo.lock index 3ac458ff..873ad0fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1728,9 +1728,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -1836,9 +1836,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "starbase_sandbox" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8a1b40b7859260a4371b1dcebb24ebf27356de1514cca4beb6b10fc273c03a" +checksum = "25dd6f9ea85ee2cfea36c1d1dd9454cc1e37cf893ed9eabf1444b40a288482dc" dependencies = [ "assert_cmd", "assert_fs", @@ -1851,9 +1851,9 @@ dependencies = [ [[package]] name = "starbase_styles" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcdc25102288ba49a8ae037a3de74dde8bcd519dfe00dc00dae4ed7344475983" +checksum = "44854a14e28e3b1d602d802576162380504df73efae50d4b901934d25579524b" dependencies = [ "dirs", "owo-colors", @@ -1862,12 +1862,11 @@ dependencies = [ [[package]] name = "starbase_utils" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b3df0c2955e598b9ac24256ebfb6e0d0291eb5fbd0a30b7075546d5972b8fa" +checksum = "641c9b34e4221ca637c645d0bbbc9935a0e4283b986f5605c20200c70b2d6f55" dependencies = [ "dirs", - "once_cell", "starbase_styles", "thiserror", "tracing", @@ -2074,21 +2073,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.16", + "toml_edit 0.22.17", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" dependencies = [ "serde", ] @@ -2106,9 +2105,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.16" +version = "0.22.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" dependencies = [ "indexmap", "serde", diff --git a/Cargo.toml b/Cargo.toml index 48e111d5..88a1546f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,6 @@ semver = "1.0.23" serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" serde_yaml = "0.9.34" -toml = "0.8.15" +toml = "0.8.16" tracing = "0.1.40" url = "2.5.2" diff --git a/book/src/config/struct/env.md b/book/src/config/struct/env.md index b10516e1..1524c9bb 100644 --- a/book/src/config/struct/env.md +++ b/book/src/config/struct/env.md @@ -1,5 +1,7 @@ # Environment variables +> Requires the `env` Cargo feature, which is enabled by default. + > Not supported for enums. Settings can also inherit values from environment variables via the `#[setting(env)]` attribute diff --git a/book/src/config/struct/extend.md b/book/src/config/struct/extend.md index f0d5c7c3..5b929bbc 100644 --- a/book/src/config/struct/extend.md +++ b/book/src/config/struct/extend.md @@ -1,5 +1,7 @@ # Extendable sources +> Requires the `extends` Cargo feature, which is enabled by default. + > Not supported for enums. Configs can extend other configs, generating an accurate layer chain, via the `#[setting(extend)]` diff --git a/book/src/config/struct/validate.md b/book/src/config/struct/validate.md index b84ac8cf..5f035628 100644 --- a/book/src/config/struct/validate.md +++ b/book/src/config/struct/validate.md @@ -1,5 +1,7 @@ # Validation rules +> Requires the `validate` Cargo feature, which is enabled by default. + What kind of configuration crate would this be without built-in validation? As such, we support it as a first-class feature, with built-in validation rules provided by the [garde](https://crates.io/crates/garde) crate. @@ -120,6 +122,6 @@ fn using_generics(value: &str, partial: &P, context: &C, finalize: bool) - The following Cargo features can be enabled for more functionality: -- `valid_email` - Enables email validation with the `schematic::validate::email` function. -- `valid_url` - Enables URL validation with the `schematic::validate::url` and `url_secure` +- `validate_email` - Enables email validation with the `schematic::validate::email` function. +- `validate_url` - Enables URL validation with the `schematic::validate::url` and `url_secure` functions. diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 94a7a746..2d32aa3f 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -23,5 +23,8 @@ syn = { version = "2.0.72", features = ["full"] } [features] default = [] config = [] +env = [] +extends = [] schema = [] tracing = [] +validate = [] diff --git a/crates/macros/src/common/field.rs b/crates/macros/src/common/field.rs index 325b4a84..5f14aaea 100644 --- a/crates/macros/src/common/field.rs +++ b/crates/macros/src/common/field.rs @@ -40,12 +40,16 @@ pub struct FieldArgs { // config #[darling(with = "preserve_str_literal", map = "Some")] pub default: Option, + #[cfg(feature = "env")] pub env: Option, + #[cfg(feature = "extends")] pub extend: bool, pub merge: Option, pub nested: bool, + #[cfg(feature = "env")] pub parse_env: Option, pub required: bool, + #[cfg(feature = "validate")] pub validate: Option, // serde @@ -111,6 +115,7 @@ impl<'l> Field<'l> { self.args.exclude } + #[cfg(feature = "extends")] pub fn is_extendable(&self) -> bool { self.args.extend } @@ -157,14 +162,17 @@ impl<'l> Field<'l> { pub fn get_env_var(&self) -> Option { if self.is_nested() { - None - } else if let Some(env_name) = &self.args.env { - Some(env_name.to_owned()) - } else { - self.env_prefix - .as_ref() - .map(|env_prefix| format!("{env_prefix}{}", self.get_name(None)).to_uppercase()) + return None; } + + #[cfg(feature = "env")] + if let Some(env_name) = &self.args.env { + return Some(env_name.to_owned()); + } + + self.env_prefix + .as_ref() + .map(|env_prefix| format!("{env_prefix}{}", self.get_name(None)).to_uppercase()) } pub fn get_serde_meta(&self) -> Option { diff --git a/crates/macros/src/common/macros.rs b/crates/macros/src/common/macros.rs index 2a3f9172..9a8bd006 100644 --- a/crates/macros/src/common/macros.rs +++ b/crates/macros/src/common/macros.rs @@ -33,6 +33,7 @@ pub struct MacroArgs { // config pub allow_unknown_fields: bool, pub context: Option, + #[cfg(feature = "env")] pub env_prefix: Option, pub file: Option, @@ -79,6 +80,7 @@ impl<'l> Macro<'l> { let mut field = Field::from(f); field.serde_args.inherit_from_container(&serde_args); field.casing_format.clone_from(&casing_format); + #[cfg(feature = "env")] field.env_prefix.clone_from(&args.env_prefix); field }) @@ -100,6 +102,7 @@ impl<'l> Macro<'l> { field.index = index; field.serde_args.inherit_from_container(&serde_args); field.casing_format.clone_from(&casing_format); + #[cfg(feature = "env")] field.env_prefix.clone_from(&args.env_prefix); field }) diff --git a/crates/macros/src/common/variant.rs b/crates/macros/src/common/variant.rs index 7a965cb9..58b3acb5 100644 --- a/crates/macros/src/common/variant.rs +++ b/crates/macros/src/common/variant.rs @@ -3,7 +3,7 @@ use crate::utils::{extract_common_attrs, format_case}; use darling::FromAttributes; use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; -use syn::{Attribute, Expr, ExprPath, Fields, Variant as NativeVariant}; +use syn::{Attribute, ExprPath, Fields, Variant as NativeVariant}; #[derive(Clone)] pub enum TaggedFormat { @@ -27,7 +27,8 @@ pub struct VariantArgs { pub merge: Option, pub nested: bool, pub required: bool, - pub validate: Option, + #[cfg(feature = "validate")] + pub validate: Option, // serde pub rename: Option, diff --git a/crates/macros/src/config/container.rs b/crates/macros/src/config/container.rs index 32e6f31f..72d2012b 100644 --- a/crates/macros/src/config/container.rs +++ b/crates/macros/src/config/container.rs @@ -1,6 +1,6 @@ use crate::common::Container; use proc_macro2::{Ident, TokenStream}; -use quote::{quote, ToTokens}; +use quote::quote; impl<'l> Container<'l> { pub fn generate_default_values(&self) -> TokenStream { @@ -89,10 +89,13 @@ impl<'l> Container<'l> { } pub fn generate_extends_from(&self) -> TokenStream { + #[cfg(feature = "extends")] match self { Self::NamedStruct { fields: settings, .. } => { + use quote::ToTokens; + // Validate only 1 setting is using it let mut names = vec![]; @@ -159,6 +162,9 @@ impl<'l> Container<'l> { quote! { None } } } + + #[cfg(not(feature = "extends"))] + quote! { None } } pub fn generate_finalize(&self) -> TokenStream { @@ -174,6 +180,16 @@ impl<'l> Container<'l> { .map(|s| s.generate_finalize_statement()) .collect::>(); + let env_statement = if cfg!(feature = "env") { + quote! { + if let Some(data) = Self::env_values()? { + partial.merge(context, data)?; + } + } + } else { + quote! {} + }; + quote! { let mut partial = Self::default(); @@ -183,9 +199,7 @@ impl<'l> Container<'l> { partial.merge(context, self)?; - if let Some(data) = Self::env_values()? { - partial.merge(context, data)?; - } + #env_statement #(#finalize_stmts)* diff --git a/crates/macros/src/config/field.rs b/crates/macros/src/config/field.rs index 46b6e87a..e28d7dcf 100644 --- a/crates/macros/src/config/field.rs +++ b/crates/macros/src/config/field.rs @@ -1,7 +1,6 @@ use crate::common::{Field, FieldValue}; use proc_macro2::{Literal, TokenStream}; use quote::{quote, ToTokens, TokenStreamExt}; -use syn::Expr; impl<'l> Field<'l> { pub fn generate_default_value(&self) -> TokenStream { @@ -20,6 +19,7 @@ impl<'l> Field<'l> { let env = self.get_env_var(); if env.is_none() { + #[cfg(feature = "env")] if self.args.parse_env.is_some() { panic!("Cannot use `parse_env` without `env` or a parent `env_prefix`."); } @@ -27,19 +27,27 @@ impl<'l> Field<'l> { return None; }; - let value = if let Some(parse_env) = &self.args.parse_env { - quote! { - parse_from_env_var(#env, #parse_env)? - } - } else { - quote! { - default_from_env_var(#env)? - } - }; + #[cfg(feature = "env")] + { + let value = if let Some(parse_env) = &self.args.parse_env { + quote! { + parse_from_env_var(#env, #parse_env)? + } + } else { + quote! { + default_from_env_var(#env)? + } + }; - let key = self.get_field_key(); + let key = self.get_field_key(); - Some(quote! { partial.#key = #value; }) + Some(quote! { partial.#key = #value; }) + } + + #[cfg(not(feature = "env"))] + { + None + } } pub fn generate_finalize_statement(&self) -> TokenStream { @@ -62,6 +70,7 @@ impl<'l> Field<'l> { #[allow(clippy::collapsible_else_if)] if matches!(self.value_type, FieldValue::Value { .. }) { // Reset extendable values since we don't have the entire resolved list + #[cfg(feature = "extends")] if self.args.extend { return quote! { Default::default() }; } @@ -119,7 +128,10 @@ impl<'l> Field<'l> { let key_quoted = self.get_field_key_string(); let mut stmts = vec![]; + #[cfg(feature = "validate")] if let Some(expr) = self.args.validate.as_ref() { + use syn::Expr; + let func = match expr { // func(arg)() Expr::Call(func) => quote! { #func }, diff --git a/crates/macros/src/config/mod.rs b/crates/macros/src/config/mod.rs index b8b008f1..1058e59a 100644 --- a/crates/macros/src/config/mod.rs +++ b/crates/macros/src/config/mod.rs @@ -26,11 +26,8 @@ impl<'l> ToTokens for ConfigMacro<'l> { // Generate implementations let default_values = cfg.type_of.generate_default_values(); - let env_values = cfg.type_of.generate_env_values(); - let extends_from = cfg.type_of.generate_extends_from(); let finalize = cfg.type_of.generate_finalize(); let merge = cfg.type_of.generate_merge(); - let validate = cfg.type_of.generate_validate(); let from_partial = cfg.type_of.generate_from_partial(&partial_name); let instrument = instrument_quote(); @@ -39,43 +36,37 @@ impl<'l> ToTokens for ConfigMacro<'l> { None => quote! { () }, }; - tokens.extend(quote! { - #[automatically_derived] - impl schematic::PartialConfig for #partial_name { - type Context = #context; - - #instrument - fn default_values(context: &Self::Context) -> Result, schematic::ConfigError> { - use schematic::internal::*; - #default_values - } + let env_method = if cfg!(feature = "env") { + let env_values = cfg.type_of.generate_env_values(); + quote! { #instrument fn env_values() -> Result, schematic::ConfigError> { use schematic::internal::*; #env_values } + } + } else { + quote! {} + }; + let extends_method = if cfg!(feature = "extends") { + let extends_from = cfg.type_of.generate_extends_from(); + + quote! { #instrument fn extends_from(&self) -> Option { #extends_from } + } + } else { + quote! {} + }; - #instrument - fn finalize(self, context: &Self::Context) -> Result { - #finalize - } - - #instrument - fn merge( - &mut self, - context: &Self::Context, - mut next: Self, - ) -> Result<(), schematic::ConfigError> { - use schematic::internal::*; - #merge - } + let validate_method = if cfg!(feature = "validate") { + let validate = cfg.type_of.generate_validate(); + quote! { #instrument fn validate_with_path( &self, @@ -94,6 +85,42 @@ impl<'l> ToTokens for ConfigMacro<'l> { Ok(()) } } + } else { + quote! {} + }; + + tokens.extend(quote! { + #[automatically_derived] + impl schematic::PartialConfig for #partial_name { + type Context = #context; + + #instrument + fn default_values(context: &Self::Context) -> Result, schematic::ConfigError> { + use schematic::internal::*; + #default_values + } + + #env_method + + #extends_method + + #instrument + fn finalize(self, context: &Self::Context) -> Result { + #finalize + } + + #instrument + fn merge( + &mut self, + context: &Self::Context, + mut next: Self, + ) -> Result<(), schematic::ConfigError> { + use schematic::internal::*; + #merge + } + + #validate_method + } #[automatically_derived] impl Default for #name { diff --git a/crates/macros/src/config/variant.rs b/crates/macros/src/config/variant.rs index 688d5e9d..47542af2 100644 --- a/crates/macros/src/config/variant.rs +++ b/crates/macros/src/config/variant.rs @@ -1,7 +1,7 @@ use crate::common::Variant; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; -use syn::{Expr, Fields, FieldsUnnamed}; +use syn::{Fields, FieldsUnnamed}; impl<'l> Variant<'l> { pub fn generate_default_value(&self) -> TokenStream { @@ -151,7 +151,10 @@ impl<'l> Variant<'l> { let mut stmts = vec![]; let name = self.get_name(Some(&self.casing_format)); + #[cfg(feature = "validate")] if let Some(expr) = self.args.validate.as_ref() { + use syn::Expr; + let func = match expr { // func(arg)() Expr::Call(func) => quote! { #func }, diff --git a/crates/schematic/Cargo.toml b/crates/schematic/Cargo.toml index 6a47d8e1..b01664e0 100644 --- a/crates/schematic/Cargo.toml +++ b/crates/schematic/Cargo.toml @@ -30,7 +30,7 @@ garde = { version = "0.20.0", default-features = false, optional = true, feature ] } serde = { workspace = true } serde_path_to_error = { version = "0.1.16", optional = true } -starbase_styles = { version = "0.4.1", optional = true } +starbase_styles = { version = "0.4.2", optional = true } # schema indexmap = { workspace = true, optional = true, features = ["serde"] } @@ -54,24 +54,32 @@ serde_yaml = { workspace = true, optional = true } reqwest = { workspace = true, optional = true, features = ["blocking"] } [features] -default = ["config", "url"] +default = ["config", "env", "extends", "validate"] config = [ - "dep:garde", "dep:serde_path_to_error", "dep:starbase_styles", "schematic_macros/config", ] -json = ["dep:serde_json"] schema = ["dep:indexmap", "schematic_macros/schema"] -toml = ["dep:toml"] tracing = ["schematic_macros/tracing"] + +# Features +env = ["schematic_macros/env"] +extends = ["schematic_macros/extends"] url = ["dep:reqwest"] +validate = ["dep:garde", "schematic_macros/validate"] + +# Formats +json = ["dep:serde_json"] +toml = ["dep:toml"] yaml = ["dep:serde_yaml"] -renderer_json_schema = ["dep:markdown", "dep:schemars", "json", "schema"] -renderer_template = [] +# Renderers +renderer_json_schema = ["json", "schema", "dep:markdown", "dep:schemars"] +renderer_template = ["schema"] renderer_typescript = ["schema"] +# Types type_chrono = ["schematic_types/chrono"] type_indexmap = ["schematic_types/indexmap"] type_regex = ["schematic_types/regex"] @@ -83,12 +91,15 @@ type_serde_toml = ["schematic_types/serde_toml"] type_serde_yaml = ["schematic_types/serde_yaml"] type_url = ["schematic_types/url"] -valid_email = ["garde/email"] -valid_url = ["garde/url"] +# Validation +validate_email = ["validate", "garde/email"] +validate_url = ["validate", "garde/url"] [dev-dependencies] schematic = { path = ".", features = [ "config", + "env", + "extends", "json", "schema", "toml", @@ -107,8 +118,9 @@ schematic = { path = ".", features = [ "type_serde_yaml", "type_url", "url", - "valid_email", - "valid_url", + "validate", + "validate_email", + "validate_url", "yaml", ] } reqwest = { workspace = true, features = [ @@ -117,7 +129,7 @@ reqwest = { workspace = true, features = [ ] } serial_test = "3.1.1" similar = "2.6.0" -starbase_sandbox = "0.7.0" +starbase_sandbox = "0.7.1" # Types chrono = { workspace = true, features = ["serde"] } diff --git a/crates/schematic/src/config/configs.rs b/crates/schematic/src/config/configs.rs index 135a9111..81910743 100644 --- a/crates/schematic/src/config/configs.rs +++ b/crates/schematic/src/config/configs.rs @@ -1,6 +1,7 @@ use super::error::ConfigError; +#[cfg(feature = "extends")] use super::extender::ExtendsFrom; -use super::path::Path; +#[cfg(feature = "validate")] use super::validator::*; use schematic_types::Schematic; use serde::{de::DeserializeOwned, Serialize}; @@ -23,11 +24,13 @@ pub trait PartialConfig: /// /// If an environment variable does not exist, the value will be [`None`]. If /// the variable fails to parse or cast into the correct type, an error is returned. + #[cfg(feature = "env")] fn env_values() -> Result, ConfigError>; /// When a setting is marked as extendable with `#[setting(extend)]`, this returns /// [`ExtendsFrom`] with the extended sources, either a list of strings or a single string. /// When no setting is extendable, this returns [`None`]. + #[cfg(feature = "extends")] fn extends_from(&self) -> Option; /// Finalize the partial configuration by consuming it and populating all fields with a value. @@ -46,8 +49,11 @@ pub trait PartialConfig: /// Recursively validate the configuration with the provided context. /// Validation should be done on the final state, after merging partials. + #[cfg(feature = "validate")] fn validate(&self, context: &Self::Context, finalize: bool) -> Result<(), ConfigError> { - if let Err(errors) = self.validate_with_path(context, finalize, Path::default()) { + if let Err(errors) = + self.validate_with_path(context, finalize, super::path::Path::default()) + { return Err(ConfigError::Validator { location: String::new(), error: Box::new(ValidatorError { errors }), @@ -59,12 +65,13 @@ pub trait PartialConfig: } /// Internal use only, use [`validate`] instead. + #[cfg(feature = "validate")] #[doc(hidden)] fn validate_with_path( &self, _context: &Self::Context, _finalize: bool, - _path: Path, + _path: super::path::Path, ) -> Result<(), Vec> { Ok(()) } diff --git a/crates/schematic/src/config/error.rs b/crates/schematic/src/config/error.rs index 52cc2b2c..1973ddc7 100644 --- a/crates/schematic/src/config/error.rs +++ b/crates/schematic/src/config/error.rs @@ -1,4 +1,5 @@ use super::parser::ParserError; +#[cfg(feature = "validate")] use super::validator::ValidatorError; use crate::format::UnsupportedFormatError; use miette::Diagnostic; @@ -88,6 +89,7 @@ pub enum ConfigError { }, // Validator + #[cfg(feature = "validate")] #[diagnostic(code(config::validate::failed))] #[error("Failed to validate {}.", .location.style(Style::File))] Validator { @@ -131,6 +133,7 @@ impl ConfigError { push_end(); message.push_str(&inner.to_string()); } + #[cfg(feature = "validate")] ConfigError::Validator { error: inner, .. } => { push_end(); for error in &inner.errors { diff --git a/crates/schematic/src/config/loader.rs b/crates/schematic/src/config/loader.rs index 84f92fc6..c3a88e28 100644 --- a/crates/schematic/src/config/loader.rs +++ b/crates/schematic/src/config/loader.rs @@ -1,6 +1,7 @@ use super::cacher::{BoxedCacher, Cacher, MemoryCache}; use super::configs::{Config, PartialConfig}; use super::error::ConfigError; +#[cfg(feature = "extends")] use super::extender::ExtendsFrom; use super::layer::Layer; use super::source::Source; @@ -104,9 +105,12 @@ impl ConfigLoader { let partial = self.merge_layers(&layers, context)?.finalize(context)?; // Validate the final result before moving on - partial.validate(context, true).map_err(|error| { - self.map_validator_error(error, layers.last().map(|layer| &layer.source)) - })?; + #[cfg(feature = "validate")] + { + partial.validate(context, true).map_err(|error| { + self.map_validator_error(error, layers.last().map(|layer| &layer.source)) + })?; + } Ok(ConfigLoadResult { config: T::from_partial(partial), @@ -150,6 +154,7 @@ impl ConfigLoader { self } + #[cfg(feature = "extends")] #[instrument(skip_all)] fn extend_additional_layers( &self, @@ -204,6 +209,7 @@ impl ConfigLoader { rel_path.to_str().unwrap_or(&self.name) } + #[cfg(feature = "url")] Source::Url { url, .. } => url, } } @@ -255,10 +261,14 @@ impl ConfigLoader { }; // Validate before continuing so we ensure the values are correct - partial - .validate(context, false) - .map_err(|error| self.map_validator_error(error, Some(source)))?; + #[cfg(feature = "validate")] + { + partial + .validate(context, false) + .map_err(|error| self.map_validator_error(error, Some(source)))?; + } + #[cfg(feature = "extends")] if let Some(extends_from) = partial.extends_from() { layers.extend(self.extend_additional_layers(context, source, &extends_from)?); } @@ -283,6 +293,7 @@ impl ConfigLoader { } } + #[cfg(feature = "validate")] fn map_validator_error(&self, outer: ConfigError, source: Option<&Source>) -> ConfigError { match outer { ConfigError::Validator { error, .. } => ConfigError::Validator { diff --git a/crates/schematic/src/config/mod.rs b/crates/schematic/src/config/mod.rs index 0bac8a46..e48150f3 100644 --- a/crates/schematic/src/config/mod.rs +++ b/crates/schematic/src/config/mod.rs @@ -1,6 +1,7 @@ mod cacher; mod configs; mod error; +#[cfg(feature = "extends")] mod extender; mod formats; mod layer; @@ -8,17 +9,20 @@ mod loader; mod parser; mod path; mod source; +#[cfg(feature = "validate")] mod validator; pub use cacher::*; pub use configs::*; pub use error::*; +#[cfg(feature = "extends")] pub use extender::*; pub use layer::*; pub use loader::*; pub use parser::*; pub use path::*; pub use source::*; +#[cfg(feature = "validate")] pub use validator::*; #[macro_export] @@ -33,4 +37,3 @@ macro_rules! derive_enum { pub type DefaultValueResult = std::result::Result, HandlerError>; pub type ParseEnvResult = std::result::Result, HandlerError>; pub type MergeResult = std::result::Result, HandlerError>; -pub type ValidateResult = std::result::Result<(), ValidateError>; diff --git a/crates/schematic/src/config/source.rs b/crates/schematic/src/config/source.rs index db8c3731..0e21e62d 100644 --- a/crates/schematic/src/config/source.rs +++ b/crates/schematic/src/config/source.rs @@ -22,6 +22,7 @@ pub enum Source { }, /// Secure URL to the configuration. + #[cfg(feature = "url")] Url { url: String, format: Format }, } @@ -34,6 +35,7 @@ impl Source { /// - Otherwise will be an error. pub fn new(value: &str, parent_source: Option<&Source>) -> Result { // Extending from a URL is allowed from any parent source + #[cfg(feature = "url")] if is_url_like(value) { return Source::url(value); } @@ -86,6 +88,7 @@ impl Source { } /// Create a new URL source with the provided URL. + #[cfg(feature = "url")] pub fn url>(url: T) -> Result { let url: String = url.try_into().map_err(|_| ConfigError::InvalidUrl)?; @@ -96,6 +99,7 @@ impl Source { } /// Parse the source contents according to the required format. + #[allow(unused_variables)] #[instrument(name = "parse_config_source", skip(cacher), fields(source = ?self))] pub fn parse(&self, name: &str, cacher: &mut BoxedCacher) -> Result where @@ -129,38 +133,31 @@ impl Source { format.parse(name, &content).map_err(handle_error) } + #[cfg(feature = "url")] Source::Url { url, format } => { if !is_secure_url(url) { return Err(ConfigError::HttpsOnly(url.to_owned())); } - #[cfg(feature = "url")] - { - let handle_reqwest_error = |error: reqwest::Error| ConfigError::ReadUrlFailed { - url: url.to_owned(), - error: Box::new(error), - }; - - let content = if let Some(cache) = cacher.read(url)? { - cache - } else { - let body = reqwest::blocking::get(url) - .map_err(handle_reqwest_error)? - .text() - .map_err(handle_reqwest_error)?; + let handle_reqwest_error = |error: reqwest::Error| ConfigError::ReadUrlFailed { + url: url.to_owned(), + error: Box::new(error), + }; - cacher.write(url, &body)?; + let content = if let Some(cache) = cacher.read(url)? { + cache + } else { + let body = reqwest::blocking::get(url) + .map_err(handle_reqwest_error)? + .text() + .map_err(handle_reqwest_error)?; - body - }; + cacher.write(url, &body)?; - format.parse(name, &content).map_err(handle_error) - } + body + }; - #[cfg(not(feature = "url"))] - { - panic!("Parsing a URL requires the `url` feature!"); - } + format.parse(name, &content).map_err(handle_error) } } } @@ -169,6 +166,7 @@ impl Source { match self { Source::Code { .. } => "", Source::File { path, .. } => path.to_str().unwrap_or_default(), + #[cfg(feature = "url")] Source::Url { url, .. } => url, } } diff --git a/crates/schematic/src/config/validator.rs b/crates/schematic/src/config/validator.rs index 0714ea08..239cbe7b 100644 --- a/crates/schematic/src/config/validator.rs +++ b/crates/schematic/src/config/validator.rs @@ -4,6 +4,8 @@ use starbase_styles::{Style, Stylize}; use std::borrow::Borrow; use thiserror::Error; +pub type ValidateResult = std::result::Result<(), ValidateError>; + /// Error for a single validation failure. #[derive(Clone, Debug, Diagnostic, Error)] #[error("{}{} {message}", .path.to_string().style(Style::Id), ":".style(Style::MutedLight))] diff --git a/crates/schematic/src/lib.rs b/crates/schematic/src/lib.rs index ba8f9a7b..8476c7f8 100644 --- a/crates/schematic/src/lib.rs +++ b/crates/schematic/src/lib.rs @@ -6,7 +6,7 @@ mod format; mod config; /// Built-in `parse_env` functions. -#[cfg(feature = "config")] +#[cfg(all(feature = "config", feature = "env"))] pub mod env; #[cfg(feature = "config")] @@ -22,7 +22,7 @@ pub mod merge; pub mod schema; /// Built-in `validate` functions. -#[cfg(feature = "config")] +#[cfg(all(feature = "config", feature = "validate"))] pub mod validate; /// ASCII color helpers for use within error messages. diff --git a/crates/schematic/src/validate/mod.rs b/crates/schematic/src/validate/mod.rs index ce968a84..6e44c9ff 100644 --- a/crates/schematic/src/validate/mod.rs +++ b/crates/schematic/src/validate/mod.rs @@ -1,22 +1,24 @@ -#[cfg(feature = "valid_email")] +#[cfg(feature = "validate_email")] mod email; +#[cfg(feature = "extends")] mod extends; mod ip; mod length; mod number; mod string; -#[cfg(feature = "valid_url")] +#[cfg(feature = "validate_url")] mod url; pub use crate::config::{ValidateError, ValidateResult}; -#[cfg(feature = "valid_email")] +#[cfg(feature = "validate_email")] pub use email::*; +#[cfg(feature = "extends")] pub use extends::*; pub use ip::*; pub use length::*; pub use number::*; pub use string::*; -#[cfg(feature = "valid_url")] +#[cfg(feature = "validate_url")] pub use url::*; /// A validator function that receives a setting value to validate, the parent