From d323bb723d1da09e022b234eb465811872c64872 Mon Sep 17 00:00:00 2001 From: Dmitrii Date: Tue, 12 Sep 2023 16:18:26 +0200 Subject: [PATCH] Extract OpeanAPI codegen to a package --- .github/CODEOWNERS | 1 + package.json | 5 +- packages/kbn-openapi-generator/README.md | 1 + packages/kbn-openapi-generator/index.ts | 9 +++ packages/kbn-openapi-generator/jest.config.js | 13 ++++ packages/kbn-openapi-generator/kibana.jsonc | 6 ++ packages/kbn-openapi-generator/package.json | 7 ++ .../src}/lib/fix_eslint.ts | 11 ++- .../src}/lib/format_output.ts | 5 +- .../src/lib/get_generated_file_path.ts | 11 +++ .../src}/lib/remove_gen_artifacts.ts | 5 +- .../src/openapi_generator.ts | 77 +++++++++++++++++++ .../src/parser/get_generation_context.ts | 34 ++++++++ .../parser/lib}/get_api_operations_list.ts | 24 ++++-- .../src/parser/lib}/get_components.ts | 8 +- .../src/parser/lib}/get_imports_map.ts | 28 +++---- .../src/parser/lib/normalize_schema.ts | 36 +++++++++ .../src/parser/lib/traverse_object.ts | 31 ++++++++ .../src/parser}/openapi_types.ts | 30 ++++++-- .../src}/template_service/register_helpers.ts | 17 +--- .../template_service/register_templates.ts | 5 +- .../src}/template_service/template_service.ts | 21 ++--- .../templates/disclaimer.handlebars | 0 .../templates/zod_operation_schema.handlebars | 18 +++-- .../templates/zod_query_item.handlebars | 51 ++++++++++++ .../templates/zod_schema_item.handlebars | 28 ++++--- packages/kbn-openapi-generator/tsconfig.json | 10 +++ tsconfig.base.json | 2 + ...lt_rules_and_timelines_status_route.gen.ts | 14 ++-- .../common/api/endpoint/actions/list.gen.ts | 2 +- .../endpoint/metadata/list_metadata.gen.ts | 4 +- .../api/endpoint/model/schema/common.gen.ts | 14 +++- .../scripts/openapi/generate.js | 11 ++- .../scripts/openapi/openapi_generator.ts | 77 ------------------- yarn.lock | 4 + 35 files changed, 428 insertions(+), 192 deletions(-) create mode 100644 packages/kbn-openapi-generator/README.md create mode 100644 packages/kbn-openapi-generator/index.ts create mode 100644 packages/kbn-openapi-generator/jest.config.js create mode 100644 packages/kbn-openapi-generator/kibana.jsonc create mode 100644 packages/kbn-openapi-generator/package.json rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/lib/fix_eslint.ts (62%) rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/lib/format_output.ts (61%) create mode 100644 packages/kbn-openapi-generator/src/lib/get_generated_file_path.ts rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/lib/remove_gen_artifacts.ts (75%) create mode 100644 packages/kbn-openapi-generator/src/openapi_generator.ts create mode 100644 packages/kbn-openapi-generator/src/parser/get_generation_context.ts rename {x-pack/plugins/security_solution/scripts/openapi/parsers => packages/kbn-openapi-generator/src/parser/lib}/get_api_operations_list.ts (87%) rename {x-pack/plugins/security_solution/scripts/openapi/parsers => packages/kbn-openapi-generator/src/parser/lib}/get_components.ts (59%) rename {x-pack/plugins/security_solution/scripts/openapi/parsers => packages/kbn-openapi-generator/src/parser/lib}/get_imports_map.ts (76%) create mode 100644 packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts create mode 100644 packages/kbn-openapi-generator/src/parser/lib/traverse_object.ts rename {x-pack/plugins/security_solution/scripts/openapi/parsers => packages/kbn-openapi-generator/src/parser}/openapi_types.ts (62%) rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/template_service/register_helpers.ts (75%) rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/template_service/register_templates.ts (83%) rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/template_service/template_service.ts (60%) rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/template_service/templates/disclaimer.handlebars (100%) rename x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schemas.handlebars => packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars (75%) create mode 100644 packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars rename x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schema_item.handlebars => packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars (79%) create mode 100644 packages/kbn-openapi-generator/tsconfig.json delete mode 100644 x-pack/plugins/security_solution/scripts/openapi/openapi_generator.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5f7502062abdc3d..99856e045af722b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -537,6 +537,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-rule-management 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 08d889905d37d22..206621af0632b7a 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": { @@ -1200,6 +1200,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 000000000000000..8391141c5fcaa8c --- /dev/null +++ b/packages/kbn-openapi-generator/README.md @@ -0,0 +1 @@ +# kbn-openapi-generator diff --git a/packages/kbn-openapi-generator/index.ts b/packages/kbn-openapi-generator/index.ts new file mode 100644 index 000000000000000..d40d74ef70968a8 --- /dev/null +++ b/packages/kbn-openapi-generator/index.ts @@ -0,0 +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 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'; diff --git a/packages/kbn-openapi-generator/jest.config.js b/packages/kbn-openapi-generator/jest.config.js new file mode 100644 index 000000000000000..6b5b1fce1c4d470 --- /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 000000000000000..b507d94ec022d65 --- /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 000000000000000..8d72a1a87886562 --- /dev/null +++ b/packages/kbn-openapi-generator/package.json @@ -0,0 +1,7 @@ +{ + "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/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 23d8bf540f73172..c205dbcebf1644c 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 6c374aa1f06d253..7b50b0732009bb2 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 000000000000000..4139d3610d8b659 --- /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 3cbf421b8c94b3c..45933864faf8f08 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 000000000000000..539994a25812649 --- /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 000000000000000..14a3b8eb2e178ec --- /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 c9d9a75c078541f..ac63820555419d3 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 5b3fef72905c0e6..6e98793de1afb60 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 8e068b61ba03448..c2259052e0cb6f9 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 000000000000000..0ea2062e369c3a8 --- /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 000000000000000..2049465e0753b73 --- /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 2449f34fa4b768f..c8b1e4f71534560 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 b3bb02f7743c895..1431dafcdfba9fa 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 fa39b52d99471b2..b8f9711ecb1a66c 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 becb02bb54ebe2f..4fd780b5e5b920e 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,21 @@ /* * 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 type TemplateName = 'schemas'; +export type TemplateName = 'zod_operation_schema'; export interface ITemplateService { - compileTemplate: (templateName: TemplateName, context: TemplateContext) => string; + compileTemplate: (templateName: TemplateName, context: GenerationContext) => string; } /** @@ -36,7 +29,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 100% 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 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 a6df5d96b124fe8..0b129b3aa13ed9f 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 000000000000000..0b718d941cbed22 --- /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 2d544a702ac0040..dbf156b6a7b125e 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 000000000000000..168274f98f0585b --- /dev/null +++ b/packages/kbn-openapi-generator/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "outDir": "target/types", + "types": ["jest", "node"] + }, + "exclude": ["target/**/*"], + "extends": "../../tsconfig.base.json", + "include": ["**/*.ts"], + "kbn_references": [] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 2c1168feaf0c5ca..ea7e1e848992f87 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1068,6 +1068,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/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 9f9abdf51dc4e68..10af415779f54ba 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 @@ -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/list.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts index f734092c2205818..acd07e71404a7a6 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 @@ -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 fca1370559ada43..74f73937c69b725 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 @@ -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 89f15504c4be5e0..313ea627509806b 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 @@ -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/scripts/openapi/generate.js b/x-pack/plugins/security_solution/scripts/openapi/generate.js index bd88357a3754d0c..774461228275053 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 272e62061c6a492..000000000000000 --- 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/yarn.lock b/yarn.lock index 14c9d60a29e6c17..82d48bf8a6cd255 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5078,6 +5078,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 ""