use serde_json::{Value, json};
use super::enforce_strict_mode;
pub(crate) fn assert_object_nodes_strict(schema: &Value) {
if let Value::Object(map) = schema {
let has_properties = map.get("properties").is_some_and(Value::is_object);
if has_properties {
assert_eq!(
map.get("additionalProperties"),
Some(&Value::Bool(false)),
"object node must set additionalProperties:false — node: {map:?}"
);
let prop_keys: std::collections::BTreeSet<String> = map
.get("properties")
.and_then(Value::as_object)
.map(|p| p.keys().cloned().collect())
.unwrap_or_default();
let required_keys: std::collections::BTreeSet<String> = map
.get("required")
.and_then(Value::as_array)
.map(|r| {
r.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
assert_eq!(
prop_keys, required_keys,
"every property must be required (and vice versa) — node: {map:?}"
);
}
if let Some(props) = map.get("properties").and_then(Value::as_object) {
for child in props.values() {
assert_object_nodes_strict(child);
}
}
if let Some(items) = map.get("items") {
assert_object_nodes_strict(items);
}
if let Some(ap) = map.get("additionalProperties")
&& ap.is_object()
{
assert_object_nodes_strict(ap);
}
} else if let Value::Array(items) = schema {
for item in items {
assert_object_nodes_strict(item);
}
}
}
#[test]
fn enforces_additional_properties_false() {
let mut schema = json!({
"type": "object",
"properties": { "a": { "type": "string" } },
"required": ["a"]
});
enforce_strict_mode(&mut schema);
assert_eq!(schema["additionalProperties"], json!(false));
}
#[test]
fn marks_all_properties_required() {
let mut schema = json!({
"type": "object",
"properties": { "a": { "type": "string" }, "b": { "type": "number" } },
"required": ["a"]
});
enforce_strict_mode(&mut schema);
let required: std::collections::BTreeSet<&str> = schema["required"]
.as_array()
.expect("required array")
.iter()
.filter_map(Value::as_str)
.collect();
assert_eq!(required, ["a", "b"].into_iter().collect());
}
#[test]
fn recurses_into_array_items() {
let mut schema = json!({
"type": "object",
"properties": {
"findings": {
"type": "array",
"items": {
"type": "object",
"properties": { "title": { "type": "string" }, "line": { "type": ["integer", "null"] } },
"required": ["title"]
}
}
},
"required": ["findings"]
});
enforce_strict_mode(&mut schema);
assert_object_nodes_strict(&schema);
let items = &schema["properties"]["findings"]["items"];
assert_eq!(items["additionalProperties"], json!(false));
let required: std::collections::BTreeSet<&str> = items["required"]
.as_array()
.expect("required")
.iter()
.filter_map(Value::as_str)
.collect();
assert_eq!(required, ["line", "title"].into_iter().collect());
}
#[test]
fn is_idempotent() {
let mut schema = json!({
"type": "object",
"properties": { "a": { "type": "string" } },
"required": ["a"]
});
enforce_strict_mode(&mut schema);
let once = schema.clone();
enforce_strict_mode(&mut schema);
assert_eq!(schema, once, "applying strict mode twice must be a no-op");
}
#[test]
fn leaves_non_object_schemas_untouched() {
let mut schema = json!({ "type": "string", "enum": ["x", "y"] });
let before = schema.clone();
enforce_strict_mode(&mut schema);
assert_eq!(schema, before);
}
#[test]
fn recurses_into_object_valued_additional_properties() {
let mut schema = json!({
"type": "object",
"properties": {
"meta": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": { "v": { "type": "string" } }
}
}
}
});
enforce_strict_mode(&mut schema);
let ap = &schema["properties"]["meta"]["additionalProperties"];
assert_object_nodes_strict(ap);
}