diff --git a/crates/Cargo.lock b/crates/Cargo.lock index dc593214d5..dbc73769f5 100644 --- a/crates/Cargo.lock +++ b/crates/Cargo.lock @@ -40,6 +40,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -277,7 +283,7 @@ dependencies = [ "opentelemetry-jaeger-propagator", "opentelemetry-otlp", "opentelemetry_sdk", - "reqwest 0.13.4", + "reqwest", "rustls", "serde", "serde_json", @@ -408,6 +414,12 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + [[package]] name = "deadpool" version = "0.12.3" @@ -514,9 +526,9 @@ dependencies = [ [[package]] name = "fancy-regex" -version = "0.14.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" dependencies = [ "bit-set", "regex-automata", @@ -537,9 +549,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fluent-uri" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" dependencies = [ "borrow-or-share", "ref-cast", @@ -558,6 +570,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -692,9 +710,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -747,7 +767,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -756,6 +776,17 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + [[package]] name = "heck" version = "0.5.0" @@ -1188,29 +1219,58 @@ dependencies = [ [[package]] name = "jsonschema" -version = "0.29.1" +version = "0.49.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161c33c3ec738cfea3288c5c53dfcdb32fd4fc2954de86ea06f71b5a1a40bfcd" +checksum = "257549c093d8f3d043337ba0d507e8eeace2447dfae3f0ee37eb0f57d3df7655" dependencies = [ "ahash", - "base64", "bytecount", + "data-encoding", "email_address", "fancy-regex", "fraction", + "getrandom 0.3.4", "idna", "itoa", + "jsonschema-regex", + "jsonschema-value", "num-cmp", - "once_cell", + "num-traits", "percent-encoding", "referencing", - "regex-syntax", - "reqwest 0.12.28", + "regex", + "reqwest", + "rustls", "serde", "serde_json", + "strum", + "unicode-general-category", "uuid-simd", ] +[[package]] +name = "jsonschema-regex" +version = "0.49.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "474790e948498099d61ec85c10dc72d459d61298b7975eda7fb163af1934b134" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "jsonschema-value" +version = "0.49.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8212315eb8e0bc44959d1af653cd5ec515771a3cdca966cfc0a10999bff144b9" +dependencies = [ + "ahash", + "bytecount", + "fraction", + "num-cmp", + "num-traits", + "serde_json", +] + [[package]] name = "lalrpop-util" version = "0.20.2" @@ -1314,6 +1374,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + [[package]] name = "mime" version = "0.3.17" @@ -1712,7 +1778,7 @@ dependencies = [ "bytes", "http", "opentelemetry", - "reqwest 0.13.4", + "reqwest", ] [[package]] @@ -1736,7 +1802,7 @@ dependencies = [ "opentelemetry-proto", "opentelemetry_sdk", "prost", - "reqwest 0.13.4", + "reqwest", "thiserror 2.0.18", "tokio", "tonic", @@ -2224,13 +2290,16 @@ dependencies = [ [[package]] name = "referencing" -version = "0.29.1" +version = "0.49.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a64b3a635fad9000648b4d8a59c8710c523ab61a23d392a7d91d47683f5adc" +checksum = "2bf1a74e036be2546c0c81cdfad6b2def6a25bf31c4e34a468037058d3bd05c6" dependencies = [ "ahash", "fluent-uri", - "once_cell", + "getrandom 0.3.4", + "hashbrown 0.17.1", + "itoa", + "micromap", "parking_lot", "percent-encoding", "serde_json", @@ -2250,9 +2319,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -2265,40 +2334,6 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "reqwest" version = "0.13.4" @@ -2596,7 +2631,7 @@ checksum = "931a20b0da02350676e3d6d3c9028d58eaa448cf42a866712eec5845a505421e" dependencies = [ "cfg_aliases", "httpdate", - "reqwest 0.13.4", + "reqwest", "sentry-backtrace", "sentry-contexts", "sentry-core", @@ -2829,6 +2864,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" @@ -3347,6 +3403,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -3475,7 +3537,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" dependencies = [ "outref", - "uuid", "vsimd", ] diff --git a/crates/coglet/Cargo.toml b/crates/coglet/Cargo.toml index 32502e9ec3..19c50d44e8 100644 --- a/crates/coglet/Cargo.toml +++ b/crates/coglet/Cargo.toml @@ -62,7 +62,7 @@ thiserror.workspace = true anyhow.workspace = true # Input validation -jsonschema = "0.29" +jsonschema = { version = "0.49.4", default-features = false, features = ["resolve-http", "resolve-file", "tls-ring"] } # Concurrent collections dashmap = "6" diff --git a/crates/coglet/src/input_validation.rs b/crates/coglet/src/input_validation.rs index d924ea0b0f..bc67d13717 100644 --- a/crates/coglet/src/input_validation.rs +++ b/crates/coglet/src/input_validation.rs @@ -209,7 +209,7 @@ impl InputValidator { } // Type/constraint errors on specific fields - let path = error.instance_path.to_string(); + let path = error.instance_path().to_string(); let field = path.trim_start_matches('/'); let field_name = if field.is_empty() { "__root__".to_string() @@ -438,6 +438,74 @@ mod tests { assert!(InputValidator::from_openapi_schema(&schema).is_none()); } + #[test] + fn validates_input_constraints() { + let cases = [ + ( + json!({"type": "integer", "minimum": 1, "maximum": 10}), + json!(10), + json!(11), + ), + ( + json!({"type": "string", "minLength": 2, "maxLength": 4}), + json!("abcd"), + json!("abcde"), + ), + ( + json!({"type": "string", "pattern": "^[a-z]+$"}), + json!("abc"), + json!("abc1"), + ), + ( + json!({"anyOf": [{"type": "integer"}, {"type": "string"}]}), + json!(42), + json!(true), + ), + ]; + + for (property, valid, invalid) in cases { + let schema = make_schema(json!({ + "type": "object", + "properties": {"value": property}, + "required": ["value"] + })); + let validator = InputValidator::from_openapi_schema(&schema).unwrap(); + assert!(validator.validate(&json!({"value": valid})).is_ok()); + let errors = validator.validate(&json!({"value": invalid})).unwrap_err(); + assert_eq!(errors.len(), 1); + assert_eq!(errors[0].field, "value"); + assert_eq!(errors[0].error_type, "value_error"); + } + } + + #[test] + fn reports_missing_fields_and_array_item_errors() { + let schema = make_schema(json!({ + "type": "object", + "properties": { + "prompt": {"type": "string"}, + "seed": {"type": "integer"}, + "items": {"type": "array", "items": {"type": "integer"}} + }, + "required": ["prompt", "seed"] + })); + let validator = InputValidator::from_openapi_schema(&schema).unwrap(); + let errors = validator + .validate(&json!({"items": [1, "invalid"]})) + .unwrap_err(); + assert_eq!(errors.len(), 3); + for field in ["prompt", "seed"] { + let error = errors.iter().find(|error| error.field == field).unwrap(); + assert_eq!(error.msg, "Field required"); + assert_eq!(error.error_type, "value_error.missing"); + } + let error = errors + .iter() + .find(|error| error.field == "items/1") + .unwrap(); + assert_eq!(error.error_type, "value_error"); + } + #[test] fn resolves_ref_for_choices() { let schema = json!({