@@ -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+ const 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