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 str),
67 Tag(&'static str),
69 RefOrTag(&'static str),
71 List(ShapeFn),
72 Map(ShapeFn),
74 Optional(ShapeFn),
76 Struct(StructShape),
77 Enum(EnumShape),
78}
79
80#[derive(Debug, Clone)]
81pub struct StructShape {
82 pub name: &'static str,
83 pub doc: &'static str,
84 pub fields: Vec<Field>,
85}
86
87#[derive(Debug, Clone)]
88pub struct Field {
89 pub name: &'static str,
90 pub doc: &'static str,
91 pub shape: ShapeFn,
92 pub required: bool,
94}
95
96#[derive(Debug, Clone)]
97pub struct EnumShape {
98 pub name: &'static str,
99 pub doc: &'static str,
100 pub tag: Option<&'static str>,
102 pub variants: Vec<Variant>,
103}
104
105#[derive(Debug, Clone)]
106pub struct Variant {
107 pub name: &'static str,
108 pub doc: &'static str,
109 pub fields: Vec<Field>,
111}
112
113impl Shape {
114 pub fn describe(&self) -> Value {
117 let mut defs = Map::new();
118 let root = self.describe_into(&mut defs);
119 let mut m = Map::new();
120 m.insert("root", root);
121 m.insert("defs", Value::Map(defs));
122 Value::Map(m)
123 }
124
125 fn describe_into(&self, defs: &mut Map) -> Value {
126 let kind = |k: &str| {
127 let mut m = Map::new();
128 m.insert("kind", Value::from(k));
129 m
130 };
131 let fields = |fs: &[Field], defs: &mut Map| -> Value {
132 Value::List(
133 fs.iter()
134 .map(|f| {
135 let mut m = Map::new();
136 m.insert("name", Value::from(f.name));
137 if !f.doc.is_empty() {
138 m.insert("doc", Value::from(f.doc));
139 }
140 m.insert("shape", (f.shape)().describe_into(defs));
141 m.insert("required", Value::Bool(f.required));
142 Value::Map(m)
143 })
144 .collect(),
145 )
146 };
147 let m = match self {
148 Shape::Any => kind("any"),
149 Shape::Bool => kind("bool"),
150 Shape::Int { min, max } => {
151 let mut m = kind("int");
152 m.insert("min", Value::Int(*min));
153 m.insert("max", Value::Int(*max));
154 m
155 }
156 Shape::Float => kind("float"),
157 Shape::Str => kind("string"),
158 Shape::Text => kind("text"),
159 Shape::Image => kind("image"),
160 Shape::Color => kind("color"),
161 Shape::Res => kind("res"),
162 Shape::Ref(k) | Shape::Tag(k) | Shape::RefOrTag(k) => {
163 let mut m = kind(match self {
164 Shape::Ref(_) => "ref",
165 Shape::Tag(_) => "tag",
166 _ => "ref_or_tag",
167 });
168 m.insert("of", Value::from(*k));
169 m
170 }
171 Shape::List(s) | Shape::Map(s) | Shape::Optional(s) => {
172 let mut m = kind(match self {
173 Shape::List(_) => "list",
174 Shape::Map(_) => "map",
175 _ => "optional",
176 });
177 m.insert("of", s().describe_into(defs));
178 m
179 }
180 Shape::Struct(s) => {
181 if defs.get(s.name).is_none() {
182 defs.insert(s.name, Value::Null);
184 let mut d = kind("struct");
185 if !s.doc.is_empty() {
186 d.insert("doc", Value::from(s.doc));
187 }
188 d.insert("fields", fields(&s.fields, defs));
189 defs.insert(s.name, Value::Map(d));
190 }
191 let mut m = kind("def");
192 m.insert("name", Value::from(s.name));
193 m
194 }
195 Shape::Enum(e) => {
196 if defs.get(e.name).is_none() {
197 defs.insert(e.name, Value::Null);
198 let mut d = kind("enum");
199 if !e.doc.is_empty() {
200 d.insert("doc", Value::from(e.doc));
201 }
202 if let Some(tag) = e.tag {
203 d.insert("tag", Value::from(tag));
204 }
205 let variants = e
206 .variants
207 .iter()
208 .map(|v| {
209 let mut m = Map::new();
210 m.insert("name", Value::from(v.name));
211 if !v.doc.is_empty() {
212 m.insert("doc", Value::from(v.doc));
213 }
214 if e.tag.is_some() {
215 m.insert("fields", fields(&v.fields, defs));
216 }
217 Value::Map(m)
218 })
219 .collect();
220 d.insert("variants", Value::List(variants));
221 defs.insert(e.name, Value::Map(d));
222 }
223 let mut m = kind("def");
224 m.insert("name", Value::from(e.name));
225 m
226 }
227 };
228 Value::Map(m)
229 }
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
236pub struct Problem {
237 pub path: Path,
238 pub message: String,
239 pub at_key: bool,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
245pub struct Found<T> {
246 pub value: T,
247 pub path: Path,
248}
249
250#[derive(Debug, Default)]
253pub struct Reader {
254 path: Path,
255 pub problems: Vec<Problem>,
256 pub refs: Vec<Found<Ref>>,
257 pub tags: Vec<Found<Ref>>,
258 pub resources: Vec<Found<Res>>,
259 allowed: Vec<&'static str>,
261}
262
263pub fn is_metadata_key(key: &str) -> bool {
265 key.starts_with('$') || key.starts_with('_') || key == "comment"
266}
267
268impl Reader {
269 pub fn new() -> Self {
270 Self::default()
271 }
272
273 pub fn path(&self) -> &Path {
274 &self.path
275 }
276
277 pub fn problem(&mut self, message: impl Into<String>) {
279 self.problems.push(Problem {
280 path: self.path.clone(),
281 message: message.into(),
282 at_key: false,
283 });
284 }
285
286 pub fn expected(&mut self, what: &str, found: &Value) {
288 self.problem(format!("expected {what}, found {}", found_text(found)));
289 }
290
291 pub fn at<T>(&mut self, seg: Seg, f: impl FnOnce(&mut Self) -> T) -> T {
293 self.path.push(seg);
294 let r = f(self);
295 self.path.pop();
296 r
297 }
298
299 pub fn note_ref(&mut self, r: Ref) {
300 self.refs.push(Found {
301 value: r,
302 path: self.path.clone(),
303 });
304 }
305
306 pub fn note_tag(&mut self, r: Ref) {
307 self.tags.push(Found {
308 value: r,
309 path: self.path.clone(),
310 });
311 }
312
313 pub fn note_res(&mut self, r: Res) {
314 self.resources.push(Found {
315 value: r,
316 path: self.path.clone(),
317 });
318 }
319
320 pub fn map<'v>(&mut self, v: &'v Value) -> Option<&'v Map> {
322 match v {
323 Value::Map(m) => Some(m),
324 other => {
325 self.expected("an object", other);
326 None
327 }
328 }
329 }
330
331 pub fn unknown_keys(&mut self, map: &Map, known: &[&str]) {
333 for key in map.keys() {
334 if known.contains(&key) || is_metadata_key(key) || self.allowed.contains(&key) {
335 continue;
336 }
337 let hint = closest(key, known)
338 .map(|k| format!(" — did you mean `{k}`?"))
339 .unwrap_or_else(|| {
340 if known.is_empty() {
341 String::new()
342 } else {
343 format!(
344 " (expected {})",
345 known
346 .iter()
347 .map(|k| format!("`{k}`"))
348 .collect::<Vec<_>>()
349 .join(", ")
350 )
351 }
352 });
353 self.problems.push(Problem {
354 path: self.path.key(key),
355 message: format!("unknown field `{key}`{hint}"),
356 at_key: true,
357 });
358 }
359 }
360
361 pub fn unknown_name(&mut self, name: &str, known: &[&str]) {
363 let hint = match closest(name, known) {
364 Some(k) => format!(" — did you mean `{k}`?"),
365 None => format!(
366 " (expected {})",
367 known
368 .iter()
369 .map(|k| format!("`{k}`"))
370 .collect::<Vec<_>>()
371 .join(", ")
372 ),
373 };
374 self.problem(format!("unknown `{name}`{hint}"));
375 }
376
377 pub fn field<T: Schema>(&mut self, map: &Map, key: &'static str) -> Option<T> {
379 match map.get(key) {
380 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
381 None => T::absent().or_else(|| {
382 self.problem(format!("missing `{key}`"));
383 None
384 }),
385 }
386 }
387
388 pub fn field_or<T: Schema>(
390 &mut self,
391 map: &Map,
392 key: &'static str,
393 default: impl FnOnce() -> T,
394 ) -> Option<T> {
395 match map.get(key) {
396 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
397 None => Some(default()),
398 }
399 }
400
401 pub fn with_allowed<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
403 self.allowed.push(key);
404 let r = f(self);
405 self.allowed.pop();
406 r
407 }
408
409 pub fn own_keys<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
411 let saved = std::mem::take(&mut self.allowed);
412 let r = f(self);
413 self.allowed = saved;
414 r
415 }
416}
417
418fn found_text(v: &Value) -> String {
419 match v {
420 Value::Str(s) if s.chars().count() <= 40 => format!("{s:?}"),
421 Value::Int(n) => n.to_string(),
422 Value::Float(f) => f.to_string(),
423 Value::Bool(b) => b.to_string(),
424 other => other.kind_name().to_string(),
425 }
426}
427
428fn closest<'k>(key: &str, known: &[&'k str]) -> Option<&'k str> {
430 let lower = key.to_ascii_lowercase();
431 known
432 .iter()
433 .map(|k| (edit_distance(&lower, k), *k))
434 .filter(|(d, k)| *d <= (k.len() / 3).max(1))
435 .min_by_key(|(d, _)| *d)
436 .map(|(_, k)| k)
437}
438
439fn edit_distance(a: &str, b: &str) -> usize {
440 let b: Vec<char> = b.chars().collect();
441 let mut prev: Vec<usize> = (0..=b.len()).collect();
442 for (i, ca) in a.chars().enumerate() {
443 let mut cur = vec![i + 1; b.len() + 1];
444 for (j, cb) in b.iter().enumerate() {
445 cur[j + 1] = (prev[j] + usize::from(ca != *cb))
446 .min(prev[j + 1] + 1)
447 .min(cur[j] + 1);
448 }
449 prev = cur;
450 }
451 prev[b.len()]
452}
453
454#[derive(Debug, Clone, PartialEq, Eq)]
456pub struct Errors(pub Vec<Located>);
457
458#[derive(Debug, Clone, PartialEq, Eq)]
460pub struct Located {
461 pub at: Option<LineCol>,
462 pub path: Path,
463 pub message: String,
464}
465
466impl fmt::Display for Located {
467 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468 if let Some(at) = self.at {
469 write!(f, "{at}: ")?;
470 }
471 if !self.path.is_root() {
472 write!(f, "{}: ", self.path)?;
473 }
474 f.write_str(&self.message)
475 }
476}
477
478impl fmt::Display for Errors {
479 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
480 for (i, e) in self.0.iter().enumerate() {
481 if i > 0 {
482 f.write_str("\n")?;
483 }
484 write!(f, "{e}")?;
485 }
486 Ok(())
487 }
488}
489
490impl std::error::Error for Errors {}
491
492#[derive(Debug)]
494pub struct Read<T> {
495 pub value: T,
496 pub refs: Vec<Found<Ref>>,
497 pub tags: Vec<Found<Ref>>,
498 pub resources: Vec<Found<Res>>,
499}
500
501pub fn from_value<T: Schema>(v: &Value) -> Result<Read<T>, Vec<Problem>> {
503 let mut cx = Reader::new();
504 let value = T::read(v, &mut cx);
505 match value {
506 Some(value) if cx.problems.is_empty() => Ok(Read {
507 value,
508 refs: cx.refs,
509 tags: cx.tags,
510 resources: cx.resources,
511 }),
512 _ => Err(cx.problems),
513 }
514}
515
516pub fn from_text<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
518 let doc = text::parse(text).map_err(|e| {
519 Errors(vec![Located {
520 at: Some(e.at),
521 path: Path::root(),
522 message: e.message,
523 }])
524 })?;
525 from_doc(text, &doc)
526}
527
528#[cfg(feature = "toml")]
530pub fn from_toml<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
531 let doc = crate::toml::parse(text).map_err(|e| {
532 Errors(vec![Located {
533 at: Some(e.at),
534 path: Path::root(),
535 message: e.message,
536 }])
537 })?;
538 from_doc(text, &doc)
539}
540
541pub fn from_doc<T: Schema>(text: &str, doc: &Doc) -> Result<Read<T>, Errors> {
543 from_value(&doc.value).map_err(|problems| {
544 let mut located: Vec<(u32, Located)> = problems
545 .into_iter()
546 .map(|p| {
547 let spans = doc.spans_at(&p.path);
548 let span = if p.at_key {
549 spans.key.unwrap_or(spans.span)
550 } else {
551 spans.span
552 };
553 (
554 span.start,
555 Located {
556 at: Some(LineCol::of(text, span.start as usize)),
557 path: p.path,
558 message: p.message,
559 },
560 )
561 })
562 .collect();
563 located.sort_by_key(|(offset, _)| *offset);
565 Errors(located.into_iter().map(|(_, l)| l).collect())
566 })
567}
568
569pub fn to_text<T: Schema>(value: &T) -> String {
571 text::to_string_pretty(&value.write())
572}
573
574impl Schema for Value {
577 fn shape() -> Shape {
578 Shape::Any
579 }
580 fn read(v: &Value, _: &mut Reader) -> Option<Self> {
581 Some(v.clone())
582 }
583 fn write(&self) -> Value {
584 self.clone()
585 }
586}
587
588impl Schema for bool {
589 fn shape() -> Shape {
590 Shape::Bool
591 }
592 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
593 v.as_bool().or_else(|| {
594 cx.expected("true or false", v);
595 None
596 })
597 }
598 fn write(&self) -> Value {
599 Value::Bool(*self)
600 }
601}
602
603macro_rules! int_schema {
604 ($($t:ty),*) => {$(
605 impl Schema for $t {
606 fn shape() -> Shape {
607 Shape::Int { min: <$t>::MIN as i64, max: (<$t>::MAX as u64).min(i64::MAX as u64) as i64 }
608 }
609 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
610 let Some(n) = v.as_i64() else {
611 cx.expected("an integer", v);
612 return None;
613 };
614 <$t>::try_from(n).ok().or_else(|| {
615 cx.problem(format!("{n} is out of range ({}..={})", <$t>::MIN, <$t>::MAX));
616 None
617 })
618 }
619 fn write(&self) -> Value {
620 Value::Int(*self as i64)
621 }
622 }
623 )*};
624}
625int_schema!(i8, i16, i32, i64, u8, u16, u32, u64, usize);
626
627macro_rules! float_schema {
628 ($($t:ty),*) => {$(
629 impl Schema for $t {
630 fn shape() -> Shape {
631 Shape::Float
632 }
633 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
634 v.as_f64().map(|f| f as $t).or_else(|| {
635 cx.expected("a number", v);
636 None
637 })
638 }
639 fn write(&self) -> Value {
640 Value::Float(*self as f64)
641 }
642 }
643 )*};
644}
645float_schema!(f32, f64);
646
647impl Schema for String {
648 fn shape() -> Shape {
649 Shape::Str
650 }
651 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
652 match v {
653 Value::Str(s) => match typed::classify(s) {
654 Ok(Value::Str(s)) => Some(s),
655 Ok(other) => {
656 cx.problem(format!(
657 "expected plain text, found {} (write `@@` for a literal `@`)",
658 other.kind_name()
659 ));
660 None
661 }
662 Err(e) => {
663 cx.problem(e);
664 None
665 }
666 },
667 other => {
668 cx.expected("a string", other);
669 None
670 }
671 }
672 }
673 fn write(&self) -> Value {
674 Value::Str(self.clone())
675 }
676}
677
678impl<T: Schema> Schema for Option<T> {
679 fn shape() -> Shape {
680 Shape::Optional(T::shape)
681 }
682 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
683 if v.is_null() {
684 Some(None)
685 } else {
686 T::read(v, cx).map(Some)
687 }
688 }
689 fn write(&self) -> Value {
690 self.as_ref().map_or(Value::Null, T::write)
691 }
692 fn absent() -> Option<Self> {
693 Some(None)
694 }
695 fn skip(&self) -> bool {
696 self.is_none()
697 }
698}
699
700impl<T: Schema> Schema for Box<T> {
701 fn shape() -> Shape {
702 T::shape()
703 }
704 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
705 T::read(v, cx).map(Box::new)
706 }
707 fn write(&self) -> Value {
708 (**self).write()
709 }
710 fn absent() -> Option<Self> {
711 T::absent().map(Box::new)
712 }
713 fn skip(&self) -> bool {
714 (**self).skip()
715 }
716}
717
718impl<T: Schema> Schema for Vec<T> {
719 fn shape() -> Shape {
720 Shape::List(T::shape)
721 }
722 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
723 let Value::List(items) = v else {
724 cx.expected("a list", v);
725 return None;
726 };
727 let mut out = Vec::with_capacity(items.len());
728 let mut ok = true;
729 for (i, item) in items.iter().enumerate() {
730 match cx.at(Seg::Index(i), |cx| T::read(item, cx)) {
731 Some(x) => out.push(x),
732 None => ok = false,
733 }
734 }
735 ok.then_some(out)
736 }
737 fn write(&self) -> Value {
738 Value::List(self.iter().map(T::write).collect())
739 }
740}
741
742impl<T: Schema, const N: usize> Schema for [T; N] {
743 fn shape() -> Shape {
744 Shape::List(T::shape)
745 }
746 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
747 let items = Vec::<T>::read(v, cx)?;
748 let len = items.len();
749 items.try_into().ok().or_else(|| {
750 cx.problem(format!("expected {N} items, found {len}"));
751 None
752 })
753 }
754 fn write(&self) -> Value {
755 Value::List(self.iter().map(T::write).collect())
756 }
757}
758
759fn read_map<T: Schema>(v: &Value, cx: &mut Reader, mut put: impl FnMut(String, T)) -> Option<()> {
760 let map = cx.map(v)?;
761 let mut ok = true;
762 for (k, item) in map.iter() {
763 if is_metadata_key(k) {
764 continue;
765 }
766 match cx.at(Seg::Key(k.to_string()), |cx| T::read(item, cx)) {
767 Some(x) => put(k.to_string(), x),
768 None => ok = false,
769 }
770 }
771 ok.then_some(())
772}
773
774impl<T: Schema> Schema for BTreeMap<String, T> {
775 fn shape() -> Shape {
776 Shape::Map(T::shape)
777 }
778 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
779 let mut out = BTreeMap::new();
780 read_map(v, cx, |k, x| {
781 out.insert(k, x);
782 })?;
783 Some(out)
784 }
785 fn write(&self) -> Value {
786 Value::Map(self.iter().map(|(k, v)| (k.clone(), v.write())).collect())
787 }
788}
789
790impl<T: Schema> Schema for HashMap<String, T> {
791 fn shape() -> Shape {
792 Shape::Map(T::shape)
793 }
794 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
795 let mut out = HashMap::new();
796 read_map(v, cx, |k, x| {
797 out.insert(k, x);
798 })?;
799 Some(out)
800 }
801 fn write(&self) -> Value {
802 let mut keys: Vec<&String> = self.keys().collect();
803 keys.sort();
804 Value::Map(
805 keys.into_iter()
806 .map(|k| (k.clone(), self[k].write()))
807 .collect(),
808 )
809 }
810}
811
812#[derive(Debug, Clone, PartialEq)]
814pub struct Ordered<T>(pub Vec<(String, T)>);
815
816impl<T> Default for Ordered<T> {
817 fn default() -> Self {
818 Self(Vec::new())
819 }
820}
821
822impl<T: Schema> Schema for Ordered<T> {
823 fn shape() -> Shape {
824 Shape::Map(T::shape)
825 }
826 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
827 let mut out = Vec::new();
828 read_map(v, cx, |k, x| out.push((k, x)))?;
829 Some(Ordered(out))
830 }
831 fn write(&self) -> Value {
832 Value::Map(self.0.iter().map(|(k, v)| (k.clone(), v.write())).collect())
833 }
834}
835
836pub trait Kind {
841 const NAME: &'static str;
842}
843
844#[macro_export]
846macro_rules! kind {
847 ($($name:ident = $kind:literal),* $(,)?) => {$(
848 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
849 pub struct $name;
850 impl $crate::Kind for $name {
851 const NAME: &'static str = $kind;
852 }
853 )*};
854}
855
856pub mod kinds {
858 crate::kind! {
859 Item = "item",
860 Block = "block",
861 Entity = "entity",
862 Recipe = "recipe",
863 Entry = "entry",
864 Category = "category",
865 Fluid = "fluid",
866 Biome = "biome",
867 Dimension = "dimension",
868 }
869}
870
871pub struct Id<K> {
874 pub id: String,
875 kind: PhantomData<K>,
876}
877
878pub type ItemId = Id<kinds::Item>;
879pub type BlockId = Id<kinds::Block>;
880pub type EntityId = Id<kinds::Entity>;
881pub type RecipeId = Id<kinds::Recipe>;
882
883impl<K> Id<K> {
884 pub fn new(id: impl Into<String>) -> Self {
885 Self {
886 id: id.into(),
887 kind: PhantomData,
888 }
889 }
890}
891
892impl<K> Clone for Id<K> {
893 fn clone(&self) -> Self {
894 Self::new(self.id.clone())
895 }
896}
897impl<K> PartialEq for Id<K> {
898 fn eq(&self, o: &Self) -> bool {
899 self.id == o.id
900 }
901}
902impl<K> Eq for Id<K> {}
903impl<K> std::hash::Hash for Id<K> {
904 fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
905 self.id.hash(h);
906 }
907}
908impl<K: Kind> fmt::Debug for Id<K> {
909 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
910 write!(f, "@{}:{}", K::NAME, self.id)
911 }
912}
913impl<K> fmt::Display for Id<K> {
914 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
915 f.write_str(&self.id)
916 }
917}
918
919fn read_ref(v: &Value, cx: &mut Reader, kind: &str) -> Option<Ref> {
921 let r = match v {
922 Value::Ref(r) => r.clone(),
923 Value::Str(s) if s.starts_with('@') => match typed::classify(s) {
924 Ok(Value::Ref(r)) => r,
925 Ok(other) => {
926 cx.problem(format!(
927 "expected {kind} `@{kind}:id`, found {}",
928 other.kind_name()
929 ));
930 return None;
931 }
932 Err(e) => {
933 cx.problem(e);
934 return None;
935 }
936 },
937 Value::Str(s) => match typed::resource_id(s, s) {
938 Ok(id) => Ref {
939 kind: kind.to_string(),
940 id,
941 },
942 Err(e) => {
943 cx.problem(e);
944 return None;
945 }
946 },
947 other => {
948 cx.expected(&format!("an id of {kind}"), other);
949 return None;
950 }
951 };
952 if !kind.is_empty() && r.kind != kind {
953 cx.problem(format!("expected {kind}, found {} `{}`", r.kind, r.id));
954 return None;
955 }
956 cx.note_ref(r.clone());
957 Some(r)
958}
959
960fn read_tag(v: &Value, cx: &mut Reader, kind: &str) -> Option<Ref> {
961 let r = match v {
962 Value::Tag(r) => r.clone(),
963 Value::Str(s) => match typed::classify(s) {
964 Ok(Value::Tag(r)) => r,
965 Ok(_) => {
966 cx.problem(format!("expected a tag `@tag:{kind}:id`, found {s:?}"));
967 return None;
968 }
969 Err(e) => {
970 cx.problem(e);
971 return None;
972 }
973 },
974 other => {
975 cx.expected("a tag", other);
976 return None;
977 }
978 };
979 if !kind.is_empty() && r.kind != kind {
980 cx.problem(format!(
981 "expected a tag of {kind}, found a tag of {}",
982 r.kind
983 ));
984 return None;
985 }
986 cx.note_tag(r.clone());
987 Some(r)
988}
989
990impl<K: Kind> Schema for Id<K> {
991 fn shape() -> Shape {
992 Shape::Ref(K::NAME)
993 }
994 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
995 read_ref(v, cx, K::NAME).map(|r| Id::new(r.id))
996 }
997 fn write(&self) -> Value {
998 Value::Ref(Ref::new(K::NAME, self.id.clone()))
999 }
1000}
1001
1002impl Schema for Ref {
1003 fn shape() -> Shape {
1004 Shape::Ref("")
1005 }
1006 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1007 if let Value::Str(s) = v
1008 && !s.starts_with('@')
1009 {
1010 cx.problem(format!("expected a reference `@kind:id`, found {s:?}"));
1011 return None;
1012 }
1013 read_ref(v, cx, "")
1014 }
1015 fn write(&self) -> Value {
1016 Value::Ref(self.clone())
1017 }
1018}
1019
1020pub struct TagId<K> {
1022 pub id: String,
1023 kind: PhantomData<K>,
1024}
1025
1026impl<K> TagId<K> {
1027 pub fn new(id: impl Into<String>) -> Self {
1028 Self {
1029 id: id.into(),
1030 kind: PhantomData,
1031 }
1032 }
1033}
1034impl<K> Clone for TagId<K> {
1035 fn clone(&self) -> Self {
1036 Self::new(self.id.clone())
1037 }
1038}
1039impl<K> PartialEq for TagId<K> {
1040 fn eq(&self, o: &Self) -> bool {
1041 self.id == o.id
1042 }
1043}
1044impl<K: Kind> fmt::Debug for TagId<K> {
1045 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1046 write!(f, "@tag:{}:{}", K::NAME, self.id)
1047 }
1048}
1049
1050impl<K: Kind> Schema for TagId<K> {
1051 fn shape() -> Shape {
1052 Shape::Tag(K::NAME)
1053 }
1054 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1055 read_tag(v, cx, K::NAME).map(|r| TagId::new(r.id))
1056 }
1057 fn write(&self) -> Value {
1058 Value::Tag(Ref::new(K::NAME, self.id.clone()))
1059 }
1060}
1061
1062#[derive(Debug, Clone, PartialEq)]
1064pub enum OneOrTag<K: Kind> {
1065 One(Id<K>),
1066 Tag(TagId<K>),
1067}
1068
1069pub type ItemIdOrTag = OneOrTag<kinds::Item>;
1071
1072impl<K: Kind> Schema for OneOrTag<K> {
1073 fn shape() -> Shape {
1074 Shape::RefOrTag(K::NAME)
1075 }
1076 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1077 let is_tag = match v {
1078 Value::Tag(_) => true,
1079 Value::Str(s) => s.starts_with("@tag:"),
1080 _ => false,
1081 };
1082 if is_tag {
1083 TagId::read(v, cx).map(OneOrTag::Tag)
1084 } else {
1085 Id::read(v, cx).map(OneOrTag::One)
1086 }
1087 }
1088 fn write(&self) -> Value {
1089 match self {
1090 OneOrTag::One(i) => i.write(),
1091 OneOrTag::Tag(t) => t.write(),
1092 }
1093 }
1094}
1095
1096impl Schema for Res {
1097 fn shape() -> Shape {
1098 Shape::Res
1099 }
1100 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1101 let r = match v {
1102 Value::Res(r) => r.clone(),
1103 Value::Str(s) => match typed::classify(s) {
1104 Ok(Value::Res(r)) => r,
1105 Ok(_) => {
1106 cx.problem(format!("expected a resource `@Set.key`, found {s:?}"));
1107 return None;
1108 }
1109 Err(e) => {
1110 cx.problem(e);
1111 return None;
1112 }
1113 },
1114 other => {
1115 cx.expected("a resource `@Set.key`", other);
1116 return None;
1117 }
1118 };
1119 cx.note_res(r.clone());
1120 Some(r)
1121 }
1122 fn write(&self) -> Value {
1123 Value::Res(self.clone())
1124 }
1125}
1126
1127#[derive(Debug, Clone, PartialEq)]
1130pub enum Text {
1131 Literal(String),
1132 Res(Res),
1133 NameOf(Ref),
1134}
1135
1136impl Default for Text {
1137 fn default() -> Self {
1138 Text::Literal(String::new())
1139 }
1140}
1141
1142impl Schema for Text {
1143 fn shape() -> Shape {
1144 Shape::Text
1145 }
1146 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1147 let typed = match v {
1148 Value::Str(s) => typed::classify(s).map_err(|e| cx.problem(e)).ok()?,
1149 Value::Text(t) => return Some(Text::Literal(t.clone())),
1150 other => other.clone(),
1151 };
1152 match typed {
1153 Value::Str(s) => Some(Text::Literal(s)),
1154 Value::Res(r) => {
1155 cx.note_res(r.clone());
1156 Some(Text::Res(r))
1157 }
1158 Value::Ref(r) => {
1159 cx.note_ref(r.clone());
1160 Some(Text::NameOf(r))
1161 }
1162 other => {
1163 cx.expected("a text", &other);
1164 None
1165 }
1166 }
1167 }
1168 fn write(&self) -> Value {
1169 match self {
1170 Text::Literal(s) => Value::Text(s.clone()),
1171 Text::Res(r) => Value::Res(r.clone()),
1172 Text::NameOf(r) => Value::Ref(r.clone()),
1173 }
1174 }
1175}
1176
1177#[derive(Debug, Clone, PartialEq)]
1180pub enum Image {
1181 Source(String),
1182 Res(Res),
1183}
1184
1185impl Schema for Image {
1186 fn shape() -> Shape {
1187 Shape::Image
1188 }
1189 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1190 match v {
1191 Value::Image(s) => Some(Image::Source(s.clone())),
1192 Value::Res(r) => {
1193 cx.note_res(r.clone());
1194 Some(Image::Res(r.clone()))
1195 }
1196 Value::Str(s) if !s.starts_with('@') => Some(Image::Source(s.clone())),
1197 Value::Str(s) => match typed::classify(s) {
1198 Ok(Value::Image(src)) => Some(Image::Source(src)),
1199 Ok(Value::Res(r)) => {
1200 cx.note_res(r.clone());
1201 Some(Image::Res(r))
1202 }
1203 Ok(Value::Str(s)) => Some(Image::Source(s)),
1204 Ok(other) => {
1205 cx.expected("an image", &other);
1206 None
1207 }
1208 Err(e) => {
1209 cx.problem(e);
1210 None
1211 }
1212 },
1213 other => {
1214 cx.expected("an image", other);
1215 None
1216 }
1217 }
1218 }
1219 fn write(&self) -> Value {
1220 match self {
1221 Image::Source(s) => Value::Image(s.clone()),
1222 Image::Res(r) => Value::Res(r.clone()),
1223 }
1224 }
1225}
1226
1227#[derive(Debug, Clone, PartialEq)]
1229pub enum Color {
1230 Argb(u32),
1231 Res(Res),
1232}
1233
1234impl Schema for Color {
1235 fn shape() -> Shape {
1236 Shape::Color
1237 }
1238 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1239 match v {
1240 Value::Color(c) => Some(Color::Argb(*c)),
1241 Value::Res(r) => {
1242 cx.note_res(r.clone());
1243 Some(Color::Res(r.clone()))
1244 }
1245 Value::Str(s) if s.starts_with('#') => {
1246 typed::parse_color(s).map(Color::Argb).or_else(|| {
1247 cx.problem(format!("`{s}` isn't a color (#RRGGBB or #AARRGGBB)"));
1248 None
1249 })
1250 }
1251 Value::Str(s) if s.starts_with('@') => Res::read(v, cx).map(Color::Res),
1252 other => {
1253 cx.expected("a color (#RRGGBB or #AARRGGBB)", other);
1254 None
1255 }
1256 }
1257 }
1258 fn write(&self) -> Value {
1259 match self {
1260 Color::Argb(c) => Value::Color(*c),
1261 Color::Res(r) => Value::Res(r.clone()),
1262 }
1263 }
1264}