Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HTTP] Versioned router implementation #153543

Merged
merged 43 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
30c31e1
move types to "core-versioned-http-server"
jloleysens Mar 22, 2023
52aff31
rename "version" to "versioned" in all files
jloleysens Mar 22, 2023
e5a80bc
export types
jloleysens Mar 22, 2023
1e88fac
remove the concept of versioned http toolkit
jloleysens Mar 22, 2023
1d28474
remove concept of versioned toolkit from code
jloleysens Mar 22, 2023
b191455
added getRoutes function and moved required "access" property to top-…
jloleysens Mar 22, 2023
3b74e08
Added VersionedRouterRoute type
jloleysens Mar 22, 2023
d720fdf
one small step for versioned http
jloleysens Mar 22, 2023
2124cae
added some check for "not found" 406 responses
jloleysens Mar 23, 2023
5a76452
move to "from" pattern for internal versioned router
jloleysens Mar 23, 2023
dc6b41b
update index exports
jloleysens Mar 23, 2023
1890a96
update index exports
jloleysens Mar 23, 2023
51f63db
updated types a bit more
jloleysens Mar 23, 2023
256a47f
got first iteration of the versioned router working, need a lot more …
jloleysens Mar 23, 2023
78a9e59
ran yarn kbn bootstrap
jloleysens Mar 23, 2023
02f7eef
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Mar 23, 2023
a2601b2
[CI] Auto-commit changed files from 'node scripts/generate codeowners'
kibanamachine Mar 23, 2023
929b3ef
remove export of non-existent types
jloleysens Mar 23, 2023
7422334
moved types to core-http-server, bye bye boilerplate
jloleysens Mar 23, 2023
9a751f2
move implementation to core/http
jloleysens Mar 23, 2023
f9241f6
run yarn kbn bootstrap and moved some internal types to internal package
jloleysens Mar 23, 2023
879575b
remove unhelpful comment
jloleysens Mar 23, 2023
f76be86
remove unnecessary type cast
jloleysens Mar 23, 2023
c83fdea
fix jest config and rename Internal... to Core...
jloleysens Mar 23, 2023
83d1d48
register route in the contstructor
jloleysens Mar 23, 2023
fd84e23
improve readbility of nullish checking
jloleysens Mar 23, 2023
3a7dfb7
[CI] Auto-commit changed files from 'node scripts/generate codeowners'
kibanamachine Mar 23, 2023
2bbc777
added some more tests
jloleysens Mar 23, 2023
91d54bf
updated test message
jloleysens Mar 23, 2023
82a2147
clean making the core request mutable
jloleysens Mar 24, 2023
3e79036
Merge branch 'main' into versioned-router-impl
jloleysens Mar 24, 2023
ae1a918
add "type" to type only import
jloleysens Mar 24, 2023
991984d
added status code to body result
jloleysens Mar 24, 2023
e7f3b6d
Pass in the mutated request
jloleysens Mar 24, 2023
d0363a3
improve if statement to run validation if there is at least some inpu…
jloleysens Mar 24, 2023
4ebba29
use mutable kibana request
jloleysens Mar 24, 2023
ff4c755
Merge branch 'main' into versioned-router-impl
jloleysens Mar 27, 2023
2970540
slight update to types
jloleysens Mar 27, 2023
0cf0319
added internal comment and updated test
jloleysens Mar 27, 2023
9e2e542
Merge branch 'main' into versioned-router-impl
jloleysens Mar 28, 2023
9ae403e
remove example.ts file, move to doc comment
jloleysens Mar 28, 2023
abb110d
refactor names of some versioning specific types
jloleysens Mar 28, 2023
7abb5da
fix ts issue
jloleysens Mar 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ packages/core/http/core-http-router-server-mocks @elastic/kibana-core
packages/core/http/core-http-server @elastic/kibana-core
packages/core/http/core-http-server-internal @elastic/kibana-core
packages/core/http/core-http-server-mocks @elastic/kibana-core
packages/core/http/core-http-versioned-router-server-internal @elastic/kibana-core
packages/core/i18n/core-i18n-browser @elastic/kibana-core
packages/core/i18n/core-i18n-browser-internal @elastic/kibana-core
packages/core/i18n/core-i18n-browser-mocks @elastic/kibana-core
Expand Down Expand Up @@ -284,7 +285,6 @@ 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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@
"@kbn/core-http-router-server-internal": "link:packages/core/http/core-http-router-server-internal",
"@kbn/core-http-server": "link:packages/core/http/core-http-server",
"@kbn/core-http-server-internal": "link:packages/core/http/core-http-server-internal",
"@kbn/core-http-versioned-router-server-internal": "link:packages/core/http/core-http-versioned-router-server-internal",
"@kbn/core-i18n-browser": "link:packages/core/i18n/core-i18n-browser",
"@kbn/core-i18n-browser-internal": "link:packages/core/i18n/core-i18n-browser-internal",
"@kbn/core-i18n-server": "link:packages/core/i18n/core-i18n-server",
Expand Down Expand Up @@ -329,7 +330,6 @@
"@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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export {
isKibanaResponse,
KibanaResponse,
} from './src/response';
export { RouteValidator } from './src/validator';
11 changes: 11 additions & 0 deletions packages/core/http/core-http-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,14 @@ export type {
HttpServiceSetup,
HttpServiceStart,
} from './src/http_contract';

export type {
AddVersionOpts,
VersionedRouteRequestValidation,
VersionedRouteResponseValidation,
ApiVersion,
VersionedRoute,
VersionedRouteConfig,
VersionedRouteRegistrar,
VersionedRouter,
} from './src/versioning';
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# @kbn/core-version-http-server

This package contains types for sever-side HTTP versioning.
This folder contains types for sever-side HTTP versioning.

## Experimental

Expand Down
18 changes: 18 additions & 0 deletions packages/core/http/core-http-server/src/versioning/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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.
*/

export type {
ApiVersion,
AddVersionOpts,
VersionedRouteRequestValidation,
VersionedRouteResponseValidation,
VersionedRoute,
VersionedRouteConfig,
VersionedRouteRegistrar,
VersionedRouter,
} from './types';
225 changes: 225 additions & 0 deletions packages/core/http/core-http-server/src/versioning/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/*
* 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 { Type } from '@kbn/config-schema';
import type { MaybePromise } from '@kbn/utility-types';
import type {
RouteConfig,
RouteMethod,
RequestHandler,
IKibanaResponse,
RouteConfigOptions,
RouteValidatorFullConfig,
RequestHandlerContextBase,
RouteValidationFunction,
} from '../..';

type RqCtx = RequestHandlerContextBase;

/**
* Assuming that version will be a monotonically increasing number where: version > 0.
* @experimental
*/
export type ApiVersion = `${number}`;

/**
* Configuration for a versioned route
* @experimental
*/
export type VersionedRouteConfig<Method extends RouteMethod> = Omit<
RouteConfig<unknown, unknown, unknown, Method>,
'validate' | 'options'
> & {
options?: Omit<RouteConfigOptions<Method>, 'access'>;
/** See {@link RouteConfigOptions<RouteMethod>['access']} */
access: RouteConfigOptions<Method>['access'];
};

/**
* Create an {@link VersionedRoute | versioned route}.
*
* @param config - The route configuration
* @returns A versioned route
* @experimental
*/
export type VersionedRouteRegistrar<Method extends RouteMethod, Ctx extends RqCtx = RqCtx> = (
config: VersionedRouteConfig<Method>
) => VersionedRoute<Method, Ctx>;

/**
* A router, very similar to {@link IRouter} that will return an {@link VersionedRoute}
* instead.
*
* @example
* const versionedRoute = versionedRouter
* .post({
* access: 'internal',
* path: '/api/my-app/foo/{id?}',
* options: { timeout: { payload: 60000 } },
* })
* .addVersion(
* {
* version: '1',
* validate: {
* request: {
* 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() }),
* },
* response: {
* 200: {
* body: schema.object({ foo: schema.string() }),
* },
* },
* },
* },
* async (ctx, req, res) => {
* await ctx.fooService.create(req.body.foo, req.params.id, req.query.name);
* return res.ok({ body: { foo: req.body.foo } });
* }
* )
* // BREAKING CHANGE: { foo: string } => { fooString: string } in body
* .addVersion(
* {
* version: '2',
* validate: {
* request: {
* 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() }),
* },
* response: {
* 200: {
* body: schema.object({ fooName: schema.string() }),
* },
* },
* },
* },
* async (ctx, req, res) => {
* await ctx.fooService.create(req.body.fooString, req.params.id, req.query.name);
* return res.ok({ body: { fooName: req.body.fooString } });
* }
* )
* // BREAKING CHANGES: Enforce min/max length on fooString
* .addVersion(
* {
* version: '3',
* validate: {
* request: {
* 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 }) }),
* },
* response: {
* 200: {
* body: schema.object({ fooName: schema.string() }),
* },
* },
* },
* },
* async (ctx, req, res) => {
* await ctx.fooService.create(req.body.fooString, req.params.id, req.query.name);
* return res.ok({ body: { fooName: req.body.fooString } });
* }
* );

* @experimental
*/
export interface VersionedRouter<Ctx extends RqCtx = RqCtx> {
/** @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 */
export type VersionedRouteRequestValidation<P, Q, B> = RouteValidatorFullConfig<P, Q, B>;

/** @experimental */
export interface VersionedRouteResponseValidation<R> {
[statusCode: number]: { body: RouteValidationFunction<R> | Type<R> };
Copy link
Contributor Author

@jloleysens jloleysens Mar 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following OAS's example here. For the responses we added validation for specific status codes. At the moment this is used to scope validation to a specific status code, but we could go further and throw if an unknown status code was provided.

Note: output validation is intended to run in dev only.

unsafe?: { body?: boolean };
}

/**
* Versioned route validation
* @experimental
*/
interface FullValidationConfig<P, Q, B, R> {
/**
* Validation to run against route inputs: params, query and body
* @experimental
*/
request?: VersionedRouteRequestValidation<P, Q, B>;
/**
* Validation to run against route output
* @note This validation is only intended to run in development. Do not use this
* for setting default values!
* @experimental
*/
response?: VersionedRouteResponseValidation<R>;
}

/**
* Options for a versioned route. Probably needs a lot more options like sunsetting
* of an endpoint etc.
* @experimental
*/
export interface AddVersionOpts<P, Q, B, R> {
/**
* Version to assign to this route
* @experimental
*/
version: ApiVersion;
/**
* Validation for this version of a route
* @experimental
*/
validate: false | FullValidationConfig<P, Q, B, R>;
}

/**
* A versioned route
* @experimental
*/
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
* @param handler The request handler for this version of a route
* @returns A versioned route, allows for fluent chaining of version declarations
* @experimental
*/
addVersion<P = unknown, Q = unknown, B = unknown, R = any>(
options: AddVersionOpts<P, Q, B, R>,
handler: (
...params: Parameters<RequestHandler<P, Q, B, Ctx>>
) => MaybePromise<IKibanaResponse<R>>
): VersionedRoute<Method, Ctx>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# @kbn/core-http-versioned-router-server-internal

This package contains the implementation for sever-side HTTP versioning.

## Experimental

See notes in `@kbn/core-http-server/src/versioning`
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@
* Side Public License, v 1.
*/

// TODO: export once types are ready
export {};
export { CoreVersionedRouter } from './src';
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
module.exports = {
preset: '@kbn/test/jest_node',
rootDir: '../../../..',
roots: ['<rootDir>/packages/core/versioning/core-version-http-server'],
roots: ['<rootDir>/packages/core/http/core-http-versioned-router-server-internal'],
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/core-version-http-server",
"id": "@kbn/core-http-versioned-router-server-internal",
"owner": "@elastic/kibana-core"
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@kbn/core-version-http-server",
"name": "@kbn/core-http-versioned-router-server-internal",
"private": true,
"version": "1.0.0",
"author": "Kibana Core",
Expand Down
Loading