1#[cfg(test)]
4mod test;
5
6use std::{fmt, ops::Deref};
7
8use crate::{
9 json,
10 warning::{self, IntoCaveat as _},
11 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 {
42 Self::InvalidType {
43 type_found: elem.value().kind(),
44 }
45 }
46}
47
48impl fmt::Display for Warning {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 Self::ContainsEscapeCodes => f.write_str("The string contains escape codes."),
52 Self::ContainsNonPrintableASCII => {
53 f.write_str("The string contains non-printable bytes.")
54 }
55 Self::InvalidType { type_found } => {
56 write!(f, "The value should be a string but is `{type_found}`")
57 }
58 Self::InvalidLengthMax { length } => {
59 write!(
60 f,
61 "The string is longer than the max length `{length}` defined in the spec.",
62 )
63 }
64 Self::InvalidLengthExact { length } => {
65 write!(f, "The string should be length `{length}`.")
66 }
67 Self::PreferUppercase => {
68 write!(f, "Upper case is preferred")
69 }
70 }
71 }
72}
73
74impl crate::Warning for Warning {
75 fn id(&self) -> warning::Id {
76 match self {
77 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
78 Self::ContainsNonPrintableASCII => {
79 warning::Id::from_static("contains_non_printable_ascii")
80 }
81 Self::InvalidType { type_found } => {
82 warning::Id::from_string(format!("invalid_type({type_found})"))
83 }
84 Self::InvalidLengthMax { .. } => warning::Id::from_static("invalid_length_max"),
85 Self::InvalidLengthExact { .. } => warning::Id::from_static("invalid_length_exact"),
86 Self::PreferUppercase => warning::Id::from_static("prefer_upper_case"),
87 }
88 }
89}
90
91#[derive(Copy, Clone, Debug)]
99pub(crate) struct CiMaxLen<'buf, const MAX_LEN: usize>(&'buf str);
100
101impl<const MAX_LEN: usize> Deref for CiMaxLen<'_, MAX_LEN> {
102 type Target = str;
103
104 fn deref(&self) -> &Self::Target {
105 self.0
106 }
107}
108
109impl<const MAX_LEN: usize> fmt::Display for CiMaxLen<'_, MAX_LEN> {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 write!(f, "{}", self.0)
112 }
113}
114
115impl<'buf, const MAX_LEN: usize> json::FromJson<'buf> for CiMaxLen<'buf, MAX_LEN> {
116 type Warning = Warning;
117
118 fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
119 let (s, mut warnings) = Base::from_json(elem)?.into_parts();
120
121 if s.len() > MAX_LEN {
122 warnings.insert(Warning::InvalidLengthMax { length: MAX_LEN }, elem);
123 }
124
125 Ok(Self(s.0).into_caveat(warnings))
126 }
127}
128
129#[derive(Copy, Clone, Debug)]
137pub(crate) struct CiExactLen<'buf, const LEN: usize>(&'buf str);
138
139impl<const LEN: usize> Deref for CiExactLen<'_, LEN> {
140 type Target = str;
141
142 fn deref(&self) -> &Self::Target {
143 self.0
144 }
145}
146
147impl<const LEN: usize> fmt::Display for CiExactLen<'_, LEN> {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 write!(f, "{}", self.0)
150 }
151}
152
153impl<'buf, const LEN: usize> json::FromJson<'buf> for CiExactLen<'buf, LEN> {
154 type Warning = Warning;
155
156 fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
157 let (s, mut warnings) = Base::from_json(elem)?.into_parts();
158
159 if s.len() != LEN {
160 warnings.insert(Warning::InvalidLengthExact { length: LEN }, elem);
161 }
162
163 Ok(Self(s.0).into_caveat(warnings))
164 }
165}
166
167#[derive(Copy, Clone, Debug)]
172struct Base<'buf>(&'buf str);
173
174impl Deref for Base<'_> {
175 type Target = str;
176
177 fn deref(&self) -> &Self::Target {
178 self.0
179 }
180}
181
182impl fmt::Display for Base<'_> {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 write!(f, "{}", self.0)
185 }
186}
187
188impl<'buf> json::FromJson<'buf> for Base<'buf> {
189 type Warning = Warning;
190
191 fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
192 let mut warnings = warning::Set::new();
193 let Some(id) = elem.to_raw_str() else {
194 return warnings.bail(Warning::invalid_type(elem), elem);
195 };
196
197 let s = id.has_escapes(elem).ignore_warnings();
200 let s = match s {
201 json::decode::PendingStr::NoEscapes(s) => {
202 if check_printable(s) {
203 warnings.insert(Warning::ContainsNonPrintableASCII, elem);
204 }
205 s
206 }
207 json::decode::PendingStr::HasEscapes(escape_str) => {
208 warnings.insert(Warning::ContainsEscapeCodes, elem);
209 let decoded = escape_str.decode_escapes(elem).ignore_warnings();
211
212 if check_printable(&decoded) {
213 warnings.insert(Warning::ContainsNonPrintableASCII, elem);
214 }
215
216 escape_str.into_raw()
217 }
218 };
219
220 Ok(Self(s).into_caveat(warnings))
221 }
222}
223
224fn check_printable(s: &str) -> bool {
227 s.chars()
228 .any(|c| c.is_ascii_whitespace() || c.is_ascii_control())
229}