Skip to main content

yog_data/
schema.rs

1//! Schemas: a format is a Rust type implementing [`Schema`] (usually
2//! `#[derive(Schema)]`). One schema reads and writes the format, checks it
3//! with the path of every problem, collects its references for the build to
4//! check, and describes itself ([`Shape`]) for Yog-IDLE's editors and the
5//! docs.
6
7use std::collections::{BTreeMap, HashMap};
8use std::fmt;
9use std::marker::PhantomData;
10
11use crate::text::{self, Doc, LineCol};
12use crate::typed;
13use crate::value::{Map, Path, Ref, Res, Seg, Value};
14
15/// A Yog Data format.
16pub trait Schema: Sized {
17    /// What the format looks like.
18    fn shape() -> Shape;
19
20    /// Reads `v`. Problems go to `cx` at its current path; `None` when the
21    /// value couldn't be read (the problem is in `cx`).
22    fn read(v: &Value, cx: &mut Reader) -> Option<Self>;
23
24    /// The value, as the format writes it.
25    fn write(&self) -> Value;
26
27    /// The value of a field that isn't there — `None`: the field is required.
28    fn absent() -> Option<Self> {
29        None
30    }
31
32    /// Whether a struct leaves this field out when writing it.
33    fn skip(&self) -> bool {
34        false
35    }
36}
37
38// ── Shapes ──────────────────────────────────────────────────────────────────
39
40/// A shape, built on demand — so shapes of recursive types are fine.
41pub type ShapeFn = fn() -> Shape;
42
43/// What a format looks like: drives Yog-IDLE's property grids and pickers,
44/// completion, hover docs and the docs' field tables.
45#[derive(Debug, Clone)]
46pub enum Shape {
47    /// Any value.
48    Any,
49    Bool,
50    Int {
51        min: i64,
52        max: i64,
53    },
54    Float,
55    /// Plain text.
56    Str,
57    /// Yog text, a `@Set.key` resource, or `@kind:id` (that content's name).
58    Text,
59    /// An image source or a `@Set.key` image resource.
60    Image,
61    /// `#RRGGBB`/`#AARRGGBB` or a `@Set.key` color resource.
62    Color,
63    /// A mod resource, `@Set.key`.
64    Res,
65    /// Content of a kind (`""`: any kind, written `@kind:id`).
66    Ref(&'static [&'static str]),
67    /// A tag of content of a kind.
68    Tag(&'static [&'static str]),
69    /// Content of a kind or a tag of it.
70    RefOrTag(&'static [&'static str]),
71    /// Content of a kind (`@item:…`) or an image source.
72    RefOrImage(&'static [&'static str]),
73    List(ShapeFn),
74    /// An object of any keys, every value this shape.
75    Map(ShapeFn),
76    /// A value or null.
77    Optional(ShapeFn),
78    Struct(StructShape),
79    Enum(EnumShape),
80}
81
82#[derive(Debug, Clone)]
83pub struct StructShape {
84    pub name: &'static str,
85    pub doc: &'static str,
86    pub fields: Vec<Field>,
87}
88
89#[derive(Debug, Clone)]
90pub struct Field {
91    pub name: &'static str,
92    pub doc: &'static str,
93    pub shape: ShapeFn,
94    /// Must be written (no default).
95    pub required: bool,
96    /// The numbers it holds must be in `min..=max` (`#[data(range = "1..=99")]`).
97    pub range: Option<(f64, f64)>,
98}
99
100#[derive(Debug, Clone)]
101pub struct EnumShape {
102    pub name: &'static str,
103    pub doc: &'static str,
104    /// The key naming the variant (`"type"`), `None` for an enum of names.
105    pub tag: Option<&'static str>,
106    pub variants: Vec<Variant>,
107}
108
109#[derive(Debug, Clone)]
110pub struct Variant {
111    pub name: &'static str,
112    pub doc: &'static str,
113    /// The variant's fields (none for a name).
114    pub fields: Vec<Field>,
115}
116
117impl Shape {
118    /// The shape as Yog Data, for tools in other languages (Yog-IDLE).
119    /// Structs and enums are written once in `defs` and referred to by name.
120    pub fn describe(&self) -> Value {
121        let mut defs = Map::new();
122        let root = self.describe_into(&mut defs);
123        let mut m = Map::new();
124        m.insert("root", root);
125        m.insert("defs", Value::Map(defs));
126        Value::Map(m)
127    }
128
129    fn describe_into(&self, defs: &mut Map) -> Value {
130        let kind = |k: &str| {
131            let mut m = Map::new();
132            m.insert("kind", Value::from(k));
133            m
134        };
135        let fields = |fs: &[Field], defs: &mut Map| -> Value {
136            Value::List(
137                fs.iter()
138                    .map(|f| {
139                        let mut m = Map::new();
140                        m.insert("name", Value::from(f.name));
141                        if !f.doc.is_empty() {
142                            m.insert("doc", Value::from(f.doc));
143                        }
144                        m.insert("shape", (f.shape)().describe_into(defs));
145                        m.insert("required", Value::Bool(f.required));
146                        if let Some((lo, hi)) = f.range {
147                            m.insert(
148                                "range",
149                                Value::List(vec![Value::Float(lo), Value::Float(hi)]),
150                            );
151                        }
152                        Value::Map(m)
153                    })
154                    .collect(),
155            )
156        };
157        let m = match self {
158            Shape::Any => kind("any"),
159            Shape::Bool => kind("bool"),
160            Shape::Int { min, max } => {
161                let mut m = kind("int");
162                m.insert("min", Value::Int(*min));
163                m.insert("max", Value::Int(*max));
164                m
165            }
166            Shape::Float => kind("float"),
167            Shape::Str => kind("string"),
168            Shape::Text => kind("text"),
169            Shape::Image => kind("image"),
170            Shape::Color => kind("color"),
171            Shape::Res => kind("res"),
172            Shape::Ref(k) | Shape::Tag(k) | Shape::RefOrTag(k) | Shape::RefOrImage(k) => {
173                let mut m = kind(match self {
174                    Shape::Ref(_) => "ref",
175                    Shape::Tag(_) => "tag",
176                    Shape::RefOrTag(_) => "ref_or_tag",
177                    _ => "ref_or_image",
178                });
179                m.insert(
180                    "of",
181                    Value::List(k.iter().map(|k| Value::from(*k)).collect()),
182                );
183                m
184            }
185            Shape::List(s) | Shape::Map(s) | Shape::Optional(s) => {
186                let mut m = kind(match self {
187                    Shape::List(_) => "list",
188                    Shape::Map(_) => "map",
189                    _ => "optional",
190                });
191                m.insert("of", s().describe_into(defs));
192                m
193            }
194            Shape::Struct(s) => {
195                if defs.get(s.name).is_none() {
196                    // Placeholder first: a recursive type finds itself defined.
197                    defs.insert(s.name, Value::Null);
198                    let mut d = kind("struct");
199                    if !s.doc.is_empty() {
200                        d.insert("doc", Value::from(s.doc));
201                    }
202                    d.insert("fields", fields(&s.fields, defs));
203                    defs.insert(s.name, Value::Map(d));
204                }
205                let mut m = kind("def");
206                m.insert("name", Value::from(s.name));
207                m
208            }
209            Shape::Enum(e) => {
210                if defs.get(e.name).is_none() {
211                    defs.insert(e.name, Value::Null);
212                    let mut d = kind("enum");
213                    if !e.doc.is_empty() {
214                        d.insert("doc", Value::from(e.doc));
215                    }
216                    if let Some(tag) = e.tag {
217                        d.insert("tag", Value::from(tag));
218                    }
219                    let variants = e
220                        .variants
221                        .iter()
222                        .map(|v| {
223                            let mut m = Map::new();
224                            m.insert("name", Value::from(v.name));
225                            if !v.doc.is_empty() {
226                                m.insert("doc", Value::from(v.doc));
227                            }
228                            if e.tag.is_some() {
229                                m.insert("fields", fields(&v.fields, defs));
230                            }
231                            Value::Map(m)
232                        })
233                        .collect();
234                    d.insert("variants", Value::List(variants));
235                    defs.insert(e.name, Value::Map(d));
236                }
237                let mut m = kind("def");
238                m.insert("name", Value::from(e.name));
239                m
240            }
241        };
242        Value::Map(m)
243    }
244}
245
246// ── Reading ─────────────────────────────────────────────────────────────────
247
248/// A problem found reading a value.
249#[derive(Debug, Clone, PartialEq, Eq)]
250pub struct Problem {
251    pub path: Path,
252    pub message: String,
253    /// About the key at `path` rather than its value (an unknown field).
254    pub at_key: bool,
255}
256
257/// Where a reference was found.
258#[derive(Debug, Clone, PartialEq, Eq)]
259pub struct Found<T> {
260    pub value: T,
261    pub path: Path,
262}
263
264/// The state of reading a value: where it is, the problems so far, and the
265/// references met (for the build to check against the content there is).
266#[derive(Default)]
267pub struct Reader<'r> {
268    path: Path,
269    /// What the tool makes of references and texts (none: kept as written).
270    resolver: Option<&'r mut dyn Resolve>,
271    pub problems: Vec<Problem>,
272    pub refs: Vec<Found<Ref>>,
273    pub tags: Vec<Found<Ref>>,
274    pub resources: Vec<Found<Res>>,
275    /// Keys a struct being read doesn't report as unknown (an enum's tag).
276    allowed: Vec<&'static str>,
277}
278
279/// Source-only keys every object may have: `$…`, `_…`, `comment`.
280pub fn is_metadata_key(key: &str) -> bool {
281    key.starts_with('$') || key.starts_with('_') || key == "comment"
282}
283
284/// What a tool makes of the references and texts it reads: `yog build`
285/// checks each reference against the content there is and turns texts
286/// into translation keys; the IDE shows them.
287pub trait Resolve {
288    /// A reference: the id to keep, or why it's wrong.
289    fn reference(&mut self, r: &Ref, _path: &Path) -> Result<String, String> {
290        Ok(r.id.clone())
291    }
292
293    /// A text: what to keep (its words, a translation key, ...), or why it's wrong.
294    fn text(&mut self, t: &Text, _path: &Path) -> Result<String, String> {
295        Ok(match t {
296            Text::Literal(s) => s.clone(),
297            Text::Res(r) => format!("@{}.{}", r.set, r.key),
298            Text::NameOf(r) => format!("@{}:{}", r.kind, r.id),
299            Text::Lang(k) => format!("@lang:{k}"),
300        })
301    }
302
303    /// The words of a `.yres` text, if known (to check what's in them).
304    fn words(&mut self, _r: &Res) -> Option<String> {
305        None
306    }
307}
308
309impl<'r> Reader<'r> {
310    pub fn new() -> Self {
311        Self::default()
312    }
313
314    /// A reader handing references and texts to `resolver`.
315    pub fn with_resolver(resolver: &'r mut dyn Resolve) -> Self {
316        Self {
317            resolver: Some(resolver),
318            ..Self::default()
319        }
320    }
321
322    /// `r` resolved (a problem if it's wrong); its id without a resolver.
323    pub fn resolve_ref(&mut self, r: &Ref) -> Option<String> {
324        let result = match &mut self.resolver {
325            Some(res) => res.reference(r, &self.path),
326            None => Ok(r.id.clone()),
327        };
328        result.map_err(|e| self.problem(e)).ok()
329    }
330
331    /// `t` resolved (a problem if it's wrong).
332    pub fn resolve_text(&mut self, t: &Text) -> Option<String> {
333        struct Keep;
334        impl Resolve for Keep {}
335        let result = match &mut self.resolver {
336            Some(res) => res.text(t, &self.path),
337            None => Keep.text(t, &self.path),
338        };
339        result.map_err(|e| self.problem(e)).ok()
340    }
341
342    /// The words of `.yres` text `r`, if the resolver knows them.
343    pub fn words(&mut self, r: &Res) -> Option<String> {
344        self.resolver.as_mut()?.words(r)
345    }
346
347    pub fn path(&self) -> &Path {
348        &self.path
349    }
350
351    /// Reports a problem with the value being read.
352    pub fn problem(&mut self, message: impl Into<String>) {
353        self.problems.push(Problem {
354            path: self.path.clone(),
355            message: message.into(),
356            at_key: false,
357        });
358    }
359
360    /// "expected X, found Y".
361    pub fn expected(&mut self, what: &str, found: &Value) {
362        self.problem(format!("expected {what}, found {}", found_text(found)));
363    }
364
365    /// Runs `f` one step deeper.
366    pub fn at<T>(&mut self, seg: Seg, f: impl FnOnce(&mut Self) -> T) -> T {
367        self.path.push(seg);
368        let r = f(self);
369        self.path.pop();
370        r
371    }
372
373    pub fn note_ref(&mut self, r: Ref) {
374        self.refs.push(Found {
375            value: r,
376            path: self.path.clone(),
377        });
378    }
379
380    pub fn note_tag(&mut self, r: Ref) {
381        self.tags.push(Found {
382            value: r,
383            path: self.path.clone(),
384        });
385    }
386
387    pub fn note_res(&mut self, r: Res) {
388        self.resources.push(Found {
389            value: r,
390            path: self.path.clone(),
391        });
392    }
393
394    /// `v` as an object, or a problem.
395    pub fn map<'v>(&mut self, v: &'v Value) -> Option<&'v Map> {
396        match v {
397            Value::Map(m) => Some(m),
398            other => {
399                self.expected("an object", other);
400                None
401            }
402        }
403    }
404
405    /// Reports keys of `map` that aren't `known` (nor metadata, nor allowed by an enclosing enum).
406    pub fn unknown_keys(&mut self, map: &Map, known: &[&str]) {
407        for key in map.keys() {
408            if known.contains(&key) || is_metadata_key(key) || self.allowed.contains(&key) {
409                continue;
410            }
411            let hint = closest(key, known)
412                .map(|k| format!(" — did you mean `{k}`?"))
413                .unwrap_or_else(|| {
414                    if known.is_empty() {
415                        String::new()
416                    } else {
417                        format!(
418                            " (expected {})",
419                            known
420                                .iter()
421                                .map(|k| format!("`{k}`"))
422                                .collect::<Vec<_>>()
423                                .join(", ")
424                        )
425                    }
426                });
427            self.problems.push(Problem {
428                path: self.path.key(key),
429                message: format!("unknown field `{key}`{hint}"),
430                at_key: true,
431            });
432        }
433    }
434
435    /// Reports a name that isn't one of `known` (an enum's variant).
436    pub fn unknown_name(&mut self, name: &str, known: &[&str]) {
437        let hint = match closest(name, known) {
438            Some(k) => format!(" — did you mean `{k}`?"),
439            None => format!(
440                " (expected {})",
441                known
442                    .iter()
443                    .map(|k| format!("`{k}`"))
444                    .collect::<Vec<_>>()
445                    .join(", ")
446            ),
447        };
448        self.problem(format!("unknown `{name}`{hint}"));
449    }
450
451    /// Field `key` of `map`: read, or its absent value, or a problem.
452    pub fn field<T: Schema>(&mut self, map: &Map, key: &'static str) -> Option<T> {
453        match map.get(key) {
454            Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
455            None => T::absent().or_else(|| {
456                self.problem(format!("missing `{key}`"));
457                None
458            }),
459        }
460    }
461
462    /// Field `key` of `map`, `default()` when it isn't there.
463    pub fn field_or<T: Schema>(
464        &mut self,
465        map: &Map,
466        key: &'static str,
467        default: impl FnOnce() -> T,
468    ) -> Option<T> {
469        match map.get(key) {
470            Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
471            None => Some(default()),
472        }
473    }
474
475    /// Field `key` of `map` through codec `W`.
476    pub fn field_with<W: crate::with::With<F>, F>(
477        &mut self,
478        map: &Map,
479        key: &'static str,
480    ) -> Option<F> {
481        match map.get(key) {
482            Some(v) => self.at(Seg::Key(key.to_string()), |cx| W::read(v, cx)),
483            None => W::absent().or_else(|| {
484                self.problem(format!("missing `{key}`"));
485                None
486            }),
487        }
488    }
489
490    /// Field `key` of `map` through codec `W`, `default()` when it isn't there.
491    pub fn field_or_with<W: crate::with::With<F>, F>(
492        &mut self,
493        map: &Map,
494        key: &'static str,
495        default: impl FnOnce() -> F,
496    ) -> Option<F> {
497        match map.get(key) {
498            Some(v) => self.at(Seg::Key(key.to_string()), |cx| W::read(v, cx)),
499            None => Some(default()),
500        }
501    }
502
503    /// Reads `v` as `T` with `key` allowed in it — the tag of the enum variant `T` is.
504    pub fn with_allowed<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
505        self.allowed.push(key);
506        let r = f(self);
507        self.allowed.pop();
508        r
509    }
510
511    /// Reads with every one of `keys` allowed (the fields of a struct read
512    /// from the same object — see [`read_both`]).
513    pub fn with_allowed_all<T>(
514        &mut self,
515        keys: &[&'static str],
516        f: impl FnOnce(&mut Self) -> T,
517    ) -> T {
518        let before = self.allowed.len();
519        self.allowed.extend_from_slice(keys);
520        let r = f(self);
521        self.allowed.truncate(before);
522        r
523    }
524
525    /// Reads a struct's own keys with no allowance inherited from outside.
526    pub fn own_keys<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
527        let saved = std::mem::take(&mut self.allowed);
528        let r = f(self);
529        self.allowed = saved;
530        r
531    }
532}
533
534fn found_text(v: &Value) -> String {
535    match v {
536        Value::Str(s) if s.chars().count() <= 40 => format!("{s:?}"),
537        Value::Int(n) => n.to_string(),
538        Value::Float(f) => f.to_string(),
539        Value::Bool(b) => b.to_string(),
540        other => other.kind_name().to_string(),
541    }
542}
543
544/// The `known` name closest to `key`, if it's close enough to be a typo
545/// (`txt` → `text`, `img` → `image`, `colour` → `color`).
546fn closest<'k>(key: &str, known: &[&'k str]) -> Option<&'k str> {
547    let lower = key.to_ascii_lowercase();
548    known
549        .iter()
550        .map(|k| (edit_distance(&lower, k), *k))
551        .filter(|(d, k)| *d <= if k.len() <= 3 { 1 } else { k.len() / 2 })
552        .min_by_key(|(d, _)| *d)
553        .map(|(_, k)| k)
554}
555
556fn edit_distance(a: &str, b: &str) -> usize {
557    let b: Vec<char> = b.chars().collect();
558    let mut prev: Vec<usize> = (0..=b.len()).collect();
559    for (i, ca) in a.chars().enumerate() {
560        let mut cur = vec![i + 1; b.len() + 1];
561        for (j, cb) in b.iter().enumerate() {
562            cur[j + 1] = (prev[j] + usize::from(ca != *cb))
563                .min(prev[j + 1] + 1)
564                .min(cur[j] + 1);
565        }
566        prev = cur;
567    }
568    prev[b.len()]
569}
570
571/// Problems found reading a document, with where each is.
572#[derive(Debug, Clone, PartialEq, Eq)]
573pub struct Errors(pub Vec<Located>);
574
575/// A problem and its place in the source.
576#[derive(Debug, Clone, PartialEq, Eq)]
577pub struct Located {
578    pub at: Option<LineCol>,
579    pub path: Path,
580    pub message: String,
581}
582
583impl fmt::Display for Located {
584    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
585        if let Some(at) = self.at {
586            write!(f, "{at}: ")?;
587        }
588        if !self.path.is_root() {
589            write!(f, "{}: ", self.path)?;
590        }
591        f.write_str(&self.message)
592    }
593}
594
595impl fmt::Display for Errors {
596    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597        for (i, e) in self.0.iter().enumerate() {
598            if i > 0 {
599                f.write_str("\n")?;
600            }
601            write!(f, "{e}")?;
602        }
603        Ok(())
604    }
605}
606
607impl std::error::Error for Errors {}
608
609/// What reading a document gives: the value and the references in it.
610#[derive(Debug)]
611pub struct Read<T> {
612    pub value: T,
613    pub refs: Vec<Found<Ref>>,
614    pub tags: Vec<Found<Ref>>,
615    pub resources: Vec<Found<Res>>,
616}
617
618/// The numbers a value holds — what `#[data(range = …)]` checks.
619pub trait Numbers {
620    fn numbers(&self, out: &mut Vec<f64>);
621}
622
623macro_rules! numbers {
624    ($($t:ty),*) => {$(
625        impl Numbers for $t {
626            fn numbers(&self, out: &mut Vec<f64>) {
627                out.push(*self as f64);
628            }
629        }
630    )*};
631}
632numbers!(i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64);
633
634impl<T: Numbers> Numbers for Option<T> {
635    fn numbers(&self, out: &mut Vec<f64>) {
636        if let Some(x) = self {
637            x.numbers(out);
638        }
639    }
640}
641
642impl<T: Numbers> Numbers for Vec<T> {
643    fn numbers(&self, out: &mut Vec<f64>) {
644        self.iter().for_each(|x| x.numbers(out));
645    }
646}
647
648impl<T: Numbers, const N: usize> Numbers for [T; N] {
649    fn numbers(&self, out: &mut Vec<f64>) {
650        self.iter().for_each(|x| x.numbers(out));
651    }
652}
653
654impl Reader<'_> {
655    /// `value` (field `key`) checked against `lo..=hi`; `None` (a problem) if it's out.
656    pub fn in_range<T: Numbers>(
657        &mut self,
658        key: &str,
659        value: Option<T>,
660        lo: f64,
661        hi: f64,
662    ) -> Option<T> {
663        let v = value?;
664        let mut ns = Vec::new();
665        v.numbers(&mut ns);
666        match ns.into_iter().find(|n| !(lo..=hi).contains(n)) {
667            None => Some(v),
668            Some(n) => {
669                let show = |x: f64| {
670                    if x.fract() == 0.0 {
671                        format!("{}", x as i64)
672                    } else {
673                        x.to_string()
674                    }
675                };
676                let bad = show(n);
677                self.at(Seg::Key(key.to_string()), |cx| {
678                    cx.problem(format!(
679                        "{bad} is out of range ({}..={})",
680                        show(lo),
681                        show(hi)
682                    ))
683                });
684                None
685            }
686        }
687    }
688}
689
690/// The field names of a struct's shape (none for anything else).
691pub fn field_names(shape: &Shape) -> Vec<&'static str> {
692    match shape {
693        Shape::Struct(s) => s.fields.iter().map(|f| f.name).collect(),
694        _ => Vec::new(),
695    }
696}
697
698/// `A` and `B` read from the same object — each allowing the other's
699/// fields, like serde's `flatten`: a recipe's common fields and its kind's.
700pub fn read_both<A: Schema, B: Schema>(
701    v: &Value,
702    cx: &mut Reader,
703    also: &[&'static str],
704) -> Option<(A, B)> {
705    let (an, bn) = (field_names(&A::shape()), field_names(&B::shape()));
706    let mut b_keys = bn.clone();
707    b_keys.extend_from_slice(also);
708    let mut a_keys = an;
709    a_keys.extend_from_slice(also);
710    let a = cx.with_allowed_all(&b_keys, |cx| A::read(v, cx));
711    let b = cx.with_allowed_all(&a_keys, |cx| B::read(v, cx));
712    Some((a?, b?))
713}
714
715/// Reads `v` as `T`: the value and its references, or every problem.
716pub fn from_value<T: Schema>(v: &Value) -> Result<Read<T>, Vec<Problem>> {
717    read_with(v, Reader::new())
718}
719
720/// `from_value`, references and texts going through `resolver`.
721pub fn from_value_with<T: Schema>(
722    v: &Value,
723    resolver: &mut dyn Resolve,
724) -> Result<Read<T>, Vec<Problem>> {
725    read_with(v, Reader::with_resolver(resolver))
726}
727
728fn read_with<T: Schema>(v: &Value, mut cx: Reader<'_>) -> Result<Read<T>, Vec<Problem>> {
729    let value = T::read(v, &mut cx);
730    match value {
731        Some(value) if cx.problems.is_empty() => Ok(Read {
732            value,
733            refs: cx.refs,
734            tags: cx.tags,
735            resources: cx.resources,
736        }),
737        _ => Err(cx.problems),
738    }
739}
740
741/// Parses `text` and reads it as `T`; problems come with their line and column.
742pub fn from_text<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
743    let doc = text::parse(text).map_err(|e| {
744        Errors(vec![Located {
745            at: Some(e.at),
746            path: Path::root(),
747            message: e.message,
748        }])
749    })?;
750    from_doc(text, &doc)
751}
752
753/// Parses TOML `text` and reads it as `T`.
754#[cfg(feature = "toml")]
755pub fn from_toml<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
756    let doc = crate::toml::parse(text).map_err(|e| {
757        Errors(vec![Located {
758            at: Some(e.at),
759            path: Path::root(),
760            message: e.message,
761        }])
762    })?;
763    from_doc(text, &doc)
764}
765
766/// Reads a parsed document as `T`; `text` is its source, for positions.
767pub fn from_doc<T: Schema>(text: &str, doc: &Doc) -> Result<Read<T>, Errors> {
768    locate(text, doc, from_value(&doc.value))
769}
770
771/// `from_doc`, references and texts going through `resolver`.
772pub fn from_doc_with<T: Schema>(
773    text: &str,
774    doc: &Doc,
775    resolver: &mut dyn Resolve,
776) -> Result<Read<T>, Errors> {
777    locate(text, doc, from_value_with(&doc.value, resolver))
778}
779
780fn locate<T>(
781    text: &str,
782    doc: &Doc,
783    read: Result<Read<T>, Vec<Problem>>,
784) -> Result<Read<T>, Errors> {
785    read.map_err(|problems| {
786        let mut located: Vec<(u32, Located)> = problems
787            .into_iter()
788            .map(|p| {
789                let spans = doc.spans_at(&p.path);
790                let span = if p.at_key {
791                    spans.key.unwrap_or(spans.span)
792                } else {
793                    spans.span
794                };
795                (
796                    span.start,
797                    Located {
798                        at: Some(LineCol::of(text, span.start as usize)),
799                        path: p.path,
800                        message: p.message,
801                    },
802                )
803            })
804            .collect();
805        // In file order, as an editor lists them.
806        located.sort_by_key(|(offset, _)| *offset);
807        Errors(located.into_iter().map(|(_, l)| l).collect())
808    })
809}
810
811/// `value` as text, indented.
812pub fn to_text<T: Schema>(value: &T) -> String {
813    text::to_string_pretty(&value.write())
814}
815
816// ── Plain types ─────────────────────────────────────────────────────────────
817
818impl Schema for Value {
819    fn shape() -> Shape {
820        Shape::Any
821    }
822    fn read(v: &Value, _: &mut Reader) -> Option<Self> {
823        Some(v.clone())
824    }
825    fn write(&self) -> Value {
826        self.clone()
827    }
828}
829
830impl Schema for bool {
831    fn shape() -> Shape {
832        Shape::Bool
833    }
834    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
835        v.as_bool().or_else(|| {
836            cx.expected("true or false", v);
837            None
838        })
839    }
840    fn write(&self) -> Value {
841        Value::Bool(*self)
842    }
843}
844
845macro_rules! int_schema {
846    ($($t:ty),*) => {$(
847        impl Schema for $t {
848            fn shape() -> Shape {
849                Shape::Int { min: <$t>::MIN as i64, max: (<$t>::MAX as u64).min(i64::MAX as u64) as i64 }
850            }
851            fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
852                let Some(n) = v.as_i64() else {
853                    cx.expected("an integer", v);
854                    return None;
855                };
856                <$t>::try_from(n).ok().or_else(|| {
857                    cx.problem(format!("{n} is out of range ({}..={})", <$t>::MIN, <$t>::MAX));
858                    None
859                })
860            }
861            fn write(&self) -> Value {
862                Value::Int(*self as i64)
863            }
864        }
865    )*};
866}
867int_schema!(i8, i16, i32, i64, u8, u16, u32, u64, usize);
868
869macro_rules! float_schema {
870    ($($t:ty),*) => {$(
871        impl Schema for $t {
872            fn shape() -> Shape {
873                Shape::Float
874            }
875            fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
876                v.as_f64().map(|f| f as $t).or_else(|| {
877                    cx.expected("a number", v);
878                    None
879                })
880            }
881            fn write(&self) -> Value {
882                Value::Float(*self as f64)
883            }
884        }
885    )*};
886}
887float_schema!(f32, f64);
888
889impl Schema for String {
890    fn shape() -> Shape {
891        Shape::Str
892    }
893    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
894        match v {
895            Value::Str(s) => match typed::classify(s) {
896                Ok(Value::Str(s)) => Some(s),
897                Ok(other) => {
898                    cx.problem(format!(
899                        "expected plain text, found {} (write `@@` for a literal `@`)",
900                        other.kind_name()
901                    ));
902                    None
903                }
904                Err(e) => {
905                    cx.problem(e);
906                    None
907                }
908            },
909            other => {
910                cx.expected("a string", other);
911                None
912            }
913        }
914    }
915    fn write(&self) -> Value {
916        Value::Str(self.clone())
917    }
918}
919
920impl<T: Schema> Schema for Option<T> {
921    fn shape() -> Shape {
922        Shape::Optional(T::shape)
923    }
924    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
925        if v.is_null() {
926            Some(None)
927        } else {
928            T::read(v, cx).map(Some)
929        }
930    }
931    fn write(&self) -> Value {
932        self.as_ref().map_or(Value::Null, T::write)
933    }
934    fn absent() -> Option<Self> {
935        Some(None)
936    }
937    fn skip(&self) -> bool {
938        self.is_none()
939    }
940}
941
942impl<T: Schema> Schema for Box<T> {
943    fn shape() -> Shape {
944        T::shape()
945    }
946    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
947        T::read(v, cx).map(Box::new)
948    }
949    fn write(&self) -> Value {
950        (**self).write()
951    }
952    fn absent() -> Option<Self> {
953        T::absent().map(Box::new)
954    }
955    fn skip(&self) -> bool {
956        (**self).skip()
957    }
958}
959
960impl<T: Schema> Schema for Vec<T> {
961    fn shape() -> Shape {
962        Shape::List(T::shape)
963    }
964    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
965        let Value::List(items) = v else {
966            cx.expected("a list", v);
967            return None;
968        };
969        let mut out = Vec::with_capacity(items.len());
970        let mut ok = true;
971        for (i, item) in items.iter().enumerate() {
972            match cx.at(Seg::Index(i), |cx| T::read(item, cx)) {
973                Some(x) => out.push(x),
974                None => ok = false,
975            }
976        }
977        ok.then_some(out)
978    }
979    fn write(&self) -> Value {
980        Value::List(self.iter().map(T::write).collect())
981    }
982}
983
984impl<T: Schema, const N: usize> Schema for [T; N] {
985    fn shape() -> Shape {
986        Shape::List(T::shape)
987    }
988    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
989        let items = Vec::<T>::read(v, cx)?;
990        let len = items.len();
991        items.try_into().ok().or_else(|| {
992            cx.problem(format!("expected {N} items, found {len}"));
993            None
994        })
995    }
996    fn write(&self) -> Value {
997        Value::List(self.iter().map(T::write).collect())
998    }
999}
1000
1001fn read_map<T: Schema>(v: &Value, cx: &mut Reader, mut put: impl FnMut(String, T)) -> Option<()> {
1002    let map = cx.map(v)?;
1003    let mut ok = true;
1004    for (k, item) in map.iter() {
1005        if is_metadata_key(k) {
1006            continue;
1007        }
1008        match cx.at(Seg::Key(k.to_string()), |cx| T::read(item, cx)) {
1009            Some(x) => put(k.to_string(), x),
1010            None => ok = false,
1011        }
1012    }
1013    ok.then_some(())
1014}
1015
1016impl<T: Schema> Schema for BTreeMap<String, T> {
1017    fn shape() -> Shape {
1018        Shape::Map(T::shape)
1019    }
1020    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1021        let mut out = BTreeMap::new();
1022        read_map(v, cx, |k, x| {
1023            out.insert(k, x);
1024        })?;
1025        Some(out)
1026    }
1027    fn write(&self) -> Value {
1028        Value::Map(self.iter().map(|(k, v)| (k.clone(), v.write())).collect())
1029    }
1030}
1031
1032impl<T: Schema> Schema for HashMap<String, T> {
1033    fn shape() -> Shape {
1034        Shape::Map(T::shape)
1035    }
1036    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1037        let mut out = HashMap::new();
1038        read_map(v, cx, |k, x| {
1039            out.insert(k, x);
1040        })?;
1041        Some(out)
1042    }
1043    fn write(&self) -> Value {
1044        let mut keys: Vec<&String> = self.keys().collect();
1045        keys.sort();
1046        Value::Map(
1047            keys.into_iter()
1048                .map(|k| (k.clone(), self[k].write()))
1049                .collect(),
1050        )
1051    }
1052}
1053
1054/// An object kept in file order, values of one schema.
1055#[derive(Debug, Clone, PartialEq)]
1056pub struct Ordered<T>(pub Vec<(String, T)>);
1057
1058impl<T> Default for Ordered<T> {
1059    fn default() -> Self {
1060        Self(Vec::new())
1061    }
1062}
1063
1064impl<T: Schema> Schema for Ordered<T> {
1065    fn shape() -> Shape {
1066        Shape::Map(T::shape)
1067    }
1068    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1069        let mut out = Vec::new();
1070        read_map(v, cx, |k, x| out.push((k, x)))?;
1071        Some(Ordered(out))
1072    }
1073    fn write(&self) -> Value {
1074        Value::Map(self.0.iter().map(|(k, v)| (k.clone(), v.write())).collect())
1075    }
1076}
1077
1078// ── Typed values ────────────────────────────────────────────────────────────
1079
1080/// A kind of content: `item`, `block`, ... — a mod declares its own with
1081/// [`kind!`](crate::kind).
1082pub trait Kind {
1083    const NAME: &'static str;
1084}
1085
1086/// One kind or several (`(Item, Block)`): what a reference field takes.
1087pub trait Kinds {
1088    const NAMES: &'static [&'static str];
1089}
1090
1091impl<K: Kind> Kinds for K {
1092    const NAMES: &'static [&'static str] = &[K::NAME];
1093}
1094
1095impl<A: Kind, B: Kind> Kinds for (A, B) {
1096    const NAMES: &'static [&'static str] = &[A::NAME, B::NAME];
1097}
1098
1099impl<A: Kind, B: Kind, C: Kind> Kinds for (A, B, C) {
1100    const NAMES: &'static [&'static str] = &[A::NAME, B::NAME, C::NAME];
1101}
1102
1103/// Declares kinds of content: `yog_data::kind!(Pattern = "pattern");`.
1104#[macro_export]
1105macro_rules! kind {
1106    ($($name:ident = $kind:literal),* $(,)?) => {$(
1107        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
1108        pub struct $name;
1109        impl $crate::Kind for $name {
1110            const NAME: &'static str = $kind;
1111        }
1112    )*};
1113}
1114
1115/// The kinds every Yog format knows.
1116pub mod kinds {
1117    crate::kind! {
1118        Item = "item",
1119        Block = "block",
1120        Entity = "entity",
1121        Recipe = "recipe",
1122        Entry = "entry",
1123        Category = "category",
1124        Fluid = "fluid",
1125        Biome = "biome",
1126        Dimension = "dimension",
1127    }
1128}
1129
1130/// The id of content of kind `K`: written `@item:minecraft:stick` or, in a
1131/// field that can only be an item, just `minecraft:stick` (`stick`).
1132pub struct Id<K> {
1133    pub id: String,
1134    kind: PhantomData<K>,
1135}
1136
1137pub type ItemId = Id<kinds::Item>;
1138pub type BlockId = Id<kinds::Block>;
1139pub type EntityId = Id<kinds::Entity>;
1140pub type RecipeId = Id<kinds::Recipe>;
1141
1142impl<K> Id<K> {
1143    pub fn new(id: impl Into<String>) -> Self {
1144        Self {
1145            id: id.into(),
1146            kind: PhantomData,
1147        }
1148    }
1149}
1150
1151impl<K> Clone for Id<K> {
1152    fn clone(&self) -> Self {
1153        Self::new(self.id.clone())
1154    }
1155}
1156impl<K> PartialEq for Id<K> {
1157    fn eq(&self, o: &Self) -> bool {
1158        self.id == o.id
1159    }
1160}
1161impl<K> Eq for Id<K> {}
1162impl<K> std::hash::Hash for Id<K> {
1163    fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
1164        self.id.hash(h);
1165    }
1166}
1167impl<K: Kind> fmt::Debug for Id<K> {
1168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1169        write!(f, "@{}:{}", K::NAME, self.id)
1170    }
1171}
1172impl<K> fmt::Display for Id<K> {
1173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1174        f.write_str(&self.id)
1175    }
1176}
1177
1178/// `v` as a reference to content of one of `kinds` (any kind if empty),
1179/// written `@kind:id` — a bare id is an error that says how to write it.
1180pub fn read_ref(v: &Value, cx: &mut Reader, kinds: &[&str]) -> Option<Ref> {
1181    let wanted = || {
1182        kinds
1183            .iter()
1184            .map(|k| format!("`@{k}:`"))
1185            .collect::<Vec<_>>()
1186            .join(" or ")
1187    };
1188    let r = match v {
1189        Value::Ref(r) => r.clone(),
1190        Value::Str(s) if s.starts_with('@') => match typed::classify(s) {
1191            Ok(Value::Ref(r)) => r,
1192            Ok(other) => {
1193                cx.problem(format!(
1194                    "expected a reference ({}), found {}",
1195                    if kinds.is_empty() {
1196                        "`@kind:id`".into()
1197                    } else {
1198                        wanted()
1199                    },
1200                    other.kind_name()
1201                ));
1202                return None;
1203            }
1204            Err(e) => {
1205                cx.problem(e);
1206                return None;
1207            }
1208        },
1209        Value::Str(s) => {
1210            let kind = kinds.first().copied().unwrap_or("item");
1211            cx.problem(format!("`{s}`: name it by a reference — `@{kind}:{s}`"));
1212            return None;
1213        }
1214        other => {
1215            cx.expected("a reference `@kind:id`", other);
1216            return None;
1217        }
1218    };
1219    if !kinds.is_empty() && !kinds.contains(&r.kind.as_str()) {
1220        cx.problem(format!(
1221            "`@{}:{}` is a reference to {} — this takes {}",
1222            r.kind,
1223            r.id,
1224            r.kind,
1225            wanted()
1226        ));
1227        return None;
1228    }
1229    cx.note_ref(r.clone());
1230    Some(r)
1231}
1232
1233fn read_tag(v: &Value, cx: &mut Reader, kind: &str) -> Option<Ref> {
1234    let r = match v {
1235        Value::Tag(r) => r.clone(),
1236        Value::Str(s) => match typed::classify(s) {
1237            Ok(Value::Tag(r)) => r,
1238            Ok(_) => {
1239                cx.problem(format!("expected a tag `@tag:{kind}:id`, found {s:?}"));
1240                return None;
1241            }
1242            Err(e) => {
1243                cx.problem(e);
1244                return None;
1245            }
1246        },
1247        other => {
1248            cx.expected("a tag", other);
1249            return None;
1250        }
1251    };
1252    if !kind.is_empty() && r.kind != kind {
1253        cx.problem(format!(
1254            "expected a tag of {kind}, found a tag of {}",
1255            r.kind
1256        ));
1257        return None;
1258    }
1259    cx.note_tag(r.clone());
1260    Some(r)
1261}
1262
1263impl<K: Kind> Schema for Id<K> {
1264    fn shape() -> Shape {
1265        Shape::Ref(<K as Kinds>::NAMES)
1266    }
1267    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1268        read_ref(v, cx, <K as Kinds>::NAMES).map(|r| Id::new(r.id))
1269    }
1270    fn write(&self) -> Value {
1271        Value::Ref(Ref::new(K::NAME, self.id.clone()))
1272    }
1273}
1274
1275impl Schema for Ref {
1276    fn shape() -> Shape {
1277        Shape::Ref(&[])
1278    }
1279    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1280        if let Value::Str(s) = v
1281            && !s.starts_with('@')
1282        {
1283            cx.problem(format!("expected a reference `@kind:id`, found {s:?}"));
1284            return None;
1285        }
1286        read_ref(v, cx, &[])
1287    }
1288    fn write(&self) -> Value {
1289        Value::Ref(self.clone())
1290    }
1291}
1292
1293/// A tag of content of kind `K`: `@tag:item:minecraft:planks`.
1294pub struct TagId<K> {
1295    pub id: String,
1296    kind: PhantomData<K>,
1297}
1298
1299impl<K> TagId<K> {
1300    pub fn new(id: impl Into<String>) -> Self {
1301        Self {
1302            id: id.into(),
1303            kind: PhantomData,
1304        }
1305    }
1306}
1307impl<K> Clone for TagId<K> {
1308    fn clone(&self) -> Self {
1309        Self::new(self.id.clone())
1310    }
1311}
1312impl<K> PartialEq for TagId<K> {
1313    fn eq(&self, o: &Self) -> bool {
1314        self.id == o.id
1315    }
1316}
1317impl<K: Kind> fmt::Debug for TagId<K> {
1318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1319        write!(f, "@tag:{}:{}", K::NAME, self.id)
1320    }
1321}
1322
1323impl<K: Kind> Schema for TagId<K> {
1324    fn shape() -> Shape {
1325        Shape::Tag(<K as Kinds>::NAMES)
1326    }
1327    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1328        read_tag(v, cx, K::NAME).map(|r| TagId::new(r.id))
1329    }
1330    fn write(&self) -> Value {
1331        Value::Tag(Ref::new(K::NAME, self.id.clone()))
1332    }
1333}
1334
1335/// Content of kind `K` or a tag of it — a recipe ingredient.
1336#[derive(Debug, Clone, PartialEq)]
1337pub enum OneOrTag<K: Kind> {
1338    One(Id<K>),
1339    Tag(TagId<K>),
1340}
1341
1342/// An item or an item tag: `"minecraft:stick"`, `"@tag:item:minecraft:planks"`.
1343pub type ItemIdOrTag = OneOrTag<kinds::Item>;
1344
1345impl<K: Kind> Schema for OneOrTag<K> {
1346    fn shape() -> Shape {
1347        Shape::RefOrTag(<K as Kinds>::NAMES)
1348    }
1349    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1350        if let Value::Str(s) = v
1351            && let Some(tag) = s.strip_prefix('#')
1352        {
1353            cx.problem(format!("`{s}`: write a tag as `@tag:{}:{tag}`", K::NAME));
1354            return None;
1355        }
1356        let is_tag = match v {
1357            Value::Tag(_) => true,
1358            Value::Str(s) => s.starts_with("@tag:"),
1359            _ => false,
1360        };
1361        if is_tag {
1362            TagId::read(v, cx).map(OneOrTag::Tag)
1363        } else {
1364            Id::read(v, cx).map(OneOrTag::One)
1365        }
1366    }
1367    fn write(&self) -> Value {
1368        match self {
1369            OneOrTag::One(i) => i.write(),
1370            OneOrTag::Tag(t) => t.write(),
1371        }
1372    }
1373}
1374
1375impl Schema for Res {
1376    fn shape() -> Shape {
1377        Shape::Res
1378    }
1379    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1380        let r = match v {
1381            Value::Res(r) => r.clone(),
1382            Value::Str(s) => match typed::classify(s) {
1383                Ok(Value::Res(r)) => r,
1384                Ok(_) => {
1385                    cx.problem(format!("expected a resource `@Set.key`, found {s:?}"));
1386                    return None;
1387                }
1388                Err(e) => {
1389                    cx.problem(e);
1390                    return None;
1391                }
1392            },
1393            other => {
1394                cx.expected("a resource `@Set.key`", other);
1395                return None;
1396            }
1397        };
1398        cx.note_res(r.clone());
1399        Some(r)
1400    }
1401    fn write(&self) -> Value {
1402        Value::Res(self.clone())
1403    }
1404}
1405
1406/// A text a player reads: Yog text as written, a `.yres` text, the name
1407/// of some content (`@item:minecraft:stick` shows "Stick"), or any
1408/// translation key (`@lang:gui.done`).
1409#[derive(Debug, Clone, PartialEq)]
1410pub enum Text {
1411    Literal(String),
1412    Res(Res),
1413    NameOf(Ref),
1414    Lang(String),
1415}
1416
1417impl Default for Text {
1418    fn default() -> Self {
1419        Text::Literal(String::new())
1420    }
1421}
1422
1423impl Schema for Text {
1424    fn shape() -> Shape {
1425        Shape::Text
1426    }
1427    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1428        let typed = match v {
1429            Value::Str(s) => typed::classify(s).map_err(|e| cx.problem(e)).ok()?,
1430            Value::Text(t) => return Some(Text::Literal(t.clone())),
1431            other => other.clone(),
1432        };
1433        match typed {
1434            Value::Str(s) => Some(Text::Literal(s)),
1435            Value::Res(r) => {
1436                cx.note_res(r.clone());
1437                Some(Text::Res(r))
1438            }
1439            Value::Ref(r) => {
1440                cx.note_ref(r.clone());
1441                Some(Text::NameOf(r))
1442            }
1443            Value::Lang(k) => Some(Text::Lang(k)),
1444            other => {
1445                cx.expected("a text", &other);
1446                None
1447            }
1448        }
1449    }
1450    fn write(&self) -> Value {
1451        match self {
1452            Text::Literal(s) => Value::Text(s.clone()),
1453            Text::Res(r) => Value::Res(r.clone()),
1454            Text::NameOf(r) => Value::Ref(r.clone()),
1455            Text::Lang(k) => Value::Lang(k.clone()),
1456        }
1457    }
1458}
1459
1460/// An image: a source (`sprite:button`, `mymod:gui/logo`, `item:minecraft:stick`)
1461/// or a `.yres` image.
1462#[derive(Debug, Clone, PartialEq)]
1463pub enum Image {
1464    Source(String),
1465    Res(Res),
1466}
1467
1468impl Schema for Image {
1469    fn shape() -> Shape {
1470        Shape::Image
1471    }
1472    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1473        match v {
1474            Value::Image(s) => Some(Image::Source(s.clone())),
1475            Value::Res(r) => {
1476                cx.note_res(r.clone());
1477                Some(Image::Res(r.clone()))
1478            }
1479            Value::Str(s) if !s.starts_with('@') => Some(Image::Source(s.clone())),
1480            Value::Str(s) => match typed::classify(s) {
1481                Ok(Value::Image(src)) => Some(Image::Source(src)),
1482                Ok(Value::Res(r)) => {
1483                    cx.note_res(r.clone());
1484                    Some(Image::Res(r))
1485                }
1486                Ok(Value::Str(s)) => Some(Image::Source(s)),
1487                Ok(other) => {
1488                    cx.expected("an image", &other);
1489                    None
1490                }
1491                Err(e) => {
1492                    cx.problem(e);
1493                    None
1494                }
1495            },
1496            other => {
1497                cx.expected("an image", other);
1498                None
1499            }
1500        }
1501    }
1502    fn write(&self) -> Value {
1503        match self {
1504            Image::Source(s) => Value::Image(s.clone()),
1505            Image::Res(r) => Value::Res(r.clone()),
1506        }
1507    }
1508}
1509
1510/// A color: `#RRGGBB`, `#AARRGGBB`, or a `.yres` color.
1511#[derive(Debug, Clone, PartialEq)]
1512pub enum Color {
1513    Argb(u32),
1514    Res(Res),
1515}
1516
1517impl Schema for Color {
1518    fn shape() -> Shape {
1519        Shape::Color
1520    }
1521    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1522        match v {
1523            Value::Color(c) => Some(Color::Argb(*c)),
1524            Value::Res(r) => {
1525                cx.note_res(r.clone());
1526                Some(Color::Res(r.clone()))
1527            }
1528            Value::Str(s) if s.starts_with('#') => {
1529                typed::parse_color(s).map(Color::Argb).or_else(|| {
1530                    cx.problem(format!("`{s}` isn't a color (#RRGGBB or #AARRGGBB)"));
1531                    None
1532                })
1533            }
1534            Value::Str(s) if s.starts_with('@') => Res::read(v, cx).map(Color::Res),
1535            other => {
1536                cx.expected("a color (#RRGGBB or #AARRGGBB)", other);
1537                None
1538            }
1539        }
1540    }
1541    fn write(&self) -> Value {
1542        match self {
1543            Color::Argb(c) => Value::Color(*c),
1544            Color::Res(r) => Value::Res(r.clone()),
1545        }
1546    }
1547}