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 Text::Lang(k) => format!("@lang:{k}"),
292 })
293 }
294
295 fn words(&mut self, _r: &Res) -> Option<String> {
297 None
298 }
299}
300
301impl<'r> Reader<'r> {
302 pub fn new() -> Self {
303 Self::default()
304 }
305
306 pub fn with_resolver(resolver: &'r mut dyn Resolve) -> Self {
308 Self {
309 resolver: Some(resolver),
310 ..Self::default()
311 }
312 }
313
314 pub fn resolve_ref(&mut self, r: &Ref) -> Option<String> {
316 let result = match &mut self.resolver {
317 Some(res) => res.reference(r, &self.path),
318 None => Ok(r.id.clone()),
319 };
320 result.map_err(|e| self.problem(e)).ok()
321 }
322
323 pub fn resolve_text(&mut self, t: &Text) -> Option<String> {
325 struct Keep;
326 impl Resolve for Keep {}
327 let result = match &mut self.resolver {
328 Some(res) => res.text(t, &self.path),
329 None => Keep.text(t, &self.path),
330 };
331 result.map_err(|e| self.problem(e)).ok()
332 }
333
334 pub fn words(&mut self, r: &Res) -> Option<String> {
336 self.resolver.as_mut()?.words(r)
337 }
338
339 pub fn path(&self) -> &Path {
340 &self.path
341 }
342
343 pub fn problem(&mut self, message: impl Into<String>) {
345 self.problems.push(Problem {
346 path: self.path.clone(),
347 message: message.into(),
348 at_key: false,
349 });
350 }
351
352 pub fn expected(&mut self, what: &str, found: &Value) {
354 self.problem(format!("expected {what}, found {}", found_text(found)));
355 }
356
357 pub fn at<T>(&mut self, seg: Seg, f: impl FnOnce(&mut Self) -> T) -> T {
359 self.path.push(seg);
360 let r = f(self);
361 self.path.pop();
362 r
363 }
364
365 pub fn note_ref(&mut self, r: Ref) {
366 self.refs.push(Found {
367 value: r,
368 path: self.path.clone(),
369 });
370 }
371
372 pub fn note_tag(&mut self, r: Ref) {
373 self.tags.push(Found {
374 value: r,
375 path: self.path.clone(),
376 });
377 }
378
379 pub fn note_res(&mut self, r: Res) {
380 self.resources.push(Found {
381 value: r,
382 path: self.path.clone(),
383 });
384 }
385
386 pub fn map<'v>(&mut self, v: &'v Value) -> Option<&'v Map> {
388 match v {
389 Value::Map(m) => Some(m),
390 other => {
391 self.expected("an object", other);
392 None
393 }
394 }
395 }
396
397 pub fn unknown_keys(&mut self, map: &Map, known: &[&str]) {
399 for key in map.keys() {
400 if known.contains(&key) || is_metadata_key(key) || self.allowed.contains(&key) {
401 continue;
402 }
403 let hint = closest(key, known)
404 .map(|k| format!(" — did you mean `{k}`?"))
405 .unwrap_or_else(|| {
406 if known.is_empty() {
407 String::new()
408 } else {
409 format!(
410 " (expected {})",
411 known
412 .iter()
413 .map(|k| format!("`{k}`"))
414 .collect::<Vec<_>>()
415 .join(", ")
416 )
417 }
418 });
419 self.problems.push(Problem {
420 path: self.path.key(key),
421 message: format!("unknown field `{key}`{hint}"),
422 at_key: true,
423 });
424 }
425 }
426
427 pub fn unknown_name(&mut self, name: &str, known: &[&str]) {
429 let hint = match closest(name, known) {
430 Some(k) => format!(" — did you mean `{k}`?"),
431 None => format!(
432 " (expected {})",
433 known
434 .iter()
435 .map(|k| format!("`{k}`"))
436 .collect::<Vec<_>>()
437 .join(", ")
438 ),
439 };
440 self.problem(format!("unknown `{name}`{hint}"));
441 }
442
443 pub fn field<T: Schema>(&mut self, map: &Map, key: &'static str) -> Option<T> {
445 match map.get(key) {
446 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
447 None => T::absent().or_else(|| {
448 self.problem(format!("missing `{key}`"));
449 None
450 }),
451 }
452 }
453
454 pub fn field_or<T: Schema>(
456 &mut self,
457 map: &Map,
458 key: &'static str,
459 default: impl FnOnce() -> T,
460 ) -> Option<T> {
461 match map.get(key) {
462 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
463 None => Some(default()),
464 }
465 }
466
467 pub fn field_with<W: crate::with::With<F>, F>(
469 &mut self,
470 map: &Map,
471 key: &'static str,
472 ) -> Option<F> {
473 match map.get(key) {
474 Some(v) => self.at(Seg::Key(key.to_string()), |cx| W::read(v, cx)),
475 None => W::absent().or_else(|| {
476 self.problem(format!("missing `{key}`"));
477 None
478 }),
479 }
480 }
481
482 pub fn field_or_with<W: crate::with::With<F>, F>(
484 &mut self,
485 map: &Map,
486 key: &'static str,
487 default: impl FnOnce() -> F,
488 ) -> Option<F> {
489 match map.get(key) {
490 Some(v) => self.at(Seg::Key(key.to_string()), |cx| W::read(v, cx)),
491 None => Some(default()),
492 }
493 }
494
495 pub fn with_allowed<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
497 self.allowed.push(key);
498 let r = f(self);
499 self.allowed.pop();
500 r
501 }
502
503 pub fn with_allowed_all<T>(
506 &mut self,
507 keys: &[&'static str],
508 f: impl FnOnce(&mut Self) -> T,
509 ) -> T {
510 let before = self.allowed.len();
511 self.allowed.extend_from_slice(keys);
512 let r = f(self);
513 self.allowed.truncate(before);
514 r
515 }
516
517 pub fn own_keys<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
519 let saved = std::mem::take(&mut self.allowed);
520 let r = f(self);
521 self.allowed = saved;
522 r
523 }
524}
525
526fn found_text(v: &Value) -> String {
527 match v {
528 Value::Str(s) if s.chars().count() <= 40 => format!("{s:?}"),
529 Value::Int(n) => n.to_string(),
530 Value::Float(f) => f.to_string(),
531 Value::Bool(b) => b.to_string(),
532 other => other.kind_name().to_string(),
533 }
534}
535
536fn closest<'k>(key: &str, known: &[&'k str]) -> Option<&'k str> {
539 let lower = key.to_ascii_lowercase();
540 known
541 .iter()
542 .map(|k| (edit_distance(&lower, k), *k))
543 .filter(|(d, k)| *d <= if k.len() <= 3 { 1 } else { k.len() / 2 })
544 .min_by_key(|(d, _)| *d)
545 .map(|(_, k)| k)
546}
547
548fn edit_distance(a: &str, b: &str) -> usize {
549 let b: Vec<char> = b.chars().collect();
550 let mut prev: Vec<usize> = (0..=b.len()).collect();
551 for (i, ca) in a.chars().enumerate() {
552 let mut cur = vec![i + 1; b.len() + 1];
553 for (j, cb) in b.iter().enumerate() {
554 cur[j + 1] = (prev[j] + usize::from(ca != *cb))
555 .min(prev[j + 1] + 1)
556 .min(cur[j] + 1);
557 }
558 prev = cur;
559 }
560 prev[b.len()]
561}
562
563#[derive(Debug, Clone, PartialEq, Eq)]
565pub struct Errors(pub Vec<Located>);
566
567#[derive(Debug, Clone, PartialEq, Eq)]
569pub struct Located {
570 pub at: Option<LineCol>,
571 pub path: Path,
572 pub message: String,
573}
574
575impl fmt::Display for Located {
576 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
577 if let Some(at) = self.at {
578 write!(f, "{at}: ")?;
579 }
580 if !self.path.is_root() {
581 write!(f, "{}: ", self.path)?;
582 }
583 f.write_str(&self.message)
584 }
585}
586
587impl fmt::Display for Errors {
588 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
589 for (i, e) in self.0.iter().enumerate() {
590 if i > 0 {
591 f.write_str("\n")?;
592 }
593 write!(f, "{e}")?;
594 }
595 Ok(())
596 }
597}
598
599impl std::error::Error for Errors {}
600
601#[derive(Debug)]
603pub struct Read<T> {
604 pub value: T,
605 pub refs: Vec<Found<Ref>>,
606 pub tags: Vec<Found<Ref>>,
607 pub resources: Vec<Found<Res>>,
608}
609
610pub fn field_names(shape: &Shape) -> Vec<&'static str> {
612 match shape {
613 Shape::Struct(s) => s.fields.iter().map(|f| f.name).collect(),
614 _ => Vec::new(),
615 }
616}
617
618pub fn read_both<A: Schema, B: Schema>(
621 v: &Value,
622 cx: &mut Reader,
623 also: &[&'static str],
624) -> Option<(A, B)> {
625 let (an, bn) = (field_names(&A::shape()), field_names(&B::shape()));
626 let mut b_keys = bn.clone();
627 b_keys.extend_from_slice(also);
628 let mut a_keys = an;
629 a_keys.extend_from_slice(also);
630 let a = cx.with_allowed_all(&b_keys, |cx| A::read(v, cx));
631 let b = cx.with_allowed_all(&a_keys, |cx| B::read(v, cx));
632 Some((a?, b?))
633}
634
635pub fn from_value<T: Schema>(v: &Value) -> Result<Read<T>, Vec<Problem>> {
637 read_with(v, Reader::new())
638}
639
640pub fn from_value_with<T: Schema>(
642 v: &Value,
643 resolver: &mut dyn Resolve,
644) -> Result<Read<T>, Vec<Problem>> {
645 read_with(v, Reader::with_resolver(resolver))
646}
647
648fn read_with<T: Schema>(v: &Value, mut cx: Reader<'_>) -> Result<Read<T>, Vec<Problem>> {
649 let value = T::read(v, &mut cx);
650 match value {
651 Some(value) if cx.problems.is_empty() => Ok(Read {
652 value,
653 refs: cx.refs,
654 tags: cx.tags,
655 resources: cx.resources,
656 }),
657 _ => Err(cx.problems),
658 }
659}
660
661pub fn from_text<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
663 let doc = text::parse(text).map_err(|e| {
664 Errors(vec![Located {
665 at: Some(e.at),
666 path: Path::root(),
667 message: e.message,
668 }])
669 })?;
670 from_doc(text, &doc)
671}
672
673#[cfg(feature = "toml")]
675pub fn from_toml<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
676 let doc = crate::toml::parse(text).map_err(|e| {
677 Errors(vec![Located {
678 at: Some(e.at),
679 path: Path::root(),
680 message: e.message,
681 }])
682 })?;
683 from_doc(text, &doc)
684}
685
686pub fn from_doc<T: Schema>(text: &str, doc: &Doc) -> Result<Read<T>, Errors> {
688 locate(text, doc, from_value(&doc.value))
689}
690
691pub fn from_doc_with<T: Schema>(
693 text: &str,
694 doc: &Doc,
695 resolver: &mut dyn Resolve,
696) -> Result<Read<T>, Errors> {
697 locate(text, doc, from_value_with(&doc.value, resolver))
698}
699
700fn locate<T>(
701 text: &str,
702 doc: &Doc,
703 read: Result<Read<T>, Vec<Problem>>,
704) -> Result<Read<T>, Errors> {
705 read.map_err(|problems| {
706 let mut located: Vec<(u32, Located)> = problems
707 .into_iter()
708 .map(|p| {
709 let spans = doc.spans_at(&p.path);
710 let span = if p.at_key {
711 spans.key.unwrap_or(spans.span)
712 } else {
713 spans.span
714 };
715 (
716 span.start,
717 Located {
718 at: Some(LineCol::of(text, span.start as usize)),
719 path: p.path,
720 message: p.message,
721 },
722 )
723 })
724 .collect();
725 located.sort_by_key(|(offset, _)| *offset);
727 Errors(located.into_iter().map(|(_, l)| l).collect())
728 })
729}
730
731pub fn to_text<T: Schema>(value: &T) -> String {
733 text::to_string_pretty(&value.write())
734}
735
736impl Schema for Value {
739 fn shape() -> Shape {
740 Shape::Any
741 }
742 fn read(v: &Value, _: &mut Reader) -> Option<Self> {
743 Some(v.clone())
744 }
745 fn write(&self) -> Value {
746 self.clone()
747 }
748}
749
750impl Schema for bool {
751 fn shape() -> Shape {
752 Shape::Bool
753 }
754 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
755 v.as_bool().or_else(|| {
756 cx.expected("true or false", v);
757 None
758 })
759 }
760 fn write(&self) -> Value {
761 Value::Bool(*self)
762 }
763}
764
765macro_rules! int_schema {
766 ($($t:ty),*) => {$(
767 impl Schema for $t {
768 fn shape() -> Shape {
769 Shape::Int { min: <$t>::MIN as i64, max: (<$t>::MAX as u64).min(i64::MAX as u64) as i64 }
770 }
771 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
772 let Some(n) = v.as_i64() else {
773 cx.expected("an integer", v);
774 return None;
775 };
776 <$t>::try_from(n).ok().or_else(|| {
777 cx.problem(format!("{n} is out of range ({}..={})", <$t>::MIN, <$t>::MAX));
778 None
779 })
780 }
781 fn write(&self) -> Value {
782 Value::Int(*self as i64)
783 }
784 }
785 )*};
786}
787int_schema!(i8, i16, i32, i64, u8, u16, u32, u64, usize);
788
789macro_rules! float_schema {
790 ($($t:ty),*) => {$(
791 impl Schema for $t {
792 fn shape() -> Shape {
793 Shape::Float
794 }
795 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
796 v.as_f64().map(|f| f as $t).or_else(|| {
797 cx.expected("a number", v);
798 None
799 })
800 }
801 fn write(&self) -> Value {
802 Value::Float(*self as f64)
803 }
804 }
805 )*};
806}
807float_schema!(f32, f64);
808
809impl Schema for String {
810 fn shape() -> Shape {
811 Shape::Str
812 }
813 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
814 match v {
815 Value::Str(s) => match typed::classify(s) {
816 Ok(Value::Str(s)) => Some(s),
817 Ok(other) => {
818 cx.problem(format!(
819 "expected plain text, found {} (write `@@` for a literal `@`)",
820 other.kind_name()
821 ));
822 None
823 }
824 Err(e) => {
825 cx.problem(e);
826 None
827 }
828 },
829 other => {
830 cx.expected("a string", other);
831 None
832 }
833 }
834 }
835 fn write(&self) -> Value {
836 Value::Str(self.clone())
837 }
838}
839
840impl<T: Schema> Schema for Option<T> {
841 fn shape() -> Shape {
842 Shape::Optional(T::shape)
843 }
844 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
845 if v.is_null() {
846 Some(None)
847 } else {
848 T::read(v, cx).map(Some)
849 }
850 }
851 fn write(&self) -> Value {
852 self.as_ref().map_or(Value::Null, T::write)
853 }
854 fn absent() -> Option<Self> {
855 Some(None)
856 }
857 fn skip(&self) -> bool {
858 self.is_none()
859 }
860}
861
862impl<T: Schema> Schema for Box<T> {
863 fn shape() -> Shape {
864 T::shape()
865 }
866 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
867 T::read(v, cx).map(Box::new)
868 }
869 fn write(&self) -> Value {
870 (**self).write()
871 }
872 fn absent() -> Option<Self> {
873 T::absent().map(Box::new)
874 }
875 fn skip(&self) -> bool {
876 (**self).skip()
877 }
878}
879
880impl<T: Schema> Schema for Vec<T> {
881 fn shape() -> Shape {
882 Shape::List(T::shape)
883 }
884 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
885 let Value::List(items) = v else {
886 cx.expected("a list", v);
887 return None;
888 };
889 let mut out = Vec::with_capacity(items.len());
890 let mut ok = true;
891 for (i, item) in items.iter().enumerate() {
892 match cx.at(Seg::Index(i), |cx| T::read(item, cx)) {
893 Some(x) => out.push(x),
894 None => ok = false,
895 }
896 }
897 ok.then_some(out)
898 }
899 fn write(&self) -> Value {
900 Value::List(self.iter().map(T::write).collect())
901 }
902}
903
904impl<T: Schema, const N: usize> Schema for [T; N] {
905 fn shape() -> Shape {
906 Shape::List(T::shape)
907 }
908 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
909 let items = Vec::<T>::read(v, cx)?;
910 let len = items.len();
911 items.try_into().ok().or_else(|| {
912 cx.problem(format!("expected {N} items, found {len}"));
913 None
914 })
915 }
916 fn write(&self) -> Value {
917 Value::List(self.iter().map(T::write).collect())
918 }
919}
920
921fn read_map<T: Schema>(v: &Value, cx: &mut Reader, mut put: impl FnMut(String, T)) -> Option<()> {
922 let map = cx.map(v)?;
923 let mut ok = true;
924 for (k, item) in map.iter() {
925 if is_metadata_key(k) {
926 continue;
927 }
928 match cx.at(Seg::Key(k.to_string()), |cx| T::read(item, cx)) {
929 Some(x) => put(k.to_string(), x),
930 None => ok = false,
931 }
932 }
933 ok.then_some(())
934}
935
936impl<T: Schema> Schema for BTreeMap<String, T> {
937 fn shape() -> Shape {
938 Shape::Map(T::shape)
939 }
940 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
941 let mut out = BTreeMap::new();
942 read_map(v, cx, |k, x| {
943 out.insert(k, x);
944 })?;
945 Some(out)
946 }
947 fn write(&self) -> Value {
948 Value::Map(self.iter().map(|(k, v)| (k.clone(), v.write())).collect())
949 }
950}
951
952impl<T: Schema> Schema for HashMap<String, T> {
953 fn shape() -> Shape {
954 Shape::Map(T::shape)
955 }
956 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
957 let mut out = HashMap::new();
958 read_map(v, cx, |k, x| {
959 out.insert(k, x);
960 })?;
961 Some(out)
962 }
963 fn write(&self) -> Value {
964 let mut keys: Vec<&String> = self.keys().collect();
965 keys.sort();
966 Value::Map(
967 keys.into_iter()
968 .map(|k| (k.clone(), self[k].write()))
969 .collect(),
970 )
971 }
972}
973
974#[derive(Debug, Clone, PartialEq)]
976pub struct Ordered<T>(pub Vec<(String, T)>);
977
978impl<T> Default for Ordered<T> {
979 fn default() -> Self {
980 Self(Vec::new())
981 }
982}
983
984impl<T: Schema> Schema for Ordered<T> {
985 fn shape() -> Shape {
986 Shape::Map(T::shape)
987 }
988 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
989 let mut out = Vec::new();
990 read_map(v, cx, |k, x| out.push((k, x)))?;
991 Some(Ordered(out))
992 }
993 fn write(&self) -> Value {
994 Value::Map(self.0.iter().map(|(k, v)| (k.clone(), v.write())).collect())
995 }
996}
997
998pub trait Kind {
1003 const NAME: &'static str;
1004}
1005
1006pub trait Kinds {
1008 const NAMES: &'static [&'static str];
1009}
1010
1011impl<K: Kind> Kinds for K {
1012 const NAMES: &'static [&'static str] = &[K::NAME];
1013}
1014
1015impl<A: Kind, B: Kind> Kinds for (A, B) {
1016 const NAMES: &'static [&'static str] = &[A::NAME, B::NAME];
1017}
1018
1019impl<A: Kind, B: Kind, C: Kind> Kinds for (A, B, C) {
1020 const NAMES: &'static [&'static str] = &[A::NAME, B::NAME, C::NAME];
1021}
1022
1023#[macro_export]
1025macro_rules! kind {
1026 ($($name:ident = $kind:literal),* $(,)?) => {$(
1027 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
1028 pub struct $name;
1029 impl $crate::Kind for $name {
1030 const NAME: &'static str = $kind;
1031 }
1032 )*};
1033}
1034
1035pub mod kinds {
1037 crate::kind! {
1038 Item = "item",
1039 Block = "block",
1040 Entity = "entity",
1041 Recipe = "recipe",
1042 Entry = "entry",
1043 Category = "category",
1044 Fluid = "fluid",
1045 Biome = "biome",
1046 Dimension = "dimension",
1047 }
1048}
1049
1050pub struct Id<K> {
1053 pub id: String,
1054 kind: PhantomData<K>,
1055}
1056
1057pub type ItemId = Id<kinds::Item>;
1058pub type BlockId = Id<kinds::Block>;
1059pub type EntityId = Id<kinds::Entity>;
1060pub type RecipeId = Id<kinds::Recipe>;
1061
1062impl<K> Id<K> {
1063 pub fn new(id: impl Into<String>) -> Self {
1064 Self {
1065 id: id.into(),
1066 kind: PhantomData,
1067 }
1068 }
1069}
1070
1071impl<K> Clone for Id<K> {
1072 fn clone(&self) -> Self {
1073 Self::new(self.id.clone())
1074 }
1075}
1076impl<K> PartialEq for Id<K> {
1077 fn eq(&self, o: &Self) -> bool {
1078 self.id == o.id
1079 }
1080}
1081impl<K> Eq for Id<K> {}
1082impl<K> std::hash::Hash for Id<K> {
1083 fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
1084 self.id.hash(h);
1085 }
1086}
1087impl<K: Kind> fmt::Debug for Id<K> {
1088 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1089 write!(f, "@{}:{}", K::NAME, self.id)
1090 }
1091}
1092impl<K> fmt::Display for Id<K> {
1093 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1094 f.write_str(&self.id)
1095 }
1096}
1097
1098pub fn read_ref(v: &Value, cx: &mut Reader, kinds: &[&str]) -> Option<Ref> {
1101 let wanted = || {
1102 kinds
1103 .iter()
1104 .map(|k| format!("`@{k}:`"))
1105 .collect::<Vec<_>>()
1106 .join(" or ")
1107 };
1108 let r = match v {
1109 Value::Ref(r) => r.clone(),
1110 Value::Str(s) if s.starts_with('@') => match typed::classify(s) {
1111 Ok(Value::Ref(r)) => r,
1112 Ok(other) => {
1113 cx.problem(format!(
1114 "expected a reference ({}), found {}",
1115 if kinds.is_empty() {
1116 "`@kind:id`".into()
1117 } else {
1118 wanted()
1119 },
1120 other.kind_name()
1121 ));
1122 return None;
1123 }
1124 Err(e) => {
1125 cx.problem(e);
1126 return None;
1127 }
1128 },
1129 Value::Str(s) => {
1130 let kind = kinds.first().copied().unwrap_or("item");
1131 cx.problem(format!("`{s}`: name it by a reference — `@{kind}:{s}`"));
1132 return None;
1133 }
1134 other => {
1135 cx.expected("a reference `@kind:id`", other);
1136 return None;
1137 }
1138 };
1139 if !kinds.is_empty() && !kinds.contains(&r.kind.as_str()) {
1140 cx.problem(format!(
1141 "`@{}:{}` is a reference to {} — this takes {}",
1142 r.kind,
1143 r.id,
1144 r.kind,
1145 wanted()
1146 ));
1147 return None;
1148 }
1149 cx.note_ref(r.clone());
1150 Some(r)
1151}
1152
1153fn read_tag(v: &Value, cx: &mut Reader, kind: &str) -> Option<Ref> {
1154 let r = match v {
1155 Value::Tag(r) => r.clone(),
1156 Value::Str(s) => match typed::classify(s) {
1157 Ok(Value::Tag(r)) => r,
1158 Ok(_) => {
1159 cx.problem(format!("expected a tag `@tag:{kind}:id`, found {s:?}"));
1160 return None;
1161 }
1162 Err(e) => {
1163 cx.problem(e);
1164 return None;
1165 }
1166 },
1167 other => {
1168 cx.expected("a tag", other);
1169 return None;
1170 }
1171 };
1172 if !kind.is_empty() && r.kind != kind {
1173 cx.problem(format!(
1174 "expected a tag of {kind}, found a tag of {}",
1175 r.kind
1176 ));
1177 return None;
1178 }
1179 cx.note_tag(r.clone());
1180 Some(r)
1181}
1182
1183impl<K: Kind> Schema for Id<K> {
1184 fn shape() -> Shape {
1185 Shape::Ref(<K as Kinds>::NAMES)
1186 }
1187 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1188 read_ref(v, cx, <K as Kinds>::NAMES).map(|r| Id::new(r.id))
1189 }
1190 fn write(&self) -> Value {
1191 Value::Ref(Ref::new(K::NAME, self.id.clone()))
1192 }
1193}
1194
1195impl Schema for Ref {
1196 fn shape() -> Shape {
1197 Shape::Ref(&[])
1198 }
1199 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1200 if let Value::Str(s) = v
1201 && !s.starts_with('@')
1202 {
1203 cx.problem(format!("expected a reference `@kind:id`, found {s:?}"));
1204 return None;
1205 }
1206 read_ref(v, cx, &[])
1207 }
1208 fn write(&self) -> Value {
1209 Value::Ref(self.clone())
1210 }
1211}
1212
1213pub struct TagId<K> {
1215 pub id: String,
1216 kind: PhantomData<K>,
1217}
1218
1219impl<K> TagId<K> {
1220 pub fn new(id: impl Into<String>) -> Self {
1221 Self {
1222 id: id.into(),
1223 kind: PhantomData,
1224 }
1225 }
1226}
1227impl<K> Clone for TagId<K> {
1228 fn clone(&self) -> Self {
1229 Self::new(self.id.clone())
1230 }
1231}
1232impl<K> PartialEq for TagId<K> {
1233 fn eq(&self, o: &Self) -> bool {
1234 self.id == o.id
1235 }
1236}
1237impl<K: Kind> fmt::Debug for TagId<K> {
1238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1239 write!(f, "@tag:{}:{}", K::NAME, self.id)
1240 }
1241}
1242
1243impl<K: Kind> Schema for TagId<K> {
1244 fn shape() -> Shape {
1245 Shape::Tag(<K as Kinds>::NAMES)
1246 }
1247 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1248 read_tag(v, cx, K::NAME).map(|r| TagId::new(r.id))
1249 }
1250 fn write(&self) -> Value {
1251 Value::Tag(Ref::new(K::NAME, self.id.clone()))
1252 }
1253}
1254
1255#[derive(Debug, Clone, PartialEq)]
1257pub enum OneOrTag<K: Kind> {
1258 One(Id<K>),
1259 Tag(TagId<K>),
1260}
1261
1262pub type ItemIdOrTag = OneOrTag<kinds::Item>;
1264
1265impl<K: Kind> Schema for OneOrTag<K> {
1266 fn shape() -> Shape {
1267 Shape::RefOrTag(<K as Kinds>::NAMES)
1268 }
1269 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1270 if let Value::Str(s) = v
1271 && let Some(tag) = s.strip_prefix('#')
1272 {
1273 cx.problem(format!("`{s}`: write a tag as `@tag:{}:{tag}`", K::NAME));
1274 return None;
1275 }
1276 let is_tag = match v {
1277 Value::Tag(_) => true,
1278 Value::Str(s) => s.starts_with("@tag:"),
1279 _ => false,
1280 };
1281 if is_tag {
1282 TagId::read(v, cx).map(OneOrTag::Tag)
1283 } else {
1284 Id::read(v, cx).map(OneOrTag::One)
1285 }
1286 }
1287 fn write(&self) -> Value {
1288 match self {
1289 OneOrTag::One(i) => i.write(),
1290 OneOrTag::Tag(t) => t.write(),
1291 }
1292 }
1293}
1294
1295impl Schema for Res {
1296 fn shape() -> Shape {
1297 Shape::Res
1298 }
1299 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1300 let r = match v {
1301 Value::Res(r) => r.clone(),
1302 Value::Str(s) => match typed::classify(s) {
1303 Ok(Value::Res(r)) => r,
1304 Ok(_) => {
1305 cx.problem(format!("expected a resource `@Set.key`, found {s:?}"));
1306 return None;
1307 }
1308 Err(e) => {
1309 cx.problem(e);
1310 return None;
1311 }
1312 },
1313 other => {
1314 cx.expected("a resource `@Set.key`", other);
1315 return None;
1316 }
1317 };
1318 cx.note_res(r.clone());
1319 Some(r)
1320 }
1321 fn write(&self) -> Value {
1322 Value::Res(self.clone())
1323 }
1324}
1325
1326#[derive(Debug, Clone, PartialEq)]
1330pub enum Text {
1331 Literal(String),
1332 Res(Res),
1333 NameOf(Ref),
1334 Lang(String),
1335}
1336
1337impl Default for Text {
1338 fn default() -> Self {
1339 Text::Literal(String::new())
1340 }
1341}
1342
1343impl Schema for Text {
1344 fn shape() -> Shape {
1345 Shape::Text
1346 }
1347 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1348 let typed = match v {
1349 Value::Str(s) => typed::classify(s).map_err(|e| cx.problem(e)).ok()?,
1350 Value::Text(t) => return Some(Text::Literal(t.clone())),
1351 other => other.clone(),
1352 };
1353 match typed {
1354 Value::Str(s) => Some(Text::Literal(s)),
1355 Value::Res(r) => {
1356 cx.note_res(r.clone());
1357 Some(Text::Res(r))
1358 }
1359 Value::Ref(r) => {
1360 cx.note_ref(r.clone());
1361 Some(Text::NameOf(r))
1362 }
1363 Value::Lang(k) => Some(Text::Lang(k)),
1364 other => {
1365 cx.expected("a text", &other);
1366 None
1367 }
1368 }
1369 }
1370 fn write(&self) -> Value {
1371 match self {
1372 Text::Literal(s) => Value::Text(s.clone()),
1373 Text::Res(r) => Value::Res(r.clone()),
1374 Text::NameOf(r) => Value::Ref(r.clone()),
1375 Text::Lang(k) => Value::Lang(k.clone()),
1376 }
1377 }
1378}
1379
1380#[derive(Debug, Clone, PartialEq)]
1383pub enum Image {
1384 Source(String),
1385 Res(Res),
1386}
1387
1388impl Schema for Image {
1389 fn shape() -> Shape {
1390 Shape::Image
1391 }
1392 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1393 match v {
1394 Value::Image(s) => Some(Image::Source(s.clone())),
1395 Value::Res(r) => {
1396 cx.note_res(r.clone());
1397 Some(Image::Res(r.clone()))
1398 }
1399 Value::Str(s) if !s.starts_with('@') => Some(Image::Source(s.clone())),
1400 Value::Str(s) => match typed::classify(s) {
1401 Ok(Value::Image(src)) => Some(Image::Source(src)),
1402 Ok(Value::Res(r)) => {
1403 cx.note_res(r.clone());
1404 Some(Image::Res(r))
1405 }
1406 Ok(Value::Str(s)) => Some(Image::Source(s)),
1407 Ok(other) => {
1408 cx.expected("an image", &other);
1409 None
1410 }
1411 Err(e) => {
1412 cx.problem(e);
1413 None
1414 }
1415 },
1416 other => {
1417 cx.expected("an image", other);
1418 None
1419 }
1420 }
1421 }
1422 fn write(&self) -> Value {
1423 match self {
1424 Image::Source(s) => Value::Image(s.clone()),
1425 Image::Res(r) => Value::Res(r.clone()),
1426 }
1427 }
1428}
1429
1430#[derive(Debug, Clone, PartialEq)]
1432pub enum Color {
1433 Argb(u32),
1434 Res(Res),
1435}
1436
1437impl Schema for Color {
1438 fn shape() -> Shape {
1439 Shape::Color
1440 }
1441 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1442 match v {
1443 Value::Color(c) => Some(Color::Argb(*c)),
1444 Value::Res(r) => {
1445 cx.note_res(r.clone());
1446 Some(Color::Res(r.clone()))
1447 }
1448 Value::Str(s) if s.starts_with('#') => {
1449 typed::parse_color(s).map(Color::Argb).or_else(|| {
1450 cx.problem(format!("`{s}` isn't a color (#RRGGBB or #AARRGGBB)"));
1451 None
1452 })
1453 }
1454 Value::Str(s) if s.starts_with('@') => Res::read(v, cx).map(Color::Res),
1455 other => {
1456 cx.expected("a color (#RRGGBB or #AARRGGBB)", other);
1457 None
1458 }
1459 }
1460 }
1461 fn write(&self) -> Value {
1462 match self {
1463 Color::Argb(c) => Value::Color(*c),
1464 Color::Res(r) => Value::Res(r.clone()),
1465 }
1466 }
1467}