Skip to main content

yog_data/
with.rs

1//! Field codecs: `#[data(with = "…")]` reads and writes a field through a
2//! type other than its own — like serde's `with`. The field keeps the plain
3//! type a program uses (`String`), the format says what it holds (an item
4//! reference, a text), checked and resolved by the [`Reader`]'s
5//! [`Resolve`](crate::schema::Resolve).
6//!
7//! ```ignore
8//! #[derive(Schema)]
9//! struct Spotlight {
10//!     /// `@item:` or `@block:`, kept as the plain id.
11//!     #[data(with = "RefTo<(kinds::Item, kinds::Block)>")]
12//!     item: String,
13//!     /// A text, a `.yres` text or a name, kept resolved.
14//!     #[data(with = "Opt<TextOf>")]
15//!     text: Option<String>,
16//! }
17//! ```
18
19use std::collections::BTreeMap;
20use std::marker::PhantomData;
21
22use crate::schema::{Kinds, Reader, Schema, Shape, Text, is_metadata_key, read_ref};
23use crate::value::{Map, Ref, Seg, Value};
24
25/// How a field of type `F` is read and written.
26pub trait With<F> {
27    fn shape() -> Shape;
28    fn read(v: &Value, cx: &mut Reader) -> Option<F>;
29    fn write(f: &F) -> Value;
30    /// The field's value when it isn't there (`None`: it's required).
31    fn absent() -> Option<F> {
32        None
33    }
34    /// Whether a struct leaves the field out.
35    fn skip(_f: &F) -> bool {
36        false
37    }
38}
39
40/// A reference to content of kind(s) `K`, kept as the resolved id.
41pub struct RefTo<K>(PhantomData<K>);
42
43impl<K: Kinds> With<String> for RefTo<K> {
44    fn shape() -> Shape {
45        Shape::Ref(K::NAMES)
46    }
47    fn read(v: &Value, cx: &mut Reader) -> Option<String> {
48        let r = read_ref(v, cx, K::NAMES)?;
49        cx.resolve_ref(&r)
50    }
51    fn write(f: &String) -> Value {
52        Value::Ref(Ref::new(
53            K::NAMES.first().copied().unwrap_or("item"),
54            f.clone(),
55        ))
56    }
57}
58
59/// A text ([`Text`]: written, a `.yres` text, or a name), kept resolved.
60pub struct TextOf;
61
62impl With<String> for TextOf {
63    fn shape() -> Shape {
64        Shape::Text
65    }
66    fn read(v: &Value, cx: &mut Reader) -> Option<String> {
67        let t = Text::read(v, cx)?;
68        cx.resolve_text(&t)
69    }
70    fn write(f: &String) -> Value {
71        Value::Text(f.clone())
72    }
73}
74
75/// An optional field through `W`.
76pub struct Opt<W>(PhantomData<W>);
77
78impl<F, W: With<F>> With<Option<F>> for Opt<W> {
79    fn shape() -> Shape {
80        Shape::Optional(W::shape)
81    }
82    fn read(v: &Value, cx: &mut Reader) -> Option<Option<F>> {
83        if v.is_null() {
84            Some(None)
85        } else {
86            W::read(v, cx).map(Some)
87        }
88    }
89    fn write(f: &Option<F>) -> Value {
90        f.as_ref().map_or(Value::Null, W::write)
91    }
92    fn absent() -> Option<Option<F>> {
93        Some(None)
94    }
95    fn skip(f: &Option<F>) -> bool {
96        f.is_none()
97    }
98}
99
100/// A list, each item through `W`.
101pub struct List<W>(PhantomData<W>);
102
103impl<F, W: With<F>> With<Vec<F>> for List<W> {
104    fn shape() -> Shape {
105        Shape::List(W::shape)
106    }
107    fn read(v: &Value, cx: &mut Reader) -> Option<Vec<F>> {
108        let Value::List(items) = v else {
109            cx.expected("a list", v);
110            return None;
111        };
112        let mut out = Vec::with_capacity(items.len());
113        let mut ok = true;
114        for (i, item) in items.iter().enumerate() {
115            match cx.at(Seg::Index(i), |cx| W::read(item, cx)) {
116                Some(x) => out.push(x),
117                None => ok = false,
118            }
119        }
120        ok.then_some(out)
121    }
122    fn write(f: &Vec<F>) -> Value {
123        Value::List(f.iter().map(W::write).collect())
124    }
125}
126
127/// An object of any keys, each value through `W`.
128pub struct Values<W>(PhantomData<W>);
129
130impl<F, W: With<F>> With<BTreeMap<String, F>> for Values<W> {
131    fn shape() -> Shape {
132        Shape::Map(W::shape)
133    }
134    fn read(v: &Value, cx: &mut Reader) -> Option<BTreeMap<String, F>> {
135        let map = cx.map(v)?;
136        let mut out = BTreeMap::new();
137        let mut ok = true;
138        for (k, item) in map.iter().filter(|(k, _)| !is_metadata_key(k)) {
139            match cx.at(Seg::Key(k.to_string()), |cx| W::read(item, cx)) {
140                Some(x) => {
141                    out.insert(k.to_string(), x);
142                }
143                None => ok = false,
144            }
145        }
146        ok.then_some(out)
147    }
148    fn write(f: &BTreeMap<String, F>) -> Value {
149        Value::Map(
150            f.iter()
151                .map(|(k, v)| (k.clone(), W::write(v)))
152                .collect::<Map>(),
153        )
154    }
155}
156
157/// The field's own schema — `with` spelled out (`List<Plain>` for a `Vec<T: Schema>`).
158pub struct Plain;
159
160impl<F: Schema> With<F> for Plain {
161    fn shape() -> Shape {
162        F::shape()
163    }
164    fn read(v: &Value, cx: &mut Reader) -> Option<F> {
165        F::read(v, cx)
166    }
167    fn write(f: &F) -> Value {
168        f.write()
169    }
170    fn absent() -> Option<F> {
171        F::absent()
172    }
173    fn skip(f: &F) -> bool {
174        f.skip()
175    }
176}