1use 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
15pub trait Schema: Sized {
17 fn shape() -> Shape;
19
20 fn read(v: &Value, cx: &mut Reader) -> Option<Self>;
23
24 fn write(&self) -> Value;
26
27 fn absent() -> Option<Self> {
29 None
30 }
31
32 fn skip(&self) -> bool {
34 false
35 }
36}
37
38pub type ShapeFn = fn() -> Shape;
42
43#[derive(Debug, Clone)]
46pub enum Shape {
47 Any,
49 Bool,
50 Int {
51 min: i64,
52 max: i64,
53 },
54 Float,
55 Str,
57 Text,
59 Image,
61 Color,
63 Res,
65 Ref(&'static [&'static str]),
67 Tag(&'static [&'static str]),
69 RefOrTag(&'static [&'static str]),
71 RefOrImage(&'static [&'static str]),
73 List(ShapeFn),
74 Map(ShapeFn),
76 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 pub required: bool,
96}
97
98#[derive(Debug, Clone)]
99pub struct EnumShape {
100 pub name: &'static str,
101 pub doc: &'static str,
102 pub tag: Option<&'static str>,
104 pub variants: Vec<Variant>,
105}
106
107#[derive(Debug, Clone)]
108pub struct Variant {
109 pub name: &'static str,
110 pub doc: &'static str,
111 pub fields: Vec<Field>,
113}
114
115impl Shape {
116 pub fn describe(&self) -> Value {
119 let mut defs = Map::new();
120 let root = self.describe_into(&mut defs);
121 let mut m = Map::new();
122 m.insert("root", root);
123 m.insert("defs", Value::Map(defs));
124 Value::Map(m)
125 }
126
127 fn describe_into(&self, defs: &mut Map) -> Value {
128 let kind = |k: &str| {
129 let mut m = Map::new();
130 m.insert("kind", Value::from(k));
131 m
132 };
133 let fields = |fs: &[Field], defs: &mut Map| -> Value {
134 Value::List(
135 fs.iter()
136 .map(|f| {
137 let mut m = Map::new();
138 m.insert("name", Value::from(f.name));
139 if !f.doc.is_empty() {
140 m.insert("doc", Value::from(f.doc));
141 }
142 m.insert("shape", (f.shape)().describe_into(defs));
143 m.insert("required", Value::Bool(f.required));
144 Value::Map(m)
145 })
146 .collect(),
147 )
148 };
149 let m = match self {
150 Shape::Any => kind("any"),
151 Shape::Bool => kind("bool"),
152 Shape::Int { min, max } => {
153 let mut m = kind("int");
154 m.insert("min", Value::Int(*min));
155 m.insert("max", Value::Int(*max));
156 m
157 }
158 Shape::Float => kind("float"),
159 Shape::Str => kind("string"),
160 Shape::Text => kind("text"),
161 Shape::Image => kind("image"),
162 Shape::Color => kind("color"),
163 Shape::Res => kind("res"),
164 Shape::Ref(k) | Shape::Tag(k) | Shape::RefOrTag(k) | Shape::RefOrImage(k) => {
165 let mut m = kind(match self {
166 Shape::Ref(_) => "ref",
167 Shape::Tag(_) => "tag",
168 Shape::RefOrTag(_) => "ref_or_tag",
169 _ => "ref_or_image",
170 });
171 m.insert(
172 "of",
173 Value::List(k.iter().map(|k| Value::from(*k)).collect()),
174 );
175 m
176 }
177 Shape::List(s) | Shape::Map(s) | Shape::Optional(s) => {
178 let mut m = kind(match self {
179 Shape::List(_) => "list",
180 Shape::Map(_) => "map",
181 _ => "optional",
182 });
183 m.insert("of", s().describe_into(defs));
184 m
185 }
186 Shape::Struct(s) => {
187 if defs.get(s.name).is_none() {
188 defs.insert(s.name, Value::Null);
190 let mut d = kind("struct");
191 if !s.doc.is_empty() {
192 d.insert("doc", Value::from(s.doc));
193 }
194 d.insert("fields", fields(&s.fields, defs));
195 defs.insert(s.name, Value::Map(d));
196 }
197 let mut m = kind("def");
198 m.insert("name", Value::from(s.name));
199 m
200 }
201 Shape::Enum(e) => {
202 if defs.get(e.name).is_none() {
203 defs.insert(e.name, Value::Null);
204 let mut d = kind("enum");
205 if !e.doc.is_empty() {
206 d.insert("doc", Value::from(e.doc));
207 }
208 if let Some(tag) = e.tag {
209 d.insert("tag", Value::from(tag));
210 }
211 let variants = e
212 .variants
213 .iter()
214 .map(|v| {
215 let mut m = Map::new();
216 m.insert("name", Value::from(v.name));
217 if !v.doc.is_empty() {
218 m.insert("doc", Value::from(v.doc));
219 }
220 if e.tag.is_some() {
221 m.insert("fields", fields(&v.fields, defs));
222 }
223 Value::Map(m)
224 })
225 .collect();
226 d.insert("variants", Value::List(variants));
227 defs.insert(e.name, Value::Map(d));
228 }
229 let mut m = kind("def");
230 m.insert("name", Value::from(e.name));
231 m
232 }
233 };
234 Value::Map(m)
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Eq)]
242pub struct Problem {
243 pub path: Path,
244 pub message: String,
245 pub at_key: bool,
247}
248
249#[derive(Debug, Clone, PartialEq, Eq)]
251pub struct Found<T> {
252 pub value: T,
253 pub path: Path,
254}
255
256#[derive(Default)]
259pub struct Reader<'r> {
260 path: Path,
261 resolver: Option<&'r mut dyn Resolve>,
263 pub problems: Vec<Problem>,
264 pub refs: Vec<Found<Ref>>,
265 pub tags: Vec<Found<Ref>>,
266 pub resources: Vec<Found<Res>>,
267 allowed: Vec<&'static str>,
269}
270
271pub fn is_metadata_key(key: &str) -> bool {
273 key.starts_with('$') || key.starts_with('_') || key == "comment"
274}
275
276pub trait Resolve {
280 fn reference(&mut self, r: &Ref, _path: &Path) -> Result<String, String> {
282 Ok(r.id.clone())
283 }
284
285 fn text(&mut self, t: &Text, _path: &Path) -> Result<String, String> {
287 Ok(match t {
288 Text::Literal(s) => s.clone(),
289 Text::Res(r) => format!("@{}.{}", r.set, r.key),
290 Text::NameOf(r) => format!("@{}:{}", r.kind, r.id),
291 })
292 }
293
294 fn words(&mut self, _r: &Res) -> Option<String> {
296 None
297 }
298}
299
300impl<'r> Reader<'r> {
301 pub fn new() -> Self {
302 Self::default()
303 }
304
305 pub fn with_resolver(resolver: &'r mut dyn Resolve) -> Self {
307 Self {
308 resolver: Some(resolver),
309 ..Self::default()
310 }
311 }
312
313 pub fn resolve_ref(&mut self, r: &Ref) -> Option<String> {
315 let result = match &mut self.resolver {
316 Some(res) => res.reference(r, &self.path),
317 None => Ok(r.id.clone()),
318 };
319 result.map_err(|e| self.problem(e)).ok()
320 }
321
322 pub fn resolve_text(&mut self, t: &Text) -> Option<String> {
324 struct Keep;
325 impl Resolve for Keep {}
326 let result = match &mut self.resolver {
327 Some(res) => res.text(t, &self.path),
328 None => Keep.text(t, &self.path),
329 };
330 result.map_err(|e| self.problem(e)).ok()
331 }
332
333 pub fn words(&mut self, r: &Res) -> Option<String> {
335 self.resolver.as_mut()?.words(r)
336 }
337
338 pub fn path(&self) -> &Path {
339 &self.path
340 }
341
342 pub fn problem(&mut self, message: impl Into<String>) {
344 self.problems.push(Problem {
345 path: self.path.clone(),
346 message: message.into(),
347 at_key: false,
348 });
349 }
350
351 pub fn expected(&mut self, what: &str, found: &Value) {
353 self.problem(format!("expected {what}, found {}", found_text(found)));
354 }
355
356 pub fn at<T>(&mut self, seg: Seg, f: impl FnOnce(&mut Self) -> T) -> T {
358 self.path.push(seg);
359 let r = f(self);
360 self.path.pop();
361 r
362 }
363
364 pub fn note_ref(&mut self, r: Ref) {
365 self.refs.push(Found {
366 value: r,
367 path: self.path.clone(),
368 });
369 }
370
371 pub fn note_tag(&mut self, r: Ref) {
372 self.tags.push(Found {
373 value: r,
374 path: self.path.clone(),
375 });
376 }
377
378 pub fn note_res(&mut self, r: Res) {
379 self.resources.push(Found {
380 value: r,
381 path: self.path.clone(),
382 });
383 }
384
385 pub fn map<'v>(&mut self, v: &'v Value) -> Option<&'v Map> {
387 match v {
388 Value::Map(m) => Some(m),
389 other => {
390 self.expected("an object", other);
391 None
392 }
393 }
394 }
395
396 pub fn unknown_keys(&mut self, map: &Map, known: &[&str]) {
398 for key in map.keys() {
399 if known.contains(&key) || is_metadata_key(key) || self.allowed.contains(&key) {
400 continue;
401 }
402 let hint = closest(key, known)
403 .map(|k| format!(" — did you mean `{k}`?"))
404 .unwrap_or_else(|| {
405 if known.is_empty() {
406 String::new()
407 } else {
408 format!(
409 " (expected {})",
410 known
411 .iter()
412 .map(|k| format!("`{k}`"))
413 .collect::<Vec<_>>()
414 .join(", ")
415 )
416 }
417 });
418 self.problems.push(Problem {
419 path: self.path.key(key),
420 message: format!("unknown field `{key}`{hint}"),
421 at_key: true,
422 });
423 }
424 }
425
426 pub fn unknown_name(&mut self, name: &str, known: &[&str]) {
428 let hint = match closest(name, known) {
429 Some(k) => format!(" — did you mean `{k}`?"),
430 None => format!(
431 " (expected {})",
432 known
433 .iter()
434 .map(|k| format!("`{k}`"))
435 .collect::<Vec<_>>()
436 .join(", ")
437 ),
438 };
439 self.problem(format!("unknown `{name}`{hint}"));
440 }
441
442 pub fn field<T: Schema>(&mut self, map: &Map, key: &'static str) -> Option<T> {
444 match map.get(key) {
445 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
446 None => T::absent().or_else(|| {
447 self.problem(format!("missing `{key}`"));
448 None
449 }),
450 }
451 }
452
453 pub fn field_or<T: Schema>(
455 &mut self,
456 map: &Map,
457 key: &'static str,
458 default: impl FnOnce() -> T,
459 ) -> Option<T> {
460 match map.get(key) {
461 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
462 None => Some(default()),
463 }
464 }
465
466 pub fn field_with<W: crate::with::With<F>, F>(
468 &mut self,
469 map: &Map,
470 key: &'static str,
471 ) -> Option<F> {
472 match map.get(key) {
473 Some(v) => self.at(Seg::Key(key.to_string()), |cx| W::read(v, cx)),
474 None => W::absent().or_else(|| {
475 self.problem(format!("missing `{key}`"));
476 None
477 }),
478 }
479 }
480
481 pub fn field_or_with<W: crate::with::With<F>, F>(
483 &mut self,
484 map: &Map,
485 key: &'static str,
486 default: impl FnOnce() -> F,
487 ) -> Option<F> {
488 match map.get(key) {
489 Some(v) => self.at(Seg::Key(key.to_string()), |cx| W::read(v, cx)),
490 None => Some(default()),
491 }
492 }
493
494 pub fn with_allowed<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
496 self.allowed.push(key);
497 let r = f(self);
498 self.allowed.pop();
499 r
500 }
501
502 pub fn own_keys<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
504 let saved = std::mem::take(&mut self.allowed);
505 let r = f(self);
506 self.allowed = saved;
507 r
508 }
509}
510
511fn found_text(v: &Value) -> String {
512 match v {
513 Value::Str(s) if s.chars().count() <= 40 => format!("{s:?}"),
514 Value::Int(n) => n.to_string(),
515 Value::Float(f) => f.to_string(),
516 Value::Bool(b) => b.to_string(),
517 other => other.kind_name().to_string(),
518 }
519}
520
521fn closest<'k>(key: &str, known: &[&'k str]) -> Option<&'k str> {
524 let lower = key.to_ascii_lowercase();
525 known
526 .iter()
527 .map(|k| (edit_distance(&lower, k), *k))
528 .filter(|(d, k)| *d <= if k.len() <= 3 { 1 } else { k.len() / 2 })
529 .min_by_key(|(d, _)| *d)
530 .map(|(_, k)| k)
531}
532
533fn edit_distance(a: &str, b: &str) -> usize {
534 let b: Vec<char> = b.chars().collect();
535 let mut prev: Vec<usize> = (0..=b.len()).collect();
536 for (i, ca) in a.chars().enumerate() {
537 let mut cur = vec![i + 1; b.len() + 1];
538 for (j, cb) in b.iter().enumerate() {
539 cur[j + 1] = (prev[j] + usize::from(ca != *cb))
540 .min(prev[j + 1] + 1)
541 .min(cur[j] + 1);
542 }
543 prev = cur;
544 }
545 prev[b.len()]
546}
547
548#[derive(Debug, Clone, PartialEq, Eq)]
550pub struct Errors(pub Vec<Located>);
551
552#[derive(Debug, Clone, PartialEq, Eq)]
554pub struct Located {
555 pub at: Option<LineCol>,
556 pub path: Path,
557 pub message: String,
558}
559
560impl fmt::Display for Located {
561 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562 if let Some(at) = self.at {
563 write!(f, "{at}: ")?;
564 }
565 if !self.path.is_root() {
566 write!(f, "{}: ", self.path)?;
567 }
568 f.write_str(&self.message)
569 }
570}
571
572impl fmt::Display for Errors {
573 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574 for (i, e) in self.0.iter().enumerate() {
575 if i > 0 {
576 f.write_str("\n")?;
577 }
578 write!(f, "{e}")?;
579 }
580 Ok(())
581 }
582}
583
584impl std::error::Error for Errors {}
585
586#[derive(Debug)]
588pub struct Read<T> {
589 pub value: T,
590 pub refs: Vec<Found<Ref>>,
591 pub tags: Vec<Found<Ref>>,
592 pub resources: Vec<Found<Res>>,
593}
594
595pub fn from_value<T: Schema>(v: &Value) -> Result<Read<T>, Vec<Problem>> {
597 read_with(v, Reader::new())
598}
599
600pub fn from_value_with<T: Schema>(
602 v: &Value,
603 resolver: &mut dyn Resolve,
604) -> Result<Read<T>, Vec<Problem>> {
605 read_with(v, Reader::with_resolver(resolver))
606}
607
608fn read_with<T: Schema>(v: &Value, mut cx: Reader<'_>) -> Result<Read<T>, Vec<Problem>> {
609 let value = T::read(v, &mut cx);
610 match value {
611 Some(value) if cx.problems.is_empty() => Ok(Read {
612 value,
613 refs: cx.refs,
614 tags: cx.tags,
615 resources: cx.resources,
616 }),
617 _ => Err(cx.problems),
618 }
619}
620
621pub fn from_text<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
623 let doc = text::parse(text).map_err(|e| {
624 Errors(vec![Located {
625 at: Some(e.at),
626 path: Path::root(),
627 message: e.message,
628 }])
629 })?;
630 from_doc(text, &doc)
631}
632
633#[cfg(feature = "toml")]
635pub fn from_toml<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
636 let doc = crate::toml::parse(text).map_err(|e| {
637 Errors(vec![Located {
638 at: Some(e.at),
639 path: Path::root(),
640 message: e.message,
641 }])
642 })?;
643 from_doc(text, &doc)
644}
645
646pub fn from_doc<T: Schema>(text: &str, doc: &Doc) -> Result<Read<T>, Errors> {
648 locate(text, doc, from_value(&doc.value))
649}
650
651pub fn from_doc_with<T: Schema>(
653 text: &str,
654 doc: &Doc,
655 resolver: &mut dyn Resolve,
656) -> Result<Read<T>, Errors> {
657 locate(text, doc, from_value_with(&doc.value, resolver))
658}
659
660fn locate<T>(
661 text: &str,
662 doc: &Doc,
663 read: Result<Read<T>, Vec<Problem>>,
664) -> Result<Read<T>, Errors> {
665 read.map_err(|problems| {
666 let mut located: Vec<(u32, Located)> = problems
667 .into_iter()
668 .map(|p| {
669 let spans = doc.spans_at(&p.path);
670 let span = if p.at_key {
671 spans.key.unwrap_or(spans.span)
672 } else {
673 spans.span
674 };
675 (
676 span.start,
677 Located {
678 at: Some(LineCol::of(text, span.start as usize)),
679 path: p.path,
680 message: p.message,
681 },
682 )
683 })
684 .collect();
685 located.sort_by_key(|(offset, _)| *offset);
687 Errors(located.into_iter().map(|(_, l)| l).collect())
688 })
689}
690
691pub fn to_text<T: Schema>(value: &T) -> String {
693 text::to_string_pretty(&value.write())
694}
695
696impl Schema for Value {
699 fn shape() -> Shape {
700 Shape::Any
701 }
702 fn read(v: &Value, _: &mut Reader) -> Option<Self> {
703 Some(v.clone())
704 }
705 fn write(&self) -> Value {
706 self.clone()
707 }
708}
709
710impl Schema for bool {
711 fn shape() -> Shape {
712 Shape::Bool
713 }
714 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
715 v.as_bool().or_else(|| {
716 cx.expected("true or false", v);
717 None
718 })
719 }
720 fn write(&self) -> Value {
721 Value::Bool(*self)
722 }
723}
724
725macro_rules! int_schema {
726 ($($t:ty),*) => {$(
727 impl Schema for $t {
728 fn shape() -> Shape {
729 Shape::Int { min: <$t>::MIN as i64, max: (<$t>::MAX as u64).min(i64::MAX as u64) as i64 }
730 }
731 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
732 let Some(n) = v.as_i64() else {
733 cx.expected("an integer", v);
734 return None;
735 };
736 <$t>::try_from(n).ok().or_else(|| {
737 cx.problem(format!("{n} is out of range ({}..={})", <$t>::MIN, <$t>::MAX));
738 None
739 })
740 }
741 fn write(&self) -> Value {
742 Value::Int(*self as i64)
743 }
744 }
745 )*};
746}
747int_schema!(i8, i16, i32, i64, u8, u16, u32, u64, usize);
748
749macro_rules! float_schema {
750 ($($t:ty),*) => {$(
751 impl Schema for $t {
752 fn shape() -> Shape {
753 Shape::Float
754 }
755 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
756 v.as_f64().map(|f| f as $t).or_else(|| {
757 cx.expected("a number", v);
758 None
759 })
760 }
761 fn write(&self) -> Value {
762 Value::Float(*self as f64)
763 }
764 }
765 )*};
766}
767float_schema!(f32, f64);
768
769impl Schema for String {
770 fn shape() -> Shape {
771 Shape::Str
772 }
773 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
774 match v {
775 Value::Str(s) => match typed::classify(s) {
776 Ok(Value::Str(s)) => Some(s),
777 Ok(other) => {
778 cx.problem(format!(
779 "expected plain text, found {} (write `@@` for a literal `@`)",
780 other.kind_name()
781 ));
782 None
783 }
784 Err(e) => {
785 cx.problem(e);
786 None
787 }
788 },
789 other => {
790 cx.expected("a string", other);
791 None
792 }
793 }
794 }
795 fn write(&self) -> Value {
796 Value::Str(self.clone())
797 }
798}
799
800impl<T: Schema> Schema for Option<T> {
801 fn shape() -> Shape {
802 Shape::Optional(T::shape)
803 }
804 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
805 if v.is_null() {
806 Some(None)
807 } else {
808 T::read(v, cx).map(Some)
809 }
810 }
811 fn write(&self) -> Value {
812 self.as_ref().map_or(Value::Null, T::write)
813 }
814 fn absent() -> Option<Self> {
815 Some(None)
816 }
817 fn skip(&self) -> bool {
818 self.is_none()
819 }
820}
821
822impl<T: Schema> Schema for Box<T> {
823 fn shape() -> Shape {
824 T::shape()
825 }
826 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
827 T::read(v, cx).map(Box::new)
828 }
829 fn write(&self) -> Value {
830 (**self).write()
831 }
832 fn absent() -> Option<Self> {
833 T::absent().map(Box::new)
834 }
835 fn skip(&self) -> bool {
836 (**self).skip()
837 }
838}
839
840impl<T: Schema> Schema for Vec<T> {
841 fn shape() -> Shape {
842 Shape::List(T::shape)
843 }
844 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
845 let Value::List(items) = v else {
846 cx.expected("a list", v);
847 return None;
848 };
849 let mut out = Vec::with_capacity(items.len());
850 let mut ok = true;
851 for (i, item) in items.iter().enumerate() {
852 match cx.at(Seg::Index(i), |cx| T::read(item, cx)) {
853 Some(x) => out.push(x),
854 None => ok = false,
855 }
856 }
857 ok.then_some(out)
858 }
859 fn write(&self) -> Value {
860 Value::List(self.iter().map(T::write).collect())
861 }
862}
863
864impl<T: Schema, const N: usize> Schema for [T; N] {
865 fn shape() -> Shape {
866 Shape::List(T::shape)
867 }
868 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
869 let items = Vec::<T>::read(v, cx)?;
870 let len = items.len();
871 items.try_into().ok().or_else(|| {
872 cx.problem(format!("expected {N} items, found {len}"));
873 None
874 })
875 }
876 fn write(&self) -> Value {
877 Value::List(self.iter().map(T::write).collect())
878 }
879}
880
881fn read_map<T: Schema>(v: &Value, cx: &mut Reader, mut put: impl FnMut(String, T)) -> Option<()> {
882 let map = cx.map(v)?;
883 let mut ok = true;
884 for (k, item) in map.iter() {
885 if is_metadata_key(k) {
886 continue;
887 }
888 match cx.at(Seg::Key(k.to_string()), |cx| T::read(item, cx)) {
889 Some(x) => put(k.to_string(), x),
890 None => ok = false,
891 }
892 }
893 ok.then_some(())
894}
895
896impl<T: Schema> Schema for BTreeMap<String, T> {
897 fn shape() -> Shape {
898 Shape::Map(T::shape)
899 }
900 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
901 let mut out = BTreeMap::new();
902 read_map(v, cx, |k, x| {
903 out.insert(k, x);
904 })?;
905 Some(out)
906 }
907 fn write(&self) -> Value {
908 Value::Map(self.iter().map(|(k, v)| (k.clone(), v.write())).collect())
909 }
910}
911
912impl<T: Schema> Schema for HashMap<String, T> {
913 fn shape() -> Shape {
914 Shape::Map(T::shape)
915 }
916 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
917 let mut out = HashMap::new();
918 read_map(v, cx, |k, x| {
919 out.insert(k, x);
920 })?;
921 Some(out)
922 }
923 fn write(&self) -> Value {
924 let mut keys: Vec<&String> = self.keys().collect();
925 keys.sort();
926 Value::Map(
927 keys.into_iter()
928 .map(|k| (k.clone(), self[k].write()))
929 .collect(),
930 )
931 }
932}
933
934#[derive(Debug, Clone, PartialEq)]
936pub struct Ordered<T>(pub Vec<(String, T)>);
937
938impl<T> Default for Ordered<T> {
939 fn default() -> Self {
940 Self(Vec::new())
941 }
942}
943
944impl<T: Schema> Schema for Ordered<T> {
945 fn shape() -> Shape {
946 Shape::Map(T::shape)
947 }
948 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
949 let mut out = Vec::new();
950 read_map(v, cx, |k, x| out.push((k, x)))?;
951 Some(Ordered(out))
952 }
953 fn write(&self) -> Value {
954 Value::Map(self.0.iter().map(|(k, v)| (k.clone(), v.write())).collect())
955 }
956}
957
958pub trait Kind {
963 const NAME: &'static str;
964}
965
966pub trait Kinds {
968 const NAMES: &'static [&'static str];
969}
970
971impl<K: Kind> Kinds for K {
972 const NAMES: &'static [&'static str] = &[K::NAME];
973}
974
975impl<A: Kind, B: Kind> Kinds for (A, B) {
976 const NAMES: &'static [&'static str] = &[A::NAME, B::NAME];
977}
978
979impl<A: Kind, B: Kind, C: Kind> Kinds for (A, B, C) {
980 const NAMES: &'static [&'static str] = &[A::NAME, B::NAME, C::NAME];
981}
982
983#[macro_export]
985macro_rules! kind {
986 ($($name:ident = $kind:literal),* $(,)?) => {$(
987 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
988 pub struct $name;
989 impl $crate::Kind for $name {
990 const NAME: &'static str = $kind;
991 }
992 )*};
993}
994
995pub mod kinds {
997 crate::kind! {
998 Item = "item",
999 Block = "block",
1000 Entity = "entity",
1001 Recipe = "recipe",
1002 Entry = "entry",
1003 Category = "category",
1004 Fluid = "fluid",
1005 Biome = "biome",
1006 Dimension = "dimension",
1007 }
1008}
1009
1010pub struct Id<K> {
1013 pub id: String,
1014 kind: PhantomData<K>,
1015}
1016
1017pub type ItemId = Id<kinds::Item>;
1018pub type BlockId = Id<kinds::Block>;
1019pub type EntityId = Id<kinds::Entity>;
1020pub type RecipeId = Id<kinds::Recipe>;
1021
1022impl<K> Id<K> {
1023 pub fn new(id: impl Into<String>) -> Self {
1024 Self {
1025 id: id.into(),
1026 kind: PhantomData,
1027 }
1028 }
1029}
1030
1031impl<K> Clone for Id<K> {
1032 fn clone(&self) -> Self {
1033 Self::new(self.id.clone())
1034 }
1035}
1036impl<K> PartialEq for Id<K> {
1037 fn eq(&self, o: &Self) -> bool {
1038 self.id == o.id
1039 }
1040}
1041impl<K> Eq for Id<K> {}
1042impl<K> std::hash::Hash for Id<K> {
1043 fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
1044 self.id.hash(h);
1045 }
1046}
1047impl<K: Kind> fmt::Debug for Id<K> {
1048 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1049 write!(f, "@{}:{}", K::NAME, self.id)
1050 }
1051}
1052impl<K> fmt::Display for Id<K> {
1053 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1054 f.write_str(&self.id)
1055 }
1056}
1057
1058pub fn read_ref(v: &Value, cx: &mut Reader, kinds: &[&str]) -> Option<Ref> {
1061 let wanted = || {
1062 kinds
1063 .iter()
1064 .map(|k| format!("`@{k}:`"))
1065 .collect::<Vec<_>>()
1066 .join(" or ")
1067 };
1068 let r = match v {
1069 Value::Ref(r) => r.clone(),
1070 Value::Str(s) if s.starts_with('@') => match typed::classify(s) {
1071 Ok(Value::Ref(r)) => r,
1072 Ok(other) => {
1073 cx.problem(format!(
1074 "expected a reference ({}), found {}",
1075 if kinds.is_empty() {
1076 "`@kind:id`".into()
1077 } else {
1078 wanted()
1079 },
1080 other.kind_name()
1081 ));
1082 return None;
1083 }
1084 Err(e) => {
1085 cx.problem(e);
1086 return None;
1087 }
1088 },
1089 Value::Str(s) => {
1090 let kind = kinds.first().copied().unwrap_or("item");
1091 cx.problem(format!("`{s}`: name it by a reference — `@{kind}:{s}`"));
1092 return None;
1093 }
1094 other => {
1095 cx.expected("a reference `@kind:id`", other);
1096 return None;
1097 }
1098 };
1099 if !kinds.is_empty() && !kinds.contains(&r.kind.as_str()) {
1100 cx.problem(format!(
1101 "`@{}:{}` is a reference to {} — this takes {}",
1102 r.kind,
1103 r.id,
1104 r.kind,
1105 wanted()
1106 ));
1107 return None;
1108 }
1109 cx.note_ref(r.clone());
1110 Some(r)
1111}
1112
1113fn read_tag(v: &Value, cx: &mut Reader, kind: &str) -> Option<Ref> {
1114 let r = match v {
1115 Value::Tag(r) => r.clone(),
1116 Value::Str(s) => match typed::classify(s) {
1117 Ok(Value::Tag(r)) => r,
1118 Ok(_) => {
1119 cx.problem(format!("expected a tag `@tag:{kind}:id`, found {s:?}"));
1120 return None;
1121 }
1122 Err(e) => {
1123 cx.problem(e);
1124 return None;
1125 }
1126 },
1127 other => {
1128 cx.expected("a tag", other);
1129 return None;
1130 }
1131 };
1132 if !kind.is_empty() && r.kind != kind {
1133 cx.problem(format!(
1134 "expected a tag of {kind}, found a tag of {}",
1135 r.kind
1136 ));
1137 return None;
1138 }
1139 cx.note_tag(r.clone());
1140 Some(r)
1141}
1142
1143impl<K: Kind> Schema for Id<K> {
1144 fn shape() -> Shape {
1145 Shape::Ref(<K as Kinds>::NAMES)
1146 }
1147 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1148 read_ref(v, cx, <K as Kinds>::NAMES).map(|r| Id::new(r.id))
1149 }
1150 fn write(&self) -> Value {
1151 Value::Ref(Ref::new(K::NAME, self.id.clone()))
1152 }
1153}
1154
1155impl Schema for Ref {
1156 fn shape() -> Shape {
1157 Shape::Ref(&[])
1158 }
1159 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1160 if let Value::Str(s) = v
1161 && !s.starts_with('@')
1162 {
1163 cx.problem(format!("expected a reference `@kind:id`, found {s:?}"));
1164 return None;
1165 }
1166 read_ref(v, cx, &[])
1167 }
1168 fn write(&self) -> Value {
1169 Value::Ref(self.clone())
1170 }
1171}
1172
1173pub struct TagId<K> {
1175 pub id: String,
1176 kind: PhantomData<K>,
1177}
1178
1179impl<K> TagId<K> {
1180 pub fn new(id: impl Into<String>) -> Self {
1181 Self {
1182 id: id.into(),
1183 kind: PhantomData,
1184 }
1185 }
1186}
1187impl<K> Clone for TagId<K> {
1188 fn clone(&self) -> Self {
1189 Self::new(self.id.clone())
1190 }
1191}
1192impl<K> PartialEq for TagId<K> {
1193 fn eq(&self, o: &Self) -> bool {
1194 self.id == o.id
1195 }
1196}
1197impl<K: Kind> fmt::Debug for TagId<K> {
1198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1199 write!(f, "@tag:{}:{}", K::NAME, self.id)
1200 }
1201}
1202
1203impl<K: Kind> Schema for TagId<K> {
1204 fn shape() -> Shape {
1205 Shape::Tag(<K as Kinds>::NAMES)
1206 }
1207 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1208 read_tag(v, cx, K::NAME).map(|r| TagId::new(r.id))
1209 }
1210 fn write(&self) -> Value {
1211 Value::Tag(Ref::new(K::NAME, self.id.clone()))
1212 }
1213}
1214
1215#[derive(Debug, Clone, PartialEq)]
1217pub enum OneOrTag<K: Kind> {
1218 One(Id<K>),
1219 Tag(TagId<K>),
1220}
1221
1222pub type ItemIdOrTag = OneOrTag<kinds::Item>;
1224
1225impl<K: Kind> Schema for OneOrTag<K> {
1226 fn shape() -> Shape {
1227 Shape::RefOrTag(<K as Kinds>::NAMES)
1228 }
1229 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1230 let is_tag = match v {
1231 Value::Tag(_) => true,
1232 Value::Str(s) => s.starts_with("@tag:"),
1233 _ => false,
1234 };
1235 if is_tag {
1236 TagId::read(v, cx).map(OneOrTag::Tag)
1237 } else {
1238 Id::read(v, cx).map(OneOrTag::One)
1239 }
1240 }
1241 fn write(&self) -> Value {
1242 match self {
1243 OneOrTag::One(i) => i.write(),
1244 OneOrTag::Tag(t) => t.write(),
1245 }
1246 }
1247}
1248
1249impl Schema for Res {
1250 fn shape() -> Shape {
1251 Shape::Res
1252 }
1253 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1254 let r = match v {
1255 Value::Res(r) => r.clone(),
1256 Value::Str(s) => match typed::classify(s) {
1257 Ok(Value::Res(r)) => r,
1258 Ok(_) => {
1259 cx.problem(format!("expected a resource `@Set.key`, found {s:?}"));
1260 return None;
1261 }
1262 Err(e) => {
1263 cx.problem(e);
1264 return None;
1265 }
1266 },
1267 other => {
1268 cx.expected("a resource `@Set.key`", other);
1269 return None;
1270 }
1271 };
1272 cx.note_res(r.clone());
1273 Some(r)
1274 }
1275 fn write(&self) -> Value {
1276 Value::Res(self.clone())
1277 }
1278}
1279
1280#[derive(Debug, Clone, PartialEq)]
1283pub enum Text {
1284 Literal(String),
1285 Res(Res),
1286 NameOf(Ref),
1287}
1288
1289impl Default for Text {
1290 fn default() -> Self {
1291 Text::Literal(String::new())
1292 }
1293}
1294
1295impl Schema for Text {
1296 fn shape() -> Shape {
1297 Shape::Text
1298 }
1299 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1300 let typed = match v {
1301 Value::Str(s) => typed::classify(s).map_err(|e| cx.problem(e)).ok()?,
1302 Value::Text(t) => return Some(Text::Literal(t.clone())),
1303 other => other.clone(),
1304 };
1305 match typed {
1306 Value::Str(s) => Some(Text::Literal(s)),
1307 Value::Res(r) => {
1308 cx.note_res(r.clone());
1309 Some(Text::Res(r))
1310 }
1311 Value::Ref(r) => {
1312 cx.note_ref(r.clone());
1313 Some(Text::NameOf(r))
1314 }
1315 other => {
1316 cx.expected("a text", &other);
1317 None
1318 }
1319 }
1320 }
1321 fn write(&self) -> Value {
1322 match self {
1323 Text::Literal(s) => Value::Text(s.clone()),
1324 Text::Res(r) => Value::Res(r.clone()),
1325 Text::NameOf(r) => Value::Ref(r.clone()),
1326 }
1327 }
1328}
1329
1330#[derive(Debug, Clone, PartialEq)]
1333pub enum Image {
1334 Source(String),
1335 Res(Res),
1336}
1337
1338impl Schema for Image {
1339 fn shape() -> Shape {
1340 Shape::Image
1341 }
1342 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1343 match v {
1344 Value::Image(s) => Some(Image::Source(s.clone())),
1345 Value::Res(r) => {
1346 cx.note_res(r.clone());
1347 Some(Image::Res(r.clone()))
1348 }
1349 Value::Str(s) if !s.starts_with('@') => Some(Image::Source(s.clone())),
1350 Value::Str(s) => match typed::classify(s) {
1351 Ok(Value::Image(src)) => Some(Image::Source(src)),
1352 Ok(Value::Res(r)) => {
1353 cx.note_res(r.clone());
1354 Some(Image::Res(r))
1355 }
1356 Ok(Value::Str(s)) => Some(Image::Source(s)),
1357 Ok(other) => {
1358 cx.expected("an image", &other);
1359 None
1360 }
1361 Err(e) => {
1362 cx.problem(e);
1363 None
1364 }
1365 },
1366 other => {
1367 cx.expected("an image", other);
1368 None
1369 }
1370 }
1371 }
1372 fn write(&self) -> Value {
1373 match self {
1374 Image::Source(s) => Value::Image(s.clone()),
1375 Image::Res(r) => Value::Res(r.clone()),
1376 }
1377 }
1378}
1379
1380#[derive(Debug, Clone, PartialEq)]
1382pub enum Color {
1383 Argb(u32),
1384 Res(Res),
1385}
1386
1387impl Schema for Color {
1388 fn shape() -> Shape {
1389 Shape::Color
1390 }
1391 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1392 match v {
1393 Value::Color(c) => Some(Color::Argb(*c)),
1394 Value::Res(r) => {
1395 cx.note_res(r.clone());
1396 Some(Color::Res(r.clone()))
1397 }
1398 Value::Str(s) if s.starts_with('#') => {
1399 typed::parse_color(s).map(Color::Argb).or_else(|| {
1400 cx.problem(format!("`{s}` isn't a color (#RRGGBB or #AARRGGBB)"));
1401 None
1402 })
1403 }
1404 Value::Str(s) if s.starts_with('@') => Res::read(v, cx).map(Color::Res),
1405 other => {
1406 cx.expected("a color (#RRGGBB or #AARRGGBB)", other);
1407 None
1408 }
1409 }
1410 }
1411 fn write(&self) -> Value {
1412 match self {
1413 Color::Argb(c) => Value::Color(*c),
1414 Color::Res(r) => Value::Res(r.clone()),
1415 }
1416 }
1417}