Skip to main content

substrate_data/schema/
expand.rs

1//! [`Expand`]: a field that takes a value or a reference to one.
2//!
3//! A format lets a person write a text in place or name it (`@word:greeting`,
4//! a word of a dictionary; `@text:intro`, a localized text). What the name
5//! stands for is the tool's: it plugs a [`Resolve`](crate::schema::Resolve)
6//! whose [`expand`](crate::schema::Resolve::expand) hands back the value, and
7//! the field is read from that value as if it had been written there.
8//!
9//! ```
10//! use substrate_data::with::Expand;
11//! use substrate_data::schema::{Dictionary, from_text, from_doc_with};
12//!
13//! #[derive(substrate_data::Schema)]
14//! struct Notice {
15//!     #[data(with = "Expand<substrate_data::with::Plain>")]
16//!     title: String,
17//! }
18//!
19//! let words: Dictionary = from_text(r#"{ word: { hello: "Hello, world" } }"#).unwrap().value;
20//!
21//! let text = r#"{ title: "@word:hello" }"#;
22//! let doc = substrate_data::text::parse(text).unwrap();
23//! let mut words = words;
24//! let notice: Notice = from_doc_with(text, &doc, &mut words).unwrap().value;
25//! assert_eq!(notice.title, "Hello, world");
26//!
27//! // Written in place, it is read as it is.
28//! let inline: Notice = from_text(r#"{ title: "Hi" }"#).unwrap().value;
29//! assert_eq!(inline.title, "Hi");
30//! ```
31
32use std::marker::PhantomData;
33
34use super::{Expansion, Reader, Shape};
35use crate::binary::{Decoder, Writer};
36use crate::frame::{Packer, Unpacker};
37use crate::model::borrowed::ValueRef;
38use crate::model::value::{Ref, Value};
39use crate::with::With;
40
41/// A field of codec `W` written in place, or as a reference the reader's
42/// resolver expands into the value `W` reads. Without a resolver, or when
43/// it keeps the reference, `W` reads what was written (a field that is
44/// itself a reference — [`RefTo`](crate::with::RefTo) — reads it as one).
45///
46/// Writing, the binary form and frames carry the value, never the
47/// reference: expansion is a read-time step of a tool that has a resolver
48/// ([`from_value_with`](crate::schema::from_value_with),
49/// [`from_doc_with`](crate::schema::from_doc_with),
50/// [`from_bytes_with`](crate::schema::from_bytes_with)). A problem with the
51/// expanded value is reported at the reference's path.
52#[derive(Debug)]
53pub struct Expand<W>(PhantomData<W>);
54
55/// The reference `v` is, as a field takes it: a reference value, or a
56/// string spelling one (`"@kind:id"`).
57fn reference_of(v: &ValueRef<'_>) -> Option<Ref> {
58    match v {
59        ValueRef::Str(s) if s.starts_with(crate::names::TYPED) => match crate::typed::classify(s) {
60            Ok(crate::typed::Spelled::Ref(r)) => Some(r),
61            _ => None,
62        },
63        other => other.to_ref(),
64    }
65}
66
67impl<F, W: With<F>> With<F> for Expand<W> {
68    fn shape() -> Shape {
69        W::shape()
70    }
71    fn read(v: &ValueRef<'_>, cx: &mut Reader<'_>) -> Option<F> {
72        let reference = reference_of(v).filter(|_| cx.resolves());
73        let Some(r) = reference else {
74            return W::read(v, cx);
75        };
76        match cx.expand_ref(&r) {
77            Expansion::Kept => W::read(v, cx),
78            Expansion::Failed => {
79                cx.note_ref(r);
80                None
81            }
82            Expansion::Value(value) => {
83                cx.note_ref(r);
84                cx.expanded(|cx| {
85                    let arena = bumpalo::Bump::new();
86                    W::read(&value.borrow(&arena), cx)
87                })
88            }
89        }
90    }
91    fn write(f: &F) -> Value {
92        W::write(f)
93    }
94    fn absent() -> Option<F> {
95        W::absent()
96    }
97    fn skip(f: &F) -> bool {
98        W::skip(f)
99    }
100    fn write_text(f: &F, w: &mut crate::text::Writer) {
101        W::write_text(f, w);
102    }
103    fn encode(f: &F, w: &mut Writer) {
104        W::encode(f, w);
105    }
106    fn decode(d: &mut Decoder<'_>) -> Option<F> {
107        W::decode(d)
108    }
109    fn pack(f: &F, p: &mut Packer) {
110        W::pack(f, p);
111    }
112    fn unpack(u: &mut Unpacker<'_>) -> Option<F> {
113        W::unpack(u)
114    }
115}