auth-js icon indicating copy to clipboard operation
auth-js copied to clipboard

Strongly Typed Errors

Open malobre opened this issue 3 years ago • 4 comments

Feature request

Is your feature request related to a problem? Please describe.

It is currently impossible to differentiate errors, e.g: when a user signs up there is a multitude of possible errors (network errors, database errors, etc) and although you can easily check if an error occured (if (res.error !== null) { /**/ }) it is not possible to know which error happened.

Describe the solution you'd like

Enum error types:

enum SignUpError {
  InvalidEmail,
  SignUpsDisabled,
  NetworkError,
  /* etc */
}

enum SignInError {
  InvalidCredentials,
  /* etc */
}
export default class GoTrueClient {
  // --snip--
  signUp(credentials: {
      email: string;
      password: string;
  }): Promise<{ /* success object */ } | SignUpError>;
  
  signIn(credentials: {
      email?: string;
      password?: string;
      provider?: Provider;
  }): Promise<{ /* success object */ } | SignInError>;
  // --snip--
}

Describe alternatives you've considered

I am not aware of any alternative.

malobre avatar Feb 03 '21 15:02 malobre

This would be a BIG DX improvement. I am willing to take a stab a this.

malobre avatar Feb 03 '21 15:02 malobre

we also have this open here: https://github.com/supabase/supabase/issues/507

We have a few options:

  1. Change the error format on the API gateway, but then each
  2. Change the error format in each of the libraries (gotrue-js, postgrest-js, realtime-js)
  3. Change the error format in this wrapper library (supabase-js)

I'm thinking that 2 might be the best option. It won't solve the error format for anyone doing cURL requests, but each of the tools has their opinion, and then our libraries are the place where we have been adding (small) DX improvements - changing defaults etc.

kiwicopple avatar Feb 04 '21 01:02 kiwicopple

Oops, I completely missed https://github.com/supabase/supabase/issues/507. I agree that 2 is the best option, there is a real need for harmonization across the three underlying librairies. Being able to handle different errors separately is the only hurdle I currently have with supabase.

malobre avatar Feb 04 '21 08:02 malobre

Yet another aspect to consider for DX is error handling. With typescript errors in catch are either of type unknown or any (depending on useUnknownInCatchVariables). We all know that any isn't a great choice as it renders TS kind of useless. So we set useUnknownInCatchVariables and TS now forces us to drilldown potential errors with type guards.

1. Errors as classes

class SupabaseError extends Error {
  constructor(
    public override message: string, 
    public code: string
  ) {
      super(message);
  }
}

const dispatchRequest = (): void => {
  throw new SupabaseError('The request timed out', 'NETWORK_FAILURE');
}

Handle the error:

try {
  dispatchRequest();
} catch(err: unknown) {
  if (err instanceof SupabaseError) {
    switch (err.code) {
      case 'NETWORK_FAILURE':
        // handle network errors
      break;
    }
  }
  // re throw unhandled error
  throw err;
}

2. Errors as objects

interface SupabaseError {
  code: string
  message: string
}

const dispatchRequest = (): void => {
  throw { code: 'NETWORK_FAILURE', message: 'The request timed out' } as SupabaseError;
}

Handle the error:

try {
  dispatchRequest();
} catch(err: unknown) {
  if (err.code === 'NETWORK_FAILURE') // object is of type unkown
  if (typeof err === 'object' && err.code === 'NETWORK_FAILURE') // object is possibly null
  if (typeof err === 'object' && err !== null && err.code === 'NETWORK_FAILURE') // property code does not exist on type object
  if (typeof err === 'object' && err !== null && 'code' in err && err.code === 'NETWORK_FAILURE') // still: property code does not exist on type object
}

Custom Typeguard to the rescue:

const isSupabaseError = (target: unknown): target is SupabaseError => {
  return (
    typeof target === 'object' 
      && target !== null 
      && 'code' in target 
      && 'message' in target
  );
}

try {
  dispatchRequest();
} catch(err: unknown) {
  if (isSupabaseError(err)) {
    switch (err.code) {
      case 'NETWORK_FAILURE':
        // handle network errors
      break;
    }
  }
  // re throw unhandled error
  throw err;
}

Closely looking at isSupabaseError at runtime can we really be sure that an target is a SupabaseError? No, this typeguard just assures that it is an object with the properties code and message, but it might have any other random property. Whereas with a class and isntanceof we can be 100% sure the error is a SupabaseError.

pixtron avatar Jul 31 '22 09:07 pixtron

Hey everyone, we've taken this feedback into consideration and this is now available in the rc branch (soon to be released in supabase-js/v2)! Will be closing this for now.

kangmingtay avatar Sep 27 '22 07:09 kangmingtay