zai_rs/model/model_validate.rs
1//! JSON Schema validation for tool parameter definitions.
2//!
3//! The core build
4//! always parses string input and requires a schema-shaped object or boolean.
5//! Enabling the `toolkits` feature additionally performs full JSON Schema
6//! meta-validation through the `jsonschema` crate.
7//!
8//! ## Errors
9//!
10//! Both validation functions return `ValidationError` with specific error
11//! codes:
12//! - `"invalid_json"` - Input string is not valid JSON
13//! - `"invalid_json_schema"` - JSON is valid but not a valid JSON Schema
14//!
15//! ## Examples
16//!
17//! ```rust
18//! use zai_rs::model::model_validate::{
19//! validate_json_schema, validate_json_schema_value,
20//! };
21//!
22//! // Validate from string
23//! let schema_str = r#"{"type": "object", "properties": {"name": {"type": "string"}}}"#;
24//! assert!(validate_json_schema(schema_str).is_ok());
25//!
26//! // Validate from parsed JSON
27//! let json_value = serde_json::json!({
28//! "type": "object",
29//! "properties": {
30//! "name": {"type": "string"}
31//! }
32//! });
33//! assert!(validate_json_schema_value(&json_value).is_ok());
34//! ```
35//!
36//! ## JSON Schema Requirements
37//!
38//! Valid JSON Schemas must:
39//! - Be valid JSON syntax
40//! - Conform to JSON Schema meta-schema validation
41//! - Have appropriate schema structure for function parameters
42//!
43
44use validator::ValidationError;
45
46/// Validate a JSON Schema encoded as a string.
47///
48/// With the `toolkits` feature enabled this performs meta-schema validation;
49/// otherwise it validates the top-level JSON shape.
50///
51/// # Errors
52///
53/// * `"invalid_json"` - The input string is not valid JSON
54/// * `"invalid_json_schema"` - The JSON is valid but not a valid JSON Schema
55///
56/// # Examples
57///
58/// ```rust
59/// use zai_rs::model::model_validate::validate_json_schema;
60///
61/// // Valid JSON Schema
62/// let valid_schema = r#"{"type": "object", "properties": {"name": {"type": "string"}}}"#;
63/// assert!(validate_json_schema(valid_schema).is_ok());
64///
65/// // Invalid JSON
66/// let invalid_json = "{ invalid json }";
67/// assert!(validate_json_schema(invalid_json).is_err());
68///
69/// // Invalid JSON Schema
70/// let invalid_schema = r#"{"type": "invalid_type"}"#;
71/// assert!(validate_json_schema(invalid_schema).is_err());
72/// ```
73#[cfg(feature = "toolkits")]
74pub fn validate_json_schema(parameters: &str) -> Result<(), ValidationError> {
75 let schema_json: serde_json::Value = serde_json::from_str(parameters).map_err(|e| {
76 // Preserve the parse error (line/column/reason) in the message instead
77 // of discarding it; the error CODE stays "invalid_json" for stability.
78 ValidationError::new("invalid_json").with_message(std::borrow::Cow::Owned(format!(
79 "invalid JSON in schema: {e}"
80 )))
81 })?;
82 if jsonschema::validator_for(&schema_json).is_err() {
83 return Err(ValidationError::new("invalid_json_schema"));
84 }
85 Ok(())
86}
87
88/// Perform lightweight structural validation when `toolkits` is disabled.
89#[cfg(not(feature = "toolkits"))]
90pub fn validate_json_schema(parameters: &str) -> Result<(), ValidationError> {
91 let value: serde_json::Value =
92 serde_json::from_str(parameters).map_err(|_| ValidationError::new("invalid_json"))?;
93 validate_json_schema_value(&value)
94}
95
96/// Validate a parsed JSON Schema value.
97///
98/// With the `toolkits` feature enabled this performs meta-schema validation;
99/// otherwise it requires an object or boolean schema.
100///
101/// # Errors
102///
103/// * `"invalid_json_schema"` - The JSON value is not a valid JSON Schema
104///
105/// # Examples
106///
107/// ```rust
108/// use zai_rs::model::model_validate::validate_json_schema_value;
109/// use serde_json::json;
110///
111/// // Valid JSON Schema
112/// let valid_schema = json!({
113/// "type": "object",
114/// "properties": {
115/// "name": {"type": "string"}
116/// }
117/// });
118/// assert!(validate_json_schema_value(&valid_schema).is_ok());
119///
120/// // Invalid JSON Schema
121/// let invalid_schema = json!({"type": "invalid_type"});
122/// assert!(validate_json_schema_value(&invalid_schema).is_err());
123/// ```
124#[cfg(feature = "toolkits")]
125pub fn validate_json_schema_value(parameters: &serde_json::Value) -> Result<(), ValidationError> {
126 if jsonschema::validator_for(parameters).is_err() {
127 return Err(ValidationError::new("invalid_json_schema"));
128 }
129 Ok(())
130}
131
132/// Require a valid top-level JSON Schema shape when `toolkits` is disabled.
133///
134/// JSON Schema permits an object schema or the boolean schemas `true` and
135/// `false`. Keyword-level meta-validation requires the `toolkits` feature.
136#[cfg(not(feature = "toolkits"))]
137pub fn validate_json_schema_value(parameters: &serde_json::Value) -> Result<(), ValidationError> {
138 if parameters.is_object() || parameters.is_boolean() {
139 Ok(())
140 } else {
141 Err(ValidationError::new("invalid_json_schema"))
142 }
143}
144
145#[cfg(all(test, feature = "toolkits"))]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_validate_json_schema_valid() {
151 let valid_schema = r#"
152 {
153 "type": "object",
154 "properties": {
155 "name": {
156 "type": "string"
157 }
158 },
159 "required": ["name"]
160 }
161 "#;
162
163 assert!(validate_json_schema(valid_schema).is_ok());
164 }
165
166 #[test]
167 fn test_validate_json_schema_invalid_json() {
168 let invalid_json = "{ invalid json }";
169 let result = validate_json_schema(invalid_json);
170 assert!(result.is_err());
171 }
172
173 #[test]
174 fn test_validate_json_schema_invalid_schema() {
175 // 使用一个明确无效的 JSON Schema(type 字段值无效)
176 let invalid_schema = r#"{"type": "invalid_type"}"#;
177 let result = validate_json_schema(invalid_schema);
178 assert!(result.is_err());
179 }
180}
181
182#[cfg(all(test, not(feature = "toolkits")))]
183mod lightweight_tests {
184 use super::*;
185
186 #[test]
187 fn validates_json_and_top_level_schema_shape_without_optional_dependency() {
188 assert!(validate_json_schema(r#"{"type":"object"}"#).is_ok());
189 assert!(validate_json_schema("true").is_ok());
190 assert!(validate_json_schema("not json").is_err());
191 assert!(validate_json_schema("[]").is_err());
192 assert!(validate_json_schema("42").is_err());
193 }
194}