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