1use std::borrow::Cow;
2use std::fmt;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
6#[non_exhaustive]
7pub struct RuleViolation {
8 pub rule_name: &'static str,
10 pub message: String,
12 pub position: Option<usize>,
14 pub fragment: Option<String>,
16}
17
18impl RuleViolation {
19 pub fn new(rule_name: &'static str, message: impl Into<String>) -> Self {
21 Self {
22 rule_name,
23 message: message.into(),
24 position: None,
25 fragment: None,
26 }
27 }
28
29 pub fn at(mut self, position: usize) -> Self {
31 self.position = Some(position);
32 self
33 }
34
35 pub fn with_fragment(mut self, fragment: impl Into<String>) -> Self {
37 self.fragment = Some(fragment.into());
38 self
39 }
40}
41
42impl fmt::Display for RuleViolation {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(f, "[{}] {}", self.rule_name, self.message)?;
45 if let Some(pos) = self.position {
46 write!(f, " (at byte {})", pos)?;
47 }
48 if let Some(ref frag) = self.fragment {
49 write!(f, " fragment={:?}", frag)?;
50 }
51 Ok(())
52 }
53}
54
55#[derive(Clone, PartialEq, Eq)]
64#[non_exhaustive]
65pub struct SanitizeError {
66 pub input: String,
68 pub violations: Vec<RuleViolation>,
70}
71
72impl SanitizeError {
73 pub fn new(input: impl Into<String>, violations: Vec<RuleViolation>) -> Self {
75 Self {
76 input: input.into(),
77 violations,
78 }
79 }
80}
81
82const DEBUG_INPUT_TRUNCATE: usize = 32;
84
85impl fmt::Debug for SanitizeError {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 let truncated: Cow<'_, str> = if self.input.len() <= DEBUG_INPUT_TRUNCATE {
88 Cow::Borrowed(&self.input)
89 } else {
90 let end = (0..=DEBUG_INPUT_TRUNCATE)
92 .rev()
93 .find(|&i| self.input.is_char_boundary(i))
94 .unwrap_or(0);
95 Cow::Owned(format!("{}…", &self.input[..end]))
96 };
97
98 f.debug_struct("SanitizeError")
99 .field("input", &truncated)
100 .field("violations", &self.violations)
101 .finish()
102 }
103}
104
105impl fmt::Display for SanitizeError {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 write!(
108 f,
109 "sanitization failed with {} violation(s)",
110 self.violations.len()
111 )?;
112 for v in &self.violations {
113 write!(f, "\n - {}", v)?;
114 }
115 Ok(())
116 }
117}
118
119impl std::error::Error for SanitizeError {}