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:
aramissennyeydd
2024-03-03 16:28:46 -05:00
parent 1eca5d5112
commit 00c6b272db
25 changed files with 435 additions and 932 deletions
+5 -1
View File
@@ -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';
+44 -8
View File
@@ -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';