Skip to content

Commit

Permalink
Rely on $request->input() instead of manually json decoding request…
Browse files Browse the repository at this point in the history
… body (#12)
  • Loading branch information
pyrou authored Mar 20, 2023
1 parent 802c2c2 commit c01e90c
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 49 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/.phpunit.result.cache
/.php-cs-fixer.cache
/composer.lock
/.idea
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- Rely on `$request->input()` instead of manually json decoding request body https://github.com/laragraph/utils/pull/12

## v1.6.0

### Added
Expand Down
90 changes: 42 additions & 48 deletions src/RequestParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Safe\Exceptions\JsonException;

use function Safe\json_decode;

/**
* Follows https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md.
Expand All @@ -27,54 +28,20 @@ public function __construct()
/**
* Converts an incoming HTTP request to one or more OperationParams.
*
* @throws \GraphQL\Server\RequestError
* @throws \Laragraph\Utils\BadRequestGraphQLException
*
* @return \GraphQL\Server\OperationParams|array<int, \GraphQL\Server\OperationParams>
*/
public function parseRequest(Request $request)
{
$method = $request->getMethod();
$bodyParams = [];
/** @var array<string, mixed> $queryParams */
$bodyParams = 'POST' === $method
? $this->bodyParams($request)
: [];
/** @var array<string, mixed> $queryParams Laravel type is not precise enough */
$queryParams = $request->query();

if ('POST' === $method) {
/**
* Never null, since Symfony defaults to application/x-www-form-urlencoded.
*
* @var string $contentType
*/
$contentType = $request->header('Content-Type');

if (Str::startsWith($contentType, ['application/json', 'application/graphql+json'])) {
/** @var string $content */
$content = $request->getContent();
try {
$bodyParams = \Safe\json_decode($content, true);
} catch (JsonException $e) {
throw new BadRequestGraphQLException("Invalid JSON: {$e->getMessage()}");
}

if (! is_array($bodyParams)) {
throw new BadRequestGraphQLException(
'GraphQL Server expects JSON object or array, but got '
. Utils::printSafeJson($bodyParams)
);
}
} elseif (Str::startsWith($contentType, 'application/graphql')) {
/** @var string $content */
$content = $request->getContent();
$bodyParams = ['query' => $content];
} elseif (Str::startsWith($contentType, 'application/x-www-form-urlencoded')) {
/** @var array<string, mixed> $bodyParams */
$bodyParams = $request->post();
} elseif (Str::startsWith($contentType, 'multipart/form-data')) {
$bodyParams = $this->inlineFiles($request);
} else {
throw new BadRequestGraphQLException('Unexpected content type: ' . Utils::printSafeJson($contentType));
}
}

return $this->helper->parseRequestParams($method, $bodyParams, $queryParams);
}

Expand All @@ -88,24 +55,20 @@ protected function inlineFiles(Request $request): array
/** @var string|null $mapParam */
$mapParam = $request->post('map');
if (null === $mapParam) {
throw new BadRequestGraphQLException(
'Could not find a valid map, be sure to conform to GraphQL multipart request specification: https://github.com/jaydenseric/graphql-multipart-request-spec'
);
throw new BadRequestGraphQLException('Could not find a valid map, be sure to conform to GraphQL multipart request specification: https://github.com/jaydenseric/graphql-multipart-request-spec');
}

/** @var string|null $operationsParam */
$operationsParam = $request->post('operations');
if (null === $operationsParam) {
throw new BadRequestGraphQLException(
'Could not find valid operations, be sure to conform to GraphQL multipart request specification: https://github.com/jaydenseric/graphql-multipart-request-spec'
);
throw new BadRequestGraphQLException('Could not find valid operations, be sure to conform to GraphQL multipart request specification: https://github.com/jaydenseric/graphql-multipart-request-spec');
}

/** @var array<string, mixed>|array<int, array<string, mixed>> $operations */
$operations = \Safe\json_decode($operationsParam, true);
$operations = json_decode($operationsParam, true);

/** @var array<int|string, array<int, string>> $map */
$map = \Safe\json_decode($mapParam, true);
$map = json_decode($mapParam, true);

foreach ($map as $fileKey => $operationsPaths) {
/** @var array<string> $operationsPaths */
Expand All @@ -118,4 +81,35 @@ protected function inlineFiles(Request $request): array

return $operations;
}

/**
* Extracts the body parameters from the request.
*
* @return array<mixed>
*/
protected function bodyParams(Request $request): array
{
$contentType = $request->header('Content-Type');
assert(is_string($contentType), 'Never null, since Symfony defaults to application/x-www-form-urlencoded.');

if (Str::startsWith($contentType, 'multipart/form-data')) {
return $this->inlineFiles($request);
}

$bodyParams = $request->input();

if (is_array($bodyParams) && Arr::isAssoc($bodyParams)) {
return $bodyParams;
}

if (Str::startsWith($contentType, 'application/graphql')) {
return ['query' => $request->getContent()];
}

if ($request->isJson()) {
throw new BadRequestGraphQLException("GraphQL Server expects JSON object or array, but got: {$request->getContent()}.");
}

throw new BadRequestGraphQLException('Unexpected content type: ' . Utils::printSafeJson($contentType));
}
}
6 changes: 5 additions & 1 deletion tests/Unit/RequestParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public function testNonsensicalContentTypes(string $contentType): void
$parser = new RequestParser();

$this->expectException(BadRequestGraphQLException::class);
$this->expectExceptionMessage('Unexpected content type: "' . $contentType . '"');
$parser->parseRequest($request);
}

Expand All @@ -148,7 +149,6 @@ public function nonsensicalContentTypes(): iterable
yield ['application/foobar'];
yield ['application/josn'];
yield ['application/grapql'];
yield ['application/foo;charset=application/json'];
}

public function testNoQuery(): void
Expand All @@ -172,6 +172,8 @@ public function testInvalidJson(): void
$parser = new RequestParser();

$this->expectException(BadRequestGraphQLException::class);
$this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got: this is not valid json');

$parser->parseRequest($request);
}

Expand All @@ -187,6 +189,8 @@ public function testNonArrayJson(): void
$parser = new RequestParser();

$this->expectException(BadRequestGraphQLException::class);
$this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got: "this should be a map with query, variables, etc."');

$parser->parseRequest($request);
}

Expand Down

0 comments on commit c01e90c

Please sign in to comment.