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::borrowed::ValueRef;
23use crate::schema::{Kinds, Reader, Schema, Shape, Text, is_metadata_key, read_ref};
24use crate::value::{Map, Ref, Seg, Value};
25
26/// How a field of type `F` is read and written.
27pub trait With<F> {
28    fn shape() -> Shape;
29    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<F>;
30    fn write(f: &F) -> Value;
31    /// The field's value when it isn't there (`None`: it's required).
32    fn absent() -> Option<F> {
33        None
34    }
35    /// Whether a struct leaves the field out.
36    fn skip(_f: &F) -> bool {
37        false
38    }
39}
40
41/// A reference to content of kind(s) `K`, kept as the resolved id.
42pub struct RefTo<K>(PhantomData<K>);
43
44impl<K: Kinds> With<String> for RefTo<K> {
45    fn shape() -> Shape {
46        Shape::Ref(K::NAMES)
47    }
48    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<String> {
49        // The usual case, a reference of a kind it takes: nothing copied but its id.
50        if let ValueRef::Ref { kind, id } = v
51            && (K::NAMES.is_empty() || K::NAMES.contains(kind))
52        {
53            if cx.keeps_found() || cx.resolves() {
54                let r = Ref::new(*kind, *id);
55                if cx.keeps_found() {
56                    cx.note_ref(r.clone());
57                }
58                return cx.take_ref(r);
59            }
60            return Some(id.to_string());
61        }
62        let r = read_ref(v, cx, K::NAMES)?;
63        cx.take_ref(r)
64    }
65    fn write(f: &String) -> Value {
66        Value::Ref(Ref::new(
67            K::NAMES.first().copied().unwrap_or("item"),
68            f.clone(),
69        ))
70    }
71}
72
73/// A text ([`Text`]: written, a `.yres` text, or a name), kept resolved.
74pub struct TextOf;
75
76impl With<String> for TextOf {
77    fn shape() -> Shape {
78        Shape::Text
79    }
80    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<String> {
81        let t = Text::read(v, cx)?;
82        cx.take_text(t)
83    }
84    fn write(f: &String) -> Value {
85        Value::Text(f.clone())
86    }
87}
88
89/// An optional field through `W`.
90pub struct Opt<W>(PhantomData<W>);
91
92impl<F, W: With<F>> With<Option<F>> for Opt<W> {
93    fn shape() -> Shape {
94        Shape::Optional(W::shape)
95    }
96    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Option<F>> {
97        if v.is_null() {
98            Some(None)
99        } else {
100            W::read(v, cx).map(Some)
101        }
102    }
103    fn write(f: &Option<F>) -> Value {
104        f.as_ref().map_or(Value::Null, W::write)
105    }
106    fn absent() -> Option<Option<F>> {
107        Some(None)
108    }
109    fn skip(f: &Option<F>) -> bool {
110        f.is_none()
111    }
112}
113
114/// A list, each item through `W`.
115pub struct List<W>(PhantomData<W>);
116
117impl<F, W: With<F>> With<Vec<F>> for List<W> {
118    fn shape() -> Shape {
119        Shape::List(W::shape)
120    }
121    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Vec<F>> {
122        let ValueRef::List(items) = v else {
123            cx.expected("a list", v);
124            return None;
125        };
126        let mut out = Vec::with_capacity(items.len());
127        let mut ok = true;
128        for (i, item) in items.iter().enumerate() {
129            match cx.at(Seg::Index(i), |cx| W::read(item, cx)) {
130                Some(x) => out.push(x),
131                None => ok = false,
132            }
133        }
134        ok.then_some(out)
135    }
136    fn write(f: &Vec<F>) -> Value {
137        Value::List(f.iter().map(W::write).collect())
138    }
139}
140
141/// An object of any keys, each value through `W`.
142pub struct Values<W>(PhantomData<W>);
143
144impl<F, W: With<F>> With<BTreeMap<String, F>> for Values<W> {
145    fn shape() -> Shape {
146        Shape::Map(W::shape)
147    }
148    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<BTreeMap<String, F>> {
149        let map = cx.map(v)?;
150        let mut out = BTreeMap::new();
151        let mut ok = true;
152        for (k, item) in map.iter().filter(|(k, _)| !is_metadata_key(k)) {
153            match cx.at(Seg::Key(k.to_string()), |cx| W::read(item, cx)) {
154                Some(x) => {
155                    out.insert(k.to_string(), x);
156                }
157                None => ok = false,
158            }
159        }
160        ok.then_some(out)
161    }
162    fn write(f: &BTreeMap<String, F>) -> Value {
163        Value::Map(
164            f.iter()
165                .map(|(k, v)| (k.clone(), W::write(v)))
166                .collect::<Map>(),
167        )
168    }
169}
170
171/// The field's own schema — `with` spelled out (`List<Plain>` for a `Vec<T: Schema>`).
172pub struct Plain;
173
174impl<F: Schema> With<F> for Plain {
175    fn shape() -> Shape {
176        F::shape()
177    }
178    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<F> {
179        F::read(v, cx)
180    }
181    fn write(f: &F) -> Value {
182        f.write()
183    }
184    fn absent() -> Option<F> {
185        F::absent()
186    }
187    fn skip(f: &F) -> bool {
188        f.skip()
189    }
190}