Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

breaking: Clean up internals/handlers. #133

Merged
merged 3 commits into from
Jul 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

##### Config

- Refactored the internals of how validation errors work.
- Refactored the internals of how merge/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_*`.
Expand Down
2 changes: 1 addition & 1 deletion book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- [Settings](./config/settings.md)
- [Partials](./config/partial.md)
- [Nesting](./config/nested.md)
- [Context & metadata](./config/context.md)
- [Context](./config/context.md)
- [Structs & enums](./config/struct/index.md)
- [Default values](./config/struct/default.md)
- [Environment variables](./config/struct/env.md)
Expand Down
11 changes: 0 additions & 11 deletions book/src/config/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,3 @@ let result = ConfigLoader::<ExampleConfig>::new()

> Refer to the [default values](./struct/default.md), [merge strategies](./struct/merge.md), and
> [validation rules](./struct/validate.md) sections for more information on how to use context.

## Metadata

[`Config`](./index.md) supports basic metadata for use within error messages. Right now we support a
name, derived from the struct/enum name or the serde `rename` attribute field.

Metadata can be accessed with the `META` constant.

```rust
ExampleConfig::META.name;
```
5 changes: 4 additions & 1 deletion book/src/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,11 @@ Schematic is powered entirely by [serde](https://serde.rs), and supports the fol
The following Cargo features are available:

- `config` (default) - Enables configuration support (all the above stuff).
- `env` (default) - Enables environment variables for settings.
- `extends` (default) - Enables configs to extend other configs.
- `json` - Enables JSON.
- `toml` - Enables TOML.
- `tracing` - Wrap generated code in tracing instrumentations.
- `url` (default) - Enables loading, extending, and parsing configs from URLs.
- `url` - Enables loading, extending, and parsing configs from URLs.
- `validate` (default) - Enables setting value validation.
- `yaml` - Enables YAML.
4 changes: 2 additions & 2 deletions crates/macros/src/config/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ impl<'l> Field<'l> {
{
let value = if let Some(parse_env) = &self.args.parse_env {
quote! {
parse_from_env_var(#env, #parse_env)?
parse_env_value(#env, #parse_env)?
}
} else {
quote! {
default_from_env_var(#env)?
default_env_value(#env)?
}
};

Expand Down
6 changes: 3 additions & 3 deletions crates/macros/src/config/field_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ impl<'l> FieldValue<'l> {
quote! { Some(#expr) }
}
Expr::Path(func) => {
quote! { handle_default_fn(#func(context))? }
quote! { handle_default_result(#func(context))? }
}
Expr::Lit(lit) => match &lit.lit {
Lit::Str(string) => quote! {
Some(handle_default_fn(#value::try_from(#string))?)
Some(handle_default_result(#value::try_from(#string))?)
},
other => quote! { Some(#other) },
},
Expand Down Expand Up @@ -88,7 +88,7 @@ impl<'l> FieldValue<'l> {
}

return quote! {
self.#key = merge_partial_setting(
self.#key = merge_nested_setting(
self.#key.take(),
next.#key.take(),
context,
Expand Down
10 changes: 10 additions & 0 deletions crates/schematic/src/config/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::merger::MergeError;
use super::parser::ParserError;
#[cfg(feature = "validate")]
use super::validator::ValidatorError;
Expand All @@ -14,6 +15,9 @@ pub enum ConfigError {
#[error(transparent)]
Handler(#[from] Box<HandlerError>),

#[error(transparent)]
Merge(#[from] Box<MergeError>),

#[error(transparent)]
UnsupportedFormat(#[from] Box<UnsupportedFormatError>),

Expand Down Expand Up @@ -153,6 +157,12 @@ impl From<HandlerError> for ConfigError {
}
}

impl From<MergeError> for ConfigError {
fn from(e: MergeError) -> ConfigError {
ConfigError::Merge(Box::new(e))
}
}

impl From<UnsupportedFormatError> for ConfigError {
fn from(e: UnsupportedFormatError) -> ConfigError {
ConfigError::UnsupportedFormat(Box::new(e))
Expand Down
20 changes: 20 additions & 0 deletions crates/schematic/src/config/merger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use miette::Diagnostic;
use std::fmt::Display;
use thiserror::Error;

pub type MergeResult<T> = std::result::Result<Option<T>, MergeError>;

/// A merger function that receives the previous and next values, the current
/// context, and can return a [`MergeError`] on failure.
pub type Merger<Val, Ctx> = Box<dyn FnOnce(Val, Val, &Ctx) -> MergeResult<Val>>;

/// Error for merge failures.
#[derive(Error, Debug, Diagnostic)]
#[error("{0}")]
pub struct MergeError(pub String);

impl MergeError {
pub fn new<T: Display>(message: T) -> Self {
Self(message.to_string())
}
}
5 changes: 4 additions & 1 deletion crates/schematic/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod extender;
mod formats;
mod layer;
mod loader;
mod merger;
mod parser;
mod path;
mod source;
Expand All @@ -19,6 +20,7 @@ pub use error::*;
pub use extender::*;
pub use layer::*;
pub use loader::*;
pub use merger::*;
pub use parser::*;
pub use path::*;
pub use source::*;
Expand All @@ -35,5 +37,6 @@ macro_rules! derive_enum {
}

pub type DefaultValueResult<T> = std::result::Result<Option<T>, HandlerError>;

#[cfg(feature = "env")]
pub type ParseEnvResult<T> = std::result::Result<Option<T>, HandlerError>;
pub type MergeResult<T> = std::result::Result<Option<T>, HandlerError>;
5 changes: 5 additions & 0 deletions crates/schematic/src/config/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ use thiserror::Error;

pub type ValidateResult = std::result::Result<(), ValidateError>;

/// A validator function that receives a setting value to validate, the parent
/// configuration the setting belongs to, the current context, and can return
/// a [`ValidateError`] on failure.
pub type Validator<Val, Data, Ctx> = Box<dyn FnOnce(&Val, &Data, &Ctx, bool) -> ValidateResult>;

/// Error for a single validation failure.
#[derive(Clone, Debug, Diagnostic, Error)]
#[error("{}{} {message}", .path.to_string().style(Style::Id), ":".style(Style::MutedLight))]
Expand Down
48 changes: 27 additions & 21 deletions crates/schematic/src/internal.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
use crate::config::{ConfigError, HandlerError, PartialConfig};
use crate::merge::merge_partial;
use crate::ParseEnvResult;
use crate::config::{ConfigError, HandlerError, MergeError, MergeResult, PartialConfig};
use schematic_types::Schema;
use std::{env, str::FromStr};
use std::str::FromStr;

pub fn handle_default_fn<T, E: std::error::Error>(result: Result<T, E>) -> Result<T, ConfigError> {
// Handles T and Option<T> values
pub fn handle_default_result<T, E: std::error::Error>(
result: Result<T, E>,
) -> Result<T, ConfigError> {
result.map_err(|error| ConfigError::InvalidDefaultValue(error.to_string()))
}

pub fn default_from_env_var<T: FromStr>(key: &str) -> ParseEnvResult<T> {
parse_from_env_var(key, |var| parse_value(var).map(|v| Some(v)))
#[cfg(feature = "env")]
pub fn default_env_value<T: FromStr>(key: &str) -> crate::config::ParseEnvResult<T> {
parse_env_value(key, |value| parse_value(value).map(|v| Some(v)))
}

pub fn parse_from_env_var<T>(
#[cfg(feature = "env")]
pub fn parse_env_value<T>(
key: &str,
parser: impl Fn(String) -> ParseEnvResult<T>,
) -> Result<Option<T>, HandlerError> {
if let Ok(var) = env::var(key) {
let value = parser(var).map_err(|error| {
HandlerError(format!("Invalid environment variable {key}. {error}"))
})?;

return Ok(value);
parser: impl Fn(String) -> crate::config::ParseEnvResult<T>,
) -> crate::config::ParseEnvResult<T> {
if let Ok(value) = std::env::var(key) {
return parser(value)
.map_err(|error| HandlerError(format!("Invalid environment variable {key}. {error}")));
}

Ok(None)
Expand All @@ -42,8 +42,8 @@ pub fn merge_setting<T, C>(
prev: Option<T>,
next: Option<T>,
context: &C,
merger: impl Fn(T, T, &C) -> Result<Option<T>, HandlerError>,
) -> Result<Option<T>, HandlerError> {
merger: impl Fn(T, T, &C) -> MergeResult<T>,
) -> MergeResult<T> {
if prev.is_some() && next.is_some() {
merger(prev.unwrap(), next.unwrap(), context)
} else if next.is_some() {
Expand All @@ -54,13 +54,19 @@ pub fn merge_setting<T, C>(
}

#[allow(clippy::unnecessary_unwrap)]
pub fn merge_partial_setting<T: PartialConfig>(
pub fn merge_nested_setting<T: PartialConfig>(
prev: Option<T>,
next: Option<T>,
context: &T::Context,
) -> Result<Option<T>, HandlerError> {
) -> MergeResult<T> {
if prev.is_some() && next.is_some() {
merge_partial(prev.unwrap(), next.unwrap(), context)
let mut nested = prev.unwrap();

nested
.merge(context, next.unwrap())
.map_err(|error| MergeError(error.to_string()))?;

Ok(Some(nested))
} else if next.is_some() {
Ok(next)
} else {
Expand Down
22 changes: 1 addition & 21 deletions crates/schematic/src/merge.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
use crate::{
config::{HandlerError, PartialConfig},
MergeResult,
};
use crate::config::MergeResult;
use std::{
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
hash::Hash,
};

/// A merger function that receives the previous and next values, the current
/// context, and can return a [`HandlerError`] on failure.
pub type Merger<Val, Ctx> = Box<dyn FnOnce(Val, Val, &Ctx) -> MergeResult<Val>>;

/// Discard both previous and next values and return [`None`].
pub fn discard<T, C>(_: T, _: T, _: &C) -> MergeResult<T> {
Ok(None)
Expand Down Expand Up @@ -103,16 +96,3 @@ where

Ok(Some(prev))
}

/// Merge the next [`PartialConfig`] into the previous [`PartialConfig`], using the merging
/// strategies defined by the [`Config`] derive implementation.
pub fn merge_partial<T: PartialConfig>(
mut prev: T,
next: T,
context: &T::Context,
) -> MergeResult<T> {
prev.merge(context, next)
.map_err(|error| HandlerError(error.to_string()))?;

Ok(Some(prev))
}
7 changes: 1 addition & 6 deletions crates/schematic/src/validate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod string;
#[cfg(feature = "validate_url")]
mod url;

pub use crate::config::{ValidateError, ValidateResult};
pub use crate::config::{ValidateError, ValidateResult, Validator};
#[cfg(feature = "validate_email")]
pub use email::*;
#[cfg(feature = "extends")]
Expand All @@ -21,11 +21,6 @@ pub use string::*;
#[cfg(feature = "validate_url")]
pub use url::*;

/// A validator function that receives a setting value to validate, the parent
/// configuration the setting belongs to, the current context, and can return
/// a [`ValidateError`] on failure.
pub type Validator<Val, Data, Ctx> = Box<dyn FnOnce(&Val, &Data, &Ctx, bool) -> ValidateResult>;

pub(crate) fn map_err(error: garde::Error) -> ValidateError {
ValidateError::new(error.to_string())
}