schematic/config/
validator.rs

1use super::path::{Path, PathSegment};
2use miette::Diagnostic;
3use starbase_styles::{Style, Stylize};
4use std::borrow::Borrow;
5use thiserror::Error;
6
7pub type ValidateResult = std::result::Result<(), ValidateError>;
8
9/// A validator function that receives a setting value to validate, the parent
10/// configuration the setting belongs to, the current context, and can return
11/// a [`ValidateError`] on failure.
12pub type Validator<Val, Data, Ctx> = Box<dyn FnOnce(&Val, &Data, &Ctx, bool) -> ValidateResult>;
13
14/// Error for a single validation failure.
15#[derive(Clone, Debug, Diagnostic, Error)]
16#[error("{}{} {message}", .path.to_string().style(Style::Id), ":".style(Style::MutedLight))]
17pub struct ValidateError {
18    /// Failure message.
19    pub message: String,
20
21    /// Relative path to the setting that failed validation.
22    pub path: Path,
23}
24
25impl ValidateError {
26    /// Create a new validation error with the provided message.
27    pub fn new<T: AsRef<str>>(message: T) -> Self {
28        ValidateError {
29            message: message.as_ref().to_owned(),
30            path: Path::default(),
31        }
32    }
33
34    /// Create a new validation error for a required setting.
35    pub fn required() -> Self {
36        ValidateError {
37            message: "this setting is required".into(),
38            path: Path::default(),
39        }
40    }
41
42    /// Create a new validation error with the provided message and [`Path`].
43    pub fn with_path<T: AsRef<str>>(message: T, path: Path) -> Self {
44        ValidateError {
45            message: message.as_ref().to_owned(),
46            path,
47        }
48    }
49
50    /// Create a new validation error with the provided message and path [`PathSegment`].
51    pub fn with_segment<T: AsRef<str>>(message: T, segment: PathSegment) -> Self {
52        Self::with_segments(message, [segment])
53    }
54
55    /// Create a new validation error with the provided message and multiple path [`PathSegment`]s.
56    pub fn with_segments<T: AsRef<str>, I>(message: T, segments: I) -> Self
57    where
58        I: IntoIterator<Item = PathSegment>,
59    {
60        ValidateError {
61            message: message.as_ref().to_owned(),
62            path: Path::new(segments.into_iter().collect()),
63        }
64    }
65
66    #[doc(hidden)]
67    pub fn prepend_path(self, path: Path) -> Self {
68        Self {
69            message: self.message,
70            path: path.join_path(&self.path),
71        }
72    }
73}
74
75/// Error that contains multiple validation errors, for each setting that failed.
76#[derive(Debug, Diagnostic, Error)]
77#[error("{}", self.render_errors())]
78pub struct ValidatorError {
79    /// A list of validation errors for the current path. Includes nested errors.
80    pub errors: Vec<ValidateError>,
81}
82
83impl ValidatorError {
84    fn render_errors(&self) -> String {
85        self.errors
86            .iter()
87            .map(|error| error.to_string())
88            .collect::<Vec<_>>()
89            .join("\n")
90    }
91}
92
93impl Borrow<dyn Diagnostic> for Box<ValidatorError> {
94    fn borrow(&self) -> &(dyn Diagnostic + 'static) {
95        self.as_ref()
96    }
97}