miniconf/
schema.rs

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