From ddf9e55c3ae652b93ade6d44da655a567b2da1e4 Mon Sep 17 00:00:00 2001 From: Aramis Date: Sun, 21 Jan 2024 10:35:02 -0500 Subject: [PATCH 01/43] initial work 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 | 5 + .../src/types/express.ts | 26 ++ packages/catalog-client/src/index.ts | 4 +- .../src/generated/.openapi-generator-ignore | 23 ++ .../src/commands/openapi/server/generate.ts | 101 ++++++ .../package/schema/openapi/generate/client.ts | 2 +- ....yaml => typescript-backstage.client.yaml} | 28 +- .../typescript-backstage.server.yaml | 10 + .../{ => client}/api.mustache | 0 .../{ => client}/apis/index.mustache | 0 .../{ => client}/index.mustache | 0 .../{ => client}/model.mustache | 0 .../{ => client}/models/models_all.mustache | 0 .../{ => client}/pluginId.mustache | 0 .../{ => client}/types/discovery.ts | 0 .../{ => client}/types/fetch.ts | 0 .../typescript-backstage/server/api.mustache | 45 +++ .../server/content.mustache | 61 ++++ plugins/catalog-backend/package.json | 1 + .../src/generated/.openapi-generator-ignore | 23 ++ .../src/generated/.openapi-generator/FILES | 3 + .../src/generated/.openapi-generator/VERSION | 1 + .../src/generated/apis/DefaultApi.client.ts | 301 ++++++++++++++++++ .../src/generated/apis/index.ts | 18 ++ .../catalog-backend/src/generated/index.ts | 27 ++ .../src/schema/openapi.generated.ts | 1 + .../src/service/createRouter.ts | 5 +- yarn.lock | 1 + 28 files changed, 668 insertions(+), 18 deletions(-) create mode 100644 packages/repo-tools/plugins/catalog-backend/src/generated/.openapi-generator-ignore create mode 100644 packages/repo-tools/src/commands/openapi/server/generate.ts rename packages/repo-tools/templates/{typescript-backstage.yaml => typescript-backstage.client.yaml} (59%) create mode 100644 packages/repo-tools/templates/typescript-backstage.server.yaml rename packages/repo-tools/templates/typescript-backstage/{ => client}/api.mustache (100%) rename packages/repo-tools/templates/typescript-backstage/{ => client}/apis/index.mustache (100%) rename packages/repo-tools/templates/typescript-backstage/{ => client}/index.mustache (100%) rename packages/repo-tools/templates/typescript-backstage/{ => client}/model.mustache (100%) rename packages/repo-tools/templates/typescript-backstage/{ => client}/models/models_all.mustache (100%) rename packages/repo-tools/templates/typescript-backstage/{ => client}/pluginId.mustache (100%) rename packages/repo-tools/templates/typescript-backstage/{ => client}/types/discovery.ts (100%) rename packages/repo-tools/templates/typescript-backstage/{ => client}/types/fetch.ts (100%) create mode 100644 packages/repo-tools/templates/typescript-backstage/server/api.mustache create mode 100644 packages/repo-tools/templates/typescript-backstage/server/content.mustache create mode 100644 plugins/catalog-backend/src/generated/.openapi-generator-ignore create mode 100644 plugins/catalog-backend/src/generated/.openapi-generator/FILES create mode 100644 plugins/catalog-backend/src/generated/.openapi-generator/VERSION create mode 100644 plugins/catalog-backend/src/generated/apis/DefaultApi.client.ts create mode 100644 plugins/catalog-backend/src/generated/apis/index.ts create mode 100644 plugins/catalog-backend/src/generated/index.ts diff --git a/packages/backend-openapi-utils/src/index.ts b/packages/backend-openapi-utils/src/index.ts index 57d9f755c2..320ad75e25 100644 --- a/packages/backend-openapi-utils/src/index.ts +++ b/packages/backend-openapi-utils/src/index.ts @@ -31,5 +31,10 @@ export type { PathParameters, } from './utility'; export type { ApiRouter } from './router'; +export type { + RequestMatcherByModelAndPath, + RequestMatcherByModelAndPathParams, +} from './types/express'; +export type { PathTemplate } from './types/common'; export { createValidatedOpenApiRouter, getOpenApiSpecRoute } from './stub'; export { wrapInOpenApiTestServer, wrapServer } from './testUtils'; diff --git a/packages/backend-openapi-utils/src/types/express.ts b/packages/backend-openapi-utils/src/types/express.ts index 127400099a..05266dade4 100644 --- a/packages/backend-openapi-utils/src/types/express.ts +++ b/packages/backend-openapi-utils/src/types/express.ts @@ -100,3 +100,29 @@ export interface DocRequestMatcher< > ): T; } + +export type RequestMatcherByModelAndPath< + PathParams, + QueryParams, + RequestBody, + ResponseBody, +> = core.RequestHandler< + PathParams, + ResponseBody, + RequestBody, + QueryParams, + Record +>; + +export type RequestMatcherByModelAndPathParams< + PathParams, + QueryParams, + RequestBody, + ResponseBody, +> = core.RequestHandlerParams< + PathParams, + ResponseBody, + RequestBody, + QueryParams, + Record +>; diff --git a/packages/catalog-client/src/index.ts b/packages/catalog-client/src/index.ts index 2e81942c2f..63748fa121 100644 --- a/packages/catalog-client/src/index.ts +++ b/packages/catalog-client/src/index.ts @@ -21,4 +21,6 @@ */ export { CatalogClient } from './CatalogClient'; -export * from './types'; +// export * from './types'; + +export * from './generated/models'; diff --git a/packages/repo-tools/plugins/catalog-backend/src/generated/.openapi-generator-ignore b/packages/repo-tools/plugins/catalog-backend/src/generated/.openapi-generator-ignore new file mode 100644 index 0000000000..1b59086ab7 --- /dev/null +++ b/packages/repo-tools/plugins/catalog-backend/src/generated/.openapi-generator-ignore @@ -0,0 +1,23 @@ +*.md +apis/baseapi.ts +apis/exception.ts +auth/* +http/* +middleware.ts +servers.ts +util.ts +configuration.ts +rxjsStub.ts +.gitignore +apis/*.ts +!apis/*.client.ts +models/*.ts +!models/*.model.ts +!index.ts +!**/index.ts +types/ObjectParamAPI.ts +types/ObservableAPI.ts +types/PromiseAPI.ts +git_push.sh +package.json +tsconfig.json \ No newline at end of file diff --git a/packages/repo-tools/src/commands/openapi/server/generate.ts b/packages/repo-tools/src/commands/openapi/server/generate.ts new file mode 100644 index 0000000000..380043efab --- /dev/null +++ b/packages/repo-tools/src/commands/openapi/server/generate.ts @@ -0,0 +1,101 @@ +/* + * Copyright 2023 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 chalk from 'chalk'; +import { resolve } from 'path'; +import { OPENAPI_IGNORE_FILES, OUTPUT_PATH } from '../constants'; +import { paths as cliPaths } from '../../../lib/paths'; +import { mkdirpSync } from 'fs-extra'; +import fs from 'fs-extra'; +import { exec } from '../../../lib/exec'; +import { resolvePackagePath } from '@backstage/backend-common'; + +async function generate(spec: string, outputDirectory: string) { + const resolvedOpenapiPath = resolve(spec); + const resolvedOutputDirectory = resolve(outputDirectory, OUTPUT_PATH); + mkdirpSync(resolvedOutputDirectory); + + await fs.mkdirp(resolvedOutputDirectory); + + await fs.writeFile( + resolve(resolvedOutputDirectory, '.openapi-generator-ignore'), + OPENAPI_IGNORE_FILES.join('\n'), + ); + + await exec( + 'node', + [ + resolvePackagePath('@openapitools/openapi-generator-cli', 'main.js'), + 'generate', + '-i', + resolvedOpenapiPath, + '-o', + resolvedOutputDirectory, + '-g', + 'typescript', + '-c', + resolvePackagePath( + '@backstage/repo-tools', + 'templates/typescript-backstage.server.yaml', + ), + `--additional-properties=clientPackageName=@backstage/catalog-client`, + '--generator-key', + 'v3.0', + ], + { + maxBuffer: Number.MAX_VALUE, + cwd: resolvePackagePath('@backstage/repo-tools'), + env: { + ...process.env, + }, + }, + ); + + await exec( + `yarn backstage-cli package lint --fix ${resolvedOutputDirectory}`, + ); + + const prettier = cliPaths.resolveTargetRoot('node_modules/.bin/prettier'); + if (prettier) { + await exec(`${prettier} --write ${resolvedOutputDirectory}`); + } + + fs.removeSync(resolve(resolvedOutputDirectory, '.openapi-generator-ignore')); + + fs.rmSync(resolve(resolvedOutputDirectory, '.openapi-generator'), { + recursive: true, + force: true, + }); +} + +export async function singleCommand({ + inputSpec, + outputDirectory, +}: { + inputSpec: string; + outputDirectory: string; +}): Promise { + try { + await generate(inputSpec, outputDirectory); + console.log(chalk.green(`Generated client for ${inputSpec}`)); + } catch (err) { + console.log(); + console.log(chalk.red(`Client generation failed in ${outputDirectory}:`)); + console.log(err); + + process.exit(1); + } +} diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts index 742f303f51..b9787a81d6 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts @@ -63,7 +63,7 @@ async function generate( '-c', resolvePackagePath( '@backstage/repo-tools', - 'templates/typescript-backstage.yaml', + 'templates/typescript-backstage.client.yaml', ), '--generator-key', 'v3.0', diff --git a/packages/repo-tools/templates/typescript-backstage.yaml b/packages/repo-tools/templates/typescript-backstage.client.yaml similarity index 59% rename from packages/repo-tools/templates/typescript-backstage.yaml rename to packages/repo-tools/templates/typescript-backstage.client.yaml index a2cb61c121..12f001654d 100644 --- a/packages/repo-tools/templates/typescript-backstage.yaml +++ b/packages/repo-tools/templates/typescript-backstage.client.yaml @@ -1,35 +1,35 @@ templateDir: templates/typescript-backstage files: - api.mustache: + client/api.mustache: templateType: API # For some reason, they check for destinationFilename differences. We have to change the ending to override the file. destinationFilename: .client.ts - model.mustache: + client/model.mustache: templateType: Model destinationFilename: .model.ts - modelGeneric.mustache: + client/models/modelGeneric.mustache: templateType: SupportingFiles - modelOneOf.mustache: + client/models/modelOneOf.mustache: templateType: SupportingFiles - modelGenericAdditionalProperties.mustache: + client/models/modelGenericAdditionalProperties.mustache: templateType: SupportingFiles - modelGenericEnums.mustache: + client/models/ modelGenericEnums.mustache: templateType: SupportingFiles - modelAlias.mustache: + client/models/modelAlias.mustache: templateType: SupportingFiles - modelEnum.mustache: + client/models/modelEnum.mustache: templateType: SupportingFiles - modelTaggedUnion.mustache: + client/models/modelTaggedUnion.mustache: templateType: SupportingFiles - models/models_all.mustache: + client/models/models_all.mustache: templateType: SupportingFiles destinationFilename: models/index.ts - types/fetch.ts: {} - types/discovery.ts: {} - apis/index.mustache: + client/types/fetch.ts: {} + client/types/discovery.ts: {} + client/apis/index.mustache: templateType: SupportingFiles destinationFilename: apis/index.ts - pluginId.mustache: + client/pluginId.mustache: templateType: SupportingFiles destinationFilename: pluginId.ts diff --git a/packages/repo-tools/templates/typescript-backstage.server.yaml b/packages/repo-tools/templates/typescript-backstage.server.yaml new file mode 100644 index 0000000000..546c3c181e --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage.server.yaml @@ -0,0 +1,10 @@ +templateDir: templates/typescript-backstage + +files: + server/api.mustache: + templateType: API + # For some reason, they check for destinationFilename differences. We have to change the ending to override the file. + destinationFilename: .client.ts + client/apis/index.mustache: + templateType: SupportingFiles + destinationFilename: apis/index.ts diff --git a/packages/repo-tools/templates/typescript-backstage/api.mustache b/packages/repo-tools/templates/typescript-backstage/client/api.mustache similarity index 100% rename from packages/repo-tools/templates/typescript-backstage/api.mustache rename to packages/repo-tools/templates/typescript-backstage/client/api.mustache diff --git a/packages/repo-tools/templates/typescript-backstage/apis/index.mustache b/packages/repo-tools/templates/typescript-backstage/client/apis/index.mustache similarity index 100% rename from packages/repo-tools/templates/typescript-backstage/apis/index.mustache rename to packages/repo-tools/templates/typescript-backstage/client/apis/index.mustache diff --git a/packages/repo-tools/templates/typescript-backstage/index.mustache b/packages/repo-tools/templates/typescript-backstage/client/index.mustache similarity index 100% rename from packages/repo-tools/templates/typescript-backstage/index.mustache rename to packages/repo-tools/templates/typescript-backstage/client/index.mustache diff --git a/packages/repo-tools/templates/typescript-backstage/model.mustache b/packages/repo-tools/templates/typescript-backstage/client/model.mustache similarity index 100% rename from packages/repo-tools/templates/typescript-backstage/model.mustache rename to packages/repo-tools/templates/typescript-backstage/client/model.mustache diff --git a/packages/repo-tools/templates/typescript-backstage/models/models_all.mustache b/packages/repo-tools/templates/typescript-backstage/client/models/models_all.mustache similarity index 100% rename from packages/repo-tools/templates/typescript-backstage/models/models_all.mustache rename to packages/repo-tools/templates/typescript-backstage/client/models/models_all.mustache diff --git a/packages/repo-tools/templates/typescript-backstage/pluginId.mustache b/packages/repo-tools/templates/typescript-backstage/client/pluginId.mustache similarity index 100% rename from packages/repo-tools/templates/typescript-backstage/pluginId.mustache rename to packages/repo-tools/templates/typescript-backstage/client/pluginId.mustache diff --git a/packages/repo-tools/templates/typescript-backstage/types/discovery.ts b/packages/repo-tools/templates/typescript-backstage/client/types/discovery.ts similarity index 100% rename from packages/repo-tools/templates/typescript-backstage/types/discovery.ts rename to packages/repo-tools/templates/typescript-backstage/client/types/discovery.ts diff --git a/packages/repo-tools/templates/typescript-backstage/types/fetch.ts b/packages/repo-tools/templates/typescript-backstage/client/types/fetch.ts similarity index 100% rename from packages/repo-tools/templates/typescript-backstage/types/fetch.ts rename to packages/repo-tools/templates/typescript-backstage/client/types/fetch.ts diff --git a/packages/repo-tools/templates/typescript-backstage/server/api.mustache b/packages/repo-tools/templates/typescript-backstage/server/api.mustache new file mode 100644 index 0000000000..7e1c82621c --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/server/api.mustache @@ -0,0 +1,45 @@ +{{>licenseInfo}} +import type { + PathTemplate,RequestMatcherByModelAndPath,RequestMatcherByModelAndPathParams +} from '@backstage/backend-openapi-utils'; +import type { +{{#imports}} + {{classname}}, +{{/imports}} +} from '{{clientPackageName}}'; +import {Router} from 'express'; + +type ExtendsString = Path; + + +{{#operations}} + +/** + * {{{description}}}{{^description}}no description{{/description}} + */ +{{! export interface TypedRouter extends Router { }} + + type InputOutput = { + {{#operation}} + + '#{{httpMethod}}|{{path}}': { + path: { + {{#pathParams}} + {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, + {{/pathParams}} + }, + query: { + {{#queryParams}} + {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, + {{/queryParams}} + }, + {{#bodyParam}} + body: {{{dataType}}}, + {{/bodyParam}} + response: {{{returnType}}}{{^returnType}}void{{/returnType}}, + }, + + {{/operation}} + } +{{! } }} +{{/operations}} diff --git a/packages/repo-tools/templates/typescript-backstage/server/content.mustache b/packages/repo-tools/templates/typescript-backstage/server/content.mustache new file mode 100644 index 0000000000..4ba5332132 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/server/content.mustache @@ -0,0 +1,61 @@ +{{#lambda.lowercase}}{{httpMethod}}({{/lambda.lowercase}} + + path: ExtendsString>, + ...handlers: Array> + ): this; + + {{#lambda.lowercase}}{{httpMethod}}({{/lambda.lowercase}} + + path: ExtendsString>, + ...handlers: Array> + ): this; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 12ca1a7be5..f3b55f929c 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -80,6 +80,7 @@ "@types/express": "^4.17.6", "codeowners-utils": "^1.0.2", "core-js": "^3.6.5", + "cross-fetch": "^4.0.0", "express": "^4.17.1", "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^11.2.0", diff --git a/plugins/catalog-backend/src/generated/.openapi-generator-ignore b/plugins/catalog-backend/src/generated/.openapi-generator-ignore new file mode 100644 index 0000000000..1b59086ab7 --- /dev/null +++ b/plugins/catalog-backend/src/generated/.openapi-generator-ignore @@ -0,0 +1,23 @@ +*.md +apis/baseapi.ts +apis/exception.ts +auth/* +http/* +middleware.ts +servers.ts +util.ts +configuration.ts +rxjsStub.ts +.gitignore +apis/*.ts +!apis/*.client.ts +models/*.ts +!models/*.model.ts +!index.ts +!**/index.ts +types/ObjectParamAPI.ts +types/ObservableAPI.ts +types/PromiseAPI.ts +git_push.sh +package.json +tsconfig.json \ No newline at end of file diff --git a/plugins/catalog-backend/src/generated/.openapi-generator/FILES b/plugins/catalog-backend/src/generated/.openapi-generator/FILES new file mode 100644 index 0000000000..d8308a8aa6 --- /dev/null +++ b/plugins/catalog-backend/src/generated/.openapi-generator/FILES @@ -0,0 +1,3 @@ +apis/DefaultApi.client.ts +apis/index.ts +index.ts diff --git a/plugins/catalog-backend/src/generated/.openapi-generator/VERSION b/plugins/catalog-backend/src/generated/.openapi-generator/VERSION new file mode 100644 index 0000000000..4be2c727ad --- /dev/null +++ b/plugins/catalog-backend/src/generated/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.5.0 \ No newline at end of file diff --git a/plugins/catalog-backend/src/generated/apis/DefaultApi.client.ts b/plugins/catalog-backend/src/generated/apis/DefaultApi.client.ts new file mode 100644 index 0000000000..43fff31806 --- /dev/null +++ b/plugins/catalog-backend/src/generated/apis/DefaultApi.client.ts @@ -0,0 +1,301 @@ +/* + * 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 type core from 'express-serve-static-core'; + + +import type { + AnalyzeLocationRequest, + AnalyzeLocationResponse, + CreateLocation201Response, + CreateLocationRequest, + EntitiesBatchResponse, + EntitiesQueryResponse, + Entity, + EntityAncestryResponse, + EntityFacetsResponse, + GetEntitiesByRefsRequest, + GetLocations200ResponseInner, + Location, + RefreshEntityRequest, + ValidateEntityRequest, +} from '@backstage/catalog-client'; +import { Router } from 'express'; + + +/** + * no description + */ + + type InputOutput = { + + '#POST|/analyze-location': { + path: { + }, + query: { + }, + body: AnalyzeLocationRequest, + response: AnalyzeLocationResponse, + }, + + + '#POST|/locations': { + path: { + }, + query: { + dryRun?: string, + }, + body: CreateLocationRequest, + response: CreateLocation201Response, + }, + + + '#DELETE|/entities/by-uid/{uid}': { + path: { + uid: string, + }, + query: { + }, + response: void, + }, + + + '#DELETE|/locations/{id}': { + path: { + id: string, + }, + query: { + }, + response: void, + }, + + + '#GET|/entities': { + path: { + }, + query: { + fields?: Array, + limit?: number, + filter?: Array, + offset?: number, + after?: string, + order?: Array, + }, + response: Array, + }, + + + '#GET|/entities/by-query': { + path: { + }, + query: { + fields?: Array, + limit?: number, + orderField?: Array, + cursor?: string, + filter?: Array, + fullTextFilterTerm?: string, + fullTextFilterFields?: Array, + }, + response: EntitiesQueryResponse, + }, + + + '#POST|/entities/by-refs': { + path: { + }, + query: { + }, + body: GetEntitiesByRefsRequest, + response: EntitiesBatchResponse, + }, + + + '#GET|/entities/by-name/{kind}/{namespace}/{name}/ancestry': { + path: { + kind: string, + namespace: string, + name: string, + }, + query: { + }, + response: EntityAncestryResponse, + }, + + + '#GET|/entities/by-name/{kind}/{namespace}/{name}': { + path: { + kind: string, + namespace: string, + name: string, + }, + query: { + }, + response: Entity, + }, + + + '#GET|/entities/by-uid/{uid}': { + path: { + uid: string, + }, + query: { + }, + response: Entity, + }, + + + '#GET|/entity-facets': { + path: { + }, + query: { + facet: Array, + filter?: Array, + }, + response: EntityFacetsResponse, + }, + + + '#GET|/locations/{id}': { + path: { + id: string, + }, + query: { + }, + response: Location, + }, + + + '#GET|/locations': { + path: { + }, + query: { + }, + response: Array, + }, + + + '#POST|/refresh': { + path: { + }, + query: { + }, + body: RefreshEntityRequest, + response: void, + }, + + + '#POST|/validate-entity': { + path: { + }, + query: { + }, + body: ValidateEntityRequest, + response: void, + }, + + }; + +type ValueOf = T[keyof T]; + +type Filter = T extends K ? K : never; + +type ExtractDocPath = Path extends `#${string}|${infer OpenApiPath}` ? OpenApiPath : never; + +type ExtractDocMethod = Path extends `#${infer Method}|${string}` ? Method : never + +type DocPath = ExtractDocPath>; + +type DocPathMethod = ExtractDocMethod; + +/** + * Typed express request handler. + * @public + */ +export type DocRequestHandler< + Doc extends InputOutput, + Path extends DocPath, + Method extends DocPathMethod, +> = core.RequestHandler< + PathSchema, + ResponseBodyToJsonSchema, + RequestBodyToJsonSchema, + QuerySchema, + Record +>; + +/** + * Typed express error handler / request handler union type. + * @public + */ +export type DocRequestHandlerParams< + Doc extends RequiredDoc, + Path extends DocPath, + Method extends DocPathMethod, +> = core.RequestHandlerParams< + PathSchema, + ResponseBodyToJsonSchema, + RequestBodyToJsonSchema, + QuerySchema, + Record +>; + +/** + * Superset of the express router path matcher that enforces typed request and response bodies. + * @public + */ +export interface DocRequestMatcher< + Doc extends RequiredDoc, + T, + Method extends + | 'all' + | 'get' + | 'post' + | 'put' + | 'delete' + | 'patch' + | 'options' + | 'head', +> { + < + Path extends MethodAwareDocPath< + Doc, + PathTemplate>, + Method + >, + >( + path: Path, + ...handlers: Array< + DocRequestHandler, Method> + > + ): T; + < + Path extends MethodAwareDocPath< + Doc, + PathTemplate>, + Method + >, + >( + path: Path, + ...handlers: Array< + DocRequestHandlerParams, Method> + > + ): T; +} + +export interface TypedRouter extends Router { + get: +} diff --git a/plugins/catalog-backend/src/generated/apis/index.ts b/plugins/catalog-backend/src/generated/apis/index.ts new file mode 100644 index 0000000000..3f4657f6ca --- /dev/null +++ b/plugins/catalog-backend/src/generated/apis/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + + +export * from './DefaultApi.client'; diff --git a/plugins/catalog-backend/src/generated/index.ts b/plugins/catalog-backend/src/generated/index.ts new file mode 100644 index 0000000000..ce0fac6ac4 --- /dev/null +++ b/plugins/catalog-backend/src/generated/index.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ +export * from "./http/http"; +export * from "./auth/auth"; +export * from "./models/all"; +export { createConfiguration } from "./configuration" +export { Configuration } from "./configuration" +export * from "./apis/exception"; +export * from "./servers"; +export { RequiredError } from "./apis/baseapi"; + +export { PromiseMiddleware as Middleware } from './middleware'; +export { PromiseDefaultApi as DefaultApi } from './types/PromiseAPI'; + diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index f3bd46c05c..ac3c93637a 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -18,6 +18,7 @@ // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import { createValidatedOpenApiRouter } from '@backstage/backend-openapi-utils'; +import { TypedRouter } from '../generated/apis'; export const spec = { openapi: '3.0.3', diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 4a89cfc6cb..d390fd4be5 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -57,6 +57,7 @@ import { } from '@backstage/backend-plugin-api'; import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; import { AuthorizedValidationService } from './AuthorizedValidationService'; +import { TypedRouter } from '../generated/apis'; /** * Options used by {@link createRouter}. @@ -85,13 +86,13 @@ export interface RouterOptions { export async function createRouter( options: RouterOptions, ): Promise { - const router = await createOpenApiRouter({ + const router = (await createOpenApiRouter({ validatorOptions: { // We want the spec to be up to date with the expected value, but the return type needs // to be controlled by the router implementation not the request validator. ignorePaths: /^\/validate-entity\/?$/, }, - }); + })) as TypedRouter; const { entitiesCatalog, locationAnalyzer, diff --git a/yarn.lock b/yarn.lock index ba52ed11df..40eadc3977 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6038,6 +6038,7 @@ __metadata: better-sqlite3: ^11.0.0 codeowners-utils: ^1.0.2 core-js: ^3.6.5 + cross-fetch: ^4.0.0 express: ^4.17.1 fast-json-stable-stringify: ^2.1.0 fs-extra: ^11.2.0 From be1db622eb97a4dbed9c96947cd6adc2b4bfca2b Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sun, 3 Mar 2024 11:10:36 -0500 Subject: [PATCH 02/43] fix templates Signed-off-by: aramissennyeydd Signed-off-by: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Signed-off-by: aramissennyeydd --- .../package/schema/openapi/generate/server.ts | 100 ++-- .../repo-tools/src/lib/openapi/constants.ts | 1 + .../typescript-backstage.client.yaml | 20 +- .../typescript-backstage.server.yaml | 2 +- .../{client => }/index.mustache | 0 .../typescript-backstage/server/api.mustache | 4 +- .../src/generated/.openapi-generator-ignore | 2 + .../src/generated/.openapi-generator/FILES | 2 +- .../src/generated/apis/DefaultApi.client.ts | 463 ++++++++++-------- .../src/generated/apis/DefaultApi.server.ts | 206 ++++++++ .../src/generated/apis/router.ts | 207 ++++++++ 11 files changed, 753 insertions(+), 254 deletions(-) rename packages/repo-tools/templates/typescript-backstage/{client => }/index.mustache (100%) create mode 100644 plugins/catalog-backend/src/generated/apis/DefaultApi.server.ts create mode 100644 plugins/catalog-backend/src/generated/apis/router.ts diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts index 1ef6fd10d2..eb0a043b9b 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts @@ -14,51 +14,85 @@ * limitations under the License. */ -import fs from 'fs-extra'; -import YAML from 'js-yaml'; import chalk from 'chalk'; +import { resolve } from 'path'; +import { + OPENAPI_IGNORE_FILES, + OUTPUT_PATH, +} from '../../../../../lib/openapi/constants'; import { paths as cliPaths } from '../../../../../lib/paths'; -import { TS_SCHEMA_PATH } from '../../../../../lib/openapi/constants'; -import { promisify } from 'util'; -import { exec as execCb } from 'child_process'; -import { getPathToCurrentOpenApiSpec } from '../../../../../lib/openapi/helpers'; - -const exec = promisify(execCb); +import { mkdirpSync } from 'fs-extra'; +import fs from 'fs-extra'; +import { exec } from '../../../../../lib/exec'; +import { resolvePackagePath } from '@backstage/backend-common'; +import { + getPathToCurrentOpenApiSpec, + getRelativePathToFile, +} from '../../../../../lib/openapi/helpers'; async function generate(abortSignal?: AbortController) { - const openapiPath = await getPathToCurrentOpenApiSpec(); - const yaml = YAML.load(await fs.readFile(openapiPath, 'utf8')); + const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); + const resolvedOutputDirectory = await getRelativePathToFile(OUTPUT_PATH); + mkdirpSync(resolvedOutputDirectory); - const tsPath = cliPaths.resolveTarget(TS_SCHEMA_PATH); + await fs.mkdirp(resolvedOutputDirectory); - // The first set of comment slashes allow for the eslint notice plugin to run - // with onNonMatchingHeader: 'replace', as is the case in the open source - // Backstage repo. Otherwise the auto-generated comment will be removed by the - // lint call below. await fs.writeFile( - tsPath, - `// - -// ****************************************************************** -// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * -// ****************************************************************** -import {createValidatedOpenApiRouter} from '@backstage/backend-openapi-utils'; -export const spec = ${JSON.stringify(yaml, null, 2)} as const; -export const createOpenApiRouter = async ( - options?: Parameters['1'], -) => createValidatedOpenApiRouter(spec, options); -`, + resolve(resolvedOutputDirectory, '.openapi-generator-ignore'), + OPENAPI_IGNORE_FILES.join('\n'), ); - await exec(`yarn backstage-cli package lint --fix ${tsPath}`, { - signal: abortSignal?.signal, - }); - if (await cliPaths.resolveTargetRoot('node_modules/.bin/prettier')) { - await exec(`yarn prettier --write ${tsPath}`, { - cwd: cliPaths.targetRoot, + await exec( + 'node', + [ + resolvePackagePath('@openapitools/openapi-generator-cli', 'main.js'), + 'generate', + '-i', + resolvedOpenapiPath, + '-o', + resolvedOutputDirectory, + '-g', + 'typescript', + '-c', + resolvePackagePath( + '@backstage/repo-tools', + 'templates/typescript-backstage.server.yaml', + ), + `--additional-properties=clientPackageName=@backstage/catalog-client`, + '--generator-key', + 'v3.0', + ], + { + maxBuffer: Number.MAX_VALUE, + cwd: resolvePackagePath('@backstage/repo-tools'), + env: { + ...process.env, + }, + signal: abortSignal?.signal, + }, + ); + + await exec( + `yarn backstage-cli package lint --fix ${resolvedOutputDirectory}`, + [], + { + signal: abortSignal?.signal, + }, + ); + + const prettier = cliPaths.resolveTargetRoot('node_modules/.bin/prettier'); + if (prettier) { + await exec(`${prettier} --write ${resolvedOutputDirectory}`, [], { signal: abortSignal?.signal, }); } + + fs.removeSync(resolve(resolvedOutputDirectory, '.openapi-generator-ignore')); + + fs.rmSync(resolve(resolvedOutputDirectory, '.openapi-generator'), { + recursive: true, + force: true, + }); } export async function command({ diff --git a/packages/repo-tools/src/lib/openapi/constants.ts b/packages/repo-tools/src/lib/openapi/constants.ts index 4936a5aee7..9a54a9a0ff 100644 --- a/packages/repo-tools/src/lib/openapi/constants.ts +++ b/packages/repo-tools/src/lib/openapi/constants.ts @@ -45,6 +45,7 @@ export const OPENAPI_IGNORE_FILES = [ // Override the created version. 'apis/*.ts', '!apis/*.client.ts', + '!apis/*.server.ts', 'models/*.ts', '!models/*.model.ts', diff --git a/packages/repo-tools/templates/typescript-backstage.client.yaml b/packages/repo-tools/templates/typescript-backstage.client.yaml index 12f001654d..e9f25c18ec 100644 --- a/packages/repo-tools/templates/typescript-backstage.client.yaml +++ b/packages/repo-tools/templates/typescript-backstage.client.yaml @@ -8,25 +8,27 @@ files: client/model.mustache: templateType: Model destinationFilename: .model.ts - client/models/modelGeneric.mustache: + modelGeneric.mustache: templateType: SupportingFiles - client/models/modelOneOf.mustache: + modelOneOf.mustache: templateType: SupportingFiles - client/models/modelGenericAdditionalProperties.mustache: + modelGenericAdditionalProperties.mustache: templateType: SupportingFiles - client/models/ modelGenericEnums.mustache: + modelGenericEnums.mustache: templateType: SupportingFiles - client/models/modelAlias.mustache: + modelAlias.mustache: templateType: SupportingFiles - client/models/modelEnum.mustache: + modelEnum.mustache: templateType: SupportingFiles - client/models/modelTaggedUnion.mustache: + modelTaggedUnion.mustache: templateType: SupportingFiles client/models/models_all.mustache: templateType: SupportingFiles destinationFilename: models/index.ts - client/types/fetch.ts: {} - client/types/discovery.ts: {} + client/types/fetch.ts: + destinationFilename: types/fetch.ts + client/types/discovery.ts: + destinationFilename: types/discovery.ts client/apis/index.mustache: templateType: SupportingFiles destinationFilename: apis/index.ts diff --git a/packages/repo-tools/templates/typescript-backstage.server.yaml b/packages/repo-tools/templates/typescript-backstage.server.yaml index 546c3c181e..06bb3ebdc0 100644 --- a/packages/repo-tools/templates/typescript-backstage.server.yaml +++ b/packages/repo-tools/templates/typescript-backstage.server.yaml @@ -4,7 +4,7 @@ files: server/api.mustache: templateType: API # For some reason, they check for destinationFilename differences. We have to change the ending to override the file. - destinationFilename: .client.ts + destinationFilename: .server.ts client/apis/index.mustache: templateType: SupportingFiles destinationFilename: apis/index.ts diff --git a/packages/repo-tools/templates/typescript-backstage/client/index.mustache b/packages/repo-tools/templates/typescript-backstage/index.mustache similarity index 100% rename from packages/repo-tools/templates/typescript-backstage/client/index.mustache rename to packages/repo-tools/templates/typescript-backstage/index.mustache diff --git a/packages/repo-tools/templates/typescript-backstage/server/api.mustache b/packages/repo-tools/templates/typescript-backstage/server/api.mustache index 7e1c82621c..7b64c49b5c 100644 --- a/packages/repo-tools/templates/typescript-backstage/server/api.mustache +++ b/packages/repo-tools/templates/typescript-backstage/server/api.mustache @@ -22,7 +22,8 @@ type ExtendsString = Path; type InputOutput = { {{#operation}} - '#{{httpMethod}}|{{path}}': { + '{{path}}': { + '{{httpMethod}}': { path: { {{#pathParams}} {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, @@ -37,6 +38,7 @@ type ExtendsString = Path; body: {{{dataType}}}, {{/bodyParam}} response: {{{returnType}}}{{^returnType}}void{{/returnType}}, + } }, {{/operation}} diff --git a/plugins/catalog-backend/src/generated/.openapi-generator-ignore b/plugins/catalog-backend/src/generated/.openapi-generator-ignore index 1b59086ab7..0561af9e08 100644 --- a/plugins/catalog-backend/src/generated/.openapi-generator-ignore +++ b/plugins/catalog-backend/src/generated/.openapi-generator-ignore @@ -1,4 +1,5 @@ *.md +*.mustache apis/baseapi.ts apis/exception.ts auth/* @@ -11,6 +12,7 @@ rxjsStub.ts .gitignore apis/*.ts !apis/*.client.ts +!apis/*.server.ts models/*.ts !models/*.model.ts !index.ts diff --git a/plugins/catalog-backend/src/generated/.openapi-generator/FILES b/plugins/catalog-backend/src/generated/.openapi-generator/FILES index d8308a8aa6..fa74778d78 100644 --- a/plugins/catalog-backend/src/generated/.openapi-generator/FILES +++ b/plugins/catalog-backend/src/generated/.openapi-generator/FILES @@ -1,3 +1,3 @@ -apis/DefaultApi.client.ts +apis/DefaultApi.server.ts apis/index.ts index.ts diff --git a/plugins/catalog-backend/src/generated/apis/DefaultApi.client.ts b/plugins/catalog-backend/src/generated/apis/DefaultApi.client.ts index 43fff31806..7d7fa2ecd3 100644 --- a/plugins/catalog-backend/src/generated/apis/DefaultApi.client.ts +++ b/plugins/catalog-backend/src/generated/apis/DefaultApi.client.ts @@ -16,7 +16,6 @@ import type core from 'express-serve-static-core'; - import type { AnalyzeLocationRequest, AnalyzeLocationResponse, @@ -35,191 +34,212 @@ import type { } from '@backstage/catalog-client'; import { Router } from 'express'; - /** * no description */ - type InputOutput = { - - '#POST|/analyze-location': { - path: { - }, - query: { - }, - body: AnalyzeLocationRequest, - response: AnalyzeLocationResponse, - }, - - - '#POST|/locations': { - path: { - }, - query: { - dryRun?: string, - }, - body: CreateLocationRequest, - response: CreateLocation201Response, - }, - - - '#DELETE|/entities/by-uid/{uid}': { - path: { - uid: string, - }, - query: { - }, - response: void, - }, - - - '#DELETE|/locations/{id}': { - path: { - id: string, - }, - query: { - }, - response: void, - }, - - - '#GET|/entities': { - path: { - }, - query: { - fields?: Array, - limit?: number, - filter?: Array, - offset?: number, - after?: string, - order?: Array, - }, - response: Array, - }, - - - '#GET|/entities/by-query': { - path: { - }, - query: { - fields?: Array, - limit?: number, - orderField?: Array, - cursor?: string, - filter?: Array, - fullTextFilterTerm?: string, - fullTextFilterFields?: Array, - }, - response: EntitiesQueryResponse, - }, - - - '#POST|/entities/by-refs': { - path: { - }, - query: { - }, - body: GetEntitiesByRefsRequest, - response: EntitiesBatchResponse, - }, - - - '#GET|/entities/by-name/{kind}/{namespace}/{name}/ancestry': { - path: { - kind: string, - namespace: string, - name: string, - }, - query: { - }, - response: EntityAncestryResponse, - }, - - - '#GET|/entities/by-name/{kind}/{namespace}/{name}': { - path: { - kind: string, - namespace: string, - name: string, - }, - query: { - }, - response: Entity, - }, - - - '#GET|/entities/by-uid/{uid}': { - path: { - uid: string, - }, - query: { - }, - response: Entity, - }, - - - '#GET|/entity-facets': { - path: { - }, - query: { - facet: Array, - filter?: Array, - }, - response: EntityFacetsResponse, - }, - - - '#GET|/locations/{id}': { - path: { - id: string, - }, - query: { - }, - response: Location, - }, - - - '#GET|/locations': { - path: { - }, - query: { - }, - response: Array, - }, - - - '#POST|/refresh': { - path: { - }, - query: { - }, - body: RefreshEntityRequest, - response: void, - }, - - - '#POST|/validate-entity': { - path: { - }, - query: { - }, - body: ValidateEntityRequest, - response: void, - }, - +type InputOutput = { + '#POST|/analyze-location': { + path: {}; + query: {}; + body: AnalyzeLocationRequest; + response: AnalyzeLocationResponse; + }; + + '#POST|/locations': { + path: {}; + query: { + dryRun?: string; }; + body: CreateLocationRequest; + response: CreateLocation201Response; + }; -type ValueOf = T[keyof T]; + '#DELETE|/entities/by-uid/{uid}': { + path: { + uid: string; + }; + query: {}; + response: void; + }; -type Filter = T extends K ? K : never; + '#DELETE|/locations/{id}': { + path: { + id: string; + }; + query: {}; + response: void; + }; -type ExtractDocPath = Path extends `#${string}|${infer OpenApiPath}` ? OpenApiPath : never; + '#GET|/entities': { + path: {}; + query: { + fields?: Array; + limit?: number; + filter?: Array; + offset?: number; + after?: string; + order?: Array; + }; + response: Array; + }; -type ExtractDocMethod = Path extends `#${infer Method}|${string}` ? Method : never + '#GET|/entities/by-query': { + path: {}; + query: { + fields?: Array; + limit?: number; + orderField?: Array; + cursor?: string; + filter?: Array; + fullTextFilterTerm?: string; + fullTextFilterFields?: Array; + }; + response: EntitiesQueryResponse; + }; -type DocPath = ExtractDocPath>; + '#POST|/entities/by-refs': { + path: {}; + query: {}; + body: GetEntitiesByRefsRequest; + response: EntitiesBatchResponse; + }; -type DocPathMethod = ExtractDocMethod; + '#GET|/entities/by-name/{kind}/{namespace}/{name}/ancestry': { + path: { + kind: string; + namespace: string; + name: string; + }; + query: {}; + response: EntityAncestryResponse; + }; + + '#GET|/entities/by-name/{kind}/{namespace}/{name}': { + path: { + kind: string; + namespace: string; + name: string; + }; + query: {}; + response: Entity; + }; + + '#GET|/entities/by-uid/{uid}': { + path: { + uid: string; + }; + query: {}; + response: Entity; + }; + + '#GET|/entity-facets': { + path: {}; + query: { + facet: Array; + filter?: Array; + }; + response: EntityFacetsResponse; + }; + + '#GET|/locations/{id}': { + path: { + id: string; + }; + query: {}; + response: Location; + }; + + '/locations': { + get: { + path: {}; + query: {}; + response: Array; + }; + }; + + '#POST|/refresh': { + path: {}; + query: {}; + body: RefreshEntityRequest; + response: void; + }; + + '#POST|/validate-entity': { + path: {}; + query: {}; + body: ValidateEntityRequest; + response: void; + }; +}; + +type PathTemplate = + Path extends `${infer Prefix}{${infer PathName}}${infer Suffix}` + ? `${Prefix}:${PathName}${PathTemplate}` + : Path; + +type HttpMethods = Uppercase<'all' | 'put' | 'get' | 'post' | 'delete'>; + +type ValueOf = T[keyof T]; + +/** + * @public + */ +export type Filter = T extends U ? T : never; + +type DocPath = ValueOf<{ + [Template in keyof Doc]: Template extends `#${string}|${infer Path}` + ? Path + : never; +}>; + +type DocPathMethod< + Doc extends InputOutput, + Path extends DocPath, +> = ValueOf<{ + [Template in keyof Doc]: Template extends `#${infer Method}|${Path}` + ? Method extends string + ? Method + : never + : never; +}>; + +type PathSchema< + Doc extends InputOutput, + Path extends DocPath, + Method extends DocPathMethod, +> = `#${Method}|${Path}` extends keyof Doc + ? 'path' extends keyof Doc[`#${Method}|${Path}`] + ? Doc[`#${Method}|${Path}`]['path'] + : never + : never; +type RequestBody< + Doc extends InputOutput, + Path extends DocPath, + Method extends DocPathMethod, +> = `#${Method}|${Path}` extends keyof Doc + ? 'body' extends keyof Doc[`#${Method}|${Path}`] + ? Doc[`#${Method}|${Path}`]['body'] + : any + : any; +type ResponseBody< + Doc extends InputOutput, + Path extends DocPath, + Method extends DocPathMethod, +> = `#${Method}|${Path}` extends keyof Doc + ? 'response' extends keyof Doc[`#${Method}|${Path}`] + ? Doc[`#${Method}|${Path}`]['response'] + : any + : any; +type QuerySchema< + Doc extends InputOutput, + Path extends DocPath, + Method extends DocPathMethod, +> = `#${Method}|${Path}` extends keyof Doc + ? 'query' extends keyof Doc[`#${Method}|${Path}`] + ? Doc[`#${Method}|${Path}`]['query'] + : never + : never; /** * Typed express request handler. @@ -231,8 +251,8 @@ export type DocRequestHandler< Method extends DocPathMethod, > = core.RequestHandler< PathSchema, - ResponseBodyToJsonSchema, - RequestBodyToJsonSchema, + ResponseBody, + RequestBody, QuerySchema, Record >; @@ -242,60 +262,85 @@ export type DocRequestHandler< * @public */ export type DocRequestHandlerParams< - Doc extends RequiredDoc, + Doc extends InputOutput, Path extends DocPath, Method extends DocPathMethod, > = core.RequestHandlerParams< PathSchema, - ResponseBodyToJsonSchema, - RequestBodyToJsonSchema, + ResponseBody, + RequestBody, QuerySchema, Record >; +/** + * @public + */ +export type MethodAwareDocPath< + Doc extends InputOutput, + Path extends DocPath, + Method extends DocPathMethod, +> = ValueOf<{ + [Template in keyof Doc]: Template extends `#${Method}|${Path}` ? Path : never; +}>; + /** * Superset of the express router path matcher that enforces typed request and response bodies. * @public */ export interface DocRequestMatcher< - Doc extends RequiredDoc, + Doc extends InputOutput, T, - Method extends - | 'all' - | 'get' - | 'post' - | 'put' - | 'delete' - | 'patch' - | 'options' - | 'head', + Method extends HttpMethods, > { < - Path extends MethodAwareDocPath< - Doc, - PathTemplate>, - Method - >, + TPath extends DocPath, + TMethod extends Filter, Method>, >( - path: Path, - ...handlers: Array< - DocRequestHandler, Method> - > + path: PathTemplate>, + ...handlers: Array> ): T; < - Path extends MethodAwareDocPath< - Doc, - PathTemplate>, - Method - >, + TPath extends DocPath, + TMethod extends Filter, Method>, >( - path: Path, - ...handlers: Array< - DocRequestHandlerParams, Method> - > + path: PathTemplate>, + ...handlers: Array> ): T; } export interface TypedRouter extends Router { - get: + get: DocRequestMatcher; + post: DocRequestMatcher; + put: DocRequestMatcher; + delete: DocRequestMatcher; } + +type method = DocRequestMatcher; + +type test2 = keyof method; + +type paths = PathTemplate>; +type test3 = PathSchema< + InputOutput, + '/entities/by-name/{kind}/{namespace}/{name}', + 'GET' +>; + +type test4 = ResponseBody< + InputOutput, + '/entities/by-name/{kind}/{namespace}/{name}', + 'GET' +>; +type Method = DocPath; +type why = DocPathMethod< + InputOutput, + '/entities/by-name/{kind}/{namespace}/{name}' +>; + +type test5 = MethodAwareDocPath; + +const router: TypedRouter = Router() as TypedRouter; +router.post('/entities', (req, res) => { + res.json([{}]); +}); diff --git a/plugins/catalog-backend/src/generated/apis/DefaultApi.server.ts b/plugins/catalog-backend/src/generated/apis/DefaultApi.server.ts new file mode 100644 index 0000000000..096a4aac70 --- /dev/null +++ b/plugins/catalog-backend/src/generated/apis/DefaultApi.server.ts @@ -0,0 +1,206 @@ +/* + * 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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +import type { + AnalyzeLocationRequest, + AnalyzeLocationResponse, + CreateLocation201Response, + CreateLocationRequest, + EntitiesBatchResponse, + EntitiesQueryResponse, + Entity, + EntityAncestryResponse, + EntityFacetsResponse, + GetEntitiesByRefsRequest, + GetLocations200ResponseInner, + Location, + RefreshEntityRequest, + ValidateEntityRequest, +} from '@backstage/catalog-client'; + +/** + * no description + */ + +type InputOutput = { + '/analyze-location': { + POST: { + path: {}; + query: {}; + body: AnalyzeLocationRequest; + response: AnalyzeLocationResponse; + }; + }; + + '/locations': { + POST: { + path: {}; + query: { + dryRun?: string; + }; + body: CreateLocationRequest; + response: CreateLocation201Response; + }; + DELETE: { + path: { + id: string; + }; + query: {}; + response: void; + }; + GET: { + path: {}; + query: {}; + response: Array; + }; + }; + + '/entities/by-uid/{uid}': { + DELETE: { + path: { + uid: string; + }; + query: {}; + response: void; + }; + GET: { + path: { + uid: string; + }; + query: {}; + response: Entity; + }; + }; + + '/entities': { + GET: { + path: {}; + query: { + fields?: Array; + limit?: number; + filter?: Array; + offset?: number; + after?: string; + order?: Array; + }; + response: Array; + }; + }; + + '/entities/by-query': { + GET: { + path: {}; + query: { + fields?: Array; + limit?: number; + orderField?: Array; + cursor?: string; + filter?: Array; + fullTextFilterTerm?: string; + fullTextFilterFields?: Array; + }; + response: EntitiesQueryResponse; + }; + }; + + '/entities/by-refs': { + POST: { + path: {}; + query: {}; + body: GetEntitiesByRefsRequest; + response: EntitiesBatchResponse; + }; + }; + + '/entities/by-name/{kind}/{namespace}/{name}/ancestry': { + GET: { + path: { + kind: string; + namespace: string; + name: string; + }; + query: {}; + response: EntityAncestryResponse; + }; + }; + + '/entities/by-name/{kind}/{namespace}/{name}': { + GET: { + path: { + kind: string; + namespace: string; + name: string; + }; + query: {}; + response: Entity; + }; + }; + + '/entity-facets': { + GET: { + path: {}; + query: { + facet: Array; + filter?: Array; + }; + response: EntityFacetsResponse; + }; + }; + + '/locations/{id}': { + GET: { + path: { + id: string; + }; + query: {}; + response: Location; + }; + }; + + '/locations/by-entity/{kind}/{namespace}/{name}': { + GET: { + path: { + kind: string; + namespace: string; + name: string; + }; + query: {}; + response: Location; + }; + }; + + '/refresh': { + POST: { + path: {}; + query: {}; + body: RefreshEntityRequest; + response: void; + }; + }; + + '/validate-entity': { + POST: { + path: {}; + query: {}; + body: ValidateEntityRequest; + response: void; + }; + }; +}; diff --git a/plugins/catalog-backend/src/generated/apis/router.ts b/plugins/catalog-backend/src/generated/apis/router.ts new file mode 100644 index 0000000000..38020570db --- /dev/null +++ b/plugins/catalog-backend/src/generated/apis/router.ts @@ -0,0 +1,207 @@ +/* + * Copyright 2023 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. + */ + +/** + * Pulled from https://github.com/varanauskas/oatx. + */ + +/** + * Get value types of `T`. + * @public + */ +export type ValueOf = T[keyof T]; + +/** + * All paths for a given doc, + * @example `/pet/{petId}` | `/pet` + * @public + */ +export type DocPath = Extract; + +/** + * Validate a string against OpenAPI path template, {@link https://spec.openapis.org/oas/v3.1.0#path-templating-matching}. + * + * @example + * ```ts + * const path: PathTemplate<"/posts/{postId}/comments/{commentId}"> = "/posts/:postId/comments/:commentId"; + * const pathWithoutParams: PathTemplate<"/posts/comments"> = "/posts/comments"; + * ``` + * + * @public + */ +export type PathTemplate = + Path extends `${infer Prefix}{${infer PathName}}${infer Suffix}` + ? `${Prefix}:${PathName}${PathTemplate}` + : Path; + +/** + * Extract path as specified in OpenAPI `Doc` based on request path + * @example + * ```ts + * const spec = { + * paths: { + * "/posts/{postId}/comments/{commentId}": {}, + * "/posts/comments": {}, + * } + * }; + * const specPathWithParams: DocPath = "/posts/{postId}/comments/{commentId}"; + * const specPathWithoutParams: DocPath = "/posts/comments"; + * ``` + * + * @public + */ +export type TemplateToDocPath< + Doc extends PathDoc, + Path extends DocPathTemplate, +> = ValueOf<{ + [Template in DocPath]: Path extends PathTemplate