From 4a9f091a40b79193c04ba2c1c0c606c637d43918 Mon Sep 17 00:00:00 2001 From: alexpetul Date: Wed, 22 May 2024 22:28:58 +0400 Subject: [PATCH] Add schema validation in CustomJSONField serializer Add schema validation in CustomJSONField serializer --- .../rest_framework/serializers.py | 9 +++++++ tests/test_openapi_schema.py | 14 +++++++---- tests/test_serializer_field.py | 24 ++++++++++++++++--- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/django_custom_jsonfield/rest_framework/serializers.py b/django_custom_jsonfield/rest_framework/serializers.py index 94b6111..873f492 100644 --- a/django_custom_jsonfield/rest_framework/serializers.py +++ b/django_custom_jsonfield/rest_framework/serializers.py @@ -1,16 +1,25 @@ import jsonschema from django.utils.translation import gettext_lazy as _ +from jsonschema import validators from rest_framework import serializers class CustomJSONField(serializers.JSONField): default_error_messages = { "invalid_data": _("Value does not match the JSON schema."), + "invalid_schema": _("Invalid JSON schema."), } def __init__(self, schema: dict, **kwargs): self.schema = schema super().__init__(**kwargs) + + validator = validators.validator_for(self.schema) + try: + validator.check_schema(self.schema) + except jsonschema.exceptions.SchemaError: + self.fail("invalid_schema") + self.validators.append(self._validate_data) def _validate_data(self, value): diff --git a/tests/test_openapi_schema.py b/tests/test_openapi_schema.py index a1266c3..8536ed3 100644 --- a/tests/test_openapi_schema.py +++ b/tests/test_openapi_schema.py @@ -1,5 +1,5 @@ from typing import Any -from unittest.mock import Mock +from unittest.mock import Mock, patch import pytest @@ -76,12 +76,16 @@ def test_map_serializer_field_const(schema: dict, expected: Any): @pytest.mark.parametrize( "schema", [ - { - "type": "test", - }, + {"type": "unknown_type"}, ], ) -def test_map_serializer_field_invalid_schema(schema: dict): +@patch( + "django_custom_jsonfield.rest_framework.serializers.jsonschema.validators.validator_for", + new=Mock(), +) +def test_map_serializer_field_fallback(schema: Any): + """Test fallback to string.""" + json_field = CustomJSONField(schema=schema) extension = CustomJSONFieldSerializerExtension(json_field) data = extension.map_serializer_field(Mock(), "response") diff --git a/tests/test_serializer_field.py b/tests/test_serializer_field.py index e54d774..f810f0b 100644 --- a/tests/test_serializer_field.py +++ b/tests/test_serializer_field.py @@ -1,6 +1,5 @@ import pytest -from rest_framework import serializers -from rest_framework.exceptions import ErrorDetail +from rest_framework import exceptions, serializers from django_custom_jsonfield.rest_framework.serializers import CustomJSONField @@ -27,10 +26,29 @@ class FakeSerializer(serializers.Serializer): expected_errors = { "json_field": [ - ErrorDetail( + exceptions.ErrorDetail( string="Value does not match the JSON schema.", code="invalid_data", ), ], } assert serializer.errors == expected_errors + + +@pytest.mark.parametrize( + "schema", + [ + {"minItems": "1"}, + {"properties": 1}, + {"pattern": "*invalid.regex"}, + ], +) +def test_map_serializer_field_invalid_schema(schema: dict): + """Test serializer raises an exception if JSON schema is invalid.""" + + with pytest.raises(exceptions.ValidationError) as e: + CustomJSONField(schema=schema) + + assert isinstance(e.value, exceptions.ValidationError) + assert e.value.detail[0] == "Invalid JSON schema." + assert e.value.detail[0].code == "invalid_schema"