Skip to content

Commit

Permalink
wip - removing NatsError in favor of easier more generic APIs and tes…
Browse files Browse the repository at this point in the history
…ting with instanceof.

Signed-off-by: Alberto Ricart <[email protected]>
  • Loading branch information
aricart committed Oct 28, 2024
1 parent 2ac7ea9 commit bc229c9
Show file tree
Hide file tree
Showing 49 changed files with 524 additions and 525 deletions.
2 changes: 1 addition & 1 deletion core/deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nats-io/nats-core",
"version": "3.0.0-30",
"version": "3.0.0-31",
"exports": {
".": "./src/mod.ts",
"./internal": "./src/internal_mod.ts"
Expand Down
2 changes: 1 addition & 1 deletion core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nats-io/nats-core",
"version": "3.0.0-30",
"version": "3.0.0-31",
"files": [
"lib/",
"LICENSE",
Expand Down
152 changes: 2 additions & 150 deletions core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,156 +44,6 @@ export enum DebugEvents {
ClientInitiatedReconnect = "client initiated reconnect",
}

export enum ErrorCode {
// emitted by the client
// ApiError = "BAD API",
BadAuthentication = "BAD_AUTHENTICATION",
BadCreds = "BAD_CREDS",
BadHeader = "BAD_HEADER",
BadJson = "BAD_JSON",
BadPayload = "BAD_PAYLOAD",
BadSubject = "BAD_SUBJECT",
Cancelled = "CANCELLED",
ConnectionClosed = "CONNECTION_CLOSED",
ConnectionDraining = "CONNECTION_DRAINING",
ConnectionRefused = "CONNECTION_REFUSED",
ConnectionTimeout = "CONNECTION_TIMEOUT",
Disconnect = "DISCONNECT",
InvalidOption = "INVALID_OPTION",
InvalidPayload = "INVALID_PAYLOAD",
MaxPayloadExceeded = "MAX_PAYLOAD_EXCEEDED",
NoResponders = "503",
NotFunction = "NOT_FUNC",
RequestError = "REQUEST_ERROR",
ServerOptionNotAvailable = "SERVER_OPT_NA",
SubClosed = "SUB_CLOSED",
SubDraining = "SUB_DRAINING",
Timeout = "TIMEOUT",
Tls = "TLS",
Unknown = "UNKNOWN_ERROR",
WssRequired = "WSS_REQUIRED",

// jetstream
JetStreamInvalidAck = "JESTREAM_INVALID_ACK",
JetStream404NoMessages = "404",
JetStream408RequestTimeout = "408",
//@deprecated: use JetStream409
JetStream409MaxAckPendingExceeded = "409",
JetStream409 = "409",
JetStreamNotEnabled = "503",
JetStreamIdleHeartBeat = "IDLE_HEARTBEAT",

// emitted by the server
AuthorizationViolation = "AUTHORIZATION_VIOLATION",
AuthenticationExpired = "AUTHENTICATION_EXPIRED",
ProtocolError = "NATS_PROTOCOL_ERR",
PermissionsViolation = "PERMISSIONS_VIOLATION",
AuthenticationTimeout = "AUTHENTICATION_TIMEOUT",
AccountExpired = "ACCOUNT_EXPIRED",
}

export function isNatsError(err: NatsError | Error): err is NatsError {
return typeof (err as NatsError).code === "string";
}

export class Messages {
messages: Map<string, string>;

constructor() {
this.messages = new Map<string, string>();
this.messages.set(
ErrorCode.InvalidPayload,
"Invalid payload type - payloads can be 'binary', 'string', or 'json'",
);
this.messages.set(ErrorCode.BadJson, "Bad JSON");
this.messages.set(
ErrorCode.WssRequired,
"TLS is required, therefore a secure websocket connection is also required",
);
}

static getMessage(s: string): string {
return messages.getMessage(s);
}

getMessage(s: string): string {
return this.messages.get(s) || s;
}
}

// safari doesn't support static class members
const messages: Messages = new Messages();

export type ApiError = {
/**
* Status code
*/
code: number;
/**
* JetStream Error Code
*/
err_code: number;
/**
* Error description
*/
description: string;
};

export class NatsError extends Error {
// TODO: on major version this should change to a number/enum
code: string;
permissionContext?: { operation: string; subject: string; queue?: string };
chainedError?: Error;
// these are for supporting jetstream
api_error?: ApiError;

/**
* @param {String} message
* @param {String} code
* @param {Error} [chainedError]
*
* @api private
*/
constructor(message: string, code: string, chainedError?: Error) {
super(message);
this.name = "NatsError";
this.message = message;
this.code = code;
this.chainedError = chainedError;
}

static errorForCode(code: string, chainedError?: Error): NatsError {
const m = Messages.getMessage(code);
return new NatsError(m, code, chainedError);
}

isAuthError(): boolean {
return this.code === ErrorCode.AuthenticationExpired ||
this.code === ErrorCode.AuthorizationViolation ||
this.code === ErrorCode.AccountExpired;
}

isAuthTimeout(): boolean {
return this.code === ErrorCode.AuthenticationTimeout;
}

isPermissionError(): boolean {
return this.code === ErrorCode.PermissionsViolation;
}

isProtocolError(): boolean {
return this.code === ErrorCode.ProtocolError;
}

isJetStreamError(): boolean {
return this.api_error !== undefined;
}

jsError(): ApiError | null {
return this.api_error ? this.api_error : null;
}
}

export type MsgCallback<T> = (err: Error | null, msg: T) => void;

/**
Expand Down Expand Up @@ -488,6 +338,8 @@ export interface NatsConnection {
* Publishes using the subject of the specified message, specifying the
* data, headers and reply found in the message if any.
* @param msg
* @throws InvalidSubjectError
* @throws InvalidOptionError,
*/
publishMessage(msg: Msg): void;

Expand Down
118 changes: 111 additions & 7 deletions core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,43 @@
* limitations under the License.
*/

/**
* Represents an error that is thrown when an invalid subject is encountered.
* This class extends the built-in Error object.
*
* @class
* @extends Error
*/
export class InvalidSubjectError extends Error {
constructor(subject: string, options?: ErrorOptions) {
super(`illegal subject: '${subject}'`, options);
this.name = "InvalidSubjectError";
}
}

/**
* Represents an error for invalid header scenarios.
*
* Instances of this error are thrown when a header is detected
* as having an invalid name or value.
*
* @param {string} message - A message providing details about the invalid header.
* @param {ErrorOptions} [options] - Optional error options.
*/
export class InvalidHeaderError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(`invalid header: ${message}`, options);
this.name = "InvalidHeaderError";
}
}

/**
* A custom error class to signify invalid options provided to an API function or
* to a ConnectionOption that doesn't match server settings.
*
* @param {string} message - A message providing details about the invalid option.
* @param {ErrorOptions} [options] - Optional error options.
*/
export class InvalidOptionError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
Expand All @@ -47,13 +70,30 @@ export class InvalidOptionError extends Error {
}
}

/**
* InvalidOperationError is a custom error class that extends the standard Error object.
* It represents an error that occurs when an invalid operation is attempted on one of
* objects returned by the API. For example, trying to iterate on an object that was
* configured with a callback.
*
* @class InvalidOperationError
* @extends {Error}
*
* @param {string} message - The error message that explains the reason for the error.
* @param {ErrorOptions} [options] - Optional parameter to provide additional error options.
*/
export class InvalidOperationError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = "InvalidOperationError";
}
}

/**
* Represents an error indicating that user authentication has expired.
* This error is typically thrown when a user attempts to access a connection
* but their authentication credentials have expired.
*/
export class UserAuthenticationExpiredError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
Expand All @@ -69,6 +109,12 @@ export class UserAuthenticationExpiredError extends Error {
}
}

/**
* Represents an error related to authorization issues.
* Note that these could represent an authorization violation,
* or that the account authentication configuration has expired,
* or an authentication timeout.
*/
export class AuthorizationError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
Expand All @@ -94,48 +140,106 @@ export class AuthorizationError extends Error {
}
}

/**
* Class representing an error thrown when an operation is attempted on a closed connection.
*
* This error is intended to signal that a connection-related operation could not be completed
* because the connection is no longer open or has been terminated.
*
* @class
* @extends Error
*/
export class ClosedConnectionError extends Error {
constructor() {
super("closed connection");
this.name = "ClosedConnectionError";
}
}

/**
* The `ConnectionDrainingError` class represents a specific type of error
* that occurs when a connection is being drained.
*
* This error is typically used in scenarios where connections need to be
* gracefully closed or when they are transitioning to an inactive state.
*
* The error message is set to "connection draining" and the error name is
* overridden to "DrainingConnectionError".
*/
export class ConnectionDrainingError extends Error {
constructor() {
super("connection draining");
this.name = "DrainingConnectionError";
}
}

/**
* Represents an error that occurs when a network connection fails.
* Extends the built-in Error class to provide additional context for connection-related issues.
*
* @param {string} message - A human-readable description of the error.
* @param {ErrorOptions} [options] - Optional settings for customizing the error behavior.
*/
export class ConnectionError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = "ConnectionError";
}
}

/**
* Represents an error encountered during protocol operations.
* This class extends the built-in `Error` class, providing a specific
* error type called `ProtocolError`.
*
* @param {string} message - A descriptive message describing the error.
* @param {ErrorOptions} [options] - Optional parameters to include additional details.
*/
export class ProtocolError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = "ProtocolError";
}
}

/**
* Class representing an error that occurs during an request operation
* (such as TimeoutError, or NoRespondersError, or some other error).
*
* @extends Error
*/
export class RequestError extends Error {
constructor(message = "", options?: ErrorOptions) {
super(message, options);
this.name = "RequestError";
}
}

/**
* TimeoutError is a custom error class that extends the built-in Error class.
* It is used to represent an error that occurs when an operation exceeds a
* predefined time limit.
*
* @class
* @extends {Error}
*/
export class TimeoutError extends Error {
constructor(options?: ErrorOptions) {
super("timeout", options);
this.name = "TimeoutError";
}
}

/**
* NoRespondersError is an error thrown when no responders (no service is
* subscribing to the subject) are found for a given subject. This error
* is typically found as the cause for a RequestError
*
* @extends Error
*
* @param {string} subject - The subject for which no responders were found.
* @param {ErrorOptions} [options] - Optional error options.
*/
export class NoRespondersError extends Error {
subject: string;
constructor(subject: string, options?: ErrorOptions) {
Expand All @@ -145,13 +249,13 @@ export class NoRespondersError extends Error {
}
}

export class ServerError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = "ServerError";
}
}

/**
* Class representing a Permission Violation Error.
* It provides information about the operation (either "publish" or "subscription")
* and the subject used for the operation and the optional queue (if a subscription).
*
* This error is terminal for a subscription.
*/
export class PermissionViolationError extends Error {
operation: "publish" | "subscription";
subject: string;
Expand Down
Loading

0 comments on commit bc229c9

Please sign in to comment.