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}