miniconf/schema.rs
1use core::{convert::Infallible, num::NonZero};
2use serde::Serialize;
3
4use crate::{
5 DescendError, ExactSize, IntoKeys, KeyError, Keys, NodeIter, Seeded, Shape, Transcode,
6};
7
8/// A numbered schema item
9#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
10pub struct Numbered {
11 /// The child schema
12 pub schema: &'static Schema,
13 /// The outer metadata
14 pub meta: Option<Meta>,
15}
16
17impl Numbered {
18 /// Create a new Numbered schema item with no outer metadata.
19 pub const fn new(schema: &'static Schema) -> Self {
20 Self { meta: None, schema }
21 }
22}
23
24/// A named schema item
25#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
26pub struct Named {
27 /// The name of the item
28 pub name: &'static str,
29 /// The child schema
30 pub schema: &'static Schema,
31 /// The outer metadata
32 pub meta: Option<Meta>,
33}
34
35impl Named {
36 /// Create a new Named schema item with no outer metadata.
37 pub const fn new(name: &'static str, schema: &'static Schema) -> Self {
38 Self {
39 meta: None,
40 name,
41 schema,
42 }
43 }
44}
45
46/// A representative schema item for a homogeneous array
47#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
48pub struct Homogeneous {
49 /// The number of items
50 pub len: NonZero<usize>,
51 /// The schema of the child nodes
52 pub schema: &'static Schema,
53 /// The outer metadata
54 pub meta: Option<Meta>,
55}
56
57impl Homogeneous {
58 /// Create a new Homogeneous schema item with no outer metadata.
59 pub const fn new(len: usize, schema: &'static Schema) -> Self {
60 Self {
61 meta: None,
62 len: NonZero::new(len).expect("Must have at least one child"),
63 schema,
64 }
65 }
66}
67
68/// An internal node with children
69///
70/// Always non-empty
71#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
72pub enum Internal {
73 /// Named children
74 Named(&'static [Named]),
75 /// Numbered heterogeneous children
76 Numbered(&'static [Numbered]),
77 /// Homogeneous numbered children
78 Homogeneous(Homogeneous),
79}
80
81impl Internal {
82 /// Return the number of direct child nodes
83 pub const fn len(&self) -> NonZero<usize> {
84 match self {
85 Self::Named(n) => NonZero::new(n.len()).expect("Must have at least one child"),
86 Self::Numbered(n) => NonZero::new(n.len()).expect("Must have at least one child"),
87 Self::Homogeneous(h) => h.len,
88 }
89 }
90
91 /// Return the child schema at the given index
92 ///
93 /// # Panics
94 /// If the index is out of bounds
95 pub const fn get_schema(&self, idx: usize) -> &Schema {
96 match self {
97 Self::Named(nameds) => nameds[idx].schema,
98 Self::Numbered(numbereds) => numbereds[idx].schema,
99 Self::Homogeneous(homogeneous) => homogeneous.schema,
100 }
101 }
102
103 /// Return the outer metadata for the given child
104 ///
105 /// # Panics
106 /// If the index is out of bounds
107 pub const fn get_meta(&self, idx: usize) -> &Option<Meta> {
108 match self {
109 Internal::Named(nameds) => &nameds[idx].meta,
110 Internal::Numbered(numbereds) => &numbereds[idx].meta,
111 Internal::Homogeneous(homogeneous) => &homogeneous.meta,
112 }
113 }
114
115 /// Perform a index-to-name lookup
116 ///
117 /// If this succeeds with None, it's a numbered or homogeneous internal node and the
118 /// name is the formatted index.
119 ///
120 /// # Panics
121 /// If the index is out of bounds
122 pub const fn get_name(&self, idx: usize) -> Option<&str> {
123 if let Self::Named(n) = self {
124 Some(n[idx].name)
125 } else {
126 None
127 }
128 }
129
130 /// Perform a name-to-index lookup
131 pub fn get_index(&self, name: &str) -> Option<usize> {
132 match self {
133 Internal::Named(n) => n.iter().position(|n| n.name == name),
134 Internal::Numbered(n) => name.parse().ok().filter(|i| *i < n.len()),
135 Internal::Homogeneous(h, ..) => name.parse().ok().filter(|i| *i < h.len.get()),
136 }
137 }
138}
139
140/// The metadata type
141///
142/// A slice of key-value pairs
143#[cfg(feature = "meta-str")]
144pub type Meta = &'static [(&'static str, &'static str)];
145#[cfg(not(any(feature = "meta-str")))]
146#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash, Serialize)]
147/// The metadata type
148///
149/// Uninhabited
150pub enum Meta {}
151
152/// Type of a node: leaf or internal
153#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Default)]
154pub struct Schema {
155 /// Inner metadata
156 pub meta: Option<Meta>,
157
158 /// Internal schemata
159 pub internal: Option<Internal>,
160}
161
162impl Schema {
163 /// A leaf without metadata
164 pub const LEAF: Self = Self {
165 meta: None,
166 internal: None,
167 };
168
169 /// Create a new internal node schema with named children and without innner metadata
170 pub const fn numbered(numbered: &'static [Numbered]) -> Self {
171 Self {
172 meta: None,
173 internal: Some(Internal::Numbered(numbered)),
174 }
175 }
176
177 /// Create a new internal node schema with numbered children and without innner metadata
178 pub const fn named(named: &'static [Named]) -> Self {
179 Self {
180 meta: None,
181 internal: Some(Internal::Named(named)),
182 }
183 }
184
185 /// Create a new internal node schema with homogenous children and without innner metadata
186 pub const fn homogeneous(homogeneous: Homogeneous) -> Self {
187 Self {
188 meta: None,
189 internal: Some(Internal::Homogeneous(homogeneous)),
190 }
191 }
192
193 /// Whether this node is a leaf
194 pub const fn is_leaf(&self) -> bool {
195 self.internal.is_none()
196 }
197
198 /// Number of child nodes
199 pub const fn len(&self) -> usize {
200 match &self.internal {
201 None => 0,
202 Some(i) => i.len().get(),
203 }
204 }
205
206 /// See [`Self::is_leaf()`]
207 pub const fn is_empty(&self) -> bool {
208 self.is_leaf()
209 }
210
211 /// Look up the next item from keys and return a child index
212 ///
213 /// # Panics
214 /// On a leaf Schema.
215 pub fn next(&self, mut keys: impl Keys) -> Result<usize, KeyError> {
216 keys.next(self.internal.as_ref().unwrap())
217 }
218
219 /// Traverse from the root to a leaf and call a function for each node.
220 ///
221 /// If a leaf is found early (`keys` being longer than required)
222 /// `Err(KeyError::TooLong)` is returned.
223 /// If `keys` is exhausted before reaching a leaf node,
224 /// `Err(KeyError::TooShort)` is returned.
225 ///
226 /// ```
227 /// # use core::convert::Infallible;
228 /// use miniconf::{IntoKeys, TreeSchema};
229 /// #[derive(TreeSchema)]
230 /// struct S {
231 /// foo: u32,
232 /// bar: [u16; 2],
233 /// };
234 /// let mut ret = [
235 /// (S::SCHEMA, Some(1usize)),
236 /// (<[u16; 2]>::SCHEMA, Some(0)),
237 /// (u16::SCHEMA, None),
238 /// ].into_iter();
239 /// let func = |schema, idx_internal: Option<_>| {
240 /// assert_eq!(ret.next().unwrap(), (schema, idx_internal.map(|(idx, _)| idx)));
241 /// Ok::<_, Infallible>(())
242 /// };
243 /// assert_eq!(S::SCHEMA.descend(["bar", "0"].into_keys(), func), Ok(()));
244 /// ```
245 ///
246 /// # Args
247 /// * `keys`: A `Key`s identifying the node.
248 /// * `func`: A `FnMut` to be called for each (internal and leaf) node on the path.
249 /// Its arguments are outer schema and optionally the inner index and internal schema.
250 /// Returning `Err(E)` aborts the traversal.
251 /// Returning `Ok(T)` continues the downward traversal.
252 ///
253 /// # Returns
254 /// The leaf `func` call return value.
255 pub fn descend<'a, T, E>(
256 &'a self,
257 mut keys: impl Keys,
258 mut func: impl FnMut(&'a Self, Option<(usize, &'a Internal)>) -> Result<T, E>,
259 ) -> Result<T, DescendError<E>> {
260 let mut schema = self;
261 while let Some(internal) = schema.internal.as_ref() {
262 let idx = keys.next(internal)?;
263 func(schema, Some((idx, internal))).map_err(DescendError::Inner)?;
264 schema = internal.get_schema(idx);
265 }
266 keys.finalize()?;
267 func(schema, None).map_err(DescendError::Inner)
268 }
269
270 /// Look up outer and inner metadata given keys.
271 pub fn get_meta(
272 &self,
273 keys: impl IntoKeys,
274 ) -> Result<(Option<&Option<Meta>>, &Option<Meta>), KeyError> {
275 let mut outer = None;
276 let mut inner = &self.meta;
277 self.descend(keys.into_keys(), |schema, idx_internal| {
278 if let Some((idx, internal)) = idx_internal {
279 outer = Some(internal.get_meta(idx));
280 }
281 inner = &schema.meta;
282 Ok::<_, Infallible>(())
283 })
284 .map_err(|e| e.try_into().unwrap())?;
285 Ok((outer, inner))
286 }
287
288 /// Get the schema of the node identified by keys.
289 pub fn get(&self, keys: impl IntoKeys) -> Result<&Self, KeyError> {
290 let mut schema = self;
291 self.descend(keys.into_keys(), |s, _idx_internal| {
292 schema = s;
293 Ok::<_, Infallible>(())
294 })
295 .map_err(|e| e.try_into().unwrap())?;
296 Ok(schema)
297 }
298
299 /// Transcode keys to a new keys type representation using its default seed.
300 ///
301 /// In order to not require the default seed, use [`Self::transcode_with`]
302 /// or [`Transcode::transcode`] on an existing `&mut N`.
303 ///
304 /// ```
305 /// use miniconf::{Indices, JsonPath, Packed, Track, Short, Path, TreeSchema};
306 /// #[derive(TreeSchema)]
307 /// struct S {
308 /// foo: u32,
309 /// bar: [u16; 5],
310 /// };
311 ///
312 /// let idx = [1, 1];
313 /// let sch = S::SCHEMA;
314 ///
315 /// let path = sch.transcode::<Path<String>>(idx).unwrap();
316 /// assert_eq!(path.path.as_str(), "/bar/1");
317 /// let path = sch.transcode::<JsonPath<String>>(idx).unwrap();
318 /// assert_eq!(path.0.as_str(), ".bar[1]");
319 /// let indices = sch.transcode::<Indices<[usize; 2]>>(&path).unwrap();
320 /// assert_eq!(indices.as_ref(), idx);
321 /// let indices = sch.transcode::<Indices<[usize; 2]>>(["bar", "1"]).unwrap();
322 /// assert_eq!(indices.as_ref(), [1, 1]);
323 /// let packed = sch.transcode::<Packed>(["bar", "4"]).unwrap();
324 /// assert_eq!(packed.into_lsb().get(), 0b1_1_100);
325 /// let path = sch.transcode::<Path<String>>(packed).unwrap();
326 /// assert_eq!(path.path.as_str(), "/bar/4");
327 /// let node = sch.transcode::<Short<Track<()>>>(&path).unwrap();
328 /// assert_eq!((node.leaf(), node.inner().depth()), (true, 2));
329 /// ```
330 ///
331 /// # Args
332 /// * `keys`: `IntoKeys` to identify the node.
333 ///
334 /// # Returns
335 /// Transcoded target and node information on success
336 pub fn transcode<N: Transcode + Seeded>(
337 &self,
338 keys: impl IntoKeys,
339 ) -> Result<N, DescendError<N::Error>> {
340 self.transcode_with(keys, N::DEFAULT_SEED)
341 }
342
343 /// Look up the key and transcode it into a fresh output constructed from `seed`.
344 pub fn transcode_with<N: Transcode + Seeded>(
345 &self,
346 keys: impl IntoKeys,
347 seed: N::Seed,
348 ) -> Result<N, DescendError<N::Error>> {
349 let mut target = N::from_seed(&seed);
350 target.transcode(self, keys)?;
351 Ok(target)
352 }
353
354 /// The Shape of the schema
355 pub const fn shape(&self) -> Shape {
356 Shape::new(self)
357 }
358
359 /// Return an iterator over nodes of a given type
360 ///
361 /// This is a walk of all leaf nodes.
362 /// The iterator will walk all paths, including those that may be absent at
363 /// runtime (see [`crate::TreeSchema#option`]).
364 /// The iterator has an exact and trusted `size_hint()`.
365 /// The `D` const generic of [`NodeIter`] is the maximum key depth.
366 ///
367 /// ```
368 /// use miniconf::{Indices, JsonPath, Short, Track, Packed, Path, TreeSchema};
369 /// #[derive(TreeSchema)]
370 /// struct S {
371 /// foo: u32,
372 /// bar: [u16; 2],
373 /// };
374 /// const MAX_DEPTH: usize = S::SCHEMA.shape().max_depth;
375 /// assert_eq!(MAX_DEPTH, 2);
376 ///
377 /// let paths: Vec<_> = S::SCHEMA
378 /// .nodes_with::<Path<String>, MAX_DEPTH>('/')
379 /// .map(|p| p.unwrap().into_inner())
380 /// .collect();
381 /// assert_eq!(paths, ["/foo", "/bar/0", "/bar/1"]);
382 ///
383 /// let paths: Vec<_> = S::SCHEMA.nodes::<JsonPath<String>, MAX_DEPTH>()
384 /// .map(|p| p.unwrap().into_inner())
385 /// .collect();
386 /// assert_eq!(paths, [".foo", ".bar[0]", ".bar[1]"]);
387 ///
388 /// let indices: Vec<_> = S::SCHEMA.nodes::<Indices<[_; 2]>, MAX_DEPTH>()
389 /// .map(|p| p.unwrap().into_inner())
390 /// .collect();
391 /// assert_eq!(indices, [([0, 0], 1), ([1, 0], 2), ([1, 1], 2)]);
392 ///
393 /// let packed: Vec<_> = S::SCHEMA.nodes::<Packed, MAX_DEPTH>()
394 /// .map(|p| p.unwrap().into_lsb().get())
395 /// .collect();
396 /// assert_eq!(packed, [0b1_0, 0b1_1_0, 0b1_1_1]);
397 ///
398 /// let nodes: Vec<_> = S::SCHEMA.nodes::<Short<Track<()>>, MAX_DEPTH>()
399 /// .map(|p| {
400 /// let p = p.unwrap();
401 /// (p.leaf(), p.inner().depth())
402 /// })
403 /// .collect();
404 /// assert_eq!(nodes, [(true, 1), (true, 2), (true, 2)]);
405 /// ```
406 pub const fn nodes<N: Seeded, const D: usize>(&'static self) -> ExactSize<NodeIter<N, D>> {
407 NodeIter::new(self, [0; D], 0, N::DEFAULT_SEED).exact_size()
408 }
409
410 /// Return an iterator over nodes using a preconfigured output seed.
411 ///
412 /// This is useful for runtime-configured path encodings such as [`crate::Path`],
413 /// where the emitted separator is stored in the target value rather than in a const generic.
414 pub fn nodes_with<N: Seeded, const D: usize>(
415 &'static self,
416 seed: N::Seed,
417 ) -> ExactSize<NodeIter<N, D>> {
418 NodeIter::new(self, [0; D], 0, seed).exact_size()
419 }
420}