Skip to main content

silicon_browser_shared/
validation.rs

1use thiserror::Error;
2
3pub(crate) const MAX_IDENTIFIER_CHARS: usize = 512;
4
5/// Input validation failure suitable for conversion into an API field error.
6#[derive(Clone, Debug, PartialEq, Eq, Error)]
7pub enum ValidationError {
8    #[error("{field} is required")]
9    Required { field: &'static str },
10    #[error("{field} must be at most {max} characters")]
11    TooLong { field: &'static str, max: usize },
12    #[error("{field} must contain at most {max} items")]
13    TooMany { field: &'static str, max: usize },
14    #[error("{field} must be between {min} and {max}")]
15    OutOfRange { field: &'static str, min: u64, max: u64 },
16    #[error("invalid {field}: {reason}")]
17    Invalid { field: &'static str, reason: String },
18    #[error("{left} conflicts with {right}")]
19    Conflict { left: &'static str, right: &'static str },
20}
21
22pub trait Validate {
23    fn validate(&self) -> Result<(), ValidationError>;
24}
25
26pub(crate) fn required(value: &str, field: &'static str) -> Result<(), ValidationError> {
27    if value.trim().is_empty() { Err(ValidationError::Required { field }) } else { Ok(()) }
28}
29
30pub(crate) fn bounded(value: &str, field: &'static str, max: usize) -> Result<(), ValidationError> {
31    required(value, field)?;
32    if value.chars().count() > max {
33        return Err(ValidationError::TooLong { field, max });
34    }
35    Ok(())
36}
37
38pub(crate) fn collection_len(len: usize, field: &'static str, max: usize) -> Result<(), ValidationError> {
39    if len > max {
40        return Err(ValidationError::TooMany { field, max });
41    }
42    Ok(())
43}
44
45pub(crate) fn identifier(value: &str, field: &'static str) -> Result<(), ValidationError> {
46    bounded(value, field, MAX_IDENTIFIER_CHARS)?;
47    if value.chars().any(|ch| ch.is_control() || ch.is_whitespace() || matches!(ch, '/' | '\\')) {
48        return Err(ValidationError::Invalid {
49            field,
50            reason: "must not contain whitespace, control characters, or path separators".into(),
51        });
52    }
53    Ok(())
54}
55
56pub(crate) fn purpose(value: &str) -> Result<(), ValidationError> {
57    bounded(value, "purpose", 2_000)
58}