mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* Allow routes to define some payload config values * Documentation typo * Move hapi `payload` config under `body` + additional validations * Update API docs * Amend explanation in API docs * Add stream and buffer types to @kbn/config-schema * Fixes based on PR feedback: - Add 'patch' and 'options' to valid RouteMethod - Add tests for all the new flags - Allow `stream` and `buffer` schema in the body validations (findings from tests) * API documentation update * Fix type definitions * Fix the NITs in the PR comments + better typing inheritance * API docs update * Fix APM-legacy wrapper's types * Fix KibanaRequest.from type exposure of hapi in API docs * Move RouterRoute interface back to private + Expose some public docs * Update @kbn/config-schema docs
This commit is contained in:
parent
5188fd919f
commit
fe7a08c3e2
60 changed files with 1064 additions and 120 deletions
|
@ -9,5 +9,5 @@ returns `basePath` value, specific for an incoming request.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
get: (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest) => string;
|
||||
get: (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest) => string;
|
||||
```
|
||||
|
|
|
@ -16,11 +16,11 @@ export declare class BasePath
|
|||
|
||||
| Property | Modifiers | Type | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [get](./kibana-plugin-server.basepath.get.md) | | <code>(request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest) => string</code> | returns <code>basePath</code> value, specific for an incoming request. |
|
||||
| [get](./kibana-plugin-server.basepath.get.md) | | <code>(request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest) => string</code> | returns <code>basePath</code> value, specific for an incoming request. |
|
||||
| [prepend](./kibana-plugin-server.basepath.prepend.md) | | <code>(path: string) => string</code> | Prepends <code>path</code> with the basePath. |
|
||||
| [remove](./kibana-plugin-server.basepath.remove.md) | | <code>(path: string) => string</code> | Removes the prepended basePath from the <code>path</code>. |
|
||||
| [serverBasePath](./kibana-plugin-server.basepath.serverbasepath.md) | | <code>string</code> | returns the server's basePath<!-- -->See [BasePath.get](./kibana-plugin-server.basepath.get.md) for getting the basePath value for a specific request |
|
||||
| [set](./kibana-plugin-server.basepath.set.md) | | <code>(request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest, requestSpecificBasePath: string) => void</code> | sets <code>basePath</code> value, specific for an incoming request. |
|
||||
| [set](./kibana-plugin-server.basepath.set.md) | | <code>(request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest, requestSpecificBasePath: string) => void</code> | sets <code>basePath</code> value, specific for an incoming request. |
|
||||
|
||||
## Remarks
|
||||
|
||||
|
|
|
@ -9,5 +9,5 @@ sets `basePath` value, specific for an incoming request.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
set: (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest, requestSpecificBasePath: string) => void;
|
||||
set: (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest, requestSpecificBasePath: string) => void;
|
||||
```
|
||||
|
|
|
@ -9,5 +9,5 @@ Register a route handler for `DELETE` request.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
delete: RouteRegistrar;
|
||||
delete: RouteRegistrar<'delete'>;
|
||||
```
|
||||
|
|
|
@ -9,5 +9,5 @@ Register a route handler for `GET` request.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
get: RouteRegistrar;
|
||||
get: RouteRegistrar<'get'>;
|
||||
```
|
||||
|
|
|
@ -16,10 +16,11 @@ export interface IRouter
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [delete](./kibana-plugin-server.irouter.delete.md) | <code>RouteRegistrar</code> | Register a route handler for <code>DELETE</code> request. |
|
||||
| [get](./kibana-plugin-server.irouter.get.md) | <code>RouteRegistrar</code> | Register a route handler for <code>GET</code> request. |
|
||||
| [delete](./kibana-plugin-server.irouter.delete.md) | <code>RouteRegistrar<'delete'></code> | Register a route handler for <code>DELETE</code> request. |
|
||||
| [get](./kibana-plugin-server.irouter.get.md) | <code>RouteRegistrar<'get'></code> | Register a route handler for <code>GET</code> request. |
|
||||
| [handleLegacyErrors](./kibana-plugin-server.irouter.handlelegacyerrors.md) | <code><P extends ObjectType, Q extends ObjectType, B extends ObjectType>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B></code> | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. |
|
||||
| [post](./kibana-plugin-server.irouter.post.md) | <code>RouteRegistrar</code> | Register a route handler for <code>POST</code> request. |
|
||||
| [put](./kibana-plugin-server.irouter.put.md) | <code>RouteRegistrar</code> | Register a route handler for <code>PUT</code> request. |
|
||||
| [patch](./kibana-plugin-server.irouter.patch.md) | <code>RouteRegistrar<'patch'></code> | Register a route handler for <code>PATCH</code> request. |
|
||||
| [post](./kibana-plugin-server.irouter.post.md) | <code>RouteRegistrar<'post'></code> | Register a route handler for <code>POST</code> request. |
|
||||
| [put](./kibana-plugin-server.irouter.put.md) | <code>RouteRegistrar<'put'></code> | Register a route handler for <code>PUT</code> request. |
|
||||
| [routerPath](./kibana-plugin-server.irouter.routerpath.md) | <code>string</code> | Resulted path |
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IRouter](./kibana-plugin-server.irouter.md) > [patch](./kibana-plugin-server.irouter.patch.md)
|
||||
|
||||
## IRouter.patch property
|
||||
|
||||
Register a route handler for `PATCH` request.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
patch: RouteRegistrar<'patch'>;
|
||||
```
|
|
@ -9,5 +9,5 @@ Register a route handler for `POST` request.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
post: RouteRegistrar;
|
||||
post: RouteRegistrar<'post'>;
|
||||
```
|
||||
|
|
|
@ -9,5 +9,5 @@ Register a route handler for `PUT` request.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
put: RouteRegistrar;
|
||||
put: RouteRegistrar<'put'>;
|
||||
```
|
||||
|
|
|
@ -9,7 +9,7 @@ Kibana specific abstraction for an incoming request.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare class KibanaRequest<Params = unknown, Query = unknown, Body = unknown>
|
||||
export declare class KibanaRequest<Params = unknown, Query = unknown, Body = unknown, Method extends RouteMethod = any>
|
||||
```
|
||||
|
||||
## Constructors
|
||||
|
@ -26,7 +26,7 @@ export declare class KibanaRequest<Params = unknown, Query = unknown, Body = unk
|
|||
| [headers](./kibana-plugin-server.kibanarequest.headers.md) | | <code>Headers</code> | Readonly copy of incoming request headers. |
|
||||
| [params](./kibana-plugin-server.kibanarequest.params.md) | | <code>Params</code> | |
|
||||
| [query](./kibana-plugin-server.kibanarequest.query.md) | | <code>Query</code> | |
|
||||
| [route](./kibana-plugin-server.kibanarequest.route.md) | | <code>RecursiveReadonly<KibanaRequestRoute></code> | matched route details |
|
||||
| [route](./kibana-plugin-server.kibanarequest.route.md) | | <code>RecursiveReadonly<KibanaRequestRoute<Method>></code> | matched route details |
|
||||
| [socket](./kibana-plugin-server.kibanarequest.socket.md) | | <code>IKibanaSocket</code> | |
|
||||
| [url](./kibana-plugin-server.kibanarequest.url.md) | | <code>Url</code> | a WHATWG URL standard object. |
|
||||
|
||||
|
|
|
@ -9,5 +9,5 @@ matched route details
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly route: RecursiveReadonly<KibanaRequestRoute>;
|
||||
readonly route: RecursiveReadonly<KibanaRequestRoute<Method>>;
|
||||
```
|
||||
|
|
|
@ -9,14 +9,14 @@ Request specific route information exposed to a handler.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface KibanaRequestRoute
|
||||
export interface KibanaRequestRoute<Method extends RouteMethod>
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [method](./kibana-plugin-server.kibanarequestroute.method.md) | <code>RouteMethod | 'patch' | 'options'</code> | |
|
||||
| [options](./kibana-plugin-server.kibanarequestroute.options.md) | <code>Required<RouteConfigOptions></code> | |
|
||||
| [method](./kibana-plugin-server.kibanarequestroute.method.md) | <code>Method</code> | |
|
||||
| [options](./kibana-plugin-server.kibanarequestroute.options.md) | <code>KibanaRequestRouteOptions<Method></code> | |
|
||||
| [path](./kibana-plugin-server.kibanarequestroute.path.md) | <code>string</code> | |
|
||||
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
method: RouteMethod | 'patch' | 'options';
|
||||
method: Method;
|
||||
```
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
options: Required<RouteConfigOptions>;
|
||||
options: KibanaRequestRouteOptions<Method>;
|
||||
```
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md)
|
||||
|
||||
## KibanaRequestRouteOptions type
|
||||
|
||||
Route options: If 'GET' or 'OPTIONS' method, body options won't be returned.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type KibanaRequestRouteOptions<Method extends RouteMethod> = Method extends 'get' | 'options' ? Required<Omit<RouteConfigOptions<Method>, 'body'>> : Required<RouteConfigOptions<Method>>;
|
||||
```
|
|
@ -83,6 +83,8 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.<!-- -->Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request |
|
||||
| [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. |
|
||||
| [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. |
|
||||
| [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route |
|
||||
| [RouteSchemas](./kibana-plugin-server.routeschemas.md) | RouteSchemas contains the schemas for validating the different parts of a request. |
|
||||
| [SavedObject](./kibana-plugin-server.savedobject.md) | |
|
||||
| [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the <code>attributes</code> property. |
|
||||
| [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. |
|
||||
|
@ -128,6 +130,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| [kibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Set of helpers used to create <code>KibanaResponse</code> to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution. |
|
||||
| [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) | The set of valid body.output |
|
||||
|
||||
## Type Aliases
|
||||
|
||||
|
@ -150,6 +153,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. |
|
||||
| [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. |
|
||||
| [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" <code>ClusterClient</code> but exposes additional <code>callAsCurrentUser</code> method that doesn't use credentials of the Kibana internal user (as <code>callAsInternalUser</code> does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.<!-- -->See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md)<!-- -->. |
|
||||
| [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. |
|
||||
| [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. |
|
||||
| [KnownHeaders](./kibana-plugin-server.knownheaders.md) | Set of well-known HTTP headers. |
|
||||
| [LifecycleResponseFactory](./kibana-plugin-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. |
|
||||
|
@ -170,8 +174,9 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [ResponseError](./kibana-plugin-server.responseerror.md) | Error message and optional data send to the client in case of error. |
|
||||
| [ResponseErrorAttributes](./kibana-plugin-server.responseerrorattributes.md) | Additional data to provide error details. |
|
||||
| [ResponseHeaders](./kibana-plugin-server.responseheaders.md) | Http response headers to set. |
|
||||
| [RouteContentType](./kibana-plugin-server.routecontenttype.md) | The set of supported parseable Content-Types |
|
||||
| [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. |
|
||||
| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Handler to declare a route. |
|
||||
| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Route handler common definition |
|
||||
| [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value |
|
||||
| [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) |
|
||||
| [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.<!-- -->\#\# SavedObjectsClient errors<!-- -->Since the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:<!-- -->1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)<!-- -->Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the <code>isXYZError()</code> helpers exposed at <code>SavedObjectsErrorHelpers</code> should be used to understand and manage error responses from the <code>SavedObjectsClient</code>.<!-- -->Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for <code>error.body.error.type</code> or doing substring checks on <code>error.body.error.reason</code>, just use the helpers to understand the meaning of the error:<!-- -->\`\`\`<!-- -->js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }<!-- -->if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }<!-- -->// always rethrow the error unless you handle it throw error; \`\`\`<!-- -->\#\#\# 404s from missing index<!-- -->From the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.<!-- -->At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.<!-- -->From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.<!-- -->\#\#\# 503s from missing index<!-- -->Unlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's <code>action.auto_create_index</code> setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.<!-- -->See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) |
|
||||
|
|
|
@ -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 extends ObjectType, Q extends ObjectType, B extends ObjectType> = (context: RequestHandlerContext, request: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>>, response: KibanaResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
|
||||
export declare type RequestHandler<P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>, Method>, response: KibanaResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
|
||||
```
|
||||
|
||||
## Example
|
||||
|
|
|
@ -9,14 +9,14 @@ Route specific configuration.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface RouteConfig<P extends ObjectType, Q extends ObjectType, B extends ObjectType>
|
||||
export interface RouteConfig<P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>, Method extends RouteMethod>
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [options](./kibana-plugin-server.routeconfig.options.md) | <code>RouteConfigOptions</code> | Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md)<!-- -->. |
|
||||
| [options](./kibana-plugin-server.routeconfig.options.md) | <code>RouteConfigOptions<Method></code> | Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md)<!-- -->. |
|
||||
| [path](./kibana-plugin-server.routeconfig.path.md) | <code>string</code> | The endpoint \_within\_ the router path to register the route. |
|
||||
| [validate](./kibana-plugin-server.routeconfig.validate.md) | <code>RouteSchemas<P, Q, B> | false</code> | A schema created with <code>@kbn/config-schema</code> that every request will be validated against. |
|
||||
|
||||
|
|
|
@ -9,5 +9,5 @@ Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfig
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
options?: RouteConfigOptions;
|
||||
options?: RouteConfigOptions<Method>;
|
||||
```
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) > [body](./kibana-plugin-server.routeconfigoptions.body.md)
|
||||
|
||||
## RouteConfigOptions.body property
|
||||
|
||||
Additional body options [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md)<!-- -->.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody;
|
||||
```
|
|
@ -9,7 +9,7 @@ Additional route options.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface RouteConfigOptions
|
||||
export interface RouteConfigOptions<Method extends RouteMethod>
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
@ -17,5 +17,6 @@ export interface RouteConfigOptions
|
|||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [authRequired](./kibana-plugin-server.routeconfigoptions.authrequired.md) | <code>boolean</code> | A flag shows that authentication for a route: <code>enabled</code> when true <code>disabled</code> when false<!-- -->Enabled by default. |
|
||||
| [body](./kibana-plugin-server.routeconfigoptions.body.md) | <code>Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody</code> | Additional body options [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md)<!-- -->. |
|
||||
| [tags](./kibana-plugin-server.routeconfigoptions.tags.md) | <code>readonly string[]</code> | Additional metadata tag strings to attach to the route. |
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [accepts](./kibana-plugin-server.routeconfigoptionsbody.accepts.md)
|
||||
|
||||
## RouteConfigOptionsBody.accepts property
|
||||
|
||||
A string or an array of strings with the allowed mime types for the endpoint. Use this settings to limit the set of allowed mime types. Note that allowing additional mime types not listed above will not enable them to be parsed, and if parse is true, the request will result in an error response.
|
||||
|
||||
Default value: allows parsing of the following mime types: \* application/json \* application/\*+json \* application/octet-stream \* application/x-www-form-urlencoded \* multipart/form-data \* text/\*
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
accepts?: RouteContentType | RouteContentType[] | string | string[];
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [maxBytes](./kibana-plugin-server.routeconfigoptionsbody.maxbytes.md)
|
||||
|
||||
## RouteConfigOptionsBody.maxBytes property
|
||||
|
||||
Limits the size of incoming payloads to the specified byte count. Allowing very large payloads may cause the server to run out of memory.
|
||||
|
||||
Default value: The one set in the kibana.yml config file under the parameter `server.maxPayloadBytes`<!-- -->.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
maxBytes?: number;
|
||||
```
|
|
@ -0,0 +1,23 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md)
|
||||
|
||||
## RouteConfigOptionsBody interface
|
||||
|
||||
Additional body options for a route
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface RouteConfigOptionsBody
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [accepts](./kibana-plugin-server.routeconfigoptionsbody.accepts.md) | <code>RouteContentType | RouteContentType[] | string | string[]</code> | A string or an array of strings with the allowed mime types for the endpoint. Use this settings to limit the set of allowed mime types. Note that allowing additional mime types not listed above will not enable them to be parsed, and if parse is true, the request will result in an error response.<!-- -->Default value: allows parsing of the following mime types: \* application/json \* application/\*+json \* application/octet-stream \* application/x-www-form-urlencoded \* multipart/form-data \* text/\* |
|
||||
| [maxBytes](./kibana-plugin-server.routeconfigoptionsbody.maxbytes.md) | <code>number</code> | Limits the size of incoming payloads to the specified byte count. Allowing very large payloads may cause the server to run out of memory.<!-- -->Default value: The one set in the kibana.yml config file under the parameter <code>server.maxPayloadBytes</code>. |
|
||||
| [output](./kibana-plugin-server.routeconfigoptionsbody.output.md) | <code>typeof validBodyOutput[number]</code> | The processed payload format. The value must be one of: \* 'data' - the incoming payload is read fully into memory. If parse is true, the payload is parsed (JSON, form-decoded, multipart) based on the 'Content-Type' header. If parse is false, a raw Buffer is returned. \* 'stream' - the incoming payload is made available via a Stream.Readable interface. If the payload is 'multipart/form-data' and parse is true, field values are presented as text while files are provided as streams. File streams from a 'multipart/form-data' upload will also have a hapi property containing the filename and headers properties. Note that payload streams for multipart payloads are a synthetic interface created on top of the entire multipart content loaded into memory. To avoid loading large multipart payloads into memory, set parse to false and handle the multipart payload in the handler using a streaming parser (e.g. pez).<!-- -->Default value: 'data', unless no validation.body is provided in the route definition. In that case the default is 'stream' to alleviate memory pressure. |
|
||||
| [parse](./kibana-plugin-server.routeconfigoptionsbody.parse.md) | <code>boolean | 'gunzip'</code> | Determines if the incoming payload is processed or presented raw. Available values: \* true - if the request 'Content-Type' matches the allowed mime types set by allow (for the whole payload as well as parts), the payload is converted into an object when possible. If the format is unknown, a Bad Request (400) error response is sent. Any known content encoding is decoded. \* false - the raw payload is returned unmodified. \* 'gunzip' - the raw payload is returned unmodified after any known content encoding is decoded.<!-- -->Default value: true, unless no validation.body is provided in the route definition. In that case the default is false to alleviate memory pressure. |
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [output](./kibana-plugin-server.routeconfigoptionsbody.output.md)
|
||||
|
||||
## RouteConfigOptionsBody.output property
|
||||
|
||||
The processed payload format. The value must be one of: \* 'data' - the incoming payload is read fully into memory. If parse is true, the payload is parsed (JSON, form-decoded, multipart) based on the 'Content-Type' header. If parse is false, a raw Buffer is returned. \* 'stream' - the incoming payload is made available via a Stream.Readable interface. If the payload is 'multipart/form-data' and parse is true, field values are presented as text while files are provided as streams. File streams from a 'multipart/form-data' upload will also have a hapi property containing the filename and headers properties. Note that payload streams for multipart payloads are a synthetic interface created on top of the entire multipart content loaded into memory. To avoid loading large multipart payloads into memory, set parse to false and handle the multipart payload in the handler using a streaming parser (e.g. pez).
|
||||
|
||||
Default value: 'data', unless no validation.body is provided in the route definition. In that case the default is 'stream' to alleviate memory pressure.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
output?: typeof validBodyOutput[number];
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [parse](./kibana-plugin-server.routeconfigoptionsbody.parse.md)
|
||||
|
||||
## RouteConfigOptionsBody.parse property
|
||||
|
||||
Determines if the incoming payload is processed or presented raw. Available values: \* true - if the request 'Content-Type' matches the allowed mime types set by allow (for the whole payload as well as parts), the payload is converted into an object when possible. If the format is unknown, a Bad Request (400) error response is sent. Any known content encoding is decoded. \* false - the raw payload is returned unmodified. \* 'gunzip' - the raw payload is returned unmodified after any known content encoding is decoded.
|
||||
|
||||
Default value: true, unless no validation.body is provided in the route definition. In that case the default is false to alleviate memory pressure.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
parse?: boolean | 'gunzip';
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteContentType](./kibana-plugin-server.routecontenttype.md)
|
||||
|
||||
## RouteContentType type
|
||||
|
||||
The set of supported parseable Content-Types
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type RouteContentType = 'application/json' | 'application/*+json' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/*';
|
||||
```
|
|
@ -9,5 +9,5 @@ The set of common HTTP methods supported by Kibana routing.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type RouteMethod = 'get' | 'post' | 'put' | 'delete';
|
||||
export declare type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options';
|
||||
```
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
## RouteRegistrar type
|
||||
|
||||
Handler to declare a route.
|
||||
Route handler common definition
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type RouteRegistrar = <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(route: RouteConfig<P, Q, B>, handler: RequestHandler<P, Q, B>) => void;
|
||||
export declare type RouteRegistrar<Method extends RouteMethod> = <P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>>(route: RouteConfig<P, Q, B, Method>, handler: RequestHandler<P, Q, B, Method>) => void;
|
||||
```
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [body](./kibana-plugin-server.routeschemas.body.md)
|
||||
|
||||
## RouteSchemas.body property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
body?: B;
|
||||
```
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md)
|
||||
|
||||
## RouteSchemas interface
|
||||
|
||||
RouteSchemas contains the schemas for validating the different parts of a request.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface RouteSchemas<P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>>
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [body](./kibana-plugin-server.routeschemas.body.md) | <code>B</code> | |
|
||||
| [params](./kibana-plugin-server.routeschemas.params.md) | <code>P</code> | |
|
||||
| [query](./kibana-plugin-server.routeschemas.query.md) | <code>Q</code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [params](./kibana-plugin-server.routeschemas.params.md)
|
||||
|
||||
## RouteSchemas.params property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
params?: P;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [query](./kibana-plugin-server.routeschemas.query.md)
|
||||
|
||||
## RouteSchemas.query property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
query?: Q;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [validBodyOutput](./kibana-plugin-server.validbodyoutput.md)
|
||||
|
||||
## validBodyOutput variable
|
||||
|
||||
The set of valid body.output
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
validBodyOutput: readonly ["data", "stream"]
|
||||
```
|
|
@ -12,6 +12,8 @@ Kibana configuration entries providing developers with a fully typed model of th
|
|||
- [`schema.number()`](#schemanumber)
|
||||
- [`schema.boolean()`](#schemaboolean)
|
||||
- [`schema.literal()`](#schemaliteral)
|
||||
- [`schema.buffer()`](#schemabuffer)
|
||||
- [`schema.stream()`](#schemastream)
|
||||
- [Composite types](#composite-types)
|
||||
- [`schema.arrayOf()`](#schemaarrayof)
|
||||
- [`schema.object()`](#schemaobject)
|
||||
|
@ -173,6 +175,36 @@ const valueSchema = [
|
|||
];
|
||||
```
|
||||
|
||||
#### `schema.buffer()`
|
||||
|
||||
Validates input data as a NodeJS `Buffer`.
|
||||
|
||||
__Output type:__ `Buffer`
|
||||
|
||||
__Options:__
|
||||
* `defaultValue: TBuffer | Reference<TBuffer> | (() => TBuffer)` - defines a default value, see [Default values](#default-values) section for more details.
|
||||
* `validate: (value: TBuffer) => Buffer | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
|
||||
|
||||
__Usage:__
|
||||
```typescript
|
||||
const valueSchema = schema.buffer({ defaultValue: Buffer.from('Hi, there!') });
|
||||
```
|
||||
|
||||
#### `schema.stream()`
|
||||
|
||||
Validates input data as a NodeJS `stream`.
|
||||
|
||||
__Output type:__ `Stream`, `Readable` or `Writtable`
|
||||
|
||||
__Options:__
|
||||
* `defaultValue: TStream | Reference<TStream> | (() => TStream)` - defines a default value, see [Default values](#default-values) section for more details.
|
||||
* `validate: (value: TStream) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
|
||||
|
||||
__Usage:__
|
||||
```typescript
|
||||
const valueSchema = schema.stream({ defaultValue: new Stream() });
|
||||
```
|
||||
|
||||
### Composite types
|
||||
|
||||
#### `schema.arrayOf()`
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import { Duration } from 'moment';
|
||||
import { Stream } from 'stream';
|
||||
|
||||
import { ByteSizeValue } from './byte_size_value';
|
||||
import { ContextReference, Reference, SiblingReference } from './references';
|
||||
|
@ -26,6 +27,7 @@ import {
|
|||
ArrayOptions,
|
||||
ArrayType,
|
||||
BooleanType,
|
||||
BufferType,
|
||||
ByteSizeOptions,
|
||||
ByteSizeType,
|
||||
ConditionalType,
|
||||
|
@ -52,6 +54,7 @@ import {
|
|||
UnionType,
|
||||
URIOptions,
|
||||
URIType,
|
||||
StreamType,
|
||||
} from './types';
|
||||
|
||||
export { ObjectType, TypeOf, Type };
|
||||
|
@ -65,6 +68,14 @@ function boolean(options?: TypeOptions<boolean>): Type<boolean> {
|
|||
return new BooleanType(options);
|
||||
}
|
||||
|
||||
function buffer(options?: TypeOptions<Buffer>): Type<Buffer> {
|
||||
return new BufferType(options);
|
||||
}
|
||||
|
||||
function stream(options?: TypeOptions<Stream>): Type<Stream> {
|
||||
return new StreamType(options);
|
||||
}
|
||||
|
||||
function string(options?: StringOptions): Type<string> {
|
||||
return new StringType(options);
|
||||
}
|
||||
|
@ -188,6 +199,7 @@ export const schema = {
|
|||
any,
|
||||
arrayOf,
|
||||
boolean,
|
||||
buffer,
|
||||
byteSize,
|
||||
conditional,
|
||||
contextRef,
|
||||
|
@ -201,6 +213,7 @@ export const schema = {
|
|||
object,
|
||||
oneOf,
|
||||
recordOf,
|
||||
stream,
|
||||
siblingRef,
|
||||
string,
|
||||
uri,
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
} from 'joi';
|
||||
import { isPlainObject } from 'lodash';
|
||||
import { isDuration } from 'moment';
|
||||
import { Stream } from 'stream';
|
||||
import { ByteSizeValue, ensureByteSizeValue } from '../byte_size_value';
|
||||
import { ensureDuration } from '../duration';
|
||||
|
||||
|
@ -89,6 +90,33 @@ export const internals = Joi.extend([
|
|||
},
|
||||
rules: [anyCustomRule],
|
||||
},
|
||||
{
|
||||
name: 'binary',
|
||||
|
||||
base: Joi.binary(),
|
||||
coerce(value: any, state: State, options: ValidationOptions) {
|
||||
// If value isn't defined, let Joi handle default value if it's defined.
|
||||
if (value !== undefined && !(typeof value === 'object' && Buffer.isBuffer(value))) {
|
||||
return this.createError('binary.base', { value }, state, options);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
rules: [anyCustomRule],
|
||||
},
|
||||
{
|
||||
name: 'stream',
|
||||
|
||||
pre(value: any, state: State, options: ValidationOptions) {
|
||||
// If value isn't defined, let Joi handle default value if it's defined.
|
||||
if (value instanceof Stream) {
|
||||
return value as any;
|
||||
}
|
||||
|
||||
return this.createError('stream.base', { value }, state, options);
|
||||
},
|
||||
rules: [anyCustomRule],
|
||||
},
|
||||
{
|
||||
name: 'string',
|
||||
|
||||
|
|
11
packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap
generated
Normal file
11
packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap
generated
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [Buffer] but got [undefined]"`;
|
||||
|
||||
exports[`is required by default 1`] = `"expected value of type [Buffer] but got [undefined]"`;
|
||||
|
||||
exports[`returns error when not a buffer 1`] = `"expected value of type [Buffer] but got [number]"`;
|
||||
|
||||
exports[`returns error when not a buffer 2`] = `"expected value of type [Buffer] but got [Array]"`;
|
||||
|
||||
exports[`returns error when not a buffer 3`] = `"expected value of type [Buffer] but got [string]"`;
|
11
packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap
generated
Normal file
11
packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap
generated
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [Stream] but got [undefined]"`;
|
||||
|
||||
exports[`is required by default 1`] = `"expected value of type [Buffer] but got [undefined]"`;
|
||||
|
||||
exports[`returns error when not a stream 1`] = `"expected value of type [Stream] but got [number]"`;
|
||||
|
||||
exports[`returns error when not a stream 2`] = `"expected value of type [Stream] but got [Array]"`;
|
||||
|
||||
exports[`returns error when not a stream 3`] = `"expected value of type [Stream] but got [string]"`;
|
57
packages/kbn-config-schema/src/types/buffer_type.test.ts
Normal file
57
packages/kbn-config-schema/src/types/buffer_type.test.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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 { schema } from '..';
|
||||
|
||||
test('returns value by default', () => {
|
||||
const value = Buffer.from('Hi!');
|
||||
expect(schema.buffer().validate(value)).toStrictEqual(value);
|
||||
});
|
||||
|
||||
test('is required by default', () => {
|
||||
expect(() => schema.buffer().validate(undefined)).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('includes namespace in failure', () => {
|
||||
expect(() =>
|
||||
schema.buffer().validate(undefined, {}, 'foo-namespace')
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
describe('#defaultValue', () => {
|
||||
test('returns default when undefined', () => {
|
||||
const value = Buffer.from('Hi!');
|
||||
expect(schema.buffer({ defaultValue: value }).validate(undefined)).toStrictEqual(value);
|
||||
});
|
||||
|
||||
test('returns value when specified', () => {
|
||||
const value = Buffer.from('Hi!');
|
||||
expect(schema.buffer({ defaultValue: Buffer.from('Bye!') }).validate(value)).toStrictEqual(
|
||||
value
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('returns error when not a buffer', () => {
|
||||
expect(() => schema.buffer().validate(123)).toThrowErrorMatchingSnapshot();
|
||||
|
||||
expect(() => schema.buffer().validate([1, 2, 3])).toThrowErrorMatchingSnapshot();
|
||||
|
||||
expect(() => schema.buffer().validate('abc')).toThrowErrorMatchingSnapshot();
|
||||
});
|
34
packages/kbn-config-schema/src/types/buffer_type.ts
Normal file
34
packages/kbn-config-schema/src/types/buffer_type.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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 typeDetect from 'type-detect';
|
||||
import { internals } from '../internals';
|
||||
import { Type, TypeOptions } from './type';
|
||||
|
||||
export class BufferType extends Type<Buffer> {
|
||||
constructor(options?: TypeOptions<Buffer>) {
|
||||
super(internals.binary(), options);
|
||||
}
|
||||
|
||||
protected handleError(type: string, { value }: Record<string, any>) {
|
||||
if (type === 'any.required' || type === 'binary.base') {
|
||||
return `expected value of type [Buffer] but got [${typeDetect(value)}]`;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ export { Type, TypeOptions } from './type';
|
|||
export { AnyType } from './any_type';
|
||||
export { ArrayOptions, ArrayType } from './array_type';
|
||||
export { BooleanType } from './boolean_type';
|
||||
export { BufferType } from './buffer_type';
|
||||
export { ByteSizeOptions, ByteSizeType } from './byte_size_type';
|
||||
export { ConditionalType, ConditionalTypeValue } from './conditional_type';
|
||||
export { DurationOptions, DurationType } from './duration_type';
|
||||
|
@ -30,6 +31,7 @@ export { MapOfOptions, MapOfType } from './map_type';
|
|||
export { NumberOptions, NumberType } from './number_type';
|
||||
export { ObjectType, ObjectTypeOptions, Props, TypeOf } from './object_type';
|
||||
export { RecordOfOptions, RecordOfType } from './record_type';
|
||||
export { StreamType } from './stream_type';
|
||||
export { StringOptions, StringType } from './string_type';
|
||||
export { UnionType } from './union_type';
|
||||
export { URIOptions, URIType } from './uri_type';
|
||||
|
|
71
packages/kbn-config-schema/src/types/stream_type.test.ts
Normal file
71
packages/kbn-config-schema/src/types/stream_type.test.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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 { schema } from '..';
|
||||
import { Stream, Readable, Writable, PassThrough } from 'stream';
|
||||
|
||||
test('returns value by default', () => {
|
||||
const value = new Stream();
|
||||
expect(schema.stream().validate(value)).toStrictEqual(value);
|
||||
});
|
||||
|
||||
test('Readable is valid', () => {
|
||||
const value = new Readable();
|
||||
expect(schema.stream().validate(value)).toStrictEqual(value);
|
||||
});
|
||||
|
||||
test('Writable is valid', () => {
|
||||
const value = new Writable();
|
||||
expect(schema.stream().validate(value)).toStrictEqual(value);
|
||||
});
|
||||
|
||||
test('Passthrough is valid', () => {
|
||||
const value = new PassThrough();
|
||||
expect(schema.stream().validate(value)).toStrictEqual(value);
|
||||
});
|
||||
|
||||
test('is required by default', () => {
|
||||
expect(() => schema.buffer().validate(undefined)).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('includes namespace in failure', () => {
|
||||
expect(() =>
|
||||
schema.stream().validate(undefined, {}, 'foo-namespace')
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
describe('#defaultValue', () => {
|
||||
test('returns default when undefined', () => {
|
||||
const value = new Stream();
|
||||
expect(schema.stream({ defaultValue: value }).validate(undefined)).toStrictEqual(value);
|
||||
});
|
||||
|
||||
test('returns value when specified', () => {
|
||||
const value = new Stream();
|
||||
expect(schema.stream({ defaultValue: new PassThrough() }).validate(value)).toStrictEqual(value);
|
||||
});
|
||||
});
|
||||
|
||||
test('returns error when not a stream', () => {
|
||||
expect(() => schema.stream().validate(123)).toThrowErrorMatchingSnapshot();
|
||||
|
||||
expect(() => schema.stream().validate([1, 2, 3])).toThrowErrorMatchingSnapshot();
|
||||
|
||||
expect(() => schema.stream().validate('abc')).toThrowErrorMatchingSnapshot();
|
||||
});
|
35
packages/kbn-config-schema/src/types/stream_type.ts
Normal file
35
packages/kbn-config-schema/src/types/stream_type.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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 typeDetect from 'type-detect';
|
||||
import { Stream } from 'stream';
|
||||
import { internals } from '../internals';
|
||||
import { Type, TypeOptions } from './type';
|
||||
|
||||
export class StreamType extends Type<Stream> {
|
||||
constructor(options?: TypeOptions<Stream>) {
|
||||
super(internals.stream(), options);
|
||||
}
|
||||
|
||||
protected handleError(type: string, { value }: Record<string, any>) {
|
||||
if (type === 'any.required' || type === 'stream.base') {
|
||||
return `expected value of type [Stream] but got [${typeDetect(value)}]`;
|
||||
}
|
||||
}
|
||||
}
|
1
packages/kbn-config-schema/types/joi.d.ts
vendored
1
packages/kbn-config-schema/types/joi.d.ts
vendored
|
@ -38,6 +38,7 @@ declare module 'joi' {
|
|||
duration: () => AnySchema;
|
||||
map: () => MapSchema;
|
||||
record: () => RecordSchema;
|
||||
stream: () => AnySchema;
|
||||
};
|
||||
|
||||
interface AnySchema {
|
||||
|
|
|
@ -54,7 +54,7 @@ function createKibanaRequestMock({
|
|||
}: RequestFixtureOptions = {}) {
|
||||
const queryString = querystring.stringify(query);
|
||||
return KibanaRequest.from(
|
||||
{
|
||||
createRawRequestMock({
|
||||
headers,
|
||||
params,
|
||||
query,
|
||||
|
@ -71,13 +71,13 @@ function createKibanaRequestMock({
|
|||
raw: {
|
||||
req: { socket },
|
||||
},
|
||||
} as any,
|
||||
}),
|
||||
{
|
||||
params: schema.object({}, { allowUnknowns: true }),
|
||||
body: schema.object({}, { allowUnknowns: true }),
|
||||
query: schema.object({}, { allowUnknowns: true }),
|
||||
}
|
||||
);
|
||||
) as KibanaRequest<Readonly<{}>, Readonly<{}>, Readonly<{}>>;
|
||||
}
|
||||
|
||||
type DeepPartial<T> = T extends any[]
|
||||
|
|
|
@ -30,6 +30,7 @@ import { HttpConfig } from './http_config';
|
|||
import { Router } from './router';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
import { HttpServer } from './http_server';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
const cookieOptions = {
|
||||
name: 'sid',
|
||||
|
@ -577,6 +578,157 @@ test('exposes route details of incoming request to a route handler', async () =>
|
|||
});
|
||||
});
|
||||
|
||||
test('exposes route details of incoming request to a route handler (POST + payload options)', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
router.post(
|
||||
{
|
||||
path: '/',
|
||||
validate: { body: schema.object({ test: schema.number() }) },
|
||||
options: { body: { accepts: 'application/json' } },
|
||||
},
|
||||
(context, req, res) => res.ok({ body: req.route })
|
||||
);
|
||||
registerRouter(router);
|
||||
|
||||
await server.start();
|
||||
await supertest(innerServer.listener)
|
||||
.post('/')
|
||||
.send({ test: 1 })
|
||||
.expect(200, {
|
||||
method: 'post',
|
||||
path: '/',
|
||||
options: {
|
||||
authRequired: true,
|
||||
tags: [],
|
||||
body: {
|
||||
parse: true, // hapi populates the default
|
||||
maxBytes: 1024, // hapi populates the default
|
||||
accepts: ['application/json'],
|
||||
output: 'data',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('body options', () => {
|
||||
test('should reject the request because the Content-Type in the request is not valid', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
router.post(
|
||||
{
|
||||
path: '/',
|
||||
validate: { body: schema.object({ test: schema.number() }) },
|
||||
options: { body: { accepts: 'multipart/form-data' } }, // supertest sends 'application/json'
|
||||
},
|
||||
(context, req, res) => res.ok({ body: req.route })
|
||||
);
|
||||
registerRouter(router);
|
||||
|
||||
await server.start();
|
||||
await supertest(innerServer.listener)
|
||||
.post('/')
|
||||
.send({ test: 1 })
|
||||
.expect(415, {
|
||||
statusCode: 415,
|
||||
error: 'Unsupported Media Type',
|
||||
message: 'Unsupported Media Type',
|
||||
});
|
||||
});
|
||||
|
||||
test('should reject the request because the payload is too large', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
router.post(
|
||||
{
|
||||
path: '/',
|
||||
validate: { body: schema.object({ test: schema.number() }) },
|
||||
options: { body: { maxBytes: 1 } },
|
||||
},
|
||||
(context, req, res) => res.ok({ body: req.route })
|
||||
);
|
||||
registerRouter(router);
|
||||
|
||||
await server.start();
|
||||
await supertest(innerServer.listener)
|
||||
.post('/')
|
||||
.send({ test: 1 })
|
||||
.expect(413, {
|
||||
statusCode: 413,
|
||||
error: 'Request Entity Too Large',
|
||||
message: 'Payload content length greater than maximum allowed: 1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should not parse the content in the request', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
router.post(
|
||||
{
|
||||
path: '/',
|
||||
validate: { body: schema.buffer() },
|
||||
options: { body: { parse: false } },
|
||||
},
|
||||
(context, req, res) => {
|
||||
try {
|
||||
expect(req.body).toBeInstanceOf(Buffer);
|
||||
expect(req.body.toString()).toBe(JSON.stringify({ test: 1 }));
|
||||
return res.ok({ body: req.route.options.body });
|
||||
} catch (err) {
|
||||
return res.internalError({ body: err.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
registerRouter(router);
|
||||
|
||||
await server.start();
|
||||
await supertest(innerServer.listener)
|
||||
.post('/')
|
||||
.send({ test: 1 })
|
||||
.expect(200, {
|
||||
parse: false,
|
||||
maxBytes: 1024, // hapi populates the default
|
||||
output: 'data',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should return a stream in the body', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
router.put(
|
||||
{
|
||||
path: '/',
|
||||
validate: { body: schema.stream() },
|
||||
options: { body: { output: 'stream' } },
|
||||
},
|
||||
(context, req, res) => {
|
||||
try {
|
||||
expect(req.body).toBeInstanceOf(Readable);
|
||||
return res.ok({ body: req.route.options.body });
|
||||
} catch (err) {
|
||||
return res.internalError({ body: err.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
registerRouter(router);
|
||||
|
||||
await server.start();
|
||||
await supertest(innerServer.listener)
|
||||
.put('/')
|
||||
.send({ test: 1 })
|
||||
.expect(200, {
|
||||
parse: true,
|
||||
maxBytes: 1024, // hapi populates the default
|
||||
output: 'stream',
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup contract', () => {
|
||||
describe('#createSessionStorage', () => {
|
||||
it('creates session storage factory', async () => {
|
||||
|
|
|
@ -127,21 +127,26 @@ export class HttpServer {
|
|||
for (const router of this.registeredRouters) {
|
||||
for (const route of router.getRoutes()) {
|
||||
this.log.debug(`registering route handler for [${route.path}]`);
|
||||
const { authRequired = true, tags } = route.options;
|
||||
// Hapi does not allow payload validation to be specified for 'head' or 'get' requests
|
||||
const validate = ['head', 'get'].includes(route.method) ? undefined : { payload: true };
|
||||
const { authRequired = true, tags, body = {} } = route.options;
|
||||
const { accepts: allow, maxBytes, output, parse } = body;
|
||||
this.server.route({
|
||||
handler: route.handler,
|
||||
method: route.method,
|
||||
path: route.path,
|
||||
options: {
|
||||
auth: authRequired ? undefined : false,
|
||||
// Enforcing the comparison with true because plugins could overwrite the auth strategy by doing `options: { authRequired: authStrategy as any }`
|
||||
auth: authRequired === true ? undefined : false,
|
||||
tags: tags ? Array.from(tags) : undefined,
|
||||
// TODO: This 'validate' section can be removed once the legacy platform is completely removed.
|
||||
// We are telling Hapi that NP routes can accept any payload, so that it can bypass the default
|
||||
// validation applied in ./http_tools#getServerOptions
|
||||
// (All NP routes are already required to specify their own validation in order to access the payload)
|
||||
validate,
|
||||
payload: [allow, maxBytes, output, parse].some(v => typeof v !== 'undefined')
|
||||
? { allow, maxBytes, output, parse }
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ const createRouterMock = (): jest.Mocked<IRouter> => ({
|
|||
get: jest.fn(),
|
||||
post: jest.fn(),
|
||||
put: jest.fn(),
|
||||
patch: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
getRoutes: jest.fn(),
|
||||
handleLegacyErrors: jest.fn().mockImplementation(handler => handler),
|
||||
|
|
|
@ -30,6 +30,7 @@ export {
|
|||
ErrorHttpResponseOptions,
|
||||
KibanaRequest,
|
||||
KibanaRequestRoute,
|
||||
KibanaRequestRouteOptions,
|
||||
IKibanaResponse,
|
||||
KnownHeaders,
|
||||
LegacyRequest,
|
||||
|
@ -44,8 +45,12 @@ export {
|
|||
RouteConfig,
|
||||
IRouter,
|
||||
RouteMethod,
|
||||
RouteConfigOptions,
|
||||
RouteRegistrar,
|
||||
RouteConfigOptions,
|
||||
RouteSchemas,
|
||||
RouteConfigOptionsBody,
|
||||
RouteContentType,
|
||||
validBodyOutput,
|
||||
} from './router';
|
||||
export { BasePathProxyServer } from './base_path_proxy_server';
|
||||
export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth';
|
||||
|
|
|
@ -23,13 +23,14 @@ import { KibanaRequest } from './request';
|
|||
import { KibanaResponseFactory } from './response';
|
||||
import { RequestHandler } from './router';
|
||||
import { RequestHandlerContext } from '../../../server';
|
||||
import { RouteMethod } from './route';
|
||||
|
||||
export const wrapErrors = <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(
|
||||
handler: RequestHandler<P, Q, B>
|
||||
): RequestHandler<P, Q, B> => {
|
||||
handler: RequestHandler<P, Q, B, RouteMethod>
|
||||
): RequestHandler<P, Q, B, RouteMethod> => {
|
||||
return async (
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>>,
|
||||
request: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>, RouteMethod>,
|
||||
response: KibanaResponseFactory
|
||||
) => {
|
||||
try {
|
||||
|
|
|
@ -22,11 +22,20 @@ export { Router, RequestHandler, IRouter, RouteRegistrar } from './router';
|
|||
export {
|
||||
KibanaRequest,
|
||||
KibanaRequestRoute,
|
||||
KibanaRequestRouteOptions,
|
||||
isRealRequest,
|
||||
LegacyRequest,
|
||||
ensureRawRequest,
|
||||
} from './request';
|
||||
export { RouteMethod, RouteConfig, RouteConfigOptions } from './route';
|
||||
export {
|
||||
RouteMethod,
|
||||
RouteConfig,
|
||||
RouteConfigOptions,
|
||||
RouteSchemas,
|
||||
RouteContentType,
|
||||
RouteConfigOptionsBody,
|
||||
validBodyOutput,
|
||||
} from './route';
|
||||
export { HapiResponseAdapter } from './response_adapter';
|
||||
export {
|
||||
CustomHttpResponseOptions,
|
||||
|
|
|
@ -20,23 +20,32 @@
|
|||
import { Url } from 'url';
|
||||
import { Request } from 'hapi';
|
||||
|
||||
import { ObjectType, TypeOf } from '@kbn/config-schema';
|
||||
import { ObjectType, Type, TypeOf } from '@kbn/config-schema';
|
||||
|
||||
import { Stream } from 'stream';
|
||||
import { deepFreeze, RecursiveReadonly } from '../../../utils';
|
||||
import { Headers } from './headers';
|
||||
import { RouteMethod, RouteSchemas, RouteConfigOptions } from './route';
|
||||
import { RouteMethod, RouteSchemas, RouteConfigOptions, validBodyOutput } from './route';
|
||||
import { KibanaSocket, IKibanaSocket } from './socket';
|
||||
|
||||
const requestSymbol = Symbol('request');
|
||||
|
||||
/**
|
||||
* Route options: If 'GET' or 'OPTIONS' method, body options won't be returned.
|
||||
* @public
|
||||
*/
|
||||
export type KibanaRequestRouteOptions<Method extends RouteMethod> = Method extends 'get' | 'options'
|
||||
? Required<Omit<RouteConfigOptions<Method>, 'body'>>
|
||||
: Required<RouteConfigOptions<Method>>;
|
||||
|
||||
/**
|
||||
* Request specific route information exposed to a handler.
|
||||
* @public
|
||||
* */
|
||||
export interface KibanaRequestRoute {
|
||||
export interface KibanaRequestRoute<Method extends RouteMethod> {
|
||||
path: string;
|
||||
method: RouteMethod | 'patch' | 'options';
|
||||
options: Required<RouteConfigOptions>;
|
||||
method: Method;
|
||||
options: KibanaRequestRouteOptions<Method>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,17 +59,22 @@ export interface LegacyRequest extends Request {} // eslint-disable-line @typesc
|
|||
* Kibana specific abstraction for an incoming request.
|
||||
* @public
|
||||
*/
|
||||
export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
|
||||
export class KibanaRequest<
|
||||
Params = unknown,
|
||||
Query = unknown,
|
||||
Body = unknown,
|
||||
Method extends RouteMethod = any
|
||||
> {
|
||||
/**
|
||||
* Factory for creating requests. Validates the request before creating an
|
||||
* instance of a KibanaRequest.
|
||||
* @internal
|
||||
*/
|
||||
public static from<P extends ObjectType, Q extends ObjectType, B extends ObjectType>(
|
||||
req: Request,
|
||||
routeSchemas?: RouteSchemas<P, Q, B>,
|
||||
withoutSecretHeaders: boolean = true
|
||||
) {
|
||||
public static from<
|
||||
P extends ObjectType,
|
||||
Q extends ObjectType,
|
||||
B extends ObjectType | Type<Buffer> | Type<Stream>
|
||||
>(req: Request, routeSchemas?: RouteSchemas<P, Q, B>, withoutSecretHeaders: boolean = true) {
|
||||
const requestParts = KibanaRequest.validate(req, routeSchemas);
|
||||
return new KibanaRequest(
|
||||
req,
|
||||
|
@ -77,7 +91,11 @@ export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
|
|||
* received in the route handler.
|
||||
* @internal
|
||||
*/
|
||||
private static validate<P extends ObjectType, Q extends ObjectType, B extends ObjectType>(
|
||||
private static validate<
|
||||
P extends ObjectType,
|
||||
Q extends ObjectType,
|
||||
B extends ObjectType | Type<Buffer> | Type<Stream>
|
||||
>(
|
||||
req: Request,
|
||||
routeSchemas: RouteSchemas<P, Q, B> | undefined
|
||||
): {
|
||||
|
@ -113,7 +131,7 @@ export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
|
|||
/** a WHATWG URL standard object. */
|
||||
public readonly url: Url;
|
||||
/** matched route details */
|
||||
public readonly route: RecursiveReadonly<KibanaRequestRoute>;
|
||||
public readonly route: RecursiveReadonly<KibanaRequestRoute<Method>>;
|
||||
/**
|
||||
* Readonly copy of incoming request headers.
|
||||
* @remarks
|
||||
|
@ -148,15 +166,28 @@ export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
|
|||
this.socket = new KibanaSocket(request.raw.req.socket);
|
||||
}
|
||||
|
||||
private getRouteInfo() {
|
||||
private getRouteInfo(): KibanaRequestRoute<Method> {
|
||||
const request = this[requestSymbol];
|
||||
const method = request.method as Method;
|
||||
const { parse, maxBytes, allow, output } = request.route.settings.payload || {};
|
||||
|
||||
const options = ({
|
||||
authRequired: request.route.settings.auth !== false,
|
||||
tags: request.route.settings.tags || [],
|
||||
body: ['get', 'options'].includes(method)
|
||||
? undefined
|
||||
: {
|
||||
parse,
|
||||
maxBytes,
|
||||
accepts: allow,
|
||||
output: output as typeof validBodyOutput[number], // We do not support all the HAPI-supported outputs and TS complains
|
||||
},
|
||||
} as unknown) as KibanaRequestRouteOptions<Method>; // TS does not understand this is OK so I'm enforced to do this enforced casting
|
||||
|
||||
return {
|
||||
path: request.path,
|
||||
method: request.method,
|
||||
options: {
|
||||
authRequired: request.route.settings.auth !== false,
|
||||
tags: request.route.settings.tags || [],
|
||||
},
|
||||
method,
|
||||
options,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,18 +17,89 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ObjectType } from '@kbn/config-schema';
|
||||
import { ObjectType, Type } from '@kbn/config-schema';
|
||||
import { Stream } from 'stream';
|
||||
|
||||
/**
|
||||
* The set of common HTTP methods supported by Kibana routing.
|
||||
* @public
|
||||
*/
|
||||
export type RouteMethod = 'get' | 'post' | 'put' | 'delete';
|
||||
export type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options';
|
||||
|
||||
/**
|
||||
* The set of valid body.output
|
||||
* @public
|
||||
*/
|
||||
export const validBodyOutput = ['data', 'stream'] as const;
|
||||
|
||||
/**
|
||||
* The set of supported parseable Content-Types
|
||||
* @public
|
||||
*/
|
||||
export type RouteContentType =
|
||||
| 'application/json'
|
||||
| 'application/*+json'
|
||||
| 'application/octet-stream'
|
||||
| 'application/x-www-form-urlencoded'
|
||||
| 'multipart/form-data'
|
||||
| 'text/*';
|
||||
|
||||
/**
|
||||
* Additional body options for a route
|
||||
* @public
|
||||
*/
|
||||
export interface RouteConfigOptionsBody {
|
||||
/**
|
||||
* A string or an array of strings with the allowed mime types for the endpoint. Use this settings to limit the set of allowed mime types. Note that allowing additional mime types not listed
|
||||
* above will not enable them to be parsed, and if parse is true, the request will result in an error response.
|
||||
*
|
||||
* Default value: allows parsing of the following mime types:
|
||||
* * application/json
|
||||
* * application/*+json
|
||||
* * application/octet-stream
|
||||
* * application/x-www-form-urlencoded
|
||||
* * multipart/form-data
|
||||
* * text/*
|
||||
*/
|
||||
accepts?: RouteContentType | RouteContentType[] | string | string[];
|
||||
|
||||
/**
|
||||
* Limits the size of incoming payloads to the specified byte count. Allowing very large payloads may cause the server to run out of memory.
|
||||
*
|
||||
* Default value: The one set in the kibana.yml config file under the parameter `server.maxPayloadBytes`.
|
||||
*/
|
||||
maxBytes?: number;
|
||||
|
||||
/**
|
||||
* The processed payload format. The value must be one of:
|
||||
* * 'data' - the incoming payload is read fully into memory. If parse is true, the payload is parsed (JSON, form-decoded, multipart) based on the 'Content-Type' header. If parse is false, a raw
|
||||
* Buffer is returned.
|
||||
* * 'stream' - the incoming payload is made available via a Stream.Readable interface. If the payload is 'multipart/form-data' and parse is true, field values are presented as text while files
|
||||
* are provided as streams. File streams from a 'multipart/form-data' upload will also have a hapi property containing the filename and headers properties. Note that payload streams for multipart
|
||||
* payloads are a synthetic interface created on top of the entire multipart content loaded into memory. To avoid loading large multipart payloads into memory, set parse to false and handle the
|
||||
* multipart payload in the handler using a streaming parser (e.g. pez).
|
||||
*
|
||||
* Default value: 'data', unless no validation.body is provided in the route definition. In that case the default is 'stream' to alleviate memory pressure.
|
||||
*/
|
||||
output?: typeof validBodyOutput[number];
|
||||
|
||||
/**
|
||||
* Determines if the incoming payload is processed or presented raw. Available values:
|
||||
* * true - if the request 'Content-Type' matches the allowed mime types set by allow (for the whole payload as well as parts), the payload is converted into an object when possible. If the
|
||||
* format is unknown, a Bad Request (400) error response is sent. Any known content encoding is decoded.
|
||||
* * false - the raw payload is returned unmodified.
|
||||
* * 'gunzip' - the raw payload is returned unmodified after any known content encoding is decoded.
|
||||
*
|
||||
* Default value: true, unless no validation.body is provided in the route definition. In that case the default is false to alleviate memory pressure.
|
||||
*/
|
||||
parse?: boolean | 'gunzip';
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional route options.
|
||||
* @public
|
||||
*/
|
||||
export interface RouteConfigOptions {
|
||||
export interface RouteConfigOptions<Method extends RouteMethod> {
|
||||
/**
|
||||
* A flag shows that authentication for a route:
|
||||
* `enabled` when true
|
||||
|
@ -42,13 +113,23 @@ export interface RouteConfigOptions {
|
|||
* Additional metadata tag strings to attach to the route.
|
||||
*/
|
||||
tags?: readonly string[];
|
||||
|
||||
/**
|
||||
* Additional body options {@link RouteConfigOptionsBody}.
|
||||
*/
|
||||
body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route specific configuration.
|
||||
* @public
|
||||
*/
|
||||
export interface RouteConfig<P extends ObjectType, Q extends ObjectType, B extends ObjectType> {
|
||||
export interface RouteConfig<
|
||||
P extends ObjectType,
|
||||
Q extends ObjectType,
|
||||
B extends ObjectType | Type<Buffer> | Type<Stream>,
|
||||
Method extends RouteMethod
|
||||
> {
|
||||
/**
|
||||
* The endpoint _within_ the router path to register the route.
|
||||
*
|
||||
|
@ -125,7 +206,7 @@ export interface RouteConfig<P extends ObjectType, Q extends ObjectType, B exten
|
|||
/**
|
||||
* Additional route options {@link RouteConfigOptions}.
|
||||
*/
|
||||
options?: RouteConfigOptions;
|
||||
options?: RouteConfigOptions<Method>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,7 +214,11 @@ export interface RouteConfig<P extends ObjectType, Q extends ObjectType, B exten
|
|||
* request.
|
||||
* @public
|
||||
*/
|
||||
export interface RouteSchemas<P extends ObjectType, Q extends ObjectType, B extends ObjectType> {
|
||||
export interface RouteSchemas<
|
||||
P extends ObjectType,
|
||||
Q extends ObjectType,
|
||||
B extends ObjectType | Type<Buffer> | Type<Stream>
|
||||
> {
|
||||
params?: P;
|
||||
query?: Q;
|
||||
body?: B;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import { Router } from './router';
|
||||
import { loggingServiceMock } from '../../logging/logging_service.mock';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
const logger = loggingServiceMock.create().get();
|
||||
const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {});
|
||||
|
||||
|
@ -45,5 +46,46 @@ describe('Router', () => {
|
|||
`"Expected a valid schema declared with '@kbn/config-schema' package at key: [params]."`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if options.body.output is not a valid value', () => {
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
expect(() =>
|
||||
router.post(
|
||||
// we use 'any' because TS already checks we cannot provide this body.output
|
||||
{
|
||||
path: '/',
|
||||
options: { body: { output: 'file' } } as any, // We explicitly don't support 'file'
|
||||
validate: { body: schema.object({}, { allowUnknowns: true }) },
|
||||
},
|
||||
(context, req, res) => res.ok({})
|
||||
)
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[options.body.output: 'file'] in route POST / is not valid. Only 'data' or 'stream' are valid."`
|
||||
);
|
||||
});
|
||||
|
||||
it('should default `output: "stream" and parse: false` when no body validation is required but not a GET', () => {
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
router.post({ path: '/', validate: {} }, (context, req, res) => res.ok({}));
|
||||
const [route] = router.getRoutes();
|
||||
expect(route.options).toEqual({ body: { output: 'stream', parse: false } });
|
||||
});
|
||||
|
||||
it('should NOT default `output: "stream" and parse: false` when the user has specified body options (he cares about it)', () => {
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
router.post(
|
||||
{ path: '/', options: { body: { maxBytes: 1 } }, validate: {} },
|
||||
(context, req, res) => res.ok({})
|
||||
);
|
||||
const [route] = router.getRoutes();
|
||||
expect(route.options).toEqual({ body: { maxBytes: 1 } });
|
||||
});
|
||||
|
||||
it('should NOT default `output: "stream" and parse: false` when no body validation is required and GET', () => {
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
router.get({ path: '/', validate: {} }, (context, req, res) => res.ok({}));
|
||||
const [route] = router.getRoutes();
|
||||
expect(route.options).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,10 +21,17 @@ import { ObjectType, TypeOf, Type } from '@kbn/config-schema';
|
|||
import { Request, ResponseObject, ResponseToolkit } from 'hapi';
|
||||
import Boom from 'boom';
|
||||
|
||||
import { Stream } from 'stream';
|
||||
import { Logger } from '../../logging';
|
||||
import { KibanaRequest } from './request';
|
||||
import { KibanaResponseFactory, kibanaResponseFactory, IKibanaResponse } from './response';
|
||||
import { RouteConfig, RouteConfigOptions, RouteMethod, RouteSchemas } from './route';
|
||||
import {
|
||||
RouteConfig,
|
||||
RouteConfigOptions,
|
||||
RouteMethod,
|
||||
RouteSchemas,
|
||||
validBodyOutput,
|
||||
} from './route';
|
||||
import { HapiResponseAdapter } from './response_adapter';
|
||||
import { RequestHandlerContext } from '../../../server';
|
||||
import { wrapErrors } from './error_wrapper';
|
||||
|
@ -32,17 +39,22 @@ import { wrapErrors } from './error_wrapper';
|
|||
interface RouterRoute {
|
||||
method: RouteMethod;
|
||||
path: string;
|
||||
options: RouteConfigOptions;
|
||||
options: RouteConfigOptions<RouteMethod>;
|
||||
handler: (req: Request, responseToolkit: ResponseToolkit) => Promise<ResponseObject | Boom<any>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler to declare a route.
|
||||
* Route handler common definition
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type RouteRegistrar = <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(
|
||||
route: RouteConfig<P, Q, B>,
|
||||
handler: RequestHandler<P, Q, B>
|
||||
export type RouteRegistrar<Method extends RouteMethod> = <
|
||||
P extends ObjectType,
|
||||
Q extends ObjectType,
|
||||
B extends ObjectType | Type<Buffer> | Type<Stream>
|
||||
>(
|
||||
route: RouteConfig<P, Q, B, Method>,
|
||||
handler: RequestHandler<P, Q, B, Method>
|
||||
) => void;
|
||||
|
||||
/**
|
||||
|
@ -62,28 +74,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: RouteRegistrar<'get'>;
|
||||
|
||||
/**
|
||||
* 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: RouteRegistrar<'post'>;
|
||||
|
||||
/**
|
||||
* 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: RouteRegistrar<'put'>;
|
||||
|
||||
/**
|
||||
* 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'>;
|
||||
|
||||
/**
|
||||
* 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: RouteRegistrar<'delete'>;
|
||||
|
||||
/**
|
||||
* Wrap a router handler to catch and converts legacy boom errors to proper custom errors.
|
||||
|
@ -94,16 +113,19 @@ export interface IRouter {
|
|||
) => RequestHandler<P, Q, B>;
|
||||
|
||||
/**
|
||||
* Returns all routes registered with the this router.
|
||||
* Returns all routes registered with this router.
|
||||
* @returns List of registered routes.
|
||||
* @internal
|
||||
*/
|
||||
getRoutes: () => RouterRoute[];
|
||||
}
|
||||
|
||||
export type ContextEnhancer<P extends ObjectType, Q extends ObjectType, B extends ObjectType> = (
|
||||
handler: RequestHandler<P, Q, B>
|
||||
) => RequestHandlerEnhanced<P, Q, B>;
|
||||
export type ContextEnhancer<
|
||||
P extends ObjectType,
|
||||
Q extends ObjectType,
|
||||
B extends ObjectType,
|
||||
Method extends RouteMethod
|
||||
> = (handler: RequestHandler<P, Q, B, Method>) => RequestHandlerEnhanced<P, Q, B, Method>;
|
||||
|
||||
function getRouteFullPath(routerPath: string, routePath: string) {
|
||||
// If router's path ends with slash and route's path starts with slash,
|
||||
|
@ -121,8 +143,8 @@ function getRouteFullPath(routerPath: string, routePath: string) {
|
|||
function routeSchemasFromRouteConfig<
|
||||
P extends ObjectType,
|
||||
Q extends ObjectType,
|
||||
B extends ObjectType
|
||||
>(route: RouteConfig<P, Q, B>, routeMethod: RouteMethod) {
|
||||
B extends ObjectType | Type<Buffer> | Type<Stream>
|
||||
>(route: RouteConfig<P, Q, B, typeof routeMethod>, routeMethod: RouteMethod) {
|
||||
// The type doesn't allow `validate` to be undefined, but it can still
|
||||
// happen when it's used from JavaScript.
|
||||
if (route.validate === undefined) {
|
||||
|
@ -144,6 +166,49 @@ function routeSchemasFromRouteConfig<
|
|||
return route.validate ? route.validate : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a valid options object with "sensible" defaults + adding some validation to the options fields
|
||||
*
|
||||
* @param method HTTP verb for these options
|
||||
* @param routeConfig The route config definition
|
||||
*/
|
||||
function validOptions(
|
||||
method: RouteMethod,
|
||||
routeConfig: RouteConfig<
|
||||
ObjectType,
|
||||
ObjectType,
|
||||
ObjectType | Type<Buffer> | Type<Stream>,
|
||||
typeof method
|
||||
>
|
||||
) {
|
||||
const shouldNotHavePayload = ['head', 'get'].includes(method);
|
||||
const { options = {}, validate } = routeConfig;
|
||||
const shouldValidateBody = (validate && !!validate.body) || !!options.body;
|
||||
|
||||
const { output } = options.body || {};
|
||||
if (typeof output === 'string' && !validBodyOutput.includes(output)) {
|
||||
throw new Error(
|
||||
`[options.body.output: '${output}'] in route ${method.toUpperCase()} ${
|
||||
routeConfig.path
|
||||
} is not valid. Only '${validBodyOutput.join("' or '")}' are valid.`
|
||||
);
|
||||
}
|
||||
|
||||
const body = shouldNotHavePayload
|
||||
? undefined
|
||||
: {
|
||||
// If it's not a GET (requires payload) but no body validation is required (or no body options are specified),
|
||||
// We assume the route does not care about the body => use the memory-cheapest approach (stream and no parsing)
|
||||
output: !shouldValidateBody ? ('stream' as const) : undefined,
|
||||
parse: !shouldValidateBody ? false : undefined,
|
||||
|
||||
// User's settings should overwrite any of the "desired" values
|
||||
...options.body,
|
||||
};
|
||||
|
||||
return { ...options, body };
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
@ -153,21 +218,21 @@ export class Router implements IRouter {
|
|||
public post: IRouter['post'];
|
||||
public delete: IRouter['delete'];
|
||||
public put: IRouter['put'];
|
||||
public patch: IRouter['patch'];
|
||||
|
||||
constructor(
|
||||
public readonly routerPath: string,
|
||||
private readonly log: Logger,
|
||||
private readonly enhanceWithContext: ContextEnhancer<any, any, any>
|
||||
private readonly enhanceWithContext: ContextEnhancer<any, any, any, any>
|
||||
) {
|
||||
const buildMethod = (method: RouteMethod) => <
|
||||
const buildMethod = <Method extends RouteMethod>(method: Method) => <
|
||||
P extends ObjectType,
|
||||
Q extends ObjectType,
|
||||
B extends ObjectType
|
||||
B extends ObjectType | Type<Buffer> | Type<Stream>
|
||||
>(
|
||||
route: RouteConfig<P, Q, B>,
|
||||
handler: RequestHandler<P, Q, B>
|
||||
route: RouteConfig<P, Q, B, Method>,
|
||||
handler: RequestHandler<P, Q, B, Method>
|
||||
) => {
|
||||
const { path, options = {} } = route;
|
||||
const routeSchemas = routeSchemasFromRouteConfig(route, method);
|
||||
|
||||
this.routes.push({
|
||||
|
@ -179,8 +244,8 @@ export class Router implements IRouter {
|
|||
handler: this.enhanceWithContext(handler),
|
||||
}),
|
||||
method,
|
||||
path: getRouteFullPath(this.routerPath, path),
|
||||
options,
|
||||
path: getRouteFullPath(this.routerPath, route.path),
|
||||
options: validOptions(method, route),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -188,6 +253,7 @@ export class Router implements IRouter {
|
|||
this.post = buildMethod('post');
|
||||
this.delete = buildMethod('delete');
|
||||
this.put = buildMethod('put');
|
||||
this.patch = buildMethod('patch');
|
||||
}
|
||||
|
||||
public getRoutes() {
|
||||
|
@ -200,7 +266,11 @@ export class Router implements IRouter {
|
|||
return wrapErrors(handler);
|
||||
}
|
||||
|
||||
private async handle<P extends ObjectType, Q extends ObjectType, B extends ObjectType>({
|
||||
private async handle<
|
||||
P extends ObjectType,
|
||||
Q extends ObjectType,
|
||||
B extends ObjectType | Type<Buffer> | Type<Stream>
|
||||
>({
|
||||
routeSchemas,
|
||||
request,
|
||||
responseToolkit,
|
||||
|
@ -208,10 +278,10 @@ export class Router implements IRouter {
|
|||
}: {
|
||||
request: Request;
|
||||
responseToolkit: ResponseToolkit;
|
||||
handler: RequestHandlerEnhanced<P, Q, B>;
|
||||
handler: RequestHandlerEnhanced<P, Q, B, typeof request.method>;
|
||||
routeSchemas?: RouteSchemas<P, Q, B>;
|
||||
}) {
|
||||
let kibanaRequest: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>>;
|
||||
let kibanaRequest: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>, typeof request.method>;
|
||||
const hapiResponseAdapter = new HapiResponseAdapter(responseToolkit);
|
||||
try {
|
||||
kibanaRequest = KibanaRequest.from(request, routeSchemas);
|
||||
|
@ -236,8 +306,9 @@ type WithoutHeadArgument<T> = T extends (first: any, ...rest: infer Params) => i
|
|||
type RequestHandlerEnhanced<
|
||||
P extends ObjectType,
|
||||
Q extends ObjectType,
|
||||
B extends ObjectType
|
||||
> = WithoutHeadArgument<RequestHandler<P, Q, B>>;
|
||||
B extends ObjectType | Type<Buffer> | Type<Stream>,
|
||||
Method extends RouteMethod
|
||||
> = WithoutHeadArgument<RequestHandler<P, Q, B, Method>>;
|
||||
|
||||
/**
|
||||
* A function executed when route path matched requested resource path.
|
||||
|
@ -272,8 +343,13 @@ type RequestHandlerEnhanced<
|
|||
* ```
|
||||
* @public
|
||||
*/
|
||||
export type RequestHandler<P extends ObjectType, Q extends ObjectType, B extends ObjectType> = (
|
||||
export type RequestHandler<
|
||||
P extends ObjectType,
|
||||
Q extends ObjectType,
|
||||
B extends ObjectType | Type<Buffer> | Type<Stream>,
|
||||
Method extends RouteMethod = any
|
||||
> = (
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>>,
|
||||
request: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>, Method>,
|
||||
response: KibanaResponseFactory
|
||||
) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
|
||||
|
|
|
@ -93,6 +93,7 @@ export {
|
|||
IsAuthenticated,
|
||||
KibanaRequest,
|
||||
KibanaRequestRoute,
|
||||
KibanaRequestRouteOptions,
|
||||
IKibanaResponse,
|
||||
LifecycleResponseFactory,
|
||||
KnownHeaders,
|
||||
|
@ -112,9 +113,13 @@ export {
|
|||
KibanaResponseFactory,
|
||||
RouteConfig,
|
||||
IRouter,
|
||||
RouteRegistrar,
|
||||
RouteMethod,
|
||||
RouteConfigOptions,
|
||||
RouteRegistrar,
|
||||
RouteSchemas,
|
||||
RouteConfigOptionsBody,
|
||||
RouteContentType,
|
||||
validBodyOutput,
|
||||
SessionStorage,
|
||||
SessionStorageCookieOptions,
|
||||
SessionCookieValidationResult,
|
||||
|
|
|
@ -449,11 +449,11 @@ export interface AuthToolkit {
|
|||
export class BasePath {
|
||||
// @internal
|
||||
constructor(serverBasePath?: string);
|
||||
get: (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest) => string;
|
||||
get: (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest) => string;
|
||||
prepend: (path: string) => string;
|
||||
remove: (path: string) => string;
|
||||
readonly serverBasePath: string;
|
||||
set: (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest, requestSpecificBasePath: string) => void;
|
||||
set: (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest, requestSpecificBasePath: string) => void;
|
||||
}
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "BootstrapArgs" needs to be exported by the entry point index.d.ts
|
||||
|
@ -714,15 +714,16 @@ export interface IndexSettingsDeprecationInfo {
|
|||
|
||||
// @public
|
||||
export interface IRouter {
|
||||
delete: RouteRegistrar;
|
||||
get: RouteRegistrar;
|
||||
delete: RouteRegistrar<'delete'>;
|
||||
get: RouteRegistrar<'get'>;
|
||||
// Warning: (ae-forgotten-export) The symbol "RouterRoute" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @internal
|
||||
getRoutes: () => RouterRoute[];
|
||||
handleLegacyErrors: <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B>;
|
||||
post: RouteRegistrar;
|
||||
put: RouteRegistrar;
|
||||
patch: RouteRegistrar<'patch'>;
|
||||
post: RouteRegistrar<'post'>;
|
||||
put: RouteRegistrar<'put'>;
|
||||
routerPath: string;
|
||||
}
|
||||
|
||||
|
@ -746,37 +747,38 @@ export interface IUiSettingsClient {
|
|||
}
|
||||
|
||||
// @public
|
||||
export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown> {
|
||||
export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown, Method extends RouteMethod = any> {
|
||||
// @internal (undocumented)
|
||||
protected readonly [requestSymbol]: Request;
|
||||
constructor(request: Request, params: Params, query: Query, body: Body, withoutSecretHeaders: boolean);
|
||||
// (undocumented)
|
||||
readonly body: Body;
|
||||
// Warning: (ae-forgotten-export) The symbol "RouteSchemas" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @internal
|
||||
static from<P extends ObjectType, Q extends ObjectType, B extends ObjectType>(req: Request, routeSchemas?: RouteSchemas<P, Q, B>, withoutSecretHeaders?: boolean): KibanaRequest<P["type"], Q["type"], B["type"]>;
|
||||
static from<P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>>(req: Request, routeSchemas?: RouteSchemas<P, Q, B>, withoutSecretHeaders?: boolean): KibanaRequest<P["type"], Q["type"], B["type"], any>;
|
||||
readonly headers: Headers;
|
||||
// (undocumented)
|
||||
readonly params: Params;
|
||||
// (undocumented)
|
||||
readonly query: Query;
|
||||
readonly route: RecursiveReadonly<KibanaRequestRoute>;
|
||||
readonly route: RecursiveReadonly<KibanaRequestRoute<Method>>;
|
||||
// (undocumented)
|
||||
readonly socket: IKibanaSocket;
|
||||
readonly url: Url;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface KibanaRequestRoute {
|
||||
export interface KibanaRequestRoute<Method extends RouteMethod> {
|
||||
// (undocumented)
|
||||
method: RouteMethod | 'patch' | 'options';
|
||||
method: Method;
|
||||
// (undocumented)
|
||||
options: Required<RouteConfigOptions>;
|
||||
options: KibanaRequestRouteOptions<Method>;
|
||||
// (undocumented)
|
||||
path: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type KibanaRequestRouteOptions<Method extends RouteMethod> = Method extends 'get' | 'options' ? Required<Omit<RouteConfigOptions<Method>, 'body'>> : Required<RouteConfigOptions<Method>>;
|
||||
|
||||
// @public
|
||||
export type KibanaResponseFactory = typeof kibanaResponseFactory;
|
||||
|
||||
|
@ -1043,7 +1045,7 @@ export type RedirectResponseOptions = HttpResponseOptions & {
|
|||
};
|
||||
|
||||
// @public
|
||||
export type RequestHandler<P extends ObjectType, Q extends ObjectType, B extends ObjectType> = (context: RequestHandlerContext, request: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>>, response: KibanaResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
|
||||
export type RequestHandler<P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest<TypeOf<P>, TypeOf<Q>, TypeOf<B>, Method>, response: KibanaResponseFactory) => IKibanaResponse<any> | Promise<IKibanaResponse<any>>;
|
||||
|
||||
// @public
|
||||
export interface RequestHandlerContext {
|
||||
|
@ -1085,23 +1087,45 @@ export type ResponseHeaders = {
|
|||
};
|
||||
|
||||
// @public
|
||||
export interface RouteConfig<P extends ObjectType, Q extends ObjectType, B extends ObjectType> {
|
||||
options?: RouteConfigOptions;
|
||||
export interface RouteConfig<P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>, Method extends RouteMethod> {
|
||||
options?: RouteConfigOptions<Method>;
|
||||
path: string;
|
||||
validate: RouteSchemas<P, Q, B> | false;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface RouteConfigOptions {
|
||||
export interface RouteConfigOptions<Method extends RouteMethod> {
|
||||
authRequired?: boolean;
|
||||
body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody;
|
||||
tags?: readonly string[];
|
||||
}
|
||||
|
||||
// @public
|
||||
export type RouteMethod = 'get' | 'post' | 'put' | 'delete';
|
||||
export interface RouteConfigOptionsBody {
|
||||
accepts?: RouteContentType | RouteContentType[] | string | string[];
|
||||
maxBytes?: number;
|
||||
output?: typeof validBodyOutput[number];
|
||||
parse?: boolean | 'gunzip';
|
||||
}
|
||||
|
||||
// @public
|
||||
export type RouteRegistrar = <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(route: RouteConfig<P, Q, B>, handler: RequestHandler<P, Q, B>) => void;
|
||||
export type RouteContentType = 'application/json' | 'application/*+json' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/*';
|
||||
|
||||
// @public
|
||||
export type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options';
|
||||
|
||||
// @public
|
||||
export type RouteRegistrar<Method extends RouteMethod> = <P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>>(route: RouteConfig<P, Q, B, Method>, handler: RequestHandler<P, Q, B, Method>) => void;
|
||||
|
||||
// @public
|
||||
export interface RouteSchemas<P extends ObjectType, Q extends ObjectType, B extends ObjectType | Type<Buffer> | Type<Stream>> {
|
||||
// (undocumented)
|
||||
body?: B;
|
||||
// (undocumented)
|
||||
params?: P;
|
||||
// (undocumented)
|
||||
query?: Q;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SavedObject<T extends SavedObjectAttributes = any> {
|
||||
|
@ -1633,6 +1657,9 @@ export interface UserProvidedValues<T extends SavedObjectAttribute = any> {
|
|||
userValue?: T;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const validBodyOutput: readonly ["data", "stream"];
|
||||
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
|
|
|
@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import * as t from 'io-ts';
|
||||
import { PathReporter } from 'io-ts/lib/PathReporter';
|
||||
import { isLeft } from 'fp-ts/lib/Either';
|
||||
import { KibanaResponseFactory } from 'src/core/server';
|
||||
import { KibanaResponseFactory, RouteRegistrar } from 'src/core/server';
|
||||
import { APMConfig } from '../../../../../../plugins/apm/server';
|
||||
import {
|
||||
ServerAPI,
|
||||
|
@ -65,7 +65,7 @@ export function createApi() {
|
|||
body: bodyRt && 'props' in bodyRt ? t.exact(bodyRt) : fallbackBodyRt
|
||||
};
|
||||
|
||||
router[routerMethod](
|
||||
(router[routerMethod] as RouteRegistrar<typeof routerMethod>)(
|
||||
{
|
||||
path,
|
||||
options,
|
||||
|
|
|
@ -41,8 +41,8 @@ describe('SAML authentication routes', () => {
|
|||
});
|
||||
|
||||
describe('Assertion consumer service endpoint', () => {
|
||||
let routeHandler: RequestHandler<any, any, any>;
|
||||
let routeConfig: RouteConfig<any, any, any>;
|
||||
let routeHandler: RequestHandler<any, any, any, any>;
|
||||
let routeConfig: RouteConfig<any, any, any, any>;
|
||||
beforeEach(() => {
|
||||
const [acsRouteConfig, acsRouteHandler] = router.post.mock.calls.find(
|
||||
([{ path }]) => path === '/api/security/saml/callback'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue