1#[cfg(test)]
4mod test;
5
6use std::{fmt, ops::Deref};
7
8use crate::{
9 json,
10 warning::{self, IntoCaveat},
11 Caveat, Verdict,
12};
13
14#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
16pub enum Warning {
17 ContainsEscapeCodes,
19
20 ContainsNonPrintableASCII,
22
23 InvalidType { type_found: json::ValueKind },
25
26 InvalidLengthMax { length: usize },
28
29 InvalidLengthExact { length: usize },
31
32 PreferUppercase,
37}
38
39impl Warning {
40 fn invalid_type(elem: &json::Element<'_>) -> Self {
41 Self::InvalidType {
42 type_found: elem.value().kind(),
43 }
44 }
45}
46
47impl fmt::Display for Warning {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 Self::ContainsEscapeCodes => f.write_str("The string contains escape codes."),
51 Self::ContainsNonPrintableASCII => {
52 f.write_str("The string contains non-printable bytes.")
53 }
54 Self::InvalidType { type_found } => {
55 write!(f, "The value should be a string but is `{type_found}`")
56 }
57 Self::InvalidLengthMax { length } => {
58 write!(
59 f,
60 "The string is longer than the max length `{length}` defined in the spec.",
61 )
62 }
63 Self::InvalidLengthExact { length } => {
64 write!(f, "The string should be length `{length}`.")
65 }
66 Self::PreferUppercase => {
67 write!(f, "Upper case is preffered")
68 }
69 }
70 }
71}
72
73impl crate::Warning for Warning {
74 fn id(&self) -> warning::Id {
75 match self {
76 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
77 Self::ContainsNonPrintableASCII => {
78 warning::Id::from_static("contains_non_printable_ascii")
79 }
80 Self::InvalidType { .. } => warning::Id::from_static("invalid_type"),
81 Self::InvalidLengthMax { .. } => warning::Id::from_static("invalid_length_max"),
82 Self::InvalidLengthExact { .. } => warning::Id::from_static("invalid_length_exact"),
83 Self::PreferUppercase => warning::Id::from_static("prefer_upper_case"),
84 }
85 }
86}
87
88#[derive(Copy, Clone, Debug)]
96pub(crate) struct CiMaxLen<'buf, const MAX_LEN: usize>(&'buf str);
97
98impl<const MAX_LEN: usize> Deref for CiMaxLen<'_, MAX_LEN> {
99 type Target = str;
100
101 fn deref(&self) -> &Self::Target {
102 self.0
103 }
104}
105
106impl<const MAX_LEN: usize> fmt::Display for CiMaxLen<'_, MAX_LEN> {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 write!(f, "{}", self.0)
109 }
110}
111
112impl<const MAX_LEN: usize> IntoCaveat for CiMaxLen<'_, MAX_LEN> {
113 fn into_caveat<W: crate::Warning>(self, warnings: warning::Set<W>) -> Caveat<Self, W> {
114 Caveat::new(self, warnings)
115 }
116}
117
118impl<'buf, 'elem: 'buf, const MAX_LEN: usize> json::FromJson<'elem, 'buf>
119 for CiMaxLen<'buf, MAX_LEN>
120{
121 type Warning = Warning;
122
123 fn from_json(elem: &'elem json::Element<'buf>) -> Verdict<Self, Self::Warning> {
124 let (s, mut warnings) = Base::from_json(elem)?.into_parts();
125
126 if s.len() > MAX_LEN {
127 warnings.insert(Warning::InvalidLengthMax { length: MAX_LEN }, elem);
128 }
129
130 Ok(Self(s.0).into_caveat(warnings))
131 }
132}
133
134#[derive(Copy, Clone, Debug)]
142pub(crate) struct CiExactLen<'buf, const LEN: usize>(&'buf str);
143
144impl<const LEN: usize> Deref for CiExactLen<'_, LEN> {
145 type Target = str;
146
147 fn deref(&self) -> &Self::Target {
148 self.0
149 }
150}
151
152impl<const LEN: usize> fmt::Display for CiExactLen<'_, LEN> {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 write!(f, "{}", self.0)
155 }
156}
157
158impl<const LEN: usize> IntoCaveat for CiExactLen<'_, LEN> {
159 fn into_caveat<W: crate::Warning>(self, warnings: warning::Set<W>) -> Caveat<Self, W> {
160 Caveat::new(self, warnings)
161 }
162}
163
164impl<'buf, 'elem: 'buf, const LEN: usize> json::FromJson<'elem, 'buf> for CiExactLen<'buf, LEN> {
165 type Warning = Warning;
166
167 fn from_json(elem: &'elem json::Element<'buf>) -> Verdict<Self, Self::Warning> {
168 let (s, mut warnings) = Base::from_json(elem)?.into_parts();
169
170 if s.len() != LEN {
171 warnings.insert(Warning::InvalidLengthExact { length: LEN }, elem);
172 }
173
174 Ok(Self(s.0).into_caveat(warnings))
175 }
176}
177
178#[derive(Copy, Clone, Debug)]
183struct Base<'buf>(&'buf str);
184
185impl Deref for Base<'_> {
186 type Target = str;
187
188 fn deref(&self) -> &Self::Target {
189 self.0
190 }
191}
192
193impl fmt::Display for Base<'_> {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 write!(f, "{}", self.0)
196 }
197}
198
199impl IntoCaveat for Base<'_> {
200 fn into_caveat<W: crate::Warning>(self, warnings: warning::Set<W>) -> Caveat<Self, W> {
201 Caveat::new(self, warnings)
202 }
203}
204
205impl<'buf, 'elem: 'buf> json::FromJson<'elem, 'buf> for Base<'buf> {
206 type Warning = Warning;
207
208 fn from_json(elem: &'elem json::Element<'buf>) -> Verdict<Self, Self::Warning> {
209 let mut warnings = warning::Set::new();
210 let Some(id) = elem.as_raw_str() else {
211 return warnings.bail(Warning::invalid_type(elem), elem);
212 };
213
214 let s = id.has_escapes(elem).ignore_warnings();
217 let s = match s {
218 json::decode::PendingStr::NoEscapes(s) => {
219 if check_printable(s) {
220 warnings.insert(Warning::ContainsNonPrintableASCII, elem);
221 }
222 s
223 }
224 json::decode::PendingStr::HasEscapes(escape_str) => {
225 warnings.insert(Warning::ContainsEscapeCodes, elem);
226 let decoded = escape_str.decode_escapes(elem).ignore_warnings();
228
229 if check_printable(&decoded) {
230 warnings.insert(Warning::ContainsNonPrintableASCII, elem);
231 }
232
233 escape_str.into_raw()
234 }
235 };
236
237 Ok(Self(s).into_caveat(warnings))
238 }
239}
240
241fn check_printable(s: &str) -> bool {
242 s.chars()
243 .any(|c| c.is_ascii_whitespace() || c.is_ascii_control())
244}