From 00c6b272dbce1893e120a1135c4bdc5834d360e3 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sun, 3 Mar 2024 16:28:46 -0500 Subject: [PATCH] it works! Signed-off-by: aramissennyeydd Signed-off-by: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Signed-off-by: aramissennyeydd --- packages/backend-openapi-utils/src/index.ts | 6 +- packages/backend-openapi-utils/src/stub.ts | 52 ++- .../src/types/generated.ts | 188 ++++++++++ .../backend-openapi-utils/src/types/index.ts | 1 + .../generated/models/NullableEntity.model.ts | 4 +- .../src/commands/openapi/server/generate.ts | 101 ------ .../package/schema/openapi/generate/server.ts | 38 ++ .../typescript-backstage.client.yaml | 3 + .../typescript-backstage.server.yaml | 5 +- .../{ => client}/index.mustache | 0 .../modelGeneric.mustache | 4 +- .../typescript-backstage/server/api.mustache | 54 ++- .../server/apis/index.mustache | 3 + .../server/content.mustache | 61 ---- .../server/index.mustache | 3 + .../src/generated/.openapi-generator-ignore | 25 -- .../src/generated/.openapi-generator/FILES | 3 - .../src/generated/.openapi-generator/VERSION | 1 - .../src/generated/apis/DefaultApi.client.ts | 340 ------------------ .../src/generated/apis/DefaultApi.server.ts | 234 ++++++------ .../src/generated/apis/index.ts | 3 +- .../src/generated/apis/router.ts | 207 ----------- .../catalog-backend/src/generated/index.ts | 12 +- .../src/schema/openapi.generated.ts | 14 +- .../src/service/createRouter.ts | 5 +- 25 files changed, 435 insertions(+), 932 deletions(-) create mode 100644 packages/backend-openapi-utils/src/types/generated.ts delete mode 100644 packages/repo-tools/src/commands/openapi/server/generate.ts rename packages/repo-tools/templates/typescript-backstage/{ => client}/index.mustache (100%) create mode 100644 packages/repo-tools/templates/typescript-backstage/server/apis/index.mustache delete mode 100644 packages/repo-tools/templates/typescript-backstage/server/content.mustache create mode 100644 packages/repo-tools/templates/typescript-backstage/server/index.mustache delete mode 100644 plugins/catalog-backend/src/generated/.openapi-generator-ignore delete mode 100644 plugins/catalog-backend/src/generated/.openapi-generator/FILES delete mode 100644 plugins/catalog-backend/src/generated/.openapi-generator/VERSION delete mode 100644 plugins/catalog-backend/src/generated/apis/DefaultApi.client.ts delete mode 100644 plugins/catalog-backend/src/generated/apis/router.ts diff --git a/packages/backend-openapi-utils/src/index.ts b/packages/backend-openapi-utils/src/index.ts index 320ad75e25..6930ada0d6 100644 --- a/packages/backend-openapi-utils/src/index.ts +++ b/packages/backend-openapi-utils/src/index.ts @@ -36,5 +36,9 @@ export type { RequestMatcherByModelAndPathParams, } from './types/express'; export type { PathTemplate } from './types/common'; -export { createValidatedOpenApiRouter, getOpenApiSpecRoute } from './stub'; export { wrapInOpenApiTestServer, wrapServer } from './testUtils'; +export { + createValidatedOpenApiRouter, + getOpenApiSpecRoute, + createValidatedOpenApiRouterFromGeneratedEndpointMap, +} from './stub'; diff --git a/packages/backend-openapi-utils/src/stub.ts b/packages/backend-openapi-utils/src/stub.ts index 0f62bf348c..615c32cb31 100644 --- a/packages/backend-openapi-utils/src/stub.ts +++ b/packages/backend-openapi-utils/src/stub.ts @@ -16,7 +16,7 @@ import PromiseRouter from 'express-promise-router'; import { ApiRouter } from './router'; -import { RequiredDoc } from './types'; +import { EndpointMap, RequiredDoc, TypedRouter } from './types'; import { ErrorRequestHandler, RequestHandler, @@ -24,6 +24,7 @@ import { Request, Response, json, + Router, } from 'express'; import { InputError } from '@backstage/errors'; import { middleware as OpenApiValidator } from 'express-openapi-validator'; @@ -58,19 +59,19 @@ export function getOpenApiSpecRoute(baseUrl: string) { } /** - * Create a new OpenAPI router with some default middleware. + * Create a router with validation middleware. This is used by typing methods to create an + * "OpenAPI router" with all of the expected validation + metadata. * @param spec - Your OpenAPI spec imported as a JSON object. * @param validatorOptions - `openapi-express-validator` options to override the defaults. * @returns A new express router with validation middleware. - * @public */ -export function createValidatedOpenApiRouter( - spec: T, +function createRouterWithValidation( + spec: RequiredDoc, options?: { validatorOptions?: Partial['0']>; middleware?: RequestHandler[]; }, -) { +): Router { const router = PromiseRouter(); router.use(options?.middleware || getDefaultRouterMiddleware()); @@ -152,6 +153,41 @@ export function createValidatedOpenApiRouter( } res.json(mergeOutput.output); }); - - return router as ApiRouter; + return router; +} + +/** + * Create a new OpenAPI router with some default middleware. + * @param spec - Your OpenAPI spec imported as a JSON object. + * @param validatorOptions - `openapi-express-validator` options to override the defaults. + * @returns A new express router with validation middleware. + * @public + */ +export function createValidatedOpenApiRouter( + spec: T, + options?: { + validatorOptions?: Partial['0']>; + middleware?: RequestHandler[]; + }, +) { + return createRouterWithValidation(spec, options) as ApiRouter; +} + +/** + * Create a new OpenAPI router with some default middleware. + * @param spec - Your OpenAPI spec imported as a JSON object. + * @param validatorOptions - `openapi-express-validator` options to override the defaults. + * @returns A new express router with validation middleware. + * @public + */ +export function createValidatedOpenApiRouterFromGeneratedEndpointMap< + T extends EndpointMap, +>( + spec: RequiredDoc, + options?: { + validatorOptions?: Partial['0']>; + middleware?: RequestHandler[]; + }, +) { + return createRouterWithValidation(spec, options) as TypedRouter; } diff --git a/packages/backend-openapi-utils/src/types/generated.ts b/packages/backend-openapi-utils/src/types/generated.ts new file mode 100644 index 0000000000..31c1f7c28f --- /dev/null +++ b/packages/backend-openapi-utils/src/types/generated.ts @@ -0,0 +1,188 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Router } from 'express'; +import type core from 'express-serve-static-core'; +import { PathTemplate, ValueOf } from './common'; + +export type EndpointMap = Record< + string, + { query?: object; body?: object; response: object | void; path?: object } +>; + +type UnknownIfVoid = T extends void ? unknown : T; + +// OpenAPI generator doesn't emit regular lowercase 'delete'. +type HttpMethods = 'all' | 'put' | 'get' | 'post' | '_delete'; + +type PathSchema< + Doc extends EndpointMap, + Endpoint extends DocEndpoint, + Method extends DocEndpointMethod, +> = `#${Method}|${Endpoint}` extends keyof Doc + ? 'path' extends keyof Doc[`#${Method}|${Endpoint}`] + ? Doc[`#${Method}|${Endpoint}`]['path'] + : never + : never; +type RequestBody< + Doc extends EndpointMap, + Endpoint extends DocEndpoint, + Method extends DocEndpointMethod, +> = `#${Method}|${Endpoint}` extends keyof Doc + ? 'body' extends keyof Doc[`#${Method}|${Endpoint}`] + ? Doc[`#${Method}|${Endpoint}`]['body'] + : unknown + : unknown; +type ResponseBody< + Doc extends EndpointMap, + Endpoint extends DocEndpoint, + Method extends DocEndpointMethod, +> = `#${Method}|${Endpoint}` extends keyof Doc + ? 'response' extends keyof Doc[`#${Method}|${Endpoint}`] + ? UnknownIfVoid + : unknown + : unknown; +type QuerySchema< + Doc extends EndpointMap, + Endpoint extends DocEndpoint, + Method extends DocEndpointMethod, +> = `#${Method}|${Endpoint}` extends keyof Doc + ? 'query' extends keyof Doc[`#${Method}|${Endpoint}`] + ? Doc[`#${Method}|${Endpoint}`]['query'] + : never + : never; + +/** + * Typed express request handler. + * @public + */ +export type EndpointMapRequestHandler< + Doc extends EndpointMap, + Path extends DocEndpoint, + Method extends DocEndpointMethod, +> = core.RequestHandler< + PathSchema, + ResponseBody, + RequestBody, + QuerySchema, + Record +>; + +/** + * Typed express error handler / request handler union type. + * @public + */ +export type EndpointMapRequestHandlerParams< + Doc extends EndpointMap, + Path extends DocEndpoint, + Method extends DocEndpointMethod, +> = core.RequestHandlerParams< + PathSchema, + ResponseBody, + RequestBody, + QuerySchema, + Record +>; + +type DocEndpoint = ValueOf<{ + [Template in keyof Doc]: Template extends `#${string}|${infer Endpoint}` + ? Endpoint + : never; +}>; + +type DocEndpointMethod< + Doc extends EndpointMap, + Endpoint extends DocEndpoint, +> = ValueOf<{ + [Template in keyof Doc]: Template extends `#${infer Method}|${Endpoint}` + ? Method + : never; +}>; + +type MethodAwareDocEndpoints< + Doc extends EndpointMap, + Endpoint extends DocEndpoint, + Method extends DocEndpointMethod, +> = ValueOf<{ + [Template in keyof Doc]: Template extends `#${Method}|${infer E}` + ? E extends DocEndpoint + ? PathTemplate + : never + : never; +}>; + +type DocEndpointTemplate = PathTemplate< + DocEndpoint +>; + +export type TemplateToDocEndpoint< + Doc extends EndpointMap, + Path extends DocEndpointTemplate, +> = ValueOf<{ + [Template in DocEndpoint]: Path extends PathTemplate