mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-06 13:10:40 +01:00
* fix(api): guard ErrorResponseHandlerV2 against non-envelope error bodies
The handler assumed every non-2xx response carried the V2 error envelope
and read response.data.error.code directly. During a deployment the
gateway returns a 5xx with an HTML/empty body, so response.data.error is
undefined and the handler threw its own TypeError, masking the real
failure and crashing the caller.
Type the inbound body as unknown and narrow it with an isErrorV2Resp type
guard; when the body isn't a V2 envelope, synthesize an APIError from the
HTTP status instead of throwing. Callers already pass
AxiosError<ErrorV2Resp>, which stays assignable to AxiosError<unknown>.
* refactor(api): keep ErrorV2Resp signature, launder response.data via unknown
Restore the AxiosError<ErrorV2Resp> signature so it documents that this is
the V2 error handler, and confine the runtime uncertainty to the one spot
that matters: launder response.data through a local unknown binding before
narrowing it with the isErrorV2Resp guard.
* fix(api): use UPSTREAM_UNAVAILABLE code for non-envelope error bodies
Address review: when the response body isn't a V2 envelope (e.g. a gateway
5xx during a deploy), throw a stable UPSTREAM_UNAVAILABLE code instead of
the stringified HTTP status, and trim the guard comment.
* fix(api): use UPSTREAM_UNAVAILABLE fallback code in convertToApiError
When the response body carries no error code, fall back to a stable
UPSTREAM_UNAVAILABLE code instead of a stringified HTTP status, mirroring
the ErrorResponseHandlerV2 fallback.
* test(dashboard-v2): update panelStatus fallback code to UPSTREAM_UNAVAILABLE
convertToApiError now returns UPSTREAM_UNAVAILABLE (not a stringified
status) when the response carries no error code; update the panelStatus
fallback assertion to match.
* fix(api): guard generated-API handler + strengthen V2 handler tests
- Guard the deprecated ErrorResponseHandlerForGeneratedAPIs against a
non-envelope response body (gateway 5xx with HTML/empty body), mirroring
ErrorResponseHandlerV2 — falls back to UPSTREAM_UNAVAILABLE instead of
throwing on response.data.error.code.
- Parametrize the V2 handler tests into an { error, expected } table and
assert the sub-error 'errors' messages, which several UI surfaces rely on.
* revert(api): drop generated-API handler guard from this PR
The deprecated ErrorResponseHandlerForGeneratedAPIs guard broke toAPIError's
defaultMessage fallback (which relied on the handler crashing), regressing the
error UX in ServiceAccount/Roles screens. Moved to a stacked PR + tracked in
engineering-pod#5761. Keeps this PR scoped to ErrorResponseHandlerV2 +
convertToApiError, and the strengthened V2 handler tests remain.
* fix(api): guard deprecated generated-API handler against non-envelope bodies (#12228)
115 lines
3.1 KiB
TypeScript
115 lines
3.1 KiB
TypeScript
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
|
import { AxiosError } from 'axios';
|
|
import APIError from 'types/api/error';
|
|
|
|
// The wire shape these handlers can actually rely on. The generated
|
|
// RenderErrorResponseDTO marks code/message/url/errors as required, but the
|
|
// server omits any of them even on valid errors (e.g. a 400 with just a
|
|
// message), so a present `error` object is all the guard can guarantee.
|
|
type ErrorEnvelope = {
|
|
error: {
|
|
code?: string;
|
|
message?: string;
|
|
url?: string;
|
|
errors?: { message?: string }[];
|
|
};
|
|
};
|
|
|
|
function isErrorEnvelope(data: unknown): data is ErrorEnvelope {
|
|
return (
|
|
typeof data === 'object' &&
|
|
data !== null &&
|
|
'error' in data &&
|
|
typeof (data as ErrorEnvelope).error === 'object' &&
|
|
(data as ErrorEnvelope).error !== null
|
|
);
|
|
}
|
|
|
|
// @deprecated Use convertToApiError instead
|
|
export function ErrorResponseHandlerForGeneratedAPIs(
|
|
error: AxiosError<RenderErrorResponseDTO>,
|
|
): never {
|
|
const { response, request } = error;
|
|
// The request was made and the server responded with a status code
|
|
// that falls out of the range of 2xx
|
|
if (response) {
|
|
// The body isn't guaranteed to be an error envelope — e.g. a gateway 5xx
|
|
// with an HTML/empty body during a deploy. Verify the shape before reading
|
|
// it; otherwise synthesize a consistent error from the status.
|
|
const data: unknown = response.data;
|
|
if (isErrorEnvelope(data)) {
|
|
const { code, message, url, errors } = data.error;
|
|
throw new APIError({
|
|
httpStatusCode: response.status || 500,
|
|
error: {
|
|
code: code ?? '',
|
|
message: message ?? '',
|
|
url: url ?? '',
|
|
errors: (errors ?? []).map((e) => ({ message: e.message ?? '' })),
|
|
},
|
|
});
|
|
}
|
|
throw new APIError({
|
|
httpStatusCode: response.status || 500,
|
|
error: {
|
|
code: 'UPSTREAM_UNAVAILABLE',
|
|
message: error.message || 'Something went wrong',
|
|
url: '',
|
|
errors: [],
|
|
},
|
|
});
|
|
}
|
|
// The request was made but no response was received
|
|
if (request) {
|
|
throw new APIError({
|
|
httpStatusCode: error.status || 500,
|
|
error: {
|
|
code: error.code || error.name,
|
|
message: error.message,
|
|
url: '',
|
|
errors: [],
|
|
},
|
|
});
|
|
}
|
|
|
|
// Something happened in setting up the request that triggered an Error
|
|
throw new APIError({
|
|
httpStatusCode: error.status || 500,
|
|
error: {
|
|
code: error.name,
|
|
message: error.message,
|
|
url: '',
|
|
errors: [],
|
|
},
|
|
});
|
|
}
|
|
|
|
// convertToApiError converts an AxiosError from generated API
|
|
// hooks into an APIError.
|
|
export function convertToApiError(
|
|
error: AxiosError<RenderErrorResponseDTO> | null,
|
|
): APIError | undefined {
|
|
if (!error) {
|
|
return undefined;
|
|
}
|
|
|
|
const response = error.response;
|
|
const errorData = response?.data?.error;
|
|
|
|
return new APIError({
|
|
httpStatusCode: response?.status || error.status || 500,
|
|
error: {
|
|
code: errorData?.code || 'UPSTREAM_UNAVAILABLE',
|
|
message:
|
|
errorData?.message ||
|
|
response?.statusText ||
|
|
error.message ||
|
|
'Something went wrong',
|
|
url: errorData?.url ?? '',
|
|
errors: (errorData?.errors ?? []).map((e) => ({
|
|
message: e.message ?? '',
|
|
})),
|
|
},
|
|
});
|
|
}
|