From 056fda5106663a4e0fb251931eed05d97f5bcafb Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Fri, 3 Mar 2023 14:43:33 -0500 Subject: [PATCH] Add support for path + query variables. Signed-off-by: Aramis Sennyey --- plugins/catalog-backend/openapi.yaml | 68 +++++----- .../src/service/schema/openapi.ts | 83 ++++++------ plugins/openapi-router/src/types/common.ts | 33 +++++ plugins/openapi-router/src/types/express.ts | 9 +- plugins/openapi-router/src/types/immutable.ts | 9 +- plugins/openapi-router/src/types/params.ts | 123 ++++++++++++------ 6 files changed, 202 insertions(+), 123 deletions(-) diff --git a/plugins/catalog-backend/openapi.yaml b/plugins/catalog-backend/openapi.yaml index 5028f4d556..07d1bc9ca5 100644 --- a/plugins/catalog-backend/openapi.yaml +++ b/plugins/catalog-backend/openapi.yaml @@ -16,6 +16,30 @@ components: examples: {} headers: {} parameters: + kind: + name: kind + in: path + required: true + schema: + type: string + namespace: + name: namespace + in: path + required: true + schema: + type: string + name: + name: name + in: path + required: true + schema: + type: string + uid: + name: uid + in: path + required: true + schema: + type: string cursor: name: cursor in: query @@ -634,11 +658,7 @@ paths: - {} - JWT: [] parameters: - - in: path - name: uid - required: true - schema: - type: string + - $ref: '#/components/parameters/uid' delete: operationId: DeleteEntityByUid responses: @@ -667,21 +687,9 @@ paths: - {} - JWT: [] parameters: - - in: path - name: kind - required: true - schema: - type: string - - in: path - name: namespace - required: true - schema: - type: string - - in: path - name: name - required: true - schema: - type: string + - $ref: '#/components/parameters/kind' + - $ref: '#/components/parameters/namespace' + - $ref: '#/components/parameters/name' /entities/by-name/{kind}/{namespace}/{name}/ancestry: get: operationId: GetEntityAncestryByName @@ -696,21 +704,9 @@ paths: - {} - JWT: [] parameters: - - in: path - name: kind - required: true - schema: - type: string - - in: path - name: namespace - required: true - schema: - type: string - - in: path - name: name - required: true - schema: - type: string + - $ref: '#/components/parameters/kind' + - $ref: '#/components/parameters/namespace' + - $ref: '#/components/parameters/name' /entities/by-refs: post: operationId: GetEntitiesByRefs @@ -835,7 +831,7 @@ paths: name: dryRun required: false schema: - type: boolean + type: string requestBody: required: true content: diff --git a/plugins/catalog-backend/src/service/schema/openapi.ts b/plugins/catalog-backend/src/service/schema/openapi.ts index ce725a59de..6ec4f25527 100644 --- a/plugins/catalog-backend/src/service/schema/openapi.ts +++ b/plugins/catalog-backend/src/service/schema/openapi.ts @@ -35,6 +35,38 @@ export default { examples: {}, headers: {}, parameters: { + kind: { + name: 'kind', + in: 'path', + required: true, + schema: { + type: 'string', + }, + }, + namespace: { + name: 'namespace', + in: 'path', + required: true, + schema: { + type: 'string', + }, + }, + name: { + name: 'name', + in: 'path', + required: true, + schema: { + type: 'string', + }, + }, + uid: { + name: 'uid', + in: 'path', + required: true, + schema: { + type: 'string', + }, + }, cursor: { name: 'cursor', in: 'query', @@ -769,12 +801,7 @@ export default { ], parameters: [ { - in: 'path', - name: 'uid', - required: true, - schema: { - type: 'string', - }, + $ref: '#/components/parameters/uid', }, ], }, @@ -826,28 +853,13 @@ export default { ], parameters: [ { - in: 'path', - name: 'kind', - required: true, - schema: { - type: 'string', - }, + $ref: '#/components/parameters/kind', }, { - in: 'path', - name: 'namespace', - required: true, - schema: { - type: 'string', - }, + $ref: '#/components/parameters/namespace', }, { - in: 'path', - name: 'name', - required: true, - schema: { - type: 'string', - }, + $ref: '#/components/parameters/name', }, ], }, @@ -875,28 +887,13 @@ export default { ], parameters: [ { - in: 'path', - name: 'kind', - required: true, - schema: { - type: 'string', - }, + $ref: '#/components/parameters/kind', }, { - in: 'path', - name: 'namespace', - required: true, - schema: { - type: 'string', - }, + $ref: '#/components/parameters/namespace', }, { - in: 'path', - name: 'name', - required: true, - schema: { - type: 'string', - }, + $ref: '#/components/parameters/name', }, ], }, @@ -1104,7 +1101,7 @@ export default { name: 'dryRun', required: false, schema: { - type: 'boolean', + type: 'string', }, }, ], diff --git a/plugins/openapi-router/src/types/common.ts b/plugins/openapi-router/src/types/common.ts index ac1fc29b84..d1171257ed 100644 --- a/plugins/openapi-router/src/types/common.ts +++ b/plugins/openapi-router/src/types/common.ts @@ -230,3 +230,36 @@ export type UnknownIfNever

= [P] extends [never] ? unknown : P; * @public */ export type ToTypeSafe = UnknownIfNever>[number]>; + +type DiscriminateUnion = Extract< + T, + Record +>; + +export type MapDiscriminatedUnion< + T extends Record, + K extends keyof T, +> = { + [V in T[K]]: DiscriminateUnion; +}; + +export type PickOptionalKeys = { + [K in keyof T]: true extends T[K]['required'] ? never : K; +}[keyof T]; + +export type PickRequiredKeys = { + [K in keyof T]: true extends T[K]['required'] ? K : never; +}[keyof T]; + +export type OptionalMap = { + [P in Exclude, undefined>]?: NonNullable; +}; + +export type RequiredMap = { + [P in Exclude, undefined>]: NonNullable; +}; + +export type FullMap = RequiredMap & + OptionalMap; + +export type Filter = T extends U ? T : never; diff --git a/plugins/openapi-router/src/types/express.ts b/plugins/openapi-router/src/types/express.ts index 4a6fcdedcb..87b91c6c7c 100644 --- a/plugins/openapi-router/src/types/express.ts +++ b/plugins/openapi-router/src/types/express.ts @@ -15,6 +15,7 @@ */ import core from 'express-serve-static-core'; import { DocPathTemplate, MethodAwareDocPath, RequiredDoc } from './common'; +import { PathSchema, QuerySchema } from './params'; import { RequestBodyToJsonSchema } from './requests'; import { ResponseBodyToJsonSchema } from './responses'; @@ -35,10 +36,10 @@ export type DocRequestHandler< Path extends DocPathTemplate, Method extends keyof Doc['paths'][Path], > = core.RequestHandler< - core.ParamsDictionary, + PathSchema, ResponseBodyToJsonSchema, RequestBodyToJsonSchema, - ParsedQs, + QuerySchema, Record >; @@ -51,10 +52,10 @@ export type DocRequestHandlerParams< Path extends DocPathTemplate, Method extends keyof Doc['paths'][Path], > = core.RequestHandlerParams< - core.ParamsDictionary, + PathSchema, ResponseBodyToJsonSchema, RequestBodyToJsonSchema, - ParsedQs, + QuerySchema, Record >; diff --git a/plugins/openapi-router/src/types/immutable.ts b/plugins/openapi-router/src/types/immutable.ts index 7cba014e0a..dc6e20c827 100644 --- a/plugins/openapi-router/src/types/immutable.ts +++ b/plugins/openapi-router/src/types/immutable.ts @@ -19,6 +19,7 @@ import type { ReferenceObject, RequestBodyObject, ParameterObject, + SchemaObject, } from 'openapi3-ts'; /** @@ -81,21 +82,23 @@ export type ImmutableHeaderObject = ImmutableObject; export interface CookieObject extends ParameterObject { in: 'cookie'; - style: 'form'; + style?: 'form'; } export type ImmutableCookieObject = ImmutableObject; export interface QueryObject extends ParameterObject { in: 'query'; - style: 'form' | 'deepObject' | 'pipeDelimited' | 'spaceDelimited'; + style?: 'form' | 'deepObject' | 'pipeDelimited' | 'spaceDelimited'; } export type ImmutableQueryObject = ImmutableObject; export interface PathObject extends ParameterObject { in: 'path'; - style: 'simple' | 'label' | 'matrix'; + style?: 'simple' | 'label' | 'matrix'; } export type ImmutablePathObject = ImmutableObject; + +export type ImmutableSchemaObject = ImmutableObject; diff --git a/plugins/openapi-router/src/types/params.ts b/plugins/openapi-router/src/types/params.ts index 3e06e266dd..9af1fa6eaa 100644 --- a/plugins/openapi-router/src/types/params.ts +++ b/plugins/openapi-router/src/types/params.ts @@ -15,23 +15,30 @@ */ import { + ImmutableCookieObject, ImmutableHeaderObject, ImmutableParameterObject, + ImmutablePathObject, + ImmutableQueryObject, ImmutableReferenceObject, + ImmutableSchemaObject, } from './immutable'; import { ComponentRef, ComponentTypes, DocOperation, - DocPathTemplate, + DocPath, + DocPathMethod, + Filter, + FullMap, + MapDiscriminatedUnion, PathTemplate, RequiredDoc, - TuplifyUnion, } from './common'; +import { FromSchema, JSONSchema7 } from 'json-schema-to-ts'; import spec from '../schema/petstore'; -import { ParameterLocation } from 'openapi3-ts'; -export type ResolveDocParameter< +export type DocParameter< Doc extends RequiredDoc, Path extends Extract, Method extends keyof Doc['paths'][Path], @@ -45,52 +52,94 @@ export type ResolveDocParameter< ? ComponentRef< Doc, 'parameters', - DocOperation['parameter'][Parameter] + DocOperation['parameters'][Parameter] > : never : DocOperation['parameters'][Parameter]; -export type DocParameter< - Doc extends RequiredDoc, - Path extends Extract, - Method extends keyof Doc['paths'][Path], - Parameter extends keyof Doc['paths'][Path][Method]['parameters'], -> = ResolveDocParameter< - Doc, - Path, - Method, - Parameter -> extends ImmutableParameterObject - ? ResolveDocParameter - : never; - -type KeysMatching = { - [K in keyof T]-?: T[K] extends V ? K : never; -}[keyof T]; -/** - * @public - */ -export type ConvertAll = []> = T extends [ - infer First extends JSONSchema7, - ...infer Rest, -] - ? ConvertAll]> - : R; /** * @public */ export type DocParameters< Doc extends RequiredDoc, - Path extends DocPathTemplate, + Path extends Extract, Method extends keyof Doc['paths'][Path], -> = TuplifyUnion<>; +> = DocOperation['parameters'] extends ReadonlyArray + ? { + [Index in keyof DocOperation< + Doc, + Path, + Method + >['parameters']]: DocParameter; + } + : never; + +export type ResolveDocParameterSchema< + Doc extends RequiredDoc, + Schema extends ImmutableParameterObject['schema'], +> = Schema extends ImmutableReferenceObject + ? 'parameters' extends ComponentTypes + ? ComponentRef + : never + : Schema; + +export type ParameterSchema< + Doc extends RequiredDoc, + Schema extends ImmutableParameterObject['schema'], +> = ResolveDocParameterSchema extends infer R + ? R extends ImmutableSchemaObject + ? R extends JSONSchema7 + ? FromSchema + : never + : never + : never; + +type MapToSchema< + Doc extends RequiredDoc, + T extends Record, +> = { + [V in keyof T]: NonNullable extends ImmutableParameterObject + ? ParameterSchema['schema']> + : never; +}; + +export type ParametersSchema< + Doc extends RequiredDoc, + Path extends Extract, + Method extends keyof Doc['paths'][Path], + FilterType extends ImmutableParameterObject, +> = number extends keyof DocParameters + ? MapToSchema< + Doc, + FullMap< + MapDiscriminatedUnion< + Filter[number], FilterType>, + 'name' + > + > + > + : never; export type HeaderSchema< Doc extends RequiredDoc, Path extends PathTemplate>, - Method extends keyof Doc['paths'][Path], -> = KeysMatching, ImmutableHeaderObject>; + Method extends DocPathMethod, +> = ParametersSchema, Method, ImmutableHeaderObject>; -type keys = HeaderSchema; +export type CookieSchema< + Doc extends RequiredDoc, + Path extends PathTemplate>, + Method extends DocPathMethod, +> = ParametersSchema, Method, ImmutableCookieObject>; -type match = KeysMatching<[1, 2, 3], 1>; +export type PathSchema< + Doc extends RequiredDoc, + Path extends PathTemplate>, + Method extends DocPathMethod, +> = ParametersSchema, Method, ImmutablePathObject>; + +export type QuerySchema< + Doc extends RequiredDoc, + Path extends PathTemplate>, + Method extends DocPathMethod, +> = ParametersSchema, Method, ImmutableQueryObject>;