Skip to content

Commit

Permalink
fix: greatly improve error handling, maybe fix bun?
Browse files Browse the repository at this point in the history
  • Loading branch information
MiniDigger committed Jul 28, 2024
1 parent 1d03dce commit a691b78
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import io.papermc.hangar.model.api.PaginatedResult;
import io.papermc.hangar.model.api.User;
import io.papermc.hangar.model.api.project.ProjectCompact;
import io.papermc.hangar.model.api.project.ProjectSortingStrategy;
import io.papermc.hangar.model.api.requests.RequestPagination;
import io.papermc.hangar.security.annotations.Anyone;
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import io.papermc.hangar.model.api.PaginatedResult;
import io.papermc.hangar.model.api.User;
import io.papermc.hangar.model.api.project.ProjectCompact;
import io.papermc.hangar.model.api.project.ProjectSortingStrategy;
import io.papermc.hangar.model.api.requests.RequestPagination;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/composables/useApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function request<T>(url: string, method: AxiosRequestConfig["method"], data: obj
})
.catch((error: AxiosError) => {
const { trace, ...err } = (error.response?.data as { trace: any }) || {};
authLog("failed", err);
fetchLog("failed", err);
reject(error);
});
});
Expand Down
63 changes: 37 additions & 26 deletions frontend/src/composables/useErrorHandling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { isAxiosError } from "axios";
import type { Composer } from "vue-i18n";
import type { HangarApiException, HangarValidationException, MultiHangarApiException } from "~/types/backend";
import { tryUseNuxtApp } from "#app/nuxt";
import { isError } from "h3";

export function handleRequestError(err: AxiosError | unknown, msg: string | undefined = undefined, alwaysShowErrorPage = false) {
const i18n = tryUseNuxtApp()?.$i18n;
Expand All @@ -11,8 +12,9 @@ export function handleRequestError(err: AxiosError | unknown, msg: string | unde
_handleRequestError(err, i18n);
return;
}
if (import.meta.env.SSR || alwaysShowErrorPage) {
if (import.meta.server || alwaysShowErrorPage) {
_handleRequestError(err, i18n);
return;
}
const notification = useNotificationStore();
const transformed = transformAxiosError(err);
Expand Down Expand Up @@ -63,36 +65,45 @@ export function handleRequestError(err: AxiosError | unknown, msg: string | unde

function _handleRequestError(err: AxiosError | unknown, i18n?: Composer) {
const transformed = transformAxiosError(err);
if (!isAxiosError(err)) {
// everything should be an AxiosError
createError({
if (!isAxiosError(err) && !isError(err)) {
// everything should be an AxiosError or h3 error
fetchLog("handle not axios error", transformed);
throw createError({
statusCode: 500,
});
fetchLog("handle not axios error", transformed);
} else if (err.response && typeof err.response.data === "object" && err.response.data) {
if ("isHangarApiException" in err.response.data) {
const data =
"isMultiException" in err.response.data ? (err.response.data as MultiHangarApiException).exceptions?.[0] : (err.response.data as HangarApiException);
createError({
statusCode: data?.httpError.statusCode,
statusMessage: data?.message ? (i18n?.te(data.message) ? i18n.t(data.message) : data.message) : undefined,
});
} else if ("isHangarValidationException" in err.response.data) {
const data = err.response.data as HangarValidationException;
createError({
statusCode: data.httpError?.statusCode,
statusMessage: data.fieldErrors?.map((f) => f.errorMsg).join(", "),
});
} else {
createError({
statusCode: err.response.status,
statusMessage: err.response.statusText,
});
}
} else if (typeof err.response?.data === "object" && err.response.data) {
_handleErrorResponse(err.response.data, i18n);
} else if (err.cause?.response?.data) {
_handleErrorResponse(err.cause.response.data, i18n);
} else if (err.cause?.statusCode) {
// this error was rethrown, lets inform nuxt
showError(err.cause);
} else {
createError({
throw createError({
statusCode: 500,
statusMessage: "Internal Error: " + transformed.code,
cause: err,
});
}
}

function _handleErrorResponse(responseData: object, i18n?: Composer) {
if ("isHangarApiException" in responseData) {
const data = "isMultiException" in responseData ? (responseData as MultiHangarApiException).exceptions?.[0] : (responseData as HangarApiException);
throw createError({
statusCode: data?.httpError.statusCode,
statusMessage: data?.message ? (i18n?.te(data.message) ? i18n.t(data.message) : data.message) : undefined,
});
} else if ("isHangarValidationException" in responseData) {
const data = responseData as HangarValidationException;
throw createError({
statusCode: data.httpError?.statusCode,
statusMessage: data.fieldErrors?.map((f) => f.errorMsg).join(", "),
});
} else {
throw createError({
statusCode: err.response.status,
statusMessage: err.response.statusText,
});
}
}
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/composables/useErrorRedirect.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import type { RouteLocationNormalizedTyped } from "unplugin-vue-router";
import type { RouteLocationNamedRaw } from "vue-router";
import type { HangarNuxtError } from "~/types/components/error";

export function useErrorRedirect(currentRoute: RouteLocationNormalizedTyped<any>, status: number, msg?: string, data?: HangarNuxtError): RouteLocationNamedRaw {
export function useErrorRedirect(currentRoute: RouteLocationNormalizedTyped<any>, status: number, msg?: string, data?: HangarNuxtError) {
const globalError = useError();
if (globalError.value) {
// we are already in an error state, do nothing
return;
}
throw createError({
statusCode: status,
message: msg,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/error.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ if (
(typeof props.error?.data === "string" && JSON.parse(props.error?.data).logErrorMessage !== false) ||
(typeof props.error?.data !== "string" && props.error?.data?.logErrorMessage !== false)
) {
console.log("error", text.value, title.value, props.error);
console.log("render error page", text.value, title.value);
}
try {
useHead(useSeo(title.value, null, useRoute(), null));
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/[user].vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ const user = ref();
const organization = ref();
const blocking = computed(() => route.name === "user");
if (blocking.value) {
const u = await useUser(route.params.user);
const u = await useUser(route.params.user).catch((err) => handleRequestError(err));
await cb(u);
} else {
// manually carry nuxt content over cause ????
const nuxtApp = useNuxtApp();
useUser(route.params.user).then((u) => nuxtApp.runWithContext(() => cb(u)));
}
async function cb(u: Ref<User | null>) {
async function cb(u: Ref<User | null> | void) {
if (!u || !u.value) {
throw useErrorRedirect(route, 404, "Not found");
} else if (route.params.user !== u.value?.name) {
Expand Down
18 changes: 9 additions & 9 deletions frontend/src/pages/[user]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const requestParams = computed(() => {
return params;
});
const userData = await useUserData(props.user.name, requestParams.value);
const userData = await useUserData(route.params.user, requestParams.value);
const { starred, watching, organizations, pinned, organizationVisibility, possibleAlts } = userData.value || { starred: null };
const projects = ref(userData.value?.projects);
const orgRoles = useBackendData.orgRoles.filter((role) => role.assignable);
Expand Down Expand Up @@ -74,7 +74,7 @@ const buttons = computed<UserButton[]>(() => {
return list;
});
const isCurrentUser = computed<boolean>(() => authStore.user != null && authStore.user.name === props.user.name);
const isCurrentUser = computed<boolean>(() => authStore.user != null && authStore.user.name === props.user?.name);
watchDebounced(
requestParams,
Expand All @@ -85,30 +85,30 @@ watchDebounced(
// set the request params
await router.replace({ query: { page: page.value, ...paramsWithoutLimit } });
// do the update
projects.value = await useApi<PaginatedResultProject>("projects", "get", { owner: props.user.name, ...requestParams.value });
projects.value = await useApi<PaginatedResultProject>("projects", "get", { owner: props.user?.name, ...requestParams.value });
},
{ deep: true, debounce: 250 }
);
const description = (props.user?.tagline ? props.user.tagline + " - " : "") + "Download " + props.user.name + "'s plugins on Hangar.";
const description = (props.user?.tagline ? props.user.tagline + " - " : "") + "Download " + props.user?.name + "'s plugins on Hangar.";
useHead(
useSeo(props.user.name, description, route, props.user.avatarUrl, [
useSeo(props.user?.name, description, route, props.user?.avatarUrl, [
{
type: "application/ld+json",
children: JSON.stringify({
"@context": "https://schema.org",
"@type": "ProfilePage",
mainEntity: {
"@type": "Person",
name: props.user.name,
name: props.user?.name,
url: config.publicHost + "/" + route.path,
description: props.user.tagline,
image: props.user.avatarUrl,
description: props.user?.tagline,
image: props.user?.avatarUrl,
interactionStatistic: [
{
"@type": "InteractionCounter",
interactionType: "https://schema.org/CreateAction",
userInteractionCount: props.user.projectCount,
userInteractionCount: props.user?.projectCount,
},
{
"@type": "InteractionCounter",
Expand Down

0 comments on commit a691b78

Please sign in to comment.