Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Return error message sent from backend instead of standard message in ResponseError #821

Open
1 of 2 tasks
raghibomar786 opened this issue Aug 13, 2024 · 1 comment
Labels

Comments

@raghibomar786
Copy link

Describe the feature

In case of error, it is important to know the reason of failure. In our use case, we need to display the reason for failure to the user. The backend sends the reason for failure in the error message, but pdk internally returns standard static message : "Response returned an error code". Is there a way to get the actual error message that the backend returns? If not, we should update this to pass back the actual error message sent by the backend.

Code:

protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise<Response> {
        const { url, init } = await this.createFetchParams(context, initOverrides);
        const response = await this.fetchApi(url, init);
        if (response && (response.status >= 200 && response.status < 300)) {
            return response;
        }
        throw new ResponseError(response, 'Response returned an error code');
    }
export class ResponseError extends Error {
    override name: "ResponseError" = "ResponseError";
    constructor(public response: Response, msg?: string) {
        super(msg);
    }
}

Use Case

There's a UI screen that can fail for multiple reason. We need to tell the user the exact reason for the failure. We send the exact reason from backend but there's no way to get it in frontend with the current code.

Proposed Solution

response is already present in the code, just need to propagate the message in the final function.

Other Information

No response

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

PDK version used

0.23.38

What languages will this feature affect?

Typescript, Java

Environment details (OS name and version, etc.)

Linux

@cogwirrel
Copy link
Member

Hi @raghibomar786,

Thanks for raising this! It would definitely be nicer if the generated typescript client returned the error message by default!

Sadly the way the generated typescript client is generated by OpenAPI generator, it doesn't extract the body of an error response. The long term fix for this is to completely own our own templates for the generated client, so we can adjust how errors are handled by default. I'm not sure when we'll be able to do this as it will take a bit of work.

The way I've worked around this for projects using PDK is to add some middleware to the client which throws the error earlier than when the generated client throws the generic ResponseError. You can pass the middleware when you instantiate the client. This might look something like this if you use the CloudScapeReactTsWebsite:

In packages/website/src/hooks/useTypeSafeApiClient.ts:

import useSigV4Client from "@aws-northstar/ui/components/CognitoAuth/hooks/useSigv4Client";
import { DefaultApi as MyApiApi, Configuration as MyApiApiConfiguration, BadRequestErrorResponseContent, InternalFailureErrorResponseContent, NotFoundErrorResponseContent, NotAuthorizedErrorResponseContent, Middleware, ResponseContext } from "myapi-typescript-react-query-hooks";
import { useContext, useMemo } from "react";
import { RuntimeConfigContext } from "../components/RuntimeContext";

// Interface for API errors
export interface ApiError {
  readonly status: number;
  readonly details:
    | BadRequestErrorResponseContent
    | InternalFailureErrorResponseContent
    | NotFoundErrorResponseContent
    | NotAuthorizedErrorResponseContent
}


export const isApiError = (e: unknown): e is ApiError =>
  !!(e && typeof e === "object" && "status" in e && "details" in e);

/**
 * Middleware for handling API errors
 */
const errorHandlingMiddleware: Middleware = {
  post: async ({ response }: ResponseContext) => {
    if (response && response.status >= 400 && response.status < 600) {
      let details;
      try {
        details = await response.json();
      } catch (e) {
        // Unable to parse response body, so continue with default error handling
        return response;
      }
      throw <ApiError>{
        status: response.status,
        details,
      };
    }
    return response;
  },
};

export const useMyApiApiClient = () => {
  const client = useSigV4Client();
  const runtimeContext = useContext(RuntimeConfigContext);

  return useMemo(() => {
    return runtimeContext?.typeSafeApis?.["MyApi"]
      ? new MyApiApi(
          new MyApiApiConfiguration({
            basePath: runtimeContext.typeSafeApis["MyApi"],
            fetchApi: client,
            // Add the middleware here
            middleware: [errorHandlingMiddleware],
          })
        )
      : undefined;
  }, [client, runtimeContext?.typeSafeApis?.["MyApi"]]);
};

It then becomes possible to access the actual error response from the hooks, eg:

import { Alert } from "@cloudscape-design/components";
import { ApiError, errorTitle, isApiError } from "../../hooks/useTypeSafeApiClient";
import { useSayHello } from "myapi-typescript-react-query-hooks"

export const ExampleComponent = () => {

  const hello = useSayHello({ name: "Jack" });

  return hello.error && isApiError(hello.error) ? (
    <Alert type="error" header="Error">
      {hello.error.details.message}
    </Alert>
  ) : ...;

};

Hope that helps!

I'll leave this open as I think we can improve things by providing this middleware or similar as part of the generated client, which isn't as heavy a lift as owning all the templates for the generated typescript fetch client.

Cheers,
Jack

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants