1use std::collections::{BTreeMap, HashMap};
8use std::fmt;
9use std::marker::PhantomData;
10
11use crate::text::{self, Doc, LineCol};
12use crate::typed;
13use crate::borrowed::{MapRef, ValueRef};
14use crate::value::{Map, Path, Ref, Res, Seg, Value};
15
16pub trait Schema: Sized {
18 fn shape() -> Shape;
20
21 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self>;
24
25 fn write(&self) -> Value;
27
28 fn absent() -> Option<Self> {
30 None
31 }
32
33 fn skip(&self) -> bool {
35 false
36 }
37}
38
39pub type ShapeFn = fn() -> Shape;
43
44#[derive(Debug, Clone)]
47pub enum Shape {
48 Any,
50 Bool,
51 Int {
52 min: i64,
53 max: i64,
54 },
55 Float,
56 Str,
58 Text,
60 Image,
62 Color,
64 Bytes,
66 Res,
68 Ref(&'static [&'static str]),
70 Tag(&'static [&'static str]),
72 RefOrTag(&'static [&'static str]),
74 RefOrImage(&'static [&'static str]),
76 List(ShapeFn),
77 Map(ShapeFn),
79 Optional(ShapeFn),
81 Struct(StructShape),
82 Enum(EnumShape),
83}
84
85#[derive(Debug, Clone)]
86pub struct StructShape {
87 pub name: &'static str,
88 pub doc: &'static str,
89 pub fields: Vec<Field>,
90}
91
92#[derive(Debug, Clone)]
93pub struct Field {
94 pub name: &'static str,
95 pub doc: &'static str,
96 pub shape: ShapeFn,
97 pub required: bool,
99 pub range: Option<(f64, f64)>,
101}
102
103#[derive(Debug, Clone)]
104pub struct EnumShape {
105 pub name: &'static str,
106 pub doc: &'static str,
107 pub tag: Option<&'static str>,
109 pub variants: Vec<Variant>,
110}
111
112#[derive(Debug, Clone)]
113pub struct Variant {
114 pub name: &'static str,
115 pub doc: &'static str,
116 pub fields: Vec<Field>,
118}
119
120impl Shape {
121 pub fn describe(&self) -> Value {
124 let mut defs = Map::new();
125 let root = self.describe_into(&mut defs);
126 let mut m = Map::new();
127 m.insert("root", root);
128 m.insert("defs", Value::Map(defs));
129 Value::Map(m)
130 }
131
132 fn describe_into(&self, defs: &mut Map) -> Value {
133 let kind = |k: &str| {
134 let mut m = Map::new();
135 m.insert("kind", Value::from(k));
136 m
137 };
138 let fields = |fs: &[Field], defs: &mut Map| -> Value {
139 Value::List(
140 fs.iter()
141 .map(|f| {
142 let mut m = Map::new();
143 m.insert("name", Value::from(f.name));
144 if !f.doc.is_empty() {
145 m.insert("doc", Value::from(f.doc));
146 }
147 m.insert("shape", (f.shape)().describe_into(defs));
148 m.insert("required", Value::Bool(f.required));
149 if let Some((lo, hi)) = f.range {
150 m.insert(
151 "range",
152 Value::List(vec![Value::Float(lo), Value::Float(hi)]),
153 );
154 }
155 Value::Map(m)
156 })
157 .collect(),
158 )
159 };
160 let m = match self {
161 Shape::Any => kind("any"),
162 Shape::Bool => kind("bool"),
163 Shape::Int { min, max } => {
164 let mut m = kind("int");
165 m.insert("min", Value::Int(*min));
166 m.insert("max", Value::Int(*max));
167 m
168 }
169 Shape::Float => kind("float"),
170 Shape::Str => kind("string"),
171 Shape::Text => kind("text"),
172 Shape::Image => kind("image"),
173 Shape::Color => kind("color"),
174 Shape::Bytes => kind("bytes"),
175 Shape::Res => kind("res"),
176 Shape::Ref(k) | Shape::Tag(k) | Shape::RefOrTag(k) | Shape::RefOrImage(k) => {
177 let mut m = kind(match self {
178 Shape::Ref(_) => "ref",
179 Shape::Tag(_) => "tag",
180 Shape::RefOrTag(_) => "ref_or_tag",
181 _ => "ref_or_image",
182 });
183 m.insert(
184 "of",
185 Value::List(k.iter().map(|k| Value::from(*k)).collect()),
186 );
187 m
188 }
189 Shape::List(s) | Shape::Map(s) | Shape::Optional(s) => {
190 let mut m = kind(match self {
191 Shape::List(_) => "list",
192 Shape::Map(_) => "map",
193 _ => "optional",
194 });
195 m.insert("of", s().describe_into(defs));
196 m
197 }
198 Shape::Struct(s) => {
199 if defs.get(s.name).is_none() {
200 defs.insert(s.name, Value::Null);
202 let mut d = kind("struct");
203 if !s.doc.is_empty() {
204 d.insert("doc", Value::from(s.doc));
205 }
206 d.insert("fields", fields(&s.fields, defs));
207 defs.insert(s.name, Value::Map(d));
208 }
209 let mut m = kind("def");
210 m.insert("name", Value::from(s.name));
211 m
212 }
213 Shape::Enum(e) => {
214 if defs.get(e.name).is_none() {
215 defs.insert(e.name, Value::Null);
216 let mut d = kind("enum");
217 if !e.doc.is_empty() {
218 d.insert("doc", Value::from(e.doc));
219 }
220 if let Some(tag) = e.tag {
221 d.insert("tag", Value::from(tag));
222 }
223 let variants = e
224 .variants
225 .iter()
226 .map(|v| {
227 let mut m = Map::new();
228 m.insert("name", Value::from(v.name));
229 if !v.doc.is_empty() {
230 m.insert("doc", Value::from(v.doc));
231 }
232 if e.tag.is_some() {
233 m.insert("fields", fields(&v.fields, defs));
234 }
235 Value::Map(m)
236 })
237 .collect();
238 d.insert("variants", Value::List(variants));
239 defs.insert(e.name, Value::Map(d));
240 }
241 let mut m = kind("def");
242 m.insert("name", Value::from(e.name));
243 m
244 }
245 };
246 Value::Map(m)
247 }
248}
249
250#[derive(Debug, Clone, PartialEq, Eq)]
254pub struct Problem {
255 pub path: Path,
256 pub message: String,
257 pub at_key: bool,
259}
260
261#[derive(Debug, Clone, PartialEq, Eq)]
263pub struct Found<T> {
264 pub value: T,
265 pub path: Path,
266}
267
268#[derive(Default)]
271pub struct Reader<'r> {
272 path: Vec<Step>,
275 value_only: bool,
277 resolver: Option<&'r mut dyn Resolve>,
279 pub problems: Vec<Problem>,
280 pub refs: Vec<Found<Ref>>,
281 pub tags: Vec<Found<Ref>>,
282 pub resources: Vec<Found<Res>>,
283 allowed: Vec<&'static str>,
285}
286
287enum Step {
289 Seg(Seg),
290 Field(&'static str),
291}
292
293pub fn is_metadata_key(key: &str) -> bool {
295 key.starts_with('$') || key.starts_with('_') || key == "comment"
296}
297
298pub trait Resolve {
302 fn reference(&mut self, r: &Ref, _path: &Path) -> Result<String, String> {
304 Ok(r.id.clone())
305 }
306
307 fn text(&mut self, t: &Text, _path: &Path) -> Result<String, String> {
309 Ok(match t {
310 Text::Literal(s) => s.clone(),
311 Text::Res(r) => format!("@{}.{}", r.set, r.key),
312 Text::NameOf(r) => format!("@{}:{}", r.kind, r.id),
313 Text::Lang(k) => format!("@lang:{k}"),
314 })
315 }
316
317 fn words(&mut self, _r: &Res) -> Option<String> {
319 None
320 }
321}
322
323impl<'r> Reader<'r> {
324 pub fn new() -> Self {
325 Self::default()
326 }
327
328 pub fn with_resolver(resolver: &'r mut dyn Resolve) -> Self {
330 Self {
331 resolver: Some(resolver),
332 ..Self::default()
333 }
334 }
335
336 pub fn resolve_ref(&mut self, r: &Ref) -> Option<String> {
338 let result = match &mut self.resolver {
339 Some(res) => res.reference(r, &Self::built(&self.path)),
340 None => Ok(r.id.clone()),
341 };
342 result.map_err(|e| self.problem(e)).ok()
343 }
344
345 pub fn resolve_text(&mut self, t: &Text) -> Option<String> {
347 struct Keep;
348 impl Resolve for Keep {}
349 let result = match &mut self.resolver {
350 Some(res) => res.text(t, &Self::built(&self.path)),
351 None => Keep.text(t, &Path::root()),
352 };
353 result.map_err(|e| self.problem(e)).ok()
354 }
355
356 pub fn take_text(&mut self, t: Text) -> Option<String> {
359 match (t, self.resolver.is_some()) {
360 (Text::Literal(s), false) => Some(s),
361 (t, _) => self.resolve_text(&t),
362 }
363 }
364
365 pub fn take_ref(&mut self, r: Ref) -> Option<String> {
367 if self.resolver.is_none() {
368 return Some(r.id);
369 }
370 self.resolve_ref(&r)
371 }
372
373 pub fn keeps_found(&self) -> bool {
375 !self.value_only
376 }
377
378 pub fn resolves(&self) -> bool {
380 self.resolver.is_some()
381 }
382
383 pub fn words(&mut self, r: &Res) -> Option<String> {
385 self.resolver.as_mut()?.words(r)
386 }
387
388 pub fn path(&self) -> Path {
390 Self::built(&self.path)
391 }
392
393 fn built(steps: &[Step]) -> Path {
394 let mut p = Path::root();
395 for s in steps {
396 p.push(match s {
397 Step::Seg(s) => s.clone(),
398 Step::Field(k) => Seg::Key((*k).to_string()),
399 });
400 }
401 p
402 }
403
404 pub fn problem(&mut self, message: impl Into<String>) {
406 self.problems.push(Problem {
407 path: self.path(),
408 message: message.into(),
409 at_key: false,
410 });
411 }
412
413 pub fn expected(&mut self, what: &str, found: &ValueRef) {
415 self.problem(format!("expected {what}, found {}", found_text(found)));
416 }
417
418 pub fn at<T>(&mut self, seg: Seg, f: impl FnOnce(&mut Self) -> T) -> T {
420 self.path.push(Step::Seg(seg));
421 let r = f(self);
422 self.path.pop();
423 r
424 }
425
426 fn in_field<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
428 self.path.push(Step::Field(key));
429 let r = f(self);
430 self.path.pop();
431 r
432 }
433
434 pub fn note_ref(&mut self, r: Ref) {
435 if self.value_only {
436 return;
437 }
438 let path = self.path();
439 self.refs.push(Found { value: r, path });
440 }
441
442 pub fn note_tag(&mut self, r: Ref) {
443 if self.value_only {
444 return;
445 }
446 let path = self.path();
447 self.tags.push(Found { value: r, path });
448 }
449
450 pub fn note_res(&mut self, r: Res) {
451 if self.value_only {
452 return;
453 }
454 let path = self.path();
455 self.resources.push(Found { value: r, path });
456 }
457
458 pub fn map<'v>(&mut self, v: &ValueRef<'v>) -> Option<MapRef<'v>> {
460 match v {
461 ValueRef::Map(m) => Some(*m),
462 other => {
463 self.expected("an object", other);
464 None
465 }
466 }
467 }
468
469 pub fn unknown_keys(&mut self, map: MapRef, known: &[&str]) {
471 for key in map.keys() {
472 if known.contains(&key) || is_metadata_key(key) || self.allowed.contains(&key) {
473 continue;
474 }
475 let hint = closest(key, known)
476 .map(|k| format!(" — did you mean `{k}`?"))
477 .unwrap_or_else(|| {
478 if known.is_empty() {
479 String::new()
480 } else {
481 format!(
482 " (expected {})",
483 known
484 .iter()
485 .map(|k| format!("`{k}`"))
486 .collect::<Vec<_>>()
487 .join(", ")
488 )
489 }
490 });
491 self.problems.push(Problem {
492 path: self.path().key(key),
493 message: format!("unknown field `{key}`{hint}"),
494 at_key: true,
495 });
496 }
497 }
498
499 pub fn unknown_name(&mut self, name: &str, known: &[&str]) {
501 let hint = match closest(name, known) {
502 Some(k) => format!(" — did you mean `{k}`?"),
503 None => format!(
504 " (expected {})",
505 known
506 .iter()
507 .map(|k| format!("`{k}`"))
508 .collect::<Vec<_>>()
509 .join(", ")
510 ),
511 };
512 self.problem(format!("unknown `{name}`{hint}"));
513 }
514
515 pub fn field<T: Schema>(&mut self, map: MapRef, key: &'static str) -> Option<T> {
517 match map.get(key) {
518 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
519 None => T::absent().or_else(|| {
520 self.problem(format!("missing `{key}`"));
521 None
522 }),
523 }
524 }
525
526 pub fn field_or<T: Schema>(
528 &mut self,
529 map: MapRef,
530 key: &'static str,
531 default: impl FnOnce() -> T,
532 ) -> Option<T> {
533 match map.get(key) {
534 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
535 None => Some(default()),
536 }
537 }
538
539 pub fn field_with<W: crate::with::With<F>, F>(
541 &mut self,
542 map: MapRef,
543 key: &'static str,
544 ) -> Option<F> {
545 match map.get(key) {
546 Some(v) => self.in_field(key, |cx| W::read(v, cx)),
547 None => W::absent().or_else(|| {
548 self.problem(format!("missing `{key}`"));
549 None
550 }),
551 }
552 }
553
554 pub fn field_or_with<W: crate::with::With<F>, F>(
556 &mut self,
557 map: MapRef,
558 key: &'static str,
559 default: impl FnOnce() -> F,
560 ) -> Option<F> {
561 match map.get(key) {
562 Some(v) => self.in_field(key, |cx| W::read(v, cx)),
563 None => Some(default()),
564 }
565 }
566
567 pub fn with_allowed<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
569 self.allowed.push(key);
570 let r = f(self);
571 self.allowed.pop();
572 r
573 }
574
575 pub fn with_allowed_all<T>(
578 &mut self,
579 keys: &[&'static str],
580 f: impl FnOnce(&mut Self) -> T,
581 ) -> T {
582 let before = self.allowed.len();
583 self.allowed.extend_from_slice(keys);
584 let r = f(self);
585 self.allowed.truncate(before);
586 r
587 }
588
589 pub fn own_keys<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
591 let saved = std::mem::take(&mut self.allowed);
592 let r = f(self);
593 self.allowed = saved;
594 r
595 }
596}
597
598fn found_text(v: &ValueRef) -> String {
599 match v {
600 ValueRef::Str(s) if s.chars().count() <= 40 => format!("{s:?}"),
601 ValueRef::Int(n) => n.to_string(),
602 ValueRef::Float(f) => f.to_string(),
603 ValueRef::Bool(b) => b.to_string(),
604 other => other.kind_name().to_string(),
605 }
606}
607
608fn closest<'k>(key: &str, known: &[&'k str]) -> Option<&'k str> {
611 let lower = key.to_ascii_lowercase();
612 known
613 .iter()
614 .map(|k| (edit_distance(&lower, k), *k))
615 .filter(|(d, k)| *d <= if k.len() <= 3 { 1 } else { k.len() / 2 })
616 .min_by_key(|(d, _)| *d)
617 .map(|(_, k)| k)
618}
619
620fn edit_distance(a: &str, b: &str) -> usize {
621 let b: Vec<char> = b.chars().collect();
622 let mut prev: Vec<usize> = (0..=b.len()).collect();
623 for (i, ca) in a.chars().enumerate() {
624 let mut cur = vec![i + 1; b.len() + 1];
625 for (j, cb) in b.iter().enumerate() {
626 cur[j + 1] = (prev[j] + usize::from(ca != *cb))
627 .min(prev[j + 1] + 1)
628 .min(cur[j] + 1);
629 }
630 prev = cur;
631 }
632 prev[b.len()]
633}
634
635#[derive(Debug, Clone, PartialEq, Eq)]
637pub struct Errors(pub Vec<Located>);
638
639#[derive(Debug, Clone, PartialEq, Eq)]
641pub struct Located {
642 pub at: Option<LineCol>,
643 pub path: Path,
644 pub message: String,
645}
646
647impl fmt::Display for Located {
648 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
649 if let Some(at) = self.at {
650 write!(f, "{at}: ")?;
651 }
652 if !self.path.is_root() {
653 write!(f, "{}: ", self.path)?;
654 }
655 f.write_str(&self.message)
656 }
657}
658
659impl fmt::Display for Errors {
660 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
661 for (i, e) in self.0.iter().enumerate() {
662 if i > 0 {
663 f.write_str("\n")?;
664 }
665 write!(f, "{e}")?;
666 }
667 Ok(())
668 }
669}
670
671impl std::error::Error for Errors {}
672
673#[derive(Debug)]
675pub struct Read<T> {
676 pub value: T,
677 pub refs: Vec<Found<Ref>>,
678 pub tags: Vec<Found<Ref>>,
679 pub resources: Vec<Found<Res>>,
680}
681
682pub trait Numbers {
684 fn numbers(&self, out: &mut Vec<f64>);
685}
686
687macro_rules! numbers {
688 ($($t:ty),*) => {$(
689 impl Numbers for $t {
690 fn numbers(&self, out: &mut Vec<f64>) {
691 out.push(*self as f64);
692 }
693 }
694 )*};
695}
696numbers!(i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64);
697
698impl<T: Numbers> Numbers for Option<T> {
699 fn numbers(&self, out: &mut Vec<f64>) {
700 if let Some(x) = self {
701 x.numbers(out);
702 }
703 }
704}
705
706impl<T: Numbers> Numbers for Vec<T> {
707 fn numbers(&self, out: &mut Vec<f64>) {
708 self.iter().for_each(|x| x.numbers(out));
709 }
710}
711
712impl<T: Numbers, const N: usize> Numbers for [T; N] {
713 fn numbers(&self, out: &mut Vec<f64>) {
714 self.iter().for_each(|x| x.numbers(out));
715 }
716}
717
718impl Reader<'_> {
719 pub fn in_range<T: Numbers>(
721 &mut self,
722 key: &str,
723 value: Option<T>,
724 lo: f64,
725 hi: f64,
726 ) -> Option<T> {
727 let v = value?;
728 let mut ns = Vec::new();
729 v.numbers(&mut ns);
730 match ns.into_iter().find(|n| !(lo..=hi).contains(n)) {
731 None => Some(v),
732 Some(n) => {
733 let show = |x: f64| {
734 if x.fract() == 0.0 {
735 format!("{}", x as i64)
736 } else {
737 x.to_string()
738 }
739 };
740 let bad = show(n);
741 self.at(Seg::Key(key.to_string()), |cx| {
742 cx.problem(format!(
743 "{bad} is out of range ({}..={})",
744 show(lo),
745 show(hi)
746 ))
747 });
748 None
749 }
750 }
751 }
752}
753
754pub fn field_names(shape: &Shape) -> Vec<&'static str> {
756 match shape {
757 Shape::Struct(s) => s.fields.iter().map(|f| f.name).collect(),
758 _ => Vec::new(),
759 }
760}
761
762pub fn read_both<A: Schema, B: Schema>(
765 v: &ValueRef,
766 cx: &mut Reader,
767 also: &[&'static str],
768) -> Option<(A, B)> {
769 let (an, bn) = (field_names(&A::shape()), field_names(&B::shape()));
770 let mut b_keys = bn.clone();
771 b_keys.extend_from_slice(also);
772 let mut a_keys = an;
773 a_keys.extend_from_slice(also);
774 let a = cx.with_allowed_all(&b_keys, |cx| A::read(v, cx));
775 let b = cx.with_allowed_all(&a_keys, |cx| B::read(v, cx));
776 Some((a?, b?))
777}
778
779pub fn from_value<T: Schema>(v: &Value) -> Result<Read<T>, Vec<Problem>> {
781 let arena = bumpalo::Bump::new();
782 read_with(&v.borrow(&arena), Reader::new())
783}
784
785pub fn read_value<T: Schema>(v: &Value) -> Result<T, Vec<Problem>> {
788 let arena = bumpalo::Bump::new();
789 read_ref_value(&v.borrow(&arena))
790}
791
792pub fn read_ref_value<T: Schema>(v: &ValueRef) -> Result<T, Vec<Problem>> {
794 let reader = Reader { value_only: true, ..Reader::new() };
795 read_with(v, reader).map(|r| r.value)
796}
797
798pub fn read_bytes<T: Schema>(bytes: &[u8]) -> Result<T, Vec<Problem>> {
801 let arena = bumpalo::Bump::new();
802 let v = crate::binary::decode_ref(bytes, &arena).map_err(|e| {
803 vec![Problem { path: Path::root(), message: e.to_string(), at_key: false }]
804 })?;
805 read_ref_value(&v)
806}
807
808pub fn from_value_with<T: Schema>(
810 v: &Value,
811 resolver: &mut dyn Resolve,
812) -> Result<Read<T>, Vec<Problem>> {
813 let arena = bumpalo::Bump::new();
814 read_with(&v.borrow(&arena), Reader::with_resolver(resolver))
815}
816
817fn read_with<T: Schema>(v: &ValueRef, mut cx: Reader<'_>) -> Result<Read<T>, Vec<Problem>> {
818 let value = T::read(v, &mut cx);
819 match value {
820 Some(value) if cx.problems.is_empty() => Ok(Read {
821 value,
822 refs: cx.refs,
823 tags: cx.tags,
824 resources: cx.resources,
825 }),
826 _ => Err(cx.problems),
827 }
828}
829
830pub fn from_text<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
832 let doc = text::parse(text).map_err(|e| {
833 Errors(vec![Located {
834 at: Some(e.at),
835 path: Path::root(),
836 message: e.message,
837 }])
838 })?;
839 from_doc(text, &doc)
840}
841
842#[cfg(feature = "toml")]
844pub fn from_toml<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
845 let doc = crate::toml::parse(text).map_err(|e| {
846 Errors(vec![Located {
847 at: Some(e.at),
848 path: Path::root(),
849 message: e.message,
850 }])
851 })?;
852 from_doc(text, &doc)
853}
854
855pub fn from_doc<T: Schema>(text: &str, doc: &Doc) -> Result<Read<T>, Errors> {
857 locate(text, doc, from_value(&doc.value))
858}
859
860pub fn from_doc_with<T: Schema>(
862 text: &str,
863 doc: &Doc,
864 resolver: &mut dyn Resolve,
865) -> Result<Read<T>, Errors> {
866 locate(text, doc, from_value_with(&doc.value, resolver))
867}
868
869fn locate<T>(
870 text: &str,
871 doc: &Doc,
872 read: Result<Read<T>, Vec<Problem>>,
873) -> Result<Read<T>, Errors> {
874 read.map_err(|problems| {
875 let mut located: Vec<(u32, Located)> = problems
876 .into_iter()
877 .map(|p| {
878 let spans = doc.spans_at(&p.path);
879 let span = if p.at_key {
880 spans.key.unwrap_or(spans.span)
881 } else {
882 spans.span
883 };
884 (
885 span.start,
886 Located {
887 at: Some(LineCol::of(text, span.start as usize)),
888 path: p.path,
889 message: p.message,
890 },
891 )
892 })
893 .collect();
894 located.sort_by_key(|(offset, _)| *offset);
896 Errors(located.into_iter().map(|(_, l)| l).collect())
897 })
898}
899
900pub fn to_text<T: Schema>(value: &T) -> String {
902 text::to_string_pretty(&value.write())
903}
904
905impl Schema for Value {
908 fn shape() -> Shape {
909 Shape::Any
910 }
911 fn read(v: &ValueRef<'_>, _: &mut Reader) -> Option<Self> {
912 Some(v.to_value())
913 }
914 fn write(&self) -> Value {
915 self.clone()
916 }
917}
918
919impl Schema for bool {
920 fn shape() -> Shape {
921 Shape::Bool
922 }
923 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
924 v.as_bool().or_else(|| {
925 cx.expected("true or false", v);
926 None
927 })
928 }
929 fn write(&self) -> Value {
930 Value::Bool(*self)
931 }
932}
933
934macro_rules! int_schema {
935 ($($t:ty),*) => {$(
936 impl Schema for $t {
937 fn shape() -> Shape {
938 Shape::Int { min: <$t>::MIN as i64, max: (<$t>::MAX as u64).min(i64::MAX as u64) as i64 }
939 }
940 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
941 let Some(n) = v.as_i64() else {
942 cx.expected("an integer", v);
943 return None;
944 };
945 <$t>::try_from(n).ok().or_else(|| {
946 cx.problem(format!("{n} is out of range ({}..={})", <$t>::MIN, <$t>::MAX));
947 None
948 })
949 }
950 fn write(&self) -> Value {
951 Value::Int(*self as i64)
952 }
953 }
954 )*};
955}
956int_schema!(i8, i16, i32, i64, u8, u16, u32, u64, usize);
957
958macro_rules! float_schema {
959 ($($t:ty),*) => {$(
960 impl Schema for $t {
961 fn shape() -> Shape {
962 Shape::Float
963 }
964 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
965 v.as_f64().map(|f| f as $t).or_else(|| {
966 cx.expected("a number", v);
967 None
968 })
969 }
970 fn write(&self) -> Value {
971 Value::Float(*self as f64)
972 }
973 }
974 )*};
975}
976float_schema!(f32, f64);
977
978impl Schema for String {
979 fn shape() -> Shape {
980 Shape::Str
981 }
982 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
983 match v {
984 ValueRef::Str(s) if !s.starts_with('@') => Some(s.to_string()),
986 ValueRef::Str(s) => match typed::classify(s) {
987 Ok(Value::Str(s)) => Some(s),
988 Ok(other) => {
989 cx.problem(format!(
990 "expected plain text, found {} (write `@@` for a literal `@`)",
991 other.kind_name()
992 ));
993 None
994 }
995 Err(e) => {
996 cx.problem(e);
997 None
998 }
999 },
1000 other => {
1001 cx.expected("a string", other);
1002 None
1003 }
1004 }
1005 }
1006 fn write(&self) -> Value {
1007 Value::Str(self.clone())
1008 }
1009}
1010
1011impl<T: Schema> Schema for Option<T> {
1012 fn shape() -> Shape {
1013 Shape::Optional(T::shape)
1014 }
1015 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1016 if v.is_null() {
1017 Some(None)
1018 } else {
1019 T::read(v, cx).map(Some)
1020 }
1021 }
1022 fn write(&self) -> Value {
1023 self.as_ref().map_or(Value::Null, T::write)
1024 }
1025 fn absent() -> Option<Self> {
1026 Some(None)
1027 }
1028 fn skip(&self) -> bool {
1029 self.is_none()
1030 }
1031}
1032
1033impl<T: Schema> Schema for Box<T> {
1034 fn shape() -> Shape {
1035 T::shape()
1036 }
1037 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1038 T::read(v, cx).map(Box::new)
1039 }
1040 fn write(&self) -> Value {
1041 (**self).write()
1042 }
1043 fn absent() -> Option<Self> {
1044 T::absent().map(Box::new)
1045 }
1046 fn skip(&self) -> bool {
1047 (**self).skip()
1048 }
1049}
1050
1051impl<T: Schema> Schema for Vec<T> {
1052 fn shape() -> Shape {
1053 Shape::List(T::shape)
1054 }
1055 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1056 let ValueRef::List(items) = v else {
1057 cx.expected("a list", v);
1058 return None;
1059 };
1060 let mut out = Vec::with_capacity(items.len());
1061 let mut ok = true;
1062 for (i, item) in items.iter().enumerate() {
1063 match cx.at(Seg::Index(i), |cx| T::read(item, cx)) {
1064 Some(x) => out.push(x),
1065 None => ok = false,
1066 }
1067 }
1068 ok.then_some(out)
1069 }
1070 fn write(&self) -> Value {
1071 Value::List(self.iter().map(T::write).collect())
1072 }
1073}
1074
1075impl<T: Schema, const N: usize> Schema for [T; N] {
1076 fn shape() -> Shape {
1077 Shape::List(T::shape)
1078 }
1079 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1080 let items = Vec::<T>::read(v, cx)?;
1081 let len = items.len();
1082 items.try_into().ok().or_else(|| {
1083 cx.problem(format!("expected {N} items, found {len}"));
1084 None
1085 })
1086 }
1087 fn write(&self) -> Value {
1088 Value::List(self.iter().map(T::write).collect())
1089 }
1090}
1091
1092impl<A: Schema, B: Schema> Schema for (A, B) {
1094 fn shape() -> Shape {
1095 Shape::List(Value::shape)
1096 }
1097 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1098 let ValueRef::List(items) = v else {
1099 cx.expected("a pair [a, b]", v);
1100 return None;
1101 };
1102 if items.len() != 2 {
1103 cx.problem(format!("expected 2 items, found {}", items.len()));
1104 return None;
1105 }
1106 let a = cx.at(Seg::Index(0), |cx| A::read(&items[0], cx));
1107 let b = cx.at(Seg::Index(1), |cx| B::read(&items[1], cx));
1108 Some((a?, b?))
1109 }
1110 fn write(&self) -> Value {
1111 Value::List(vec![self.0.write(), self.1.write()])
1112 }
1113}
1114
1115fn read_map<T: Schema>(v: &ValueRef, cx: &mut Reader, mut put: impl FnMut(String, T)) -> Option<()> {
1116 let map = cx.map(v)?;
1117 let mut ok = true;
1118 for (k, item) in map.iter() {
1119 if is_metadata_key(k) {
1120 continue;
1121 }
1122 match cx.at(Seg::Key(k.to_string()), |cx| T::read(item, cx)) {
1123 Some(x) => put(k.to_string(), x),
1124 None => ok = false,
1125 }
1126 }
1127 ok.then_some(())
1128}
1129
1130impl<T: Schema> Schema for BTreeMap<String, T> {
1131 fn shape() -> Shape {
1132 Shape::Map(T::shape)
1133 }
1134 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1135 let mut out = BTreeMap::new();
1136 read_map(v, cx, |k, x| {
1137 out.insert(k, x);
1138 })?;
1139 Some(out)
1140 }
1141 fn write(&self) -> Value {
1142 Value::Map(self.iter().map(|(k, v)| (k.clone(), v.write())).collect())
1143 }
1144}
1145
1146impl<T: Schema> Schema for HashMap<String, T> {
1147 fn shape() -> Shape {
1148 Shape::Map(T::shape)
1149 }
1150 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1151 let mut out = HashMap::new();
1152 read_map(v, cx, |k, x| {
1153 out.insert(k, x);
1154 })?;
1155 Some(out)
1156 }
1157 fn write(&self) -> Value {
1158 let mut keys: Vec<&String> = self.keys().collect();
1159 keys.sort();
1160 Value::Map(
1161 keys.into_iter()
1162 .map(|k| (k.clone(), self[k].write()))
1163 .collect(),
1164 )
1165 }
1166}
1167
1168#[derive(Debug, Clone, PartialEq)]
1170pub struct Ordered<T>(pub Vec<(String, T)>);
1171
1172impl<T> Default for Ordered<T> {
1173 fn default() -> Self {
1174 Self(Vec::new())
1175 }
1176}
1177
1178impl<T: Schema> Schema for Ordered<T> {
1179 fn shape() -> Shape {
1180 Shape::Map(T::shape)
1181 }
1182 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1183 let mut out = Vec::new();
1184 read_map(v, cx, |k, x| out.push((k, x)))?;
1185 Some(Ordered(out))
1186 }
1187 fn write(&self) -> Value {
1188 Value::Map(self.0.iter().map(|(k, v)| (k.clone(), v.write())).collect())
1189 }
1190}
1191
1192pub trait Kind {
1197 const NAME: &'static str;
1198}
1199
1200pub trait Kinds {
1202 const NAMES: &'static [&'static str];
1203}
1204
1205impl<K: Kind> Kinds for K {
1206 const NAMES: &'static [&'static str] = &[K::NAME];
1207}
1208
1209impl<A: Kind, B: Kind> Kinds for (A, B) {
1210 const NAMES: &'static [&'static str] = &[A::NAME, B::NAME];
1211}
1212
1213impl<A: Kind, B: Kind, C: Kind> Kinds for (A, B, C) {
1214 const NAMES: &'static [&'static str] = &[A::NAME, B::NAME, C::NAME];
1215}
1216
1217#[macro_export]
1219macro_rules! kind {
1220 ($($name:ident = $kind:expr),* $(,)?) => {$(
1221 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
1222 pub struct $name;
1223 impl $crate::Kind for $name {
1224 const NAME: &'static str = $kind;
1225 }
1226 )*};
1227}
1228
1229pub mod kinds {
1231 crate::kind! {
1232 Item = yog_ids::kind::ITEM,
1233 Block = yog_ids::kind::BLOCK,
1234 Entity = yog_ids::kind::ENTITY,
1235 Recipe = yog_ids::kind::RECIPE,
1236 Entry = yog_ids::kind::ENTRY,
1237 Category = yog_ids::kind::CATEGORY,
1238 Fluid = yog_ids::kind::FLUID,
1239 Biome = yog_ids::kind::BIOME,
1240 Dimension = yog_ids::kind::DIMENSION,
1241 Texture = yog_ids::kind::TEXTURE,
1242 Material = yog_ids::kind::MATERIAL,
1243 }
1244}
1245
1246pub struct Id<K> {
1249 pub id: String,
1250 kind: PhantomData<K>,
1251}
1252
1253pub type ItemId = Id<kinds::Item>;
1254pub type BlockId = Id<kinds::Block>;
1255pub type EntityId = Id<kinds::Entity>;
1256pub type RecipeId = Id<kinds::Recipe>;
1257
1258impl<K> Id<K> {
1259 pub fn new(id: impl Into<String>) -> Self {
1260 Self {
1261 id: id.into(),
1262 kind: PhantomData,
1263 }
1264 }
1265}
1266
1267impl<K> Clone for Id<K> {
1268 fn clone(&self) -> Self {
1269 Self::new(self.id.clone())
1270 }
1271}
1272impl<K> PartialEq for Id<K> {
1273 fn eq(&self, o: &Self) -> bool {
1274 self.id == o.id
1275 }
1276}
1277impl<K> Eq for Id<K> {}
1278impl<K> std::hash::Hash for Id<K> {
1279 fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
1280 self.id.hash(h);
1281 }
1282}
1283impl<K: Kind> fmt::Debug for Id<K> {
1284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1285 write!(f, "@{}:{}", K::NAME, self.id)
1286 }
1287}
1288impl<K> fmt::Display for Id<K> {
1289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1290 f.write_str(&self.id)
1291 }
1292}
1293
1294pub fn read_ref(v: &ValueRef, cx: &mut Reader, kinds: &[&str]) -> Option<Ref> {
1297 let wanted = || {
1298 kinds
1299 .iter()
1300 .map(|k| format!("`@{k}:`"))
1301 .collect::<Vec<_>>()
1302 .join(" or ")
1303 };
1304 let r = match v {
1305 ValueRef::Ref { kind, id } => Ref::new(*kind, *id),
1306 ValueRef::Str(s) if s.starts_with('@') => match typed::classify(s) {
1307 Ok(Value::Ref(r)) => r,
1308 Ok(other) => {
1309 cx.problem(format!(
1310 "expected a reference ({}), found {}",
1311 if kinds.is_empty() {
1312 "`@kind:id`".into()
1313 } else {
1314 wanted()
1315 },
1316 other.kind_name()
1317 ));
1318 return None;
1319 }
1320 Err(e) => {
1321 cx.problem(e);
1322 return None;
1323 }
1324 },
1325 ValueRef::Str(s) => {
1326 let kind = kinds.first().copied().unwrap_or(yog_ids::kind::ITEM);
1327 cx.problem(format!("`{s}`: name it by a reference — `@{kind}:{s}`"));
1328 return None;
1329 }
1330 other => {
1331 cx.expected("a reference `@kind:id`", other);
1332 return None;
1333 }
1334 };
1335 if !kinds.is_empty() && !kinds.contains(&r.kind.as_str()) {
1336 cx.problem(format!(
1337 "`@{}:{}` is a reference to {} — this takes {}",
1338 r.kind,
1339 r.id,
1340 r.kind,
1341 wanted()
1342 ));
1343 return None;
1344 }
1345 cx.note_ref(r.clone());
1346 Some(r)
1347}
1348
1349fn read_tag(v: &ValueRef, cx: &mut Reader, kind: &str) -> Option<Ref> {
1350 let r = match v {
1351 ValueRef::Tag { kind, id } => Ref::new(*kind, *id),
1352 ValueRef::Str(s) => match typed::classify(s) {
1353 Ok(Value::Tag(r)) => r,
1354 Ok(_) => {
1355 cx.problem(format!("expected a tag `@tag:{kind}:id`, found {s:?}"));
1356 return None;
1357 }
1358 Err(e) => {
1359 cx.problem(e);
1360 return None;
1361 }
1362 },
1363 other => {
1364 cx.expected("a tag", other);
1365 return None;
1366 }
1367 };
1368 if !kind.is_empty() && r.kind != kind {
1369 cx.problem(format!(
1370 "expected a tag of {kind}, found a tag of {}",
1371 r.kind
1372 ));
1373 return None;
1374 }
1375 cx.note_tag(r.clone());
1376 Some(r)
1377}
1378
1379impl<K: Kind> Schema for Id<K> {
1380 fn shape() -> Shape {
1381 Shape::Ref(<K as Kinds>::NAMES)
1382 }
1383 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1384 read_ref(v, cx, <K as Kinds>::NAMES).map(|r| Id::new(r.id))
1385 }
1386 fn write(&self) -> Value {
1387 Value::Ref(Ref::new(K::NAME, self.id.clone()))
1388 }
1389}
1390
1391impl Schema for Ref {
1392 fn shape() -> Shape {
1393 Shape::Ref(&[])
1394 }
1395 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1396 if let ValueRef::Str(s) = v
1397 && !s.starts_with('@')
1398 {
1399 cx.problem(format!("expected a reference `@kind:id`, found {s:?}"));
1400 return None;
1401 }
1402 read_ref(v, cx, &[])
1403 }
1404 fn write(&self) -> Value {
1405 Value::Ref(self.clone())
1406 }
1407}
1408
1409#[derive(Debug, Clone, PartialEq)]
1412pub enum AnyRefOrTag {
1413 One(Ref),
1414 Tag(Ref),
1415}
1416
1417impl AnyRefOrTag {
1418 pub fn kind(&self) -> &str {
1420 match self {
1421 AnyRefOrTag::One(r) | AnyRefOrTag::Tag(r) => &r.kind,
1422 }
1423 }
1424}
1425
1426impl fmt::Display for AnyRefOrTag {
1427 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1428 match self {
1429 AnyRefOrTag::One(r) => write!(f, "@{}:{}", r.kind, r.id),
1430 AnyRefOrTag::Tag(r) => write!(f, "@tag:{}:{}", r.kind, r.id),
1431 }
1432 }
1433}
1434
1435impl Schema for AnyRefOrTag {
1436 fn shape() -> Shape {
1437 Shape::RefOrTag(&[])
1438 }
1439 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1440 let is_tag = match v {
1441 ValueRef::Tag { .. } => true,
1442 ValueRef::Str(s) => s.starts_with("@tag:"),
1443 _ => false,
1444 };
1445 if is_tag {
1446 let r = match v {
1447 ValueRef::Tag { kind, id } => Ref::new(*kind, *id),
1448 ValueRef::Str(s) => match typed::classify(s) {
1449 Ok(Value::Tag(r)) => r,
1450 Ok(_) => {
1451 cx.problem(format!("expected a tag `@tag:kind:id`, found {s:?}"));
1452 return None;
1453 }
1454 Err(e) => {
1455 cx.problem(e);
1456 return None;
1457 }
1458 },
1459 _ => return None,
1460 };
1461 return Some(AnyRefOrTag::Tag(r));
1462 }
1463 if let ValueRef::Str(s) = v
1464 && !s.starts_with('@')
1465 {
1466 cx.problem(format!("`{s}`: write `@<kind>:{s}` or a tag `@tag:<kind>:{s}`"));
1467 return None;
1468 }
1469 read_ref(v, cx, &[]).map(AnyRefOrTag::One)
1470 }
1471 fn write(&self) -> Value {
1472 match self {
1473 AnyRefOrTag::One(r) => Value::Ref(r.clone()),
1474 AnyRefOrTag::Tag(r) => Value::Tag(r.clone()),
1475 }
1476 }
1477}
1478
1479pub struct TagId<K> {
1481 pub id: String,
1482 kind: PhantomData<K>,
1483}
1484
1485impl<K> TagId<K> {
1486 pub fn new(id: impl Into<String>) -> Self {
1487 Self {
1488 id: id.into(),
1489 kind: PhantomData,
1490 }
1491 }
1492}
1493impl<K> Clone for TagId<K> {
1494 fn clone(&self) -> Self {
1495 Self::new(self.id.clone())
1496 }
1497}
1498impl<K> PartialEq for TagId<K> {
1499 fn eq(&self, o: &Self) -> bool {
1500 self.id == o.id
1501 }
1502}
1503impl<K: Kind> fmt::Debug for TagId<K> {
1504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1505 write!(f, "@tag:{}:{}", K::NAME, self.id)
1506 }
1507}
1508
1509impl<K: Kind> Schema for TagId<K> {
1510 fn shape() -> Shape {
1511 Shape::Tag(<K as Kinds>::NAMES)
1512 }
1513 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1514 read_tag(v, cx, K::NAME).map(|r| TagId::new(r.id))
1515 }
1516 fn write(&self) -> Value {
1517 Value::Tag(Ref::new(K::NAME, self.id.clone()))
1518 }
1519}
1520
1521#[derive(Debug, Clone, PartialEq)]
1523pub enum OneOrTag<K: Kind> {
1524 One(Id<K>),
1525 Tag(TagId<K>),
1526}
1527
1528pub type ItemIdOrTag = OneOrTag<kinds::Item>;
1530
1531impl<K: Kind> Schema for OneOrTag<K> {
1532 fn shape() -> Shape {
1533 Shape::RefOrTag(<K as Kinds>::NAMES)
1534 }
1535 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1536 if let ValueRef::Str(s) = v
1537 && let Some(tag) = s.strip_prefix('#')
1538 {
1539 cx.problem(format!("`{s}`: write a tag as `@tag:{}:{tag}`", K::NAME));
1540 return None;
1541 }
1542 let is_tag = match v {
1543 ValueRef::Tag { .. } => true,
1544 ValueRef::Str(s) => s.starts_with("@tag:"),
1545 _ => false,
1546 };
1547 if is_tag {
1548 TagId::read(v, cx).map(OneOrTag::Tag)
1549 } else {
1550 Id::read(v, cx).map(OneOrTag::One)
1551 }
1552 }
1553 fn write(&self) -> Value {
1554 match self {
1555 OneOrTag::One(i) => i.write(),
1556 OneOrTag::Tag(t) => t.write(),
1557 }
1558 }
1559}
1560
1561impl Schema for Res {
1562 fn shape() -> Shape {
1563 Shape::Res
1564 }
1565 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1566 let r = match v {
1567 ValueRef::Res { set, key } => Res { set: set.to_string(), key: key.to_string() },
1568 ValueRef::Str(s) => match typed::classify(s) {
1569 Ok(Value::Res(r)) => r,
1570 Ok(_) => {
1571 cx.problem(format!("expected a resource `@Set.key`, found {s:?}"));
1572 return None;
1573 }
1574 Err(e) => {
1575 cx.problem(e);
1576 return None;
1577 }
1578 },
1579 other => {
1580 cx.expected("a resource `@Set.key`", other);
1581 return None;
1582 }
1583 };
1584 cx.note_res(r.clone());
1585 Some(r)
1586 }
1587 fn write(&self) -> Value {
1588 Value::Res(self.clone())
1589 }
1590}
1591
1592#[derive(Debug, Clone, PartialEq)]
1596pub enum Text {
1597 Literal(String),
1598 Res(Res),
1599 NameOf(Ref),
1600 Lang(String),
1601}
1602
1603impl Default for Text {
1604 fn default() -> Self {
1605 Text::Literal(String::new())
1606 }
1607}
1608
1609impl Schema for Text {
1610 fn shape() -> Shape {
1611 Shape::Text
1612 }
1613 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1614 let typed = match v {
1615 ValueRef::Str(s) if !s.starts_with('@') => return Some(Text::Literal(s.to_string())),
1617 ValueRef::Text(t) => return Some(Text::Literal(t.to_string())),
1618 ValueRef::Lang(k) => return Some(Text::Lang(k.to_string())),
1619 ValueRef::Str(s) => typed::classify(s).map_err(|e| cx.problem(e)).ok()?,
1620 ValueRef::Res { .. } | ValueRef::Ref { .. } => v.to_value(),
1621 other => {
1622 cx.expected("a text", other);
1623 return None;
1624 }
1625 };
1626 match typed {
1627 Value::Str(s) => Some(Text::Literal(s)),
1628 Value::Res(r) => {
1629 cx.note_res(r.clone());
1630 Some(Text::Res(r))
1631 }
1632 Value::Ref(r) => {
1633 cx.note_ref(r.clone());
1634 Some(Text::NameOf(r))
1635 }
1636 Value::Lang(k) => Some(Text::Lang(k)),
1637 other => {
1638 cx.problem(format!("expected a text, found {}", other.kind_name()));
1639 None
1640 }
1641 }
1642 }
1643 fn write(&self) -> Value {
1644 match self {
1645 Text::Literal(s) => Value::Text(s.clone()),
1646 Text::Res(r) => Value::Res(r.clone()),
1647 Text::NameOf(r) => Value::Ref(r.clone()),
1648 Text::Lang(k) => Value::Lang(k.clone()),
1649 }
1650 }
1651}
1652
1653#[derive(Debug, Clone, PartialEq)]
1656pub enum Image {
1657 Source(String),
1658 Res(Res),
1659}
1660
1661impl Schema for Image {
1662 fn shape() -> Shape {
1663 Shape::Image
1664 }
1665 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1666 match v {
1667 ValueRef::Image(s) => Some(Image::Source(s.to_string())),
1668 ValueRef::Res { .. } => Res::read(v, cx).map(Image::Res),
1669 ValueRef::Str(s) if !s.starts_with('@') => Some(Image::Source(s.to_string())),
1670 ValueRef::Str(s) => match typed::classify(s) {
1671 Ok(Value::Image(src)) => Some(Image::Source(src)),
1672 Ok(Value::Res(r)) => {
1673 cx.note_res(r.clone());
1674 Some(Image::Res(r))
1675 }
1676 Ok(Value::Str(s)) => Some(Image::Source(s)),
1677 Ok(other) => {
1678 cx.problem(format!("expected an image, found {}", other.kind_name()));
1679 None
1680 }
1681 Err(e) => {
1682 cx.problem(e);
1683 None
1684 }
1685 },
1686 other => {
1687 cx.expected("an image", other);
1688 None
1689 }
1690 }
1691 }
1692 fn write(&self) -> Value {
1693 match self {
1694 Image::Source(s) => Value::Image(s.clone()),
1695 Image::Res(r) => Value::Res(r.clone()),
1696 }
1697 }
1698}
1699
1700#[derive(Debug, Clone, PartialEq)]
1702pub enum Color {
1703 Argb(u32),
1704 Res(Res),
1705}
1706
1707impl Schema for Color {
1708 fn shape() -> Shape {
1709 Shape::Color
1710 }
1711 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1712 match v {
1713 ValueRef::Color(c) => Some(Color::Argb(*c)),
1714 ValueRef::Res { .. } => Res::read(v, cx).map(Color::Res),
1715 ValueRef::Str(s) if s.starts_with('#') => {
1716 typed::parse_color(s).map(Color::Argb).or_else(|| {
1717 cx.problem(format!("`{s}` isn't a color (#RRGGBB or #AARRGGBB)"));
1718 None
1719 })
1720 }
1721 ValueRef::Str(s) if s.starts_with('@') => Res::read(v, cx).map(Color::Res),
1722 other => {
1723 cx.expected("a color (#RRGGBB or #AARRGGBB)", other);
1724 None
1725 }
1726 }
1727 }
1728 fn write(&self) -> Value {
1729 match self {
1730 Color::Argb(c) => Value::Color(*c),
1731 Color::Res(r) => Value::Res(r.clone()),
1732 }
1733 }
1734}