From 4dd0205c113c65f6a19747aabcea4cfea2c2bd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Mon, 24 Aug 2020 10:16:54 -0400 Subject: [PATCH] Add support for decorating and handling 429 errors with the saved objects client (#75664) * Add support for decorating 429 errors in the saved objects client * Update the docs Co-authored-by: Elastic Machine --- ...errorhelpers.createtoomanyrequestserror.md | 23 +++++++++ ...rorhelpers.decoratetoomanyrequestserror.md | 23 +++++++++ ...ectserrorhelpers.istoomanyrequestserror.md | 22 +++++++++ ...in-core-server.savedobjectserrorhelpers.md | 3 ++ .../service/lib/decorate_es_error.test.ts | 9 ++++ .../service/lib/decorate_es_error.ts | 5 ++ .../saved_objects/service/lib/errors.test.ts | 47 +++++++++++++++++++ .../saved_objects/service/lib/errors.ts | 14 ++++++ src/core/server/server.api.md | 6 +++ 9 files changed, 152 insertions(+) create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.createtoomanyrequestserror.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.decoratetoomanyrequestserror.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.istoomanyrequestserror.md diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.createtoomanyrequestserror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.createtoomanyrequestserror.md new file mode 100644 index 0000000000000..6d93dc97a107e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.createtoomanyrequestserror.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) > [createTooManyRequestsError](./kibana-plugin-core-server.savedobjectserrorhelpers.createtoomanyrequestserror.md) + +## SavedObjectsErrorHelpers.createTooManyRequestsError() method + +Signature: + +```typescript +static createTooManyRequestsError(type: string, id: string): DecoratedError; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | + +Returns: + +`DecoratedError` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.decoratetoomanyrequestserror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.decoratetoomanyrequestserror.md new file mode 100644 index 0000000000000..46c94e1756edd --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.decoratetoomanyrequestserror.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) > [decorateTooManyRequestsError](./kibana-plugin-core-server.savedobjectserrorhelpers.decoratetoomanyrequestserror.md) + +## SavedObjectsErrorHelpers.decorateTooManyRequestsError() method + +Signature: + +```typescript +static decorateTooManyRequestsError(error: Error, reason?: string): DecoratedError; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| error | Error | | +| reason | string | | + +Returns: + +`DecoratedError` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.istoomanyrequestserror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.istoomanyrequestserror.md new file mode 100644 index 0000000000000..4422966ee3e50 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.istoomanyrequestserror.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) > [isTooManyRequestsError](./kibana-plugin-core-server.savedobjectserrorhelpers.istoomanyrequestserror.md) + +## SavedObjectsErrorHelpers.isTooManyRequestsError() method + +Signature: + +```typescript +static isTooManyRequestsError(error: Error | DecoratedError): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| error | Error | DecoratedError | | + +Returns: + +`boolean` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md index 7874be311d52c..a2eff4dd99ea5 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md @@ -19,6 +19,7 @@ export declare class SavedObjectsErrorHelpers | [createConflictError(type, id)](./kibana-plugin-core-server.savedobjectserrorhelpers.createconflicterror.md) | static | | | [createGenericNotFoundError(type, id)](./kibana-plugin-core-server.savedobjectserrorhelpers.creategenericnotfounderror.md) | static | | | [createInvalidVersionError(versionInput)](./kibana-plugin-core-server.savedobjectserrorhelpers.createinvalidversionerror.md) | static | | +| [createTooManyRequestsError(type, id)](./kibana-plugin-core-server.savedobjectserrorhelpers.createtoomanyrequestserror.md) | static | | | [createUnsupportedTypeError(type)](./kibana-plugin-core-server.savedobjectserrorhelpers.createunsupportedtypeerror.md) | static | | | [decorateBadRequestError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decoratebadrequesterror.md) | static | | | [decorateConflictError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decorateconflicterror.md) | static | | @@ -28,6 +29,7 @@ export declare class SavedObjectsErrorHelpers | [decorateGeneralError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decorategeneralerror.md) | static | | | [decorateNotAuthorizedError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decoratenotauthorizederror.md) | static | | | [decorateRequestEntityTooLargeError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decoraterequestentitytoolargeerror.md) | static | | +| [decorateTooManyRequestsError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decoratetoomanyrequestserror.md) | static | | | [isBadRequestError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isbadrequesterror.md) | static | | | [isConflictError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isconflicterror.md) | static | | | [isEsCannotExecuteScriptError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isescannotexecutescripterror.md) | static | | @@ -38,4 +40,5 @@ export declare class SavedObjectsErrorHelpers | [isNotFoundError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isnotfounderror.md) | static | | | [isRequestEntityTooLargeError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isrequestentitytoolargeerror.md) | static | | | [isSavedObjectsClientError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.issavedobjectsclienterror.md) | static | | +| [isTooManyRequestsError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.istoomanyrequestserror.md) | static | | diff --git a/src/core/server/saved_objects/service/lib/decorate_es_error.test.ts b/src/core/server/saved_objects/service/lib/decorate_es_error.test.ts index 623610eebd8d7..3358de1c10311 100644 --- a/src/core/server/saved_objects/service/lib/decorate_es_error.test.ts +++ b/src/core/server/saved_objects/service/lib/decorate_es_error.test.ts @@ -73,6 +73,15 @@ describe('savedObjectsClient/decorateEsError', () => { expect(SavedObjectsErrorHelpers.isConflictError(error)).toBe(true); }); + it('makes TooManyRequests a SavedObjectsClient/tooManyRequests error', () => { + const error = new esErrors.ResponseError( + elasticsearchClientMock.createApiResponse({ statusCode: 429 }) + ); + expect(SavedObjectsErrorHelpers.isTooManyRequestsError(error)).toBe(false); + expect(decorateEsError(error)).toBe(error); + expect(SavedObjectsErrorHelpers.isTooManyRequestsError(error)).toBe(true); + }); + it('makes NotAuthorized a SavedObjectsClient/NotAuthorized error', () => { const error = new esErrors.ResponseError( elasticsearchClientMock.createApiResponse({ statusCode: 401 }) diff --git a/src/core/server/saved_objects/service/lib/decorate_es_error.ts b/src/core/server/saved_objects/service/lib/decorate_es_error.ts index cf8a16cdaae6f..592b268d8219b 100644 --- a/src/core/server/saved_objects/service/lib/decorate_es_error.ts +++ b/src/core/server/saved_objects/service/lib/decorate_es_error.ts @@ -28,6 +28,7 @@ const responseErrors = { isRequestEntityTooLarge: (statusCode: number) => statusCode === 413, isNotFound: (statusCode: number) => statusCode === 404, isBadRequest: (statusCode: number) => statusCode === 400, + isTooManyRequests: (statusCode: number) => statusCode === 429, }; const { ConnectionError, NoLivingConnectionsError, TimeoutError } = esErrors; const SCRIPT_CONTEXT_DISABLED_REGEX = /(?:cannot execute scripts using \[)([a-z]*)(?:\] context)/; @@ -76,6 +77,10 @@ export function decorateEsError(error: EsErrors) { return SavedObjectsErrorHelpers.createGenericNotFoundError(); } + if (responseErrors.isTooManyRequests(error.statusCode)) { + return SavedObjectsErrorHelpers.decorateTooManyRequestsError(error, reason); + } + if (responseErrors.isBadRequest(error.statusCode)) { if ( SCRIPT_CONTEXT_DISABLED_REGEX.test(reason || '') || diff --git a/src/core/server/saved_objects/service/lib/errors.test.ts b/src/core/server/saved_objects/service/lib/errors.test.ts index 324d19e279212..931d9f725e412 100644 --- a/src/core/server/saved_objects/service/lib/errors.test.ts +++ b/src/core/server/saved_objects/service/lib/errors.test.ts @@ -274,6 +274,53 @@ describe('savedObjectsClient/errorTypes', () => { }); }); + describe('TooManyRequests error', () => { + describe('decorateTooManyRequestsError', () => { + it('returns original object', () => { + const error = new Error(); + expect(SavedObjectsErrorHelpers.decorateTooManyRequestsError(error)).toBe(error); + }); + + it('makes the error identifiable as a TooManyRequests error', () => { + const error = new Error(); + expect(SavedObjectsErrorHelpers.isTooManyRequestsError(error)).toBe(false); + SavedObjectsErrorHelpers.decorateTooManyRequestsError(error); + expect(SavedObjectsErrorHelpers.isTooManyRequestsError(error)).toBe(true); + }); + + it('adds boom properties', () => { + const error = SavedObjectsErrorHelpers.decorateTooManyRequestsError(new Error()); + expect(error).toHaveProperty('isBoom', true); + }); + + describe('error.output', () => { + it('defaults to message of error', () => { + const error = SavedObjectsErrorHelpers.decorateTooManyRequestsError(new Error('foobar')); + expect(error.output.payload).toHaveProperty('message', 'foobar'); + }); + + it('prefixes message with passed reason', () => { + const error = SavedObjectsErrorHelpers.decorateTooManyRequestsError( + new Error('foobar'), + 'biz' + ); + expect(error.output.payload).toHaveProperty('message', 'biz: foobar'); + }); + + it('sets statusCode to 429', () => { + const error = SavedObjectsErrorHelpers.decorateTooManyRequestsError(new Error('foo')); + expect(error.output).toHaveProperty('statusCode', 429); + }); + + it('preserves boom properties of input', () => { + const error = Boom.tooManyRequests(); + SavedObjectsErrorHelpers.decorateTooManyRequestsError(error); + expect(error.output).toHaveProperty('statusCode', 429); + }); + }); + }); + }); + describe('EsCannotExecuteScript error', () => { describe('decorateEsCannotExecuteScriptError', () => { it('returns original object', () => { diff --git a/src/core/server/saved_objects/service/lib/errors.ts b/src/core/server/saved_objects/service/lib/errors.ts index 9614d692741e0..6fd5bc9de0ec5 100644 --- a/src/core/server/saved_objects/service/lib/errors.ts +++ b/src/core/server/saved_objects/service/lib/errors.ts @@ -33,6 +33,8 @@ const CODE_REQUEST_ENTITY_TOO_LARGE = 'SavedObjectsClient/requestEntityTooLarge' const CODE_NOT_FOUND = 'SavedObjectsClient/notFound'; // 409 - Conflict const CODE_CONFLICT = 'SavedObjectsClient/conflict'; +// 429 - Too Many Requests +const CODE_TOO_MANY_REQUESTS = 'SavedObjectsClient/tooManyRequests'; // 400 - Es Cannot Execute Script const CODE_ES_CANNOT_EXECUTE_SCRIPT = 'SavedObjectsClient/esCannotExecuteScript'; // 503 - Es Unavailable @@ -162,6 +164,18 @@ export class SavedObjectsErrorHelpers { return isSavedObjectsClientError(error) && error[code] === CODE_CONFLICT; } + public static decorateTooManyRequestsError(error: Error, reason?: string) { + return decorate(error, CODE_TOO_MANY_REQUESTS, 429, reason); + } + + public static createTooManyRequestsError(type: string, id: string) { + return SavedObjectsErrorHelpers.decorateTooManyRequestsError(Boom.tooManyRequests()); + } + + public static isTooManyRequestsError(error: Error | DecoratedError) { + return isSavedObjectsClientError(error) && error[code] === CODE_TOO_MANY_REQUESTS; + } + public static decorateEsCannotExecuteScriptError(error: Error, reason?: string) { return decorate(error, CODE_ES_CANNOT_EXECUTE_SCRIPT, 400, reason); } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 772be68f507d5..afc71d39d4a62 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2220,6 +2220,8 @@ export class SavedObjectsErrorHelpers { // (undocumented) static createInvalidVersionError(versionInput?: string): DecoratedError; // (undocumented) + static createTooManyRequestsError(type: string, id: string): DecoratedError; + // (undocumented) static createUnsupportedTypeError(type: string): DecoratedError; // (undocumented) static decorateBadRequestError(error: Error, reason?: string): DecoratedError; @@ -2238,6 +2240,8 @@ export class SavedObjectsErrorHelpers { // (undocumented) static decorateRequestEntityTooLargeError(error: Error, reason?: string): DecoratedError; // (undocumented) + static decorateTooManyRequestsError(error: Error, reason?: string): DecoratedError; + // (undocumented) static isBadRequestError(error: Error | DecoratedError): boolean; // (undocumented) static isConflictError(error: Error | DecoratedError): boolean; @@ -2259,6 +2263,8 @@ export class SavedObjectsErrorHelpers { // // (undocumented) static isSavedObjectsClientError(error: any): error is DecoratedError; + // (undocumented) + static isTooManyRequestsError(error: Error | DecoratedError): boolean; } // @public