Skip to main content

doido_controller/
constraints.rs

1//! Route/param constraints (Rails `constraints: { id: /\d+/ }`).
2//!
3//! Axum's path matcher has no regex constraints, so constraints are enforced as
4//! a check — typically from a `#[before_action]` that 404s (or 400s) when a
5//! path param fails its format. [`Constraints`] maps param names to validators.
6
7/// Non-empty and all ASCII digits.
8pub fn numeric(s: &str) -> bool {
9    !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit())
10}
11
12/// Non-empty and all ASCII letters.
13pub fn alpha(s: &str) -> bool {
14    !s.is_empty() && s.bytes().all(|b| b.is_ascii_alphabetic())
15}
16
17/// Non-empty and all ASCII letters or digits.
18pub fn alphanumeric(s: &str) -> bool {
19    !s.is_empty() && s.bytes().all(|b| b.is_ascii_alphanumeric())
20}
21
22/// The canonical 8-4-4-4-12 hex UUID shape.
23pub fn uuid_like(s: &str) -> bool {
24    let groups = [8, 4, 4, 4, 12];
25    let parts: Vec<&str> = s.split('-').collect();
26    parts.len() == groups.len()
27        && parts
28            .iter()
29            .zip(groups)
30            .all(|(p, n)| p.len() == n && p.bytes().all(|b| b.is_ascii_hexdigit()))
31}
32
33/// A path-param validator: returns whether the value satisfies the constraint.
34pub type Validator = fn(&str) -> bool;
35
36/// A set of `param name → validator` rules.
37#[derive(Default)]
38pub struct Constraints {
39    rules: Vec<(String, Validator)>,
40}
41
42impl Constraints {
43    pub fn new() -> Self {
44        Self::default()
45    }
46
47    /// Require `name` to satisfy `validator`.
48    pub fn param(mut self, name: &str, validator: Validator) -> Self {
49        self.rules.push((name.to_string(), validator));
50        self
51    }
52
53    /// Whether every rule is satisfied by `params` (a rule's param must be
54    /// present and valid; unruled params are ignored).
55    pub fn matches(&self, params: &[(&str, &str)]) -> bool {
56        self.rules.iter().all(|(name, validator)| {
57            params
58                .iter()
59                .find(|(k, _)| k == name)
60                .map(|(_, v)| validator(v))
61                .unwrap_or(false)
62        })
63    }
64}