Skip to content

format checkers assigned to validators #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 30, 2023
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
run: timeout 10s poetry run pip --version || rm -rf .venv

- name: Install dependencies
run: poetry install -E rfc3339-validator -E strict-rfc3339 -E isodate
run: poetry install -E rfc3339-validator

- name: Test
env:
Expand Down
130 changes: 19 additions & 111 deletions openapi_schema_validator/_format.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,10 @@
import binascii
from base64 import b64decode
from base64 import b64encode
from datetime import datetime
from typing import Any
from typing import Tuple
from typing import Union
from uuid import UUID

from jsonschema._format import FormatChecker
from jsonschema.exceptions import FormatError

DATETIME_HAS_RFC3339_VALIDATOR = False
DATETIME_HAS_STRICT_RFC3339 = False
DATETIME_HAS_ISODATE = False
DATETIME_RAISES: Tuple[Exception, ...] = ()

try:
import isodate
except ImportError:
pass
else:
DATETIME_HAS_ISODATE = True
DATETIME_RAISES += (ValueError, isodate.ISO8601Error)

try:
from rfc3339_validator import validate_rfc3339
except ImportError:
pass
else:
DATETIME_HAS_RFC3339_VALIDATOR = True
DATETIME_RAISES += (ValueError, TypeError)

try:
import strict_rfc3339
except ImportError:
pass
else:
DATETIME_HAS_STRICT_RFC3339 = True
DATETIME_RAISES += (ValueError, TypeError)


def is_int32(instance: Any) -> bool:
Expand Down Expand Up @@ -65,91 +32,32 @@ def is_binary(instance: Any) -> bool:
def is_byte(instance: Union[str, bytes]) -> bool:
if isinstance(instance, str):
instance = instance.encode()

try:
encoded = b64encode(b64decode(instance))
except TypeError:
return False
else:
return encoded == instance


def is_datetime(instance: str) -> bool:
if not isinstance(instance, (bytes, str)):
return False

if DATETIME_HAS_RFC3339_VALIDATOR:
return bool(validate_rfc3339(instance))

if DATETIME_HAS_STRICT_RFC3339:
return bool(strict_rfc3339.validate_rfc3339(instance))

if DATETIME_HAS_ISODATE:
return bool(isodate.parse_datetime(instance))

return True


def is_date(instance: Any) -> bool:
if not isinstance(instance, (bytes, str)):
if not isinstance(instance, bytes):
return False

if isinstance(instance, bytes):
instance = instance.decode()

return bool(datetime.strptime(instance, "%Y-%m-%d"))
encoded = b64encode(b64decode(instance))
return encoded == instance


def is_uuid(instance: Any) -> bool:
def is_password(instance: Any) -> bool:
if not isinstance(instance, (bytes, str)):
return False

if isinstance(instance, bytes):
instance = instance.decode()

return str(UUID(instance)).lower() == instance.lower()


def is_password(instance: Any) -> bool:
return True


class OASFormatChecker(FormatChecker): # type: ignore

checkers = {
"int32": (is_int32, ()),
"int64": (is_int64, ()),
"float": (is_float, ()),
"double": (is_double, ()),
"byte": (is_byte, (binascii.Error, TypeError)),
"binary": (is_binary, ()),
"date": (is_date, (ValueError,)),
"date-time": (is_datetime, DATETIME_RAISES),
"password": (is_password, ()),
# non standard
"uuid": (is_uuid, (AttributeError, ValueError)),
}

def check(self, instance: Any, format: str) -> Any:
if format not in self.checkers:
raise FormatError(
f"Format checker for {format!r} format not found"
)

func, raises = self.checkers[format]
result, cause = None, None
try:
result = func(instance)
except raises as e: # type: ignore
cause = e

if not result:
raise FormatError(
f"{instance!r} is not a {format!r}",
cause=cause,
)
return result


oas30_format_checker = OASFormatChecker()
oas31_format_checker = oas30_format_checker
oas30_format_checker = FormatChecker()
oas30_format_checker.checks("int32")(is_int32)
oas30_format_checker.checks("int64")(is_int64)
oas30_format_checker.checks("float")(is_float)
oas30_format_checker.checks("double")(is_double)
oas30_format_checker.checks("binary")(is_binary)
oas30_format_checker.checks("byte", (binascii.Error, TypeError))(is_byte)
oas30_format_checker.checks("password")(is_password)

oas31_format_checker = FormatChecker()
oas31_format_checker.checks("int32")(is_int32)
oas31_format_checker.checks("int64")(is_int64)
oas31_format_checker.checks("float")(is_float)
oas31_format_checker.checks("double")(is_double)
oas31_format_checker.checks("password")(is_password)
3 changes: 3 additions & 0 deletions openapi_schema_validator/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from jsonschema.validators import create
from jsonschema.validators import extend

from openapi_schema_validator import _format as oas_format
from openapi_schema_validator import _types as oas_types
from openapi_schema_validator import _validators as oas_validators
from openapi_schema_validator._types import oas31_type_checker
Expand Down Expand Up @@ -55,6 +56,7 @@
"deprecated": oas_validators.not_implemented,
},
type_checker=oas_types.oas30_type_checker,
format_checker=oas_format.oas30_format_checker,
# NOTE: version causes conflict with global jsonschema validator
# See https://github.com/p1c2u/openapi-schema-validator/pull/12
# version="oas30",
Expand Down Expand Up @@ -94,6 +96,7 @@
"example": oas_validators.not_implemented,
},
type_checker=oas31_type_checker,
format_checker=oas_format.oas31_format_checker,
)


Expand Down
12 changes: 0 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,10 @@ strict = true
module = "jsonschema.*"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "isodate"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "rfc3339_validator"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "strict_rfc3339"
ignore_missing_imports = true

[tool.poetry]
name = "openapi-schema-validator"
version = "0.4.1"
Expand All @@ -54,13 +46,9 @@ classifiers = [
python = "^3.7.0"
jsonschema = "^4.0.0"
rfc3339-validator = {version = "*", optional = true}
strict-rfc3339 = {version = "*", optional = true}
isodate = {version = "*", optional = true}

[tool.poetry.extras]
rfc3339-validator = ["rfc3339-validator"]
strict-rfc3339 = ["strict-rfc3339"]
isodate = ["isodate"]

[tool.poetry.dev-dependencies]
black = "^22.0.0"
Expand Down
Loading