Skip to main content

vld/combinators/
lazy_schema.rs

1use serde_json::Value;
2use std::sync::OnceLock;
3
4use crate::error::VldError;
5use crate::schema::VldSchema;
6
7/// Lazy schema for recursive/self-referencing data structures.
8///
9/// The schema factory is called on the first `parse_value` of this node and the
10/// result is cached, which allows defining schemas that reference themselves
11/// without rebuilding them on every parse.
12///
13/// Created via [`vld::lazy()`](crate::lazy).
14///
15/// Every `vld::lazy(...)` node calls its factory once; nested lazy nodes
16/// built by the factory are themselves created on first use, so a recursive
17/// schema unfolds only as deep as the inputs go.
18///
19/// # Example
20/// ```ignore
21/// // Recursive tree: { value: i64, children: Tree[] }
22/// fn tree_schema() -> impl VldSchema<Output = serde_json::Map<String, serde_json::Value>> {
23///     vld::object()
24///         .field("value", vld::number().int())
25///         .field("children", vld::array(vld::lazy(tree_schema)))
26/// }
27/// ```
28pub struct ZLazy<T, F>
29where
30    F: Fn() -> T,
31    T: VldSchema,
32{
33    factory: F,
34    schema: OnceLock<T>,
35}
36
37impl<T, F> ZLazy<T, F>
38where
39    F: Fn() -> T,
40    T: VldSchema,
41{
42    /// Create the schema (the same as its `vld::` constructor function).
43    pub fn new(factory: F) -> Self {
44        Self {
45            factory,
46            schema: OnceLock::new(),
47        }
48    }
49
50    /// The (cached) schema produced by the factory.
51    pub fn schema(&self) -> &T {
52        self.schema.get_or_init(|| (self.factory)())
53    }
54}
55
56impl<T, F> VldSchema for ZLazy<T, F>
57where
58    F: Fn() -> T,
59    T: VldSchema,
60{
61    type Output = T::Output;
62
63    fn parse_value(&self, value: &Value) -> Result<T::Output, VldError> {
64        self.schema().parse_value(value)
65    }
66    fn parse_missing(&self) -> Result<T::Output, VldError> {
67        self.schema().parse_missing()
68    }
69}