Skip to main content

yog_data/schema/
mod.rs

1//! Schemas: a format is a Rust type implementing [`Schema`] (usually
2//! `#[derive(Schema)]`). One schema reads and writes the format, checks it
3//! with the path of every problem, collects its references for the build to
4//! check, and describes itself ([`Shape`]) for Yog-IDLE's editors and the
5//! docs.
6
7mod fields;
8mod one_or_list;
9mod plain;
10mod reader;
11mod shape;
12mod typed_values;
13pub mod with;
14
15pub use fields::{Fields, field_name};
16pub use one_or_list::OneOrList;
17pub use plain::*;
18pub use reader::*;
19pub use shape::*;
20pub use typed_values::*;
21
22use std::collections::{BTreeMap, HashMap};
23use std::fmt;
24use std::marker::PhantomData;
25
26use crate::binary::{Decoder, Writer};
27use crate::diagnostic::{Diagnostic, Errors};
28use crate::frame::{Packer, Unpacker};
29use crate::model::borrowed::{MapRef, ValueRef};
30use crate::model::value::{Map, Path, Ref, Res, Seg, Value};
31use crate::text::{self, Doc, LineCol};
32use crate::typed;
33
34/// A Yog Data format.
35pub trait Schema: Sized {
36    /// What the format looks like.
37    fn shape() -> Shape;
38
39    /// Reads `v`. Problems go to `cx` at its current path; `None` when the
40    /// value couldn't be read (the problem is in `cx`).
41    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self>;
42
43    /// The value, as the format writes it.
44    fn write(&self) -> Value;
45
46    /// The value of a field that isn't there — `None`: the field is required.
47    fn absent() -> Option<Self> {
48        None
49    }
50
51    /// Whether a struct leaves this field out when writing it.
52    fn skip(&self) -> bool {
53        false
54    }
55
56    /// Writes itself in the binary form, straight — [`Schema::write`]'s
57    /// value encoded, unless the type knows better (derived ones do).
58    fn encode(&self, w: &mut Writer) {
59        w.value(&self.write());
60    }
61
62    /// Reads itself from the binary form, straight; `None` when it isn't
63    /// what [`Schema::encode`] writes — then [`read_bytes`] reads the slow
64    /// way, which says what's wrong. Through a value unless the type knows
65    /// better (derived ones do).
66    fn decode(d: &mut Decoder<'_>) -> Option<Self> {
67        let arena = bumpalo::Bump::new();
68        let v = d.value_ref(&arena).ok()?;
69        read_ref_value(&v).ok()
70    }
71
72    /// Packs itself into a frame ([`crate::frame`]): its binary form as
73    /// bytes, unless the type packs itself (derived ones and plain types do).
74    fn pack(&self, p: &mut Packer) {
75        p.put_tagged(self);
76    }
77
78    /// Reads itself out of a frame; `None` if the bytes aren't what
79    /// [`Schema::pack`] writes.
80    fn unpack(u: &mut Unpacker<'_>) -> Option<Self> {
81        u.get_tagged()
82    }
83
84    /// A list of these packed: a count, then each one (floats raw).
85    fn pack_list(items: &[Self], p: &mut Packer) {
86        let at = p.begin_list(items.len());
87        for item in items {
88            item.pack(p);
89        }
90        p.end(at);
91    }
92
93    /// A list of these out of a frame.
94    fn unpack_list(u: &mut Unpacker<'_>) -> Option<Vec<Self>> {
95        let (n, end) = u.list()?;
96        let mut out = Vec::with_capacity(n);
97        for _ in 0..n {
98            out.push(Self::unpack(u)?);
99        }
100        u.skip_to(end)?;
101        Some(out)
102    }
103
104    /// A list of these written straight (numbers pack theirs).
105    fn encode_list(items: &[Self], w: &mut Writer) {
106        w.list(items.len());
107        for item in items {
108            item.encode(w);
109        }
110    }
111
112    /// A list of these read straight.
113    fn decode_list(d: &mut Decoder<'_>) -> Option<Vec<Self>> {
114        let n = d.list()?;
115        let mut out = Vec::with_capacity(n);
116        for _ in 0..n {
117            out.push(Self::decode(d)?);
118        }
119        d.leave();
120        Some(out)
121    }
122}
123
124/// Where a tagged enum's variant keeps what it wraps when that isn't a
125/// struct (`Id(String)` is `{type: "id", value: "…"}`); a wrapped struct's
126/// fields sit next to the tag instead.
127pub const WRAPPED: &str = yog_ids::word::VALUE;
128
129/// Whether a tagged enum's variant wrapping `T` lays `T`'s fields next to
130/// the tag (a struct) or keeps `T` under [`WRAPPED`].
131#[doc(hidden)]
132pub fn flattens<T: Schema>() -> bool {
133    matches!(T::shape(), Shape::Struct(_))
134}
135
136/// `v` in the binary form, written straight ([`Schema::encode`]).
137pub fn to_bytes<T: Schema>(v: &T) -> Vec<u8> {
138    let mut w = Writer::new();
139    v.encode(&mut w);
140    w.finish()
141}
142
143/// The field names of a struct's shape (none for anything else).
144pub fn field_names(shape: &Shape) -> Vec<&'static str> {
145    match shape {
146        Shape::Struct(s) => s.fields.iter().map(|f| f.name).collect(),
147        _ => Vec::new(),
148    }
149}
150
151/// `A` and `B` read from the same object — each allowing the other's
152/// fields, like serde's `flatten`: a recipe's common fields and its kind's.
153pub fn read_both<A: Schema, B: Schema>(
154    v: &ValueRef,
155    cx: &mut Reader,
156    also: &[&'static str],
157) -> Option<(A, B)> {
158    let (an, bn) = (field_names(&A::shape()), field_names(&B::shape()));
159    let mut b_keys = bn.clone();
160    b_keys.extend_from_slice(also);
161    let mut a_keys = an;
162    a_keys.extend_from_slice(also);
163    let a = cx.with_allowed_all(&b_keys, |cx| A::read(v, cx));
164    let b = cx.with_allowed_all(&a_keys, |cx| B::read(v, cx));
165    Some((a?, b?))
166}
167
168/// Reads `v` as `T`: the value and its references, or every problem.
169pub fn from_value<T: Schema>(v: &Value) -> Result<Read<T>, Vec<Diagnostic>> {
170    let arena = bumpalo::Bump::new();
171    read_with(&v.borrow(&arena), Reader::new())
172}
173
174/// Reads `v` as `T`, nothing else: what the game wants of content (the
175/// references met aren't kept).
176pub fn read_value<T: Schema>(v: &Value) -> Result<T, Vec<Diagnostic>> {
177    let arena = bumpalo::Bump::new();
178    read_ref_value(&v.borrow(&arena))
179}
180
181/// [`read_value`] of a borrowed value.
182pub fn read_ref_value<T: Schema>(v: &ValueRef) -> Result<T, Vec<Diagnostic>> {
183    let reader = Reader {
184        value_only: true,
185        ..Reader::new()
186    };
187    read_with(v, reader).map(|r| r.value)
188}
189
190/// Reads the binary form as `T`, borrowed all the way: no string is
191/// copied but into `T` itself — content as the runtime takes it.
192pub fn read_bytes<T: Schema>(bytes: &[u8]) -> Result<T, Vec<Diagnostic>> {
193    // Straight, the usual case; the slow way says what's wrong.
194    if let Ok(mut d) = Decoder::open(bytes)
195        && let Some(v) = T::decode(&mut d)
196        && d.at_end()
197    {
198        return Ok(v);
199    }
200    let arena = bumpalo::Bump::new();
201    let v = crate::binary::decode_ref(bytes, &arena)
202        .map_err(|e| vec![Diagnostic::error(yog_ids::data_problem::BAD_BINARY, e)])?;
203    read_ref_value(&v)
204}
205
206/// `from_value`, references and texts going through `resolver`.
207pub fn from_value_with<T: Schema>(
208    v: &Value,
209    resolver: &mut dyn Resolve,
210) -> Result<Read<T>, Vec<Diagnostic>> {
211    let arena = bumpalo::Bump::new();
212    read_with(&v.borrow(&arena), Reader::with_resolver(resolver))
213}
214
215fn read_with<T: Schema>(v: &ValueRef, mut cx: Reader<'_>) -> Result<Read<T>, Vec<Diagnostic>> {
216    let value = T::read(v, &mut cx);
217    match value {
218        Some(value) if cx.problems.is_empty() => Ok(Read {
219            value,
220            refs: cx.refs,
221            tags: cx.tags,
222            resources: cx.resources,
223        }),
224        _ => Err(cx.problems),
225    }
226}
227
228/// A table a crate ships (`include_str!` of its `.ydata`), read: what it
229/// says, or — when it won't read, which its crate's tests catch — the
230/// error logged (`what`: which table) and nothing.
231pub fn built_in<T: Schema + Default>(what: &str, text: &str) -> T {
232    match from_text::<T>(text) {
233        Ok(read) => read.value,
234        Err(e) => {
235            yog_logging::error!("{what} won't read: {e}");
236            T::default()
237        }
238    }
239}
240
241/// Parses `text` and reads it as `T`; problems come with their line and column.
242pub fn from_text<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
243    let doc = text::parse(text)?;
244    from_doc(text, &doc)
245}
246
247/// Parses TOML `text` and reads it as `T`.
248#[cfg(feature = "toml")]
249pub fn from_toml<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
250    let doc = crate::toml::parse(text)?;
251    from_doc(text, &doc)
252}
253
254/// Reads a parsed document as `T`; `text` is its source, for positions.
255pub fn from_doc<T: Schema>(text: &str, doc: &Doc) -> Result<Read<T>, Errors> {
256    locate(text, doc, from_value(&doc.value))
257}
258
259/// `from_doc`, references and texts going through `resolver`.
260pub fn from_doc_with<T: Schema>(
261    text: &str,
262    doc: &Doc,
263    resolver: &mut dyn Resolve,
264) -> Result<Read<T>, Errors> {
265    locate(text, doc, from_value_with(&doc.value, resolver))
266}
267
268fn locate<T>(
269    text: &str,
270    doc: &Doc,
271    read: Result<Read<T>, Vec<Diagnostic>>,
272) -> Result<Read<T>, Errors> {
273    read.map_err(|problems| {
274        let mut located: Vec<Diagnostic> =
275            problems.into_iter().map(|p| placed(text, doc, p)).collect();
276        // In file order, as an editor lists them.
277        located.sort_by_key(|d| (d.line, d.col));
278        Errors(located)
279    })
280}
281
282/// `d` (about a value of `doc`, parsed from `text`) at its line and column:
283/// its key's for one about the key (an unknown field).
284pub fn placed(text: &str, doc: &Doc, d: Diagnostic) -> Diagnostic {
285    let spans = doc.spans_at(&d.path);
286    let span = match (d.on_key, spans.key) {
287        (true, Some(key)) => key,
288        _ => spans.span,
289    };
290    let at = LineCol::of(text, span.start as usize);
291    d.at(at)
292}
293
294/// `value` as text, indented.
295pub fn to_text<T: Schema>(value: &T) -> String {
296    text::to_string_pretty(&value.write())
297}