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