use super::AdditionalProperties;use super::JsonSchema;use super::JsonSchemaPrimitiveType;use super::JsonSchemaType;use super::parse_tool_input_schema;use super::parse_tool_input_schema_without_compaction;use pretty_assertions::assert_eq;use std::collections::BTreeMap;// Tests in this section exercise normalization transforms that mutate badly// formed JSON for consumption by the Responses API.#[test]fn parse_tool_input_schema_coerces_boolean_schemas() { // Example schema shape: // true // // Expected normalization behavior: // - JSON Schema boolean forms are coerced to `{ "type": "string" }` // because the baseline enum model cannot represent boolean-schema // semantics directly. let schema = parse_tool_input_schema(&serde_json::json!(true)).expect("parse schema"); assert_eq!(schema, JsonSchema::string(/*description*/ None));}#[test]fn json_schema_serializes_encrypted_marker() { let schema = JsonSchema::string(Some("Secret value".to_string())).with_encrypted(); assert_eq!( serde_json::to_value(schema).expect("serialize schema"), serde_json::json!({ "type": "string", "description": "Secret value", "encrypted": true, }) );}#[test]fn parse_tool_input_schema_infers_object_shape_and_defaults_properties() { // Example schema shape: // { // "properties": { // "query": { "description": "search query" } // } // } // // Expected normalization behavior: // - `properties` implies an object schema when `type` is omitted. // - The child property has no recognized schema hints, so it is coerced to // an empty permissive schema. let schema = parse_tool_input_schema(&serde_json::json!({ "properties": { "query": {"description": "search query"} } })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::from([("query".to_string(), JsonSchema::default())]), /*required*/ None, /*additional_properties*/ None ) );}#[test]fn parse_tool_input_schema_coerces_unrecognized_object_schema_to_empty_schema() { // Example schema shape: // { // "description": "Ticket identifier", // "title": "Ticket ID" // } // // Expected normalization behavior: // - Object schemas with no recognized schema hints are treated as // malformed and coerced to the empty permissive schema. let schema = parse_tool_input_schema(&serde_json::json!({ "description": "Ticket identifier", "title": "Ticket ID" })) .expect("parse schema"); assert_eq!(schema, JsonSchema::default());}#[test]fn parse_tool_input_schema_preserves_integer_and_defaults_array_items() { // Example schema shape: // { // "type": "object", // "properties": { // "page": { "type": "integer" }, // "tags": { "type": "array" } // } // } // // Expected normalization behavior: // - `"integer"` is preserved distinctly from `"number"`. // - Arrays missing `items` receive a permissive string `items` schema. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "page": {"type": "integer"}, "tags": {"type": "array"} } })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::from([ ( "page".to_string(), JsonSchema::integer(/*description*/ None), ), ( "tags".to_string(), JsonSchema::array( JsonSchema::string(/*description*/ None), /*description*/ None, ) ), ]), /*required*/ None, /*additional_properties*/ None ) );}#[test]fn parse_tool_input_schema_sanitizes_additional_properties_schema() { // Example schema shape: // { // "type": "object", // "additionalProperties": { // "required": ["value"], // "properties": { // "value": { // "anyOf": [ // { "type": "string" }, // { "type": "number" } // ] // } // } // } // } // // Expected normalization behavior: // - `additionalProperties` schema objects are recursively sanitized. // - The nested schema is normalized into the current object/anyOf form. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "additionalProperties": { "required": ["value"], "properties": { "value": {"anyOf": [{"type": "string"}, {"type": "number"}]} } } })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::new(), /*required*/ None, Some(AdditionalProperties::Schema(Box::new(JsonSchema::object( BTreeMap::from([( "value".to_string(), JsonSchema::any_of( vec![ JsonSchema::string(/*description*/ None), JsonSchema::number(/*description*/ None), ], /*description*/ None, ), )]), Some(vec!["value".to_string()]), /*additional_properties*/ None, )))) ) );}#[test]fn parse_tool_input_schema_infers_object_shape_from_boolean_additional_properties_only() { // Example schema shape: // { // "additionalProperties": false // } // // Expected normalization behavior: // - `additionalProperties` implies an object schema when `type` is omitted. // - The boolean `additionalProperties` setting is preserved. let schema = parse_tool_input_schema(&serde_json::json!({ "additionalProperties": false })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object(BTreeMap::new(), /*required*/ None, Some(false.into())) );}#[test]fn parse_tool_input_schema_infers_number_from_numeric_keywords() { // Example schema shape: // { // "minimum": 1 // } // // Expected normalization behavior: // - Numeric constraint keywords imply a number schema when `type` is // omitted. let schema = parse_tool_input_schema(&serde_json::json!({ "minimum": 1 })) .expect("parse schema"); assert_eq!(schema, JsonSchema::number(/*description*/ None));}#[test]fn parse_tool_input_schema_infers_number_from_multiple_of() { // Example schema shape: // { // "multipleOf": 5 // } // // Expected normalization behavior: // - `multipleOf` follows the same numeric-keyword inference path as // `minimum` / `maximum`. let schema = parse_tool_input_schema(&serde_json::json!({ "multipleOf": 5 })) .expect("parse schema"); assert_eq!(schema, JsonSchema::number(/*description*/ None));}#[test]fn parse_tool_input_schema_infers_string_from_enum_const_and_format_keywords() { // Example schema shapes: // { "enum": ["fast", "safe"] } // { "const": "file" } // { "format": "date-time" } // // Expected normalization behavior: // - `enum` and `const` normalize into explicit string-enum schemas. // - `format` still falls back to a plain string schema. let enum_schema = parse_tool_input_schema(&serde_json::json!({ "enum": ["fast", "safe"] })) .expect("parse enum schema"); let const_schema = parse_tool_input_schema(&serde_json::json!({ "const": "file" })) .expect("parse const schema"); let format_schema = parse_tool_input_schema(&serde_json::json!({ "format": "date-time" })) .expect("parse format schema"); assert_eq!( enum_schema, JsonSchema::string_enum( vec![serde_json::json!("fast"), serde_json::json!("safe")], /*description*/ None, ) ); assert_eq!( const_schema, JsonSchema::string_enum(vec![serde_json::json!("file")], /*description*/ None) ); assert_eq!(format_schema, JsonSchema::string(/*description*/ None));}#[test]fn parse_tool_input_schema_preserves_empty_schema() { // Example schema shape: // {} // // Expected normalization behavior: // - An empty JSON Schema is already a valid permissive schema, so it stays // empty rather than being rewritten as an object schema. let schema = parse_tool_input_schema(&serde_json::json!({})).expect("parse schema"); assert_eq!(schema, JsonSchema::default());}#[test]fn parse_tool_input_schema_preserves_nested_empty_schema() { // Example schema shape: // { // "type": "object", // "properties": { // "metadata": { // "properties": { // "extra": {} // } // } // } // } // // Expected normalization behavior: // - The sanitizer recurses through nested object properties. // - The innermost `extra` field is an empty JSON Schema and stays empty. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "metadata": { "properties": { "extra": {} } } } })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::from([( "metadata".to_string(), JsonSchema::object( BTreeMap::from([("extra".to_string(), JsonSchema::default())]), /*required*/ None, /*additional_properties*/ None, ) )]), /*required*/ None, /*additional_properties*/ None, ) );}#[test]fn parse_tool_input_schema_infers_array_from_prefix_items() { // Example schema shape: // { // "prefixItems": [ // { "type": "string" } // ] // } // // Expected normalization behavior: // - `prefixItems` implies an array schema when `type` is omitted. // - The normalized result is stored as a regular array schema with string // items. let schema = parse_tool_input_schema(&serde_json::json!({ "prefixItems": [ {"type": "string"} ] })) .expect("parse schema"); assert_eq!( schema, JsonSchema::array( JsonSchema::string(/*description*/ None), /*description*/ None, ) );}#[test]fn parse_tool_input_schema_preserves_boolean_additional_properties_on_inferred_object() { // Example schema shape: // { // "type": "object", // "properties": { // "metadata": { // "additionalProperties": true // } // } // } // // Expected normalization behavior: // - The nested `metadata` schema is inferred to be an object because it has // `additionalProperties`. // - `additionalProperties: true` is preserved rather than rewritten. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "metadata": { "additionalProperties": true } } })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::from([( "metadata".to_string(), JsonSchema::object(BTreeMap::new(), /*required*/ None, Some(true.into())), )]), /*required*/ None, /*additional_properties*/ None ) );}#[test]fn parse_tool_input_schema_infers_object_shape_from_schema_additional_properties_only() { // Example schema shape: // { // "additionalProperties": { // "type": "string" // } // } // // Expected normalization behavior: // - A schema-valued `additionalProperties` also implies an object schema // when `type` is omitted. // - The nested schema is preserved as the object's // `additionalProperties` definition. let schema = parse_tool_input_schema(&serde_json::json!({ "additionalProperties": { "type": "string" } })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::new(), /*required*/ None, Some(JsonSchema::string(/*description*/ None).into()) ) );}#[test]fn parse_tool_input_schema_rewrites_const_to_single_value_enum() { // Example schema shape: // { // "const": "tagged" // } // // Expected normalization behavior: // - `const` is rewritten through the sanitizer's `map.remove("const")` // path into an equivalent single-value string enum schema. let schema = parse_tool_input_schema(&serde_json::json!({ "const": "tagged" })) .expect("parse schema"); assert_eq!( schema, JsonSchema::string_enum(vec![serde_json::json!("tagged")], /*description*/ None) );}#[test]fn parse_tool_input_schema_rejects_singleton_null_type() { let err = parse_tool_input_schema(&serde_json::json!({ "type": "null" })) .expect_err("singleton null should be rejected"); assert!( err.to_string() .contains("tool input schema must not be a singleton null type"), "unexpected error: {err}" );}#[test]fn parse_tool_input_schema_fills_default_properties_for_nullable_object_union() { // Example schema shape: // { // "type": ["object", "null"] // } // // Expected normalization behavior: // - The full union is preserved. // - Object members of the union still receive default `properties`. let schema = parse_tool_input_schema(&serde_json::json!({ "type": ["object", "null"] })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Multiple(vec![ JsonSchemaPrimitiveType::Object, JsonSchemaPrimitiveType::Null, ])), properties: Some(BTreeMap::new()), ..Default::default() } );}#[test]fn parse_tool_input_schema_fills_default_items_for_nullable_array_union() { // Example schema shape: // { // "type": ["array", "null"] // } // // Expected normalization behavior: // - The full union is preserved. // - Array members of the union still receive default `items`. let schema = parse_tool_input_schema(&serde_json::json!({ "type": ["array", "null"] })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Multiple(vec![ JsonSchemaPrimitiveType::Array, JsonSchemaPrimitiveType::Null, ])), items: Some(Box::new(JsonSchema::string(/*description*/ None))), ..Default::default() } );}// Schemas that should be preserved for Responses API compatibility rather than// being rewritten into a different shape.#[test]fn parse_tool_input_schema_preserves_nested_nullable_any_of_shape() { // Example schema shape: // { // "type": "object", // "properties": { // "open": { // "anyOf": [ // { // "type": "array", // "items": { // "type": "object", // "properties": { // "ref_id": { "type": "string" }, // "lineno": { "anyOf": [{ "type": "integer" }, { "type": "null" }] } // }, // "required": ["ref_id"], // "additionalProperties": false // } // }, // { "type": "null" } // ] // } // } // } // // Expected normalization behavior: // - Nested nullable `anyOf` shapes are preserved all the way down. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "open": { "anyOf": [ { "type": "array", "items": { "type": "object", "properties": { "ref_id": {"type": "string"}, "lineno": {"anyOf": [{"type": "integer"}, {"type": "null"}]} }, "required": ["ref_id"], "additionalProperties": false } }, {"type": "null"} ] } } })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::from([( "open".to_string(), JsonSchema::any_of( vec![ JsonSchema::array( JsonSchema::object( BTreeMap::from([ ( "lineno".to_string(), JsonSchema::any_of( vec![ JsonSchema::integer(/*description*/ None), JsonSchema::null(/*description*/ None), ], /*description*/ None, ), ), ( "ref_id".to_string(), JsonSchema::string(/*description*/ None), ), ]), Some(vec!["ref_id".to_string()]), Some(false.into()), ), /*description*/ None, ), JsonSchema::null(/*description*/ None), ], /*description*/ None, ), ),]), /*required*/ None, /*additional_properties*/ None ) );}#[test]fn parse_tool_input_schema_preserves_nested_nullable_type_union() { // Example schema shape: // { // "type": "object", // "properties": { // "nickname": { // "type": ["string", "null"], // "description": "Optional nickname" // } // }, // "required": ["nickname"], // "additionalProperties": false // } // // Expected normalization behavior: // - The nested property keeps the explicit `["string", "null"]` union. // - The object-level `required` and `additionalProperties: false` stay intact. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "nickname": { "type": ["string", "null"], "description": "Optional nickname" } }, "required": ["nickname"], "additionalProperties": false })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::from([( "nickname".to_string(), JsonSchema { schema_type: Some(JsonSchemaType::Multiple(vec![ JsonSchemaPrimitiveType::String, JsonSchemaPrimitiveType::Null, ])), description: Some("Optional nickname".to_string()), ..Default::default() }, )]), Some(vec!["nickname".to_string()]), Some(false.into()), ) );}#[test]fn parse_tool_input_schema_preserves_nested_any_of_property() { // Example schema shape: // { // "type": "object", // "properties": { // "query": { // "anyOf": [ // { "type": "string" }, // { "type": "number" } // ] // } // } // } // // Expected normalization behavior: // - The nested `anyOf` is preserved rather than flattened into a single // fallback type. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "query": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } } })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::from([( "query".to_string(), JsonSchema::any_of( vec![ JsonSchema::string(/*description*/ None), JsonSchema::number(/*description*/ None), ], /*description*/ None, ), )]), /*required*/ None, /*additional_properties*/ None ) );}#[test]fn parse_tool_input_schema_preserves_nested_one_of_property() { // Example schema shape: // { // "type": "object", // "properties": { // "query": { // "oneOf": [ // { "const": "exact" }, // { "type": "number" } // ] // } // } // } // // Expected normalization behavior: // - The nested `oneOf` is preserved. // - Child variants are recursively sanitized, including `const` to `enum`. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "query": { "oneOf": [ { "const": "exact" }, { "type": "number" } ] } } })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::from([( "query".to_string(), JsonSchema::one_of( vec![ JsonSchema::string_enum( vec![serde_json::json!("exact")], /*description*/ None, ), JsonSchema::number(/*description*/ None), ], /*description*/ None, ), )]), /*required*/ None, /*additional_properties*/ None ) );}#[test]fn parse_tool_input_schema_preserves_nested_all_of_property() { // Example schema shape: // { // "type": "object", // "properties": { // "query": { // "allOf": [ // { "type": "string" }, // { "description": "unrecognized by itself" } // ] // } // } // } // // Expected normalization behavior: // - The nested `allOf` is preserved structurally rather than flattened. // - Child variants are recursively sanitized. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "query": { "allOf": [ { "type": "string" }, { "description": "unrecognized by itself" } ] } } })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::from([( "query".to_string(), JsonSchema::all_of( vec![ JsonSchema::string(/*description*/ None), JsonSchema::default(), ], /*description*/ None, ), )]), /*required*/ None, /*additional_properties*/ None ) );}#[test]fn parse_tool_input_schema_preserves_type_unions_without_rewriting_to_any_of() { // Example schema shape: // { // "type": ["string", "null"], // "description": "optional string" // } // // Expected normalization behavior: // - Explicit type unions are preserved as unions rather than rewritten to // `anyOf`. let schema = parse_tool_input_schema(&serde_json::json!({ "type": ["string", "null"], "description": "optional string" })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Multiple(vec![ JsonSchemaPrimitiveType::String, JsonSchemaPrimitiveType::Null, ])), description: Some("optional string".to_string()), ..Default::default() } );}#[test]fn parse_tool_input_schema_preserves_explicit_enum_type_union() { // Example schema shape: // { // "type": ["string", "null"], // "enum": ["short", "medium", "long"], // "description": "optional response length" // } // // Expected normalization behavior: // - The explicit string/null union is preserved alongside the enum values. let schema = super::parse_tool_input_schema(&serde_json::json!({ "type": ["string", "null"], "enum": ["short", "medium", "long"], "description": "optional response length" })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Multiple(vec![ JsonSchemaPrimitiveType::String, JsonSchemaPrimitiveType::Null, ])), description: Some("optional response length".to_string()), enum_values: Some(vec![ serde_json::json!("short"), serde_json::json!("medium"), serde_json::json!("long"), ]), ..Default::default() } );}fn many_string_properties(count: usize) -> serde_json::Map<String, serde_json::Value> { (0..count) .map(|index| { ( format!("field_{index:03}"), serde_json::json!({ "type": "string" }), ) }) .collect()}#[test]fn parse_large_tool_input_schema_compacts_descriptions_only_on_default_path() { let input_schema = serde_json::json!({ "type": "object", "description": "x".repeat(4_500), "properties": { "metadata": { "$ref": "#/$defs/metadata" } }, "$defs": { "metadata": { "type": "string", "description": "Metadata value" } } }); let schema = parse_tool_input_schema(&input_schema).expect("parse schema"); assert_eq!( serde_json::to_value(schema).expect("serialize schema"), serde_json::json!({ "type": "object", "properties": { "metadata": { "$ref": "#/$defs/metadata" } }, "$defs": { "metadata": { "type": "string" } } }) ); let schema = parse_tool_input_schema_without_compaction(&input_schema).expect("parse schema"); assert_eq!( serde_json::to_value(schema).expect("serialize schema"), serde_json::json!({ "type": "object", "description": "x".repeat(4_500), "properties": { "metadata": { "$ref": "#/$defs/metadata" } }, "$defs": { "metadata": { "type": "string", "description": "Metadata value" } } }) );}#[test]fn parse_large_tool_input_schema_ignores_dropped_metadata_for_budget() { let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "event": { "type": "object", "title": "Calendar event", "properties": { "recurrence": { "type": "object", "examples": [ { "payload": "x".repeat(4_500) } ], "properties": { "pattern": { "type": "string", "title": "Recurrence pattern" } } } } } } })) .expect("parse schema"); assert_eq!( serde_json::to_value(schema).expect("serialize schema"), serde_json::json!({ "type": "object", "properties": { "event": { "type": "object", "properties": { "recurrence": { "type": "object", "properties": { "pattern": { "type": "string" } } } } } } }) );}#[test]fn parse_large_tool_input_schema_stops_after_dropping_root_definitions_when_under_budget() { let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "description": "x".repeat(4_500), "properties": { "event": { "type": "object", "description": "Calendar event", "properties": { "recurrence": { "type": "object", "description": "Recurrence settings", "properties": { "pattern": { "type": "string", "description": "Recurrence pattern" } } } } }, "metadata": { "$ref": "#/$defs/metadata" } }, "$defs": { "metadata": { "type": "object", "description": "metadata object", "properties": many_string_properties(/*count*/ 300) } } })) .expect("parse schema"); assert_eq!( serde_json::to_value(schema).expect("serialize schema"), serde_json::json!({ "type": "object", "properties": { "event": { "type": "object", "properties": { "recurrence": { "type": "object", "properties": { "pattern": { "type": "string" } } } } }, "metadata": {} } }) );}#[test]fn parse_large_tool_input_schema_strips_descriptions_without_removing_description_property() { let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "description": "x".repeat(4_500), "properties": { "description": { "type": "string", "description": "User-facing description value" }, "metadata": { "type": "object", "description": "Metadata object", "properties": { "label": { "type": "string", "description": "Metadata label" } } }, "tags": { "type": "array", "description": "Tag list", "items": { "type": "string", "description": "Tag value" } }, "extras": { "type": "object", "additionalProperties": { "type": "string", "description": "Extra value" } }, "choice": { "description": "Choice value", "anyOf": [ { "type": "string", "description": "String choice" }, { "type": "number", "description": "Number choice" } ] } } })) .expect("parse schema"); assert_eq!( serde_json::to_value(schema).expect("serialize schema"), serde_json::json!({ "type": "object", "properties": { "choice": { "anyOf": [ { "type": "string" }, { "type": "number" } ] }, "description": { "type": "string" }, "extras": { "type": "object", "properties": {}, "additionalProperties": { "type": "string" } }, "metadata": { "type": "object", "properties": { "label": { "type": "string" } } }, "tags": { "type": "array", "items": { "type": "string" } } } }) );}#[test]fn parse_large_tool_input_schema_prunes_compositions_as_last_resort() { for composition_key in super::COMPOSITION_SCHEMA_KEYS { let variants = vec![ serde_json::json!({ "type": "string", "enum": ["first ".repeat(400)] }), serde_json::json!({ "type": "string", "enum": ["second ".repeat(400)] }), serde_json::json!({ "type": "string", "enum": ["third ".repeat(400)] }), ]; let mut choice = serde_json::Map::new(); choice.insert( composition_key.to_string(), serde_json::Value::Array(variants), ); let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "choice": choice } })) .expect("parse schema"); assert_eq!( serde_json::to_value(schema).expect("serialize schema"), serde_json::json!({ "type": "object", "properties": { "choice": {} } }) ); }}#[test]fn parse_large_tool_input_schema_prunes_single_composition_variant_if_still_over_budget() { let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "choice": { "anyOf": [ { "type": "string", "enum": ["x".repeat(4_500)] } ] } } })) .expect("parse schema"); assert_eq!( serde_json::to_value(schema).expect("serialize schema"), serde_json::json!({ "type": "object", "properties": { "choice": {} } }) );}#[test]fn parse_large_tool_input_schema_preserves_object_enum_literal_descriptions() { let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "description": "x".repeat(4_500), "properties": { "choice": { "enum": [ { "description": "first literal", "id": 1 }, { "description": "second literal", "id": 2 } ] } } })) .expect("parse schema"); assert_eq!( serde_json::to_value(schema).expect("serialize schema"), serde_json::json!({ "type": "object", "properties": { "choice": { "type": "string", "enum": [ { "description": "first literal", "id": 1 }, { "description": "second literal", "id": 2 } ] } } }) );}#[test]fn collapse_deep_schema_objects_traverses_schema_children() { let mut schema = serde_json::json!({ "type": "object", "properties": { "object_parent": { "type": "object", "properties": { "complex": { "type": "object", "properties": { "nested": { "type": "object", "properties": { "leaf": { "type": "string" } } } } }, "scalar": { "type": "string" } } }, "array_parent": { "type": "array", "items": { "type": "object", "properties": { "nested": { "type": "object", "properties": { "leaf": { "type": "string" } } } } } }, "map_parent": { "type": "object", "additionalProperties": { "type": "object", "properties": { "nested": { "type": "object", "properties": { "leaf": { "type": "string" } } } } } }, "union_parent": { "anyOf": [ { "type": "object", "properties": { "nested": { "type": "object", "properties": { "leaf": { "type": "string" } } } } }, { "type": "string" } ] } } }); super::collapse_deep_schema_objects(&mut schema, /*depth*/ 0); assert_eq!( schema, serde_json::json!({ "type": "object", "properties": { "object_parent": { "type": "object", "properties": { "complex": { "type": "object", "properties": { "nested": {} } }, "scalar": { "type": "string" } } }, "array_parent": { "type": "array", "items": { "type": "object", "properties": { "nested": {} } } }, "map_parent": { "type": "object", "additionalProperties": { "type": "object", "properties": { "nested": {} } } }, "union_parent": { "anyOf": [ { "type": "object", "properties": { "nested": {} } }, { "type": "string" } ] } } }) );}#[test]fn parse_tool_input_schema_preserves_string_enum_constraints() { // Example schema shape: // { // "type": "object", // "properties": { // "response_length": { "type": "enum", "enum": ["short", "medium", "long"] }, // "kind": { "type": "const", "const": "tagged" }, // "scope": { "type": "enum", "enum": ["one", "two"] } // } // } // // Expected normalization behavior: // - Legacy `type: "enum"` and `type: "const"` inputs are normalized into // the current string-enum representation. let schema = super::parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "response_length": { "type": "enum", "enum": ["short", "medium", "long"] }, "kind": { "type": "const", "const": "tagged" }, "scope": { "type": "enum", "enum": ["one", "two"] } } })) .expect("parse schema"); assert_eq!( schema, JsonSchema::object( BTreeMap::from([ ( "kind".to_string(), JsonSchema::string_enum( vec![serde_json::json!("tagged")], /*description*/ None, ), ), ( "response_length".to_string(), JsonSchema::string_enum( vec![ serde_json::json!("short"), serde_json::json!("medium"), serde_json::json!("long"), ], /*description*/ None, ), ), ( "scope".to_string(), JsonSchema::string_enum( vec![serde_json::json!("one"), serde_json::json!("two")], /*description*/ None, ), ), ]), /*required*/ None, /*additional_properties*/ None ) );}#[test]fn parse_tool_input_schema_preserves_refs_and_prunes_unreachable_defs() { // Example schema shape: // { // "type": "object", // "properties": { "user": { "$ref": "#/$defs/User" } }, // "$defs": { // "User": { "type": "object", "properties": { "name": { "type": "string" } } }, // "Unused": { "type": "string" } // } // } // // Expected normalization behavior: // - Local `$ref` is preserved as a schema hint. // - Reachable `$defs` entries stay attached to the root schema. // - Unreachable `$defs` entries are pruned. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "user": {"$ref": "#/$defs/User"} }, "$defs": { "User": { "type": "object", "properties": { "name": {"type": "string"} } }, "Unused": {"type": "string"} } })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object)), properties: Some(BTreeMap::from([( "user".to_string(), JsonSchema { schema_ref: Some("#/$defs/User".to_string()), ..Default::default() }, )])), defs: Some(BTreeMap::from([( "User".to_string(), JsonSchema::object( BTreeMap::from([( "name".to_string(), JsonSchema::string(/*description*/ None), )]), /*required*/ None, /*additional_properties*/ None, ), )])), ..Default::default() } );}#[test]fn parse_tool_input_schema_preserves_refs_from_properties_named_def_tables() { // Example schema shape: // { // "type": "object", // "properties": { // "$defs": { "$ref": "#/$defs/User" } // }, // "$defs": { "User": { "type": "string" }, "Unused": { "type": "boolean" } } // } // // Expected normalization behavior: // - A property named like the `$defs` keyword is treated as a user field // while traversing `properties`. // - Refs from that property schema still mark root definitions reachable. // - Unreferenced root definitions are still pruned. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "$defs": {"$ref": "#/$defs/User"} }, "$defs": { "User": {"type": "string"}, "Unused": {"type": "boolean"} } })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object)), properties: Some(BTreeMap::from([( "$defs".to_string(), JsonSchema { schema_ref: Some("#/$defs/User".to_string()), ..Default::default() }, )])), defs: Some(BTreeMap::from([( "User".to_string(), JsonSchema::string(/*description*/ None), )])), ..Default::default() } );}#[test]fn parse_tool_input_schema_collects_refs_from_schema_child_keywords() { let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "items_holder": { "type": "array", "items": {"$ref": "#/$defs/Item"} }, "map_holder": { "type": "object", "additionalProperties": {"$ref": "#/$defs/Extra"} }, "choice": { "anyOf": [ {"$ref": "#/$defs/Choice"}, {"type": "string"} ] }, "exclusive_choice": { "oneOf": [ {"$ref": "#/$defs/ExclusiveChoice"}, {"type": "integer"} ] }, "combined": { "allOf": [ {"$ref": "#/$defs/Combined"}, {"type": "object"} ] } }, "$defs": { "Combined": {"type": "object"}, "Choice": {"type": "boolean"}, "ExclusiveChoice": {"type": "null"}, "Extra": {"type": "number"}, "Item": {"type": "string"}, "Unused": {"type": "null"} } })) .expect("parse schema"); assert_eq!( serde_json::to_value(schema).expect("serialize schema"), serde_json::json!({ "type": "object", "properties": { "choice": { "anyOf": [ {"$ref": "#/$defs/Choice"}, {"type": "string"} ] }, "combined": { "allOf": [ {"$ref": "#/$defs/Combined"}, {"type": "object", "properties": {}} ] }, "exclusive_choice": { "oneOf": [ {"$ref": "#/$defs/ExclusiveChoice"}, {"type": "integer"} ] }, "items_holder": { "type": "array", "items": {"$ref": "#/$defs/Item"} }, "map_holder": { "type": "object", "properties": {}, "additionalProperties": {"$ref": "#/$defs/Extra"} } }, "$defs": { "Combined": {"type": "object", "properties": {}}, "Choice": {"type": "boolean"}, "ExclusiveChoice": {"type": "null"}, "Extra": {"type": "number"}, "Item": {"type": "string"} } }) );}#[test]fn parse_tool_input_schema_handles_cyclic_local_refs() { // Example schema shape: // { // "type": "object", // "properties": { "node": { "$ref": "#/$defs/Node" } }, // "$defs": { // "Node": { // "type": "object", // "properties": { "next": { "$ref": "#/$defs/Node" } } // } // } // } // // Expected normalization behavior: // - Recursive refs are preserved. // - Pruning traversal terminates after visiting each local target once. // - Responses API handles this recursive local-ref shape correctly. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "node": {"$ref": "#/$defs/Node"} }, "$defs": { "Node": { "type": "object", "properties": { "next": {"$ref": "#/$defs/Node"} } } } })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object)), properties: Some(BTreeMap::from([( "node".to_string(), JsonSchema { schema_ref: Some("#/$defs/Node".to_string()), ..Default::default() }, )])), defs: Some(BTreeMap::from([( "Node".to_string(), JsonSchema::object( BTreeMap::from([( "next".to_string(), JsonSchema { schema_ref: Some("#/$defs/Node".to_string()), ..Default::default() }, )]), /*required*/ None, /*additional_properties*/ None, ), )])), ..Default::default() } );}#[test]fn parse_tool_input_schema_preserves_legacy_definitions() { // Example schema shape: // { // "type": "object", // "properties": { "user": { "$ref": "#/definitions/User" } }, // "definitions": { // "User": { "type": "object", "properties": { "profile": { "$ref": "#/definitions/Profile" } } }, // "Profile": { "type": "object", "properties": { "name": { "type": "string" } } } // } // } // // Expected normalization behavior: // - Codex preserves legacy `definitions`. // - Reachability follows refs through the legacy definition table. // - Unreachable legacy definition entries are pruned. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "user": {"$ref": "#/definitions/User"} }, "definitions": { "User": { "type": "object", "properties": { "profile": {"$ref": "#/definitions/Profile"} } }, "Profile": { "type": "object", "properties": { "name": {"type": "string"} } }, "Unused": {"type": "string"} } })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object)), properties: Some(BTreeMap::from([( "user".to_string(), JsonSchema { schema_ref: Some("#/definitions/User".to_string()), ..Default::default() }, )])), definitions: Some(BTreeMap::from([ ( "Profile".to_string(), JsonSchema::object( BTreeMap::from([( "name".to_string(), JsonSchema::string(/*description*/ None), )]), /*required*/ None, /*additional_properties*/ None, ), ), ( "User".to_string(), JsonSchema::object( BTreeMap::from([( "profile".to_string(), JsonSchema { schema_ref: Some("#/definitions/Profile".to_string()), ..Default::default() }, )]), /*required*/ None, /*additional_properties*/ None, ), ), ])), ..Default::default() } );}#[test]fn parse_tool_input_schema_preserves_unresolved_and_external_refs() { // Example schema shape: // { // "type": "object", // "properties": { // "missing": { "$ref": "#/$defs/Missing" }, // "remote": { "$ref": "https://example.com/schema.json" } // }, // "$defs": { "Unused": { "type": "string" } } // } // // Expected normalization behavior: // - Unresolved local refs and external refs are preserved. // - Unreachable local definitions are still pruned. // - Responses API handles these refs correctly during downstream validation. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "missing": {"$ref": "#/$defs/Missing"}, "remote": {"$ref": "https://example.com/schema.json"} }, "$defs": { "Unused": {"type": "string"} } })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object)), properties: Some(BTreeMap::from([ ( "missing".to_string(), JsonSchema { schema_ref: Some("#/$defs/Missing".to_string()), ..Default::default() }, ), ( "remote".to_string(), JsonSchema { schema_ref: Some("https://example.com/schema.json".to_string()), ..Default::default() }, ), ])), ..Default::default() } );}#[test]fn parse_tool_input_schema_preserves_nested_defs_ref_parent() { // Example schema shape: // { // "type": "object", // "properties": { "name": { "$ref": "#/$defs/User/properties/name" } }, // "$defs": { // "User": { "type": "object", "properties": { "name": { "type": "string" } } }, // "name": { "type": "string" }, // "Unused": { "type": "boolean" } // } // } // // Expected normalization behavior: // - The nested JSON Pointer ref remains unchanged. // - The parent root definition is retained so the local ref does not dangle. // - Unreferenced root definitions are still pruned. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "name": {"$ref": "#/$defs/User/properties/name"} }, "$defs": { "User": { "type": "object", "properties": { "name": {"type": "string"} } }, "name": {"type": "string"}, "Unused": {"type": "boolean"} } })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object)), properties: Some(BTreeMap::from([( "name".to_string(), JsonSchema { schema_ref: Some("#/$defs/User/properties/name".to_string()), ..Default::default() }, )])), defs: Some(BTreeMap::from([( "User".to_string(), JsonSchema::object( BTreeMap::from([( "name".to_string(), JsonSchema::string(/*description*/ None), )]), /*required*/ None, /*additional_properties*/ None, ), )])), ..Default::default() } );}#[test]fn parse_tool_input_schema_preserves_percent_encoded_definition_refs() { // Example schema shape: // { // "type": "object", // "properties": { // "user": { "$ref": "#/$defs/User%20Name" }, // "profile": { "$ref": "#/%24defs/Profile%7E0Name" } // }, // "$defs": { // "User Name": { "type": "string" }, // "Profile~Name": { "type": "string" }, // "Unused": { "type": "boolean" } // } // } // // Expected normalization behavior: // - URI fragment percent encoding is decoded before JSON Pointer `~` // escaping, per RFC 6901 section 6. // - The original `$ref` strings are preserved, but their definition // targets are recognized as reachable and retained. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "user": {"$ref": "#/$defs/User%20Name"}, "profile": {"$ref": "#/%24defs/Profile%7E0Name"} }, "$defs": { "User Name": {"type": "string"}, "Profile~Name": {"type": "string"}, "Unused": {"type": "boolean"} } })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object)), properties: Some(BTreeMap::from([ ( "profile".to_string(), JsonSchema { schema_ref: Some("#/%24defs/Profile%7E0Name".to_string()), ..Default::default() }, ), ( "user".to_string(), JsonSchema { schema_ref: Some("#/$defs/User%20Name".to_string()), ..Default::default() }, ), ])), defs: Some(BTreeMap::from([ ( "Profile~Name".to_string(), JsonSchema::string(/*description*/ None), ), ( "User Name".to_string(), JsonSchema::string(/*description*/ None), ), ])), ..Default::default() } );}#[test]fn parse_tool_input_schema_drops_malformed_definition_tables() { // Example schema shape: // { // "type": "object", // "properties": { "user": { "$ref": "#/$defs/User" } }, // "$defs": ["not", "an", "object"] // } // // Expected normalization behavior: // - Malformed `$defs` tables are dropped instead of rejecting the schema. // - The unresolved local ref remains visible to the model. let schema = parse_tool_input_schema(&serde_json::json!({ "type": "object", "properties": { "user": {"$ref": "#/$defs/User"} }, "$defs": ["not", "an", "object"] })) .expect("parse schema"); assert_eq!( schema, JsonSchema { schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object)), properties: Some(BTreeMap::from([( "user".to_string(), JsonSchema { schema_ref: Some("#/$defs/User".to_string()), ..Default::default() }, )])), ..Default::default() } );}