Skip to main content

oxihuman_core/
json_schema_validator.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Simple JSON schema validator stub.
6
7/// Supported JSON schema types.
8#[derive(Debug, Clone, PartialEq)]
9pub enum SchemaType {
10    Null,
11    Boolean,
12    Integer,
13    Number,
14    String,
15    Array,
16    Object,
17}
18
19/// A minimal JSON Schema node.
20#[derive(Debug, Clone)]
21pub struct SchemaNode {
22    pub schema_type: Option<SchemaType>,
23    pub required: Vec<String>,
24    pub min_length: Option<usize>,
25    pub max_length: Option<usize>,
26    pub minimum: Option<f64>,
27    pub maximum: Option<f64>,
28    pub description: Option<String>,
29}
30
31#[allow(clippy::derivable_impls)]
32impl Default for SchemaNode {
33    fn default() -> Self {
34        SchemaNode {
35            schema_type: None,
36            required: vec![],
37            min_length: None,
38            max_length: None,
39            minimum: None,
40            maximum: None,
41            description: None,
42        }
43    }
44}
45
46impl SchemaNode {
47    /// Create a new empty schema node.
48    pub fn new() -> Self {
49        Self::default()
50    }
51
52    /// Set the type constraint.
53    pub fn with_type(mut self, t: SchemaType) -> Self {
54        self.schema_type = Some(t);
55        self
56    }
57
58    /// Add a required property name.
59    pub fn require(mut self, field: &str) -> Self {
60        self.required.push(field.to_string());
61        self
62    }
63
64    /// Set the minimum numeric value.
65    pub fn with_minimum(mut self, v: f64) -> Self {
66        self.minimum = Some(v);
67        self
68    }
69
70    /// Set the maximum numeric value.
71    pub fn with_maximum(mut self, v: f64) -> Self {
72        self.maximum = Some(v);
73        self
74    }
75}
76
77/// Validation error.
78#[derive(Debug, Clone, PartialEq)]
79pub struct ValidationError {
80    pub path: String,
81    pub message: String,
82}
83
84/// Validate a numeric value against a schema node.
85pub fn validate_number(schema: &SchemaNode, value: f64, path: &str) -> Vec<ValidationError> {
86    let mut errs = vec![];
87    if let Some(min) = schema.minimum {
88        if value < min {
89            errs.push(ValidationError {
90                path: path.to_string(),
91                message: format!("{value} < minimum {min}"),
92            });
93        }
94    }
95    if let Some(max) = schema.maximum {
96        if value > max {
97            errs.push(ValidationError {
98                path: path.to_string(),
99                message: format!("{value} > maximum {max}"),
100            });
101        }
102    }
103    errs
104}
105
106/// Validate a string value against a schema node.
107pub fn validate_string(schema: &SchemaNode, value: &str, path: &str) -> Vec<ValidationError> {
108    let mut errs = vec![];
109    let len = value.len();
110    if let Some(min) = schema.min_length {
111        if len < min {
112            errs.push(ValidationError {
113                path: path.to_string(),
114                message: format!("string length {len} < minLength {min}"),
115            });
116        }
117    }
118    if let Some(max) = schema.max_length {
119        if len > max {
120            errs.push(ValidationError {
121                path: path.to_string(),
122                message: format!("string length {len} > maxLength {max}"),
123            });
124        }
125    }
126    errs
127}
128
129/// Return `true` if a field name is in the required list.
130pub fn is_required(schema: &SchemaNode, field: &str) -> bool {
131    schema.required.iter().any(|r| r == field)
132}
133
134/// Count required fields.
135pub fn required_count(schema: &SchemaNode) -> usize {
136    schema.required.len()
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_schema_default() {
145        /* default schema has no type */
146        let s = SchemaNode::new();
147        assert!(s.schema_type.is_none());
148    }
149
150    #[test]
151    fn test_with_type() {
152        /* with_type sets schema_type */
153        let s = SchemaNode::new().with_type(SchemaType::String);
154        assert_eq!(s.schema_type, Some(SchemaType::String));
155    }
156
157    #[test]
158    fn test_require_adds_field() {
159        /* require appends to required list */
160        let s = SchemaNode::new().require("name").require("age");
161        assert_eq!(s.required.len(), 2);
162    }
163
164    #[test]
165    fn test_is_required_true() {
166        /* listed field is required */
167        let s = SchemaNode::new().require("email");
168        assert!(is_required(&s, "email"));
169    }
170
171    #[test]
172    fn test_is_required_false() {
173        /* unlisted field is not required */
174        let s = SchemaNode::new().require("email");
175        assert!(!is_required(&s, "phone"));
176    }
177
178    #[test]
179    fn test_validate_number_ok() {
180        /* value in range produces no errors */
181        let s = SchemaNode::new().with_minimum(0.0).with_maximum(100.0);
182        assert!(validate_number(&s, 50.0, "/x").is_empty());
183    }
184
185    #[test]
186    fn test_validate_number_below_min() {
187        /* value below minimum produces error */
188        let s = SchemaNode::new().with_minimum(10.0);
189        assert!(!validate_number(&s, 5.0, "/x").is_empty());
190    }
191
192    #[test]
193    fn test_validate_string_ok() {
194        /* string in length range is valid */
195        let mut s = SchemaNode::new();
196        s.min_length = Some(2);
197        s.max_length = Some(10);
198        assert!(validate_string(&s, "hello", "/s").is_empty());
199    }
200
201    #[test]
202    fn test_validate_string_too_short() {
203        /* string shorter than minLength fails */
204        let mut s = SchemaNode::new();
205        s.min_length = Some(5);
206        assert!(!validate_string(&s, "hi", "/s").is_empty());
207    }
208
209    #[test]
210    fn test_required_count() {
211        /* required_count returns correct count */
212        let s = SchemaNode::new().require("a").require("b").require("c");
213        assert_eq!(required_count(&s), 3);
214    }
215}