1use crate::Diagnostic;
3
4use super::{MapRef, Path, Ref, Res, Schema, Seg, Text, ValueRef};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Found<T> {
11 pub value: T,
12 pub path: Path,
13}
14
15#[derive(Default)]
18pub struct Reader<'r> {
19 pub(super) path: Vec<Step>,
22 pub(super) value_only: bool,
24 pub(super) resolver: Option<&'r mut dyn Resolve>,
26 pub problems: Vec<Diagnostic>,
27 pub refs: Vec<Found<Ref>>,
28 pub tags: Vec<Found<Ref>>,
29 pub resources: Vec<Found<Res>>,
30 pub(super) allowed: Vec<&'static str>,
32}
33
34pub(super) enum Step {
36 Seg(Seg),
37 Field(&'static str),
38}
39
40pub const COMMENT_KEY: &str = "comment";
42pub const META_PREFIX: char = '$';
44pub const PRIVATE_PREFIX: char = '_';
45
46pub fn is_metadata_key(key: &str) -> bool {
48 key.starts_with(META_PREFIX) || key.starts_with(PRIVATE_PREFIX) || key == COMMENT_KEY
49}
50
51pub trait Resolve {
55 fn reference(&mut self, r: &Ref, _path: &Path) -> Result<String, String> {
57 Ok(r.id.clone())
58 }
59
60 fn text(&mut self, t: &Text, _path: &Path) -> Result<String, String> {
62 Ok(match t {
63 Text::Literal(s) => s.clone(),
64 Text::Res(r) => r.to_string(),
65 Text::NameOf(r) => format!("@{}:{}", r.kind, r.id),
66 Text::Lang(k) => format!("@lang:{k}"),
67 })
68 }
69
70 fn words(&mut self, _r: &Res) -> Option<String> {
72 None
73 }
74}
75
76impl<'r> Reader<'r> {
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn with_resolver(resolver: &'r mut dyn Resolve) -> Self {
83 Self {
84 resolver: Some(resolver),
85 ..Self::default()
86 }
87 }
88
89 pub fn resolve_ref(&mut self, r: &Ref) -> Option<String> {
91 let result = match &mut self.resolver {
92 Some(res) => res.reference(r, &Self::built(&self.path)),
93 None => Ok(r.id.clone()),
94 };
95 result.map_err(|e| self.problem(e)).ok()
96 }
97
98 pub fn resolve_text(&mut self, t: &Text) -> Option<String> {
100 struct Keep;
101 impl Resolve for Keep {}
102 let result = match &mut self.resolver {
103 Some(res) => res.text(t, &Self::built(&self.path)),
104 None => Keep.text(t, &Path::root()),
105 };
106 result.map_err(|e| self.problem(e)).ok()
107 }
108
109 pub fn take_text(&mut self, t: Text) -> Option<String> {
112 match (t, self.resolver.is_some()) {
113 (Text::Literal(s), false) => Some(s),
114 (t, _) => self.resolve_text(&t),
115 }
116 }
117
118 pub fn take_ref(&mut self, r: Ref) -> Option<String> {
120 if self.resolver.is_none() {
121 return Some(r.id);
122 }
123 self.resolve_ref(&r)
124 }
125
126 pub fn keeps_found(&self) -> bool {
128 !self.value_only
129 }
130
131 pub fn resolves(&self) -> bool {
133 self.resolver.is_some()
134 }
135
136 pub fn words(&mut self, r: &Res) -> Option<String> {
138 self.resolver.as_mut()?.words(r)
139 }
140
141 pub fn path(&self) -> Path {
143 Self::built(&self.path)
144 }
145
146 fn built(steps: &[Step]) -> Path {
147 let mut p = Path::root();
148 for s in steps {
149 p.push(match s {
150 Step::Seg(s) => s.clone(),
151 Step::Field(k) => Seg::Key((*k).to_string()),
152 });
153 }
154 p
155 }
156
157 pub fn problem(&mut self, message: impl Into<String>) {
159 self.problem_of(yog_ids::data_problem::INVALID, message);
160 }
161
162 pub fn problem_of(&mut self, code: &'static str, message: impl Into<String>) {
164 let found = Diagnostic::error(code, message.into()).at_path(self.path());
165 self.problems.push(found);
166 }
167
168 pub fn expected(&mut self, what: &str, found: &ValueRef) {
170 self.problem_of(
171 yog_ids::data_problem::EXPECTED,
172 format!("expected {what}, found {}", found_text(found)),
173 );
174 }
175
176 pub fn at<T>(&mut self, seg: Seg, f: impl FnOnce(&mut Self) -> T) -> T {
178 self.path.push(Step::Seg(seg));
179 let r = f(self);
180 self.path.pop();
181 r
182 }
183
184 fn in_field<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
186 self.path.push(Step::Field(key));
187 let r = f(self);
188 self.path.pop();
189 r
190 }
191
192 pub fn note_ref(&mut self, r: Ref) {
193 if self.value_only {
194 return;
195 }
196 let path = self.path();
197 self.refs.push(Found { value: r, path });
198 }
199
200 pub fn note_tag(&mut self, r: Ref) {
201 if self.value_only {
202 return;
203 }
204 let path = self.path();
205 self.tags.push(Found { value: r, path });
206 }
207
208 pub fn note_res(&mut self, r: Res) {
209 if self.value_only {
210 return;
211 }
212 let path = self.path();
213 self.resources.push(Found { value: r, path });
214 }
215
216 pub fn map<'v>(&mut self, v: &ValueRef<'v>) -> Option<MapRef<'v>> {
218 match v {
219 ValueRef::Map(m) => Some(*m),
220 other => {
221 self.expected("an object", other);
222 None
223 }
224 }
225 }
226
227 pub fn unknown_keys(&mut self, map: MapRef, known: &[&str]) {
229 for key in map.keys() {
230 if known.contains(&key) || is_metadata_key(key) || self.allowed.contains(&key) {
231 continue;
232 }
233 let hint = closest(key, known)
234 .map(|k| format!(" — did you mean `{k}`?"))
235 .unwrap_or_else(|| {
236 if known.is_empty() {
237 String::new()
238 } else {
239 format!(
240 " (expected {})",
241 known
242 .iter()
243 .map(|k| format!("`{k}`"))
244 .collect::<Vec<_>>()
245 .join(", ")
246 )
247 }
248 });
249 let mut found = Diagnostic::error(
250 yog_ids::data_problem::UNKNOWN_FIELD,
251 format!("unknown field `{key}`{hint}"),
252 )
253 .at_path(self.path().key(key));
254 found.on_key = true;
255 self.problems.push(found);
256 }
257 }
258
259 pub fn unknown_name(&mut self, name: &str, known: &[&str]) {
261 let hint = match closest(name, known) {
262 Some(k) => format!(" — did you mean `{k}`?"),
263 None => format!(
264 " (expected {})",
265 known
266 .iter()
267 .map(|k| format!("`{k}`"))
268 .collect::<Vec<_>>()
269 .join(", ")
270 ),
271 };
272 self.problem(format!("unknown `{name}`{hint}"));
273 }
274
275 pub fn field<T: Schema>(&mut self, map: MapRef, key: &'static str) -> Option<T> {
277 match map.get(key) {
278 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
279 None => T::absent().or_else(|| {
280 self.problem_of(
281 yog_ids::data_problem::MISSING_FIELD,
282 format!("missing `{key}`"),
283 );
284 None
285 }),
286 }
287 }
288
289 pub fn field_or<T: Schema>(
291 &mut self,
292 map: MapRef,
293 key: &'static str,
294 default: impl FnOnce() -> T,
295 ) -> Option<T> {
296 match map.get(key) {
297 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
298 None => Some(default()),
299 }
300 }
301
302 pub fn field_with<W: crate::with::With<F>, F>(
304 &mut self,
305 map: MapRef,
306 key: &'static str,
307 ) -> Option<F> {
308 match map.get(key) {
309 Some(v) => self.in_field(key, |cx| W::read(v, cx)),
310 None => W::absent().or_else(|| {
311 self.problem_of(
312 yog_ids::data_problem::MISSING_FIELD,
313 format!("missing `{key}`"),
314 );
315 None
316 }),
317 }
318 }
319
320 pub fn field_or_with<W: crate::with::With<F>, F>(
322 &mut self,
323 map: MapRef,
324 key: &'static str,
325 default: impl FnOnce() -> F,
326 ) -> Option<F> {
327 match map.get(key) {
328 Some(v) => self.in_field(key, |cx| W::read(v, cx)),
329 None => Some(default()),
330 }
331 }
332
333 pub fn with_allowed<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
335 self.allowed.push(key);
336 let r = f(self);
337 self.allowed.pop();
338 r
339 }
340
341 pub fn with_allowed_all<T>(
344 &mut self,
345 keys: &[&'static str],
346 f: impl FnOnce(&mut Self) -> T,
347 ) -> T {
348 let before = self.allowed.len();
349 self.allowed.extend_from_slice(keys);
350 let r = f(self);
351 self.allowed.truncate(before);
352 r
353 }
354
355 pub fn own_keys<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
357 let saved = std::mem::take(&mut self.allowed);
358 let r = f(self);
359 self.allowed = saved;
360 r
361 }
362}
363
364fn found_text(v: &ValueRef) -> String {
365 match v {
366 ValueRef::Str(s) if s.chars().count() <= 40 => format!("{s:?}"),
367 ValueRef::Int(n) => n.to_string(),
368 ValueRef::Float(f) => f.to_string(),
369 ValueRef::Bool(b) => b.to_string(),
370 other => other.kind_name().to_string(),
371 }
372}
373
374fn closest<'k>(key: &str, known: &[&'k str]) -> Option<&'k str> {
377 let lower = key.to_ascii_lowercase();
378 known
379 .iter()
380 .map(|k| (edit_distance(&lower, k), *k))
381 .filter(|(d, k)| *d <= if k.len() <= 3 { 1 } else { k.len() / 2 })
382 .min_by_key(|(d, _)| *d)
383 .map(|(_, k)| k)
384}
385
386fn edit_distance(a: &str, b: &str) -> usize {
387 let b: Vec<char> = b.chars().collect();
388 let mut prev: Vec<usize> = (0..=b.len()).collect();
389 for (i, ca) in a.chars().enumerate() {
390 let mut cur = vec![i + 1; b.len() + 1];
391 for (j, cb) in b.iter().enumerate() {
392 cur[j + 1] = (prev[j] + usize::from(ca != *cb))
393 .min(prev[j + 1] + 1)
394 .min(cur[j] + 1);
395 }
396 prev = cur;
397 }
398 prev[b.len()]
399}
400
401#[derive(Debug)]
403pub struct Read<T> {
404 pub value: T,
405 pub refs: Vec<Found<Ref>>,
406 pub tags: Vec<Found<Ref>>,
407 pub resources: Vec<Found<Res>>,
408}
409
410pub trait Numbers {
412 fn numbers(&self, out: &mut Vec<f64>);
413}
414
415macro_rules! numbers {
416 ($($t:ty),*) => {$(
417 impl Numbers for $t {
418 fn numbers(&self, out: &mut Vec<f64>) {
419 out.push(*self as f64);
420 }
421 }
422 )*};
423}
424numbers!(i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64);
425
426impl<T: Numbers> Numbers for Option<T> {
427 fn numbers(&self, out: &mut Vec<f64>) {
428 if let Some(x) = self {
429 x.numbers(out);
430 }
431 }
432}
433
434impl<T: Numbers> Numbers for Vec<T> {
435 fn numbers(&self, out: &mut Vec<f64>) {
436 self.iter().for_each(|x| x.numbers(out));
437 }
438}
439
440impl<T: Numbers, const N: usize> Numbers for [T; N] {
441 fn numbers(&self, out: &mut Vec<f64>) {
442 self.iter().for_each(|x| x.numbers(out));
443 }
444}
445
446impl Reader<'_> {
447 pub fn in_range<T: Numbers>(
449 &mut self,
450 key: &str,
451 value: Option<T>,
452 lo: f64,
453 hi: f64,
454 ) -> Option<T> {
455 let v = value?;
456 let mut ns = Vec::new();
457 v.numbers(&mut ns);
458 match ns.into_iter().find(|n| !(lo..=hi).contains(n)) {
459 None => Some(v),
460 Some(n) => {
461 let show = |x: f64| {
462 if x.fract() == 0.0 {
463 format!("{}", x as i64)
464 } else {
465 x.to_string()
466 }
467 };
468 let bad = show(n);
469 self.at(Seg::Key(key.to_string()), |cx| {
470 cx.problem_of(
471 yog_ids::data_problem::OUT_OF_RANGE,
472 format!("{bad} is out of range ({}..={})", show(lo), show(hi)),
473 )
474 });
475 None
476 }
477 }
478 }
479}