1#[cfg(test)]
4mod test;
5
6#[cfg(test)]
7mod test_from_schema;
8
9#[cfg(test)]
10mod test_reasonable_str;
11
12use std::{fmt, ops::Deref};
13
14use crate::{
15 json, schema,
16 warning::{self, IntoCaveat as _},
17 FromSchema, Verdict,
18};
19
20#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
22pub enum Warning {
23 ContainsEscapeCodes,
25
26 ContainsNonPrintableASCII,
28
29 InvalidType { type_found: json::ValueKind },
31
32 InvalidLengthMax { length: usize },
34
35 InvalidLengthExact { length: usize },
37
38 PreferUppercase,
43}
44
45impl Warning {
46 fn invalid_type(elem: &json::Element<'_>) -> Self {
48 Self::InvalidType {
49 type_found: elem.value().kind(),
50 }
51 }
52}
53
54impl fmt::Display for Warning {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 Self::ContainsEscapeCodes => f.write_str("The string contains escape codes."),
58 Self::ContainsNonPrintableASCII => {
59 f.write_str("The string contains non-printable bytes.")
60 }
61 Self::InvalidType { type_found } => {
62 write!(f, "The value should be a string but is `{type_found}`")
63 }
64 Self::InvalidLengthMax { length } => {
65 write!(
66 f,
67 "The string is longer than the max length `{length}` defined in the spec.",
68 )
69 }
70 Self::InvalidLengthExact { length } => {
71 write!(f, "The string should be length `{length}`.")
72 }
73 Self::PreferUppercase => {
74 write!(f, "Upper case is preferred")
75 }
76 }
77 }
78}
79
80impl crate::Warning for Warning {
81 fn id(&self) -> warning::Id {
82 match self {
83 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
84 Self::ContainsNonPrintableASCII => {
85 warning::Id::from_static("contains_non_printable_ascii")
86 }
87 Self::InvalidType { type_found } => {
88 warning::Id::from_string(format!("invalid_type({type_found})"))
89 }
90 Self::InvalidLengthMax { .. } => warning::Id::from_static("invalid_length_max"),
91 Self::InvalidLengthExact { .. } => warning::Id::from_static("invalid_length_exact"),
92 Self::PreferUppercase => warning::Id::from_static("prefer_upper_case"),
93 }
94 }
95}
96
97#[derive(Copy, Clone, Debug)]
105pub(crate) struct CiMaxLen<'buf, const MAX_LEN: usize>(&'buf str);
106
107impl<const MAX_LEN: usize> Deref for CiMaxLen<'_, MAX_LEN> {
108 type Target = str;
109
110 fn deref(&self) -> &Self::Target {
111 self.0
112 }
113}
114
115impl<const MAX_LEN: usize> fmt::Display for CiMaxLen<'_, MAX_LEN> {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 write!(f, "{}", self.0)
118 }
119}
120
121impl<'buf, const MAX_LEN: usize> json::FromJson<'buf> for CiMaxLen<'buf, MAX_LEN> {
122 type Warning = Warning;
123
124 fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
125 let (s, mut warnings) = Base::from_json(elem)?.into_parts();
126
127 if s.len() > MAX_LEN {
128 warnings.insert(elem, Warning::InvalidLengthMax { length: MAX_LEN });
129 }
130
131 Ok(Self(s.0).into_caveat(warnings))
132 }
133}
134
135impl<'buf, const MAX_LEN: usize> FromSchema<'buf, schema::Str<'buf>> for CiMaxLen<'buf, MAX_LEN> {
136 type Warning = Warning;
137
138 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
139 let (s, mut warnings) = Base::from_schema(source)?.into_parts();
140
141 if s.len() > MAX_LEN {
142 warnings.insert(
143 source.element(),
144 Warning::InvalidLengthMax { length: MAX_LEN },
145 );
146 }
147
148 Ok(Self(s.0).into_caveat(warnings))
149 }
150}
151
152#[derive(Copy, Clone, Debug)]
160pub(crate) struct CiExactLen<'buf, const LEN: usize>(&'buf str);
161
162impl<const LEN: usize> Deref for CiExactLen<'_, LEN> {
163 type Target = str;
164
165 fn deref(&self) -> &Self::Target {
166 self.0
167 }
168}
169
170impl<const LEN: usize> fmt::Display for CiExactLen<'_, LEN> {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 write!(f, "{}", self.0)
173 }
174}
175
176impl<'buf, const LEN: usize> json::FromJson<'buf> for CiExactLen<'buf, LEN> {
177 type Warning = Warning;
178
179 fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
180 let (s, mut warnings) = Base::from_json(elem)?.into_parts();
181
182 if s.len() != LEN {
183 warnings.insert(elem, Warning::InvalidLengthExact { length: LEN });
184 }
185
186 Ok(Self(s.0).into_caveat(warnings))
187 }
188}
189
190impl<'buf, const LEN: usize> FromSchema<'buf, schema::Str<'buf>> for CiExactLen<'buf, LEN> {
191 type Warning = Warning;
192
193 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
194 let (s, mut warnings) = Base::from_schema(source)?.into_parts();
195
196 if s.len() != LEN {
197 warnings.insert(
198 source.element(),
199 Warning::InvalidLengthExact { length: LEN },
200 );
201 }
202
203 Ok(Self(s.0).into_caveat(warnings))
204 }
205}
206
207#[derive(Copy, Clone, Debug)]
212struct Base<'buf>(&'buf str);
213
214impl Deref for Base<'_> {
215 type Target = str;
216
217 fn deref(&self) -> &Self::Target {
218 self.0
219 }
220}
221
222impl fmt::Display for Base<'_> {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 write!(f, "{}", self.0)
225 }
226}
227
228impl<'buf> json::FromJson<'buf> for Base<'buf> {
229 type Warning = Warning;
230
231 fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
232 let mut warnings = warning::Set::new();
233 let Some(raw) = elem.to_raw_str() else {
234 return warnings.bail(elem, Warning::invalid_type(elem));
235 };
236
237 let issues = raw.lexical_issues();
240 if issues.escapes {
241 warnings.insert(elem, Warning::ContainsEscapeCodes);
242 }
243 if issues.non_printable_ascii {
244 warnings.insert(elem, Warning::ContainsNonPrintableASCII);
245 }
246
247 Ok(Self(raw.as_unescaped_str()).into_caveat(warnings))
248 }
249}
250
251impl<'buf> FromSchema<'buf, schema::Str<'buf>> for Base<'buf> {
252 type Warning = Warning;
253
254 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
255 let mut warnings = warning::Set::new();
256 let elem = source.element();
257 let raw = source.value();
258
259 let issues = raw.lexical_issues();
263 if issues.escapes {
264 warnings.insert(elem, Warning::ContainsEscapeCodes);
265 }
266 if issues.non_printable_ascii {
267 warnings.insert(elem, Warning::ContainsNonPrintableASCII);
268 }
269
270 Ok(Self(raw.as_unescaped_str()).into_caveat(warnings))
271 }
272}
273
274pub(crate) struct SizeExceedsMax(());
276
277impl std::error::Error for SizeExceedsMax {}
278
279impl fmt::Debug for SizeExceedsMax {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 f.debug_tuple("SizeExceedsMax").finish()
282 }
283}
284
285impl fmt::Display for SizeExceedsMax {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 write!(
288 f,
289 "The size of the input string exceeds the maximum length of {} megabytes",
290 ReasonableLen::FACTOR
291 )
292 }
293}
294
295#[derive(Copy, Clone)]
297pub(crate) struct ReasonableLen<'buf>(&'buf str);
298
299impl<'buf> ReasonableLen<'buf> {
300 const MEGA: usize = 1_000_000;
302
303 pub(crate) const FACTOR: usize = 5;
305
306 pub(crate) const MAX_STR_INPUT_LEN: usize = Self::FACTOR * Self::MEGA;
316
317 pub(crate) fn new(s: &'buf str) -> Result<ReasonableLen<'buf>, SizeExceedsMax> {
319 if s.len() >= Self::MAX_STR_INPUT_LEN {
320 return Err(SizeExceedsMax(()));
321 }
322
323 Ok(Self(s))
324 }
325
326 pub(crate) fn into_inner(self) -> &'buf str {
328 self.0
329 }
330}