@@ -178,6 +178,9 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
178178 return ep . request != null && ep . response != null
179179 }
180180
181+ // Check that all type names are unique
182+ validateUniqueTypeNames ( apiModel , modelError )
183+
181184 // Validate all endpoints. We start by those that are ready for validation so that transitive validation of common
182185 // data types is associated with these endpoints and their errors are not filtered out in the error report.
183186 apiModel . endpoints . filter ( ep => readyForValidation ( ep ) ) . forEach ( validateEndpoint )
@@ -204,6 +207,63 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
204207
205208 // -----------------------------------------------------------------------------------------------
206209
210+ /**
211+ * Validates that all type names in the model are unique
212+ */
213+ function validateUniqueTypeNames ( apiModel : model . Model , modelError : ( msg : string ) => void ) : void {
214+ const existingDuplicates : Record < string , string [ ] > = {
215+ "Action" : [ "indices.modify_data_stream" , "indices.update_aliases" , "watcher._types" ] ,
216+ "Actions" : [ "ilm._types" , "security.put_privileges" , "watcher._types" ] ,
217+ "ComponentTemplate" : [ "cat.component_templates" , "cluster._types" ] ,
218+ "Context" : [ "_global.get_script_context" , "_global.search._types" , "nodes._types" ] ,
219+ "DatabaseConfigurationMetadata" : [ "ingest.get_geoip_database" , "ingest.get_ip_location_database" ] ,
220+ "Datafeed" : [ "ml._types" , "xpack.usage" ] ,
221+ "Destination" : [ "_global.reindex" , "transform._types" ] ,
222+ "Feature" : [ "features._types" , "indices.get" , "xpack.info" ] ,
223+ "Features" : [ "indices.get" , "xpack.info" ] ,
224+ "Filter" : [ "_global.termvectors" , "ml._types" ] ,
225+ "IndexingPressure" : [ "cluster.stats" , "indices._types" , "nodes._types" ] ,
226+ "IndexingPressureMemory" : [ "cluster.stats" , "indices._types" , "nodes._types" ] ,
227+ "Ingest" : [ "ingest._types" , "nodes._types" ] ,
228+ "MigrationFeature" : [ "migration.get_feature_upgrade_status" , "migration.post_feature_upgrade" ] ,
229+ "Operation" : [ "_global.mget" , "_global.mtermvectors" ] ,
230+ "Phase" : [ "ilm._types" , "xpack.usage" ] ,
231+ "Phases" : [ "ilm._types" , "xpack.usage" ] ,
232+ "Pipeline" : [ "ingest._types" , "logstash._types" ] ,
233+ "Policy" : [ "enrich._types" , "ilm._types" , "slm._types" ] ,
234+ "RequestItem" : [ "_global.msearch" , "_global.msearch_template" ] ,
235+ "ResponseItem" : [ "_global.bulk" , "_global.mget" , "_global.msearch" ] ,
236+ "RoleMapping" : [ "security._types" , "xpack.usage" ] ,
237+ "RuntimeFieldTypes" : [ "cluster.stats" , "xpack.usage" ] ,
238+ "ShardsStats" : [ "indices.field_usage_stats" , "snapshot._types" ] ,
239+ "ShardStats" : [ "ccr._types" , "indices.stats" ] ,
240+ "Source" : [ "_global.reindex" , "transform._types" ] ,
241+ "Token" : [ "_global.termvectors" , "security.authenticate" , "security.create_service_token" , "security.enroll_kibana" ] ,
242+ }
243+
244+ // collect namespaces for each type name
245+ const typeNames = new Map < string , string [ ] > ( )
246+ for ( const type of apiModel . types ) {
247+ let name = type . name . name
248+ if ( name !== 'Request' && name !== 'Response' && name !== 'ResponseBase' ) {
249+ const namespaces = typeNames . get ( name ) ?? [ ]
250+ namespaces . push ( type . name . namespace )
251+ typeNames . set ( name , namespaces )
252+ }
253+ }
254+
255+ // check for duplicates
256+ for ( const [ name , namespaces ] of typeNames ) {
257+ if ( namespaces . length > 1 ) {
258+ const allowedDuplicates = existingDuplicates [ name ] ?? [ ]
259+ const hasUnexpectedDuplicate = namespaces . some ( ns => ! allowedDuplicates . includes ( ns ) )
260+ if ( hasUnexpectedDuplicate ) {
261+ modelError ( `${ name } is present in multiple namespaces: ${ namespaces . sort ( ) . join ( ' and ' ) } ` )
262+ }
263+ }
264+ }
265+ }
266+
207267 /**
208268 * Validate an endpoint
209269 */
0 commit comments