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