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}