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 crate.

In Schematic, validation does not happen as part of the serde parsing process, and instead happens for each partial configuration to be merged. However, with that said, prefer serde parsing over validation rules for structural adherence (learn more).

Validation can be applied on a per-setting basis with the #[setting(validate)] attribute field, which requires a reference to a function to call.

#![allow(unused)]
fn main() {
#[derive(Config)]
struct AppConfig {
	#[setting(validate = schematic::validate::alphanumeric)]
	pub secret_key: String,

	#[setting(validate = schematic::validate::regex("^\.env"))]
	pub env_file: String,
}
}

Or on a per-variant basis when using an enum.

#![allow(unused)]
fn main() {
#[derive(Config)]
enum Projects {
	#[setting(validate = schematic::validate::min_length(1))]
	List(Vec<String>),
	// ...
}
}

We provide a handful of built-in validation functions in the validate module. Furthermore, some functions are factories which can be called to produce a validator.

Validate handler function

You can also define your own function for validating values, also known as a validator.

When defining a custom validate function, the value to check is passed as the first argument, the current/parent partial as the second, the context as the third, and whether this is the final validation pass.

#![allow(unused)]
fn main() {
fn validate_string(
	value: &str,
	partial: &PartialAppConfig,
	context: &Context
	finalize: bool
) -> ValidateResult {
	if !do_check(value) {
		return Err(ValidateError::new("Some failure message"));
	}

	Ok(())
}
}

If validation fails, you must return a ValidateError with a failure message.

Factories

For composition and reusability concerns, we also support factory functions that can be called to create a unique validator. This can be seen above with schematic::validate::regex. To create your own factory, declare a normal function, with any number of arguments, that returns a Validator.

Using the regex factory as an example, it would look something like this.

#![allow(unused)]
fn main() {
use schematic::Validator;

fn regex<T, P, C>(pattern: &str) -> Validator<T, P, C> {
	let pattern = regex::Regex::new(pattern).unwrap();

	Box::new(move |value, _, _| {
		if !pattern.is_match(value) {
			return Err(ValidateError::new("Some failure message"));
		}

		Ok(())
	})
}
}

Path targeting

If validating an item in a list or collection, you can specifiy the nested path when failing. This is extremely useful when building error messages.

#![allow(unused)]
fn main() {
use schematic::PathSegment;

ValidateError::with_segments(
	"Some failure message",
	// [i].key
	[PathSegment::Index(i), PathSegment::Key(key.to_string())]
)
}

Context and partial handling

If you’re not using context, or want to create a validator for any kind of partial, we suggest generic inferrence.

#![allow(unused)]
fn main() {
fn using_generics<P, C>(value: &str, partial: &P, context: &C, finalize: bool) -> ValidateResult {
	// ...
}
}

Cargo features

The following Cargo features can be enabled for more functionality:

  • 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.