From 649c587133ba991b22f1387b421e8df29226422a Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 20 Feb 2023 10:35:16 +0100 Subject: [PATCH 01/12] added initial package: core-version-http-server --- .../core-version-http-server/README.md | 13 +++++++++++++ .../core-version-http-server/index.ts | 7 +++++++ .../core-version-http-server/jest.config.js | 13 +++++++++++++ .../core-version-http-server/kibana.jsonc | 5 +++++ .../core-version-http-server/package.json | 7 +++++++ .../core-version-http-server/tsconfig.json | 18 ++++++++++++++++++ 6 files changed, 63 insertions(+) create mode 100644 packages/core/versioning/core-version-http-server/README.md create mode 100644 packages/core/versioning/core-version-http-server/index.ts create mode 100644 packages/core/versioning/core-version-http-server/jest.config.js create mode 100644 packages/core/versioning/core-version-http-server/kibana.jsonc create mode 100644 packages/core/versioning/core-version-http-server/package.json create mode 100644 packages/core/versioning/core-version-http-server/tsconfig.json diff --git a/packages/core/versioning/core-version-http-server/README.md b/packages/core/versioning/core-version-http-server/README.md new file mode 100644 index 0000000000000..b419f083b0d7e --- /dev/null +++ b/packages/core/versioning/core-version-http-server/README.md @@ -0,0 +1,13 @@ +# @kbn/core-version-http-server + +This package contains types for sever-side HTTP versioning. + +## Experimental + +The types in this package are all experimental and may be subject to extensive changes. +Use this package as a reference for current thinking and as a starting point to +raise questions and discussion. + +## Versioning specification + +Currently the versioning spec is being designed. \ No newline at end of file diff --git a/packages/core/versioning/core-version-http-server/index.ts b/packages/core/versioning/core-version-http-server/index.ts new file mode 100644 index 0000000000000..5c2d5b68ae2e0 --- /dev/null +++ b/packages/core/versioning/core-version-http-server/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ diff --git a/packages/core/versioning/core-version-http-server/jest.config.js b/packages/core/versioning/core-version-http-server/jest.config.js new file mode 100644 index 0000000000000..7f87846044ae9 --- /dev/null +++ b/packages/core/versioning/core-version-http-server/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['/packages/core/versioning/core-version-http-server'], +}; diff --git a/packages/core/versioning/core-version-http-server/kibana.jsonc b/packages/core/versioning/core-version-http-server/kibana.jsonc new file mode 100644 index 0000000000000..38c17bf30d2a5 --- /dev/null +++ b/packages/core/versioning/core-version-http-server/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/core-version-http-server", + "owner": "@elastic/kibana-core" +} diff --git a/packages/core/versioning/core-version-http-server/package.json b/packages/core/versioning/core-version-http-server/package.json new file mode 100644 index 0000000000000..160267c76a449 --- /dev/null +++ b/packages/core/versioning/core-version-http-server/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/core-version-http-server", + "private": true, + "version": "1.0.0", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/core/versioning/core-version-http-server/tsconfig.json b/packages/core/versioning/core-version-http-server/tsconfig.json new file mode 100644 index 0000000000000..6f535b8372d54 --- /dev/null +++ b/packages/core/versioning/core-version-http-server/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts" + ], + "kbn_references": [ + ], + "exclude": [ + "target/**/*", + ] +} From 9d09f489eae0693c971b0192f984930cac7bf4e9 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 20 Feb 2023 11:27:09 +0100 Subject: [PATCH 02/12] added initial version of types --- .../core-version-http-server/index.ts | 3 + .../core-version-http-server/src/example.ts | 42 ++++++ .../src/version_http_toolkit.ts | 124 ++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 packages/core/versioning/core-version-http-server/src/example.ts create mode 100644 packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts diff --git a/packages/core/versioning/core-version-http-server/index.ts b/packages/core/versioning/core-version-http-server/index.ts index 5c2d5b68ae2e0..2ed8fca6a33f4 100644 --- a/packages/core/versioning/core-version-http-server/index.ts +++ b/packages/core/versioning/core-version-http-server/index.ts @@ -5,3 +5,6 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + +// TODO: export once types are ready +export {}; diff --git a/packages/core/versioning/core-version-http-server/src/example.ts b/packages/core/versioning/core-version-http-server/src/example.ts new file mode 100644 index 0000000000000..5daa9b27188f8 --- /dev/null +++ b/packages/core/versioning/core-version-http-server/src/example.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; +import type { IRouter, RequestHandlerContextBase } from '@kbn/core-http-server'; +import type { VersionHTTPToolkit } from './version_http_toolkit'; + +interface MyCustomContext extends RequestHandlerContextBase { + fooService: { create: (value: string) => Promise }; +} +const vtk = {} as unknown as VersionHTTPToolkit; +const router = {} as unknown as IRouter; + +const versionedRouter = vtk.createVersionedRouter({ router }); + +// @ts-ignore unused variable +const versionedRoute = versionedRouter + .post({ + path: '/api/my-app/foo', + options: { timeout: { payload: 60000 } }, + }) + // First version of the API, accepts { foo: string } in the body + .addVersion( + { version: '1', validate: { body: schema.object({ foo: schema.string() }) } }, + async (ctx, req, res) => { + await ctx.fooService.create(req.body.foo); + return res.ok({ body: { foo: req.body.foo } }); + } + ) + // Second version of the API, accepts { fooName: string } in the body + .addVersion( + { version: '2', validate: { body: schema.object({ fooName: schema.string() }) } }, + async (ctx, req, res) => { + await ctx.fooService.create(req.body.fooName); + return res.ok({ body: { fooName: req.body.fooName } }); + } + ); diff --git a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts new file mode 100644 index 0000000000000..c53e8bd09d9fb --- /dev/null +++ b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { + IRouter, + RouteConfig, + RouteMethod, + RequestHandler, + RouteValidatorFullConfig, + RequestHandlerContextBase, +} from '@kbn/core-http-server'; + +type RqCtx = RequestHandlerContextBase; + +/** A set of type literals to determine accepted versions */ +export type Version = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10'; + +/** Arguments to create a {@link VersionedRouter | versioned router}. */ +export interface CreateVersionedRouterArgs { + /** A router instance */ + router: IRouter; +} + +/** + * This interface is the starting point for creating versioned routers and routes + * + * @example + * const versionedRouter = vtk.createVersionedRouter({ router }); + * + * ```ts + * const versionedRoute = versionedRouter + * .post({ + * path: '/api/my-app/foo', + * options: { timeout: { payload: 60000 } }, + * }) + * // First version of the API, accepts { foo: string } in the body + * .addVersion( + * { version: '1', validate: { body: schema.object({ foo: schema.string() }) } }, + * async (ctx, req, res) => { + * await ctx.fooService.create(req.body.foo); + * return res.ok({ body: { foo: req.body.foo } }); + * } + * ) + * // Second version of the API, accepts { fooName: string } in the body + * .addVersion( + * { version: '2', validate: { body: schema.object({ fooName: schema.string() }) } }, + * async (ctx, req, res) => { + * await ctx.fooService.create(req.body.fooName); + * return res.ok({ body: { fooName: req.body.fooName } }); + * } + * ); + * ``` + */ +export interface VersionHTTPToolkit { + /** + * Create a versioned router + * @param args - The arguments to create a versioned router + * @returns A versioned router + */ + createVersionedRouter( + args: CreateVersionedRouterArgs + ): VersionedRouter; +} + +/** + * Configuraiton for a versioned route + */ +export type VersionedRouteConfig = Omit< + RouteConfig, + 'validate' +>; + +/** + * Create an {@link VersionedRoute | versioned route}. + * + * @param config - The route configuration + * @returns A versioned route + */ +export type VersionedRouteRegistrar = ( + config: VersionedRouteConfig +) => VersionedRoute; + +/** + * A router, very similar to {@link IRouter} that will return an {@link VersionedRoute} + * instead. + */ +export interface VersionedRouter { + get: VersionedRouteRegistrar<'get', Ctx>; + put: VersionedRouteRegistrar<'put', Ctx>; + post: VersionedRouteRegistrar<'post', Ctx>; + patch: VersionedRouteRegistrar<'patch', Ctx>; + delete: VersionedRouteRegistrar<'delete', Ctx>; + options: VersionedRouteRegistrar<'options', Ctx>; +} + +/** + * Options for a versioned route. Probably needs a lot more options like sunsetting + * of an endpoint etc. + */ +export interface AddVersionOpts { + /** Version to assign to this route */ + version: Version; + /** Validation for this version of a route */ + validate: false | RouteValidatorFullConfig; +} + +/** A versioned route */ +export interface VersionedRoute { + /** + * Add a new version of this route + * @param opts {@link AddVersionOpts | Options} for this version of a route + * @param handler The request handler for this version of a route + * @returns A versioned route, allows for fluent chaining of version declarations + */ + addVersion( + opts: AddVersionOpts, + handler: RequestHandler + ): VersionedRoute; +} From 02f41b2b8dcbf2b8356a8d0dd7db5e63a42a08ee Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 20 Feb 2023 11:54:57 +0100 Subject: [PATCH 03/12] ran bootstrap --- package.json | 1 + tsconfig.base.json | 2 ++ yarn.lock | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/package.json b/package.json index 29b9ab242452e..9fb2ba9d24704 100644 --- a/package.json +++ b/package.json @@ -326,6 +326,7 @@ "@kbn/core-usage-data-base-server-internal": "link:packages/core/usage-data/core-usage-data-base-server-internal", "@kbn/core-usage-data-server": "link:packages/core/usage-data/core-usage-data-server", "@kbn/core-usage-data-server-internal": "link:packages/core/usage-data/core-usage-data-server-internal", + "@kbn/core-version-http-server": "link:packages/core/versioning/core-version-http-server", "@kbn/cross-cluster-replication-plugin": "link:x-pack/plugins/cross_cluster_replication", "@kbn/crypto": "link:packages/kbn-crypto", "@kbn/crypto-browser": "link:packages/kbn-crypto-browser", diff --git a/tsconfig.base.json b/tsconfig.base.json index e4cf587ee9cb4..cff7037533bfe 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -556,6 +556,8 @@ "@kbn/core-usage-data-server-internal/*": ["packages/core/usage-data/core-usage-data-server-internal/*"], "@kbn/core-usage-data-server-mocks": ["packages/core/usage-data/core-usage-data-server-mocks"], "@kbn/core-usage-data-server-mocks/*": ["packages/core/usage-data/core-usage-data-server-mocks/*"], + "@kbn/core-version-http-server": ["packages/core/versioning/core-version-http-server"], + "@kbn/core-version-http-server/*": ["packages/core/versioning/core-version-http-server/*"], "@kbn/cross-cluster-replication-plugin": ["x-pack/plugins/cross_cluster_replication"], "@kbn/cross-cluster-replication-plugin/*": ["x-pack/plugins/cross_cluster_replication/*"], "@kbn/crypto": ["packages/kbn-crypto"], diff --git a/yarn.lock b/yarn.lock index b1924d5eff093..57f4d956462e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3833,6 +3833,10 @@ version "0.0.0" uid "" +"@kbn/core-version-http-server@link:packages/core/versioning/core-version-http-server": + version "0.0.0" + uid "" + "@kbn/core@link:src/core": version "0.0.0" uid "" From bd0229152cd47e530f422a00e8b654dc580aa6b2 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 20 Feb 2023 11:01:05 +0000 Subject: [PATCH 04/12] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/core/versioning/core-version-http-server/tsconfig.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/versioning/core-version-http-server/tsconfig.json b/packages/core/versioning/core-version-http-server/tsconfig.json index 6f535b8372d54..fa73dc9c397bf 100644 --- a/packages/core/versioning/core-version-http-server/tsconfig.json +++ b/packages/core/versioning/core-version-http-server/tsconfig.json @@ -11,6 +11,8 @@ "**/*.ts" ], "kbn_references": [ + "@kbn/config-schema", + "@kbn/core-http-server", ], "exclude": [ "target/**/*", From e110070439a418b180de1f3e4792ac1c3bcba352 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 20 Feb 2023 11:07:25 +0000 Subject: [PATCH 05/12] [CI] Auto-commit changed files from 'node scripts/generate codeowners' --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 49205649178b0..539d57300669f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -281,6 +281,7 @@ packages/core/usage-data/core-usage-data-base-server-internal @elastic/kibana-co packages/core/usage-data/core-usage-data-server @elastic/kibana-core packages/core/usage-data/core-usage-data-server-internal @elastic/kibana-core packages/core/usage-data/core-usage-data-server-mocks @elastic/kibana-core +packages/core/versioning/core-version-http-server @elastic/kibana-core x-pack/plugins/cross_cluster_replication @elastic/platform-deployment-management packages/kbn-crypto @elastic/kibana-security packages/kbn-crypto-browser @elastic/kibana-core From 5b80d9c2a5c3b1b02e12d80f72eabadee7d5df05 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 20 Feb 2023 14:51:38 +0100 Subject: [PATCH 06/12] Update packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts Co-authored-by: Ahmad Bamieh --- .../core-version-http-server/src/version_http_toolkit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts index c53e8bd09d9fb..e69183e18d66b 100644 --- a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts +++ b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts @@ -18,7 +18,7 @@ import type { type RqCtx = RequestHandlerContextBase; /** A set of type literals to determine accepted versions */ -export type Version = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10'; +export type Version = `${number}`; /** Arguments to create a {@link VersionedRouter | versioned router}. */ export interface CreateVersionedRouterArgs { From f70b66e7f56ad4acba3a855e33accc24aa42db59 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 20 Feb 2023 14:53:21 +0100 Subject: [PATCH 07/12] updated comment --- .../core-version-http-server/src/version_http_toolkit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts index e69183e18d66b..09f2c2097f1cf 100644 --- a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts +++ b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts @@ -17,7 +17,7 @@ import type { type RqCtx = RequestHandlerContextBase; -/** A set of type literals to determine accepted versions */ +/** Assuming that version will be a monotonically increasing number where: version > 0. */ export type Version = `${number}`; /** Arguments to create a {@link VersionedRouter | versioned router}. */ From 8b02ffd9d940d2b5abc996f4026cde78b7256bdb Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 20 Feb 2023 15:04:03 +0100 Subject: [PATCH 08/12] incoroporate rudolfs feedback and fix some typos --- .../core-version-http-server/src/example.ts | 8 ++- .../src/version_http_toolkit.ts | 65 ++++++++++++------- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/packages/core/versioning/core-version-http-server/src/example.ts b/packages/core/versioning/core-version-http-server/src/example.ts index 5daa9b27188f8..7e429c5985ee0 100644 --- a/packages/core/versioning/core-version-http-server/src/example.ts +++ b/packages/core/versioning/core-version-http-server/src/example.ts @@ -21,7 +21,7 @@ const versionedRouter = vtk.createVersionedRouter({ router }); // @ts-ignore unused variable const versionedRoute = versionedRouter .post({ - path: '/api/my-app/foo', + path: '/api/my-app/foo/{name?}', options: { timeout: { payload: 60000 } }, }) // First version of the API, accepts { foo: string } in the body @@ -34,7 +34,11 @@ const versionedRoute = versionedRouter ) // Second version of the API, accepts { fooName: string } in the body .addVersion( - { version: '2', validate: { body: schema.object({ fooName: schema.string() }) } }, + { + version: '2', + path: '/api/my-app/foo/{id?}', // Update the path to something new + validate: { body: schema.object({ fooName: schema.string() }) }, + }, async (ctx, req, res) => { await ctx.fooService.create(req.body.fooName); return res.ok({ body: { fooName: req.body.fooName } }); diff --git a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts index 09f2c2097f1cf..677412f035533 100644 --- a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts +++ b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts @@ -11,6 +11,7 @@ import type { RouteConfig, RouteMethod, RequestHandler, + RouteConfigOptions, RouteValidatorFullConfig, RequestHandlerContextBase, } from '@kbn/core-http-server'; @@ -34,26 +35,30 @@ export interface CreateVersionedRouterArgs { * * ```ts * const versionedRoute = versionedRouter - * .post({ - * path: '/api/my-app/foo', - * options: { timeout: { payload: 60000 } }, - * }) - * // First version of the API, accepts { foo: string } in the body - * .addVersion( - * { version: '1', validate: { body: schema.object({ foo: schema.string() }) } }, - * async (ctx, req, res) => { - * await ctx.fooService.create(req.body.foo); - * return res.ok({ body: { foo: req.body.foo } }); - * } - * ) - * // Second version of the API, accepts { fooName: string } in the body - * .addVersion( - * { version: '2', validate: { body: schema.object({ fooName: schema.string() }) } }, - * async (ctx, req, res) => { - * await ctx.fooService.create(req.body.fooName); - * return res.ok({ body: { fooName: req.body.fooName } }); - * } - * ); + * .post({ + * path: '/api/my-app/foo/{name?}', + * options: { timeout: { payload: 60000 } }, + * }) + * // First version of the API, accepts { foo: string } in the body + * .addVersion( + * { version: '1', validate: { body: schema.object({ foo: schema.string() }) } }, + * async (ctx, req, res) => { + * await ctx.fooService.create(req.body.foo); + * return res.ok({ body: { foo: req.body.foo } }); + * } + * ) + * // Second version of the API, accepts { fooName: string } in the body + * .addVersion( + * { + * version: '2', + * path: '/api/my-app/foo/{id?}', // Update the path to something new + * validate: { body: schema.object({ fooName: schema.string() }) }, + * }, + * async (ctx, req, res) => { + * await ctx.fooService.create(req.body.fooName); + * return res.ok({ body: { fooName: req.body.fooName } }); + * } + * ); * ``` */ export interface VersionHTTPToolkit { @@ -68,7 +73,7 @@ export interface VersionHTTPToolkit { } /** - * Configuraiton for a versioned route + * Configuration for a versioned route */ export type VersionedRouteConfig = Omit< RouteConfig, @@ -83,7 +88,7 @@ export type VersionedRouteConfig = Omit< */ export type VersionedRouteRegistrar = ( config: VersionedRouteConfig -) => VersionedRoute; +) => VersionedRoute; /** * A router, very similar to {@link IRouter} that will return an {@link VersionedRoute} @@ -102,15 +107,25 @@ export interface VersionedRouter { * Options for a versioned route. Probably needs a lot more options like sunsetting * of an endpoint etc. */ -export interface AddVersionOpts { +export interface AddVersionOpts + extends RouteConfigOptions { /** Version to assign to this route */ version: Version; /** Validation for this version of a route */ validate: false | RouteValidatorFullConfig; + /** + * Override the path of of this "route". Useful to update, add or change existing path parameters. + * @note This option should preferably not introduce dramatic changes to the path as we may be + * better of creating a new route entirely. + */ + path?: string; } /** A versioned route */ -export interface VersionedRoute { +export interface VersionedRoute< + Method extends RouteMethod = RouteMethod, + Ctx extends RqCtx = RqCtx +> { /** * Add a new version of this route * @param opts {@link AddVersionOpts | Options} for this version of a route @@ -120,5 +135,5 @@ export interface VersionedRoute { addVersion( opts: AddVersionOpts, handler: RequestHandler - ): VersionedRoute; + ): VersionedRoute; } From 313dd75b2bd3b025f6a2786b9c12881f7ee20b3e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 21 Feb 2023 10:52:03 +0100 Subject: [PATCH 09/12] updated example with some nuanced cases --- .../core-version-http-server/src/example.ts | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/packages/core/versioning/core-version-http-server/src/example.ts b/packages/core/versioning/core-version-http-server/src/example.ts index 7e429c5985ee0..66301caaa0412 100644 --- a/packages/core/versioning/core-version-http-server/src/example.ts +++ b/packages/core/versioning/core-version-http-server/src/example.ts @@ -11,7 +11,7 @@ import type { IRouter, RequestHandlerContextBase } from '@kbn/core-http-server'; import type { VersionHTTPToolkit } from './version_http_toolkit'; interface MyCustomContext extends RequestHandlerContextBase { - fooService: { create: (value: string) => Promise }; + fooService: { create: (value: string, id: undefined | string, name?: string) => Promise }; } const vtk = {} as unknown as VersionHTTPToolkit; const router = {} as unknown as IRouter; @@ -21,26 +21,62 @@ const versionedRouter = vtk.createVersionedRouter({ router }); // @ts-ignore unused variable const versionedRoute = versionedRouter .post({ - path: '/api/my-app/foo/{name?}', + path: '/api/my-app/foo/{fooId?}/{name?}', // <= design mistake, {name?} should be a query parameter, but it has been released... options: { timeout: { payload: 60000 } }, }) - // First version of the API, accepts { foo: string } in the body .addVersion( - { version: '1', validate: { body: schema.object({ foo: schema.string() }) } }, + { + version: '1', + validate: { + params: schema.object({ + fooId: schema.maybe(schema.string({ minLength: 10, maxLength: 13 })), + name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), + }), + body: schema.object({ foo: schema.string() }), + }, + }, async (ctx, req, res) => { - await ctx.fooService.create(req.body.foo); + await ctx.fooService.create(req.body.foo, req.params.fooId, req.params.name); return res.ok({ body: { foo: req.body.foo } }); } ) - // Second version of the API, accepts { fooName: string } in the body + // BREAKING CHANGE: { foo: string } => { fooString: string } in body .addVersion( { version: '2', - path: '/api/my-app/foo/{id?}', // Update the path to something new - validate: { body: schema.object({ fooName: schema.string() }) }, + path: '/api/my-app/foo/{id?}/{name?}', // Update "fooId" => "id", this is not a breaking change! + validate: { + params: schema.object({ + id: schema.maybe(schema.string({ minLength: 10, maxLength: 13 })), + name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), + }), + body: schema.object({ fooString: schema.string() }), + }, + }, + async (ctx, req, res) => { + await ctx.fooService.create(req.body.fooString, req.params.id, req.params.name); + return res.ok({ body: { fooName: req.body.fooString } }); + } + ) + // BREAKING CHANGES: + // 1. Move {name?} from params to query (hopefully an uncommon change) + // 2. Enforce min/max length on fooString + .addVersion( + { + version: '3', + path: '/api/my-app/foo/{id?}', // Breaking change to the path, we move "name" to the query + validate: { + query: schema.object({ + name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), + }), + params: schema.object({ + id: schema.maybe(schema.string({ minLength: 10, maxLength: 13 })), + }), + body: schema.object({ fooString: schema.string({ minLength: 0, maxLength: 1000 }) }), + }, }, async (ctx, req, res) => { - await ctx.fooService.create(req.body.fooName); - return res.ok({ body: { fooName: req.body.fooName } }); + await ctx.fooService.create(req.body.fooString, req.params.id, req.query.name); + return res.ok({ body: { fooName: req.body.fooString } }); } ); From 2c164294415be36db836c363c54e2a3152f1b32b Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 21 Feb 2023 11:05:59 +0100 Subject: [PATCH 10/12] update doc comments with @experimental --- .../src/version_http_toolkit.ts | 78 +++++++++++++++---- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts index 677412f035533..3fa17349cf5dc 100644 --- a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts +++ b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts @@ -18,12 +18,21 @@ import type { type RqCtx = RequestHandlerContextBase; -/** Assuming that version will be a monotonically increasing number where: version > 0. */ +/** + * Assuming that version will be a monotonically increasing number where: version > 0. + * @experimental + */ export type Version = `${number}`; -/** Arguments to create a {@link VersionedRouter | versioned router}. */ +/** + * Arguments to create a {@link VersionedRouter | versioned router}. + * @experimental + */ export interface CreateVersionedRouterArgs { - /** A router instance */ + /** + * A router instance + * @experimental + */ router: IRouter; } @@ -36,36 +45,52 @@ export interface CreateVersionedRouterArgs { * ```ts * const versionedRoute = versionedRouter * .post({ - * path: '/api/my-app/foo/{name?}', + * path: '/api/my-app/foo/{fooId?}/{name?}', // <= design mistake, {name?} should be a query parameter, but it has been released... * options: { timeout: { payload: 60000 } }, * }) - * // First version of the API, accepts { foo: string } in the body * .addVersion( - * { version: '1', validate: { body: schema.object({ foo: schema.string() }) } }, + * { + * version: '1', + * validate: { + * params: schema.object({ + * fooId: schema.maybe(schema.string({ minLength: 10, maxLength: 13 })), + * name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), + * }), + * body: schema.object({ foo: schema.string() }), + * }, + * }, * async (ctx, req, res) => { - * await ctx.fooService.create(req.body.foo); + * await ctx.fooService.create(req.body.foo, req.params.fooId, req.params.name); * return res.ok({ body: { foo: req.body.foo } }); * } * ) - * // Second version of the API, accepts { fooName: string } in the body + * // BREAKING CHANGE: { foo: string } => { fooString: string } in body * .addVersion( * { * version: '2', - * path: '/api/my-app/foo/{id?}', // Update the path to something new - * validate: { body: schema.object({ fooName: schema.string() }) }, + * path: '/api/my-app/foo/{id?}/{name?}', // Update "fooId" => "id", this is not a breaking change! + * validate: { + * params: schema.object({ + * id: schema.maybe(schema.string({ minLength: 10, maxLength: 13 })), + * name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), + * }), + * body: schema.object({ fooString: schema.string() }), + * }, * }, * async (ctx, req, res) => { - * await ctx.fooService.create(req.body.fooName); - * return res.ok({ body: { fooName: req.body.fooName } }); + * await ctx.fooService.create(req.body.fooString, req.params.id, req.params.name); + * return res.ok({ body: { fooName: req.body.fooString } }); * } - * ); + * ) * ``` + * @experimental */ export interface VersionHTTPToolkit { /** * Create a versioned router * @param args - The arguments to create a versioned router * @returns A versioned router + * @experimental */ createVersionedRouter( args: CreateVersionedRouterArgs @@ -74,6 +99,7 @@ export interface VersionHTTPToolkit { /** * Configuration for a versioned route + * @experimental */ export type VersionedRouteConfig = Omit< RouteConfig, @@ -85,6 +111,7 @@ export type VersionedRouteConfig = Omit< * * @param config - The route configuration * @returns A versioned route + * @experimental */ export type VersionedRouteRegistrar = ( config: VersionedRouteConfig @@ -93,35 +120,53 @@ export type VersionedRouteRegistrar { + /** @experimental */ get: VersionedRouteRegistrar<'get', Ctx>; + /** @experimental */ put: VersionedRouteRegistrar<'put', Ctx>; + /** @experimental */ post: VersionedRouteRegistrar<'post', Ctx>; + /** @experimental */ patch: VersionedRouteRegistrar<'patch', Ctx>; + /** @experimental */ delete: VersionedRouteRegistrar<'delete', Ctx>; + /** @experimental */ options: VersionedRouteRegistrar<'options', Ctx>; } /** * Options for a versioned route. Probably needs a lot more options like sunsetting * of an endpoint etc. + * @experimental */ export interface AddVersionOpts extends RouteConfigOptions { - /** Version to assign to this route */ + /** + * Version to assign to this route + * @experimental + */ version: Version; - /** Validation for this version of a route */ + /** + * Validation for this version of a route + * @experimental + */ validate: false | RouteValidatorFullConfig; /** * Override the path of of this "route". Useful to update, add or change existing path parameters. * @note This option should preferably not introduce dramatic changes to the path as we may be * better of creating a new route entirely. + * @experimental */ path?: string; } -/** A versioned route */ +/** + * A versioned route + * @experimental + */ export interface VersionedRoute< Method extends RouteMethod = RouteMethod, Ctx extends RqCtx = RqCtx @@ -131,6 +176,7 @@ export interface VersionedRoute< * @param opts {@link AddVersionOpts | Options} for this version of a route * @param handler The request handler for this version of a route * @returns A versioned route, allows for fluent chaining of version declarations + * @experimental */ addVersion( opts: AddVersionOpts, From c352f4cebaa629668d4ecc0d0d48dd6034025722 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 21 Feb 2023 17:13:44 +0100 Subject: [PATCH 11/12] remove additional options for now and simplify example --- .../core-version-http-server/src/example.ts | 22 +++++++++---------- .../src/version_http_toolkit.ts | 11 +--------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/packages/core/versioning/core-version-http-server/src/example.ts b/packages/core/versioning/core-version-http-server/src/example.ts index 66301caaa0412..de529ccb07d9d 100644 --- a/packages/core/versioning/core-version-http-server/src/example.ts +++ b/packages/core/versioning/core-version-http-server/src/example.ts @@ -21,22 +21,24 @@ const versionedRouter = vtk.createVersionedRouter({ router }); // @ts-ignore unused variable const versionedRoute = versionedRouter .post({ - path: '/api/my-app/foo/{fooId?}/{name?}', // <= design mistake, {name?} should be a query parameter, but it has been released... + path: '/api/my-app/foo/{id?}', options: { timeout: { payload: 60000 } }, }) .addVersion( { version: '1', validate: { - params: schema.object({ - fooId: schema.maybe(schema.string({ minLength: 10, maxLength: 13 })), + query: schema.object({ name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), }), + params: schema.object({ + id: schema.maybe(schema.string({ minLength: 10, maxLength: 13 })), + }), body: schema.object({ foo: schema.string() }), }, }, async (ctx, req, res) => { - await ctx.fooService.create(req.body.foo, req.params.fooId, req.params.name); + await ctx.fooService.create(req.body.foo, req.params.id, req.query.name); return res.ok({ body: { foo: req.body.foo } }); } ) @@ -44,27 +46,25 @@ const versionedRoute = versionedRouter .addVersion( { version: '2', - path: '/api/my-app/foo/{id?}/{name?}', // Update "fooId" => "id", this is not a breaking change! validate: { + query: schema.object({ + name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), + }), params: schema.object({ id: schema.maybe(schema.string({ minLength: 10, maxLength: 13 })), - name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), }), body: schema.object({ fooString: schema.string() }), }, }, async (ctx, req, res) => { - await ctx.fooService.create(req.body.fooString, req.params.id, req.params.name); + await ctx.fooService.create(req.body.fooString, req.params.id, req.query.name); return res.ok({ body: { fooName: req.body.fooString } }); } ) - // BREAKING CHANGES: - // 1. Move {name?} from params to query (hopefully an uncommon change) - // 2. Enforce min/max length on fooString + // BREAKING CHANGES: Enforce min/max length on fooString .addVersion( { version: '3', - path: '/api/my-app/foo/{id?}', // Breaking change to the path, we move "name" to the query validate: { query: schema.object({ name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), diff --git a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts index 3fa17349cf5dc..6044f151612e9 100644 --- a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts +++ b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts @@ -11,7 +11,6 @@ import type { RouteConfig, RouteMethod, RequestHandler, - RouteConfigOptions, RouteValidatorFullConfig, RequestHandlerContextBase, } from '@kbn/core-http-server'; @@ -142,8 +141,7 @@ export interface VersionedRouter { * of an endpoint etc. * @experimental */ -export interface AddVersionOpts - extends RouteConfigOptions { +export interface AddVersionOpts { /** * Version to assign to this route * @experimental @@ -154,13 +152,6 @@ export interface AddVersionOpts; - /** - * Override the path of of this "route". Useful to update, add or change existing path parameters. - * @note This option should preferably not introduce dramatic changes to the path as we may be - * better of creating a new route entirely. - * @experimental - */ - path?: string; } /** From fe9b46f35a2caa5349e30f78ceb18ac363382ac7 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 21 Feb 2023 17:14:17 +0100 Subject: [PATCH 12/12] updated doc comment --- .../src/version_http_toolkit.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts index 6044f151612e9..719e0075c0070 100644 --- a/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts +++ b/packages/core/versioning/core-version-http-server/src/version_http_toolkit.ts @@ -44,22 +44,24 @@ export interface CreateVersionedRouterArgs { * ```ts * const versionedRoute = versionedRouter * .post({ - * path: '/api/my-app/foo/{fooId?}/{name?}', // <= design mistake, {name?} should be a query parameter, but it has been released... + * path: '/api/my-app/foo/{id?}', * options: { timeout: { payload: 60000 } }, * }) * .addVersion( * { * version: '1', * validate: { - * params: schema.object({ - * fooId: schema.maybe(schema.string({ minLength: 10, maxLength: 13 })), + * query: schema.object({ * name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), * }), + * params: schema.object({ + * id: schema.maybe(schema.string({ minLength: 10, maxLength: 13 })), + * }), * body: schema.object({ foo: schema.string() }), * }, * }, * async (ctx, req, res) => { - * await ctx.fooService.create(req.body.foo, req.params.fooId, req.params.name); + * await ctx.fooService.create(req.body.foo, req.params.id, req.query.name); * return res.ok({ body: { foo: req.body.foo } }); * } * ) @@ -67,17 +69,18 @@ export interface CreateVersionedRouterArgs { * .addVersion( * { * version: '2', - * path: '/api/my-app/foo/{id?}/{name?}', // Update "fooId" => "id", this is not a breaking change! * validate: { + * query: schema.object({ + * name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), + * }), * params: schema.object({ * id: schema.maybe(schema.string({ minLength: 10, maxLength: 13 })), - * name: schema.maybe(schema.string({ minLength: 2, maxLength: 50 })), * }), * body: schema.object({ fooString: schema.string() }), * }, * }, * async (ctx, req, res) => { - * await ctx.fooService.create(req.body.fooString, req.params.id, req.params.name); + * await ctx.fooService.create(req.body.fooString, req.params.id, req.query.name); * return res.ok({ body: { fooName: req.body.fooString } }); * } * )