1use crate::value::VmValue;
2
3mod api;
4mod canonicalize;
5mod limits;
6mod result;
7mod transform;
8mod type_check;
9mod validate;
10
11pub(crate) use api::{
12 canonical_param_schema, schema_assert_canonical_param, schema_assert_param,
13 schema_expect_value, schema_extend_value, schema_from_json_schema_value,
14 schema_from_openapi_schema_value, schema_is_value, schema_omit_value, schema_partial_value,
15 schema_pick_value, schema_report_value, schema_result_value, schema_to_json_schema_value,
16 schema_to_openapi_schema_value, CanonicalParamSchema,
17};
18pub use canonicalize::json_to_vm_value;
19
20pub fn elicitation_validate_schema(schema: &VmValue) -> Result<VmValue, crate::value::VmError> {
25 schema_from_json_schema_value(schema)
26}
27
28pub fn elicitation_validate(
32 data: &VmValue,
33 schema: &VmValue,
34) -> Result<VmValue, crate::value::VmError> {
35 schema_expect_value(data, schema, false)
36}
37
38pub fn canonicalize_json_schema(schema: &VmValue) -> Result<VmValue, String> {
44 canonicalize::canonicalize_schema_value(schema)
45}
46
47pub fn validate_value_against_schema(
54 data: &VmValue,
55 schema: &VmValue,
56 apply_defaults: bool,
57) -> Result<VmValue, String> {
58 let normalized = canonicalize_json_schema(schema)?;
59 validate_value_against_canonical_schema(data, &normalized, apply_defaults)
60}
61
62pub fn validate_value_against_canonical_schema(
65 data: &VmValue,
66 schema: &VmValue,
67 apply_defaults: bool,
68) -> Result<VmValue, String> {
69 let result = validate::validate_schema_value(
70 data,
71 schema,
72 validate::ValidationOptions {
73 apply_defaults,
74 numeric_compat: false,
75 },
76 );
77 if result.errors.is_empty() {
78 Ok(result.value)
79 } else {
80 Err(result::issue_messages(&result.errors).join("; "))
81 }
82}
83
84pub(crate) const BYTES_B64_TAG: &str = "$bytes_b64";
85
86pub(crate) fn tagged_bytes_json(bytes: &[u8]) -> serde_json::Value {
87 use base64::Engine;
88
89 serde_json::json!({
90 BYTES_B64_TAG: base64::engine::general_purpose::STANDARD.encode(bytes),
91 })
92}
93
94fn vm_value_to_serde_json(value: &VmValue) -> serde_json::Value {
95 match value {
96 VmValue::Nil => serde_json::Value::Null,
97 VmValue::Bool(value) => serde_json::Value::Bool(*value),
98 VmValue::Int(value) => serde_json::json!(value),
99 VmValue::Float(value) => serde_json::json!(value),
100 VmValue::String(value) => serde_json::Value::String(value.to_string()),
101 VmValue::Bytes(bytes) => tagged_bytes_json(bytes),
102 VmValue::List(items) => {
103 serde_json::Value::Array(items.iter().map(vm_value_to_serde_json).collect())
104 }
105 VmValue::Set(set) => {
106 serde_json::Value::Array(set.iter().map(vm_value_to_serde_json).collect())
107 }
108 VmValue::Dict(items) => serde_json::Value::Object(
109 items
110 .iter()
111 .map(|(key, value)| (key.to_string(), vm_value_to_serde_json(value)))
112 .collect(),
113 ),
114 _ => serde_json::Value::String(value.display()),
115 }
116}
117
118fn schema_bool(schema: &crate::value::DictMap, key: &str) -> bool {
119 matches!(schema.get(key), Some(VmValue::Bool(true)))
120}
121
122fn schema_i64(schema: &crate::value::DictMap, key: &str) -> Option<i64> {
123 match schema.get(key) {
124 Some(VmValue::Int(value)) => Some(*value),
125 _ => None,
126 }
127}
128
129fn schema_number(schema: &crate::value::DictMap, key: &str) -> Option<f64> {
130 match schema.get(key) {
131 Some(VmValue::Int(value)) => Some(*value as f64),
132 Some(VmValue::Float(value)) => Some(*value),
133 _ => None,
134 }
135}
136
137fn child_path(path: &str, key: &str) -> String {
138 if path.is_empty() {
139 key.to_string()
140 } else {
141 format!("{path}.{key}")
142 }
143}
144
145fn index_path(path: &str, index: usize) -> String {
146 if path.is_empty() {
147 format!("[{index}]")
148 } else {
149 format!("{path}[{index}]")
150 }
151}
152
153#[cfg(test)]
154mod tests;