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

Add context type parameter to IRouter and createRouter #57674

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Provides ability to declare a handler function for a particular path and HTTP re
<b>Signature:</b>

```typescript
createRouter: () => IRouter;
createRouter: <Context extends RequestHandlerContext = RequestHandlerContext>() => IRouter<Context>;
```

## Remarks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ async (context, request, response) => {
| [auth](./kibana-plugin-server.httpservicesetup.auth.md) | <code>{</code><br/><code> get: GetAuthState;</code><br/><code> isAuthenticated: IsAuthenticated;</code><br/><code> }</code> | |
| [basePath](./kibana-plugin-server.httpservicesetup.basepath.md) | <code>IBasePath</code> | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-server.ibasepath.md)<!-- -->. |
| [createCookieSessionStorageFactory](./kibana-plugin-server.httpservicesetup.createcookiesessionstoragefactory.md) | <code>&lt;T&gt;(cookieOptions: SessionStorageCookieOptions&lt;T&gt;) =&gt; Promise&lt;SessionStorageFactory&lt;T&gt;&gt;</code> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) |
| [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | <code>() =&gt; IRouter</code> | Provides ability to declare a handler function for a particular path and HTTP request method. |
| [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | <code>&lt;Context extends RequestHandlerContext = RequestHandlerContext&gt;() =&gt; IRouter&lt;Context&gt;</code> | Provides ability to declare a handler function for a particular path and HTTP request method. |
| [csp](./kibana-plugin-server.httpservicesetup.csp.md) | <code>ICspConfig</code> | The CSP config used for Kibana. |
| [getServerInfo](./kibana-plugin-server.httpservicesetup.getserverinfo.md) | <code>() =&gt; HttpServerInfo</code> | Provides common [information](./kibana-plugin-server.httpserverinfo.md) about the running http server. |
| [isTlsEnabled](./kibana-plugin-server.httpservicesetup.istlsenabled.md) | <code>boolean</code> | Flag showing whether a server was configured to use TLS connection. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Register a route handler for `DELETE` request.
<b>Signature:</b>

```typescript
delete: RouteRegistrar<'delete'>;
delete: RouteRegistrar<'delete', Context>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Register a route handler for `GET` request.
<b>Signature:</b>

```typescript
get: RouteRegistrar<'get'>;
get: RouteRegistrar<'get', Context>;
```
12 changes: 6 additions & 6 deletions docs/development/core/server/kibana-plugin-server.irouter.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ Registers route handlers for specified resource path and method. See [RouteConfi
<b>Signature:</b>

```typescript
export interface IRouter
export interface IRouter<Context extends RequestHandlerContext = RequestHandlerContext>
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [delete](./kibana-plugin-server.irouter.delete.md) | <code>RouteRegistrar&lt;'delete'&gt;</code> | Register a route handler for <code>DELETE</code> request. |
| [get](./kibana-plugin-server.irouter.get.md) | <code>RouteRegistrar&lt;'get'&gt;</code> | Register a route handler for <code>GET</code> request. |
| [delete](./kibana-plugin-server.irouter.delete.md) | <code>RouteRegistrar&lt;'delete', Context&gt;</code> | Register a route handler for <code>DELETE</code> request. |
| [get](./kibana-plugin-server.irouter.get.md) | <code>RouteRegistrar&lt;'get', Context&gt;</code> | Register a route handler for <code>GET</code> request. |
| [handleLegacyErrors](./kibana-plugin-server.irouter.handlelegacyerrors.md) | <code>&lt;P, Q, B&gt;(handler: RequestHandler&lt;P, Q, B&gt;) =&gt; RequestHandler&lt;P, Q, B&gt;</code> | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. |
| [patch](./kibana-plugin-server.irouter.patch.md) | <code>RouteRegistrar&lt;'patch'&gt;</code> | Register a route handler for <code>PATCH</code> request. |
| [post](./kibana-plugin-server.irouter.post.md) | <code>RouteRegistrar&lt;'post'&gt;</code> | Register a route handler for <code>POST</code> request. |
| [put](./kibana-plugin-server.irouter.put.md) | <code>RouteRegistrar&lt;'put'&gt;</code> | Register a route handler for <code>PUT</code> request. |
| [patch](./kibana-plugin-server.irouter.patch.md) | <code>RouteRegistrar&lt;'patch', Context&gt;</code> | Register a route handler for <code>PATCH</code> request. |
| [post](./kibana-plugin-server.irouter.post.md) | <code>RouteRegistrar&lt;'post', Context&gt;</code> | Register a route handler for <code>POST</code> request. |
| [put](./kibana-plugin-server.irouter.put.md) | <code>RouteRegistrar&lt;'put', Context&gt;</code> | Register a route handler for <code>PUT</code> request. |
| [routerPath](./kibana-plugin-server.irouter.routerpath.md) | <code>string</code> | Resulted path |

Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Register a route handler for `PATCH` request.
<b>Signature:</b>

```typescript
patch: RouteRegistrar<'patch'>;
patch: RouteRegistrar<'patch', Context>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Register a route handler for `POST` request.
<b>Signature:</b>

```typescript
post: RouteRegistrar<'post'>;
post: RouteRegistrar<'post', Context>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Register a route handler for `PUT` request.
<b>Signature:</b>

```typescript
put: RouteRegistrar<'put'>;
put: RouteRegistrar<'put', Context>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A function executed when route path matched requested resource path. Request han
<b>Signature:</b>

```typescript
export declare type RequestHandler<P = unknown, Q = unknown, B = unknown, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest<P, Q, B, Method>, response: KibanaResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
export declare type RequestHandler<P = unknown, Q = unknown, B = unknown, Method extends RouteMethod = any, Context extends RequestHandlerContext = RequestHandlerContext> = (context: Context, request: KibanaRequest<P, Q, B, Method>, response: KibanaResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
```

## Example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Route handler common definition
<b>Signature:</b>

```typescript
export declare type RouteRegistrar<Method extends RouteMethod> = <P, Q, B>(route: RouteConfig<P, Q, B, Method>, handler: RequestHandler<P, Q, B, Method>) => void;
export declare type RouteRegistrar<Method extends RouteMethod, Context extends RequestHandlerContext = RequestHandlerContext> = <P, Q, B>(route: RouteConfig<P, Q, B, Method>, handler: RequestHandler<P, Q, B, Method, Context>) => void;
```
2 changes: 1 addition & 1 deletion src/core/server/http/http_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const createSetupContractMock = () => {
registerOnPreAuth: jest.fn(),
registerAuth: jest.fn(),
registerOnPostAuth: jest.fn(),
registerRouteHandlerContext: jest.fn(),
registerRouteHandlerContext: jest.fn() as any,
registerOnPreResponse: jest.fn(),
createRouter: jest.fn().mockImplementation(() => mockRouter.create({})),
basePath: createBasePathMock(),
Expand Down
19 changes: 13 additions & 6 deletions src/core/server/http/http_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,25 @@ export class HttpService implements CoreService<InternalHttpServiceSetup, HttpSe
const contract: InternalHttpServiceSetup = {
...serverContract,

createRouter: (path: string, pluginId: PluginOpaqueId = this.coreContext.coreId) => {
createRouter: <Context extends RequestHandlerContext = RequestHandlerContext>(
path: string,
pluginId: PluginOpaqueId = this.coreContext.coreId
) => {
const enhanceHandler = this.requestHandlerContext!.createHandler.bind(null, pluginId);
const router = new Router(path, this.log, enhanceHandler);
const router = new Router<Context>(path, this.log, enhanceHandler);
registerRouter(router);
return router;
},

registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(
registerRouteHandlerContext: <
TContextType extends RequestHandlerContext = RequestHandlerContext,
TContextName extends keyof TContextType = 'core'
>(
pluginOpaqueId: PluginOpaqueId,
contextName: T,
provider: RequestHandlerContextProvider<T>
) => this.requestHandlerContext!.registerContext(pluginOpaqueId, contextName, provider),
contextName: TContextName,
provider: RequestHandlerContextProvider<TContextName, TContextType>
) =>
this.requestHandlerContext!.registerContext(pluginOpaqueId, contextName as any, provider),
};

return contract;
Expand Down
10 changes: 5 additions & 5 deletions src/core/server/http/router/error_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ import { RequestHandler } from './router';
import { RequestHandlerContext } from '../../../server';
import { RouteMethod } from './route';

export const wrapErrors = <P, Q, B>(
handler: RequestHandler<P, Q, B, RouteMethod>
): RequestHandler<P, Q, B, RouteMethod> => {
export const wrapErrors = <P, Q, B, M extends RouteMethod, C extends RequestHandlerContext>(
handler: RequestHandler<P, Q, B, M, C>
): RequestHandler<P, Q, B, M, C> => {
return async (
context: RequestHandlerContext,
request: KibanaRequest<P, Q, B, RouteMethod>,
context: C,
request: KibanaRequest<P, Q, B, M>,
response: KibanaResponseFactory
) => {
try {
Expand Down
68 changes: 42 additions & 26 deletions src/core/server/http/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@ interface RouterRoute {
*
* @public
*/
export type RouteRegistrar<Method extends RouteMethod> = <P, Q, B>(
export type RouteRegistrar<
Method extends RouteMethod,
Context extends RequestHandlerContext = RequestHandlerContext
> = <P, Q, B>(
route: RouteConfig<P, Q, B, Method>,
handler: RequestHandler<P, Q, B, Method>
handler: RequestHandler<P, Q, B, Method, Context>
) => void;

/**
Expand All @@ -53,7 +56,7 @@ export type RouteRegistrar<Method extends RouteMethod> = <P, Q, B>(
*
* @public
*/
export interface IRouter {
export interface IRouter<Context extends RequestHandlerContext = RequestHandlerContext> {
/**
* Resulted path
*/
Expand All @@ -64,35 +67,35 @@ export interface IRouter {
* @param route {@link RouteConfig} - a route configuration.
* @param handler {@link RequestHandler} - a function to call to respond to an incoming request
*/
get: RouteRegistrar<'get'>;
get: RouteRegistrar<'get', Context>;

/**
* Register a route handler for `POST` request.
* @param route {@link RouteConfig} - a route configuration.
* @param handler {@link RequestHandler} - a function to call to respond to an incoming request
*/
post: RouteRegistrar<'post'>;
post: RouteRegistrar<'post', Context>;

/**
* Register a route handler for `PUT` request.
* @param route {@link RouteConfig} - a route configuration.
* @param handler {@link RequestHandler} - a function to call to respond to an incoming request
*/
put: RouteRegistrar<'put'>;
put: RouteRegistrar<'put', Context>;

/**
* Register a route handler for `PATCH` request.
* @param route {@link RouteConfig} - a route configuration.
* @param handler {@link RequestHandler} - a function to call to respond to an incoming request
*/
patch: RouteRegistrar<'patch'>;
patch: RouteRegistrar<'patch', Context>;

/**
* Register a route handler for `DELETE` request.
* @param route {@link RouteConfig} - a route configuration.
* @param handler {@link RequestHandler} - a function to call to respond to an incoming request
*/
delete: RouteRegistrar<'delete'>;
delete: RouteRegistrar<'delete', Context>;

/**
* Wrap a router handler to catch and converts legacy boom errors to proper custom errors.
Expand All @@ -108,9 +111,15 @@ export interface IRouter {
getRoutes: () => RouterRoute[];
}

export type ContextEnhancer<P, Q, B, Method extends RouteMethod> = (
handler: RequestHandler<P, Q, B, Method>
) => RequestHandlerEnhanced<P, Q, B, Method>;
export type ContextEnhancer<
P,
Q,
B,
Method extends RouteMethod,
Context extends RequestHandlerContext
> = (
handler: RequestHandler<P, Q, B, Method, Context>
) => RequestHandlerEnhanced<P, Q, B, Method, Context>;

function getRouteFullPath(routerPath: string, routePath: string) {
// If router's path ends with slash and route's path starts with slash,
Expand Down Expand Up @@ -193,22 +202,22 @@ function validOptions(
/**
* @internal
*/
export class Router implements IRouter {
export class Router<Context extends RequestHandlerContext> implements IRouter<Context> {
public routes: Array<Readonly<RouterRoute>> = [];
public get: IRouter['get'];
public post: IRouter['post'];
public delete: IRouter['delete'];
public put: IRouter['put'];
public patch: IRouter['patch'];
public get: IRouter<Context>['get'];
public post: IRouter<Context>['post'];
public delete: IRouter<Context>['delete'];
public put: IRouter<Context>['put'];
public patch: IRouter<Context>['patch'];

constructor(
public readonly routerPath: string,
private readonly log: Logger,
private readonly enhanceWithContext: ContextEnhancer<any, any, any, any>
private readonly enhanceWithContext: ContextEnhancer<any, any, any, any, any>
) {
const buildMethod = <Method extends RouteMethod>(method: Method) => <P, Q, B>(
route: RouteConfig<P, Q, B, Method>,
handler: RequestHandler<P, Q, B, Method>
handler: RequestHandler<P, Q, B, Method, Context>
) => {
const routeSchemas = routeSchemasFromRouteConfig(route, method);

Expand Down Expand Up @@ -237,7 +246,9 @@ export class Router implements IRouter {
return [...this.routes];
}

public handleLegacyErrors<P, Q, B>(handler: RequestHandler<P, Q, B>): RequestHandler<P, Q, B> {
public handleLegacyErrors<P, Q, B, M extends RouteMethod, C extends RequestHandlerContext>(
handler: RequestHandler<P, Q, B, M, C>
): RequestHandler<P, Q, B, M, C> {
return wrapErrors(handler);
}

Expand All @@ -249,7 +260,7 @@ export class Router implements IRouter {
}: {
request: Request;
responseToolkit: ResponseToolkit;
handler: RequestHandlerEnhanced<P, Q, B, typeof request.method>;
handler: RequestHandlerEnhanced<P, Q, B, typeof request.method, Context>;
routeSchemas?: RouteValidator<P, Q, B>;
}) {
let kibanaRequest: KibanaRequest<P, Q, B, typeof request.method>;
Expand All @@ -274,9 +285,13 @@ type WithoutHeadArgument<T> = T extends (first: any, ...rest: infer Params) => i
? (...rest: Params) => Return
: never;

type RequestHandlerEnhanced<P, Q, B, Method extends RouteMethod> = WithoutHeadArgument<
RequestHandler<P, Q, B, Method>
>;
type RequestHandlerEnhanced<
P,
Q,
B,
Method extends RouteMethod,
Context extends RequestHandlerContext
> = WithoutHeadArgument<RequestHandler<P, Q, B, Method, Context>>;

/**
* A function executed when route path matched requested resource path.
Expand Down Expand Up @@ -316,9 +331,10 @@ export type RequestHandler<
P = unknown,
Q = unknown,
B = unknown,
Method extends RouteMethod = any
Method extends RouteMethod = any,
Context extends RequestHandlerContext = RequestHandlerContext
> = (
context: RequestHandlerContext,
context: Context,
request: KibanaRequest<P, Q, B, Method>,
response: KibanaResponseFactory
) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
32 changes: 22 additions & 10 deletions src/core/server/http/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ export type RequestHandlerContextContainer = IContextContainer<RequestHandler<an
* @public
*/
export type RequestHandlerContextProvider<
TContextName extends keyof RequestHandlerContext
> = IContextProvider<RequestHandler<any, any, any>, TContextName>;
TContextName extends keyof TContextType,
TContextType extends RequestHandlerContext = RequestHandlerContext
> = IContextProvider<RequestHandler<any, any, any, any, TContextType>, TContextName>;

/**
* Kibana HTTP Service provides own abstraction for work with HTTP stack.
Expand Down Expand Up @@ -223,7 +224,9 @@ export interface HttpServiceSetup {
* ```
* @public
*/
createRouter: () => IRouter;
createRouter: <Context extends RequestHandlerContext = RequestHandlerContext>() => IRouter<
Context
>;

/**
* Register a context provider for a route handler.
Expand All @@ -248,9 +251,12 @@ export interface HttpServiceSetup {
* ```
* @public
*/
registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(
contextName: T,
provider: RequestHandlerContextProvider<T>
registerRouteHandlerContext: <
TContextType extends RequestHandlerContext = RequestHandlerContext,
TContextName extends keyof TContextType = 'core'
>(
contextName: TContextName,
provider: RequestHandlerContextProvider<TContextName, TContextType>
Comment on lines -251 to +259
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, some explanations on that:

I introduced the new TContextType parametrized type. this one has a default, but is explicit. TContextName had no default, but was explicit. as defaulting generics must be declared after non-default ones, I could have done

registerRouteHandlerContext: <
    TContextName extends keyof TContextType
    TContextType extends RequestHandlerContext = RequestHandlerContext,
  >

However, the TContextName is inferred from the input parameter, and TContextType is not. That would have forced to declare the name generic on every calls:

http.registerRouteHandlerContext<'search', MyCustomHandlerContext>('search', provider)

The default=core trick allow to reverse the generics order, to have the inferred one last, and allow not to declare it:

http.registerRouteHandlerContext<MyCustomHandlerContext>('search', provider)

) => RequestHandlerContextContainer;

/**
Expand All @@ -264,12 +270,18 @@ export interface InternalHttpServiceSetup
extends Omit<HttpServiceSetup, 'createRouter' | 'registerRouteHandlerContext'> {
auth: HttpServerSetup['auth'];
server: HttpServerSetup['server'];
createRouter: (path: string, plugin?: PluginOpaqueId) => IRouter;
createRouter: <Context extends RequestHandlerContext = RequestHandlerContext>(
path: string,
plugin?: PluginOpaqueId
) => IRouter<Context>;
getAuthHeaders: GetAuthHeaders;
registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(
registerRouteHandlerContext: <
TContextType extends RequestHandlerContext = RequestHandlerContext,
TContextName extends keyof TContextType = 'core'
>(
pluginOpaqueId: PluginOpaqueId,
contextName: T,
provider: RequestHandlerContextProvider<T>
contextName: TContextName,
provider: RequestHandlerContextProvider<TContextName, TContextType>
) => RequestHandlerContextContainer;
}

Expand Down
Loading