mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-05-28 18:23:05 +03:00
OpenAPI: Warn user about duplicate operationIds. (#7207)
This commit is contained in:
parent
5b16a17242
commit
797518af6d
|
@ -34,6 +34,32 @@ class SchemaGenerator(BaseSchemaGenerator):
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
def check_duplicate_operation_id(self, paths):
|
||||||
|
ids = {}
|
||||||
|
for route in paths:
|
||||||
|
for method in paths[route]:
|
||||||
|
if 'operationId' not in paths[route][method]:
|
||||||
|
continue
|
||||||
|
operation_id = paths[route][method]['operationId']
|
||||||
|
if operation_id in ids:
|
||||||
|
warnings.warn(
|
||||||
|
'You have a duplicated operationId in your OpenAPI schema: {operation_id}\n'
|
||||||
|
'\tRoute: {route1}, Method: {method1}\n'
|
||||||
|
'\tRoute: {route2}, Method: {method2}\n'
|
||||||
|
'\tAn operationId has to be unique accros your schema. Your schema may not work in other tools.'
|
||||||
|
.format(
|
||||||
|
route1=ids[operation_id]['route'],
|
||||||
|
method1=ids[operation_id]['method'],
|
||||||
|
route2=route,
|
||||||
|
method2=method,
|
||||||
|
operation_id=operation_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ids[operation_id] = {
|
||||||
|
'route': route,
|
||||||
|
'method': method
|
||||||
|
}
|
||||||
|
|
||||||
def get_schema(self, request=None, public=False):
|
def get_schema(self, request=None, public=False):
|
||||||
"""
|
"""
|
||||||
Generate a OpenAPI schema.
|
Generate a OpenAPI schema.
|
||||||
|
@ -57,6 +83,8 @@ class SchemaGenerator(BaseSchemaGenerator):
|
||||||
paths.setdefault(path, {})
|
paths.setdefault(path, {})
|
||||||
paths[path][method.lower()] = operation
|
paths[path][method.lower()] = operation
|
||||||
|
|
||||||
|
self.check_duplicate_operation_id(paths)
|
||||||
|
|
||||||
# Compile final schema.
|
# Compile final schema.
|
||||||
schema = {
|
schema = {
|
||||||
'openapi': '3.0.2',
|
'openapi': '3.0.2',
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import uuid
|
import uuid
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
@ -659,6 +660,24 @@ class TestOperationIntrospection(TestCase):
|
||||||
assert schema_str.count("newExample") == 1
|
assert schema_str.count("newExample") == 1
|
||||||
assert schema_str.count("oldExample") == 1
|
assert schema_str.count("oldExample") == 1
|
||||||
|
|
||||||
|
def test_duplicate_operation_id(self):
|
||||||
|
patterns = [
|
||||||
|
url(r'^duplicate1/?$', views.ExampleOperationIdDuplicate1.as_view()),
|
||||||
|
url(r'^duplicate2/?$', views.ExampleOperationIdDuplicate2.as_view()),
|
||||||
|
]
|
||||||
|
|
||||||
|
generator = SchemaGenerator(patterns=patterns)
|
||||||
|
request = create_request('/')
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter('always')
|
||||||
|
generator.get_schema(request=request)
|
||||||
|
|
||||||
|
assert len(w) == 1
|
||||||
|
assert issubclass(w[-1].category, UserWarning)
|
||||||
|
print(str(w[-1].message))
|
||||||
|
assert 'You have a duplicated operationId' in str(w[-1].message)
|
||||||
|
|
||||||
def test_serializer_datefield(self):
|
def test_serializer_datefield(self):
|
||||||
path = '/'
|
path = '/'
|
||||||
method = 'GET'
|
method = 'GET'
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.core.validators import (
|
||||||
DecimalValidator, MaxLengthValidator, MaxValueValidator,
|
DecimalValidator, MaxLengthValidator, MaxValueValidator,
|
||||||
MinLengthValidator, MinValueValidator, RegexValidator
|
MinLengthValidator, MinValueValidator, RegexValidator
|
||||||
)
|
)
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
from rest_framework import generics, permissions, serializers
|
from rest_framework import generics, permissions, serializers
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
@ -137,3 +138,32 @@ class ExampleValidatedAPIView(generics.GenericAPIView):
|
||||||
url='http://localhost', uuid=uuid.uuid4(), ip4='127.0.0.1', ip6='::1',
|
url='http://localhost', uuid=uuid.uuid4(), ip4='127.0.0.1', ip6='::1',
|
||||||
ip='192.168.1.1')
|
ip='192.168.1.1')
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
# Serializer with model.
|
||||||
|
class OpenAPIExample(models.Model):
|
||||||
|
first_name = models.CharField(max_length=30)
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleSerializerModel(serializers.Serializer):
|
||||||
|
date = serializers.DateField()
|
||||||
|
datetime = serializers.DateTimeField()
|
||||||
|
hstore = serializers.HStoreField()
|
||||||
|
uuid_field = serializers.UUIDField(default=uuid.uuid4)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OpenAPIExample
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleOperationIdDuplicate1(generics.GenericAPIView):
|
||||||
|
serializer_class = ExampleSerializerModel
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleOperationIdDuplicate2(generics.GenericAPIView):
|
||||||
|
serializer_class = ExampleSerializerModel
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
Loading…
Reference in New Issue
Block a user