diff --git a/src/Controller/GraphQLiteController.php b/src/Controller/GraphQLiteController.php index aaa91fa..f5fe84c 100644 --- a/src/Controller/GraphQLiteController.php +++ b/src/Controller/GraphQLiteController.php @@ -1,20 +1,21 @@ getMethod()) === 'POST' && empty($psr7Request->getParsedBody())) { $content = $psr7Request->getBody()->getContents(); - $parsedBody = json_decode($content, true); - if (json_last_error() !== JSON_ERROR_NONE) { - throw new \RuntimeException('Invalid JSON received in POST body: '.json_last_error_msg()); + try { + $parsedBody = json_decode( + json: $content, + associative: true, + flags: \JSON_THROW_ON_ERROR + ); + } catch (\JsonException $e) { + return $this->invalidJsonBodyResponse($e); } - if (!is_array($parsedBody)){ - throw new \RuntimeException('Expecting associative array from request, got ' . gettype($parsedBody)); + + if (!is_array($parsedBody)) { + return $this->invalidRequestBodyExpectedAssociativeResponse($parsedBody); } + $psr7Request = $psr7Request->withParsedBody($parsedBody); } @@ -125,4 +133,51 @@ private function handlePsr7Request(ServerRequestInterface $request, Request $sym throw new RuntimeException('Only SyncPromiseAdapter is supported'); } + + private function invalidJsonBodyResponse(\JsonException $e): JsonResponse + { + $jsonException = JsonException::create( + reason: $e->getMessage(), + code: Response::HTTP_UNSUPPORTED_MEDIA_TYPE, + previous: $e, + ); + $result = new ExecutionResult( + null, + [ + new Error( + 'Invalid JSON.', + previous: $jsonException, + extensions: $jsonException->getExtensions(), + ), + ] + ); + + return new JsonResponse( + $result->toArray($this->debug), + $this->httpCodeDecider->decideHttpStatusCode($result) + ); + } + + private function invalidRequestBodyExpectedAssociativeResponse(mixed $parsedBody): JsonResponse + { + $jsonException = JsonException::create( + reason: 'Expecting associative array from request, got ' . gettype($parsedBody), + code: Response::HTTP_UNPROCESSABLE_ENTITY, + ); + $result = new ExecutionResult( + null, + [ + new Error( + 'Invalid JSON.', + previous: $jsonException, + extensions: $jsonException->getExtensions(), + ), + ] + ); + + return new JsonResponse( + $result->toArray($this->debug), + $this->httpCodeDecider->decideHttpStatusCode($result) + ); + } } diff --git a/src/Exceptions/JsonException.php b/src/Exceptions/JsonException.php new file mode 100644 index 0000000..a474c8d --- /dev/null +++ b/src/Exceptions/JsonException.php @@ -0,0 +1,19 @@ + $reason] + ); + } +} diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php index cdc90ed..07848bc 100644 --- a/tests/FunctionalTest.php +++ b/tests/FunctionalTest.php @@ -104,6 +104,28 @@ public function testErrors(): void $kernel = new GraphQLiteTestingKernel(); $kernel->boot(); + $request = Request::create('/graphql', 'POST', [], [], [], ['CONTENT_TYPE' => 'application/json'], '{"query":"{ invalidJsonSyntax }"'); + + $response = $kernel->handle($request); + + $this->assertSame(415, $response->getStatusCode()); + + $result = json_decode($response->getContent(), true); + + $this->assertSame('Invalid JSON.', $result['errors'][0]['message']); + $this->assertSame('Syntax error', $result['errors'][0]['extensions']['reason']); + + $request = Request::create('/graphql', 'POST', [], [], [], ['CONTENT_TYPE' => 'application/json'], '"Unexpected Json Content"'); + + $response = $kernel->handle($request); + + $this->assertSame(422, $response->getStatusCode()); + + $result = json_decode($response->getContent(), true); + + $this->assertSame('Invalid JSON.', $result['errors'][0]['message']); + $this->assertSame('Expecting associative array from request, got string', $result['errors'][0]['extensions']['reason']); + $request = Request::create('/graphql', 'GET', ['query' => ' { notExists