refactor: unify optional JSON object payload parsing
This commit is contained in:
+12
-1
@@ -1,8 +1,19 @@
|
||||
"""Helpers for validating common request payload fields."""
|
||||
"""Helpers for validating and reading common request payload fields."""
|
||||
|
||||
from typing import Any, Optional, Tuple
|
||||
|
||||
|
||||
async def read_optional_object_payload(request: Any) -> dict[str, Any]:
|
||||
"""Read an optional JSON object, returning an empty dict on invalid input."""
|
||||
if not getattr(request, "can_read_body", True):
|
||||
return {}
|
||||
try:
|
||||
payload = await request.json()
|
||||
except Exception:
|
||||
return {}
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
|
||||
|
||||
def validate_workflow_payload(
|
||||
workflow: Any,
|
||||
*,
|
||||
|
||||
@@ -4,6 +4,8 @@ import asyncio
|
||||
import threading
|
||||
from functools import wraps
|
||||
|
||||
from ..request_utils import read_optional_object_payload
|
||||
|
||||
|
||||
def register_service_route(
|
||||
context,
|
||||
@@ -165,13 +167,10 @@ def create_route_helpers(web, logger, load_settings, hash_calculation_cancelled)
|
||||
async def get_override_settings_from_request(request):
|
||||
settings = await asyncio.to_thread(load_settings)
|
||||
if request.method == "POST":
|
||||
try:
|
||||
payload = await request.json()
|
||||
if isinstance(payload, dict) and "aria2c_path" in payload:
|
||||
settings = dict(settings)
|
||||
settings["aria2c_path"] = payload.get("aria2c_path", "")
|
||||
except Exception:
|
||||
pass
|
||||
payload = await read_optional_object_payload(request)
|
||||
if "aria2c_path" in payload:
|
||||
settings = dict(settings)
|
||||
settings["aria2c_path"] = payload.get("aria2c_path", "")
|
||||
return settings
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import os
|
||||
from urllib.parse import unquote, urlparse
|
||||
|
||||
from ..request_utils import read_optional_object_payload
|
||||
from ..routes.context import RouteContext
|
||||
|
||||
|
||||
@@ -307,13 +308,8 @@ class DownloadService:
|
||||
|
||||
async def aria2_install(self, request):
|
||||
"""Install aria2 and persist the selected download backend."""
|
||||
try:
|
||||
payload = await request.json()
|
||||
except Exception:
|
||||
payload = {}
|
||||
force = False
|
||||
if isinstance(payload, dict):
|
||||
force = self.to_bool(payload.get("force"), False)
|
||||
payload = await read_optional_object_payload(request)
|
||||
force = self.to_bool(payload.get("force"), False)
|
||||
try:
|
||||
install_result = await self.asyncio.to_thread(
|
||||
self.install_aria2_engine,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from ..request_utils import read_optional_object_payload
|
||||
from ..routes.context import RouteContext
|
||||
|
||||
|
||||
@@ -34,18 +35,9 @@ class MetadataService:
|
||||
def metadata_builder_progress(self):
|
||||
return self.extension.metadata_builder_progress
|
||||
|
||||
async def _read_optional_object_payload(self, request):
|
||||
if not request.can_read_body:
|
||||
return {}
|
||||
try:
|
||||
payload = await request.json()
|
||||
except Exception:
|
||||
return {}
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
|
||||
async def metadata_size_audit(self, request):
|
||||
"""Check local metadata sidecars for stale file-size values."""
|
||||
payload = await self._read_optional_object_payload(request)
|
||||
payload = await read_optional_object_payload(request)
|
||||
|
||||
force_rescan = self.to_bool(payload.get("force_rescan"), True)
|
||||
result = await self.asyncio.to_thread(
|
||||
@@ -64,7 +56,7 @@ class MetadataService:
|
||||
|
||||
async def metadata_build_start(self, request):
|
||||
"""Start building missing local metadata sidecars in the background."""
|
||||
payload = await self._read_optional_object_payload(request)
|
||||
payload = await read_optional_object_payload(request)
|
||||
|
||||
force_rescan = self.to_bool(payload.get("force_rescan"), True)
|
||||
worker_count = self.to_int(payload.get("worker_count"), 0)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
from core.request_utils import read_optional_object_payload
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("payload", "expected"),
|
||||
[
|
||||
({"force": True}, {"force": True}),
|
||||
([], {}),
|
||||
("invalid", {}),
|
||||
(None, {}),
|
||||
],
|
||||
)
|
||||
async def test_read_optional_object_payload_normalizes_json_values(
|
||||
payload, expected
|
||||
):
|
||||
request = SimpleNamespace(
|
||||
can_read_body=True,
|
||||
json=AsyncMock(return_value=payload),
|
||||
)
|
||||
|
||||
assert await read_optional_object_payload(request) == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_optional_object_payload_ignores_json_parse_errors():
|
||||
request = SimpleNamespace(
|
||||
can_read_body=True,
|
||||
json=AsyncMock(side_effect=ValueError("invalid json")),
|
||||
)
|
||||
|
||||
assert await read_optional_object_payload(request) == {}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_optional_object_payload_skips_unreadable_body():
|
||||
request = SimpleNamespace(
|
||||
can_read_body=False,
|
||||
json=AsyncMock(),
|
||||
)
|
||||
|
||||
assert await read_optional_object_payload(request) == {}
|
||||
request.json.assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_optional_object_payload_supports_requests_without_body_flag():
|
||||
request = SimpleNamespace(json=AsyncMock(return_value={"value": 1}))
|
||||
|
||||
assert await read_optional_object_payload(request) == {"value": 1}
|
||||
Reference in New Issue
Block a user