UNPKG

grpc-boom

Version:

A gRPC implementation of the awesome Boom library to help create gRPC-friendly error objects.

447 lines 18.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Status = void 0; const grpc_js_1 = require("@grpc/grpc-js"); /** * Enum of status codes that gRPC can return */ /* eslint-disable @typescript-eslint/naming-convention */ var Status; (function (Status) { /** * Not an error; returned on success */ Status[Status["OK"] = 0] = "OK"; /** * The operation was cancelled (typically by the caller). */ Status[Status["CANCELLED"] = 1] = "CANCELLED"; /** * Unknown error. An example of where this error may be returned is * if a status value received from another address space belongs to * an error-space that is not known in this address space. Also * errors raised by APIs that do not return enough error information * may be converted to this error. */ Status[Status["UNKNOWN"] = 2] = "UNKNOWN"; /** * Client specified an invalid argument. Note that this differs * from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments * that are problematic regardless of the state of the system * (e.g., a malformed file name). */ Status[Status["INVALID_ARGUMENT"] = 3] = "INVALID_ARGUMENT"; /** * Deadline expired before operation could complete. For operations * that change the state of the system, this error may be returned * even if the operation has completed successfully. For example, a * successful response from a server could have been delayed long * enough for the deadline to expire. */ Status[Status["DEADLINE_EXCEEDED"] = 4] = "DEADLINE_EXCEEDED"; /** * Some requested entity (e.g., file or directory) was not found. */ Status[Status["NOT_FOUND"] = 5] = "NOT_FOUND"; /** * Some entity that we attempted to create (e.g., file or directory) * already exists. */ Status[Status["ALREADY_EXISTS"] = 6] = "ALREADY_EXISTS"; /** * The caller does not have permission to execute the specified * operation. PERMISSION_DENIED must not be used for rejections * caused by exhausting some resource (use RESOURCE_EXHAUSTED * instead for those errors). PERMISSION_DENIED must not be * used if the caller can not be identified (use UNAUTHENTICATED * instead for those errors). */ Status[Status["PERMISSION_DENIED"] = 7] = "PERMISSION_DENIED"; /** * Some resource has been exhausted, perhaps a per-user quota, or * perhaps the entire file system is out of space. */ Status[Status["RESOURCE_EXHAUSTED"] = 8] = "RESOURCE_EXHAUSTED"; /** * Operation was rejected because the system is not in a state * required for the operation's execution. For example, directory * to be deleted may be non-empty, an rmdir operation is applied to * a non-directory, etc. * * A litmus test that may help a service implementor in deciding * between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE: * * - Use UNAVAILABLE if the client can retry just the failing call. * - Use ABORTED if the client should retry at a higher-level * (e.g., restarting a read-modify-write sequence). * - Use FAILED_PRECONDITION if the client should not retry until * the system state has been explicitly fixed. E.g., if an "rmdir" * fails because the directory is non-empty, FAILED_PRECONDITION * should be returned since the client should not retry unless * they have first fixed up the directory by deleting files from it. * - Use FAILED_PRECONDITION if the client performs conditional * REST Get/Update/Delete on a resource and the resource on the * server does not match the condition. E.g., conflicting * read-modify-write on the same resource. */ Status[Status["FAILED_PRECONDITION"] = 9] = "FAILED_PRECONDITION"; /** * The operation was aborted, typically due to a concurrency issue * like sequencer check failures, transaction aborts, etc. * * See litmus test above for deciding between FAILED_PRECONDITION, * ABORTED, and UNAVAILABLE. */ Status[Status["ABORTED"] = 10] = "ABORTED"; /** * Operation was attempted past the valid range. E.g., seeking or * reading past end of file. * * Unlike INVALID_ARGUMENT, this error indicates a problem that may * be fixed if the system state changes. For example, a 32-bit file * system will generate INVALID_ARGUMENT if asked to read at an * offset that is not in the range [0,2^32-1], but it will generate * OUT_OF_RANGE if asked to read from an offset past the current * file size. * * There is a fair bit of overlap between FAILED_PRECONDITION and * OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific * error) when it applies so that callers who are iterating through * a space can easily look for an OUT_OF_RANGE error to detect when * they are done. */ Status[Status["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE"; /** * Operation is not implemented or not supported/enabled in this service. */ Status[Status["UNIMPLEMENTED"] = 12] = "UNIMPLEMENTED"; /** * Internal errors. Means some invariants expected by underlying * system has been broken. If you see one of these errors, * something is very broken. */ Status[Status["INTERNAL"] = 13] = "INTERNAL"; /** * The service is currently unavailable. This is a most likely a * transient condition and may be corrected by retrying with * a back off. * * See litmus test above for deciding between FAILED_PRECONDITION, * ABORTED, and UNAVAILABLE. */ Status[Status["UNAVAILABLE"] = 14] = "UNAVAILABLE"; /** * Unrecoverable data loss or corruption. */ Status[Status["DATA_LOSS"] = 15] = "DATA_LOSS"; /** * The request does not have valid authentication credentials for the * operation. */ Status[Status["UNAUTHENTICATED"] = 16] = "UNAUTHENTICATED"; })(Status = exports.Status || (exports.Status = {})); class GrpcBoom { isBoom; metadata; /** code - the gRPC status code. */ code; /** error - the gRPC status message. */ error; /** message - the error message. */ message; /** details - the error details. */ details; /** name - the error name. */ name; static [Symbol.hasInstance](instance) { return instance && instance.isBoom; } static fallbackStatus = Status.UNKNOWN; static fallbackMessage = 'An unknown error occurred'; constructor(message, options) { // Parse the options const code = options && options.code !== undefined ? options.code : GrpcBoom.fallbackStatus; const ctor = options && options.ctor !== undefined ? options.ctor : GrpcBoom; const error = options && options.error !== undefined ? options.error : undefined; // Set the defaults this.name = 'Error'; this.isBoom = true; this.code = code; this.error = error; this.message = message; this.metadata = options && options.metadata !== undefined ? options.metadata : new grpc_js_1.Metadata(); this.details = options && options.details !== undefined ? options.details : ''; this.reformat(); // Filter the stack to our external API Error.captureStackTrace(this, ctor); } static boomify(instance, options) { const castInstance = instance; let code = castInstance.code ?? GrpcBoom.fallbackStatus; if (options && options.code) { code = options.code; } let message = instance && instance.message ? instance.message : GrpcBoom.fallbackMessage; if (options && options.message && !(options.message instanceof Error)) { message = options.message; } let error; if (options && options.error) { error = options.error; } const newOptions = { code, error }; newOptions.details = castInstance.details ?? ''; if (options && options.details !== undefined) { newOptions.details = options.details; } if (options && options.metadata) { newOptions.metadata = options.metadata; } if (instance && instance instanceof GrpcBoom && instance.isBoom) { instance.message = message; instance.code = code; instance.error = error; return instance; } return new GrpcBoom(message, newOptions); } /* eslint-disable @typescript-eslint/unbound-method */ /** * Not an error; returned on success */ static ok(message, metadata, details) { return this.create(message, Status.OK, metadata, this.ok, details); } /** * The operation was cancelled (typically by the caller). */ static cancelled(message, metadata, details) { return this.create(message, Status.CANCELLED, metadata, this.cancelled, details); } /** * Unknown error. An example of where this error may be returned is * if a status value received from another address space belongs to * an error-space that is not known in this address space. Also * errors raised by APIs that do not return enough error information * may be converted to this error. */ static unknown(message, metadata, details) { return this.create(message, Status.UNKNOWN, metadata, this.unknown, details); } /** * Client specified an invalid argument. Note that this differs * from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments * that are problematic regardless of the state of the system * (e.g., a malformed file name). */ static invalidArgument(message, metadata, details) { return this.create(message, Status.INVALID_ARGUMENT, metadata, this.invalidArgument, details); } /** * Deadline expired before operation could complete. For operations * that change the state of the system, this error may be returned * even if the operation has completed successfully. For example, a * successful response from a server could have been delayed long * enough for the deadline to expire. */ static deadlineExceeded(message, metadata, details) { return this.create(message, Status.DEADLINE_EXCEEDED, metadata, this.deadlineExceeded, details); } /** * Some requested entity (e.g., file or directory) was not found. */ static notFound(message, metadata, details) { return this.create(message, Status.NOT_FOUND, metadata, this.notFound, details); } /** * Some entity that we attempted to create (e.g., file or directory) * already exists. */ static alreadyExists(message, metadata, details) { return this.create(message, Status.ALREADY_EXISTS, metadata, this.alreadyExists, details); } /** * The caller does not have permission to execute the specified * operation. PERMISSION_DENIED must not be used for rejections * caused by exhausting some resource (use RESOURCE_EXHAUSTED * instead for those errors). PERMISSION_DENIED must not be * used if the caller can not be identified (use UNAUTHENTICATED * instead for those errors). */ static permissionDenied(message, metadata, details) { return this.create(message, Status.PERMISSION_DENIED, metadata, this.permissionDenied, details); } /** * Some resource has been exhausted, perhaps a per-user quota, or * perhaps the entire file system is out of space. */ static resourceExhausted(message, metadata, details) { return this.create(message, Status.RESOURCE_EXHAUSTED, metadata, this.resourceExhausted, details); } /** * Operation was rejected because the system is not in a state * required for the operation's execution. For example, directory * to be deleted may be non-empty, an rmdir operation is applied to * a non-directory, etc. * * A litmus test that may help a service implementor in deciding * between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE: * * - Use UNAVAILABLE if the client can retry just the failing call. * - Use ABORTED if the client should retry at a higher-level * (e.g., restarting a read-modify-write sequence). * - Use FAILED_PRECONDITION if the client should not retry until * the system state has been explicitly fixed. E.g., if an "rmdir" * fails because the directory is non-empty, FAILED_PRECONDITION * should be returned since the client should not retry unless * they have first fixed up the directory by deleting files from it. * - Use FAILED_PRECONDITION if the client performs conditional * REST Get/Update/Delete on a resource and the resource on the * server does not match the condition. E.g., conflicting * read-modify-write on the same resource. */ static failedPrecondition(message, metadata, details) { return this.create(message, Status.FAILED_PRECONDITION, metadata, this.failedPrecondition, details); } /** * The operation was aborted, typically due to a concurrency issue * like sequencer check failures, transaction aborts, etc. * * See litmus test above for deciding between FAILED_PRECONDITION, * ABORTED, and UNAVAILABLE. */ static aborted(message, metadata, details) { return this.create(message, Status.ABORTED, metadata, this.aborted, details); } /** * Operation was attempted past the valid range. E.g., seeking or * reading past end of file. * * Unlike INVALID_ARGUMENT, this error indicates a problem that may * be fixed if the system state changes. For example, a 32-bit file * system will generate INVALID_ARGUMENT if asked to read at an * offset that is not in the range [0,2^32-1], but it will generate * OUT_OF_RANGE if asked to read from an offset past the current * file size. * * There is a fair bit of overlap between FAILED_PRECONDITION and * OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific * error) when it applies so that callers who are iterating through * a space can easily look for an OUT_OF_RANGE error to detect when * they are done. */ static outOfRange(message, metadata, details) { return this.create(message, Status.OUT_OF_RANGE, metadata, this.outOfRange, details); } /** * Operation is not implemented or not supported/enabled in this service. */ static unimplemented(message, metadata, details) { return this.create(message, Status.UNIMPLEMENTED, metadata, this.unimplemented, details); } /** * Internal errors. Means some invariants expected by underlying * system has been broken. If you see one of these errors, * something is very broken. */ static internal(message, metadata, details) { return this.create(message, Status.INTERNAL, metadata, this.internal, details); } /** * The service is currently unavailable. This is a most likely a * transient condition and may be corrected by retrying with * a back off. * * See litmus test above for deciding between FAILED_PRECONDITION, * ABORTED, and UNAVAILABLE. */ static unavailable(message, metadata, details) { return this.create(message, Status.UNAVAILABLE, metadata, this.unavailable, details); } /** * Unrecoverable data loss or corruption. */ static dataLoss(message, metadata, details) { return this.create(message, Status.DATA_LOSS, metadata, this.dataLoss, details); } /** * The request does not have valid authentication credentials for the * operation. */ static unauthenticated(message, metadata, details) { return this.create(message, Status.UNAUTHENTICATED, metadata, this.unauthenticated, details); } /* eslint-enable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/naming-convention */ static httpStatusCodeToGrpcErrorCodeMapper = { 400: Status.INVALID_ARGUMENT.valueOf(), 401: Status.UNAUTHENTICATED.valueOf(), 403: Status.PERMISSION_DENIED.valueOf(), 404: Status.NOT_FOUND.valueOf(), 408: Status.DEADLINE_EXCEEDED.valueOf(), 409: Status.ALREADY_EXISTS.valueOf(), 422: Status.ABORTED.valueOf(), 500: Status.INTERNAL.valueOf(), 501: Status.UNIMPLEMENTED.valueOf(), 503: Status.UNAVAILABLE.valueOf(), }; /* eslint-enable @typescript-eslint/naming-convention */ /** * This method attempts to convert an http exception to a grpc * boom error, it will fail-over to an unknown grpc error if the * error code cannot be inferred. This method supports *Boom* errors. */ static fromHttpException(httpException, metadata) { const { code, data, details, message, output, status, statusCode } = httpException; const httpStatusCode = typeof code === 'number' ? code : status ?? statusCode ?? output?.statusCode ?? output?.payload?.statusCode; const grpcErrorCode = httpStatusCode ? GrpcBoom.httpStatusCodeToGrpcErrorCodeMapper[httpStatusCode] : GrpcBoom.fallbackStatus; const dataDetails = data ? JSON.stringify(data, null, 2) : undefined; return GrpcBoom.boomify({ code: grpcErrorCode, details: details ?? (dataDetails || ''), message: message ?? (output?.payload?.message || ''), }, { metadata }); } static create(message, code, metadata, ctor, details) { const grpcBoom = new GrpcBoom(message, { code, details, metadata, ctor, }); return grpcBoom.initialize(grpcBoom, code, message, metadata, details); } initialize(instance, code, message, metadata, details) { this.isBoom = true; if (details) { this.details = details; } if (metadata) { this.metadata = metadata; } this.code = code; if (message === undefined && instance.message === undefined) { this.reformat(); message = this.error; } this.reformat(); return this; } reformat() { if (this.code === undefined) { this.code = GrpcBoom.fallbackStatus; } if (this.error === undefined) { this.error = Status[this.code]; } if (this.message === undefined) { this.message = GrpcBoom.fallbackMessage; } } } exports.default = GrpcBoom; //# sourceMappingURL=index.js.map