Added APIError en exception to filter out any system Error providing an easy way to handle business errors with translations

This commit is contained in:
Clément Le Bihan
2023-02-23 00:46:03 +01:00
parent b8e7b5d09d
commit 48deb3c176
3 changed files with 39 additions and 7 deletions
+32 -5
View File
@@ -9,6 +9,7 @@ import User from "./models/User";
import Constants from "expo-constants"; import Constants from "expo-constants";
import store from "./state/Store"; import store from "./state/Store";
import { Platform } from "react-native"; import { Platform } from "react-native";
import { en } from "./i18n/Translations";
type AuthenticationInput = { username: string; password: string }; type AuthenticationInput = { username: string; password: string };
type RegistrationInput = AuthenticationInput & { email: string }; type RegistrationInput = AuthenticationInput & { email: string };
@@ -22,8 +23,23 @@ type FetchParams = {
raw?: true; raw?: true;
}; };
const dummyIllustrations = // This Exception is intended to cover all business logic errors (invalid credentials, couldn't find a song, etc.)
[ // technical errors (network, server, etc.) should be handled as standard Error exceptions
// it helps to filter errors in the catch block, APIErrors messages should
// be safe to use in combination with the i18n library
export class APIError extends Error {
constructor(
message: string,
public status: number,
// Set the message to the correct error this is a placeholder
// when the error is only used internally (middleman)
public userMessage : keyof typeof en = "unknownError"
) {
super(message);
}
}
const dummyIllustrations = [
"https://i.discogs.com/syRCX8NaLwK2SMk8X6TVU_DWc8RRqE4b-tebAQ6kVH4/rs:fit/g:sm/q:90/h:600/w:600/czM6Ly9kaXNjb2dz/LWRhdGFiYXNlLWlt/YWdlcy9SLTgyNTQz/OC0xNjE3ODE0NDI2/LTU1MjUuanBlZw.jpeg", "https://i.discogs.com/syRCX8NaLwK2SMk8X6TVU_DWc8RRqE4b-tebAQ6kVH4/rs:fit/g:sm/q:90/h:600/w:600/czM6Ly9kaXNjb2dz/LWRhdGFiYXNlLWlt/YWdlcy9SLTgyNTQz/OC0xNjE3ODE0NDI2/LTU1MjUuanBlZw.jpeg",
"https://folkr.fr/wp-content/uploads/2017/06/dua-lipa-folkr-2017-cover-04.jpg", "https://folkr.fr/wp-content/uploads/2017/06/dua-lipa-folkr-2017-cover-04.jpg",
"https://folkr.fr/wp-content/uploads/2017/06/dua-lipa-folkr-2017-cover-03.jpg", "https://folkr.fr/wp-content/uploads/2017/06/dua-lipa-folkr-2017-cover-03.jpg",
@@ -56,16 +72,20 @@ export default class API {
header, header,
body: JSON.stringify(params.body), body: JSON.stringify(params.body),
method: params.method ?? "GET", method: params.method ?? "GET",
}).catch(() => {
throw new Error("Error while fetching API: " + baseAPIUrl);
}); });
if (params.raw) { if (params.raw) {
return response.arrayBuffer(); return response.arrayBuffer();
} }
const body = await response.text(); const body = await response.text();
try { try {
const jsonResponse = body.length != 0 ? JSON.parse(body) : {}; const jsonResponse = body.length != 0 ? JSON.parse(body) : {};
if (!response.ok) { if (!response.ok) {
throw new Error(jsonResponse.error ?? response.statusText); throw new APIError(
jsonResponse ?? response.statusText,
response.status
);
} }
return jsonResponse; return jsonResponse;
} catch (e) { } catch (e) {
@@ -82,7 +102,14 @@ export default class API {
route: "/auth/login", route: "/auth/login",
body: authenticationInput, body: authenticationInput,
method: "POST", method: "POST",
}).then((responseBody) => responseBody.access_token); })
.then((responseBody) => responseBody.access_token)
.catch((e) => {
if (!(e instanceof APIError)) throw e;
if (e.status == 401) throw new APIError("invalidCredentials", 401, "invalidCredentials");
throw e;
});
} }
/** /**
* Create a new user profile, with an email and a password * Create a new user profile, with an email and a password
+3
View File
@@ -89,6 +89,7 @@ export const en = {
forgottenPassword: 'Forgotten password', forgottenPassword: 'Forgotten password',
partition: 'Partition', partition: 'Partition',
unknownError: 'Unknown error',
}; };
export const fr: typeof en = { export const fr: typeof en = {
@@ -182,6 +183,7 @@ export const fr: typeof en = {
forgottenPassword: "Mot de passe oublié", forgottenPassword: "Mot de passe oublié",
partition: 'Partition', partition: 'Partition',
unknownError: 'Erreur inconnue',
}; };
export const sp: typeof en = { export const sp: typeof en = {
@@ -277,4 +279,5 @@ export const sp: typeof en = {
repeatPassword: 'Répéter le mot de passe', repeatPassword: 'Répéter le mot de passe',
partition: 'Partition', partition: 'Partition',
unknownError: 'Error desconocido',
}; };
+4 -2
View File
@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { useDispatch } from '../state/Store'; import { useDispatch } from '../state/Store';
import { Translate, translate } from "../i18n/i18n"; import { Translate, translate } from "../i18n/i18n";
import API from "../API"; import API, { APIError } from "../API";
import { setAccessToken } from "../state/UserSlice"; import { setAccessToken } from "../state/UserSlice";
import { Center, Button, Text } from 'native-base'; import { Center, Button, Text } from 'native-base';
import SigninForm from "../components/forms/signinform"; import SigninForm from "../components/forms/signinform";
@@ -14,7 +14,9 @@ const hanldeSignin = async (username: string, password: string, apiSetter: (acce
apiSetter(apiAccess); apiSetter(apiAccess);
return translate("loggedIn"); return translate("loggedIn");
} catch (error) { } catch (error) {
return "Username or password incorrect"; if (error instanceof APIError) return translate(error.userMessage);
if (error instanceof Error) return error.message;
return "Unknown error";
} }
}; };