Skip to content

Parsing API Parameters

EK edited this page Mar 15, 2016 · 9 revisions

JSON API clients should send data to servers

  • As URL parameters
  • In HTTP headers
  • In HTTP body

The server should parse those data, check them and act accordingly. This package provides a few helpers that simplify these tasks.

As an interface for HTTP Requests PSR-7 is used (\Psr\Http\Message\ServerRequestInterface in particularly).

If your application does not supports PSR-7 you can use a wrapper \Neomerx\JsonApi\Http\Request which requires only a couple of methods to be implemented.

$nonPsr7request = ...;
$psr7request    = new Request(function ($name) use ($nonPsr7request) {
    return $nonPsr7request->getHeader($name);
}, function () use ($nonPsr7request) {
    return $nonPsr7request->getQueryParams();
});

Note: \Neomerx\JsonApi\Http\Request is not a full implementation of PSR-7 but implements only a few methods required for this package.

URL and Header Parameters

Application may use the following parameters in URL

The package provides parser for these parameters that returns ParametersInterface which is used for checking validity of input parameters and in application logic (data fetching from database, sorting, filtering, etc).

/** @var \Psr\Http\Message\ServerRequestInterface $request */
$request    = ...; 
$factory    = new Factory();
$parameters = $factory->createParametersParser()->parse($request);

Handling HTTP Accept and Content-Type headers

As the specification says

Clients MUST indicate that they can accept the JSON API media type, per the semantics of the HTTP Accept header.

which basically means JSON API server must handle those headers in accordance with RFC 2616. That's an example of a header that servers must support

Accept: application/vnd.api+json;q=0.5,text/html;q=0.8;*/*;q=0.1

Note: the package supports media type parameters and extensions as well. Specification reserves 'ext' parameter for future use and do not use media type extensions.

And server may choose to support text/html in order to simplify viewing content via a web browser.

Clients send their preferences in Accept and Content-Type headers. In order to parse HTTP headers in accordance with RFC 7231 the package provides implementation for CodecMatcherInterface. It registers mapping between input/output media types (Content-Type/Accept headers) and decoder/encoder handlers (Closures). Then CodecMatcher can find best match for decoder/encoder by media types provided.

$factory = new Factory();

$matcher = $factory->createCodecMatcher();

// Register 'media type' => decoder / encoder

$decoderClosure = function () {
    /** @var \Neomerx\JsonApi\Contracts\Decoder\DecoderInterface $decoder */
    $decoder = ...;
    return $decoder;
};
$encoderClosure = function () {
    /** @var \Neomerx\JsonApi\Contracts\Encoder\EncoderInterface $encoder */
    $encoder = ...;
    return $encoder;
};
$jsonApiType = $factory->createMediaType(
    MediaTypeInterface::JSON_API_TYPE,
    MediaTypeInterface::JSON_API_SUB_TYPE
);
$jsonApiTypeUtf8 = $factory->createMediaType(
    MediaTypeInterface::JSON_API_TYPE,
    MediaTypeInterface::JSON_API_SUB_TYPE,
    ['charset' => 'UTF-8']
);
$matcher->registerEncoder($jsonApiType, $encoderClosure);
$matcher->registerDecoder($jsonApiType, $decoderClosure);
$matcher->registerEncoder($jsonApiTypeUtf8, $encoderClosure);
$matcher->registerDecoder($jsonApiTypeUtf8, $decoderClosure);

Then decoders/encoders could be matched by media types

$acceptHeader = AcceptHeader::parse(
    'type1/subtype1;q=1.0, type1/subtype1;ext=ext1;q=0.8, */*;q=0.1'
);
$contentTypeHeader = Header::parse(
    'type1/subtype1',
    HeaderInterface::HEADER_CONTENT_TYPE
);

$matcher->matchEncoder($acceptHeader);
$encoder = $matcher->getEncoder();
$type    = $matcher->getEncoderHeaderMatchedType();

$matcher->matchDecoder($contentTypeHeader);
$decoder = $matcher->getDecoder();
$type    = $matcher->getDecoderHeaderMatchedType();

Validation for URL and Header Parameters

According to specification servers have some responsibilities to respond with 406 and 415 HTTP codes in some cases. The package provides with implementations for HeadersCheckerInterface and QueryCheckerInterface which accept ParametersInterface and validate it against negotiation requirements and allowed headers and parameters for include paths, sparse field sets, sorting, pagination and filtering.

$factory = new Factory();

$allowUnrecognised   = true;
$includePaths        = ['author', 'comments'];
$fieldSetTypes       = ['header', 'created-at', 'updated-at'];
$sortParameters      = ['created-at', 'updated-at'];
$pagingParameters    = ['first', 'last'];
$filteringParameters = ['header', 'body', 'created-at', 'updated-at'];

$checker = $factory->createParametersChecker(
    $matcher,
    $allowUnrecognised,
    $includePaths,
    $fieldSetTypes,
    $sortParameters,
    $pagingParameters,
    $filteringParameters
);

// throws JsonApiException if validtion fails
$checker->check($parameters);

HTTP responses

JSON API has a variety of possible HTTP responses. The package provides \Neomerx\JsonApi\Http\Responses class which is designed to be combined with Encoder, Parameters, MediaType and provide easy data transformation to HTTP Response.

Integration sample

$parameters = $this->getRequestParameters();
$matcher    = $this->getCodecMatcher();

$matcher->matchEncoder($parameters->getAcceptHeader());

$responses = new Responses(
    $parameters,
    $matcher->getEncoderRegisteredMatchedType(),
    $this->getSupportedExtensions(),
    $matcher->getEncoder(),
    $this->getSchemaContainer(),
    $this->getEncoderOptions()->getUrlPrefix()
);

// response for newly created resource with
// HTTP code 201 + 'location' header
$response = $responses->getCreatedResponse($newAuthor);