Merge pull request #23610 from aramissennyeydd/openapi-tooling/generated-server

feat(openapi-tooling): stop inferring server types
This commit is contained in:
Patrik Oldsberg
2024-10-29 16:14:40 +01:00
committed by GitHub
151 changed files with 3270 additions and 239 deletions
+210 -1
View File
@@ -102,12 +102,45 @@ export function createValidatedOpenApiRouter<T extends RequiredDoc>(
},
): ApiRouter<T>;
// @public
export function createValidatedOpenApiRouterFromGeneratedEndpointMap<
T extends EndpointMap,
>(
spec: RequiredDoc,
options?: {
validatorOptions?: Partial<Parameters<typeof middleware>['0']>;
middleware?: RequestHandler[];
},
): TypedRouter<T>;
// @public (undocumented)
type DiscriminateUnion<T, K extends keyof T, V extends T[K]> = Extract<
T,
Record<K, V>
>;
// @public (undocumented)
type DocEndpoint<Doc extends EndpointMap> = ValueOf<{
[Template in keyof Doc]: Template extends `#${string}|${infer Endpoint}`
? Endpoint
: never;
}>;
// @public (undocumented)
type DocEndpointMethod<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
> = ValueOf<{
[Template in keyof Doc]: Template extends `#${infer Method}|${Endpoint}`
? Method
: never;
}>;
// @public (undocumented)
type DocEndpointTemplate<Doc extends EndpointMap> = PathTemplate<
DocEndpoint<Doc>
>;
// @public (undocumented)
type DocOperation<
Doc extends RequiredDoc,
@@ -239,6 +272,85 @@ interface DocRequestMatcher<
): T;
}
// @public (undocumented)
type EndpointMap = Record<
string,
{
query?: object;
body?: object;
response?: object | void;
path?: object;
}
>;
// @public
type EndpointMapRequestHandler<
Doc extends EndpointMap,
Path extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Path>,
> = core.RequestHandler<
StaticPathParamsSchema<Doc, Path, Method>,
StaticResponseSchema<Doc, Path, Method>,
StaticRequestBodySchema<Doc, Path, Method>,
StaticQueryParamsSchema<Doc, Path, Method>,
Record<string, string>
>;
// @public
type EndpointMapRequestHandlerParams<
Doc extends EndpointMap,
Path extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Path>,
> = core.RequestHandlerParams<
StaticPathParamsSchema<Doc, Path, Method>,
StaticResponseSchema<Doc, Path, Method>,
StaticRequestBodySchema<Doc, Path, Method>,
StaticQueryParamsSchema<Doc, Path, Method>,
Record<string, string>
>;
// @public
interface EndpointMapRequestMatcher<
Doc extends EndpointMap,
T,
Method extends HttpMethods,
> {
// (undocumented)
<
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;
// (undocumented)
<
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;
}
// @public (undocumented)
type Filter<T, U> = T extends U ? T : never;
@@ -278,6 +390,9 @@ type HeaderSchema<
Method extends DocPathMethod<Doc, Path>,
> = ParametersSchema<Doc, Path, Method, ImmutableHeaderObject>;
// @public (undocumented)
type HttpMethods = 'all' | 'put' | 'get' | 'post' | '_delete';
// @public
type Immutable<T> = T extends
| Function
@@ -401,6 +516,21 @@ declare namespace internal {
Response_3 as Response,
ResponseSchemas,
ResponseBodyToJsonSchema,
EndpointMap,
HttpMethods,
StaticPathParamsSchema,
StaticRequestBodySchema,
StaticResponseSchema,
StaticQueryParamsSchema,
EndpointMapRequestHandler,
EndpointMapRequestHandlerParams,
DocEndpoint,
DocEndpointMethod,
MethodAwareDocEndpoints,
DocEndpointTemplate,
TemplateToDocEndpoint,
EndpointMapRequestMatcher,
TypedRouter,
};
}
export { internal };
@@ -427,6 +557,19 @@ type MapToSchema<
: never;
};
// @public (undocumented)
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;
}>;
// @public (undocumented)
type MethodAwareDocPath<
Doc extends PathDoc,
@@ -513,7 +656,7 @@ type PathSchema<
> = ParametersSchema<Doc, Path, Method, ImmutablePathObject>;
// @public
type PathTemplate<Path extends string> =
export type PathTemplate<Path extends string> =
Path extends `${infer Prefix}{${infer PathName}}${infer Suffix}`
? `${Prefix}:${PathName}${PathTemplate<Suffix>}`
: Path;
@@ -684,6 +827,60 @@ type SchemaRef<Doc extends RequiredDoc, Schema> = Schema extends {
[Key in keyof Schema]: SchemaRef<Doc, Schema[Key]>;
};
// @public (undocumented)
type StaticPathParamsSchema<
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;
// @public (undocumented)
type StaticQueryParamsSchema<
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;
// @public (undocumented)
type StaticRequestBodySchema<
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;
// @public (undocumented)
type StaticResponseSchema<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = `#${Method}|${Endpoint}` extends keyof Doc
? 'response' extends keyof Doc[`#${Method}|${Endpoint}`]
? Doc[`#${Method}|${Endpoint}`]['response']
: unknown
: unknown;
// @public (undocumented)
type TemplateToDocEndpoint<
Doc extends EndpointMap,
Path extends DocEndpointTemplate<Doc>,
> = ValueOf<{
[Template in DocEndpoint<Doc>]: Path extends PathTemplate<Template>
? Template
: never;
}>;
// @public
type TemplateToDocPath<
Doc extends PathDoc,
@@ -704,6 +901,18 @@ type TuplifyUnion<
N = [T] extends [never] ? true : false,
> = true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>>, L>;
// @public (undocumented)
interface TypedRouter<GeneratedEndpointMap extends EndpointMap> extends Router {
// (undocumented)
delete: EndpointMapRequestMatcher<GeneratedEndpointMap, this, '_delete'>;
// (undocumented)
get: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'get'>;
// (undocumented)
post: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'post'>;
// (undocumented)
put: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'put'>;
}
// @public
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
+6 -1
View File
@@ -31,5 +31,10 @@ export type {
PathParameters,
} from './utility';
export type { ApiRouter } from './router';
export { createValidatedOpenApiRouter, getOpenApiSpecRoute } from './stub';
export type { PathTemplate } from './types/common';
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,222 @@
/*
* 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';
/**
* @public
*/
export type EndpointMap = Record<
string,
{ query?: object; body?: object; response?: object | void; path?: object }
>;
// OpenAPI generator doesn't emit regular lowercase 'delete'.
/**
* @public
*/
export type HttpMethods = 'all' | 'put' | 'get' | 'post' | '_delete';
/**
* @public
*/
export type StaticPathParamsSchema<
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;
/**
* @public
*/
export type StaticRequestBodySchema<
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;
/**
* @public
*/
export type StaticResponseSchema<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = `#${Method}|${Endpoint}` extends keyof Doc
? 'response' extends keyof Doc[`#${Method}|${Endpoint}`]
? Doc[`#${Method}|${Endpoint}`]['response']
: unknown
: unknown;
/**
* @public
*/
export type StaticQueryParamsSchema<
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<
StaticPathParamsSchema<Doc, Path, Method>,
StaticResponseSchema<Doc, Path, Method>,
StaticRequestBodySchema<Doc, Path, Method>,
StaticQueryParamsSchema<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<
StaticPathParamsSchema<Doc, Path, Method>,
StaticResponseSchema<Doc, Path, Method>,
StaticRequestBodySchema<Doc, Path, Method>,
StaticQueryParamsSchema<Doc, Path, Method>,
Record<string, string>
>;
/**
* @public
*/
export type DocEndpoint<Doc extends EndpointMap> = ValueOf<{
[Template in keyof Doc]: Template extends `#${string}|${infer Endpoint}`
? Endpoint
: never;
}>;
/**
* @public
*/
export type DocEndpointMethod<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
> = ValueOf<{
[Template in keyof Doc]: Template extends `#${infer Method}|${Endpoint}`
? Method
: never;
}>;
/**
* @public
*/
export 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;
}>;
/**
* @public
*/
export type DocEndpointTemplate<Doc extends EndpointMap> = PathTemplate<
DocEndpoint<Doc>
>;
/**
* @public
*/
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;
}
/**
* @public
*/
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';