Fix scalar throwing 500 (#9465)

## Context
Scalar validation/serde throws 500 instead of 400.

## Test
Before
<img width="785" alt="Screenshot 2025-01-08 at 19 17 14"
src="https://github.com/user-attachments/assets/61fadbd0-ce62-49be-bda6-c7047feb8725"
/>

After
<img width="831" alt="Screenshot 2025-01-08 at 19 16 05"
src="https://github.com/user-attachments/assets/d7038f0c-ea7a-4016-90d8-d2ce35c5763b"
/>
This commit is contained in:
Weiko
2025-01-08 19:24:14 +01:00
committed by GitHub
parent 09513b66c4
commit 9bfcd1573c
5 changed files with 24 additions and 12 deletions

View File

@ -1,25 +1,27 @@
import { GraphQLScalarType, Kind } from 'graphql'; import { GraphQLScalarType, Kind } from 'graphql';
import { ValidationError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
export const CursorScalarType = new GraphQLScalarType({ export const CursorScalarType = new GraphQLScalarType({
name: 'Cursor', name: 'Cursor',
description: 'A custom scalar that represents a cursor for pagination', description: 'A custom scalar that represents a cursor for pagination',
serialize(value) { serialize(value) {
if (typeof value !== 'string') { if (typeof value !== 'string') {
throw new Error('Cursor must be a string'); throw new ValidationError('Cursor must be a string');
} }
return value; return value;
}, },
parseValue(value) { parseValue(value) {
if (typeof value !== 'string') { if (typeof value !== 'string') {
throw new Error('Cursor must be a string'); throw new ValidationError('Cursor must be a string');
} }
return value; return value;
}, },
parseLiteral(ast) { parseLiteral(ast) {
if (ast.kind !== Kind.STRING) { if (ast.kind !== Kind.STRING) {
throw new Error('Cursor must be a string'); throw new ValidationError('Cursor must be a string');
} }
return ast.value; return ast.value;

View File

@ -1,5 +1,7 @@
import { GraphQLScalarType, Kind } from 'graphql'; import { GraphQLScalarType, Kind } from 'graphql';
import { ValidationError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
type PositionType = 'first' | 'last' | number; type PositionType = 'first' | 'last' | number;
const isValidStringPosition = (value: string): boolean => const isValidStringPosition = (value: string): boolean =>
@ -13,7 +15,9 @@ const checkPosition = (value: any): PositionType => {
return value; return value;
} }
throw new Error('Invalid position found'); throw new ValidationError(
`Invalid position value: '${value}'. Position must be 'first', 'last', or a number`,
);
}; };
export const PositionScalarType = new GraphQLScalarType({ export const PositionScalarType = new GraphQLScalarType({
@ -34,6 +38,6 @@ export const PositionScalarType = new GraphQLScalarType({
return parseFloat(ast.value); return parseFloat(ast.value);
} }
throw new Error('Invalid position found'); throw new ValidationError('Invalid position value');
}, },
}); });

View File

@ -3,6 +3,8 @@ import { Maybe } from 'graphql-yoga';
import { ObjMap } from 'graphql/jsutils/ObjMap'; import { ObjMap } from 'graphql/jsutils/ObjMap';
import { ASTNode, Kind, ValueNode } from 'graphql/language'; import { ASTNode, Kind, ValueNode } from 'graphql/language';
import { ValidationError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
const parseLiteral = ( const parseLiteral = (
ast: ValueNode, ast: ValueNode,
variables?: Maybe<ObjMap<unknown>>, variables?: Maybe<ObjMap<unknown>>,
@ -25,7 +27,7 @@ const parseLiteral = (
case Kind.VARIABLE: case Kind.VARIABLE:
return variables ? variables[ast.name.value] : undefined; return variables ? variables[ast.name.value] : undefined;
default: default:
throw new TypeError( throw new ValidationError(
`JSONStringify cannot represent value: ${JSON.stringify(ast)}`, `JSONStringify cannot represent value: ${JSON.stringify(ast)}`,
); );
} }
@ -54,7 +56,7 @@ const parseJSON = (value: string): object => {
try { try {
return JSON.parse(value); return JSON.parse(value);
} catch (e) { } catch (e) {
throw new TypeError(`Value is not valid JSON: ${value}`); throw new ValidationError(`Value is not valid JSON: ${value}`);
} }
}; };

View File

@ -1,6 +1,8 @@
import { GraphQLScalarType } from 'graphql'; import { GraphQLScalarType } from 'graphql';
import { IntValueNode, Kind } from 'graphql/language'; import { IntValueNode, Kind } from 'graphql/language';
import { ValidationError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
export const TimeScalarType = new GraphQLScalarType({ export const TimeScalarType = new GraphQLScalarType({
name: 'Time', name: 'Time',
description: 'Time custom scalar type', description: 'Time custom scalar type',
@ -17,8 +19,8 @@ export const TimeScalarType = new GraphQLScalarType({
if (typeof intAst.value === 'number') { if (typeof intAst.value === 'number') {
return new Date(intAst.value); return new Date(intAst.value);
} }
throw new Error(`Invalid timestamp value: ${ast.value}`); throw new ValidationError(`Invalid timestamp value: ${ast.value}`);
} }
throw new Error(`Invalid AST kind: ${ast.kind}`); throw new ValidationError(`Invalid AST kind: ${ast.kind}`);
}, },
}); });

View File

@ -1,12 +1,14 @@
import { GraphQLScalarType, Kind } from 'graphql'; import { GraphQLScalarType, Kind } from 'graphql';
import { validate as uuidValidate } from 'uuid'; import { validate as uuidValidate } from 'uuid';
import { ValidationError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
const checkUUID = (value: any): string => { const checkUUID = (value: any): string => {
if (typeof value !== 'string') { if (typeof value !== 'string') {
throw new Error('UUID must be a string'); throw new ValidationError('UUID must be a string');
} }
if (!uuidValidate(value)) { if (!uuidValidate(value)) {
throw new Error('Invalid UUID'); throw new ValidationError('Invalid UUID');
} }
return value; return value;
@ -19,7 +21,7 @@ export const UUIDScalarType = new GraphQLScalarType({
parseValue: checkUUID, parseValue: checkUUID,
parseLiteral(ast): string { parseLiteral(ast): string {
if (ast.kind !== Kind.STRING) { if (ast.kind !== Kind.STRING) {
throw new Error('UUID must be a string'); throw new ValidationError('UUID must be a string');
} }
return ast.value; return ast.value;