diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b6a1c3e86c3e75..647de24ad4920d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -539,6 +539,7 @@ x-pack/plugins/observability @elastic/actionable-observability x-pack/plugins/observability_shared @elastic/observability-ui x-pack/test/security_api_integration/plugins/oidc_provider @elastic/kibana-security test/common/plugins/otel_metrics @elastic/infra-monitoring-ui +packages/kbn-openapi-generator @elastic/security-detection-engine packages/kbn-optimizer @elastic/kibana-operations packages/kbn-optimizer-webpack-helpers @elastic/kibana-operations packages/kbn-osquery-io-ts-types @elastic/security-asset-management diff --git a/package.json b/package.json index eaafb301c5dfc4..54123db6f2eb7e 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,9 @@ "types": "./kibana.d.ts", "tsdocMetadata": "./build/tsdoc-metadata.json", "build": { + "date": "2023-05-15T23:12:09+0000", "number": 8467, - "sha": "6cb7fec4e154faa0a4a3fee4b33dfef91b9870d9", - "date": "2023-05-15T23:12:09+0000" + "sha": "6cb7fec4e154faa0a4a3fee4b33dfef91b9870d9" }, "homepage": "https://www.elastic.co/products/kibana", "bugs": { @@ -1205,6 +1205,7 @@ "@kbn/managed-vscode-config": "link:packages/kbn-managed-vscode-config", "@kbn/managed-vscode-config-cli": "link:packages/kbn-managed-vscode-config-cli", "@kbn/management-storybook-config": "link:packages/kbn-management/storybook/config", + "@kbn/openapi-generator": "link:packages/kbn-openapi-generator", "@kbn/optimizer": "link:packages/kbn-optimizer", "@kbn/optimizer-webpack-helpers": "link:packages/kbn-optimizer-webpack-helpers", "@kbn/peggy": "link:packages/kbn-peggy", diff --git a/packages/kbn-openapi-generator/README.md b/packages/kbn-openapi-generator/README.md new file mode 100644 index 00000000000000..fc75a76827934a --- /dev/null +++ b/packages/kbn-openapi-generator/README.md @@ -0,0 +1,201 @@ +# OpenAPI Code Generator for Kibana + +This code generator could be used to generate runtime types, documentation, server stub implementations, clients, and much more given OpenAPI specification. + +## Getting started + +To start with code generation you should have OpenAPI specification describing your API endpoint request and response schemas along with common types used in your API. The code generation script supports OpenAPI 3.1.0, refer to https://swagger.io/specification/ for more details. + +OpenAPI specification should be in YAML format and have `.schema.yaml` extension. Here's a simple example of OpenAPI specification: + +```yaml +openapi: 3.0.0 +info: + title: Install Prebuilt Rules API endpoint + version: 2023-10-31 +paths: + /api/detection_engine/rules/prepackaged: + put: + operationId: InstallPrebuiltRules + x-codegen-enabled: true + summary: Installs all Elastic prebuilt rules and timelines + tags: + - Prebuilt Rules API + responses: + 200: + description: Indicates a successful call + content: + application/json: + schema: + type: object + properties: + rules_installed: + type: integer + description: The number of rules installed + minimum: 0 + rules_updated: + type: integer + description: The number of rules updated + minimum: 0 + timelines_installed: + type: integer + description: The number of timelines installed + minimum: 0 + timelines_updated: + type: integer + description: The number of timelines updated + minimum: 0 + required: + - rules_installed + - rules_updated + - timelines_installed + - timelines_updated +``` + +Put it anywhere in your plugin, the code generation script will traverse the whole plugin directory and find all `.schema.yaml` files. + +Then to generate code run the following command: + +```bash +node scripts/generate_openapi --rootDir ./x-pack/plugins/security_solution +``` + +![Generator command output](image.png) + +By default it uses the `zod_operation_schema` template which produces runtime types for request and response schemas described in OpenAPI specification. The generated code will be placed adjacent to the `.schema.yaml` file and will have `.gen.ts` extension. + +Example of generated code: + +```ts +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +export type InstallPrebuiltRulesResponse = z.infer; +export const InstallPrebuiltRulesResponse = z.object({ + /** + * The number of rules installed + */ + rules_installed: z.number().int().min(0), + /** + * The number of rules updated + */ + rules_updated: z.number().int().min(0), + /** + * The number of timelines installed + */ + timelines_installed: z.number().int().min(0), + /** + * The number of timelines updated + */ + timelines_updated: z.number().int().min(0), +}); +``` +## Programmatic API + +Alternatively, you can use the code generator programmatically. You can create a script file and run it with `node` command. This could be useful if you want to set up code generation in your CI pipeline. Here's an example of such script: + +```ts +require('../../../../../src/setup_node_env'); +const { generate } = require('@kbn/openapi-generator'); +const { resolve } = require('path'); + +const SECURITY_SOLUTION_ROOT = resolve(__dirname, '../..'); + +generate({ + rootDir: SECURITY_SOLUTION_ROOT, // Path to the plugin root directory + sourceGlob: './**/*.schema.yaml', // Glob pattern to find OpenAPI specification files + templateName: 'zod_operation_schema', // Name of the template to use +}); +``` + +## CI integration + +To make sure that generated code is always in sync with its OpenAPI specification it is recommended to add a command to your CI pipeline that will run code generation on every pull request and commit the changes if there are any. + +First, create a script that will run code generation and commit the changes. See `.buildkite/scripts/steps/code_generation/security_solution_codegen.sh` for an example: + +```bash +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +.buildkite/scripts/bootstrap.sh + +echo --- Security Solution OpenAPI Code Generation + +(cd x-pack/plugins/security_solution && yarn openapi:generate) +check_for_changed_files "yarn openapi:generate" true +``` + +This scripts sets up the minimal environment required fro code generation and runs the code generation script. Then it checks if there are any changes and commits them if there are any using the `check_for_changed_files` function. + +Then add the code generation script to your plugin build pipeline. Open your plugin build pipeline, for example `.buildkite/pipelines/pull_request/security_solution.yml`, and add the following command to the steps list adjusting the path to your code generation script: + +```yaml + - command: .buildkite/scripts/steps/code_generation/security_solution_codegen.sh + label: 'Security Solution OpenAPI codegen' + agents: + queue: n2-2-spot + timeout_in_minutes: 60 + parallelism: 1 +``` + +Now on every pull request the code generation script will run and commit the changes if there are any. + +## OpenAPI Schema + +The code generator supports the OpenAPI definitions described in the request, response, and component sections of the document. + +For every API operation (GET, POST, etc) it is required to specify the `operationId` field. This field is used to generate the name of the generated types. For example, if the `operationId` is `InstallPrebuiltRules` then the generated types will be named `InstallPrebuiltRulesResponse` and `InstallPrebuiltRulesRequest`. If the `operationId` is not specified then the code generation will throw an error. + +The `x-codegen-enabled` field is used to enable or disable code generation for the operation. If it is not specified then code generation is disabled by default. This field could be also used to disable code generation of common components described in the `components` section of the OpenAPI specification. + +Keep in mind that disabling code generation for common components that are referenced by external OpenAPI specifications could lead to errors during code generation. + +### Schema files organization + +It is recommended to limit the number of operations and components described in a single OpenAPI specification file. Having one HTTP operation in a single file will make it easier to maintain and will keep the generated artifacts granular for ease of reuse and better tree shaking. You can have as many OpenAPI specification files as you want. + +### Common components + +It is common to have shared types that are used in multiple API operations. To avoid code duplication you can define common components in the `components` section of the OpenAPI specification and put them in a separate file. Then you can reference these components in the `parameters` and `responses` sections of the API operations. + +Here's an example of the schema that references common components: + +```yaml +openapi: 3.0.0 +info: + title: Delete Rule API endpoint + version: 2023-10-31 +paths: + /api/detection_engine/rules: + delete: + operationId: DeleteRule + description: Deletes a single rule using the `rule_id` or `id` field. + parameters: + - name: id + in: query + required: false + description: The rule's `id` value. + schema: + $ref: '../../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleSignatureId' + - name: rule_id + in: query + required: false + description: The rule's `rule_id` value. + schema: + $ref: '../../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleObjectId' + responses: + 200: + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '../../../model/rule_schema/rule_schemas.schema.yaml#/components/schemas/RuleResponse' +``` diff --git a/packages/kbn-openapi-generator/image.png b/packages/kbn-openapi-generator/image.png new file mode 100644 index 00000000000000..e8710d78b80b6d Binary files /dev/null and b/packages/kbn-openapi-generator/image.png differ diff --git a/packages/kbn-openapi-generator/index.ts b/packages/kbn-openapi-generator/index.ts new file mode 100644 index 00000000000000..eeaad5343dc9f3 --- /dev/null +++ b/packages/kbn-openapi-generator/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './src/openapi_generator'; +export * from './src/cli'; diff --git a/packages/kbn-openapi-generator/jest.config.js b/packages/kbn-openapi-generator/jest.config.js new file mode 100644 index 00000000000000..6b5b1fce1c4d47 --- /dev/null +++ b/packages/kbn-openapi-generator/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-openapi-generator'], +}; diff --git a/packages/kbn-openapi-generator/kibana.jsonc b/packages/kbn-openapi-generator/kibana.jsonc new file mode 100644 index 00000000000000..b507d94ec022d6 --- /dev/null +++ b/packages/kbn-openapi-generator/kibana.jsonc @@ -0,0 +1,6 @@ +{ + "devOnly": true, + "id": "@kbn/openapi-generator", + "owner": "@elastic/security-detection-engine", + "type": "shared-common" +} diff --git a/packages/kbn-openapi-generator/package.json b/packages/kbn-openapi-generator/package.json new file mode 100644 index 00000000000000..5847d729d025c9 --- /dev/null +++ b/packages/kbn-openapi-generator/package.json @@ -0,0 +1,10 @@ +{ + "bin": { + "openapi-generator": "./bin/openapi-generator.js" + }, + "description": "OpenAPI code generator for Kibana", + "license": "SSPL-1.0 OR Elastic License 2.0", + "name": "@kbn/openapi-generator", + "private": true, + "version": "1.0.0" +} diff --git a/packages/kbn-openapi-generator/src/cli.ts b/packages/kbn-openapi-generator/src/cli.ts new file mode 100644 index 00000000000000..722fc6123e899e --- /dev/null +++ b/packages/kbn-openapi-generator/src/cli.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import yargs from 'yargs/yargs'; +import { generate } from './openapi_generator'; +import { AVAILABLE_TEMPLATES } from './template_service/template_service'; + +export function runCli() { + yargs(process.argv.slice(2)) + .command( + '*', + 'Generate code artifacts from OpenAPI specifications', + (y) => + y + .option('rootDir', { + describe: 'Root directory to search for OpenAPI specs', + demandOption: true, + string: true, + }) + .option('sourceGlob', { + describe: 'Elasticsearch target', + default: './**/*.schema.yaml', + string: true, + }) + .option('templateName', { + describe: 'Template to use for code generation', + default: 'zod_operation_schema' as const, + choices: AVAILABLE_TEMPLATES, + }) + .showHelpOnFail(false), + (argv) => { + generate(argv).catch((err) => { + // eslint-disable-next-line no-console + console.error(err); + process.exit(1); + }); + } + ) + .parse(); +} diff --git a/x-pack/plugins/security_solution/scripts/openapi/lib/fix_eslint.ts b/packages/kbn-openapi-generator/src/lib/fix_eslint.ts similarity index 62% rename from x-pack/plugins/security_solution/scripts/openapi/lib/fix_eslint.ts rename to packages/kbn-openapi-generator/src/lib/fix_eslint.ts index 23d8bf540f7317..c205dbcebf1644 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/lib/fix_eslint.ts +++ b/packages/kbn-openapi-generator/src/lib/fix_eslint.ts @@ -1,19 +1,18 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import execa from 'execa'; -import { resolve } from 'path'; - -const KIBANA_ROOT = resolve(__dirname, '../../../../../../'); +import { REPO_ROOT } from '@kbn/repo-info'; export async function fixEslint(path: string) { await execa('npx', ['eslint', '--fix', path], { // Need to run eslint from the Kibana root directory, otherwise it will not // be able to pick up the right config - cwd: KIBANA_ROOT, + cwd: REPO_ROOT, }); } diff --git a/x-pack/plugins/security_solution/scripts/openapi/lib/format_output.ts b/packages/kbn-openapi-generator/src/lib/format_output.ts similarity index 61% rename from x-pack/plugins/security_solution/scripts/openapi/lib/format_output.ts rename to packages/kbn-openapi-generator/src/lib/format_output.ts index 6c374aa1f06d25..7b50b0732009bb 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/lib/format_output.ts +++ b/packages/kbn-openapi-generator/src/lib/format_output.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import execa from 'execa'; diff --git a/packages/kbn-openapi-generator/src/lib/get_generated_file_path.ts b/packages/kbn-openapi-generator/src/lib/get_generated_file_path.ts new file mode 100644 index 00000000000000..4139d3610d8b65 --- /dev/null +++ b/packages/kbn-openapi-generator/src/lib/get_generated_file_path.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export function getGeneratedFilePath(sourcePath: string) { + return sourcePath.replace(/\..+$/, '.gen.ts'); +} diff --git a/x-pack/plugins/security_solution/scripts/openapi/lib/remove_gen_artifacts.ts b/packages/kbn-openapi-generator/src/lib/remove_gen_artifacts.ts similarity index 75% rename from x-pack/plugins/security_solution/scripts/openapi/lib/remove_gen_artifacts.ts rename to packages/kbn-openapi-generator/src/lib/remove_gen_artifacts.ts index 3cbf421b8c94b3..45933864faf8f0 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/lib/remove_gen_artifacts.ts +++ b/packages/kbn-openapi-generator/src/lib/remove_gen_artifacts.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import fs from 'fs/promises'; diff --git a/packages/kbn-openapi-generator/src/openapi_generator.ts b/packages/kbn-openapi-generator/src/openapi_generator.ts new file mode 100644 index 00000000000000..539994a2581264 --- /dev/null +++ b/packages/kbn-openapi-generator/src/openapi_generator.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* eslint-disable no-console */ + +import SwaggerParser from '@apidevtools/swagger-parser'; +import chalk from 'chalk'; +import fs from 'fs/promises'; +import globby from 'globby'; +import { resolve } from 'path'; +import { fixEslint } from './lib/fix_eslint'; +import { formatOutput } from './lib/format_output'; +import { getGeneratedFilePath } from './lib/get_generated_file_path'; +import { removeGenArtifacts } from './lib/remove_gen_artifacts'; +import { getGenerationContext } from './parser/get_generation_context'; +import type { OpenApiDocument } from './parser/openapi_types'; +import { initTemplateService, TemplateName } from './template_service/template_service'; + +export interface GeneratorConfig { + rootDir: string; + sourceGlob: string; + templateName: TemplateName; +} + +export const generate = async (config: GeneratorConfig) => { + const { rootDir, sourceGlob, templateName } = config; + + console.log(chalk.bold(`Generating API route schemas`)); + console.log(chalk.bold(`Working directory: ${chalk.underline(rootDir)}`)); + + console.log(`๐Ÿ‘€ Searching for source files`); + const sourceFilesGlob = resolve(rootDir, sourceGlob); + const schemaPaths = await globby([sourceFilesGlob]); + + console.log(`๐Ÿ•ต๏ธโ€โ™€๏ธ Found ${schemaPaths.length} schemas, parsing`); + const parsedSources = await Promise.all( + schemaPaths.map(async (sourcePath) => { + const parsedSchema = (await SwaggerParser.parse(sourcePath)) as OpenApiDocument; + return { sourcePath, parsedSchema }; + }) + ); + + console.log(`๐Ÿงน Cleaning up any previously generated artifacts`); + await removeGenArtifacts(rootDir); + + console.log(`๐Ÿช„ Generating new artifacts`); + const TemplateService = await initTemplateService(); + await Promise.all( + parsedSources.map(async ({ sourcePath, parsedSchema }) => { + const generationContext = getGenerationContext(parsedSchema); + + // If there are no operations or components to generate, skip this file + const shouldGenerate = + generationContext.operations.length > 0 || generationContext.components !== undefined; + if (!shouldGenerate) { + return; + } + + const result = TemplateService.compileTemplate(templateName, generationContext); + + // Write the generation result to disk + await fs.writeFile(getGeneratedFilePath(sourcePath), result); + }) + ); + + // Format the output folder using prettier as the generator produces + // unformatted code and fix any eslint errors + console.log(`๐Ÿ’… Formatting output`); + const generatedArtifactsGlob = resolve(rootDir, './**/*.gen.ts'); + await formatOutput(generatedArtifactsGlob); + await fixEslint(generatedArtifactsGlob); +}; diff --git a/packages/kbn-openapi-generator/src/parser/get_generation_context.ts b/packages/kbn-openapi-generator/src/parser/get_generation_context.ts new file mode 100644 index 00000000000000..14a3b8eb2e178e --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/get_generation_context.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { OpenAPIV3 } from 'openapi-types'; +import { getApiOperationsList } from './lib/get_api_operations_list'; +import { getComponents } from './lib/get_components'; +import { getImportsMap, ImportsMap } from './lib/get_imports_map'; +import { normalizeSchema } from './lib/normalize_schema'; +import { NormalizedOperation, OpenApiDocument } from './openapi_types'; + +export interface GenerationContext { + components: OpenAPIV3.ComponentsObject | undefined; + operations: NormalizedOperation[]; + imports: ImportsMap; +} + +export function getGenerationContext(document: OpenApiDocument): GenerationContext { + const normalizedDocument = normalizeSchema(document); + + const components = getComponents(normalizedDocument); + const operations = getApiOperationsList(normalizedDocument); + const imports = getImportsMap(normalizedDocument); + + return { + components, + operations, + imports, + }; +} diff --git a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_api_operations_list.ts b/packages/kbn-openapi-generator/src/parser/lib/get_api_operations_list.ts similarity index 87% rename from x-pack/plugins/security_solution/scripts/openapi/parsers/get_api_operations_list.ts rename to packages/kbn-openapi-generator/src/parser/lib/get_api_operations_list.ts index c9d9a75c078541..ac63820555419d 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_api_operations_list.ts +++ b/packages/kbn-openapi-generator/src/parser/lib/get_api_operations_list.ts @@ -1,12 +1,18 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { OpenAPIV3 } from 'openapi-types'; -import type { NormalizedOperation, ObjectSchema, OpenApiDocument } from './openapi_types'; +import type { + NormalizedOperation, + NormalizedSchemaItem, + ObjectSchema, + OpenApiDocument, +} from '../openapi_types'; const HTTP_METHODS = Object.values(OpenAPIV3.HttpMethods); @@ -61,14 +67,12 @@ export function getApiOperationsList(parsedSchema: OpenApiDocument): NormalizedO `Cannot generate response for ${method} ${path}: $ref in response is not supported` ); } - const response = operation.responses?.['200']?.content?.['application/json']?.schema; if (operation.requestBody && '$ref' in operation.requestBody) { throw new Error( `Cannot generate request for ${method} ${path}: $ref in request body is not supported` ); } - const requestBody = operation.requestBody?.content?.['application/json']?.schema; const { operationId, description, tags, deprecated } = operation; @@ -78,7 +82,13 @@ export function getApiOperationsList(parsedSchema: OpenApiDocument): NormalizedO throw new Error(`Missing operationId for ${method} ${path}`); } - return { + const response = operation.responses?.['200']?.content?.['application/json']?.schema as + | NormalizedSchemaItem + | undefined; + const requestBody = operation.requestBody?.content?.['application/json']?.schema as + | NormalizedSchemaItem + | undefined; + const normalizedOperation: NormalizedOperation = { path, method, operationId, @@ -90,6 +100,8 @@ export function getApiOperationsList(parsedSchema: OpenApiDocument): NormalizedO requestBody, response, }; + + return normalizedOperation; }); } ); diff --git a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_components.ts b/packages/kbn-openapi-generator/src/parser/lib/get_components.ts similarity index 59% rename from x-pack/plugins/security_solution/scripts/openapi/parsers/get_components.ts rename to packages/kbn-openapi-generator/src/parser/lib/get_components.ts index 5b3fef72905c0e..6e98793de1afb6 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_components.ts +++ b/packages/kbn-openapi-generator/src/parser/lib/get_components.ts @@ -1,15 +1,17 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import type { OpenApiDocument } from './openapi_types'; +import type { OpenApiDocument } from '../openapi_types'; export function getComponents(parsedSchema: OpenApiDocument) { if (parsedSchema.components?.['x-codegen-enabled'] === false) { return undefined; } + return parsedSchema.components; } diff --git a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_imports_map.ts b/packages/kbn-openapi-generator/src/parser/lib/get_imports_map.ts similarity index 76% rename from x-pack/plugins/security_solution/scripts/openapi/parsers/get_imports_map.ts rename to packages/kbn-openapi-generator/src/parser/lib/get_imports_map.ts index 8e068b61ba0344..c2259052e0cb6f 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_imports_map.ts +++ b/packages/kbn-openapi-generator/src/parser/lib/get_imports_map.ts @@ -1,12 +1,14 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { uniq } from 'lodash'; -import type { OpenApiDocument } from './openapi_types'; +import type { OpenApiDocument } from '../openapi_types'; +import { traverseObject } from './traverse_object'; export interface ImportsMap { [importPath: string]: string[]; @@ -55,23 +57,11 @@ const hasRef = (obj: unknown): obj is { $ref: string } => { function findRefs(obj: unknown): string[] { const refs: string[] = []; - function search(element: unknown) { - if (typeof element === 'object' && element !== null) { - if (hasRef(element)) { - refs.push(element.$ref); - } - - Object.values(element).forEach((value) => { - if (Array.isArray(value)) { - value.forEach(search); - } else { - search(value); - } - }); + traverseObject(obj, (element) => { + if (hasRef(element)) { + refs.push(element.$ref); } - } - - search(obj); + }); return refs; } diff --git a/packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts b/packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts new file mode 100644 index 00000000000000..0ea2062e369c3a --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { OpenAPIV3 } from 'openapi-types'; +import { NormalizedReferenceObject } from '../openapi_types'; +import { traverseObject } from './traverse_object'; + +/** + * Check if an object has a $ref property + * + * @param obj Any object + * @returns True if the object has a $ref property + */ +const hasRef = (obj: unknown): obj is NormalizedReferenceObject => { + return typeof obj === 'object' && obj !== null && '$ref' in obj; +}; + +export function normalizeSchema(schema: OpenAPIV3.Document) { + traverseObject(schema, (element) => { + if (hasRef(element)) { + const referenceName = element.$ref.split('/').pop(); + if (!referenceName) { + throw new Error(`Cannot parse reference name: ${element.$ref}`); + } + + element.referenceName = referenceName; + } + }); + + return schema; +} diff --git a/packages/kbn-openapi-generator/src/parser/lib/traverse_object.ts b/packages/kbn-openapi-generator/src/parser/lib/traverse_object.ts new file mode 100644 index 00000000000000..2049465e0753b7 --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/lib/traverse_object.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * A generic function to traverse an object or array of objects recursively + * + * @param obj The object to traverse + * @param onVisit A function that will be called for each traversed node in the object + */ +export function traverseObject(obj: unknown, onVisit: (element: object) => void) { + function search(element: unknown) { + if (typeof element === 'object' && element !== null) { + onVisit(element); + + Object.values(element).forEach((value) => { + if (Array.isArray(value)) { + value.forEach(search); + } else { + search(value); + } + }); + } + } + + search(obj); +} diff --git a/x-pack/plugins/security_solution/scripts/openapi/parsers/openapi_types.ts b/packages/kbn-openapi-generator/src/parser/openapi_types.ts similarity index 62% rename from x-pack/plugins/security_solution/scripts/openapi/parsers/openapi_types.ts rename to packages/kbn-openapi-generator/src/parser/openapi_types.ts index 2449f34fa4b768..c8b1e4f7153456 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/parsers/openapi_types.ts +++ b/packages/kbn-openapi-generator/src/parser/openapi_types.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import type { OpenAPIV3 } from 'openapi-types'; @@ -27,6 +28,21 @@ declare module 'openapi-types' { } } +export type NormalizedReferenceObject = OpenAPIV3.ReferenceObject & { + referenceName: string; +}; + +export interface UnknownType { + type: 'unknown'; +} + +export type NormalizedSchemaObject = + | OpenAPIV3.ArraySchemaObject + | OpenAPIV3.NonArraySchemaObject + | UnknownType; + +export type NormalizedSchemaItem = OpenAPIV3.SchemaObject | NormalizedReferenceObject; + /** * OpenAPI types do not have a dedicated type for objects, so we need to create * to use for path and query parameters @@ -36,7 +52,7 @@ export interface ObjectSchema { required: string[]; description?: string; properties: { - [name: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject; + [name: string]: NormalizedSchemaItem; }; } @@ -50,8 +66,8 @@ export interface NormalizedOperation { description?: string; tags?: string[]; deprecated?: boolean; - requestParams?: ObjectSchema; - requestQuery?: ObjectSchema; - requestBody?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject; - response?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject; + requestParams?: NormalizedSchemaItem; + requestQuery?: NormalizedSchemaItem; + requestBody?: NormalizedSchemaItem; + response?: NormalizedSchemaItem; } diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/register_helpers.ts b/packages/kbn-openapi-generator/src/template_service/register_helpers.ts similarity index 75% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/register_helpers.ts rename to packages/kbn-openapi-generator/src/template_service/register_helpers.ts index b3bb02f7743c89..1431dafcdfba9f 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/register_helpers.ts +++ b/packages/kbn-openapi-generator/src/template_service/register_helpers.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import type Handlebars from '@kbn/handlebars'; @@ -13,9 +14,6 @@ export function registerHelpers(handlebarsInstance: typeof Handlebars) { const values = args.slice(0, -1) as unknown[]; return values.join(''); }); - handlebarsInstance.registerHelper('parseRef', (refName: string) => { - return refName.split('/').pop(); - }); handlebarsInstance.registerHelper('snakeCase', snakeCase); handlebarsInstance.registerHelper('camelCase', camelCase); handlebarsInstance.registerHelper('toJSON', (value: unknown) => { @@ -43,13 +41,4 @@ export function registerHelpers(handlebarsInstance: typeof Handlebars) { handlebarsInstance.registerHelper('isUnknown', (val: object) => { return !('type' in val || '$ref' in val || 'anyOf' in val || 'oneOf' in val || 'allOf' in val); }); - handlebarsInstance.registerHelper('isEmpty', (val) => { - if (Array.isArray(val)) { - return val.length === 0; - } - if (typeof val === 'object') { - return Object.keys(val).length === 0; - } - return val === undefined || val === null || val === ''; - }); } diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/register_templates.ts b/packages/kbn-openapi-generator/src/template_service/register_templates.ts similarity index 83% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/register_templates.ts rename to packages/kbn-openapi-generator/src/template_service/register_templates.ts index fa39b52d99471b..b8f9711ecb1a66 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/register_templates.ts +++ b/packages/kbn-openapi-generator/src/template_service/register_templates.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import type Handlebars from '@kbn/handlebars'; diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/template_service.ts b/packages/kbn-openapi-generator/src/template_service/template_service.ts similarity index 60% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/template_service.ts rename to packages/kbn-openapi-generator/src/template_service/template_service.ts index becb02bb54ebe2..9980282bbfdc3c 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/template_service.ts +++ b/packages/kbn-openapi-generator/src/template_service/template_service.ts @@ -1,28 +1,23 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import Handlebars from 'handlebars'; -import type { OpenAPIV3 } from 'openapi-types'; import { resolve } from 'path'; -import type { ImportsMap } from '../parsers/get_imports_map'; -import type { NormalizedOperation } from '../parsers/openapi_types'; +import { GenerationContext } from '../parser/get_generation_context'; import { registerHelpers } from './register_helpers'; import { registerTemplates } from './register_templates'; -export interface TemplateContext { - importsMap: ImportsMap; - apiOperations: NormalizedOperation[]; - components: OpenAPIV3.ComponentsObject | undefined; -} +export const AVAILABLE_TEMPLATES = ['zod_operation_schema'] as const; -export type TemplateName = 'schemas'; +export type TemplateName = typeof AVAILABLE_TEMPLATES[number]; export interface ITemplateService { - compileTemplate: (templateName: TemplateName, context: TemplateContext) => string; + compileTemplate: (templateName: TemplateName, context: GenerationContext) => string; } /** @@ -36,7 +31,7 @@ export const initTemplateService = async (): Promise => { const templates = await registerTemplates(resolve(__dirname, './templates'), handlebars); return { - compileTemplate: (templateName: TemplateName, context: TemplateContext) => { + compileTemplate: (templateName: TemplateName, context: GenerationContext) => { return handlebars.compile(templates[templateName])(context); }, }; diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/disclaimer.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/disclaimer.handlebars similarity index 80% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/templates/disclaimer.handlebars rename to packages/kbn-openapi-generator/src/template_service/templates/disclaimer.handlebars index 4be0a93d1b79e6..403a5b54f09b37 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/disclaimer.handlebars +++ b/packages/kbn-openapi-generator/src/template_service/templates/disclaimer.handlebars @@ -1,5 +1,5 @@ /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ \ No newline at end of file diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schemas.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars similarity index 75% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schemas.handlebars rename to packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars index a6df5d96b124fe..0b129b3aa13ed9 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schemas.handlebars +++ b/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars @@ -9,7 +9,7 @@ import { z } from "zod"; {{> disclaimer}} -{{#each importsMap}} +{{#each imports}} import { {{#each this}}{{.}},{{/each}} } from "{{@key}}" @@ -22,11 +22,15 @@ import { */ {{/description}} export type {{@key}} = z.infer; -export const {{@key}} = {{> schema_item}}; +export const {{@key}} = {{> zod_schema_item}}; +{{#if enum}} +export const {{@key}}Enum = {{@key}}.enum; +export type {{@key}}Enum = typeof {{@key}}.enum; +{{/if}} {{/each}} -{{#each apiOperations}} +{{#each operations}} {{#if requestQuery}} {{#if requestQuery.description}} /** @@ -34,7 +38,7 @@ export const {{@key}} = {{> schema_item}}; */ {{/if}} export type {{operationId}}RequestQuery = z.infer; -export const {{operationId}}RequestQuery = {{> schema_item requestQuery }}; +export const {{operationId}}RequestQuery = {{> zod_query_item requestQuery }}; export type {{operationId}}RequestQueryInput = z.input; {{/if}} @@ -45,7 +49,7 @@ export type {{operationId}}RequestQueryInput = z.input; -export const {{operationId}}RequestParams = {{> schema_item requestParams }}; +export const {{operationId}}RequestParams = {{> zod_schema_item requestParams }}; export type {{operationId}}RequestParamsInput = z.input; {{/if}} @@ -56,7 +60,7 @@ export type {{operationId}}RequestParamsInput = z.input; -export const {{operationId}}RequestBody = {{> schema_item requestBody }}; +export const {{operationId}}RequestBody = {{> zod_schema_item requestBody }}; export type {{operationId}}RequestBodyInput = z.input; {{/if}} @@ -67,6 +71,6 @@ export type {{operationId}}RequestBodyInput = z.input; -export const {{operationId}}Response = {{> schema_item response }}; +export const {{operationId}}Response = {{> zod_schema_item response }}; {{/if}} {{/each}} diff --git a/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars new file mode 100644 index 00000000000000..0b718d941cbed2 --- /dev/null +++ b/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars @@ -0,0 +1,51 @@ +{{~#if $ref~}} + {{referenceName}} + {{~#if nullable}}.nullable(){{/if~}} + {{~#if (eq requiredBool false)}}.optional(){{/if~}} + {{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}} +{{~/if~}} + +{{~#if (eq type "object")}} + z.object({ + {{#each properties}} + {{#if description}} + /** + * {{{description}}} + */ + {{/if}} + {{@key}}: {{> zod_query_item requiredBool=(includes ../required @key)}}, + {{/each}} + }) +{{~/if~}} + +{{~#if (eq type "array")}} + z.preprocess( + (value: unknown) => (typeof value === "string") ? value === '' ? [] : value.split(",") : value, + z.array({{~> zod_schema_item items ~}}) + ) + {{~#if minItems}}.min({{minItems}}){{/if~}} + {{~#if maxItems}}.max({{maxItems}}){{/if~}} + {{~#if (eq requiredBool false)}}.optional(){{/if~}} + {{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}} +{{~/if~}} + +{{~#if (eq type "boolean")}} + z.preprocess( + (value: unknown) => (typeof value === "boolean") ? String(value) : value, + z.enum(["true", "false"]) + {{~#if (defined default)}}.default("{{{toJSON default}}}"){{/if~}} + .transform((value) => value === "true") + ) +{{~/if~}} + +{{~#if (eq type "string")}} +{{> zod_schema_item}} +{{~/if~}} + +{{~#if (eq type "integer")}} +{{> zod_schema_item}} +{{~/if~}} + +{{~#if (eq type "number")}} +{{> zod_schema_item}} +{{~/if~}} diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schema_item.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars similarity index 79% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schema_item.handlebars rename to packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars index 2d544a702ac004..dbf156b6a7b125 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schema_item.handlebars +++ b/packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars @@ -6,7 +6,7 @@ {{~/if~}} {{~#if $ref~}} - {{parseRef $ref}} + {{referenceName}} {{~#if nullable}}.nullable(){{/if~}} {{~#if (eq requiredBool false)}}.optional(){{/if~}} {{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}} @@ -15,9 +15,9 @@ {{~#if allOf~}} {{~#each allOf~}} {{~#if @first~}} - {{> schema_item }} + {{> zod_schema_item }} {{~else~}} - .and({{> schema_item }}) + .and({{> zod_schema_item }}) {{~/if~}} {{~/each~}} {{~/if~}} @@ -25,7 +25,7 @@ {{~#if anyOf~}} z.union([ {{~#each anyOf~}} - {{~> schema_item ~}}, + {{~> zod_schema_item ~}}, {{~/each~}} ]) {{~/if~}} @@ -33,7 +33,7 @@ {{~#if oneOf~}} z.union([ {{~#each oneOf~}} - {{~> schema_item ~}}, + {{~> zod_schema_item ~}}, {{~/each~}} ]) {{~/if~}} @@ -43,11 +43,7 @@ z.unknown() {{/if}} {{~#*inline "type_array"~}} - {{~#if x-preprocess}} - z.preprocess({{x-preprocess}}, z.array({{~> schema_item items ~}})) - {{else}} - z.array({{~> schema_item items ~}}) - {{~/if~}} + z.array({{~> zod_schema_item items ~}}) {{~#if minItems}}.min({{minItems}}){{/if~}} {{~#if maxItems}}.max({{maxItems}}){{/if~}} {{~/inline~}} @@ -58,11 +54,13 @@ z.unknown() {{~/inline~}} {{~#*inline "type_integer"~}} - {{~#if x-coerce}} - z.coerce.number() - {{~else~}} + z.number().int() + {{~#if minimum includeZero=true}}.min({{minimum}}){{/if~}} + {{~#if maximum includeZero=true}}.max({{maximum}}){{/if~}} +{{~/inline~}} + +{{~#*inline "type_number"~}} z.number() - {{~/if~}} {{~#if minimum includeZero=true}}.min({{minimum}}){{/if~}} {{~#if maximum includeZero=true}}.max({{maximum}}){{/if~}} {{~/inline~}} @@ -75,7 +73,7 @@ z.unknown() * {{{description}}} */ {{/if}} - {{@key}}: {{> schema_item requiredBool=(includes ../required @key)}}, + {{@key}}: {{> zod_schema_item requiredBool=(includes ../required @key)}}, {{/each}} }) {{#if (eq additionalProperties false)}}.strict(){{/if}} diff --git a/packages/kbn-openapi-generator/tsconfig.json b/packages/kbn-openapi-generator/tsconfig.json new file mode 100644 index 00000000000000..465b739262cf82 --- /dev/null +++ b/packages/kbn-openapi-generator/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "outDir": "target/types", + "types": ["jest", "node"] + }, + "exclude": ["target/**/*"], + "extends": "../../tsconfig.base.json", + "include": ["**/*.ts"], + "kbn_references": [ + "@kbn/repo-info", + "@kbn/handlebars", + ] +} diff --git a/scripts/generate_openapi.js b/scripts/generate_openapi.js new file mode 100644 index 00000000000000..2dfae34bf46dd7 --- /dev/null +++ b/scripts/generate_openapi.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +require('../src/setup_node_env'); +require('@kbn/openapi-generator').runCli(); diff --git a/tsconfig.base.json b/tsconfig.base.json index 31381c82719626..a33f7b1cd960bd 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1072,6 +1072,8 @@ "@kbn/oidc-provider-plugin/*": ["x-pack/test/security_api_integration/plugins/oidc_provider/*"], "@kbn/open-telemetry-instrumented-plugin": ["test/common/plugins/otel_metrics"], "@kbn/open-telemetry-instrumented-plugin/*": ["test/common/plugins/otel_metrics/*"], + "@kbn/openapi-generator": ["packages/kbn-openapi-generator"], + "@kbn/openapi-generator/*": ["packages/kbn-openapi-generator/*"], "@kbn/optimizer": ["packages/kbn-optimizer"], "@kbn/optimizer/*": ["packages/kbn-optimizer/*"], "@kbn/optimizer-webpack-helpers": ["packages/kbn-optimizer-webpack-helpers"], diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/warning_schema.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/warning_schema.gen.ts index 9bb7d32c19645b..c99e7d77da8300 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/warning_schema.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/warning_schema.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type WarningSchema = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen.ts index 9f9abdf51dc4e6..f68164f5bc12b0 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type GetPrebuiltRulesAndTimelinesStatusResponse = z.infer< @@ -20,30 +20,30 @@ export const GetPrebuiltRulesAndTimelinesStatusResponse = z /** * The total number of custom rules */ - rules_custom_installed: z.number().min(0), + rules_custom_installed: z.number().int().min(0), /** * The total number of installed prebuilt rules */ - rules_installed: z.number().min(0), + rules_installed: z.number().int().min(0), /** * The total number of available prebuilt rules that are not installed */ - rules_not_installed: z.number().min(0), + rules_not_installed: z.number().int().min(0), /** * The total number of outdated prebuilt rules */ - rules_not_updated: z.number().min(0), + rules_not_updated: z.number().int().min(0), /** * The total number of installed prebuilt timelines */ - timelines_installed: z.number().min(0), + timelines_installed: z.number().int().min(0), /** * The total number of available prebuilt timelines that are not installed */ - timelines_not_installed: z.number().min(0), + timelines_not_installed: z.number().int().min(0), /** * The total number of outdated prebuilt timelines */ - timelines_not_updated: z.number().min(0), + timelines_not_updated: z.number().int().min(0), }) .strict(); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts index a36476054dfdc7..36a15ffc5f7018 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { Page, PageSize, StartDate, EndDate, AgentId } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts index b9557d98e87f97..546878e699cd91 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type DetailsRequestParams = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts index e1176c167fcf49..dbd24eef454d31 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { BaseActionSchema, Command, Timeout } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts index 0b70a7676e069d..945a03f0d38d2f 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type FileDownloadRequestParams = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts index 1e4e7813d35b8f..ef9a40462334e9 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type FileInfoRequestParams = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts index af03b239d39138..785a4a1097e0cd 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { BaseActionSchema } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts index d8109d433fab44..648f1700a54ca1 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { BaseActionSchema } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts index f734092c220581..32844921170bd2 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { @@ -31,7 +31,7 @@ export const ListRequestQuery = z.object({ /** * Number of items per page */ - pageSize: z.number().min(1).max(10000).optional().default(10), + pageSize: z.number().int().min(1).max(10000).optional().default(10), startDate: StartDate.optional(), endDate: EndDate.optional(), userIds: UserIds.optional(), diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.gen.ts index fca1370559ada4..d56463621d3c89 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type ListRequestQuery = z.infer; @@ -17,11 +17,11 @@ export const ListRequestQuery = z.object({ /** * Page number */ - page: z.number().min(0).optional().default(0), + page: z.number().int().min(0).optional().default(0), /** * Number of items per page */ - pageSize: z.number().min(1).max(10000).optional().default(10), + pageSize: z.number().int().min(1).max(10000).optional().default(10), kuery: z.string().nullable().optional(), sortField: z .enum([ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts index 89f15504c4be5e..2dffbc473ecc6f 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type Id = z.infer; @@ -22,13 +22,13 @@ export const IdOrUndefined = Id.nullable(); * Page number */ export type Page = z.infer; -export const Page = z.number().min(1).default(1); +export const Page = z.number().int().min(1).default(1); /** * Number of items per page */ export type PageSize = z.infer; -export const PageSize = z.number().min(1).max(100).default(10); +export const PageSize = z.number().int().min(1).max(100).default(10); /** * Start date @@ -65,6 +65,8 @@ export const Command = z.enum([ 'execute', 'upload', ]); +export const CommandEnum = Command.enum; +export type CommandEnum = typeof Command.enum; export type Commands = z.infer; export const Commands = z.array(Command); @@ -73,10 +75,12 @@ export const Commands = z.array(Command); * The maximum timeout value in milliseconds (optional) */ export type Timeout = z.infer; -export const Timeout = z.number().min(1); +export const Timeout = z.number().int().min(1); export type Status = z.infer; export const Status = z.enum(['failed', 'pending', 'successful']); +export const StatusEnum = Status.enum; +export type StatusEnum = typeof Status.enum; export type Statuses = z.infer; export const Statuses = z.array(Status); @@ -95,6 +99,8 @@ export const WithOutputs = z.union([z.array(z.string().min(1)).min(1), z.string( export type Type = z.infer; export const Type = z.enum(['automated', 'manual']); +export const TypeEnum = Type.enum; +export type TypeEnum = typeof Type.enum; export type Types = z.infer; export const Types = z.array(Type); @@ -143,7 +149,7 @@ export const ProcessActionSchemas = BaseActionSchema.and( z.object({ parameters: z.union([ z.object({ - pid: z.number().min(1).optional(), + pid: z.number().int().min(1).optional(), }), z.object({ entity_id: z.string().min(1).optional(), diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts index 3e511f7b4aad47..0b81cbdccd8825 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { SuccessResponse, AgentId } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.gen.ts index a827c94d3b5fdb..c1bc91e6ded867 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { SuccessResponse } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/scripts/openapi/generate.js b/x-pack/plugins/security_solution/scripts/openapi/generate.js index bd88357a3754d0..77446122827505 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/generate.js +++ b/x-pack/plugins/security_solution/scripts/openapi/generate.js @@ -6,6 +6,13 @@ */ require('../../../../../src/setup_node_env'); -const { generate } = require('./openapi_generator'); +const { generate } = require('@kbn/openapi-generator'); +const { resolve } = require('path'); -generate(); +const SECURITY_SOLUTION_ROOT = resolve(__dirname, '../..'); + +generate({ + rootDir: SECURITY_SOLUTION_ROOT, + sourceGlob: './**/*.schema.yaml', + templateName: 'zod_operation_schema', +}); diff --git a/x-pack/plugins/security_solution/scripts/openapi/openapi_generator.ts b/x-pack/plugins/security_solution/scripts/openapi/openapi_generator.ts deleted file mode 100644 index 272e62061c6a49..00000000000000 --- a/x-pack/plugins/security_solution/scripts/openapi/openapi_generator.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* eslint-disable no-console */ - -import SwaggerParser from '@apidevtools/swagger-parser'; -import chalk from 'chalk'; -import fs from 'fs/promises'; -import globby from 'globby'; -import { resolve } from 'path'; -import { fixEslint } from './lib/fix_eslint'; -import { formatOutput } from './lib/format_output'; -import { removeGenArtifacts } from './lib/remove_gen_artifacts'; -import { getApiOperationsList } from './parsers/get_api_operations_list'; -import { getComponents } from './parsers/get_components'; -import { getImportsMap } from './parsers/get_imports_map'; -import type { OpenApiDocument } from './parsers/openapi_types'; -import { initTemplateService } from './template_service/template_service'; - -const ROOT_SECURITY_SOLUTION_FOLDER = resolve(__dirname, '../..'); -const COMMON_API_FOLDER = resolve(ROOT_SECURITY_SOLUTION_FOLDER, './common/api'); -const SCHEMA_FILES_GLOB = resolve(ROOT_SECURITY_SOLUTION_FOLDER, './**/*.schema.yaml'); -const GENERATED_ARTIFACTS_GLOB = resolve(COMMON_API_FOLDER, './**/*.gen.ts'); - -export const generate = async () => { - console.log(chalk.bold(`Generating API route schemas`)); - console.log(chalk.bold(`Working directory: ${chalk.underline(COMMON_API_FOLDER)}`)); - - console.log(`๐Ÿ‘€ Searching for schemas`); - const schemaPaths = await globby([SCHEMA_FILES_GLOB]); - - console.log(`๐Ÿ•ต๏ธโ€โ™€๏ธ Found ${schemaPaths.length} schemas, parsing`); - const parsedSchemas = await Promise.all( - schemaPaths.map(async (schemaPath) => { - const parsedSchema = (await SwaggerParser.parse(schemaPath)) as OpenApiDocument; - return { schemaPath, parsedSchema }; - }) - ); - - console.log(`๐Ÿงน Cleaning up any previously generated artifacts`); - await removeGenArtifacts(COMMON_API_FOLDER); - - console.log(`๐Ÿช„ Generating new artifacts`); - const TemplateService = await initTemplateService(); - await Promise.all( - parsedSchemas.map(async ({ schemaPath, parsedSchema }) => { - const components = getComponents(parsedSchema); - const apiOperations = getApiOperationsList(parsedSchema); - const importsMap = getImportsMap(parsedSchema); - - // If there are no operations or components to generate, skip this file - const shouldGenerate = apiOperations.length > 0 || components !== undefined; - if (!shouldGenerate) { - return; - } - - const result = TemplateService.compileTemplate('schemas', { - components, - apiOperations, - importsMap, - }); - - // Write the generation result to disk - await fs.writeFile(schemaPath.replace('.schema.yaml', '.gen.ts'), result); - }) - ); - - // Format the output folder using prettier as the generator produces - // unformatted code and fix any eslint errors - console.log(`๐Ÿ’… Formatting output`); - await formatOutput(GENERATED_ARTIFACTS_GLOB); - await fixEslint(GENERATED_ARTIFACTS_GLOB); -}; diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index e73140fec20b72..3dbc778394ecbd 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -170,8 +170,8 @@ "@kbn/core-logging-server-mocks", "@kbn/core-lifecycle-browser", "@kbn/security-solution-features", - "@kbn/handlebars", "@kbn/content-management-plugin", - "@kbn/subscription-tracking" + "@kbn/subscription-tracking", + "@kbn/openapi-generator" ] } diff --git a/yarn.lock b/yarn.lock index 83eb6e673dc60c..84df6498ff2507 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5087,6 +5087,10 @@ version "0.0.0" uid "" +"@kbn/openapi-generator@link:packages/kbn-openapi-generator": + version "0.0.0" + uid "" + "@kbn/optimizer-webpack-helpers@link:packages/kbn-optimizer-webpack-helpers": version "0.0.0" uid ""