refactor(core): remove unsafe type assertions in error utils (Phase 1.1) (#19750)

This commit is contained in:
matt korwel
2026-02-20 19:00:57 -06:00
committed by GitHub
parent 5d98ed5820
commit 09218572d0
2 changed files with 86 additions and 16 deletions

View File

@@ -10,6 +10,16 @@ interface GaxiosError {
};
}
function isGaxiosError(error: unknown): error is GaxiosError {
return (
typeof error === 'object' &&
error !== null &&
'response' in error &&
typeof (error as { response: unknown }).response === 'object' &&
(error as { response: unknown }).response !== null
);
}
export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
return error instanceof Error && 'code' in error;
}
@@ -105,11 +115,41 @@ interface ResponseData {
};
}
function isResponseData(data: unknown): data is ResponseData {
if (typeof data !== 'object' || data === null) {
return false;
}
const candidate = data as ResponseData;
if (!('error' in candidate)) {
return false;
}
const error = candidate.error;
if (typeof error !== 'object' || error === null) {
return false; // error property exists but is not an object (could be undefined, but we checked 'in')
}
// Optional properties check
if (
'code' in error &&
typeof error.code !== 'number' &&
error.code !== undefined
) {
return false;
}
if (
'message' in error &&
typeof error.message !== 'string' &&
error.message !== undefined
) {
return false;
}
return true;
}
export function toFriendlyError(error: unknown): unknown {
if (error && typeof error === 'object' && 'response' in error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const gaxiosError = error as GaxiosError;
const data = parseResponseData(gaxiosError);
if (isGaxiosError(error)) {
const data = parseResponseData(error);
if (data && data.error && data.error.message && data.error.code) {
switch (data.error.code) {
case 400:
@@ -129,17 +169,20 @@ export function toFriendlyError(error: unknown): unknown {
}
function parseResponseData(error: GaxiosError): ResponseData | undefined {
let data = error.response?.data;
// Inexplicably, Gaxios sometimes doesn't JSONify the response data.
if (typeof error.response?.data === 'string') {
if (typeof data === 'string') {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return JSON.parse(error.response?.data) as ResponseData;
data = JSON.parse(data);
} catch {
return undefined;
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return error.response?.data as ResponseData | undefined;
if (isResponseData(data)) {
return data;
}
return undefined;
}
/**
@@ -152,8 +195,15 @@ function parseResponseData(error: GaxiosError): ResponseData | undefined {
export function isAuthenticationError(error: unknown): boolean {
// Check for MCP SDK errors with code property
// (SseError and StreamableHTTPError both have numeric 'code' property)
if (error && typeof error === 'object' && 'code' in error) {
const errorCode = (error as { code: unknown }).code;
if (
error &&
typeof error === 'object' &&
'code' in error &&
typeof (error as { code: unknown }).code === 'number'
) {
// Safe access after check
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const errorCode = (error as { code: number }).code;
if (errorCode === 401) {
return true;
}

View File

@@ -207,9 +207,13 @@ export function parseGoogleApiError(error: unknown): GoogleApiError | null {
detailObj['@type'] = detailObj[typeKey];
delete detailObj[typeKey];
}
// We can just cast it; the consumer will have to switch on @type
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
details.push(detailObj as unknown as GoogleApiErrorDetail);
// Basic structural check before casting.
// Since the proto definitions are loose, we primarily rely on @type presence.
if (typeof detailObj['@type'] === 'string') {
// We can just cast it; the consumer will have to switch on @type
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
details.push(detailObj as unknown as GoogleApiErrorDetail);
}
}
}
}
@@ -225,6 +229,16 @@ export function parseGoogleApiError(error: unknown): GoogleApiError | null {
return null;
}
function isErrorShape(obj: unknown): obj is ErrorShape {
return (
typeof obj === 'object' &&
obj !== null &&
(('message' in obj &&
typeof (obj as { message: unknown }).message === 'string') ||
('code' in obj && typeof (obj as { code: unknown }).code === 'number'))
);
}
function fromGaxiosError(errorObj: object): ErrorShape | undefined {
const gaxiosError = errorObj as {
response?: {
@@ -260,7 +274,10 @@ function fromGaxiosError(errorObj: object): ErrorShape | undefined {
if (typeof data === 'object' && data !== null) {
if ('error' in data) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
outerError = (data as { error: ErrorShape }).error;
const potentialError = (data as { error: unknown }).error;
if (isErrorShape(potentialError)) {
outerError = potentialError;
}
}
}
}
@@ -320,7 +337,10 @@ function fromApiError(errorObj: object): ErrorShape | undefined {
if (typeof data === 'object' && data !== null) {
if ('error' in data) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
outerError = (data as { error: ErrorShape }).error;
const potentialError = (data as { error: unknown }).error;
if (isErrorShape(potentialError)) {
outerError = potentialError;
}
}
}
}