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 {
28 length: usize,
30 },
31
32 InvalidLengthExact {
34 length: usize,
36 },
37
38 IncorrectCase,
43}
44
45impl fmt::Display for Warning {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 Self::ContainsEscapeCodes => f.write_str("The string contains escape codes."),
49 Self::ContainsNonPrintableASCII => {
50 f.write_str("The string contains non-printable bytes.")
51 }
52 Self::InvalidLengthMax { length } => {
53 write!(
54 f,
55 "The string is longer than the max length `{length}` defined in the spec.",
56 )
57 }
58 Self::InvalidLengthExact { length } => {
59 write!(f, "The string should be length `{length}`.")
60 }
61 Self::IncorrectCase => {
62 write!(f, "Upper case is preferred")
63 }
64 }
65 }
66}
67
68impl crate::Warning for Warning {
69 fn id(&self) -> warning::Id {
70 match self {
71 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
72 Self::ContainsNonPrintableASCII => {
73 warning::Id::from_static("contains_non_printable_ascii")
74 }
75 Self::InvalidLengthMax { .. } => warning::Id::from_static("invalid_length_max"),
76 Self::InvalidLengthExact { .. } => warning::Id::from_static("invalid_length_exact"),
77 Self::IncorrectCase => warning::Id::from_static("incorrect_case"),
78 }
79 }
80}
81
82#[derive(Copy, Clone, Debug)]
90pub(crate) struct CiMaxLen<'buf, const MAX_LEN: usize>(&'buf str);
91
92impl<const MAX_LEN: usize> Deref for CiMaxLen<'_, MAX_LEN> {
93 type Target = str;
94
95 fn deref(&self) -> &Self::Target {
96 self.0
97 }
98}
99
100impl<const MAX_LEN: usize> fmt::Display for CiMaxLen<'_, MAX_LEN> {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 write!(f, "{}", self.0)
103 }
104}
105
106impl<'buf, const MAX_LEN: usize> FromSchema<'buf, schema::Str<'buf>> for CiMaxLen<'buf, MAX_LEN> {
107 type Warning = Warning;
108
109 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
110 let (s, mut warnings) = Base::from_schema(source)?.into_parts();
111
112 if s.len() > MAX_LEN {
113 warnings.insert(
114 source.element(),
115 Warning::InvalidLengthMax { length: MAX_LEN },
116 );
117 }
118
119 Ok(Self(s.0).into_caveat(warnings))
120 }
121}
122
123#[derive(Copy, Clone, Debug)]
131pub(crate) struct CiExactLen<'buf, const LEN: usize>(&'buf str);
132
133impl<const LEN: usize> Deref for CiExactLen<'_, LEN> {
134 type Target = str;
135
136 fn deref(&self) -> &Self::Target {
137 self.0
138 }
139}
140
141impl<const LEN: usize> fmt::Display for CiExactLen<'_, LEN> {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 write!(f, "{}", self.0)
144 }
145}
146
147impl<'buf, const LEN: usize> FromSchema<'buf, schema::Str<'buf>> for CiExactLen<'buf, LEN> {
148 type Warning = Warning;
149
150 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
151 let (s, mut warnings) = Base::from_schema(source)?.into_parts();
152
153 if s.len() != LEN {
154 warnings.insert(
155 source.element(),
156 Warning::InvalidLengthExact { length: LEN },
157 );
158 }
159
160 Ok(Self(s.0).into_caveat(warnings))
161 }
162}
163
164#[derive(Copy, Clone, Debug)]
169struct Base<'buf>(&'buf str);
170
171impl Deref for Base<'_> {
172 type Target = str;
173
174 fn deref(&self) -> &Self::Target {
175 self.0
176 }
177}
178
179impl fmt::Display for Base<'_> {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 write!(f, "{}", self.0)
182 }
183}
184
185impl<'buf> FromSchema<'buf, schema::Str<'buf>> for Base<'buf> {
186 type Warning = Warning;
187
188 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
189 let mut warnings = warning::Set::new();
190 let elem = source.element();
191 let raw = source.value();
192
193 let issues = raw.lexical_issues();
197 if issues.escapes {
198 warnings.insert(elem, Warning::ContainsEscapeCodes);
199 }
200 if issues.non_printable_ascii {
201 warnings.insert(elem, Warning::ContainsNonPrintableASCII);
202 }
203
204 Ok(Self(raw.as_unescaped_str()).into_caveat(warnings))
205 }
206}
207
208pub(crate) struct SizeExceedsMax(());
210
211impl std::error::Error for SizeExceedsMax {}
212
213impl fmt::Debug for SizeExceedsMax {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 f.debug_tuple("SizeExceedsMax").finish()
216 }
217}
218
219impl fmt::Display for SizeExceedsMax {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 write!(
222 f,
223 "The size of the input string exceeds the maximum length of {} megabytes",
224 ReasonableLen::FACTOR
225 )
226 }
227}
228
229#[derive(Copy, Clone)]
231pub(crate) struct ReasonableLen<'buf>(&'buf str);
232
233impl<'buf> ReasonableLen<'buf> {
234 const MEGA: usize = 1_000_000;
236
237 pub(crate) const FACTOR: usize = 5;
239
240 pub(crate) const MAX_STR_INPUT_LEN: usize = Self::FACTOR * Self::MEGA;
250
251 pub(crate) fn new(s: &'buf str) -> Result<ReasonableLen<'buf>, SizeExceedsMax> {
253 if s.len() >= Self::MAX_STR_INPUT_LEN {
254 return Err(SizeExceedsMax(()));
255 }
256
257 Ok(Self(s))
258 }
259
260 pub(crate) fn into_inner(self) -> &'buf str {
262 self.0
263 }
264}