Added validate payload error (#12494)

* Added validate payload error

* added logger

* added test case
This commit is contained in:
Jugal D. Bhatt
2025-07-11 04:24:48 +05:30
committed by GitHub
parent b0003bd03c
commit 0691ff8d13
2 changed files with 105 additions and 6 deletions
@@ -5,6 +5,7 @@ import orjson
from fastapi import Request, UploadFile, status
from litellm._logging import verbose_proxy_logger
from litellm.proxy._types import ProxyException
from litellm.types.router import Deployment
@@ -44,8 +45,8 @@ async def _read_request_body(request: Optional[Request]) -> Dict:
else:
try:
parsed_body = orjson.loads(body)
except orjson.JSONDecodeError:
# Fall back to the standard json module which is more forgiving
except orjson.JSONDecodeError as e:
# First try the standard json module which is more forgiving
# First decode bytes to string if needed
body_str = body.decode("utf-8") if isinstance(body, bytes) else body
@@ -61,15 +62,26 @@ async def _read_request_body(request: Optional[Request]) -> Dict:
r"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]", "", body_str
)
parsed_body = json.loads(body_str)
try:
parsed_body = json.loads(body_str)
except json.JSONDecodeError:
# If both orjson and json.loads fail, throw a proper error
verbose_proxy_logger.error(f"Invalid JSON payload received: {str(e)}")
raise ProxyException(
message=f"Invalid JSON payload: {str(e)}",
type="invalid_request_error",
param="request_body",
code=status.HTTP_400_BAD_REQUEST,
)
# Cache the parsed result
_safe_set_request_parsed_body(request=request, parsed_body=parsed_body)
return parsed_body
except (json.JSONDecodeError, orjson.JSONDecodeError):
verbose_proxy_logger.exception("Invalid JSON payload received.")
return {}
except (json.JSONDecodeError, orjson.JSONDecodeError, ProxyException) as e:
# Re-raise ProxyException as-is
verbose_proxy_logger.error(f"Invalid JSON payload received: {str(e)}")
raise
except Exception as e:
# Catch unexpected errors to avoid crashes
verbose_proxy_logger.exception(
@@ -20,6 +20,7 @@ from litellm.proxy.common_utils.http_parsing_utils import (
_safe_set_request_parsed_body,
get_form_data,
)
from litellm.proxy._types import ProxyException
@pytest.mark.asyncio
@@ -150,6 +151,92 @@ async def test_circular_reference_handling():
) # This will pass, showing the cache pollution
@pytest.mark.asyncio
async def test_json_parsing_error_handling():
"""
Test that JSON parsing errors are properly handled and raise ProxyException
with appropriate error messages.
"""
# Test case 1: Trailing comma error
mock_request = MagicMock()
invalid_json_with_trailing_comma = b'''{
"model": "gpt-4o",
"tools": [
{
"type": "mcp",
"server_label": "litellm",
"headers": {
"x-litellm-api-key": "Bearer sk-1234",
}
}
],
"input": "Run available tools"
}'''
mock_request.body = AsyncMock(return_value=invalid_json_with_trailing_comma)
mock_request.headers = {"content-type": "application/json"}
mock_request.scope = {}
# Should raise ProxyException for trailing comma
with pytest.raises(ProxyException) as exc_info:
await _read_request_body(mock_request)
assert exc_info.value.code == "400"
assert "Invalid JSON payload" in exc_info.value.message
assert "trailing comma" in exc_info.value.message
# Test case 2: Unquoted property name error
mock_request2 = MagicMock()
invalid_json_unquoted_property = b'''{
"model": "gpt-4o",
"tools": [
{
type: "mcp",
"server_label": "litellm"
}
],
"input": "Run available tools"
}'''
mock_request2.body = AsyncMock(return_value=invalid_json_unquoted_property)
mock_request2.headers = {"content-type": "application/json"}
mock_request2.scope = {}
# Should raise ProxyException for unquoted property
with pytest.raises(ProxyException) as exc_info2:
await _read_request_body(mock_request2)
assert exc_info2.value.code == "400"
assert "Invalid JSON payload" in exc_info2.value.message
# Test case 3: Valid JSON should work normally
mock_request3 = MagicMock()
valid_json = b'''{
"model": "gpt-4o",
"tools": [
{
"type": "mcp",
"server_label": "litellm",
"headers": {
"x-litellm-api-key": "Bearer sk-1234"
}
}
],
"input": "Run available tools"
}'''
mock_request3.body = AsyncMock(return_value=valid_json)
mock_request3.headers = {"content-type": "application/json"}
mock_request3.scope = {}
# Should parse successfully
result = await _read_request_body(mock_request3)
assert result["model"] == "gpt-4o"
assert result["input"] == "Run available tools"
assert len(result["tools"]) == 1
assert result["tools"][0]["type"] == "mcp"
@pytest.mark.asyncio
async def test_get_form_data():
"""