This package can validate PSR-7 messages against OpenAPI (3.0.x) specifications expressed in YAML or JSON.
n/a
composer require lezhnev74/openapi-psr7-validator
There are some specific terms that are used in the package. These terms come from OpenAPI:
specification
- an OpenAPI document describing an API, expressed in JSON or YAML filedata
- actual thing that we validate against a specification, including body and metadataschema
- the part of the specification that describes the body of the request / responsekeyword
- properties that are used to describe the instance are called key words, or schema keywordspath
- a relative path to an individual endpointoperation
- a method that we apply on the path (likeget /password
)response
- described response (includes status code, content types etc)
If you see a similar error:
... Argument 1 passed to OpenAPIValidation\Schema\Validator::__construct() must be an instance of cebe\openapi\spec\Schema, instance of cebe\openapi\spec\Reference given ...
and your OpenAPI file contains references like this:
...
/path1:
get:
parameters:
- $ref: 'schemas.yaml#/components/parameters/HeaderA'
...
You need to resolve them first before using this package. In other words you need to have a single file with resolved references. This constraint mentioned in #4.
Until the issue is resolved, please use speccy tool to resolve dependencies.
speccy resolve spec.yaml
There is a known issue in the underlying cebe/php-openapi
package that we use
which prevents you from using \cebe\openapi\Reader::readFromYamlFile('api.yaml')
on a Windows machine.
Until the issue is resolved we recommend using \cebe\openapi\Reader::readFromYaml(json_get_contents('api.yaml'))
instead.
You can validate \Psr\Http\Message\ServerRequestInterface
instance like this:
$yamlFile = "api.yaml";
$validator = new \OpenAPIValidation\PSR7\ServerRequestValidator(
\cebe\openapi\Reader::readFromYamlFile($yamlFile)
);
$validator->validate($request);
Validation of \Psr\Http\Message\ResponseInterface
is a bit more complicated
. Because you need not only YAML file and Response itself, but also you need
to know which operation this response belongs to (in terms of OpenAPI).
Example:
$yamlFile = "api.yaml";
$validator = new \OpenAPIValidation\PSR7\ResponseValidator(
\cebe\openapi\Reader::readFromYamlFile($yamlFile)
);
$operation = new \OpenAPIValidation\PSR7\OperationAddress('/password/gen', 'get') ;
$validator->validate($operation, $request);
\Psr\Http\Message\RequestInterface
validation is not implemented.
PSR-15 middleware can be used like this:
$middleware = \OpenAPIValidation\PSR15\ValidationMiddleware::fromYamlSpec("api.yaml");
# or
$middleware = \OpenAPIValidation\PSR15\ValidationMiddleware::fromJsonSpec("api.json");
Slim framework uses slightly different middleware interface, so here is an adapter which you can use like this:
$psr15Middleware = \OpenAPIValidation\PSR15\ValidationMiddleware::fromYamlSpec
("api.yaml");
$slimMiddleware = new \OpenAPIValidation\PSR15\SlimAdapter($psr15Middleware);
/** @var \Slim\App $app */
$app->add($slimMiddleware);
The package contains a standalone validator which can validate any data against an OpenAPI schema like this:
$spec = <<<SPEC
schema:
type: string
enum:
- a
- b
SPEC;
$data = "c";
$spec = cebe\openapi\Reader::readFromYaml($spec);
$schema = new cebe\openapi\spec\Schema($spec->schema);
try {
(new OpenAPIValidation\Schema\Validator($schema, $data))->validate();
} catch(\OpenAPIValidation\Schema\Exception\ValidationKeywordFailed $e) {
// you can evaluate failure details
// $e->keyword() == "enum"
// $e->data() == "c"
// $e->dataBreadCrumb()->buildChain() -- only for nested data
}
As you know, OpenAPI allows you to add formats to types:
schema:
type: string
format: binary
This package contains a bunch of built-in format validators:
string
type:byte
date
date-time
email
hostname
ipv4
ipv6
uri
uuid
(uuid4)
number
typefloat
double
You can also add your own formats. Like this:
# A format validator must be a callable
# It must return bool value (true if format matched the data, false otherwise)
# A callable class:
$customFormat = new class()
{
function __invoke($value): bool
{
return $value === "good value";
}
};
# Or just a closure:
$customFormat = function ($value): bool {
return $value === "good value";
};
# Register your callable like this before validating your data
\OpenAPIValidation\Schema\TypeFormats\FormatsContainer::registerFormat('string', 'custom', $customFormat);
The package throws a list of various exceptions which you can catch and handle. There are some of them:
- Schema related:
\OpenAPIValidation\Schema\Exception\ValidationKeywordFailed
- data does not match given keyword's rule. For exampletype:string
won't match integer12
.\OpenAPIValidation\Schema\Exception\FormatMismatch
- data mismatched a given type format. For exampletype: string, format: email
won't matchnot-email
.
- PSR7 Messages related:
\OpenAPIValidation\PSR7\Exception\NoContentType
- Response contains no Content-Type header. General HTTP errors.\OpenAPIValidation\PSR7\Exception\NoPath
- path is not found in the spec\OpenAPIValidation\PSR7\Exception\NoOperation
- operation os not found in the path\OpenAPIValidation\PSR7\Exception\NoResponseCode
- response code not found under the operation in the spec- Request related:
\OpenAPIValidation\PSR7\Exception \MultipleOperationsMismatchForRequest
- request matched multiple operations in the spec, but validation failed for all of them.\OpenAPIValidation\PSR7\Exception\MissedRequestCookie
- Request does not contain expected cookie\OpenAPIValidation\PSR7\Exception\MissedRequestHeader
- Request does not contain expected header\OpenAPIValidation\PSR7\Exception\MissedRequestQueryArgument
- Request does not have expected query argument\OpenAPIValidation\PSR7\Exception\RequestBodyMismatch
- request's body does not match the specification schema\OpenAPIValidation\PSR7\Exception\RequestCookiesMismatch
- request's cookie does not match the specification schema\OpenAPIValidation\PSR7\Exception\RequestHeadersMismatch
- request's headers do not match spec schema\OpenAPIValidation\PSR7\Exception\RequestPathParameterMismatch
- request's path does not match spec's path template\OpenAPIValidation\PSR7\Exception\RequestQueryArgumentMismatch
- request's query arguments does not match the spec schema\OpenAPIValidation\PSR7\Exception\UnexpectedRequestContentType
- request's body content type is unexpected\OpenAPIValidation\PSR7\Exception\UnexpectedRequestHeader
- request carries unexpected header\OpenAPIValidation\PSR7\Exception\Request\Security\NoRequestSecurityApiKey
- described security apiKey not found in the request\OpenAPIValidation\PSR7\Exception\Request\Security\RequestSecurityMismatch
- request does not match with the described security requirements
- Response related:
\OpenAPIValidation\PSR7\Exception\MissedResponseHeader
- response does not have a header\OpenAPIValidation\PSR7\Exception\ResponseBodyMismatch
- response body does not match a schema\OpenAPIValidation\PSR7\Exception\ResponseHeadersMismatch
- response headers do not match the spec\OpenAPIValidation\PSR7\Exception\UnexpectedResponseHeader
- response contains header not mentioned in the spec\OpenAPIValidation\PSR7\Exception\UnexpectedResponseContentType
- response contains unexpected content-type
You can run the tests with:
vendor/bin/phpunit
People:
- Dmitry Lezhnev
- Carsten Brandt
- Samuel Nela
- Pavel Batanov
- Christopher L Bray
- David Pauli
- A big thank you to Henrik Karlström who kind of inspired me to work on this package.
Resources:
- Icons made by Freepik, licensed by CC 3.0 BY
- cebe/php-openapi package for Reading OpenAPI files
- slim3-psr15 package for Slim middleware adapter
The MIT License (MIT). Please see License.md
file for more information.
- #4 Resolve references automatically
- Support Discriminator Object
- parameters serialization
- Does anyone use this serialization? It looks very... unpractical.
- add validation for Request class.
- Usually for serverside testing purposes ServerRequest is what we need. But, Request should be quite easy to add.