0

I have defined the following object

const AuthErrorCode = {
   EMAIL_ALREADY_EXISTS: {
      code: "auth/email-already-exists",
      message: "Hello world!"
   },
   ... more error codes
};

And I am implementing a class that extends Error

class AuthError extends Error {
  constructor(code, message = undefined) {
    switch(code) {
      case "EMAIL_ALREADY_EXISTS":
        code = AuthErrorCode[code].code;
        message = message ?? AuthErrorCode[code].message;
        break;

      default:
        throw new Error("Invalid code");
    }

    super(message); 

    Object.assign(this, {
      code,
      name: "AuthError",
    });
  }
}

which is supposed to receive a code and an optional custom message.

This class has to check that the given code is in the AuthErrorCode object (EMAIL_ALREADY_EXISTS || "auth/email-already-exists" are valid). If it is not inside it, then some kind of feedback should be displayed to the programmer (an error or something). I mean, I need to make sure that the code is a valid AuthErrorCode, because if not, the class is being used incorrectly.

How can I do that? Is it possible?

For example, this code must fail:

throw new AuthError("auth/some-invented-code", "Hello world!");

Example of correct use:

throw new AuthError("EMAIL_ALREADY_EXISTS", "Hello world!");
throw new AuthError("auth/email-already-exists");
5
  • Why mix & match classic object construction with newer Class based facade? Why write a simple enum in such a convoluted way as you have in the IFFE? It seems natural that an AuthError class would extend AuthErrorCode class which would extend Error. That would allow the AuthErrorCode to determine its own fate. If the 'is a' relationship is uncomfortable for you, you can always encapsulate the AuthErrorCode class. As you have it written right now this will throw: AuthErrorCode[code].message if the code doesn't exist. So that certainly doesn't achieve your goal. Commented Nov 26, 2021 at 0:46
  • @RandyCasburn I updated the code. I think that is is cool for me to use a simple object in order to define the AuthErrorCodes, with fallback default messages. My main purpose is to accept both types of codes in my class constructor ("EMAIL_ALREADY_EXISTS" and "auth/email-already-exists") in order to be flexible with the error definitions. Commented Nov 26, 2021 at 1:10
  • "some kind of feedback should be displayed to the programmer"... the solution for this is TypeScript Commented Nov 26, 2021 at 1:28
  • Still don't get your design decision after your edits. You state you want to provide flexibility to the users of your API, yet the must use one-of-two error codes for any error. Providing two mandates vs one is not being flexible - it is being confusing (which do I use and when - there must be a reason for having two, where is it documented, etc) You really need to read up on what Enum data types are and why they are so functional. You're reinventing the wheel. Here is a link to an article about Enums and how to use them with JavaScript. Commented Nov 26, 2021 at 1:33
  • @RandyCasburn I am throwing custom AuthErrors in the endpoints of the authentication service I have implemented. There are multiple types of "valid errors" (auth/invalid-email, auth/invalid-birthday, auth/form-empty-fields, auth/email-already-exists...). My idea behind the enum was to provide some restrictive codes for the AuthError class, with fallback messages, because it has no sense to throw AuthError("bitcoin", "ethereum"), for example. Commented Nov 26, 2021 at 1:44

1 Answer 1

2

There's a way but ultimately I think it's bad design.

If you already know the set of valid values why not simply expose factory functions for every error types?

e.g.

// Assume encapsulated in a module

class AuthError extends Error {
  constructor(message, code) {
    super(message);
    this.code = code;
  }
}

// This would get exported (factory methods for errors)
const authErrors = Object.freeze({
  emailAlreadyExists(message = 'Hello world!') {
    return new AuthError('auth/email-already-exists', message);
  }

   // other error types
});

// Client usage
throw authErrors.emailAlreadyExists('email already registered');

Alternatively you could create an explicit exception class per error type whic is perhaps more aligned with the Open-Closed Principle:

// Assume encapsulated in a module

class AuthError extends Error {
  constructor(message, code) {
    super(message);
    this.code = code;
  }
}

// Export this
class EmailAdreadyExists extends AuthError {
  constructor(message = "Hello world!") {
    super(message, "auth/email-already-exists");
  }
}

// Client usage
throw new EmailAdreadyExists("email already registered");

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.