From 17dab4c54ea54075de387df4e686bb0e1389d220 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Fri, 16 May 2025 14:48:08 -0700 Subject: [PATCH] Enforce globally unique type names (#4377) (cherry picked from commit e0bb763f32de59a78cd5b1ab3953d7ee08ee20f2) --- compiler/src/steps/validate-model.ts | 60 +++++++++++++++++++++++++++ output/schema/schema-serverless.json | 4 +- output/schema/schema.json | 14 +++---- specification/watcher/_types/Input.ts | 3 -- 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/compiler/src/steps/validate-model.ts b/compiler/src/steps/validate-model.ts index 1dd14656a6..70f3e123cb 100644 --- a/compiler/src/steps/validate-model.ts +++ b/compiler/src/steps/validate-model.ts @@ -178,6 +178,9 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma return ep.request != null && ep.response != null } + // Check that all type names are unique + validateUniqueTypeNames(apiModel, modelError) + // Validate all endpoints. We start by those that are ready for validation so that transitive validation of common // data types is associated with these endpoints and their errors are not filtered out in the error report. apiModel.endpoints.filter(ep => readyForValidation(ep)).forEach(validateEndpoint) @@ -204,6 +207,63 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma // ----------------------------------------------------------------------------------------------- + /** + * Validates that all type names in the model are unique + */ + function validateUniqueTypeNames (apiModel: model.Model, modelError: (msg: string) => void): void { + const existingDuplicates: Record = { + Action: ['indices.modify_data_stream', 'indices.update_aliases', 'watcher._types'], + Actions: ['ilm._types', 'security.put_privileges', 'watcher._types'], + ComponentTemplate: ['cat.component_templates', 'cluster._types'], + Context: ['_global.get_script_context', '_global.search._types', 'nodes._types'], + DatabaseConfigurationMetadata: ['ingest.get_geoip_database', 'ingest.get_ip_location_database'], + Datafeed: ['ml._types', 'xpack.usage'], + Destination: ['_global.reindex', 'transform._types'], + Feature: ['features._types', 'indices.get', 'xpack.info'], + Features: ['indices.get', 'xpack.info'], + Filter: ['_global.termvectors', 'ml._types'], + IndexingPressure: ['cluster.stats', 'indices._types', 'nodes._types'], + IndexingPressureMemory: ['cluster.stats', 'indices._types', 'nodes._types'], + Ingest: ['ingest._types', 'nodes._types'], + MigrationFeature: ['migration.get_feature_upgrade_status', 'migration.post_feature_upgrade'], + Operation: ['_global.mget', '_global.mtermvectors'], + Phase: ['ilm._types', 'xpack.usage'], + Phases: ['ilm._types', 'xpack.usage'], + Pipeline: ['ingest._types', 'logstash._types'], + Policy: ['enrich._types', 'ilm._types', 'slm._types'], + RequestItem: ['_global.msearch', '_global.msearch_template'], + ResponseItem: ['_global.bulk', '_global.mget', '_global.msearch'], + RoleMapping: ['security._types', 'xpack.usage'], + RuntimeFieldTypes: ['cluster.stats', 'xpack.usage'], + ShardsStats: ['indices.field_usage_stats', 'snapshot._types'], + ShardStats: ['ccr._types', 'indices.stats'], + Source: ['_global.reindex', 'transform._types'], + Token: ['_global.termvectors', 'security.authenticate', 'security.create_service_token', 'security.enroll_kibana'] + } + + // collect namespaces for each type name + const typeNames = new Map() + for (const type of apiModel.types) { + const name = type.name.name + if (name !== 'Request' && name !== 'Response' && name !== 'ResponseBase') { + const namespaces = typeNames.get(name) ?? [] + namespaces.push(type.name.namespace) + typeNames.set(name, namespaces) + } + } + + // check for duplicates + for (const [name, namespaces] of typeNames) { + if (namespaces.length > 1) { + const allowedDuplicates = existingDuplicates[name] ?? [] + const hasUnexpectedDuplicate = namespaces.some(ns => !allowedDuplicates.includes(ns)) + if (hasUnexpectedDuplicate) { + modelError(`${name} is present in multiple namespaces: ${namespaces.sort().join(' and ')}`) + } + } + } + } + /** * Validate an endpoint */ diff --git a/output/schema/schema-serverless.json b/output/schema/schema-serverless.json index 6f5cbcaef2..29e7c75e94 100644 --- a/output/schema/schema-serverless.json +++ b/output/schema/schema-serverless.json @@ -108467,7 +108467,7 @@ "name": "InputType", "namespace": "watcher._types" }, - "specLocation": "watcher/_types/Input.ts#L100-L104" + "specLocation": "watcher/_types/Input.ts#L97-L101" }, { "kind": "enum", @@ -108583,7 +108583,7 @@ "name": "ResponseContentType", "namespace": "watcher._types" }, - "specLocation": "watcher/_types/Input.ts#L106-L110" + "specLocation": "watcher/_types/Input.ts#L103-L107" }, { "codegenNames": [ diff --git a/output/schema/schema.json b/output/schema/schema.json index 51e4acb10b..911f1eb35d 100644 --- a/output/schema/schema.json +++ b/output/schema/schema.json @@ -236987,7 +236987,7 @@ } } ], - "specLocation": "watcher/_types/Input.ts#L90-L98", + "specLocation": "watcher/_types/Input.ts#L87-L95", "variants": { "kind": "container" } @@ -237009,7 +237009,7 @@ "name": "InputType", "namespace": "watcher._types" }, - "specLocation": "watcher/_types/Input.ts#L100-L104" + "specLocation": "watcher/_types/Input.ts#L97-L101" }, { "kind": "interface", @@ -237590,7 +237590,7 @@ "name": "ResponseContentType", "namespace": "watcher._types" }, - "specLocation": "watcher/_types/Input.ts#L106-L110" + "specLocation": "watcher/_types/Input.ts#L103-L107" }, { "kind": "interface", @@ -237908,7 +237908,7 @@ } } ], - "specLocation": "watcher/_types/Input.ts#L112-L116" + "specLocation": "watcher/_types/Input.ts#L109-L113" }, { "kind": "interface", @@ -237929,7 +237929,7 @@ } } ], - "specLocation": "watcher/_types/Input.ts#L147-L149" + "specLocation": "watcher/_types/Input.ts#L144-L146" }, { "kind": "interface", @@ -238008,7 +238008,7 @@ } } ], - "specLocation": "watcher/_types/Input.ts#L118-L125" + "specLocation": "watcher/_types/Input.ts#L115-L122" }, { "kind": "interface", @@ -238084,7 +238084,7 @@ } } ], - "specLocation": "watcher/_types/Input.ts#L128-L145" + "specLocation": "watcher/_types/Input.ts#L125-L142" }, { "kind": "interface", diff --git a/specification/watcher/_types/Input.ts b/specification/watcher/_types/Input.ts index 7504ab6c9e..aa1ecba3b7 100644 --- a/specification/watcher/_types/Input.ts +++ b/specification/watcher/_types/Input.ts @@ -84,9 +84,6 @@ export class HttpInputRequestDefinition { scheme?: ConnectionScheme url?: string } - -export class Input {} - /** * @variants container */