@hey-api/client-fetch does not really `throw` where it should
steinathan opened this issue · comments
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
Yeah I think this is more TanStack Query integration question as it will need a slightly different implementation to make this work 👀
Ok, i'll stick to using fetch then until @hey-api/client-axios
is available
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
})
@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;
});
};