fix(error): GraphQL-aligned GraphError type
Expand GraphError.path to accept strings and numbers for field names and list indexes, rather than strings only. Constrain isRawError to check this assumption. Add custom path formatter to use dot notation for fields and brackets for indexes.
This commit is contained in:
+30
-6
@@ -3,7 +3,8 @@
|
||||
*/
|
||||
export interface GraphError {
|
||||
message: string;
|
||||
path?: string[];
|
||||
/** GraphQL response path - field names and list indexes. */
|
||||
path?: ReadonlyArray<string | number>;
|
||||
}
|
||||
|
||||
/** RawError is an error that can be converted to a string by ErrorMessage */
|
||||
@@ -15,14 +16,22 @@ export type RawError = ErrorMessage | Error | string | GraphError[];
|
||||
* @returns true if the error is a RawError, false otherwise
|
||||
*/
|
||||
export const isRawError = (error: unknown): error is RawError => {
|
||||
const isGraphError = (entry: unknown): entry is GraphError => {
|
||||
if (!entry || typeof entry !== 'object') return false;
|
||||
const candidate = entry as { message?: unknown; path?: unknown };
|
||||
if (typeof candidate.message !== 'string') return false;
|
||||
if (candidate.path === undefined) return true;
|
||||
return (
|
||||
Array.isArray(candidate.path) &&
|
||||
candidate.path.every((p) => typeof p === 'string' || typeof p === 'number')
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
error instanceof ErrorMessage ||
|
||||
error instanceof Error ||
|
||||
typeof error === 'string' ||
|
||||
(Array.isArray(error) &&
|
||||
error.every(
|
||||
(e) => typeof e.message === 'string' && (e.path === undefined || Array.isArray(e.path))
|
||||
))
|
||||
(Array.isArray(error) && error.every(isGraphError))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -73,6 +82,21 @@ export const checkGraphResponse = (
|
||||
export class ErrorMessage {
|
||||
private _lines: string[] = [];
|
||||
|
||||
/** formats a GraphQL path using dot notation for fields and brackets for indexes */
|
||||
private static formatGraphPath(path: ReadonlyArray<string | number>): string {
|
||||
let formatted = '';
|
||||
for (const segment of path) {
|
||||
if (typeof segment === 'number') {
|
||||
formatted += `[${segment}]`;
|
||||
} else if (formatted.length === 0) {
|
||||
formatted = segment;
|
||||
} else {
|
||||
formatted += `.${segment}`;
|
||||
}
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always creates a new ErrorMessage instance, even if there are no errors.
|
||||
* @param errors The raw errors to convert and store, or null/undefined for no error.
|
||||
@@ -136,7 +160,7 @@ export class ErrorMessage {
|
||||
errorLines = raw.map((e) => {
|
||||
const messageString = e.message || 'Unknown error';
|
||||
if (e.path && e.path.length > 0) {
|
||||
return `"${messageString}" at ${e.path.join('.')}`;
|
||||
return `"${messageString}" at ${ErrorMessage.formatGraphPath(e.path)}`;
|
||||
}
|
||||
return messageString;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user