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> {
431 let lower = key.to_ascii_lowercase();
432 known
433 .iter()
434 .map(|k| (edit_distance(&lower, k), *k))
435 .filter(|(d, k)| *d <= if k.len() <= 3 { 1 } else { k.len() / 2 })
436 .min_by_key(|(d, _)| *d)
437 .map(|(_, k)| k)
438}
439
440fn edit_distance(a: &str, b: &str) -> usize {
441 let b: Vec<char> = b.chars().collect();
442 let mut prev: Vec<usize> = (0..=b.len()).collect();
443 for (i, ca) in a.chars().enumerate() {
444 let mut cur = vec![i + 1; b.len() + 1];
445 for (j, cb) in b.iter().enumerate() {
446 cur[j + 1] = (prev[j] + usize::from(ca != *cb))
447 .min(prev[j + 1] + 1)
448 .min(cur[j] + 1);
449 }
450 prev = cur;
451 }
452 prev[b.len()]
453}
454
455#[derive(Debug, Clone, PartialEq, Eq)]
457pub struct Errors(pub Vec<Located>);
458
459#[derive(Debug, Clone, PartialEq, Eq)]
461pub struct Located {
462 pub at: Option<LineCol>,
463 pub path: Path,
464 pub message: String,
465}
466
467impl fmt::Display for Located {
468 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469 if let Some(at) = self.at {
470 write!(f, "{at}: ")?;
471 }
472 if !self.path.is_root() {
473 write!(f, "{}: ", self.path)?;
474 }
475 f.write_str(&self.message)
476 }
477}
478
479impl fmt::Display for Errors {
480 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
481 for (i, e) in self.0.iter().enumerate() {
482 if i > 0 {
483 f.write_str("\n")?;
484 }
485 write!(f, "{e}")?;
486 }
487 Ok(())
488 }
489}
490
491impl std::error::Error for Errors {}
492
493#[derive(Debug)]
495pub struct Read<T> {
496 pub value: T,
497 pub refs: Vec<Found<Ref>>,
498 pub tags: Vec<Found<Ref>>,
499 pub resources: Vec<Found<Res>>,
500}
501
502pub fn from_value<T: Schema>(v: &Value) -> Result<Read<T>, Vec<Problem>> {
504 let mut cx = Reader::new();
505 let value = T::read(v, &mut cx);
506 match value {
507 Some(value) if cx.problems.is_empty() => Ok(Read {
508 value,
509 refs: cx.refs,
510 tags: cx.tags,
511 resources: cx.resources,
512 }),
513 _ => Err(cx.problems),
514 }
515}
516
517pub fn from_text<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
519 let doc = text::parse(text).map_err(|e| {
520 Errors(vec![Located {
521 at: Some(e.at),
522 path: Path::root(),
523 message: e.message,
524 }])
525 })?;
526 from_doc(text, &doc)
527}
528
529#[cfg(feature = "toml")]
531pub fn from_toml<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
532 let doc = crate::toml::parse(text).map_err(|e| {
533 Errors(vec![Located {
534 at: Some(e.at),
535 path: Path::root(),
536 message: e.message,
537 }])
538 })?;
539 from_doc(text, &doc)
540}
541
542pub fn from_doc<T: Schema>(text: &str, doc: &Doc) -> Result<Read<T>, Errors> {
544 from_value(&doc.value).map_err(|problems| {
545 let mut located: Vec<(u32, Located)> = problems
546 .into_iter()
547 .map(|p| {
548 let spans = doc.spans_at(&p.path);
549 let span = if p.at_key {
550 spans.key.unwrap_or(spans.span)
551 } else {
552 spans.span
553 };
554 (
555 span.start,
556 Located {
557 at: Some(LineCol::of(text, span.start as usize)),
558 path: p.path,
559 message: p.message,
560 },
561 )
562 })
563 .collect();
564 located.sort_by_key(|(offset, _)| *offset);
566 Errors(located.into_iter().map(|(_, l)| l).collect())
567 })
568}
569
570pub fn to_text<T: Schema>(value: &T) -> String {
572 text::to_string_pretty(&value.write())
573}
574
575impl Schema for Value {
578 fn shape() -> Shape {
579 Shape::Any
580 }
581 fn read(v: &Value, _: &mut Reader) -> Option<Self> {
582 Some(v.clone())
583 }
584 fn write(&self) -> Value {
585 self.clone()
586 }
587}
588
589impl Schema for bool {
590 fn shape() -> Shape {
591 Shape::Bool
592 }
593 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
594 v.as_bool().or_else(|| {
595 cx.expected("true or false", v);
596 None
597 })
598 }
599 fn write(&self) -> Value {
600 Value::Bool(*self)
601 }
602}
603
604macro_rules! int_schema {
605 ($($t:ty),*) => {$(
606 impl Schema for $t {
607 fn shape() -> Shape {
608 Shape::Int { min: <$t>::MIN as i64, max: (<$t>::MAX as u64).min(i64::MAX as u64) as i64 }
609 }
610 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
611 let Some(n) = v.as_i64() else {
612 cx.expected("an integer", v);
613 return None;
614 };
615 <$t>::try_from(n).ok().or_else(|| {
616 cx.problem(format!("{n} is out of range ({}..={})", <$t>::MIN, <$t>::MAX));
617 None
618 })
619 }
620 fn write(&self) -> Value {
621 Value::Int(*self as i64)
622 }
623 }
624 )*};
625}
626int_schema!(i8, i16, i32, i64, u8, u16, u32, u64, usize);
627
628macro_rules! float_schema {
629 ($($t:ty),*) => {$(
630 impl Schema for $t {
631 fn shape() -> Shape {
632 Shape::Float
633 }
634 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
635 v.as_f64().map(|f| f as $t).or_else(|| {
636 cx.expected("a number", v);
637 None
638 })
639 }
640 fn write(&self) -> Value {
641 Value::Float(*self as f64)
642 }
643 }
644 )*};
645}
646float_schema!(f32, f64);
647
648impl Schema for String {
649 fn shape() -> Shape {
650 Shape::Str
651 }
652 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
653 match v {
654 Value::Str(s) => match typed::classify(s) {
655 Ok(Value::Str(s)) => Some(s),
656 Ok(other) => {
657 cx.problem(format!(
658 "expected plain text, found {} (write `@@` for a literal `@`)",
659 other.kind_name()
660 ));
661 None
662 }
663 Err(e) => {
664 cx.problem(e);
665 None
666 }
667 },
668 other => {
669 cx.expected("a string", other);
670 None
671 }
672 }
673 }
674 fn write(&self) -> Value {
675 Value::Str(self.clone())
676 }
677}
678
679impl<T: Schema> Schema for Option<T> {
680 fn shape() -> Shape {
681 Shape::Optional(T::shape)
682 }
683 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
684 if v.is_null() {
685 Some(None)
686 } else {
687 T::read(v, cx).map(Some)
688 }
689 }
690 fn write(&self) -> Value {
691 self.as_ref().map_or(Value::Null, T::write)
692 }
693 fn absent() -> Option<Self> {
694 Some(None)
695 }
696 fn skip(&self) -> bool {
697 self.is_none()
698 }
699}
700
701impl<T: Schema> Schema for Box<T> {
702 fn shape() -> Shape {
703 T::shape()
704 }
705 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
706 T::read(v, cx).map(Box::new)
707 }
708 fn write(&self) -> Value {
709 (**self).write()
710 }
711 fn absent() -> Option<Self> {
712 T::absent().map(Box::new)
713 }
714 fn skip(&self) -> bool {
715 (**self).skip()
716 }
717}
718
719impl<T: Schema> Schema for Vec<T> {
720 fn shape() -> Shape {
721 Shape::List(T::shape)
722 }
723 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
724 let Value::List(items) = v else {
725 cx.expected("a list", v);
726 return None;
727 };
728 let mut out = Vec::with_capacity(items.len());
729 let mut ok = true;
730 for (i, item) in items.iter().enumerate() {
731 match cx.at(Seg::Index(i), |cx| T::read(item, cx)) {
732 Some(x) => out.push(x),
733 None => ok = false,
734 }
735 }
736 ok.then_some(out)
737 }
738 fn write(&self) -> Value {
739 Value::List(self.iter().map(T::write).collect())
740 }
741}
742
743impl<T: Schema, const N: usize> Schema for [T; N] {
744 fn shape() -> Shape {
745 Shape::List(T::shape)
746 }
747 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
748 let items = Vec::<T>::read(v, cx)?;
749 let len = items.len();
750 items.try_into().ok().or_else(|| {
751 cx.problem(format!("expected {N} items, found {len}"));
752 None
753 })
754 }
755 fn write(&self) -> Value {
756 Value::List(self.iter().map(T::write).collect())
757 }
758}
759
760fn read_map<T: Schema>(v: &Value, cx: &mut Reader, mut put: impl FnMut(String, T)) -> Option<()> {
761 let map = cx.map(v)?;
762 let mut ok = true;
763 for (k, item) in map.iter() {
764 if is_metadata_key(k) {
765 continue;
766 }
767 match cx.at(Seg::Key(k.to_string()), |cx| T::read(item, cx)) {
768 Some(x) => put(k.to_string(), x),
769 None => ok = false,
770 }
771 }
772 ok.then_some(())
773}
774
775impl<T: Schema> Schema for BTreeMap<String, T> {
776 fn shape() -> Shape {
777 Shape::Map(T::shape)
778 }
779 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
780 let mut out = BTreeMap::new();
781 read_map(v, cx, |k, x| {
782 out.insert(k, x);
783 })?;
784 Some(out)
785 }
786 fn write(&self) -> Value {
787 Value::Map(self.iter().map(|(k, v)| (k.clone(), v.write())).collect())
788 }
789}
790
791impl<T: Schema> Schema for HashMap<String, T> {
792 fn shape() -> Shape {
793 Shape::Map(T::shape)
794 }
795 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
796 let mut out = HashMap::new();
797 read_map(v, cx, |k, x| {
798 out.insert(k, x);
799 })?;
800 Some(out)
801 }
802 fn write(&self) -> Value {
803 let mut keys: Vec<&String> = self.keys().collect();
804 keys.sort();
805 Value::Map(
806 keys.into_iter()
807 .map(|k| (k.clone(), self[k].write()))
808 .collect(),
809 )
810 }
811}
812
813#[derive(Debug, Clone, PartialEq)]
815pub struct Ordered<T>(pub Vec<(String, T)>);
816
817impl<T> Default for Ordered<T> {
818 fn default() -> Self {
819 Self(Vec::new())
820 }
821}
822
823impl<T: Schema> Schema for Ordered<T> {
824 fn shape() -> Shape {
825 Shape::Map(T::shape)
826 }
827 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
828 let mut out = Vec::new();
829 read_map(v, cx, |k, x| out.push((k, x)))?;
830 Some(Ordered(out))
831 }
832 fn write(&self) -> Value {
833 Value::Map(self.0.iter().map(|(k, v)| (k.clone(), v.write())).collect())
834 }
835}
836
837pub trait Kind {
842 const NAME: &'static str;
843}
844
845#[macro_export]
847macro_rules! kind {
848 ($($name:ident = $kind:literal),* $(,)?) => {$(
849 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
850 pub struct $name;
851 impl $crate::Kind for $name {
852 const NAME: &'static str = $kind;
853 }
854 )*};
855}
856
857pub mod kinds {
859 crate::kind! {
860 Item = "item",
861 Block = "block",
862 Entity = "entity",
863 Recipe = "recipe",
864 Entry = "entry",
865 Category = "category",
866 Fluid = "fluid",
867 Biome = "biome",
868 Dimension = "dimension",
869 }
870}
871
872pub struct Id<K> {
875 pub id: String,
876 kind: PhantomData<K>,
877}
878
879pub type ItemId = Id<kinds::Item>;
880pub type BlockId = Id<kinds::Block>;
881pub type EntityId = Id<kinds::Entity>;
882pub type RecipeId = Id<kinds::Recipe>;
883
884impl<K> Id<K> {
885 pub fn new(id: impl Into<String>) -> Self {
886 Self {
887 id: id.into(),
888 kind: PhantomData,
889 }
890 }
891}
892
893impl<K> Clone for Id<K> {
894 fn clone(&self) -> Self {
895 Self::new(self.id.clone())
896 }
897}
898impl<K> PartialEq for Id<K> {
899 fn eq(&self, o: &Self) -> bool {
900 self.id == o.id
901 }
902}
903impl<K> Eq for Id<K> {}
904impl<K> std::hash::Hash for Id<K> {
905 fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
906 self.id.hash(h);
907 }
908}
909impl<K: Kind> fmt::Debug for Id<K> {
910 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
911 write!(f, "@{}:{}", K::NAME, self.id)
912 }
913}
914impl<K> fmt::Display for Id<K> {
915 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
916 f.write_str(&self.id)
917 }
918}
919
920fn read_ref(v: &Value, cx: &mut Reader, kind: &str) -> Option<Ref> {
922 let r = match v {
923 Value::Ref(r) => r.clone(),
924 Value::Str(s) if s.starts_with('@') => match typed::classify(s) {
925 Ok(Value::Ref(r)) => r,
926 Ok(other) => {
927 cx.problem(format!(
928 "expected {kind} `@{kind}:id`, found {}",
929 other.kind_name()
930 ));
931 return None;
932 }
933 Err(e) => {
934 cx.problem(e);
935 return None;
936 }
937 },
938 Value::Str(s) => match typed::resource_id(s, s) {
939 Ok(id) => Ref {
940 kind: kind.to_string(),
941 id,
942 },
943 Err(e) => {
944 cx.problem(e);
945 return None;
946 }
947 },
948 other => {
949 cx.expected(&format!("an id of {kind}"), other);
950 return None;
951 }
952 };
953 if !kind.is_empty() && r.kind != kind {
954 cx.problem(format!("expected {kind}, found {} `{}`", r.kind, r.id));
955 return None;
956 }
957 cx.note_ref(r.clone());
958 Some(r)
959}
960
961fn read_tag(v: &Value, cx: &mut Reader, kind: &str) -> Option<Ref> {
962 let r = match v {
963 Value::Tag(r) => r.clone(),
964 Value::Str(s) => match typed::classify(s) {
965 Ok(Value::Tag(r)) => r,
966 Ok(_) => {
967 cx.problem(format!("expected a tag `@tag:{kind}:id`, found {s:?}"));
968 return None;
969 }
970 Err(e) => {
971 cx.problem(e);
972 return None;
973 }
974 },
975 other => {
976 cx.expected("a tag", other);
977 return None;
978 }
979 };
980 if !kind.is_empty() && r.kind != kind {
981 cx.problem(format!(
982 "expected a tag of {kind}, found a tag of {}",
983 r.kind
984 ));
985 return None;
986 }
987 cx.note_tag(r.clone());
988 Some(r)
989}
990
991impl<K: Kind> Schema for Id<K> {
992 fn shape() -> Shape {
993 Shape::Ref(K::NAME)
994 }
995 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
996 read_ref(v, cx, K::NAME).map(|r| Id::new(r.id))
997 }
998 fn write(&self) -> Value {
999 Value::Ref(Ref::new(K::NAME, self.id.clone()))
1000 }
1001}
1002
1003impl Schema for Ref {
1004 fn shape() -> Shape {
1005 Shape::Ref("")
1006 }
1007 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1008 if let Value::Str(s) = v
1009 && !s.starts_with('@')
1010 {
1011 cx.problem(format!("expected a reference `@kind:id`, found {s:?}"));
1012 return None;
1013 }
1014 read_ref(v, cx, "")
1015 }
1016 fn write(&self) -> Value {
1017 Value::Ref(self.clone())
1018 }
1019}
1020
1021pub struct TagId<K> {
1023 pub id: String,
1024 kind: PhantomData<K>,
1025}
1026
1027impl<K> TagId<K> {
1028 pub fn new(id: impl Into<String>) -> Self {
1029 Self {
1030 id: id.into(),
1031 kind: PhantomData,
1032 }
1033 }
1034}
1035impl<K> Clone for TagId<K> {
1036 fn clone(&self) -> Self {
1037 Self::new(self.id.clone())
1038 }
1039}
1040impl<K> PartialEq for TagId<K> {
1041 fn eq(&self, o: &Self) -> bool {
1042 self.id == o.id
1043 }
1044}
1045impl<K: Kind> fmt::Debug for TagId<K> {
1046 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1047 write!(f, "@tag:{}:{}", K::NAME, self.id)
1048 }
1049}
1050
1051impl<K: Kind> Schema for TagId<K> {
1052 fn shape() -> Shape {
1053 Shape::Tag(K::NAME)
1054 }
1055 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1056 read_tag(v, cx, K::NAME).map(|r| TagId::new(r.id))
1057 }
1058 fn write(&self) -> Value {
1059 Value::Tag(Ref::new(K::NAME, self.id.clone()))
1060 }
1061}
1062
1063#[derive(Debug, Clone, PartialEq)]
1065pub enum OneOrTag<K: Kind> {
1066 One(Id<K>),
1067 Tag(TagId<K>),
1068}
1069
1070pub type ItemIdOrTag = OneOrTag<kinds::Item>;
1072
1073impl<K: Kind> Schema for OneOrTag<K> {
1074 fn shape() -> Shape {
1075 Shape::RefOrTag(K::NAME)
1076 }
1077 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1078 let is_tag = match v {
1079 Value::Tag(_) => true,
1080 Value::Str(s) => s.starts_with("@tag:"),
1081 _ => false,
1082 };
1083 if is_tag {
1084 TagId::read(v, cx).map(OneOrTag::Tag)
1085 } else {
1086 Id::read(v, cx).map(OneOrTag::One)
1087 }
1088 }
1089 fn write(&self) -> Value {
1090 match self {
1091 OneOrTag::One(i) => i.write(),
1092 OneOrTag::Tag(t) => t.write(),
1093 }
1094 }
1095}
1096
1097impl Schema for Res {
1098 fn shape() -> Shape {
1099 Shape::Res
1100 }
1101 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1102 let r = match v {
1103 Value::Res(r) => r.clone(),
1104 Value::Str(s) => match typed::classify(s) {
1105 Ok(Value::Res(r)) => r,
1106 Ok(_) => {
1107 cx.problem(format!("expected a resource `@Set.key`, found {s:?}"));
1108 return None;
1109 }
1110 Err(e) => {
1111 cx.problem(e);
1112 return None;
1113 }
1114 },
1115 other => {
1116 cx.expected("a resource `@Set.key`", other);
1117 return None;
1118 }
1119 };
1120 cx.note_res(r.clone());
1121 Some(r)
1122 }
1123 fn write(&self) -> Value {
1124 Value::Res(self.clone())
1125 }
1126}
1127
1128#[derive(Debug, Clone, PartialEq)]
1131pub enum Text {
1132 Literal(String),
1133 Res(Res),
1134 NameOf(Ref),
1135}
1136
1137impl Default for Text {
1138 fn default() -> Self {
1139 Text::Literal(String::new())
1140 }
1141}
1142
1143impl Schema for Text {
1144 fn shape() -> Shape {
1145 Shape::Text
1146 }
1147 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1148 let typed = match v {
1149 Value::Str(s) => typed::classify(s).map_err(|e| cx.problem(e)).ok()?,
1150 Value::Text(t) => return Some(Text::Literal(t.clone())),
1151 other => other.clone(),
1152 };
1153 match typed {
1154 Value::Str(s) => Some(Text::Literal(s)),
1155 Value::Res(r) => {
1156 cx.note_res(r.clone());
1157 Some(Text::Res(r))
1158 }
1159 Value::Ref(r) => {
1160 cx.note_ref(r.clone());
1161 Some(Text::NameOf(r))
1162 }
1163 other => {
1164 cx.expected("a text", &other);
1165 None
1166 }
1167 }
1168 }
1169 fn write(&self) -> Value {
1170 match self {
1171 Text::Literal(s) => Value::Text(s.clone()),
1172 Text::Res(r) => Value::Res(r.clone()),
1173 Text::NameOf(r) => Value::Ref(r.clone()),
1174 }
1175 }
1176}
1177
1178#[derive(Debug, Clone, PartialEq)]
1181pub enum Image {
1182 Source(String),
1183 Res(Res),
1184}
1185
1186impl Schema for Image {
1187 fn shape() -> Shape {
1188 Shape::Image
1189 }
1190 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1191 match v {
1192 Value::Image(s) => Some(Image::Source(s.clone())),
1193 Value::Res(r) => {
1194 cx.note_res(r.clone());
1195 Some(Image::Res(r.clone()))
1196 }
1197 Value::Str(s) if !s.starts_with('@') => Some(Image::Source(s.clone())),
1198 Value::Str(s) => match typed::classify(s) {
1199 Ok(Value::Image(src)) => Some(Image::Source(src)),
1200 Ok(Value::Res(r)) => {
1201 cx.note_res(r.clone());
1202 Some(Image::Res(r))
1203 }
1204 Ok(Value::Str(s)) => Some(Image::Source(s)),
1205 Ok(other) => {
1206 cx.expected("an image", &other);
1207 None
1208 }
1209 Err(e) => {
1210 cx.problem(e);
1211 None
1212 }
1213 },
1214 other => {
1215 cx.expected("an image", other);
1216 None
1217 }
1218 }
1219 }
1220 fn write(&self) -> Value {
1221 match self {
1222 Image::Source(s) => Value::Image(s.clone()),
1223 Image::Res(r) => Value::Res(r.clone()),
1224 }
1225 }
1226}
1227
1228#[derive(Debug, Clone, PartialEq)]
1230pub enum Color {
1231 Argb(u32),
1232 Res(Res),
1233}
1234
1235impl Schema for Color {
1236 fn shape() -> Shape {
1237 Shape::Color
1238 }
1239 fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1240 match v {
1241 Value::Color(c) => Some(Color::Argb(*c)),
1242 Value::Res(r) => {
1243 cx.note_res(r.clone());
1244 Some(Color::Res(r.clone()))
1245 }
1246 Value::Str(s) if s.starts_with('#') => {
1247 typed::parse_color(s).map(Color::Argb).or_else(|| {
1248 cx.problem(format!("`{s}` isn't a color (#RRGGBB or #AARRGGBB)"));
1249 None
1250 })
1251 }
1252 Value::Str(s) if s.starts_with('@') => Res::read(v, cx).map(Color::Res),
1253 other => {
1254 cx.expected("a color (#RRGGBB or #AARRGGBB)", other);
1255 None
1256 }
1257 }
1258 }
1259 fn write(&self) -> Value {
1260 match self {
1261 Color::Argb(c) => Value::Color(*c),
1262 Color::Res(r) => Value::Res(r.clone()),
1263 }
1264 }
1265}