157 lines
4.8 KiB
Rust
157 lines
4.8 KiB
Rust
//! Partial JSON Parser Tests
|
|
//!
|
|
//! Tests for the partial JSON parser with allow_partial_strings flag behavior
|
|
|
|
use sglang_router_rs::tool_parser::partial_json::PartialJson;
|
|
|
|
#[test]
|
|
fn test_partial_string_flag_disallows_incomplete_strings() {
|
|
// Test case from the bug report: {"name": "
|
|
// With allow_partial_strings=false, should return {} (stop before incomplete string)
|
|
let parser = PartialJson::new(32, true);
|
|
let input = r#"{"name": ""#;
|
|
|
|
let result = parser.parse_value(input, false);
|
|
assert!(result.is_ok());
|
|
|
|
let (obj, consumed) = result.unwrap();
|
|
|
|
// Should parse just the opening brace and stop at the incomplete string
|
|
assert!(obj.is_object());
|
|
let obj_map = obj.as_object().unwrap();
|
|
|
|
// Should have empty object (stopped before parsing incomplete "name" key)
|
|
assert!(
|
|
obj_map.is_empty() || !obj_map.contains_key("name"),
|
|
"Should not parse incomplete string key, got: {:?}",
|
|
obj_map
|
|
);
|
|
|
|
// Should consume characters up to the incomplete string
|
|
assert!(consumed <= input.len());
|
|
}
|
|
|
|
#[test]
|
|
fn test_partial_string_flag_allows_incomplete_strings() {
|
|
// Test case: {"name": "
|
|
// With allow_partial_strings=true, should parse the incomplete string
|
|
let parser = PartialJson::new(32, true);
|
|
let input = r#"{"name": ""#;
|
|
|
|
let result = parser.parse_value(input, true);
|
|
assert!(result.is_ok());
|
|
|
|
let (obj, consumed) = result.unwrap();
|
|
|
|
// Should parse the object with incomplete string value
|
|
assert!(obj.is_object());
|
|
let obj_map = obj.as_object().unwrap();
|
|
|
|
// With allow_partial_strings=true, should parse "name" key with empty string value
|
|
assert!(
|
|
obj_map.contains_key("name"),
|
|
"Should parse incomplete string with allow_partial_strings=true"
|
|
);
|
|
|
|
assert_eq!(consumed, input.len());
|
|
}
|
|
|
|
#[test]
|
|
fn test_partial_string_flag_complete_json() {
|
|
// Test case: {"name": "test"}
|
|
// Both flags should parse complete JSON the same way
|
|
let input = r#"{"name": "test"}"#;
|
|
|
|
let parser = PartialJson::new(32, true);
|
|
let result1 = parser.parse_value(input, false);
|
|
assert!(result1.is_ok());
|
|
let (obj1, consumed1) = result1.unwrap();
|
|
|
|
let result2 = parser.parse_value(input, true);
|
|
assert!(result2.is_ok());
|
|
let (obj2, consumed2) = result2.unwrap();
|
|
|
|
// Both should parse the same complete JSON
|
|
assert_eq!(obj1, obj2);
|
|
assert_eq!(consumed1, consumed2);
|
|
assert_eq!(consumed1, input.len());
|
|
|
|
// Check the parsed value
|
|
assert!(obj1.is_object());
|
|
let obj_map = obj1.as_object().unwrap();
|
|
assert_eq!(obj_map.get("name").and_then(|v| v.as_str()), Some("test"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_backward_compatibility_default() {
|
|
// Test that default PartialJson still allows partial strings (backward compatible)
|
|
let parser = PartialJson::default();
|
|
let input = r#"{"name": ""#;
|
|
|
|
let result = parser.parse_value(input, true);
|
|
assert!(result.is_ok());
|
|
|
|
let (obj, _) = result.unwrap();
|
|
assert!(obj.is_object());
|
|
|
|
// Default behavior should allow partial strings
|
|
let obj_map = obj.as_object().unwrap();
|
|
assert!(
|
|
obj_map.contains_key("name"),
|
|
"Default should allow partial strings for backward compatibility"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_partial_string_in_nested_object() {
|
|
// Test case: {"tool": {"name": "
|
|
let parser = PartialJson::new(32, true);
|
|
let input = r#"{"tool": {"name": ""#;
|
|
|
|
let result = parser.parse_value(input, false);
|
|
assert!(result.is_ok());
|
|
|
|
let (obj, _) = result.unwrap();
|
|
assert!(obj.is_object());
|
|
|
|
// With allow_partial_strings=false, should stop before incomplete nested string
|
|
let obj_map = obj.as_object().unwrap();
|
|
if let Some(tool) = obj_map.get("tool") {
|
|
if let Some(tool_map) = tool.as_object() {
|
|
assert!(
|
|
!tool_map.contains_key("name")
|
|
|| tool_map.get("name").and_then(|v| v.as_str()).is_none(),
|
|
"Should not parse incomplete nested string"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_bug_fix_exact_scenario() {
|
|
// This test verifies the exact bug scenario from the issue:
|
|
// buffer = "{\"name\": \""
|
|
// flags = Allow.ALL & ~Allow.STR
|
|
// Python returns: Parsed object: {}, consumed length: 10
|
|
|
|
let parser = PartialJson::new(32, true);
|
|
let input = r#"{"name": ""#;
|
|
|
|
let result = parser.parse_value(input, false);
|
|
assert!(result.is_ok());
|
|
|
|
let (obj, consumed) = result.unwrap();
|
|
|
|
// Should return empty object (not {"name": null} or {"name": ""})
|
|
assert!(obj.is_object());
|
|
let obj_map = obj.as_object().unwrap();
|
|
assert!(
|
|
obj_map.is_empty(),
|
|
"Expected empty object, got: {:?}. This matches Python behavior with Allow.ALL & ~Allow.STR",
|
|
obj_map
|
|
);
|
|
|
|
// Should consume all characters (10 bytes)
|
|
assert_eq!(consumed, 10, "Should consume all 10 characters");
|
|
}
|