Skip to content

Commit f88cd69

Browse files
gnuletikPierre Chiquet
authored and
Pierre Chiquet
committed
OpenAPI: Warn user about duplicate operationIds. (encode#7207)
1 parent 6a852b3 commit f88cd69

File tree

3 files changed

+77
-0
lines changed

3 files changed

+77
-0
lines changed

rest_framework/schemas/openapi.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,32 @@ def get_info(self):
3434

3535
return info
3636

37+
def check_duplicate_operation_id(self, paths):
38+
ids = {}
39+
for route in paths:
40+
for method in paths[route]:
41+
if 'operationId' not in paths[route][method]:
42+
continue
43+
operation_id = paths[route][method]['operationId']
44+
if operation_id in ids:
45+
warnings.warn(
46+
'You have a duplicated operationId in your OpenAPI schema: {operation_id}\n'
47+
'\tRoute: {route1}, Method: {method1}\n'
48+
'\tRoute: {route2}, Method: {method2}\n'
49+
'\tAn operationId has to be unique accros your schema. Your schema may not work in other tools.'
50+
.format(
51+
route1=ids[operation_id]['route'],
52+
method1=ids[operation_id]['method'],
53+
route2=route,
54+
method2=method,
55+
operation_id=operation_id
56+
)
57+
)
58+
ids[operation_id] = {
59+
'route': route,
60+
'method': method
61+
}
62+
3763
def get_schema(self, request=None, public=False):
3864
"""
3965
Generate a OpenAPI schema.
@@ -57,6 +83,8 @@ def get_schema(self, request=None, public=False):
5783
paths.setdefault(path, {})
5884
paths[path][method.lower()] = operation
5985

86+
self.check_duplicate_operation_id(paths)
87+
6088
# Compile final schema.
6189
schema = {
6290
'openapi': '3.0.2',

tests/schemas/test_openapi.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import uuid
2+
import warnings
23

34
import pytest
45
from django.conf.urls import url
@@ -659,6 +660,24 @@ def test_repeat_operation_ids(self):
659660
assert schema_str.count("newExample") == 1
660661
assert schema_str.count("oldExample") == 1
661662

663+
def test_duplicate_operation_id(self):
664+
patterns = [
665+
url(r'^duplicate1/?$', views.ExampleOperationIdDuplicate1.as_view()),
666+
url(r'^duplicate2/?$', views.ExampleOperationIdDuplicate2.as_view()),
667+
]
668+
669+
generator = SchemaGenerator(patterns=patterns)
670+
request = create_request('/')
671+
672+
with warnings.catch_warnings(record=True) as w:
673+
warnings.simplefilter('always')
674+
generator.get_schema(request=request)
675+
676+
assert len(w) == 1
677+
assert issubclass(w[-1].category, UserWarning)
678+
print(str(w[-1].message))
679+
assert 'You have a duplicated operationId' in str(w[-1].message)
680+
662681
def test_serializer_datefield(self):
663682
path = '/'
664683
method = 'GET'

tests/schemas/views.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
DecimalValidator, MaxLengthValidator, MaxValueValidator,
55
MinLengthValidator, MinValueValidator, RegexValidator
66
)
7+
from django.db import models
78

89
from rest_framework import generics, permissions, serializers
910
from rest_framework.decorators import action
@@ -137,3 +138,32 @@ def get(self, *args, **kwargs):
137138
url='http://localhost', uuid=uuid.uuid4(), ip4='127.0.0.1', ip6='::1',
138139
ip='192.168.1.1')
139140
return Response(serializer.data)
141+
142+
143+
# Serializer with model.
144+
class OpenAPIExample(models.Model):
145+
first_name = models.CharField(max_length=30)
146+
147+
148+
class ExampleSerializerModel(serializers.Serializer):
149+
date = serializers.DateField()
150+
datetime = serializers.DateTimeField()
151+
hstore = serializers.HStoreField()
152+
uuid_field = serializers.UUIDField(default=uuid.uuid4)
153+
154+
class Meta:
155+
model = OpenAPIExample
156+
157+
158+
class ExampleOperationIdDuplicate1(generics.GenericAPIView):
159+
serializer_class = ExampleSerializerModel
160+
161+
def get(self, *args, **kwargs):
162+
pass
163+
164+
165+
class ExampleOperationIdDuplicate2(generics.GenericAPIView):
166+
serializer_class = ExampleSerializerModel
167+
168+
def get(self, *args, **kwargs):
169+
pass

0 commit comments

Comments
 (0)