1use std::{borrow::Cow, fmt};
4
5use crate::{
6 json,
7 warning::{self, IntoCaveat},
8 Caveat, Verdict,
9};
10
11#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
13pub enum WarningKind {
14 ContainsEscapeCodes,
16
17 ContainsNonPrintableASCII,
19
20 InvalidType,
22
23 ExceedsMaxLength,
25}
26
27impl fmt::Display for WarningKind {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 WarningKind::ContainsEscapeCodes => f.write_str("The string contains escape codes."),
31 WarningKind::ContainsNonPrintableASCII => {
32 f.write_str("The string contains non-printable bytes.")
33 }
34 WarningKind::ExceedsMaxLength => {
35 f.write_str("The string is longer than the max length defined in the spec.")
36 }
37 WarningKind::InvalidType => f.write_str("The value should be a string."),
38 }
39 }
40}
41
42impl warning::Kind for WarningKind {
43 fn id(&self) -> Cow<'static, str> {
44 match self {
45 WarningKind::ContainsEscapeCodes => "contains_escape_codes".into(),
46 WarningKind::ContainsNonPrintableASCII => "contains_non_printable_ascii".into(),
47 WarningKind::ExceedsMaxLength => "exceeds_max_len".into(),
48 WarningKind::InvalidType => "invalid_type".into(),
49 }
50 }
51}
52
53#[derive(Copy, Clone, Debug)]
58#[expect(dead_code, reason = "CiString will be used in mod generate")]
59pub(crate) struct CiString<'buf, const BYTE_LEN: usize>(&'buf str);
60
61impl<const BYTE_LEN: usize> fmt::Display for CiString<'_, BYTE_LEN> {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(f, "{}", self.0)
64 }
65}
66
67impl<const BYTE_LEN: usize> IntoCaveat for CiString<'_, BYTE_LEN> {
68 fn into_caveat<K: warning::Kind>(self, warnings: warning::Set<K>) -> Caveat<Self, K> {
69 Caveat::new(self, warnings)
70 }
71}
72
73impl<'buf, 'elem: 'buf, const BYTE_LEN: usize> json::FromJson<'elem, 'buf>
74 for CiString<'buf, BYTE_LEN>
75{
76 type WarningKind = WarningKind;
77
78 fn from_json(elem: &'elem json::Element<'buf>) -> Verdict<Self, Self::WarningKind> {
79 let mut warnings = warning::Set::new();
80 let mut check_len_and_printable = |s: &str| {
81 if s.len() > BYTE_LEN {
82 warnings.with_elem(WarningKind::ExceedsMaxLength, elem);
83 }
84
85 if s.chars()
86 .any(|c| c.is_ascii_whitespace() || c.is_ascii_control())
87 {
88 warnings.with_elem(WarningKind::ContainsNonPrintableASCII, elem);
89 }
90 };
91 let Some(id) = elem.as_raw_str() else {
92 warnings.with_elem(WarningKind::InvalidType, elem);
93 return Err(warnings);
94 };
95
96 let id = id.has_escapes(elem).ignore_warnings();
99 let id = match id {
100 json::decode::PendingStr::NoEscapes(s) => {
101 check_len_and_printable(s);
102 s
103 }
104 json::decode::PendingStr::HasEscapes(escape_str) => {
105 let decoded = escape_str.decode_escapes(elem).ignore_warnings();
107 check_len_and_printable(&decoded);
108 escape_str.into_raw()
109 }
110 };
111
112 Ok(CiString(id).into_caveat(warnings))
113 }
114}