Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions src/mock_vws/_services_validators/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,28 @@
from collections.abc import Mapping
from dataclasses import dataclass
from functools import cached_property
from typing import Any, TypeIs
from typing import TypeIs

from beartype import beartype
from pydantic import TypeAdapter

from mock_vws._base64_decoding import decode_base64
from mock_vws._database_matchers import AnyDatabase

type JSONValue = (
bool | int | float | str | list[JSONValue] | dict[str, JSONValue] | None
)
_OPTIONAL_STRING_ADAPTER: TypeAdapter[str | None] = TypeAdapter(
type=str | None,
)


@beartype
def _is_json_object(value: object, /) -> TypeIs[dict[str, Any]]: # pyrefly: ignore [explicit-any]
def _is_json_object(value: object, /) -> TypeIs[dict[str, JSONValue]]:
"""Return whether a decoded JSON value is an object.

JSON object keys are always strings, so a ``dict`` from ``json.loads``
is a ``dict[str, Any]``.
is a ``dict[str, JSONValue]``.
"""
return isinstance(value, dict)

Expand Down Expand Up @@ -66,7 +74,7 @@ class ValidatorContext:
allowed_for_inactive_cloud_project: bool

@cached_property
def request_json(self) -> dict[str, Any]: # pyrefly: ignore [explicit-any]
def request_json(self) -> dict[str, JSONValue]:
"""The request body parsed as a JSON object.

A route's JSON validator runs before any validator which reads this,
Expand Down Expand Up @@ -99,7 +107,10 @@ def decoded_image(self) -> bytes | None:
Raises:
binascii.Error: The image cannot be base64 decoded.
"""
image = self.request_json.get("image")
image = _OPTIONAL_STRING_ADAPTER.validate_python(
self.request_json.get("image"),
strict=True,
)
if image is None:
return None
return decode_base64(encoded_data=image)
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def validate_instance_id_not_empty(*, context: ValidatorContext) -> None:
"""
instance_id = context.request_json["instance_id"]

if instance_id:
if instance_id != "":
return

_LOGGER.warning(msg='The value of "instance_id" is empty.')
Expand Down
14 changes: 12 additions & 2 deletions src/mock_vws/_services_validators/metadata_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from http import HTTPStatus

from beartype import beartype
from pydantic import TypeAdapter

from mock_vws._base64_decoding import decode_base64
from mock_vws._services_validators.context import ValidatorContext
Expand All @@ -14,6 +15,9 @@
)

_LOGGER = logging.getLogger(name=__name__)
_OPTIONAL_METADATA_ADAPTER: TypeAdapter[str | None] = TypeAdapter(
type=str | None,
)


@beartype
Expand All @@ -30,7 +34,10 @@ def validate_metadata_size(*, context: ValidatorContext) -> None:
large.
"""
request_json = context.request_json
application_metadata = request_json.get("application_metadata")
application_metadata = _OPTIONAL_METADATA_ADAPTER.validate_python(
request_json.get("application_metadata"),
strict=True,
)
if application_metadata is None:
return
decoded = decode_base64(encoded_data=application_metadata)
Expand All @@ -55,7 +62,10 @@ def validate_metadata_encoding(*, context: ValidatorContext) -> None:
decoded.
"""
request_json = context.request_json
application_metadata = request_json.get("application_metadata")
application_metadata = _OPTIONAL_METADATA_ADAPTER.validate_python(
request_json.get("application_metadata"),
strict=True,
)

if application_metadata is None:
return
Expand Down
16 changes: 11 additions & 5 deletions src/mock_vws/_services_validators/name_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from http import HTTPStatus

from beartype import beartype
from pydantic import TypeAdapter

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

_MAX_CHARACTER_ORD = 65535
_OPTIONAL_NAME_ADAPTER: TypeAdapter[str | None] = TypeAdapter(type=str | None)
_NAME_ADAPTER: TypeAdapter[str] = TypeAdapter(type=str)


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


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


@beartype
Expand Down
5 changes: 1 addition & 4 deletions src/mock_vws/_services_validators/width_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ def validate_width(*, context: ValidatorContext) -> None:

width = request_json["width"]

width_is_number = isinstance(width, int | float)
width_positive = width_is_number and width > 0

if not width_positive:
if not isinstance(width, int | float) or width <= 0:
_LOGGER.warning(msg="Width is not a positive number.")
raise FailError(status_code=HTTPStatus.BAD_REQUEST)
Loading