fusionauth-typescript-client icon indicating copy to clipboard operation
fusionauth-typescript-client copied to clipboard

Improve ClientResponse typing

Open ColinFrick opened this issue 5 months ago • 0 comments

Currently the Client Response is typed as:

export default class ClientResponse<T> {
  public statusCode: number;
  public response: T;
  public exception: Error;

  wasSuccessful() {
    return this.statusCode >= 200 && this.statusCode < 300;
  }
}

The thing is if a API function returns a value it is always a successful response, because in cause of an error the Error is thrown (ergo the function does not return normally).

try {
    const response = await client.retrieveRefreshTokens(user);
    if (response.wasSuccessful()) { // Always true
        const token = response.response.refreshToken; // response.response: RefreshTokenResponse
        const error = response.exception.message; // No compile error, but will throw a `exception is undefined` error
    }
} catch (e) {
    console.log(e); // e is a ClientResponse without response
}

So both response and exception properties should be marked as optional:

export default class ClientResponse<T> {
  public statusCode: number;
  public response?: T;
  public exception?: Error;

  wasSuccessful() {
    return this.statusCode >= 200 && this.statusCode < 300;
  }
}

But because either the function returns a response or throws an exception they should be the same type:

class ClientResponse<T> {
    public statusCode: number;
    public response: T;
}

class ErrorResponse<T> {
    public statusCode: number;
    public exception: Error;
}

wasSuccessful can be dropped in this case, because the function either returns something (wasSuccessful = true) or it throws an error (wasSuccessful = false).

As an alternative functions could never throw, but return either ClientResponse<T> or ErrorResponse:

type ClientResponse<T> = {
    statusCode: number;
    response: T;
    wasSuccessful: true;
}

type ErrorResponse = {
    statusCode: number;
    exception: Error;
    wasSuccessful: false;
}

type ClientResponse<T> = Response<T> | ErrorResponse;

I used types instead of classes because of type-hinting:

const response = await client.retrieveRefreshTokens(userId);
if (response.wasSuccessful) {
    const token = response.response.refreshToken; // response.response: RefreshTokenResponse
} else {
    throw new Error(response.exception.message); // response.exception: Error
}

ColinFrick avatar Sep 03 '24 10:09 ColinFrick