UNPKG

@instamenta/grpc-errors

Version:

grpc status codes builder for javascript and typescript grpc server responses

351 lines (317 loc) 11.3 kB
import {ServerErrorResponse, Metadata, ServerWritableStream, sendUnaryData, MetadataValue} from '@grpc/grpc-js'; import {ZodError, ZodIssue} from 'zod'; /** * Defines interface with default set of keys for gRPC error * types to be provided for generic * or extended in case new errors are added. */ export interface IGrpcErrorKeys { INVALID_ARGUMENT: 'INVALID_ARGUMENT'; DEADLINE_EXCEEDED: 'DEADLINE_EXCEEDED'; NOT_FOUND: 'NOT_FOUND'; ALREADY_EXISTS: 'ALREADY_EXISTS'; PERMISSION_DENIED: 'PERMISSION_DENIED'; INTERNAL: 'INTERNAL'; UNAVAILABLE: 'UNAVAILABLE'; DATA_LOSS: 'DATA_LOSS'; UNAUTHENTICATED: 'UNAUTHENTICATED'; FAILED_PRECONDITION: 'FAILED_PRECONDITION'; VALIDATION: 'VALIDATION'; UNAUTHORIZED: 'UNAUTHORIZED'; RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND'; } //! GRPC DEFAULT ERRORS const invalidArgumentError: ServerErrorResponse = { name: 'InvalidArgument', code: 3, //? GRPC error code for "INVALID ARGUMENT" details: '', metadata: new Metadata(), message: 'Invalid argument provided.', }; const deadlineExceededError: ServerErrorResponse = { name: 'DeadlineExceeded', code: 4, //? GRPC error code for "DEADLINE EXCEEDED" details: '', metadata: new Metadata(), message: 'Deadline for the operation exceeded.', }; const notFoundError: ServerErrorResponse = { name: 'NotFound', code: 5, //? GRPC error code for "NOT FOUND" details: '', metadata: new Metadata(), message: 'Resource not found.', }; const alreadyExistsError: ServerErrorResponse = { name: 'AlreadyExists', code: 6, //? GRPC error code for "ALREADY EXISTS" details: '', metadata: new Metadata(), message: 'Resource already exists.', }; const permissionDeniedError: ServerErrorResponse = { name: 'PermissionDenied', code: 7, //? GRPC error code for "PERMISSION DENIED" details: '', metadata: new Metadata(), message: 'Permission denied for the operation.', }; const internalError: ServerErrorResponse = { name: 'Internal', code: 13, //? GRPC error code for "INTERNAL" details: 'Server ran into unexpected internal error.', metadata: new Metadata(), message: 'Internal server error.', }; const unavailableError: ServerErrorResponse = { name: 'Unavailable', code: 14, //? GRPC error code for "UNAVAILABLE" details: '', metadata: new Metadata(), message: 'Service unavailable.', }; const dataLossError: ServerErrorResponse = { name: 'DataLoss', code: 15, //? GRPC error code for "DATA LOSS" details: '', metadata: new Metadata(), message: 'Data loss occurred.', }; const unauthenticatedError: ServerErrorResponse = { name: 'Unauthenticated', code: 16, //? GRPC error code for "UNAUTHENTICATED" details: '', metadata: new Metadata(), message: 'Request not authenticated.', }; const failedPreconditionError: ServerErrorResponse = { name: 'FailedPrecondition', code: 9, //? GRPC error code for "FAILED PRECONDITION" details: '', metadata: new Metadata(), message: 'Operation failed precondition check.', }; const validationError: ServerErrorResponse = { name: 'GrpcValidationError', code: 3, // GRPC error code for "INVALID ARGUMENT" details: '', metadata: new Metadata(), message: 'gRPC validation error occurred.', }; const unauthorizedError: ServerErrorResponse = { name: 'GrpcUnauthorized', code: 16, // GRPC error code for "UNAUTHENTICATED" details: '', metadata: new Metadata(), message: 'gRPC unauthorized access.', }; const resourceNotFoundError: ServerErrorResponse = { name: 'GrpcResourceNotFound', code: 5, // GRPC error code for "NOT FOUND" details: '', metadata: new Metadata(), message: 'gRPC resource not found.', }; /** Tired of boring old meta whose methods cant be chained? Try this! Simple yet wonderful */ export class Meta { #metadata: Metadata = new Metadata(); public set(key: string, value: MetadataValue): this { this.#metadata.set(key, value); return this; } public get(): Metadata { return this.#metadata; } static build(): Meta { return new Meta(); } } /** * * Class that provides utility functions for managing gRPC errors. Logging meaningful errors to the console. * * ! With unmatched type safety and extendability! * * @template T - string literal type representing gRPC error keys. * @class GrpcErrors */ export default class GrpcErrors<T extends IGrpcErrorKeys> { public K: T = { INVALID_ARGUMENT: 'INVALID_ARGUMENT', DEADLINE_EXCEEDED: 'DEADLINE_EXCEEDED', NOT_FOUND: 'NOT_FOUND', ALREADY_EXISTS: 'ALREADY_EXISTS', PERMISSION_DENIED: 'PERMISSION_DENIED', INTERNAL: 'INTERNAL', UNAVAILABLE: 'UNAVAILABLE', DATA_LOSS: 'DATA_LOSS', UNAUTHENTICATED: 'UNAUTHENTICATED', FAILED_PRECONDITION: 'FAILED_PRECONDITION', VALIDATION: 'VALIDATION', UNAUTHORIZED: 'UNAUTHORIZED', RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND', } as T; /** * A record of gRPC errors, where keys correspond to error codes * and values contain error details. */ ERRORS = <Record<keyof T, ServerErrorResponse>>{ INVALID_ARGUMENT: invalidArgumentError, DEADLINE_EXCEEDED: deadlineExceededError, NOT_FOUND: notFoundError, ALREADY_EXISTS: alreadyExistsError, PERMISSION_DENIED: permissionDeniedError, INTERNAL: internalError, UNAVAILABLE: unavailableError, DATA_LOSS: dataLossError, UNAUTHENTICATED: unauthenticatedError, FAILED_PRECONDITION: failedPreconditionError, VALIDATION: validationError, UNAUTHORIZED: unauthorizedError, RESOURCE_NOT_FOUND: resourceNotFoundError, }; /** * * Creates and returns a new instance of the GrpcErrors class. * * @template T - string literal type representing gRPC error keys. * @returns - new instance of the GrpcErrors class. */ static getInstance<T extends IGrpcErrorKeys>(): GrpcErrors<T> { return new GrpcErrors<T>(); } /** * * Extends the ERRORS record with a custom gRPC error object. * * @param _key - The key representing the custom gRPC error type. * @param _error - The custom gRPC error object to add to the ERRORS record. * @param _metadata - Additional metadata to attach to the custom gRPC error (if available). * @param _details - Additional details about the custom gRPC error (if available). */ public EXTEND({ _key, _error, _metadata = null, _details = null, }: I_EXTEND<T> ): void { this.ERRORS[_key] = _error; } /** * * Throws a gRPC error and logs it. * * @param _key - The key representing the gRPC error type. * @param _source - The source of the error (if available). * @param _metadata - Additional metadata to attach to the gRPC error. * @param _details - Additional details about the error (if available). * @returns - The thrown gRPC error response. */ public T({ _key, _source = null, _metadata = null, _details = null, }: I_THROW<T> ): ServerErrorResponse { const key = String(_key) console.log(_source ? `[ Emitting GRPC ERROR: [ ${key} ] from "${_source}" ]` : `[ Emitting ERROR: [ ${key} ] ]`); const _error = this.ERRORS[_key]; if (_metadata) _error.metadata = _metadata; if (_details) _error.details = _details; return _error; } /** * * Emits a gRPC error on a writable stream and logs it. * * @param call - The writable stream on which to emit the gRPC error. * @param _key - The key representing the gRPC error type. * @param _source - The source of the error (if available). * @param _metadata - Additional metadata to attach to the gRPC error. * @param _details - Additional details about the error (if available). */ public E({ call, _key, _source = null, _metadata = null, _details = null, }: I_EMIT<T> ): void { const key = String(_key) const _error = this.ERRORS[_key]; if (_metadata) _error.metadata = _metadata; if (_details) _error.details = _details; call.emit('error', _error) return console.log(_source ? `[ Emitting GRPC ERROR: [ ${key} ] from "${_source}" ]` : `[ Emitting ERROR: [ ${key} ] ]`); } /** * * Calls a callback function with a gRPC error and logs it. * * @param callback - The callback function to call with the gRPC error. * @param _key - The key representing the gRPC error type. * @param _source - The source of the error (if available). * @param _metadata - Additional metadata to attach to the gRPC error. (if available). * @param _details - Additional details about the error (if available). */ public CB({ callback, _key, _source = null, _metadata = null, _details = null, }: I_CALLBACK<T> ): void { const key = String(_key) const _error = this.ERRORS[_key]; if (_metadata) _error.metadata = _metadata; if (_details) _error.details = _details; callback(_error) return console.log(_source ? `[ Emitting GRPC ERROR: [ ${key} ] from "${_source}" ]` : `[ Emitting ERROR: [ ${key} ] ]`); } /** * * Converts a Zod error to a gRPC error response with metadata and details. * * @param error - The Zod error to convert. * @returns - The gRPC error response. */ public handleZodError(error: ZodError): ServerErrorResponse { const _error = this.ERRORS.VALIDATION; const _metadata = new Metadata(); error?.errors?.forEach((e: ZodIssue, i: number) => { _metadata.set(`error_${i}`, e.message); }); _error.metadata = _metadata; _error.details = error?.errors?.map((e) => e.message).join(', '); return _error; } } /** * * Get default Class instance. * * @type {GrpcErrors<IGrpcErrorKeys>} */ export const w = GrpcErrors.getInstance<IGrpcErrorKeys>(); export interface I_EXTEND<T> { _key: keyof T; _error: ServerErrorResponse; _metadata?: null | Metadata; _details?: string | null; } export interface I_THROW<T> { _key: keyof T; _source?: string | null; _metadata?: null | Metadata; _details?: string | null; } export interface I_EMIT<T> { call: ServerWritableStream<any, any>; _key: keyof T; _source?: string | null; _metadata?: null | Metadata; _details?: string | null; } export interface I_CALLBACK<T> { callback: sendUnaryData<any>; _key: keyof T; _source?: string | null; _metadata?: null | Metadata; _details?: string | null; }