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