Implement content negotiation
This commit is contained in:
parent
c1dc91c558
commit
8b37d47616
|
|
@ -8,7 +8,8 @@
|
||||||
"json-api-php/json-api": "^2.2",
|
"json-api-php/json-api": "^2.2",
|
||||||
"nyholm/psr7": "^1.3",
|
"nyholm/psr7": "^1.3",
|
||||||
"psr/http-message": "^1.0",
|
"psr/http-message": "^1.0",
|
||||||
"psr/http-server-handler": "^1.0"
|
"psr/http-server-handler": "^1.0",
|
||||||
|
"xynha/http-accept": "dev-master"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of tobyz/json-api-server.
|
|
||||||
*
|
|
||||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Tobyz\JsonApiServer\Http;
|
|
||||||
|
|
||||||
class MediaTypes
|
|
||||||
{
|
|
||||||
private $value;
|
|
||||||
|
|
||||||
public function __construct(string $value)
|
|
||||||
{
|
|
||||||
$this->value = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine whether the list contains the given type without modifications
|
|
||||||
*
|
|
||||||
* This is meant to ease implementation of JSON:API rules for content
|
|
||||||
* negotiation, which demand HTTP error responses e.g. when all of the
|
|
||||||
* JSON:API media types in the "Accept" header are modified with "media type
|
|
||||||
* parameters". Therefore, this method only returns true when the requested
|
|
||||||
* media type is contained without additional parameters (except for the
|
|
||||||
* weight parameter "q" and "Accept extension parameters").
|
|
||||||
*
|
|
||||||
* @param string $mediaType
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function containsExactly(string $mediaType): bool
|
|
||||||
{
|
|
||||||
$types = array_map('trim', explode(',', $this->value));
|
|
||||||
|
|
||||||
// Accept headers can contain multiple media types, so we need to check
|
|
||||||
// whether any of them matches.
|
|
||||||
foreach ($types as $type) {
|
|
||||||
$parts = array_map('trim', explode(';', $type));
|
|
||||||
|
|
||||||
// The actual media type needs to be an exact match
|
|
||||||
if (array_shift($parts) !== $mediaType) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The media type can optionally be followed by "media type
|
|
||||||
// parameters". Parameters after the "q" parameter are considered
|
|
||||||
// "Accept extension parameters", which we don't care about. Thus,
|
|
||||||
// we have an exact match if there are no parameters at all or if
|
|
||||||
// the first one is named "q".
|
|
||||||
// See https://tools.ietf.org/html/rfc7231#section-5.3.2.
|
|
||||||
if (empty($parts) || substr($parts[0], 0, 2) === 'q=') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
182
src/JsonApi.php
182
src/JsonApi.php
|
|
@ -26,6 +26,7 @@ use Tobyz\JsonApiServer\Exception\ResourceNotFoundException;
|
||||||
use Tobyz\JsonApiServer\Exception\UnsupportedMediaTypeException;
|
use Tobyz\JsonApiServer\Exception\UnsupportedMediaTypeException;
|
||||||
use Tobyz\JsonApiServer\Extension\Extension;
|
use Tobyz\JsonApiServer\Extension\Extension;
|
||||||
use Tobyz\JsonApiServer\Schema\Concerns\HasMeta;
|
use Tobyz\JsonApiServer\Schema\Concerns\HasMeta;
|
||||||
|
use Xynha\HttpAccept\AcceptParser;
|
||||||
|
|
||||||
final class JsonApi implements RequestHandlerInterface
|
final class JsonApi implements RequestHandlerInterface
|
||||||
{
|
{
|
||||||
|
|
@ -113,33 +114,52 @@ final class JsonApi implements RequestHandlerInterface
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request): Response
|
public function handle(Request $request): Response
|
||||||
{
|
{
|
||||||
// $this->validateRequest($request);
|
|
||||||
|
|
||||||
$this->validateQueryParameters($request);
|
$this->validateQueryParameters($request);
|
||||||
|
|
||||||
$context = new Context($this, $request);
|
$context = new Context($this, $request);
|
||||||
|
|
||||||
foreach ($this->extensions as $extension) {
|
$response = $this->runExtensions($context);
|
||||||
if ($response = $extension->handle($context)) {
|
|
||||||
return $response;
|
if (! $response) {
|
||||||
}
|
$response = $this->route($context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: apply Vary: Accept header to response
|
return $response->withAddedHeader('Vary', 'Accept');
|
||||||
|
}
|
||||||
|
|
||||||
$path = $this->stripBasePath(
|
private function runExtensions(Context $context): ?Response
|
||||||
$request->getUri()->getPath()
|
{
|
||||||
|
$request = $context->getRequest();
|
||||||
|
|
||||||
|
$contentTypeExtensionUris = $this->getContentTypeExtensionUris($request);
|
||||||
|
$acceptableExtensionUris = $this->getAcceptableExtensionUris($request);
|
||||||
|
|
||||||
|
$activeExtensions = array_intersect_key(
|
||||||
|
$this->extensions,
|
||||||
|
array_flip($contentTypeExtensionUris),
|
||||||
|
array_flip($acceptableExtensionUris)
|
||||||
);
|
);
|
||||||
|
|
||||||
$segments = explode('/', trim($path, '/'));
|
foreach ($activeExtensions as $extension) {
|
||||||
|
if ($response = $extension->handle($context)) {
|
||||||
|
return $response->withHeader('Content-Type', self::MEDIA_TYPE.'; ext='.$extension->uri());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function route(Context $context): Response
|
||||||
|
{
|
||||||
|
$segments = explode('/', trim($context->getPath(), '/'));
|
||||||
$resourceType = $this->getResourceType($segments[0]);
|
$resourceType = $this->getResourceType($segments[0]);
|
||||||
|
|
||||||
switch (count($segments)) {
|
switch (count($segments)) {
|
||||||
case 1:
|
case 1:
|
||||||
return $this->handleCollection($context, $resourceType);
|
return $this->routeCollection($context, $resourceType);
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
return $this->handleResource($context, $resourceType, $segments[1]);
|
return $this->routeResource($context, $resourceType, $segments[1]);
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
|
@ -165,63 +185,7 @@ final class JsonApi implements RequestHandlerInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function validateRequest(Request $request): void
|
private function routeCollection(Context $context, ResourceType $resourceType): Response
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
// split content type
|
|
||||||
// ensure type is json-api
|
|
||||||
// ensure no params other than ext/profile
|
|
||||||
// ensure no ext other than those supported
|
|
||||||
// return list of ext/profiles to apply
|
|
||||||
|
|
||||||
if ($accept = $request->getHeaderLine('Accept')) {
|
|
||||||
$types = array_map('trim', explode(',', $accept));
|
|
||||||
|
|
||||||
foreach ($types as $type) {
|
|
||||||
$parts = array_map('trim', explode(';', $type));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if accept present
|
|
||||||
// split accept, order by qvalue
|
|
||||||
// for each media type:
|
|
||||||
// if type is not json-api, continue
|
|
||||||
// if any params other than ext/profile, continue
|
|
||||||
// if any ext other than those supported, continue
|
|
||||||
// return list of ext/profiles to apply
|
|
||||||
// if none matching, Not Acceptable
|
|
||||||
}
|
|
||||||
|
|
||||||
// private function validateRequestContentType(Request $request): void
|
|
||||||
// {
|
|
||||||
// $header = $request->getHeaderLine('Content-Type');
|
|
||||||
//
|
|
||||||
// if ((new MediaTypes($header))->containsWithOptionalParameters(self::MEDIA_TYPE, ['ext'])) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// throw new UnsupportedMediaTypeException;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private function getAcceptedParameters(Request $request): array
|
|
||||||
// {
|
|
||||||
// $header = $request->getHeaderLine('Accept');
|
|
||||||
//
|
|
||||||
// if (empty($header)) {
|
|
||||||
// return [];
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// $mediaTypes = new MediaTypes($header);
|
|
||||||
//
|
|
||||||
// if ($parameters = $mediaTypes->get(self::MEDIA_TYPE, ['ext', 'profile'])) {
|
|
||||||
// return $parameters;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// throw new NotAcceptableException;
|
|
||||||
// }
|
|
||||||
|
|
||||||
private function handleCollection(Context $context, ResourceType $resourceType): Response
|
|
||||||
{
|
{
|
||||||
switch ($context->getRequest()->getMethod()) {
|
switch ($context->getRequest()->getMethod()) {
|
||||||
case 'GET':
|
case 'GET':
|
||||||
|
|
@ -235,9 +199,9 @@ final class JsonApi implements RequestHandlerInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleResource(Context $context, ResourceType $resourceType, string $id): Response
|
private function routeResource(Context $context, ResourceType $resourceType, string $resourceId): Response
|
||||||
{
|
{
|
||||||
$model = $this->findResource($resourceType, $id, $context);
|
$model = $this->findResource($resourceType, $resourceId, $context);
|
||||||
|
|
||||||
switch ($context->getRequest()->getMethod()) {
|
switch ($context->getRequest()->getMethod()) {
|
||||||
case 'PATCH':
|
case 'PATCH':
|
||||||
|
|
@ -254,6 +218,82 @@ final class JsonApi implements RequestHandlerInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getContentTypeExtensionUris(Request $request): array
|
||||||
|
{
|
||||||
|
if (! $contentType = $request->getHeaderLine('Content-Type')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$mediaList = (new AcceptParser())->parse($contentType);
|
||||||
|
|
||||||
|
if ($mediaList->count() > 1) {
|
||||||
|
throw new UnsupportedMediaTypeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$mediaType = $mediaList->preferredMedia(0);
|
||||||
|
|
||||||
|
if ($mediaType->mimetype() !== JsonApi::MEDIA_TYPE) {
|
||||||
|
throw new UnsupportedMediaTypeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameters = $this->parseParameters($mediaType->parameters());
|
||||||
|
|
||||||
|
if (! empty(array_diff(array_keys($parameters), ['ext', 'profile']))) {
|
||||||
|
throw new UnsupportedMediaTypeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$extensionUris = isset($parameters['ext']) ? explode(' ', $parameters['ext']) : [];
|
||||||
|
|
||||||
|
if (! empty(array_diff($extensionUris, array_keys($this->extensions)))) {
|
||||||
|
throw new UnsupportedMediaTypeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $extensionUris;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getAcceptableExtensionUris(Request $request): array
|
||||||
|
{
|
||||||
|
if (! $accept = $request->getHeaderLine('Accept')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$mediaList = (new AcceptParser())->parse($accept);
|
||||||
|
$count = $mediaList->count();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $count; $i++) {
|
||||||
|
$mediaType = $mediaList->preferredMedia($i);
|
||||||
|
|
||||||
|
if (! in_array($mediaType->mimetype(), [JsonApi::MEDIA_TYPE, '*/*'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameters = $this->parseParameters($mediaType->parameters());
|
||||||
|
|
||||||
|
if (! empty(array_diff(array_keys($parameters), ['ext', 'profile']))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extensionUris = isset($parameters['ext']) ? explode(' ', $parameters['ext']) : [];
|
||||||
|
|
||||||
|
if (! empty(array_diff($extensionUris, array_keys($this->extensions)))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $extensionUris;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotAcceptableException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseParameters(array $parameters): array
|
||||||
|
{
|
||||||
|
return array_reduce($parameters, function ($a, $v) {
|
||||||
|
$parts = explode('=', $v, 2);
|
||||||
|
$a[$parts[0]] = trim($parts[1], '"');
|
||||||
|
return $a;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an exception into a JSON:API error document response.
|
* Convert an exception into a JSON:API error document response.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ use Tobyz\JsonApiServer\Schema\Field;
|
||||||
function json_api_response($document, int $status = 200): Response
|
function json_api_response($document, int $status = 200): Response
|
||||||
{
|
{
|
||||||
return (new Response($status))
|
return (new Response($status))
|
||||||
->withHeader('content-type', JsonApi::MEDIA_TYPE)
|
->withHeader('Content-Type', JsonApi::MEDIA_TYPE)
|
||||||
->withBody(Stream::create(json_encode($document, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES)));
|
->withBody(Stream::create(json_encode($document, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,14 @@
|
||||||
|
|
||||||
namespace Tobyz\Tests\JsonApiServer\specification;
|
namespace Tobyz\Tests\JsonApiServer\specification;
|
||||||
|
|
||||||
use Tobyz\JsonApiServer\JsonApi;
|
|
||||||
use Tobyz\JsonApiServer\Exception\NotAcceptableException;
|
use Tobyz\JsonApiServer\Exception\NotAcceptableException;
|
||||||
use Tobyz\JsonApiServer\Exception\UnsupportedMediaTypeException;
|
use Tobyz\JsonApiServer\Exception\UnsupportedMediaTypeException;
|
||||||
use Tobyz\JsonApiServer\Schema\Type;
|
use Tobyz\JsonApiServer\JsonApi;
|
||||||
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
||||||
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://jsonapi.org/format/#content-negotiation
|
* @see https://jsonapi.org/format/1.1/#content-negotiation
|
||||||
*/
|
*/
|
||||||
class ContentNegotiationTest extends AbstractTestCase
|
class ContentNegotiationTest extends AbstractTestCase
|
||||||
{
|
{
|
||||||
|
|
@ -31,9 +30,7 @@ class ContentNegotiationTest extends AbstractTestCase
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
$this->api = new JsonApi('http://example.com');
|
$this->api = new JsonApi('http://example.com');
|
||||||
$this->api->resourceType('users', new MockAdapter(), function (Type $type) {
|
$this->api->resourceType('users', new MockAdapter());
|
||||||
// no fields
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_json_api_content_type_is_returned()
|
public function test_json_api_content_type_is_returned()
|
||||||
|
|
@ -48,36 +45,36 @@ class ContentNegotiationTest extends AbstractTestCase
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_error_when_request_content_type_has_parameters()
|
public function test_success_when_request_content_type_contains_profile()
|
||||||
|
{
|
||||||
|
$response = $this->api->handle(
|
||||||
|
$this->buildRequest('GET', '/users/1')
|
||||||
|
->withHeader('Accept', 'application/vnd.api+json; profile="http://example.com/profile"')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_error_when_request_content_type_contains_unknown_parameter()
|
||||||
{
|
{
|
||||||
$request = $this->buildRequest('PATCH', '/users/1')
|
$request = $this->buildRequest('PATCH', '/users/1')
|
||||||
->withHeader('Content-Type', 'application/vnd.api+json;profile="http://example.com/last-modified"');
|
->withHeader('Content-Type', 'application/vnd.api+json; unknown="parameter"');
|
||||||
|
|
||||||
$this->expectException(UnsupportedMediaTypeException::class);
|
$this->expectException(UnsupportedMediaTypeException::class);
|
||||||
|
|
||||||
$this->api->handle($request);
|
$this->api->handle($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_error_when_all_accepts_have_parameters()
|
public function test_error_when_request_content_type_contains_unsupported_extension()
|
||||||
{
|
{
|
||||||
$request = $this->buildRequest('GET', '/users/1')
|
$request = $this->buildRequest('PATCH', '/users/1')
|
||||||
->withHeader('Accept', 'application/vnd.api+json;profile="http://example.com/last-modified", application/vnd.api+json;profile="http://example.com/versioning"');
|
->withHeader('Content-Type', 'application/vnd.api+json; ext="http://example.com/extension"');
|
||||||
|
|
||||||
$this->expectException(NotAcceptableException::class);
|
$this->expectException(UnsupportedMediaTypeException::class);
|
||||||
|
|
||||||
$this->api->handle($request);
|
$this->api->handle($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_success_when_only_some_accepts_have_parameters()
|
|
||||||
{
|
|
||||||
$response = $this->api->handle(
|
|
||||||
$this->buildRequest('GET', '/users/1')
|
|
||||||
->withHeader('Accept', 'application/vnd.api+json;profile="http://example.com/last-modified", application/vnd.api+json')
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_success_when_accepts_wildcard()
|
public function test_success_when_accepts_wildcard()
|
||||||
{
|
{
|
||||||
$response = $this->api->handle(
|
$response = $this->api->handle(
|
||||||
|
|
@ -87,4 +84,33 @@ class ContentNegotiationTest extends AbstractTestCase
|
||||||
|
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_error_when_all_accepts_have_unknown_parameters()
|
||||||
|
{
|
||||||
|
$request = $this->buildRequest('GET', '/users/1')
|
||||||
|
->withHeader('Accept', 'application/vnd.api+json; unknown="parameter", application/vnd.api+json; unknown="parameter2"');
|
||||||
|
|
||||||
|
$this->expectException(NotAcceptableException::class);
|
||||||
|
|
||||||
|
$this->api->handle($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_success_when_only_some_accepts_have_parameters()
|
||||||
|
{
|
||||||
|
$response = $this->api->handle(
|
||||||
|
$this->buildRequest('GET', '/users/1')
|
||||||
|
->withHeader('Accept', 'application/vnd.api+json; unknown="parameter", application/vnd.api+json')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_responds_with_vary_header()
|
||||||
|
{
|
||||||
|
$response = $this->api->handle(
|
||||||
|
$this->buildRequest('GET', '/users/1')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals('Accept', $response->getHeaderLine('vary'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of JSON-API.
|
|
||||||
*
|
|
||||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Tobyz\Tests\JsonApiServer\unit\Http;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Tobyz\JsonApiServer\Http\MediaTypes;
|
|
||||||
|
|
||||||
class MediaTypesTest extends TestCase
|
|
||||||
{
|
|
||||||
public function test_contains_on_exact_match()
|
|
||||||
{
|
|
||||||
$header = new MediaTypes('application/json');
|
|
||||||
|
|
||||||
$this->assertTrue(
|
|
||||||
$header->containsExactly('application/json')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_contains_does_not_match_with_extra_parameters()
|
|
||||||
{
|
|
||||||
$header = new MediaTypes('application/json; profile=foo');
|
|
||||||
|
|
||||||
$this->assertFalse(
|
|
||||||
$header->containsExactly('application/json')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_contains_matches_when_only_weight_is_provided()
|
|
||||||
{
|
|
||||||
$header = new MediaTypes('application/json; q=0.8');
|
|
||||||
|
|
||||||
$this->assertTrue(
|
|
||||||
$header->containsExactly('application/json')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_contains_does_not_match_with_extra_parameters_before_weight()
|
|
||||||
{
|
|
||||||
$header = new MediaTypes('application/json; profile=foo; q=0.8');
|
|
||||||
|
|
||||||
$this->assertFalse(
|
|
||||||
$header->containsExactly('application/json')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_contains_matches_with_extra_parameters_after_weight()
|
|
||||||
{
|
|
||||||
$header = new MediaTypes('application/json; q=0.8; profile=foo');
|
|
||||||
|
|
||||||
$this->assertTrue(
|
|
||||||
$header->containsExactly('application/json')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_contains_matches_when_one_of_multiple_media_types_is_valid()
|
|
||||||
{
|
|
||||||
$header = new MediaTypes('application/json; profile=foo, application/json; q=0.6');
|
|
||||||
|
|
||||||
$this->assertTrue(
|
|
||||||
$header->containsExactly('application/json')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue