Skip to content
This repository was archived by the owner on Oct 30, 2026. It is now read-only.

Commit 657b570

Browse files
committed
Type validator request JSON recursively
1 parent cbf8c01 commit 657b570

5 files changed

Lines changed: 41 additions & 17 deletions

File tree

‎src/mock_vws/_services_validators/context.py‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,28 @@
44
from collections.abc import Mapping
55
from dataclasses import dataclass
66
from functools import cached_property
7-
from typing import Any, TypeIs
7+
from typing import TypeIs
88

99
from beartype import beartype
10+
from pydantic import TypeAdapter
1011

1112
from mock_vws._base64_decoding import decode_base64
1213
from mock_vws._database_matchers import AnyDatabase
1314

15+
type JSONValue = (
16+
bool | int | float | str | list[JSONValue] | dict[str, JSONValue] | None
17+
)
18+
_OPTIONAL_STRING_ADAPTER: TypeAdapter[str | None] = TypeAdapter(
19+
type=str | None,
20+
)
21+
1422

1523
@beartype
16-
def _is_json_object(value: object, /) -> TypeIs[dict[str, Any]]: # pyrefly: ignore [explicit-any]
24+
def _is_json_object(value: object, /) -> TypeIs[dict[str, JSONValue]]:
1725
"""Return whether a decoded JSON value is an object.
1826
1927
JSON object keys are always strings, so a ``dict`` from ``json.loads``
20-
is a ``dict[str, Any]``.
28+
is a ``dict[str, JSONValue]``.
2129
"""
2230
return isinstance(value, dict)
2331

@@ -66,7 +74,7 @@ class ValidatorContext:
6674
allowed_for_inactive_cloud_project: bool
6775

6876
@cached_property
69-
def request_json(self) -> dict[str, Any]: # pyrefly: ignore [explicit-any]
77+
def request_json(self) -> dict[str, JSONValue]:
7078
"""The request body parsed as a JSON object.
7179
7280
A route's JSON validator runs before any validator which reads this,
@@ -99,7 +107,10 @@ def decoded_image(self) -> bytes | None:
99107
Raises:
100108
binascii.Error: The image cannot be base64 decoded.
101109
"""
102-
image = self.request_json.get("image")
110+
image = _OPTIONAL_STRING_ADAPTER.validate_python(
111+
self.request_json.get("image"),
112+
strict=True,
113+
)
103114
if image is None:
104115
return None
105116
return decode_base64(encoded_data=image)

‎src/mock_vws/_services_validators/instance_id_validators.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def validate_instance_id_not_empty(*, context: ValidatorContext) -> None:
5050
"""
5151
instance_id = context.request_json["instance_id"]
5252

53-
if instance_id:
53+
if instance_id != "":
5454
return
5555

5656
_LOGGER.warning(msg='The value of "instance_id" is empty.')

‎src/mock_vws/_services_validators/metadata_validators.py‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from http import HTTPStatus
66

77
from beartype import beartype
8+
from pydantic import TypeAdapter
89

910
from mock_vws._base64_decoding import decode_base64
1011
from mock_vws._services_validators.context import ValidatorContext
@@ -14,6 +15,9 @@
1415
)
1516

1617
_LOGGER = logging.getLogger(name=__name__)
18+
_OPTIONAL_METADATA_ADAPTER: TypeAdapter[str | None] = TypeAdapter(
19+
type=str | None,
20+
)
1721

1822

1923
@beartype
@@ -30,7 +34,10 @@ def validate_metadata_size(*, context: ValidatorContext) -> None:
3034
large.
3135
"""
3236
request_json = context.request_json
33-
application_metadata = request_json.get("application_metadata")
37+
application_metadata = _OPTIONAL_METADATA_ADAPTER.validate_python(
38+
request_json.get("application_metadata"),
39+
strict=True,
40+
)
3441
if application_metadata is None:
3542
return
3643
decoded = decode_base64(encoded_data=application_metadata)
@@ -55,7 +62,10 @@ def validate_metadata_encoding(*, context: ValidatorContext) -> None:
5562
decoded.
5663
"""
5764
request_json = context.request_json
58-
application_metadata = request_json.get("application_metadata")
65+
application_metadata = _OPTIONAL_METADATA_ADAPTER.validate_python(
66+
request_json.get("application_metadata"),
67+
strict=True,
68+
)
5969

6070
if application_metadata is None:
6171
return

‎src/mock_vws/_services_validators/name_validators.py‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from http import HTTPStatus
55

66
from beartype import beartype
7+
from pydantic import TypeAdapter
78

89
from mock_vws._services_validators.context import ValidatorContext
910
from mock_vws._services_validators.exceptions import (
@@ -18,6 +19,8 @@
1819
_LOGGER = logging.getLogger(name=__name__)
1920

2021
_MAX_CHARACTER_ORD = 65535
22+
_OPTIONAL_NAME_ADAPTER: TypeAdapter[str | None] = TypeAdapter(type=str | None)
23+
_NAME_ADAPTER: TypeAdapter[str] = TypeAdapter(type=str)
2124

2225

2326
@beartype
@@ -32,9 +35,10 @@ def _given_name(*, context: ValidatorContext) -> str | None:
3235
The value has already been checked to be a string by
3336
:py:func:`validate_name_type`.
3437
"""
35-
request_json = context.request_json
36-
name: str | None = request_json.get("name") # ty: ignore[unsound-assignment]
37-
return name
38+
return _OPTIONAL_NAME_ADAPTER.validate_python(
39+
context.request_json.get("name"),
40+
strict=True,
41+
)
3842

3943

4044
@beartype
@@ -63,8 +67,10 @@ def _new_target_name(*, context: ValidatorContext) -> str:
6367
a request which does not give one, and :py:func:`validate_name_type`
6468
has already rejected one which is not a string.
6569
"""
66-
name: str = context.request_json["name"] # ty: ignore[unsound-assignment]
67-
return name
70+
return _NAME_ADAPTER.validate_python(
71+
context.request_json["name"],
72+
strict=True,
73+
)
6874

6975

7076
@beartype

‎src/mock_vws/_services_validators/width_validators.py‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ def validate_width(*, context: ValidatorContext) -> None:
2727

2828
width = request_json["width"]
2929

30-
width_is_number = isinstance(width, int | float)
31-
width_positive = width_is_number and width > 0
32-
33-
if not width_positive:
30+
if not isinstance(width, int | float) or width <= 0:
3431
_LOGGER.warning(msg="Width is not a positive number.")
3532
raise FailError(status_code=HTTPStatus.BAD_REQUEST)

0 commit comments

Comments
 (0)