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

@hey-api/client-fetch does not really throw where it should #680

Open
steinathan opened this issue Jun 15, 2024 · 4 comments
Open

@hey-api/client-fetch does not really throw where it should #680

steinathan opened this issue Jun 15, 2024 · 4 comments
Labels
bug 🔥 Something isn't working

Comments

@steinathan
Copy link

Description

Using @hey-api/client-fetch does not actually show errors where they should be, see the comparison examples below

My client (would love an axios client :) )

import { createClient } from "@hey-api/client-fetch";

  const [idToken] = useLocalStorage("id_token");
  createClient({
    baseUrl: import.meta.env["VITE_APP_API_URL"],
    headers: {
      Authorization: `Bearer ${idToken}`,
    },
  });

@hey-api/client-fetch ❌

export const useLogin = () => {
  const { toast } = useToast();
  const navigate = useNavigate();
  const setUser = useUserStore((state) => state.setUser);

  const {
    error,
    isPending: loading,
    mutateAsync: loginFn,
  } = useMutation({
    mutationFn: login,
    onError: (err) => console.log("ERROR", err), // NEVER WORKS
    onSuccess: ({ data, error }) => {
      if (data) {
        setUser(data.user);
        localStorage.setItem("id_token", data.access_token);
        navigate("/dashboard");
      }
      
      if (error) { /// Errors shouldn't be handled here
        toast({
          variant: "destructive",
          title: "Uh oh! Something went wrong.",
          description: error?.detail,
        });

        console.log("ERROR", error);
      }
    },
  });

  return { loginFn, loading, error };
};

Axios client ✅

export const useLogin = () => {
  const { toast } = useToast();
  const navigate = useNavigate();
  const setUser = useUserStore((state) => state.setUser);

  const {
    error,
    isPending: loading,
    mutateAsync: loginFn,
  } = useMutation({
    mutationFn: login,
    onError: (error) => {
      toast({
        variant: "destructive",
        title: error?.detail ?? error.message,
        description: "Uh oh! Something went wrong.",
      });
    },
    onSuccess: (data) => {
      if (data) {
        setUser(data.user);
        localStorage.setItem("id_token", data.access_token);
        navigate("/dashboard");
      }
    },
  });

  return { loginFn, loading, error };
};

OpenAPI specification (optional)

{"openapi":"3.1.0","info":{"title":"FastAPI","version":"0.1.0"},"paths":{"/api/auth/me":{"get":{"tags":["auth"],"summary":"Get Me","operationId":"get_me","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"null"}],"title":"Response Get Me Api Auth Me Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/auth/login":{"post":{"tags":["auth"],"summary":"Login","operationId":"login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserLoginInput"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserLoginResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/register":{"post":{"tags":["auth"],"summary":"Register","description":"Registers a new user by creating a user in the authentication service and adding them to the user table.\n\nParameters:\n    params (UserRegisterInput): The input parameters for registering a new user, including the email and password.\n\nReturns:\n    User","operationId":"register","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserRegisterInput"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/":{"get":{"summary":"Index","operationId":"index","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Index  Get"}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"User":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"},"picture":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Picture"},"username":{"type":"string","title":"Username"},"email":{"type":"string","title":"Email"},"email_verified":{"type":"boolean","title":"Email Verified"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","username","email","email_verified","created_at","updated_at"],"title":"User","description":"Represents a User record"},"UserLoginInput":{"properties":{"identifier":{"type":"string","title":"Identifier"},"password":{"type":"string","title":"Password"}},"type":"object","required":["identifier","password"],"title":"UserLoginInput"},"UserLoginResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type"},"user":{"$ref":"#/components/schemas/User"}},"type":"object","required":["access_token","token_type","user"],"title":"UserLoginResponse"},"UserRegisterInput":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"email":{"type":"string","format":"email","title":"Email"},"name":{"type":"string","title":"Name"},"password":{"type":"string","title":"Password"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"}},"type":"object","required":["email","name","password"],"title":"UserRegisterInput"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}

Configuration

export default defineConfig({
  input: "http://localhost:1337/openapi.json",
  client: "@hey-api/client-fetch",
  output: {
    path: "src/openapi",
    lint: "eslint",
    format: "prettier",
  },
  services: {
    asClass: false,
  },
  types: {
    enums: "javascript",
    name: "PascalCase",
  },
});

System information (optional)

No response

@steinathan steinathan added the bug 🔥 Something isn't working label Jun 15, 2024
@steinathan steinathan changed the title @hey-api/client-fetch does not really throw properly @hey-api/client-fetch does not really throw where it should Jun 15, 2024
@mrlubos
Copy link
Contributor

mrlubos commented Jun 15, 2024

Yeah I think this is more TanStack Query integration question as it will need a slightly different implementation to make this work 👀

@steinathan
Copy link
Author

Ok, i'll stick to using fetch then until @hey-api/client-axios is available

@frdwhite24
Copy link

the returning of an object of { data: DataType, error: ErrorType, request: Request, response: Response } makes things quite hard to work with Tanstack Query, but it's totally doable. With regards to not throwing an error, I encountered this yesterday and just put this into a response interceptor, hope it helps!

client.interceptors.response.use(async (response, request) => {
  const statusCategory = response?.status.toString()[0]

  switch (statusCategory) {
    case '4': {
      throw new MyApiError()
    }
    case '5':
      throw new MyApiError()
  }

  return response
})

@steinathan
Copy link
Author

@frdwhite24 Thanks a lot - your solution works for me

 const setOpenAPIClient = () => {
    const client = createClient({
      baseUrl: import.meta.env["VITE_APP_API_URL"],
      headers: {
        Authorization: `Bearer ${idToken}`,
      },
    });

    client.interceptors.response.use(async (response, _) => {
      const statusCategory = response?.status?.toString()[0];

      if (!statusCategory) {
        throw new Error("Invalid response status");
      }

      const responseData = await response.clone().json();
      const message = responseData?.detail;

      if (["3", "4", "5"].includes(statusCategory)) {
        const serverError = new ServerApiError(message);
        console.error(message, statusCategory);
        throw serverError;
      }

      return response;
    });
  };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🔥 Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants