1use std::borrow::Cow;
6use std::fmt;
7
8use serde_json::{Map, Value};
9
10use crate::HapError;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14#[non_exhaustive]
15pub struct Note {
16 pub path: String,
18 pub message: String,
20}
21
22impl fmt::Display for Note {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "{}: {}", self.path, self.message)
25 }
26}
27
28#[derive(Debug, Default)]
30pub(crate) struct Findings {
31 pub notes: Vec<Note>,
32 pub unmodelled: Vec<String>,
34}
35
36pub(crate) fn parse_object(bytes: &[u8]) -> Result<Map<String, Value>, HapError> {
39 let bytes = bytes.strip_prefix(b"\xef\xbb\xbf").unwrap_or(bytes);
40 match serde_json::from_slice(bytes)? {
41 Value::Object(map) => Ok(map),
42 other => Err(HapError::NotAnObject { found: kind(&other) }),
43 }
44}
45
46pub(crate) fn pretty(raw: &Value) -> String {
49 let mut text = serde_json::to_string_pretty(raw).expect("a serde_json::Value always serializes");
50 text.push('\n');
51 text
52}
53
54pub(crate) fn kind(value: &Value) -> &'static str {
55 match value {
56 Value::Null => "null",
57 Value::Bool(_) => "boolean",
58 Value::Number(_) => "number",
59 Value::String(_) => "string",
60 Value::Array(_) => "array",
61 Value::Object(_) => "object",
62 }
63}
64
65pub(crate) struct Obj<'v, 'f> {
68 map: &'v Map<String, Value>,
69 path: String,
70 findings: &'f mut Findings,
71 read: Vec<&'static str>,
72}
73
74impl<'v, 'f> Obj<'v, 'f> {
75 pub(crate) fn root(map: &'v Map<String, Value>, findings: &'f mut Findings) -> Self {
76 Obj { map, path: String::new(), findings, read: Vec::new() }
77 }
78
79 pub(crate) fn to_value(&self) -> Value {
80 Value::Object(self.map.clone())
81 }
82
83 fn child_path(&self, key: &str) -> String {
84 if self.path.is_empty() { key.to_owned() } else { format!("{}.{key}", self.path) }
85 }
86
87 fn get(&mut self, key: &'static str) -> Option<&'v Value> {
89 self.read.push(key);
90 self.map.get(key).filter(|v| !v.is_null())
91 }
92
93 fn mismatch(&mut self, path: String, expected: &str, found: &Value) {
94 self.findings.notes.push(Note { path, message: format!("expected {expected}, found {}", kind(found)) });
95 }
96
97 fn typed<T>(&mut self, key: &'static str, expected: &str, pick: impl FnOnce(&'v Value) -> Option<T>) -> Option<T> {
98 let value = self.get(key)?;
99 let picked = pick(value);
100 if picked.is_none() {
101 self.mismatch(self.child_path(key), expected, value);
102 }
103 picked
104 }
105
106 pub(crate) fn str(&mut self, key: &'static str) -> Option<String> {
107 self.typed(key, "string", |v| v.as_str().map(str::to_owned))
108 }
109
110 pub(crate) fn text(&mut self, key: &'static str) -> Option<String> {
113 self.typed(key, "string, number or boolean", |v| match v {
114 Value::String(s) => Some(s.clone()),
115 Value::Number(n) => Some(n.to_string()),
116 Value::Bool(b) => Some(b.to_string()),
117 Value::Null | Value::Array(_) | Value::Object(_) => None,
118 })
119 }
120
121 pub(crate) fn bool(&mut self, key: &'static str) -> Option<bool> {
122 self.typed(key, "boolean", Value::as_bool)
123 }
124
125 pub(crate) fn int(&mut self, key: &'static str) -> Option<i64> {
126 self.typed(key, "integer", Value::as_i64)
127 }
128
129 pub(crate) fn strings(&mut self, key: &'static str) -> Vec<String> {
131 let Some(items) = self.typed(key, "array", Value::as_array) else {
132 return Vec::new();
133 };
134 let mut out = Vec::with_capacity(items.len());
135 for (i, item) in items.iter().enumerate() {
136 match item.as_str() {
137 Some(s) => out.push(s.to_owned()),
138 None => self.mismatch(format!("{}[{i}]", self.child_path(key)), "string", item),
139 }
140 }
141 out
142 }
143
144 pub(crate) fn object<T>(&mut self, key: &'static str, read: impl FnOnce(&mut Obj<'v, '_>) -> T) -> Option<T> {
145 let map = self.typed(key, "object", Value::as_object)?;
146 Some(self.descend(map, self.child_path(key), read))
147 }
148
149 pub(crate) fn objects<T>(&mut self, key: &'static str, mut read: impl FnMut(&mut Obj<'v, '_>) -> T) -> Vec<T> {
151 let Some(items) = self.typed(key, "array", Value::as_array) else {
152 return Vec::new();
153 };
154 let mut out = Vec::with_capacity(items.len());
155 for (i, item) in items.iter().enumerate() {
156 let path = format!("{}[{i}]", self.child_path(key));
157 match item.as_object() {
158 Some(map) => out.push(self.descend(map, path, &mut read)),
159 None => self.mismatch(path, "object", item),
160 }
161 }
162 out
163 }
164
165 fn descend<T>(&mut self, map: &'v Map<String, Value>, path: String, read: impl FnOnce(&mut Obj<'v, '_>) -> T) -> T {
166 let mut child = Obj { map, path, findings: self.findings, read: Vec::new() };
167 let value = read(&mut child);
168 child.finish();
169 value
170 }
171
172 pub(crate) fn ignore(&mut self, keys: &[&'static str]) {
174 self.read.extend_from_slice(keys);
175 }
176
177 pub(crate) fn finish(self) {
181 for key in self.map.keys() {
182 let key = key.as_str();
183 let known = |k: &str| self.read.contains(&k);
184 if known(key) || key.strip_suffix("Id").is_some_and(known) {
185 continue;
186 }
187 self.findings.unmodelled.push(self.child_path(key));
188 }
189 }
190}
191
192pub(crate) fn token(text: &str) -> Cow<'_, str> {
195 if !text.is_empty() && !text.chars().any(|c| c.is_whitespace() || asc_query::text::must_escape(c) || c == '"') {
196 return Cow::Borrowed(text);
197 }
198 Cow::Owned(asc_query::text::quoted(text))
199}
200
201pub(crate) struct Line(String);
203
204impl Line {
205 pub(crate) fn new(record: &str) -> Self {
206 Line(record.to_owned())
207 }
208
209 #[must_use]
211 pub(crate) fn word(mut self, text: &str) -> Self {
212 self.0.push(' ');
213 self.0.push_str(&token(text));
214 self
215 }
216
217 #[must_use]
218 pub(crate) fn raw(mut self, key: &str, value: impl fmt::Display) -> Self {
219 self.0.push_str(&format!(" {key}={value}"));
220 self
221 }
222
223 #[must_use]
224 pub(crate) fn opt(self, key: &str, value: Option<&str>) -> Self {
225 match value {
226 Some(v) => {
227 let v = token(v);
228 self.raw(key, v)
229 }
230 None => self.raw(key, "-"),
231 }
232 }
233
234 #[must_use]
235 pub(crate) fn flag(self, key: &str, value: Option<bool>) -> Self {
236 self.num(key, value)
237 }
238
239 #[must_use]
240 pub(crate) fn num<T: fmt::Display>(self, key: &str, value: Option<T>) -> Self {
241 match value {
242 Some(v) => self.raw(key, v),
243 None => self.raw(key, "-"),
244 }
245 }
246
247 #[must_use]
248 pub(crate) fn list(self, key: &str, values: &[String]) -> Self {
249 if values.is_empty() {
250 return self.raw(key, "-");
251 }
252 let joined = values.iter().map(|v| token(v)).collect::<Vec<_>>().join(",");
253 self.raw(key, joined)
254 }
255
256 #[must_use]
258 pub(crate) fn opt_present(self, key: &str, value: Option<&str>) -> Self {
259 if value.is_some() { self.opt(key, value) } else { self }
260 }
261
262 #[must_use]
263 pub(crate) fn flag_present(self, key: &str, value: Option<bool>) -> Self {
264 if value.is_some() { self.flag(key, value) } else { self }
265 }
266
267 #[must_use]
268 pub(crate) fn list_present(self, key: &str, values: &[String]) -> Self {
269 if values.is_empty() { self } else { self.list(key, values) }
270 }
271
272 pub(crate) fn end(self) -> String {
273 self.0
274 }
275}