diff --git a/drf_spectacular/plumbing.py b/drf_spectacular/plumbing.py index 249083e0..497b4d53 100644 --- a/drf_spectacular/plumbing.py +++ b/drf_spectacular/plumbing.py @@ -525,12 +525,18 @@ def safe_ref(schema: _SchemaType) -> _SchemaType: def append_meta(schema: _SchemaType, meta: _SchemaType) -> _SchemaType: if spectacular_settings.OAS_VERSION.startswith('3.1'): + schema = schema.copy() + meta = meta.copy() + schema_nullable = meta.pop('nullable', None) meta_nullable = schema.pop('nullable', None) if schema_nullable or meta_nullable: if 'type' in schema: - schema['type'] = [schema['type'], 'null'] + if isinstance(schema['type'], str): + schema['type'] = [schema['type'], 'null'] + else: + schema['type'] = [*schema['type'], 'null'] elif '$ref' in schema: schema = {'oneOf': [schema, {'type': 'null'}]} elif len(schema) == 1 and 'oneOf' in schema: diff --git a/tests/test_extend_schema.py b/tests/test_extend_schema.py index a9c64fe0..f142eef2 100644 --- a/tests/test_extend_schema.py +++ b/tests/test_extend_schema.py @@ -232,6 +232,54 @@ def test_extend_schema(no_warnings): ) +@mock.patch('drf_spectacular.settings.spectacular_settings.OAS_VERSION', '3.1.0') +def test_extend_schema_field_with_dict_oas_3_1(no_warnings): + @extend_schema_field({"type": "string"}) + class CustomField(serializers.CharField): + pass + + class XSerializer(serializers.Serializer): + field1 = CustomField(read_only=True, allow_null=True) + field2 = CustomField(read_only=True, allow_null=True) + field3 = CustomField(read_only=True, allow_null=True) + + @extend_schema(request=XSerializer, responses=XSerializer) + @api_view(['POST']) + def view_func(request, format=None): + pass # pragma: no cover + + schema = generate_schema('x', view_function=view_func) + + assert schema['components']['schemas']['X']['properties'] == { + 'field1': {'readOnly': True, 'type': ['string', 'null']}, + 'field2': {'readOnly': True, 'type': ['string', 'null']}, + 'field3': {'readOnly': True, 'type': ['string', 'null']} + } + + +@mock.patch('drf_spectacular.settings.spectacular_settings.OAS_VERSION', '3.1.0') +def test_extend_schema_field_with_schema_as_oas_3_1(no_warnings): + @extend_schema_field({'type': ['string', 'integer']}) + class CustomField(serializers.CharField): + pass + + class XSerializer(serializers.Serializer): + field1 = CustomField(read_only=True, allow_null=True) + field2 = CustomField(read_only=True, allow_null=True) + + @extend_schema(request=XSerializer, responses=XSerializer) + @api_view(['POST']) + def view_func(request, format=None): + pass # pragma: no cover + + schema = generate_schema('x', view_function=view_func) + + assert schema['components']['schemas']['X']['properties'] == { + 'field1': {'readOnly': True, 'type': ['string', 'integer', 'null']}, + 'field2': {'readOnly': True, 'type': ['string', 'integer', 'null']}, + } + + def test_layered_extend_schema_on_view_and_method_with_meta(no_warnings): class XSerializer(serializers.Serializer): field = serializers.IntegerField()