1#[cfg(test)]
4mod test_from_schema;
5
6#[cfg(test)]
7mod test_reasonable_str;
8
9use std::{fmt, ops::Deref};
10
11use crate::{
12 schema::{self, HasElement as _},
13 warning::{self, IntoCaveat as _},
14 FromSchema, Verdict,
15};
16
17#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
19pub enum Warning {
20 ContainsEscapeCodes,
22
23 ContainsNonPrintableASCII,
25
26 InvalidLengthMax { length: usize },
28
29 InvalidLengthExact { length: usize },
31
32 PreferUppercase,
37}
38
39impl fmt::Display for Warning {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 Self::ContainsEscapeCodes => f.write_str("The string contains escape codes."),
43 Self::ContainsNonPrintableASCII => {
44 f.write_str("The string contains non-printable bytes.")
45 }
46 Self::InvalidLengthMax { length } => {
47 write!(
48 f,
49 "The string is longer than the max length `{length}` defined in the spec.",
50 )
51 }
52 Self::InvalidLengthExact { length } => {
53 write!(f, "The string should be length `{length}`.")
54 }
55 Self::PreferUppercase => {
56 write!(f, "Upper case is preferred")
57 }
58 }
59 }
60}
61
62impl crate::Warning for Warning {
63 fn id(&self) -> warning::Id {
64 match self {
65 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
66 Self::ContainsNonPrintableASCII => {
67 warning::Id::from_static("contains_non_printable_ascii")
68 }
69 Self::InvalidLengthMax { .. } => warning::Id::from_static("invalid_length_max"),
70 Self::InvalidLengthExact { .. } => warning::Id::from_static("invalid_length_exact"),
71 Self::PreferUppercase => warning::Id::from_static("prefer_upper_case"),
72 }
73 }
74}
75
76#[derive(Copy, Clone, Debug)]
84pub(crate) struct CiMaxLen<'buf, const MAX_LEN: usize>(&'buf str);
85
86impl<const MAX_LEN: usize> Deref for CiMaxLen<'_, MAX_LEN> {
87 type Target = str;
88
89 fn deref(&self) -> &Self::Target {
90 self.0
91 }
92}
93
94impl<const MAX_LEN: usize> fmt::Display for CiMaxLen<'_, MAX_LEN> {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 write!(f, "{}", self.0)
97 }
98}
99
100impl<'buf, const MAX_LEN: usize> FromSchema<'buf, schema::Str<'buf>> for CiMaxLen<'buf, MAX_LEN> {
101 type Warning = Warning;
102
103 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
104 let (s, mut warnings) = Base::from_schema(source)?.into_parts();
105
106 if s.len() > MAX_LEN {
107 warnings.insert(
108 source.element(),
109 Warning::InvalidLengthMax { length: MAX_LEN },
110 );
111 }
112
113 Ok(Self(s.0).into_caveat(warnings))
114 }
115}
116
117#[derive(Copy, Clone, Debug)]
125pub(crate) struct CiExactLen<'buf, const LEN: usize>(&'buf str);
126
127impl<const LEN: usize> Deref for CiExactLen<'_, LEN> {
128 type Target = str;
129
130 fn deref(&self) -> &Self::Target {
131 self.0
132 }
133}
134
135impl<const LEN: usize> fmt::Display for CiExactLen<'_, LEN> {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 write!(f, "{}", self.0)
138 }
139}
140
141impl<'buf, const LEN: usize> FromSchema<'buf, schema::Str<'buf>> for CiExactLen<'buf, LEN> {
142 type Warning = Warning;
143
144 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
145 let (s, mut warnings) = Base::from_schema(source)?.into_parts();
146
147 if s.len() != LEN {
148 warnings.insert(
149 source.element(),
150 Warning::InvalidLengthExact { length: LEN },
151 );
152 }
153
154 Ok(Self(s.0).into_caveat(warnings))
155 }
156}
157
158#[derive(Copy, Clone, Debug)]
163struct Base<'buf>(&'buf str);
164
165impl Deref for Base<'_> {
166 type Target = str;
167
168 fn deref(&self) -> &Self::Target {
169 self.0
170 }
171}
172
173impl fmt::Display for Base<'_> {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 write!(f, "{}", self.0)
176 }
177}
178
179impl<'buf> FromSchema<'buf, schema::Str<'buf>> for Base<'buf> {
180 type Warning = Warning;
181
182 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
183 let mut warnings = warning::Set::new();
184 let elem = source.element();
185 let raw = source.value();
186
187 let issues = raw.lexical_issues();
191 if issues.escapes {
192 warnings.insert(elem, Warning::ContainsEscapeCodes);
193 }
194 if issues.non_printable_ascii {
195 warnings.insert(elem, Warning::ContainsNonPrintableASCII);
196 }
197
198 Ok(Self(raw.as_unescaped_str()).into_caveat(warnings))
199 }
200}
201
202pub(crate) struct SizeExceedsMax(());
204
205impl std::error::Error for SizeExceedsMax {}
206
207impl fmt::Debug for SizeExceedsMax {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 f.debug_tuple("SizeExceedsMax").finish()
210 }
211}
212
213impl fmt::Display for SizeExceedsMax {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 write!(
216 f,
217 "The size of the input string exceeds the maximum length of {} megabytes",
218 ReasonableLen::FACTOR
219 )
220 }
221}
222
223#[derive(Copy, Clone)]
225pub(crate) struct ReasonableLen<'buf>(&'buf str);
226
227impl<'buf> ReasonableLen<'buf> {
228 const MEGA: usize = 1_000_000;
230
231 pub(crate) const FACTOR: usize = 5;
233
234 pub(crate) const MAX_STR_INPUT_LEN: usize = Self::FACTOR * Self::MEGA;
244
245 pub(crate) fn new(s: &'buf str) -> Result<ReasonableLen<'buf>, SizeExceedsMax> {
247 if s.len() >= Self::MAX_STR_INPUT_LEN {
248 return Err(SizeExceedsMax(()));
249 }
250
251 Ok(Self(s))
252 }
253
254 pub(crate) fn into_inner(self) -> &'buf str {
256 self.0
257 }
258}