From 450c1021426123c82595b4328911dd38994993ed Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Wed, 26 Nov 2025 17:36:42 +0400 Subject: [PATCH 1/9] Remove reAddAvailability function --- compiler/src/compiler.ts | 4 +--- compiler/src/model/build-model.ts | 18 ------------------ 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/compiler/src/compiler.ts b/compiler/src/compiler.ts index e3ff58f39a..4d08e38da9 100644 --- a/compiler/src/compiler.ts +++ b/compiler/src/compiler.ts @@ -23,8 +23,7 @@ import stringify from 'safe-stable-stringify' import { Model } from './model/metamodel' import { compileEndpoints, - compileSpecification, - reAddAvailability + compileSpecification } from './model/build-model' import buildJsonSpec, { JsonSpec } from './model/json-spec' import { ValidationErrors } from './validation-errors' @@ -58,7 +57,6 @@ export default class Compiler { this.jsonSpec = buildJsonSpec() const endpoints = compileEndpoints() this.model = compileSpecification(endpoints, this.specsFolder, this.outputFolder) - this.model = reAddAvailability(this.model) // resync availability information based on json spec if typescript has none. return this } diff --git a/compiler/src/model/build-model.ts b/compiler/src/model/build-model.ts index 838ceb9567..8849f4ef33 100644 --- a/compiler/src/model/build-model.ts +++ b/compiler/src/model/build-model.ts @@ -59,24 +59,6 @@ import { const jsonSpec = buildJsonSpec() -export function reAddAvailability (model: model.Model): model.Model { - for (const [api, spec] of jsonSpec.entries()) { - for (const endpoint of model.endpoints) { - if (endpoint.name === api) { - if ((spec.stability != null || spec.visibility != null) && (endpoint.availability.stack === undefined && endpoint.availability.serverless === undefined)) { - endpoint.availability = { - stack: { - stability: spec.stability, - visibility: spec.visibility - } - } - } - } - } - } - return model -} - export function compileEndpoints (): Record { // Create endpoints and merge them with // the recorded mappings if present. From a105a280144adb0b0be2cf71160c713575825133 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Wed, 26 Nov 2025 17:37:12 +0400 Subject: [PATCH 2/9] Remove jsonSpec as a global variable This makes it easier to spot functions that use it. --- compiler/src/compiler.ts | 4 ++-- compiler/src/model/build-model.ts | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/src/compiler.ts b/compiler/src/compiler.ts index 4d08e38da9..9d382c1fb9 100644 --- a/compiler/src/compiler.ts +++ b/compiler/src/compiler.ts @@ -55,8 +55,8 @@ export default class Compiler { generateModel (): this { this.jsonSpec = buildJsonSpec() - const endpoints = compileEndpoints() - this.model = compileSpecification(endpoints, this.specsFolder, this.outputFolder) + const endpoints = compileEndpoints(this.jsonSpec) + this.model = compileSpecification(this.jsonSpec, endpoints, this.specsFolder, this.outputFolder) return this } diff --git a/compiler/src/model/build-model.ts b/compiler/src/model/build-model.ts index 8849f4ef33..c11d0e1b8a 100644 --- a/compiler/src/model/build-model.ts +++ b/compiler/src/model/build-model.ts @@ -32,7 +32,7 @@ import { TypeAliasDeclaration } from 'ts-morph' import * as model from './metamodel' -import buildJsonSpec from './json-spec' +import { JsonSpec } from './json-spec' import { assert, customTypes, @@ -57,9 +57,7 @@ import { mediaTypeToStringArray } from './utils' -const jsonSpec = buildJsonSpec() - -export function compileEndpoints (): Record { +export function compileEndpoints (jsonSpec: Map): Record { // Create endpoints and merge them with // the recorded mappings if present. const map = {} @@ -92,7 +90,7 @@ export function compileEndpoints (): Record { return map } -export function compileSpecification (endpointMappings: Record, specsFolder: string, outputFolder: string): model.Model { +export function compileSpecification (jsonSpec: Map, endpointMappings: Record, specsFolder: string, outputFolder: string): model.Model { const tsConfigFilePath = join(specsFolder, 'tsconfig.json') const project = new Project({ tsConfigFilePath }) From ae546971158a0caf064822679d85d3c5500fd6b9 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Wed, 26 Nov 2025 17:40:23 +0400 Subject: [PATCH 3/9] Stop reading endpoints from jsonSpec --- compiler/src/compiler.ts | 2 +- compiler/src/model/build-model.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/compiler.ts b/compiler/src/compiler.ts index 9d382c1fb9..972dff61f9 100644 --- a/compiler/src/compiler.ts +++ b/compiler/src/compiler.ts @@ -56,7 +56,7 @@ export default class Compiler { generateModel (): this { this.jsonSpec = buildJsonSpec() const endpoints = compileEndpoints(this.jsonSpec) - this.model = compileSpecification(this.jsonSpec, endpoints, this.specsFolder, this.outputFolder) + this.model = compileSpecification(endpoints, this.specsFolder, this.outputFolder) return this } diff --git a/compiler/src/model/build-model.ts b/compiler/src/model/build-model.ts index c11d0e1b8a..a44d3bbacf 100644 --- a/compiler/src/model/build-model.ts +++ b/compiler/src/model/build-model.ts @@ -90,7 +90,7 @@ export function compileEndpoints (jsonSpec: Map): Record, endpointMappings: Record, specsFolder: string, outputFolder: string): model.Model { +export function compileSpecification (endpointMappings: Record, specsFolder: string, outputFolder: string): model.Model { const tsConfigFilePath = join(specsFolder, 'tsconfig.json') const project = new Project({ tsConfigFilePath }) @@ -131,8 +131,8 @@ export function compileSpecification (jsonSpec: Map, endpointM definedButNeverUsed.join('\n'), { encoding: 'utf8', flag: 'w' } ) - for (const api of jsonSpec.keys()) { - model.endpoints.push(endpointMappings[api]) + for (const endpoint of Object.values(endpointMappings)) { + model.endpoints.push(endpoint) } // Visit all class, interface, enum and type alias definitions From d3cdec5608cd96cc503b37eb1b412c99858e0c13 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Wed, 10 Dec 2025 11:08:28 +0400 Subject: [PATCH 4/9] Stop reading docUrl, docTag, extDocUrl, requestBodyRequired and availability from rest-api-spec --- compiler/src/model/build-model.ts | 14 +++----------- compiler/src/model/json-spec.ts | 7 ------- output/schema/schema.json | 3 ++- specification/_doc_ids/table.csv | 2 +- specification/ml/validate/MlValidateJobRequest.ts | 1 + 5 files changed, 7 insertions(+), 20 deletions(-) diff --git a/compiler/src/model/build-model.ts b/compiler/src/model/build-model.ts index a44d3bbacf..dca24fcd38 100644 --- a/compiler/src/model/build-model.ts +++ b/compiler/src/model/build-model.ts @@ -65,15 +65,9 @@ export function compileEndpoints (jsonSpec: Map): Record { return { @@ -83,9 +77,7 @@ export function compileEndpoints (jsonSpec: Map): Record { diff --git a/output/schema/schema.json b/output/schema/schema.json index 9452a30a10..7e9702836b 100644 --- a/output/schema/schema.json +++ b/output/schema/schema.json @@ -16127,6 +16127,7 @@ } }, "description": "Validate an anomaly detection job.", + "docId": "ml-jobs", "docTag": "ml anomaly", "docUrl": "https://www.elastic.co/guide/en/machine-learning/current/ml-jobs.html", "name": "ml.validate", @@ -226056,7 +226057,7 @@ }, "path": [], "query": [], - "specLocation": "ml/validate/MlValidateJobRequest.ts#L27-L55" + "specLocation": "ml/validate/MlValidateJobRequest.ts#L27-L56" }, { "kind": "response", diff --git a/specification/_doc_ids/table.csv b/specification/_doc_ids/table.csv index 8e381e5f71..24dfc5f3ba 100644 --- a/specification/_doc_ids/table.csv +++ b/specification/_doc_ids/table.csv @@ -484,7 +484,7 @@ ml-get-memory,https://www.elastic.co/docs/api/doc/elasticsearch/operation/operat ml-get-overall-buckets,https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-ml-get-overall-buckets,https://www.elastic.co/guide/en/elasticsearch/reference/8.18/ml-get-overall-buckets.html, ml-get-record,https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-ml-get-records,https://www.elastic.co/guide/en/elasticsearch/reference/8.18/ml-get-record.html, ml-get-snapshot,https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-ml-get-model-snapshots,https://www.elastic.co/guide/en/elasticsearch/reference/8.18/ml-get-snapshot.html, -ml-jobs,https://www.elastic.co/docs/explore-analyze/machine-learning/anomaly-detection/ml-ad-run-jobs,, +ml-jobs,https://www.elastic.co/guide/en/machine-learning/current/ml-jobs.html,, ml-model-snapshots,https://www.elastic.co/docs/explore-analyze/machine-learning/anomaly-detection/ml-ad-run-jobs#ml-ad-model-snapshots,, ml-open-job,https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-ml-open-job,https://www.elastic.co/guide/en/elasticsearch/reference/8.18/ml-open-job.html, ml-post-calendar-event,https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-ml-post-calendar-events,https://www.elastic.co/guide/en/elasticsearch/reference/8.18/ml-post-calendar-event.html, diff --git a/specification/ml/validate/MlValidateJobRequest.ts b/specification/ml/validate/MlValidateJobRequest.ts index d020d52461..2e2d531556 100644 --- a/specification/ml/validate/MlValidateJobRequest.ts +++ b/specification/ml/validate/MlValidateJobRequest.ts @@ -30,6 +30,7 @@ import { ModelPlotConfig } from '@ml/_types/ModelPlot' * @rest_spec_name ml.validate * @availability stack since=6.3.0 stability=stable visibility=private * @availability serverless stability=stable visibility=private + * @doc_id ml-jobs * @doc_tag ml anomaly */ export interface Request extends RequestBase { From a23b6bc0808f8a754916774baac59c51bdcf5107 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Mon, 8 Dec 2025 14:24:43 +0400 Subject: [PATCH 5/9] Stop reading description and urls from rest-api-spec --- compiler/src/index.ts | 2 - compiler/src/model/build-model.ts | 14 +++---- compiler/src/steps/add-description.ts | 56 --------------------------- 3 files changed, 6 insertions(+), 66 deletions(-) delete mode 100644 compiler/src/steps/add-description.ts diff --git a/compiler/src/index.ts b/compiler/src/index.ts index d9337825c4..604a75f898 100644 --- a/compiler/src/index.ts +++ b/compiler/src/index.ts @@ -23,7 +23,6 @@ import { argv } from 'zx' import Compiler from './compiler' import validateRestSpec from './steps/validate-rest-spec' import addInfo from './steps/add-info' -import addDescription from './steps/add-description' import validateModel from './steps/validate-model' import readDefinitionValidation from './steps/read-definition-validation' import addDeprecation from './steps/add-deprecation' @@ -74,7 +73,6 @@ compiler .step(addDeprecation) .step(readDefinitionValidation) .step(validateRestSpec) - .step(addDescription) .step(validateModel) .step(addExamples) .write() diff --git a/compiler/src/model/build-model.ts b/compiler/src/model/build-model.ts index dca24fcd38..aaf6cce1a1 100644 --- a/compiler/src/model/build-model.ts +++ b/compiler/src/model/build-model.ts @@ -64,18 +64,12 @@ export function compileEndpoints (jsonSpec: Map): Record { - return { - path: path.path, - methods: path.methods, - ...(path.deprecated != null && { deprecation: path.deprecated }) - } - }) + urls: [] } map[api].availability = {} } @@ -197,6 +191,10 @@ function compileClassOrInterfaceDeclaration (declaration: ClassDeclaration | Int throw new Error(`Cannot find url template for ${namespace}, very likely the specification folder does not follow the rest-api-spec`) } + if (type.description) { + mapping.description = type.description || '' + } + let pathMember: Node | null = null let bodyProperties: model.Property[] = [] let bodyValue: model.ValueOf | null = null diff --git a/compiler/src/steps/add-description.ts b/compiler/src/steps/add-description.ts deleted file mode 100644 index ab817db9af..0000000000 --- a/compiler/src/steps/add-description.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import assert from 'assert' -import * as model from '../model/metamodel' -import { JsonSpec } from '../model/json-spec' - -/** - * Adds the description from the rest-api-spec to every endpoint, path and query property - * if not already present (i.e. missing in the TypeScript definition) - */ -export default async function addDescription (model: model.Model, jsonSpec: Map): Promise { - for (const endpoint of model.endpoints) { - if (endpoint.request == null) continue - const requestDefinition = getDefinition(endpoint.request) - const spec = jsonSpec.get(endpoint.name) - assert(spec, `Can't find the json spec for ${endpoint.name}`) - - if (spec.documentation.description != null) { - requestDefinition.description = requestDefinition.description ?? spec.documentation.description - } - - // An API endpoint is defined by an endpoint object (paths and http methods) and a request - // type (parameters and structure). - endpoint.description = requestDefinition.description ?? spec.documentation.description - } - - return model - - function getDefinition (request: model.TypeName): model.Request { - for (const type of model.types) { - if (type.kind === 'request') { - if (type.name.name === request.name && type.name.namespace === request.namespace) { - return type - } - } - } - throw new Error(`Can't find the request definiton for ${request.namespace}.${request.name}`) - } -} From a31c509911dd7904177ac3ee5fafb172889f8504 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Mon, 8 Dec 2025 15:02:56 +0400 Subject: [PATCH 6/9] Stop validating rest-api-spec --- compiler/src/steps/validate-rest-spec.ts | 215 ----------------------- 1 file changed, 215 deletions(-) delete mode 100644 compiler/src/steps/validate-rest-spec.ts diff --git a/compiler/src/steps/validate-rest-spec.ts b/compiler/src/steps/validate-rest-spec.ts deleted file mode 100644 index ba9e6a024d..0000000000 --- a/compiler/src/steps/validate-rest-spec.ts +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import assert from 'assert' -import * as model from '../model/metamodel' -import { JsonSpec } from '../model/json-spec' -import { ValidationErrors } from '../validation-errors' -import { deepEqual } from '../model/utils' - -// This code can be simplified once https://github.com/tc39/proposal-set-methods is available - -enum Body { - noBody = 0, - yesBody = 1 -} - -const LOG = 'ALL' // default: ALL - -/** - * Validates the model against the rest-api-spec. - * It verifies is the model has exactly the same path and query parameters, - * furthermore it verifies if the body is required or not. - * If a validation fails, it will log a warning. - */ -export default async function validateRestSpec (model: model.Model, jsonSpec: Map, errors: ValidationErrors): Promise { - for (const endpoint of model.endpoints) { - if (endpoint.request == null) continue - const requestDefinition = getDefinition(endpoint.request) - const requestProperties = getProperties(requestDefinition) - if (endpoint.request.name === LOG || LOG === 'ALL') { - const spec = jsonSpec.get(endpoint.name) - assert(spec, `Can't find the json spec for ${endpoint.name}`) - - // Check URL paths and methods - if (spec.url.paths.length !== endpoint.urls.length) { - errors.addEndpointError(endpoint.name, 'request', `${endpoint.request.name}: different number of urls in the json spec`) - } else { - for (const modelUrl of endpoint.urls) { - // URL path - const restSpecUrl = spec.url.paths.find(path => path.path === modelUrl.path) - if (restSpecUrl == null) { - errors.addEndpointError(endpoint.name, 'request', `${endpoint.request.name}: url path '${modelUrl.path}' not found in the json spec`) - } else { - // URL methods - if (!deepEqual([...restSpecUrl.methods].sort(), [...modelUrl.methods].sort())) { - errors.addEndpointError(endpoint.name, 'request', `${modelUrl.path}: different http methods in the json spec`) - } - - // Deprecation. - if ((restSpecUrl.deprecated != null) !== (modelUrl.deprecation != null)) { - errors.addEndpointError(endpoint.name, 'request', `${endpoint.request.name}: different deprecation in the json spec`) - } - } - } - } - - // Check url parts - const urlParts = Array.from(new Set(spec.url.paths - .filter(path => path.parts != null) - .flatMap(path => { - assert(path.parts != null) - return Object.keys(path.parts) - }) - )) - const pathProperties = requestProperties.path.map(property => property.name) - // are all the parameters in the request definition present in the json spec? - for (const name of pathProperties) { - if (!urlParts.includes(name)) { - errors.addEndpointError(endpoint.name, 'request', `${endpoint.request.name}: path parameter '${name}' does not exist in the json spec`) - } - } - - // are all the parameters in the json spec present in the request definition? - for (const name of urlParts) { - if (!pathProperties.includes(name)) { - errors.addEndpointError(endpoint.name, 'request', `${endpoint.request.name}: missing json spec path parameter '${name}'`) - } - } - - // are all path parameters properly required or optional? - let urlPartsRequired = new Set(urlParts) - // A part is considered required if it is included in - // every path for the API endpoint. - for (const path of spec.url.paths) { - if (path.parts == null) { - // No parts means that all path parameters are optional! - urlPartsRequired = new Set() - break - } - urlPartsRequired = new Set([...Object.keys(path.parts)].filter((x) => urlPartsRequired.has(x))) - } - - // transform [{name: ..., required: ...}] -> {name: {required: ...}} - const pathPropertyMap: Record = requestProperties.path.reduce((prev, prop) => ({ ...prev, [prop.name]: prop }), {}) - for (const name of pathProperties) { - // okay to skip if it's not included since this scenario - // is covered above with a different error. - if (!urlParts.includes(name)) { - continue - } - // Find the mismatches between the specs - if (urlPartsRequired.has(name) && !pathPropertyMap[name].required) { - errors.addEndpointError(endpoint.name, 'request', `${endpoint.request.name}: path parameter '${name}' is required in the json spec`) - } else if (!urlPartsRequired.has(name) && pathPropertyMap[name].required) { - errors.addEndpointError(endpoint.name, 'request', `${endpoint.request.name}: path parameter '${name}' is optional in the json spec`) - } - } - - // fleet API are deliberately undocumented in rest-api-spec) - if (spec.params != null && !endpoint.name.startsWith('fleet.')) { - const params = Object.keys(spec.params) - const queryProperties = requestProperties.query.map(property => property.name) - // are all the parameters in the request definition present in the json spec? - for (const name of queryProperties) { - if (!params.includes(name)) { - errors.addEndpointError(endpoint.name, 'request', `${endpoint.request.name}: query parameter '${name}' does not exist in the json spec`) - } - } - - // are all the parameters in the json spec present in the request definition? - for (const name of params) { - if (!queryProperties.includes(name)) { - errors.addEndpointError(endpoint.name, 'request', `${endpoint.request.name}: missing json spec query parameter '${name}'`) - } - } - } - - if (requestProperties.body === Body.yesBody && spec.body == null) { - errors.addEndpointError(endpoint.name, 'request', `${endpoint.request.name}: should not have a body`) - } - - if (requestProperties.body === Body.noBody && spec.body != null && spec.body.required === true) { - errors.addEndpointError(endpoint.name, 'request', `${endpoint.request.name}: should have a body definition`) - } - - if (spec.body != null && spec.body.required === true && spec.body.required !== endpoint.requestBodyRequired) { - errors.addEndpointError(endpoint.name, 'request', ': should not be an optional body definition') - } - } - } - - return model - - function getDefinition (name: model.TypeName): model.Request | model.Interface { - for (const type of model.types) { - if (type.kind === 'request' || type.kind === 'interface') { - if (type.name.name === name.name && type.name.namespace === name.namespace) { - return type - } - } - } - throw new Error(`Can't find the request definiton for ${name.namespace}.${name.name}`) - } - - // recursively gets the properties from the current and inherited classes - function getProperties (definition: model.Request | model.Interface): { path: model.Property[], query: model.Property[], body: Body } { - const path: model.Property[] = [] - const query: model.Property[] = [] - let body: Body = Body.noBody - - if (definition.kind === 'request') { - if (definition.path.length > 0) { - path.push(...definition.path) - } - - if (definition.query.length > 0) { - query.push(...definition.query) - } - - if (definition.body.kind !== 'no_body') { - body = Body.yesBody - } - - if (definition.attachedBehaviors != null) { - for (const attachedBehavior of definition.attachedBehaviors) { - const type_ = getDefinition({ - namespace: '_spec_utils', - name: attachedBehavior - }) - if ( - type_.kind === 'interface' && - // allowing CommonQueryParameters too generates many errors - attachedBehavior === 'CommonCatQueryParameters' - ) { - for (const prop of type_.properties) { - query.push(prop) - } - } - } - } - } else { - if (definition.properties.length > 0) { - query.push(...definition.properties) - } - } - - return { path, query, body } - } -} From 512d772dbb84d94fb69f28ade56c2232c99a0d20 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Mon, 8 Dec 2025 15:09:18 +0400 Subject: [PATCH 7/9] Stop using jsonSpec in compilation steps --- compiler/src/compiler.ts | 4 +- compiler/src/index.ts | 2 - compiler/src/model/json-spec.ts | 77 ------------------- compiler/src/steps/add-deprecation.ts | 3 +- compiler/src/steps/add-examples.ts | 5 +- compiler/src/steps/add-info.ts | 3 +- .../src/steps/read-definition-validation.ts | 3 +- compiler/src/steps/validate-model.ts | 3 +- output/schema/validation-errors.json | 16 +--- 9 files changed, 8 insertions(+), 108 deletions(-) delete mode 100644 compiler/src/model/json-spec.ts diff --git a/compiler/src/compiler.ts b/compiler/src/compiler.ts index 972dff61f9..3ee61de996 100644 --- a/compiler/src/compiler.ts +++ b/compiler/src/compiler.ts @@ -28,7 +28,7 @@ import { import buildJsonSpec, { JsonSpec } from './model/json-spec' import { ValidationErrors } from './validation-errors' -type StepFunction = (model: Model, restSpec: Map, errors: ValidationErrors) => Promise +type StepFunction = (model: Model, errors: ValidationErrors) => Promise /** * The main job of the compiler is to generate the Model and write it on disk. @@ -62,7 +62,7 @@ export default class Compiler { async write (): Promise { for (const step of this.queue) { - this.model = await step(this.model, this.jsonSpec, this.errors) + this.model = await step(this.model, this.errors) } const customStringify = stringify.configure( diff --git a/compiler/src/index.ts b/compiler/src/index.ts index 604a75f898..0769a350c3 100644 --- a/compiler/src/index.ts +++ b/compiler/src/index.ts @@ -21,7 +21,6 @@ import { readFileSync, existsSync, lstatSync } from 'fs' import { join, resolve } from 'path' import { argv } from 'zx' import Compiler from './compiler' -import validateRestSpec from './steps/validate-rest-spec' import addInfo from './steps/add-info' import validateModel from './steps/validate-model' import readDefinitionValidation from './steps/read-definition-validation' @@ -72,7 +71,6 @@ compiler .step(addInfo) .step(addDeprecation) .step(readDefinitionValidation) - .step(validateRestSpec) .step(validateModel) .step(addExamples) .write() diff --git a/compiler/src/model/json-spec.ts b/compiler/src/model/json-spec.ts deleted file mode 100644 index 6b2232be1d..0000000000 --- a/compiler/src/model/json-spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* eslint-disable @typescript-eslint/no-var-requires */ - -import { join } from 'path' -import { readdirSync } from 'fs' -import * as model from './metamodel' - -const jsonSpecPath = join(__dirname, '..', '..', '..', 'specification', '_json_spec') - -export interface JsonSpec { - documentation: { - url: string - description: string - } - feature_flag?: string - headers: { - accept?: string[] - content_type?: string[] - } - url: { - paths: Array<{ - path: string - methods: string[] - parts?: Record - deprecated?: { - version: string - description: string - } - }> - } - params?: Record - body?: { - description: string - required?: boolean - } -} - -export default function buildJsonSpec (): Map { - const files = readdirSync(jsonSpecPath) - .filter(file => file.endsWith('.json')) - - const map: Map = new Map() - for (const file of files) { - const json = require(join(jsonSpecPath, file)) - const name = Object.keys(json)[0] - map.set(name, json[name]) - } - - // Ensure deterministic ordering - return new Map([...map.entries()].sort((a, b) => a[0].localeCompare(b[0]))) -} diff --git a/compiler/src/steps/add-deprecation.ts b/compiler/src/steps/add-deprecation.ts index 718bf6b9cb..016b1f144f 100644 --- a/compiler/src/steps/add-deprecation.ts +++ b/compiler/src/steps/add-deprecation.ts @@ -18,12 +18,11 @@ */ import * as model from '../model/metamodel' -import { JsonSpec } from '../model/json-spec' /** * Populates the `deprecation` field for endpoints from the value of the corresponding request definition. */ -export default async function addContentType (model: model.Model, jsonSpec: Map): Promise { +export default async function addContentType (model: model.Model): Promise { for (const endpoint of model.endpoints) { if (endpoint.deprecation != null) { continue diff --git a/compiler/src/steps/add-examples.ts b/compiler/src/steps/add-examples.ts index 7bf870327d..3b565696e6 100644 --- a/compiler/src/steps/add-examples.ts +++ b/compiler/src/steps/add-examples.ts @@ -18,7 +18,6 @@ */ import * as model from '../model/metamodel' -import { JsonSpec } from '../model/json-spec' import * as path from 'path' import * as fs from 'fs' import * as yaml from 'js-yaml' @@ -35,9 +34,7 @@ export default class ExamplesProcessor { } // Add request and response examples for all the endpoints in the model. - // Note that the 'jsonSpec' is a parameter that is passed to a 'Step'. - // We don't need that parameter for the the 'addExamples' functionality. - async addExamples (model: model.Model, jsonSpec: Map): Promise { + async addExamples (model: model.Model): Promise { const requestExamplesProcessor = new RequestExamplesProcessor(model, this.specsFolder) const responseExamplesProcessor = new ResponseExamplesProcessor(model, this.specsFolder) for (const endpoint of model.endpoints) { diff --git a/compiler/src/steps/add-info.ts b/compiler/src/steps/add-info.ts index 19205c22d3..c0a410babc 100644 --- a/compiler/src/steps/add-info.ts +++ b/compiler/src/steps/add-info.ts @@ -18,12 +18,11 @@ */ import * as model from '../model/metamodel' -import { JsonSpec } from '../model/json-spec' /** * Adds the `_info` field to the JSON model. */ -export default async function addInfo (model: model.Model, jsonSpec: Map): Promise { +export default async function addInfo (model: model.Model): Promise { model._info = { title: 'Elasticsearch Request & Response Specification', license: { diff --git a/compiler/src/steps/read-definition-validation.ts b/compiler/src/steps/read-definition-validation.ts index cf1b5ce668..39e8820ab7 100644 --- a/compiler/src/steps/read-definition-validation.ts +++ b/compiler/src/steps/read-definition-validation.ts @@ -19,7 +19,6 @@ import assert from 'assert' import * as model from '../model/metamodel' -import { JsonSpec } from '../model/json-spec' import chalk from 'chalk' /** @@ -27,7 +26,7 @@ import chalk from 'chalk' * contains the same properties of their "write" version. * Then, it copies every model.Property from write to read but 'required'. */ -export default async function readDefinitionValidation (model: model.Model, jsonSpec: Map): Promise { +export default async function readDefinitionValidation (model: model.Model): Promise { for (const type of model.types) { if (type.kind !== 'interface') continue const readBehavior = type.behaviors?.find(behavior => behavior.type.name === 'OverloadOf') diff --git a/compiler/src/steps/validate-model.ts b/compiler/src/steps/validate-model.ts index e65a6278f2..8e75953fc1 100644 --- a/compiler/src/steps/validate-model.ts +++ b/compiler/src/steps/validate-model.ts @@ -19,7 +19,6 @@ import * as model from '../model/metamodel' import { ValidationErrors } from '../validation-errors' -import { JsonSpec } from '../model/json-spec' import assert from 'assert' import { TypeName } from '../model/metamodel' @@ -46,7 +45,7 @@ enum JsonEvent { * - verify that request parents don't define properties (would they be path/request/body properties?) * - verify that unions can be distinguished in a JSON stream (otherwise they should be inheritance trees) */ -export default async function validateModel (apiModel: model.Model, restSpec: Map, errors: ValidationErrors): Promise { +export default async function validateModel (apiModel: model.Model, errors: ValidationErrors): Promise { const initialTypeCount = apiModel.types.length // Returns the fully-qualified name of a type name diff --git a/output/schema/validation-errors.json b/output/schema/validation-errors.json index 478e127529..19748ff6b5 100644 --- a/output/schema/validation-errors.json +++ b/output/schema/validation-errors.json @@ -1,19 +1,5 @@ { - "endpointErrors": { - "_internal.update_desired_nodes": { - "request": [ - "Request: query parameter 'master_timeout' does not exist in the json spec", - "Request: query parameter 'timeout' does not exist in the json spec" - ], - "response": [] - }, - "capabilities": { - "request": [ - "Request: query parameter 'timeout' does not exist in the json spec" - ], - "response": [] - } - }, + "endpointErrors": {}, "generalErrors": [ "Dangling type '_global.scripts_painless_execute:PainlessExecutionPosition'", "Dangling type '_global.scripts_painless_execute:PainlessScript'", From 82886c32d33095f7363658f44f860007c06fee94 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Wed, 10 Dec 2025 11:27:51 +0400 Subject: [PATCH 8/9] Stop using rest-api-spec altogether --- compiler/src/compiler.ts | 11 ++-------- compiler/src/model/build-model.ts | 35 ++++++++----------------------- compiler/src/model/utils.ts | 22 +++++++++++++++---- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/compiler/src/compiler.ts b/compiler/src/compiler.ts index 3ee61de996..9865dfcca2 100644 --- a/compiler/src/compiler.ts +++ b/compiler/src/compiler.ts @@ -21,11 +21,7 @@ import { writeFile, mkdir } from 'fs/promises' import { join } from 'path' import stringify from 'safe-stable-stringify' import { Model } from './model/metamodel' -import { - compileEndpoints, - compileSpecification -} from './model/build-model' -import buildJsonSpec, { JsonSpec } from './model/json-spec' +import { compileSpecification } from './model/build-model' import { ValidationErrors } from './validation-errors' type StepFunction = (model: Model, errors: ValidationErrors) => Promise @@ -41,7 +37,6 @@ type StepFunction = (model: Model, errors: ValidationErrors) => Promise export default class Compiler { queue: StepFunction[] model: Model - jsonSpec: Map errors: ValidationErrors specsFolder: string outputFolder: string @@ -54,9 +49,7 @@ export default class Compiler { } generateModel (): this { - this.jsonSpec = buildJsonSpec() - const endpoints = compileEndpoints(this.jsonSpec) - this.model = compileSpecification(endpoints, this.specsFolder, this.outputFolder) + this.model = compileSpecification(this.specsFolder, this.outputFolder) return this } diff --git a/compiler/src/model/build-model.ts b/compiler/src/model/build-model.ts index aaf6cce1a1..77c6d39418 100644 --- a/compiler/src/model/build-model.ts +++ b/compiler/src/model/build-model.ts @@ -32,7 +32,6 @@ import { TypeAliasDeclaration } from 'ts-morph' import * as model from './metamodel' -import { JsonSpec } from './json-spec' import { assert, customTypes, @@ -57,28 +56,10 @@ import { mediaTypeToStringArray } from './utils' -export function compileEndpoints (jsonSpec: Map): Record { - // Create endpoints and merge them with - // the recorded mappings if present. - const map = {} - for (const [api, spec] of jsonSpec.entries()) { - map[api] = { - name: api, - description: null, - docUrl: null, - request: null, - requestBodyRequired: false, - response: null, - urls: [] - } - map[api].availability = {} - } - return map -} - -export function compileSpecification (endpointMappings: Record, specsFolder: string, outputFolder: string): model.Model { +export function compileSpecification (specsFolder: string, outputFolder: string): model.Model { const tsConfigFilePath = join(specsFolder, 'tsconfig.json') const project = new Project({ tsConfigFilePath }) + const endpointMappings: Record = {} verifyUniqueness(project) @@ -117,9 +98,6 @@ export function compileSpecification (endpointMappings: Record, name: string): model.Endpoint { + mappings[name] = { + name: name, + // @ts-expect-error TODO + description: null, + // @ts-expect-error TODO + docUrl: null, + request: null, + requestBodyRequired: false, + response: null, + urls: [] + } + mappings[name].availability = {} + + return mappings[name] +} + /** Lifts jsDoc type annotations to request properties */ export function hoistRequestAnnotations ( request: model.Request, jsDocs: JSDoc[], mappings: Record, response: model.TypeName | null @@ -651,9 +667,7 @@ export function hoistRequestAnnotations ( assert(jsDocs, apiName !== '' && apiName !== null && apiName !== undefined, `Request ${request.name.name} does not declare the @rest_spec_name to link back to`) - const endpoint = mappings[apiName] - assert(jsDocs, endpoint != null, `The api '${apiName}' does not exists, did you mean '${closest(apiName, Object.keys(mappings))}'?`) - + const endpoint = updateEndpoints(mappings, apiName) endpoint.request = request.name endpoint.response = response From 54de8234c41cfe2526f2959b80db29e422097af2 Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Thu, 11 Dec 2025 13:46:17 +0400 Subject: [PATCH 9/9] Fix compiler tests --- compiler/src/model/build-model.ts | 4 +-- .../specification/_global/index/request.ts | 6 ++++ .../specification/_global/index/request.ts | 6 ++++ .../specification/_global/info/request.ts | 6 ++++ .../specification/_global/index/request.ts | 6 ++++ .../specification/_global/info/request.ts | 24 -------------- .../specification/tsconfig.json | 5 --- compiler/test/wrong-api-name/test.ts | 32 ------------------- 8 files changed, 26 insertions(+), 63 deletions(-) delete mode 100644 compiler/test/wrong-api-name/specification/_global/info/request.ts delete mode 100644 compiler/test/wrong-api-name/specification/tsconfig.json delete mode 100644 compiler/test/wrong-api-name/test.ts diff --git a/compiler/src/model/build-model.ts b/compiler/src/model/build-model.ts index 77c6d39418..8e21e42a3e 100644 --- a/compiler/src/model/build-model.ts +++ b/compiler/src/model/build-model.ts @@ -241,7 +241,7 @@ function compileClassOrInterfaceDeclaration (declaration: ClassDeclaration | Int assert( pathMember as Node, urlTemplateParams.includes(part.name), - `The property '${part.name}' does not exist in the rest-api-spec ${namespace} url template` + `The property '${part.name}' does not exist in the url template` ) if (type.query.map(p => p.name).includes(part.name)) { const queryType = type.query.find(property => property != null && property.name === part.name) as model.Property @@ -589,7 +589,7 @@ function visitRequestOrResponseProperty (member: PropertyDeclaration | PropertyS * ``` * urls: [ * { - * /** @deprecated 1.2.3 Use something else + * /** \@deprecated 1.2.3 Use something else * path: '/some/path', * methods: ["GET", "POST"] * } diff --git a/compiler/test/body-codegen-name/specification/_global/index/request.ts b/compiler/test/body-codegen-name/specification/_global/index/request.ts index b18e03f3b7..aa92429be4 100644 --- a/compiler/test/body-codegen-name/specification/_global/index/request.ts +++ b/compiler/test/body-codegen-name/specification/_global/index/request.ts @@ -23,6 +23,12 @@ * @doc_id docs-index */ export interface Request { + urls: [ + { + path: '/foo', + methods: ['POST'] + } + ] body: Foo } diff --git a/compiler/test/duplicate-body-codegen-name/specification/_global/index/request.ts b/compiler/test/duplicate-body-codegen-name/specification/_global/index/request.ts index 7b3193bc6a..5638675df5 100644 --- a/compiler/test/duplicate-body-codegen-name/specification/_global/index/request.ts +++ b/compiler/test/duplicate-body-codegen-name/specification/_global/index/request.ts @@ -23,6 +23,12 @@ * @doc_id docs-index */ export interface Request { + urls: [ + { + path: "/foo/{id}" + methods: ["POST"] + } + ] path_parts: { id: string } diff --git a/compiler/test/no-body/specification/_global/info/request.ts b/compiler/test/no-body/specification/_global/info/request.ts index ddf89ba1cf..4a3f604339 100644 --- a/compiler/test/no-body/specification/_global/info/request.ts +++ b/compiler/test/no-body/specification/_global/info/request.ts @@ -23,6 +23,12 @@ * @doc_id api-root */ export interface Request { + urls: [ + { + path: "/foo" + methods: ["GET"] + } + ] body: { foo: string } diff --git a/compiler/test/request-availability/specification/_global/index/request.ts b/compiler/test/request-availability/specification/_global/index/request.ts index b69746c3e3..b3c077278d 100644 --- a/compiler/test/request-availability/specification/_global/index/request.ts +++ b/compiler/test/request-availability/specification/_global/index/request.ts @@ -24,6 +24,12 @@ * @doc_id docs-index */ export interface Request { + urls: [ + { + path: '/{index}/_doc/{id}' + methods: ['POST', 'PUT'] + } + ] path_parts: { id?: string index: string diff --git a/compiler/test/wrong-api-name/specification/_global/info/request.ts b/compiler/test/wrong-api-name/specification/_global/info/request.ts deleted file mode 100644 index 43ac7ffe54..0000000000 --- a/compiler/test/wrong-api-name/specification/_global/info/request.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * @rest_spec_name foobar - * @availability stack since=0.0.0 stability=stable - */ -export interface Request {} diff --git a/compiler/test/wrong-api-name/specification/tsconfig.json b/compiler/test/wrong-api-name/specification/tsconfig.json deleted file mode 100644 index a983782068..0000000000 --- a/compiler/test/wrong-api-name/specification/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../../../specification/tsconfig.json", - "typeRoots": ["./**/*.ts"], - "include": ["./**/*.ts"] -} diff --git a/compiler/test/wrong-api-name/test.ts b/compiler/test/wrong-api-name/test.ts deleted file mode 100644 index 1ea3da40c3..0000000000 --- a/compiler/test/wrong-api-name/test.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { join } from 'path' -import test from 'ava' -import Compiler from '../../src/compiler' -import * as Model from '../../src/model/metamodel' - -const specsFolder = join(__dirname, 'specification') -const outputFolder = join(__dirname, 'output') - -test('Wrong rest_spec_name for request definition', t => { - const compiler = new Compiler(specsFolder, outputFolder) - const error = t.throws(() => compiler.generateModel()) - t.true(error?.message.startsWith("The api 'foobar' does not exists, did you mean")) -})