66
77from openapi_spec_validator .exceptions import (
88 ParameterDuplicateError , ExtraParametersError , UnresolvableParameterError ,
9- OpenAPIValidationError
9+ OpenAPIValidationError , DuplicateOperationIDError ,
1010)
1111from openapi_spec_validator .decorators import ValidationErrorWrapper
1212from openapi_spec_validator .factories import Draft4ExtendedValidatorFactory
@@ -157,8 +157,10 @@ def _iter_value_errors(self, schema, value):
157157
158158class PathsValidator (object ):
159159
160- def __init__ (self , dereferencer ):
160+ def __init__ (self , dereferencer , operation_ids_registry = None ):
161161 self .dereferencer = dereferencer
162+ self .operation_ids_registry = [] if operation_ids_registry is None \
163+ else operation_ids_registry
162164
163165 @wraps_errors
164166 def iter_errors (self , paths ):
@@ -168,13 +170,17 @@ def iter_errors(self, paths):
168170 yield err
169171
170172 def _iter_path_errors (self , url , path_item ):
171- return PathValidator (self .dereferencer ).iter_errors (url , path_item )
173+ return PathValidator (
174+ self .dereferencer , self .operation_ids_registry ).iter_errors (
175+ url , path_item )
172176
173177
174178class PathValidator (object ):
175179
176- def __init__ (self , dereferencer ):
180+ def __init__ (self , dereferencer , operation_ids_registry = None ):
177181 self .dereferencer = dereferencer
182+ self .operation_ids_registry = [] if operation_ids_registry is None \
183+ else operation_ids_registry
178184
179185 @wraps_errors
180186 def iter_errors (self , url , path_item ):
@@ -184,7 +190,9 @@ def iter_errors(self, url, path_item):
184190 yield err
185191
186192 def _iter_path_item_errors (self , url , path_item ):
187- return PathItemValidator (self .dereferencer ).iter_errors (url , path_item )
193+ return PathItemValidator (
194+ self .dereferencer , self .operation_ids_registry ).iter_errors (
195+ url , path_item )
188196
189197
190198class PathItemValidator (object ):
@@ -193,8 +201,10 @@ class PathItemValidator(object):
193201 'get' , 'put' , 'post' , 'delete' , 'options' , 'head' , 'patch' , 'trace' ,
194202 ]
195203
196- def __init__ (self , dereferencer ):
204+ def __init__ (self , dereferencer , operation_ids_registry = None ):
197205 self .dereferencer = dereferencer
206+ self .operation_ids_registry = [] if operation_ids_registry is None \
207+ else operation_ids_registry
198208
199209 @wraps_errors
200210 def iter_errors (self , url , path_item ):
@@ -213,23 +223,33 @@ def iter_errors(self, url, path_item):
213223 yield err
214224
215225 def _iter_operation_errors (self , url , name , operation , path_parameters ):
216- return OperationValidator (self .dereferencer ).iter_errors (
217- url , name , operation , path_parameters )
226+ return OperationValidator (
227+ self .dereferencer , self .operation_ids_registry ).iter_errors (
228+ url , name , operation , path_parameters )
218229
219230 def _iter_parameters_errors (self , parameters ):
220231 return ParametersValidator (self .dereferencer ).iter_errors (parameters )
221232
222233
223234class OperationValidator (object ):
224235
225- def __init__ (self , dereferencer ):
236+ def __init__ (self , dereferencer , seen_ids = None ):
226237 self .dereferencer = dereferencer
238+ self .seen_ids = [] if seen_ids is None else seen_ids
227239
228240 @wraps_errors
229241 def iter_errors (self , url , name , operation , path_parameters = None ):
230242 path_parameters = path_parameters or []
231243 operation_deref = self .dereferencer .dereference (operation )
232244
245+ operation_id = operation_deref .get ('operationId' )
246+ if operation_id is not None and operation_id in self .seen_ids :
247+ yield DuplicateOperationIDError (
248+ "Operation ID '{0}' for '{1}' in '{2}' is not unique" .format (
249+ operation_id , name , url )
250+ )
251+ self .seen_ids .append (operation_id )
252+
233253 parameters = operation_deref .get ('parameters' , [])
234254 for err in self ._iter_parameters_errors (parameters ):
235255 yield err
0 commit comments