/* * Copyright (c) 2022 EdgerOS Team. * All rights reserved. * * Detailed license information can be found in the LICENSE file. * * Author : 薛强 * Date : 2024-11-21 10:11:11 * LastEditors : 薛强 * LastEditTime : 2024-11-21 17:10:09 */ /* --------------------------------------------------------- * Copyright (C) Microsoft Corporation. All rights reserved. *-------------------------------------------------------- */ export class ClientRuntimeError extends Error { constructor(message: string) { super(`${message} Please report this error at https://github.com/microsoft/etcd3`) } } /** * Thrown if a method is called after the client is closed. */ export class ClientClosedError extends Error { constructor(namespace: string) { super(`Tried to call a ${namespace} method on etcd3, but the client was already closed`) } } /** * Symbol present on transient errors which will be resolved through default * fault handling. */ export const RecoverableError = Symbol('RecoverableError') /** * Returns whether the error is a network or server error that should trigger * fault-handling policies. */ export const isRecoverableError = (error: Error) => RecoverableError in error /** * A GenericError is rejected via the connection when some error occurs * that we can't be more specific about. */ export class GenericError extends Error {} /** * ProtocolError is thrown when a protocol error occurs on the other end, * indicating that the external implementation is incorrect or incompatible. */ export class ProtocolError extends GenericError {} /** * InternalError is thrown when a internal error occurs on either end. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class InternalError extends GenericError { [RecoverableError] = true } /** * CancelledError is emitted when an ongoing call is cancelled. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class CancelledError extends GenericError { [RecoverableError] = true } /** * Unknown error. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class UnknownError extends GenericError { [RecoverableError] = true } /** * Client specified an invalid argument. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class InvalidArgumentError extends GenericError {} /** * Deadline expired before operation could complete. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class DeadlineExceededError extends GenericError { [RecoverableError] = true } /** * Some requested entity (e.g., file or directory) was not found. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class NotFoundError extends GenericError {} /** * Some entity that we attempted to create (e.g., file or directory) already exists. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class AlreadyExistsError extends GenericError {} /** * Some resource has been exhausted, perhaps a per-user quota, or * perhaps the entire file system is out of space. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class ResourceExhastedError extends GenericError { [RecoverableError] = true } /** * Operation was rejected because the system is not in a state * required for the operation's execution. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class FailedPreconditionError extends GenericError {} /** * The operation was aborted, typically due to a concurrency issue * like sequencer check failures, transaction aborts, etc. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class AbortedError extends GenericError { [RecoverableError] = true } /** * Operation is not implemented or not supported/enabled in this service. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class NotImplementedError extends GenericError {} /** * Operation was attempted past the valid range. E.g., seeking or reading * past end of file. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class OutOfRangeError extends GenericError {} /** * Unrecoverable data loss or corruption. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class DataLossError extends GenericError {} /** * Unrecoverable data loss or corruption. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class UnavailableError extends GenericError { [RecoverableError] = true } /** * The request does not have valid authentication credentials for the operation. * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class UnauthenticatedError extends GenericError {} /** * EBError is an application error returned by etcd. */ export class EBError extends Error {} /** * LeaseInvalidError is thrown when trying to renew a lease that's * expired. */ export class LeaseInvalidError extends Error { constructor(leaseID: string) { super(`Lease ${leaseID} is expired or revoked`) } } /** * EtcdRoleExistsError is thrown when trying to create a role that already exists. */ export class EtcdRoleExistsError extends Error {} /** * EtcdUserExistsError is thrown when trying to create a user that already exists. */ export class EtcdUserExistsError extends Error {} /** * EtcdRoleNotGrantedError is thrown when trying to revoke a role from a user * to which the role is not granted. */ export class EtcdRoleNotGrantedError extends Error {} /** * EtcdRoleNotFoundError is thrown when trying to operate on a role that does * not exist. */ export class EtcdRoleNotFoundError extends Error {} /** * EtcdUserNotFoundError is thrown when trying to operate on a user that does * not exist. */ export class EtcdUserNotFoundError extends Error {} /** * EtcdLockFailedError is thrown when we fail to aquire a lock. */ export class LockFailedError extends Error {} /** * EtcdAuthenticationFailedError is thrown when an invalid username/password * combination is submitted. * * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class EtcdAuthenticationFailedError extends Error {} /** * EtcdInvalidAuthTokenError is thrown when an invalid or expired authentication * token is presented. */ export class EtcdInvalidAuthTokenError extends Error {} /** * EtcdPermissionDeniedError is thrown when the user attempts to modify a key * that they don't have access to. * * Also can be emitted from GRPC. * * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ export class EtcdPermissionDeniedError extends Error {} /** * EtcdWatchStreamEnded is emitted when a watch stream closes gracefully. * This is an unexpected occurrence. * * @see https://github.com/microsoft/etcd3/issues/72#issuecomment-386851271 */ export class WatchStreamEnded extends Error { constructor() { super('The etcd watch stream was unexpectedly ended') } } /** * Thrown from methods of {@link ElectionCampaign} if the campaign has ceased. */ export class NotCampaigningError extends Error {} /** * An STMConflictError is thrown from the `SoftwareTransaction.transact` * if we continue to get conflicts and exceed the maximum number * of retries. */ export class STMConflictError extends Error { constructor() { super('A conflict occurred executing the software transaction') } } type IErrorCtor = new (message: string) => Error /** * Mapping of GRPC error messages to typed error. GRPC errors are untyped * by default and sourced from within a mess of C code. */ const grpcMessageToError = new Map([ ['etcdserver: role name already exists', EtcdRoleExistsError], ['etcdserver: user name already exists', EtcdUserExistsError], ['etcdserver: role is not granted to the user', EtcdRoleNotGrantedError], ['etcdserver: role name not found', EtcdRoleNotFoundError], ['etcdserver: user name not found', EtcdUserNotFoundError], ['etcdserver: authentication failed, invalid user ID or password', EtcdAuthenticationFailedError], ['etcdserver: permission denied', EtcdPermissionDeniedError], ['etcdserver: invalid auth token', EtcdInvalidAuthTokenError], ['etcdserver: requested lease not found', LeaseInvalidError], ]) /** * Error code mapping * @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html */ const codeToError = new Map([ [1, CancelledError], [2, UnknownError], [3, InvalidArgumentError], [4, DeadlineExceededError], [5, NotFoundError], [6, AlreadyExistsError], [7, EtcdPermissionDeniedError], [8, ResourceExhastedError], [9, FailedPreconditionError], [10, AbortedError], [11, OutOfRangeError], [12, NotImplementedError], [13, InternalError], [14, UnavailableError], [15, DataLossError], [16, UnauthenticatedError], ]) function getMatchingGrpcError(message: string): IErrorCtor | undefined { for (const [key, value] of grpcMessageToError) { if (message.includes(key)) { return value } } return undefined } function rewriteErrorName(str: string, ctor: new (...args: any[]) => Error): string { return str.replace(/^Error:/, `${ctor.name}:`) } /** * Tries to convert an Etcd error string to an etcd error. */ export function castGrpcErrorMessage(message: string): Error { const Ctor = getMatchingGrpcError(message) || EBError return new Ctor(message) } /** * Tries to convert GRPC's generic, untyped errors to typed errors we can * consume. Yes, this method is abhorrent. */ export function castGrpcError(err: Error): Error { if (err.constructor !== Error) { return err // it looks like it's already some kind of typed error } let Ctor = getMatchingGrpcError(err.message) if (!Ctor && 'code' in err && typeof err.code === 'number') { Ctor = codeToError.get(err.code) } Ctor ||= err.message.includes('etcdserver:') ? EBError : GenericError const castError = new Ctor(rewriteErrorName(err.message, Ctor)) castError.stack = rewriteErrorName(String(err.stack), Ctor) return castError }