Skip to main content

shell_sanitize/
sanitizer.rs

1use std::marker::PhantomData;
2
3use crate::error::{RuleViolation, SanitizeError};
4use crate::marker::MarkerType;
5use crate::rule::Rule;
6use crate::sanitized::Sanitized;
7
8/// A composable sanitizer that runs a chain of [`Rule`]s against input.
9///
10/// Construct via [`Sanitizer::builder`].
11///
12/// # Example
13///
14/// ```
15/// use shell_sanitize::{Sanitizer, ShellArg};
16///
17/// let sanitizer = Sanitizer::<ShellArg>::builder()
18///     // .add_rule(some_rule)
19///     .build();
20///
21/// let result = sanitizer.sanitize("hello");
22/// assert!(result.is_ok());
23/// ```
24pub struct Sanitizer<T: MarkerType> {
25    rules: Vec<Box<dyn Rule>>,
26    _marker: PhantomData<T>,
27}
28
29impl<T: MarkerType> Sanitizer<T> {
30    /// Start building a new sanitizer.
31    pub fn builder() -> SanitizerBuilder<T> {
32        SanitizerBuilder {
33            rules: Vec::new(),
34            _marker: PhantomData,
35        }
36    }
37
38    /// Run all rules against `input`.
39    ///
40    /// Returns `Sanitized<T>` on success, or `SanitizeError` containing
41    /// all violations found across all rules.
42    #[must_use = "sanitization result must be checked"]
43    pub fn sanitize(&self, input: &str) -> Result<Sanitized<T>, SanitizeError> {
44        let mut all_violations: Vec<RuleViolation> = Vec::new();
45
46        for rule in &self.rules {
47            if let Err(violations) = rule.check(input) {
48                all_violations.extend(violations);
49            }
50        }
51
52        if all_violations.is_empty() {
53            Ok(Sanitized::new(input.to_owned()))
54        } else {
55            Err(SanitizeError {
56                input: input.to_owned(),
57                violations: all_violations,
58            })
59        }
60    }
61
62    /// Returns the number of rules registered.
63    #[must_use]
64    pub fn rule_count(&self) -> usize {
65        self.rules.len()
66    }
67}
68
69/// Builder for [`Sanitizer`].
70pub struct SanitizerBuilder<T: MarkerType> {
71    rules: Vec<Box<dyn Rule>>,
72    _marker: PhantomData<T>,
73}
74
75impl<T: MarkerType> SanitizerBuilder<T> {
76    /// Add a rule to the sanitizer.
77    pub fn add_rule<R: Rule + 'static>(mut self, rule: R) -> Self {
78        self.rules.push(Box::new(rule));
79        self
80    }
81
82    /// Add multiple boxed rules at once.
83    ///
84    /// This is useful for integrating with functions that return
85    /// `Vec<Box<dyn Rule>>`:
86    ///
87    /// ```
88    /// use shell_sanitize::{Sanitizer, ShellArg, Rule, RuleResult};
89    ///
90    /// struct PassRule;
91    /// impl Rule for PassRule {
92    ///     fn name(&self) -> &'static str { "pass" }
93    ///     fn check(&self, _: &str) -> RuleResult { Ok(()) }
94    /// }
95    ///
96    /// let rules: Vec<Box<dyn Rule>> = vec![Box::new(PassRule)];
97    /// let s = Sanitizer::<ShellArg>::builder()
98    ///     .add_rules(rules)
99    ///     .build();
100    /// assert_eq!(s.rule_count(), 1);
101    /// ```
102    pub fn add_rules(mut self, rules: Vec<Box<dyn Rule>>) -> Self {
103        self.rules.extend(rules);
104        self
105    }
106
107    /// Build the sanitizer with all configured rules.
108    pub fn build(self) -> Sanitizer<T> {
109        Sanitizer {
110            rules: self.rules,
111            _marker: PhantomData,
112        }
113    }
114}