mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 21:11:53 +00:00
fix: handle 4XX and 204 properly (#8722)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
This commit is contained in:
@@ -78,3 +78,6 @@ _data/
|
||||
|
||||
# Claude
|
||||
CLAUDE.md
|
||||
|
||||
# LLM's (Until we have a standard one)
|
||||
AGENTS.md
|
||||
|
||||
+8
-1
@@ -2,9 +2,10 @@
|
||||
|
||||
All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
## [1.13.0] (Prowler unreleased)
|
||||
## [1.13.0] (Prowler UNRELEASED)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- Support for Markdown and AdditionalURLs in findings detail page [(#8704)](https://github.com/prowler-cloud/prowler/pull/8704)
|
||||
- `Prowler Hub` menu item with tooltip [(#8692)] (https://github.com/prowler-cloud/prowler/pull/8692)
|
||||
- Copy link button to finding detail page [(#8685)] (https://github.com/prowler-cloud/prowler/pull/8685)
|
||||
@@ -15,6 +16,12 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
---
|
||||
|
||||
## [1.12.2] (Prowler v5.12.2)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Handle 4XX errors consistently and 204 responses properly[(#8722)](https://github.com/prowler-cloud/prowler/pull/8722)
|
||||
|
||||
## [1.12.1] (Prowler v5.12.1)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
@@ -159,8 +159,7 @@ export const updateRole = async (formData: FormData, roleId: string) => {
|
||||
manage_providers: formData.get("manage_providers") === "true",
|
||||
manage_account: formData.get("manage_account") === "true",
|
||||
manage_scans: formData.get("manage_scans") === "true",
|
||||
// TODO: Add back when we have integrations ready
|
||||
// manage_integrations: formData.get("manage_integrations") === "true",
|
||||
manage_integrations: formData.get("manage_integrations") === "true",
|
||||
unlimited_visibility: formData.get("unlimited_visibility") === "true",
|
||||
},
|
||||
relationships: {},
|
||||
|
||||
@@ -147,7 +147,8 @@ export const AuthForm = ({
|
||||
} else {
|
||||
newUser.errors.forEach((error: ApiError) => {
|
||||
const errorMessage = error.detail;
|
||||
switch (error.source.pointer) {
|
||||
const pointer = error.source?.pointer;
|
||||
switch (pointer) {
|
||||
case "/data/attributes/name":
|
||||
form.setError("name", { type: "server", message: errorMessage });
|
||||
break;
|
||||
|
||||
@@ -53,7 +53,8 @@ export const SendInvitationForm = ({
|
||||
if (data?.errors && data.errors.length > 0) {
|
||||
data.errors.forEach((error: ApiError) => {
|
||||
const errorMessage = error.detail;
|
||||
switch (error.source.pointer) {
|
||||
const pointer = error.source?.pointer;
|
||||
switch (pointer) {
|
||||
case "/data/attributes/email":
|
||||
form.setError("email", {
|
||||
type: "server",
|
||||
|
||||
@@ -69,7 +69,8 @@ export const AddGroupForm = ({
|
||||
if (data?.errors && data.errors.length > 0) {
|
||||
data.errors.forEach((error: ApiError) => {
|
||||
const errorMessage = error.detail;
|
||||
switch (error.source.pointer) {
|
||||
const pointer = error.source?.pointer;
|
||||
switch (pointer) {
|
||||
case "/data/attributes/name":
|
||||
form.setError("name", {
|
||||
type: "server",
|
||||
|
||||
@@ -105,7 +105,8 @@ export const EditGroupForm = ({
|
||||
if (data?.errors && data.errors.length > 0) {
|
||||
data.errors.forEach((error: ApiError) => {
|
||||
const errorMessage = error.detail;
|
||||
switch (error.source.pointer) {
|
||||
const pointer = error.source?.pointer;
|
||||
switch (pointer) {
|
||||
case "/data/attributes/name":
|
||||
form.setError("name", {
|
||||
type: "server",
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
CustomInput,
|
||||
} from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { permissionFormFields } from "@/lib";
|
||||
import { getErrorMessage, permissionFormFields } from "@/lib";
|
||||
import { addRoleFormSchema, ApiError } from "@/types";
|
||||
|
||||
type FormValues = z.infer<typeof addRoleFormSchema>;
|
||||
@@ -113,7 +113,8 @@ export const AddRoleForm = ({
|
||||
if (data?.errors && data.errors.length > 0) {
|
||||
data.errors.forEach((error: ApiError) => {
|
||||
const errorMessage = error.detail;
|
||||
switch (error.source.pointer) {
|
||||
const pointer = error.source?.pointer;
|
||||
switch (pointer) {
|
||||
case "/data/attributes/name":
|
||||
form.setError("name", {
|
||||
type: "server",
|
||||
@@ -139,7 +140,7 @@ export const AddRoleForm = ({
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
description: "An unexpected error occurred. Please try again.",
|
||||
description: getErrorMessage(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
CustomInput,
|
||||
} from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { permissionFormFields } from "@/lib";
|
||||
import { getErrorMessage, permissionFormFields } from "@/lib";
|
||||
import { ApiError, editRoleFormSchema } from "@/types";
|
||||
|
||||
type FormValues = z.infer<typeof editRoleFormSchema>;
|
||||
@@ -133,7 +133,8 @@ export const EditRoleForm = ({
|
||||
if (data?.errors && data.errors.length > 0) {
|
||||
data.errors.forEach((error: ApiError) => {
|
||||
const errorMessage = error.detail;
|
||||
switch (error.source.pointer) {
|
||||
const pointer = error.source?.pointer;
|
||||
switch (pointer) {
|
||||
case "/data/attributes/name":
|
||||
form.setError("name", {
|
||||
type: "server",
|
||||
@@ -159,7 +160,7 @@ export const EditRoleForm = ({
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
description: "An unexpected error occurred. Please try again.",
|
||||
description: getErrorMessage(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,7 +19,8 @@ export const useFormServerErrors = <T extends Record<string, any>>(
|
||||
) => {
|
||||
errors.forEach((error: ApiError) => {
|
||||
const errorMessage = error.detail;
|
||||
const fieldName = errorMapping?.[error.source.pointer];
|
||||
const pointer = error.source?.pointer;
|
||||
const fieldName = pointer ? errorMapping?.[pointer] : undefined;
|
||||
|
||||
if (fieldName && fieldName in form.formState.defaultValues!) {
|
||||
form.setError(fieldName as any, {
|
||||
|
||||
+49
-9
@@ -348,10 +348,27 @@ export const handleApiResponse = async (
|
||||
parse = true,
|
||||
) => {
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => null);
|
||||
const errorDetail = errorData?.errors?.[0]?.detail;
|
||||
// Read error body safely; prefer JSON, fallback to plain text
|
||||
const rawErrorText = await response.text().catch(() => "");
|
||||
let errorData: any = null;
|
||||
try {
|
||||
errorData = rawErrorText ? JSON.parse(rawErrorText) : null;
|
||||
} catch {
|
||||
errorData = null;
|
||||
}
|
||||
|
||||
// Special handling for server errors (500+)
|
||||
const errorsArray = Array.isArray(errorData?.errors)
|
||||
? (errorData.errors as any[])
|
||||
: undefined;
|
||||
const errorDetail =
|
||||
errorsArray?.[0]?.detail ||
|
||||
errorData?.error ||
|
||||
errorData?.message ||
|
||||
(rawErrorText && rawErrorText.trim()) ||
|
||||
response.statusText ||
|
||||
"Oops! Something went wrong.";
|
||||
|
||||
//5XX errors
|
||||
if (response.status >= 500) {
|
||||
throw new Error(
|
||||
errorDetail ||
|
||||
@@ -359,14 +376,37 @@ export const handleApiResponse = async (
|
||||
);
|
||||
}
|
||||
|
||||
// Client errors (4xx)
|
||||
throw new Error(
|
||||
errorDetail ||
|
||||
`Request failed (${response.status}): ${response.statusText}`,
|
||||
);
|
||||
return errorsArray
|
||||
? { error: errorDetail, errors: errorsArray, status: response.status }
|
||||
: ({ error: errorDetail, status: response.status } as any);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
// Handle empty or no-content responses gracefully (e.g., 204, empty body)
|
||||
if (response.status === 204) {
|
||||
if (pathToRevalidate && pathToRevalidate !== "") {
|
||||
revalidatePath(pathToRevalidate);
|
||||
}
|
||||
return { success: true, status: response.status } as any;
|
||||
}
|
||||
|
||||
// Read raw text to determine if there's a body to parse
|
||||
const rawText = await response.text();
|
||||
const hasBody = rawText && rawText.trim().length > 0;
|
||||
|
||||
if (!hasBody) {
|
||||
if (pathToRevalidate && pathToRevalidate !== "") {
|
||||
revalidatePath(pathToRevalidate);
|
||||
}
|
||||
return { success: true, status: response.status } as any;
|
||||
}
|
||||
|
||||
let data: any;
|
||||
try {
|
||||
data = JSON.parse(rawText);
|
||||
} catch (e) {
|
||||
// If body isn't valid JSON, return as text payload
|
||||
data = { data: rawText };
|
||||
}
|
||||
|
||||
if (pathToRevalidate && pathToRevalidate !== "") {
|
||||
revalidatePath(pathToRevalidate);
|
||||
|
||||
Reference in New Issue
Block a user