it works!
Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com> Signed-off-by: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com>
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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<T extends RequiredDoc>(
|
||||
spec: T,
|
||||
function createRouterWithValidation(
|
||||
spec: RequiredDoc,
|
||||
options?: {
|
||||
validatorOptions?: Partial<Parameters<typeof OpenApiValidator>['0']>;
|
||||
middleware?: RequestHandler[];
|
||||
},
|
||||
) {
|
||||
): Router {
|
||||
const router = PromiseRouter();
|
||||
router.use(options?.middleware || getDefaultRouterMiddleware());
|
||||
|
||||
@@ -152,6 +153,41 @@ export function createValidatedOpenApiRouter<T extends RequiredDoc>(
|
||||
}
|
||||
res.json(mergeOutput.output);
|
||||
});
|
||||
|
||||
return router as ApiRouter<typeof spec>;
|
||||
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<T extends RequiredDoc>(
|
||||
spec: T,
|
||||
options?: {
|
||||
validatorOptions?: Partial<Parameters<typeof OpenApiValidator>['0']>;
|
||||
middleware?: RequestHandler[];
|
||||
},
|
||||
) {
|
||||
return createRouterWithValidation(spec, options) as ApiRouter<typeof spec>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Parameters<typeof OpenApiValidator>['0']>;
|
||||
middleware?: RequestHandler[];
|
||||
},
|
||||
) {
|
||||
return createRouterWithValidation(spec, options) as TypedRouter<T>;
|
||||
}
|
||||
|
||||
@@ -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> = 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<Doc>,
|
||||
Method extends DocEndpointMethod<Doc, Endpoint>,
|
||||
> = `#${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<Doc>,
|
||||
Method extends DocEndpointMethod<Doc, Endpoint>,
|
||||
> = `#${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<Doc>,
|
||||
Method extends DocEndpointMethod<Doc, Endpoint>,
|
||||
> = `#${Method}|${Endpoint}` extends keyof Doc
|
||||
? 'response' extends keyof Doc[`#${Method}|${Endpoint}`]
|
||||
? UnknownIfVoid<Doc[`#${Method}|${Endpoint}`]['response']>
|
||||
: unknown
|
||||
: unknown;
|
||||
type QuerySchema<
|
||||
Doc extends EndpointMap,
|
||||
Endpoint extends DocEndpoint<Doc>,
|
||||
Method extends DocEndpointMethod<Doc, Endpoint>,
|
||||
> = `#${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<Doc>,
|
||||
Method extends DocEndpointMethod<Doc, Path>,
|
||||
> = core.RequestHandler<
|
||||
PathSchema<Doc, Path, Method>,
|
||||
ResponseBody<Doc, Path, Method>,
|
||||
RequestBody<Doc, Path, Method>,
|
||||
QuerySchema<Doc, Path, Method>,
|
||||
Record<string, string>
|
||||
>;
|
||||
|
||||
/**
|
||||
* Typed express error handler / request handler union type.
|
||||
* @public
|
||||
*/
|
||||
export type EndpointMapRequestHandlerParams<
|
||||
Doc extends EndpointMap,
|
||||
Path extends DocEndpoint<Doc>,
|
||||
Method extends DocEndpointMethod<Doc, Path>,
|
||||
> = core.RequestHandlerParams<
|
||||
PathSchema<Doc, Path, Method>,
|
||||
ResponseBody<Doc, Path, Method>,
|
||||
RequestBody<Doc, Path, Method>,
|
||||
QuerySchema<Doc, Path, Method>,
|
||||
Record<string, string>
|
||||
>;
|
||||
|
||||
type DocEndpoint<Doc extends EndpointMap> = ValueOf<{
|
||||
[Template in keyof Doc]: Template extends `#${string}|${infer Endpoint}`
|
||||
? Endpoint
|
||||
: never;
|
||||
}>;
|
||||
|
||||
type DocEndpointMethod<
|
||||
Doc extends EndpointMap,
|
||||
Endpoint extends DocEndpoint<Doc>,
|
||||
> = ValueOf<{
|
||||
[Template in keyof Doc]: Template extends `#${infer Method}|${Endpoint}`
|
||||
? Method
|
||||
: never;
|
||||
}>;
|
||||
|
||||
type MethodAwareDocEndpoints<
|
||||
Doc extends EndpointMap,
|
||||
Endpoint extends DocEndpoint<Doc>,
|
||||
Method extends DocEndpointMethod<Doc, Endpoint>,
|
||||
> = ValueOf<{
|
||||
[Template in keyof Doc]: Template extends `#${Method}|${infer E}`
|
||||
? E extends DocEndpoint<Doc>
|
||||
? PathTemplate<E>
|
||||
: never
|
||||
: never;
|
||||
}>;
|
||||
|
||||
type DocEndpointTemplate<Doc extends EndpointMap> = PathTemplate<
|
||||
DocEndpoint<Doc>
|
||||
>;
|
||||
|
||||
export type TemplateToDocEndpoint<
|
||||
Doc extends EndpointMap,
|
||||
Path extends DocEndpointTemplate<Doc>,
|
||||
> = ValueOf<{
|
||||
[Template in DocEndpoint<Doc>]: Path extends PathTemplate<Template>
|
||||
? Template
|
||||
: never;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Superset of the express router path matcher that enforces typed request and response bodies.
|
||||
* @public
|
||||
*/
|
||||
export interface EndpointMapRequestMatcher<
|
||||
Doc extends EndpointMap,
|
||||
T,
|
||||
Method extends HttpMethods,
|
||||
> {
|
||||
<
|
||||
TPath extends MethodAwareDocEndpoints<
|
||||
Doc,
|
||||
DocEndpoint<Doc>,
|
||||
Method & DocEndpointMethod<Doc, DocEndpoint<Doc>>
|
||||
>,
|
||||
TMethod extends Method &
|
||||
DocEndpointMethod<Doc, TemplateToDocEndpoint<Doc, TPath>>,
|
||||
>(
|
||||
path: TPath,
|
||||
...handlers: Array<
|
||||
EndpointMapRequestHandler<Doc, TemplateToDocEndpoint<Doc, TPath>, TMethod>
|
||||
>
|
||||
): T;
|
||||
<
|
||||
TPath extends MethodAwareDocEndpoints<
|
||||
Doc,
|
||||
DocEndpoint<Doc>,
|
||||
Method & DocEndpointMethod<Doc, DocEndpoint<Doc>>
|
||||
>,
|
||||
TMethod extends Method &
|
||||
DocEndpointMethod<Doc, TemplateToDocEndpoint<Doc, TPath>>,
|
||||
>(
|
||||
path: TPath,
|
||||
...handlers: Array<
|
||||
EndpointMapRequestHandlerParams<
|
||||
Doc,
|
||||
TemplateToDocEndpoint<Doc, TPath>,
|
||||
TMethod
|
||||
>
|
||||
>
|
||||
): T;
|
||||
}
|
||||
|
||||
export interface TypedRouter<GeneratedEndpointMap extends EndpointMap>
|
||||
extends Router {
|
||||
get: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'get'>;
|
||||
post: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'post'>;
|
||||
put: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'put'>;
|
||||
delete: EndpointMapRequestMatcher<GeneratedEndpointMap, this, '_delete'>;
|
||||
}
|
||||
@@ -20,3 +20,4 @@ export * from './immutable';
|
||||
export * from './params';
|
||||
export * from './requests';
|
||||
export * from './responses';
|
||||
export * from './generated';
|
||||
|
||||
Reference in New Issue
Block a user