Skip to main content

vld/primitives/
json_value.rs

1use serde_json::Value;
2
3use crate::error::{IssueCode, PathSegment, VldError};
4use crate::schema::VldSchema;
5
6/// Schema for any JSON value, with optional shape checks. Built with `vld::json_value()`.
7#[derive(Clone)]
8pub struct ZJsonValue {
9    require_object: bool,
10    require_array: bool,
11    required_keys: Vec<String>,
12    max_depth: Option<usize>,
13    custom_type_error: Option<String>,
14}
15
16impl ZJsonValue {
17    /// Create the schema (the same as its `vld::` constructor function).
18    pub fn new() -> Self {
19        Self {
20            require_object: false,
21            require_array: false,
22            required_keys: Vec::new(),
23            max_depth: None,
24            custom_type_error: None,
25        }
26    }
27
28    /// Set a custom error message for type mismatch (`object()` / `array()`
29    /// / `require_key` on a non-object).
30    pub fn type_error(mut self, msg: impl Into<String>) -> Self {
31        self.custom_type_error = Some(msg.into());
32        self
33    }
34
35    /// Must be a JSON object.
36    pub fn object(mut self) -> Self {
37        self.require_object = true;
38        self.require_array = false;
39        self
40    }
41
42    /// Must be a JSON array.
43    pub fn array(mut self) -> Self {
44        self.require_array = true;
45        self.require_object = false;
46        self
47    }
48
49    /// Must be an object containing `key`.
50    pub fn require_key(mut self, key: impl Into<String>) -> Self {
51        self.required_keys.push(key.into());
52        self
53    }
54
55    /// Nesting must not be deeper than `depth`.
56    pub fn max_depth(mut self, depth: usize) -> Self {
57        self.max_depth = Some(depth);
58        self
59    }
60}
61
62impl Default for ZJsonValue {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68fn depth(value: &Value) -> usize {
69    match value {
70        Value::Array(arr) => 1 + arr.iter().map(depth).max().unwrap_or(0),
71        Value::Object(map) => 1 + map.values().map(depth).max().unwrap_or(0),
72        _ => 1,
73    }
74}
75
76impl VldSchema for ZJsonValue {
77    type Output = Value;
78
79    fn parse_value(&self, value: &Value) -> Result<Self::Output, VldError> {
80        if self.require_object && !value.is_object() {
81            return Err(crate::primitives::type_error(
82                &self.custom_type_error,
83                "object",
84                value,
85            ));
86        }
87        if self.require_array && !value.is_array() {
88            return Err(crate::primitives::type_error(
89                &self.custom_type_error,
90                "array",
91                value,
92            ));
93        }
94        if !self.required_keys.is_empty() {
95            let obj = value.as_object().ok_or_else(|| {
96                crate::primitives::type_error(&self.custom_type_error, "object", value)
97            })?;
98            for k in &self.required_keys {
99                if !obj.contains_key(k) {
100                    return Err(VldError::from_code(IssueCode::MissingField)
101                        .with_prefix(PathSegment::Field(k.clone())));
102                }
103            }
104        }
105        if let Some(max_depth) = self.max_depth {
106            if depth(value) > max_depth {
107                return Err(VldError::from_code_with_value(
108                    IssueCode::JsonTooDeep { max_depth },
109                    value,
110                ));
111            }
112        }
113        Ok(value.clone())
114    }
115}
116
117#[cfg(feature = "openapi")]
118impl crate::openapi::JsonSchema for ZJsonValue {
119    fn json_schema(&self) -> serde_json::Value {
120        let mut schema = serde_json::json!({});
121        // `require_key` implies an object too.
122        if self.require_object || !self.required_keys.is_empty() {
123            schema["type"] = serde_json::json!("object");
124            if !self.required_keys.is_empty() {
125                schema["required"] = serde_json::json!(self.required_keys);
126            }
127        } else if self.require_array {
128            schema["type"] = serde_json::json!("array");
129        }
130        if let Some(depth) = self.max_depth {
131            schema["x-maxDepth"] = serde_json::json!(depth);
132        }
133        schema
134    }
135    /// Required only when `null` is rejected (object / array / required keys).
136    fn is_required(&self) -> bool {
137        self.require_object || self.require_array || !self.required_keys.is_empty()
138    }
139}
140
141#[cfg(feature = "openapi")]
142impl crate::openapi::CollectNestedSchemas for ZJsonValue {}