It validates PSR-7 messages (HTTP request/response) against OpenAPI specifications

Overview

Latest Stable Version Build Status License contributions welcome

OpenAPI PSR-7 Message (HTTP Request/Response) Validator

This package can validate PSR-7 messages against OpenAPI (3.0.x) specifications expressed in YAML or JSON.

Installation

composer require league/openapi-psr7-validator

OpenAPI (OAS) Terms

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 file
  • data - actual thing that we validate against a specification, including body and metadata
  • schema - the part of the specification that describes the body of the request / response
  • keyword - properties that are used to describe the instance are called key words, or schema keywords
  • path - a relative path to an individual endpoint
  • operation - a method that we apply on the path (like get /password)
  • response - described response (includes status code, content types etc)

How To Validate

ServerRequest Message

You can validate \Psr\Http\Message\ServerRequestInterface instance like this:

$yamlFile = "api.yaml";
$jsonFile = "api.json";

$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getServerRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYaml(file_get_contents($yamlFile))->getServerRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJson(file_get_contents($jsonFile))->getServerRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJsonFile($jsonFile)->getServerRequestValidator();
#or
$schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getServerRequestValidator();

$match = $validator->validate($request);

As a result you would get and OperationAddress $match which has matched the given request. If you already know the operation which should match your request (i.e you have routing in your project), you can use RouterRequestValidator

$address = new \League\OpenAPIValidation\PSR7\OperationAddress('/some/operation', 'post');

$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getRoutedRequestValidator();

$validator->validate($address, $request);

This would simplify validation a lot and give you more performance.

Request Message

You can validate \Psr\Http\Message\RequestInterface instance like this:

$yamlFile = "api.yaml";
$jsonFile = "api.json";

$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYaml(file_get_contents($yamlFile))->getRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJson(file_get_contents($jsonFile))->getRequestValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJsonFile($jsonFile)->getRequestValidator();
#or
$schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getRequestValidator();

$match = $validator->validate($request);

Response Message

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";
$jsonFile = "api.json";

$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getResponseValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYaml(file_get_contents($yamlFile))->getResponseValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJson(file_get_contents($jsonFile))->getResponseValidator();
#or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJsonFile($jsonFile)->getResponseValidator();
#or
$schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getResponseValidator();

$operation = new \League\OpenAPIValidation\PSR7\OperationAddress('/password/gen', 'get') ;

$validator->validate($operation, $response);

Reuse Schema After Validation

\League\OpenAPIValidation\PSR7\ValidatorBuilder reads and compiles schema in memory as instance of \cebe\openapi\spec\OpenApi. Validators use this instance to perform validation logic. You can reuse this instance after the validation like this:

$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getServerRequestValidator();
# or
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getResponseValidator();

/** @var \cebe\openapi\spec\OpenApi */
$openApi = $validator->getSchema();

PSR-15 Middleware

PSR-15 middleware can be used like this:

$yamlFile = 'api.yaml';
$jsonFile = 'api.json';

$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYamlFile($yamlFile)->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYaml(file_get_contents($yamlFile))->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJsonFile($jsonFile)->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJson(file_get_contents($jsonFile))->getValidationMiddleware();
#or
$schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand
$validator = (new \League\OpenAPIValidation\PSR7\ValidationMiddlewareBuilder)->fromSchema($schema)->getValidationMiddleware();

SlimFramework Middleware

Slim framework uses slightly different middleware interface, so here is an adapter which you can use like this:

$yamlFile = 'api.yaml';
$jsonFile = 'api.json';

$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYamlFile($yamlFile)->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYaml(file_get_contents($yamlFile))->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJsonFile($jsonFile)->getValidationMiddleware();
#or
$psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJson(file_get_contents($jsonFile))->getValidationMiddleware();
#or
$schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand
$validator = (new \League\OpenAPIValidation\PSR7\ValidationMiddlewareBuilder)->fromSchema($schema)->getValidationMiddleware();

$slimMiddleware = new \League\OpenAPIValidation\PSR15\SlimAdapter($psr15Middleware);

/** @var \Slim\App $app */
$app->add($slimMiddleware);

Caching Layer / PSR-6 Support

PSR-7 Validator has a built-in caching layer (based on PSR-6 interfaces) which saves time on parsing OpenAPI specs. It is optional. You enable caching if you pass a configured Cache Pool Object to the static constructor like this:

// Configure a PSR-6 Cache Pool
$cachePool = new ArrayCachePool();

// Pass it as a 2nd argument
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)
    ->fromYamlFile($yamlFile)
    ->setCache($cachePool)
    ->getResponseValidator();
# or
$psr15Middleware = (new \OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)
    ->fromYamlFile($yamlFile)
    ->setCache($cachePool)
    ->getValidationMiddleware();

You can use ->setCache($pool, $ttl) call for both PSR-7 and PSR-15 builder in order to set proper expiration ttl in seconds (or explicit null)

If you want take control over the cache key for schema item, or your cache does not support cache key generation by itself you can ->overrideCacheKey('my_custom_key') to ensure cache uses key you want.

Standalone OpenAPI Validator

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);
# (optional) reference resolving
$spec->resolveReferences(new ReferenceContext($spec, "/"));
$schema = new cebe\openapi\spec\Schema($spec->schema);

try {
    (new \League\OpenAPIValidation\Schema\SchemaValidator())->validate($data, $schema);
} catch(\League\OpenAPIValidation\Schema\Exception\KeywordMismatch $e) {
    // you can evaluate failure details
    // $e->keyword() == "enum"
    // $e->data() == "c"
    // $e->dataBreadCrumb()->buildChain() -- only for nested data
}

Custom Type Formats

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 type
    • float
    • 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
\League\OpenAPIValidation\Schema\TypeFormats\FormatsContainer::registerFormat('string', 'custom', $customFormat);

Exceptions

The package throws a list of various exceptions which you can catch and handle. There are some of them:

  • Schema related:
    • \League\OpenAPIValidation\Schema\Exception\KeywordMismatch - Indicates that data was not matched against a schema's keyword
      • \League\OpenAPIValidation\Schema\Exception\TypeMismatch - Validation for type keyword failed against a given data. For example type:string and value is 12
      • \League\OpenAPIValidation\Schema\Exception\FormatMismatch - data mismatched a given type format. For example type: string, format: email won't match not-email.
  • PSR7 Messages related:
    • \League\OpenAPIValidation\PSR7\Exception\NoContentType - HTTP message(request/response) contains no Content-Type header. General HTTP errors.
    • \League\OpenAPIValidation\PSR7\Exception\NoPath - path is not found in the spec
    • \League\OpenAPIValidation\PSR7\Exception\NoOperation - operation os not found in the path
    • \League\OpenAPIValidation\PSR7\Exception\NoResponseCode - response code not found under the operation in the spec
    • Validation exceptions (check parent exception for possible root causes):
      • \League\OpenAPIValidation\PSR7\Exception\ValidationFailed - generic exception for failed PSR-7 message
      • \League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody - body does not match schema
      • \League\OpenAPIValidation\PSR7\Exception\Validation\InvalidCookies - cookies does not match schema or missing required cookie
      • \League\OpenAPIValidation\PSR7\Exception\Validation\InvalidHeaders - header does not match schema or missing required header
      • \League\OpenAPIValidation\PSR7\Exception\Validation\InvalidPath - path does not match pattern or pattern values does not match schema
      • \League\OpenAPIValidation\PSR7\Exception\Validation\InvalidQueryArgs - query args does not match schema or missing required argument
      • \League\OpenAPIValidation\PSR7\Exception\Validation\InvalidSecurity - request does not match security schema or invalid security headers
    • Request related:
      • \League\OpenAPIValidation\PSR7\Exception\MultipleOperationsMismatchForRequest - request matched multiple operations in the spec, but validation failed for all of them.

Testing

You can run the tests with:

vendor/bin/phpunit

Contribution Guide

Feel free to open an Issue or add a Pull request. There is a certain code style that this package follows: doctrine/coding-standard.

To conform to this style please use a git hook, shipped with this package at .githooks/pre-commit.

How to use it:

  1. Clone the package locally and navigate to the folder
  2. Create a symlink to the hook like this: ln -s -f ../../.githooks/pre-commit .git/hooks/pre-commit
  3. Add execution rights: chmod +x .git/hooks/pre-commit
  4. Now commit any new changes and the code will be checked and formatted accordingly.
  5. If there are any issues with your code, check the log here: .phpcs-report.txt

Credits

People:

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

License

The MIT License (MIT). Please see License.md file for more information.

TODO

  • Support Discriminator Object (note: apparently, this is not so straightforward, as discriminator can point to any external scheme)
Comments
  • A package to validate HttpFoundation responses against OpenAPI definitions, built upon yours

    A package to validate HttpFoundation responses against OpenAPI definitions, built upon yours

    Hey guys,

    Once more, not an issue, more like a request for feedback :)

    For a couple of years now I've been using your package in Laravel projects to validate the responses returned by API endpoints against an OpenAPI definition, as part of integration tests.

    Laravel uses Symfony's HttpFoundation component under the hood for its HTTP messages, so what I'd do is I'd use Symfony's PSR-7 Bridge to convert the HttpFoundation responses to PSR-7 messages first, and then use your package to validate the responses.

    That resulted in the same classes that I'd copy over again and again in all of my projects, so I've decided to make a package out of it, to make my life easier and also because I think it could be useful to other people, seeing how much HttpFoundation is used in major projects and frameworks.

    Here is the package: https://github.com/osteel/openapi-httpfoundation-validator

    It's not published yet, as I would very much like your feedback on it first: is this the kind of project you expected people to build on top of yours? Does it make sense to you? Can you spot anything obviously wrong I'd be doing?

    The package itself is rather simple, as it's made of a couple of classes only, and not huge ones. Hopefully a quick look at the README file will make it clear how it's supposed to be used.

    If you've got a few spare minutes to have a look, that'd be awesome :)

    No worries if not though!

    Cheers,

    Yannick

    question 
    opened by osteel 20
  • Fix date-time type format

    Fix date-time type format

    Fixes https://github.com/thephpleague/openapi-psr7-validator/issues/19

    The the RFC3339 date-time can be with or without milliseconds and with or without specific timezone offsets.

    With PHP milliseconds support was added in DateTimeInterface::RFC3339_EXTENDED (https://www.php.net/manual/en/class.datetimeinterface.php#datetime.constants.rfc3339_extended) but this doesn't support the other format without milliseconds. So we need to check both.

    https://3v4l.org/r059V

    opened by sunspikes 20
  • Add anchors if pattern is constant and begins and ends with same letter.

    Add anchors if pattern is constant and begins and ends with same letter.

    If I have a schema like this

    "format": {
      "pattern": "snakes",
      "type": "string"
    }
    

    anchors around snakes are not added. This PR fixes that.

    opened by canvural 17
  • Validation issue when using allOf with $ref

    Validation issue when using allOf with $ref

    Hi,

    Validating responses whose schemas is a reference with some overwritten values doesn't seem to work properly. What's seemingly happening is that the reference is correctly read but the overwritten values are ignored.

    Here is a test class so you can see for yourself:

    <?php
    
    declare(strict_types=1);
    
    namespace League\OpenAPIValidation\Tests\FromCommunity;
    
    use GuzzleHttp\Psr7\Response;
    use League\OpenAPIValidation\PSR7\OperationAddress;
    use League\OpenAPIValidation\PSR7\ValidatorBuilder;
    use PHPUnit\Framework\TestCase;
    
    use function GuzzleHttp\Psr7\stream_for;
    
    final class IssueWithAllOfTest extends TestCase
    {
        private function assertResponseMatchesSpec(string $spec): void
        {
            $validator = (new ValidatorBuilder())->fromYaml($spec)->getResponseValidator();
            $operation = new OperationAddress('/dummy', 'get');
            $response  = (new Response())
                ->withHeader('Content-Type', 'application/json')
                ->withBody(stream_for(json_encode(['test' => null])));
    
            $validator->validate($operation, $response);
            $this->addToAssertionCount(1);
        }
    
        public function testNoRefOk(): void
        {
            $spec = <<<SPEC
    openapi: 3.0.2
    info:
      title: Dummy API
      version: 0.0.1
    paths:
      /dummy:
        get:
          summary: dummy
          operationId: dummy
          responses:
            200:
              description: dummy
              content:
                application/json:
                  schema:
                    type: object
                    properties:
                      test:
                        type: string
                        description: a dummy object
                        nullable: true
                        example: foobar
    SPEC;
    
            $this->assertResponseMatchesSpec($spec);
        }
    
        public function testRefOnlyOk(): void
        {
            $spec = <<<SPEC
    openapi: 3.0.2
    info:
      title: Dummy API
      version: 0.0.1
    paths:
      /dummy:
        get:
          summary: dummy
          operationId: dummy
          responses:
            200:
              description: dummy
              content:
                application/json:
                  schema:
                    \$ref: '#/components/schemas/dummy'
    components:
      schemas:
        dummy:
          type: object
          properties:
            test:
              type: string
              description: a dummy object
              nullable: true
              example: foobar
    SPEC;
    
            $this->assertResponseMatchesSpec($spec);
        }
    
        public function testAllOfWithRefNok(): void
        {
            $spec = <<<SPEC
    openapi: 3.0.2
    info:
      title: Dummy API
      version: 0.0.1
    paths:
      /dummy:
        get:
          summary: dummy
          operationId: dummy
          responses:
            200:
              description: dummy
              content:
                application/json:
                  schema:
                    type: object
                    properties:
                      test:
                        allOf:
                          - \$ref: '#/components/schemas/dummy'
                          - description: different description
                            nullable: true
    components:
      schemas:
        dummy:
          type: string
          description: a dummy object
          example: foobar
    SPEC;
    
            $this->assertResponseMatchesSpec($spec);
        }
    
        public function testAllOfWithoutRefNok(): void
        {
            $spec = <<<SPEC
    openapi: 3.0.2
    info:
      title: Dummy API
      version: 0.0.1
    paths:
      /dummy:
        get:
          summary: dummy
          operationId: dummy
          responses:
            200:
              description: dummy
              content:
                application/json:
                  schema:
                    type: object
                    properties:
                      test:
                        allOf:
                          - type: string
                            description: a dummy object
                            example: foobar
                          - description: different description
                            nullable: true
    SPEC;
    
            $this->assertResponseMatchesSpec($spec);
        }
    
        public function testAllOfWithoutRefBothNullableNok(): void
        {
            $spec = <<<SPEC
    openapi: 3.0.2
    info:
      title: Dummy API
      version: 0.0.1
    paths:
      /dummy:
        get:
          summary: dummy
          operationId: dummy
          responses:
            200:
              description: dummy
              content:
                application/json:
                  schema:
                    type: object
                    properties:
                      test:
                        allOf:
                          - description: different description
                            nullable: true
                          - type: string
                            description: a dummy object
                            example: foobar
                            nullable: true
    SPEC;
    
            $this->assertResponseMatchesSpec($spec);
        }
    }
    

    It seems kinda related to this issue.

    I'm also not 100% sure if this is coming from this package or cebe/php-openapi.

    I'm using version 0.15.2 at the moment.

    Cheers,

    Yannick

    opened by osteel 15
  • Beginner mistakes,

    Beginner mistakes,

    Hello, I'm testing the league / openapi-psr7-validator. Unfortunately I have a problem. I originally use the Swagger Yaml file from https://editor.swagger.io.

    My script:

    $yamlFile = "path/to/my/petshop_swagger.yaml";
    $yamlFileContent = file_get_contents($yamlFile);
    
    $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getServerRequestValidator();
    

    I get the error message:

    Fatal error: Uncaught Error: Call to a member function setReferenceContext() on array in C:\xampp1\htdocs\swagger-php\vendor\cebe\php-openapi\src\spec\Reference.php on line 177
    

    What is wrong ?

    Your sincerly Stephan

    bug 
    opened by StephanKrauss 14
  • nullable oneOf/anyOf value rejected

    nullable oneOf/anyOf value rejected

    <?php
    
    declare(strict_types=1);
    
    namespace League\OpenAPIValidation\Tests\FromCommunity;
    
    use GuzzleHttp\Psr7\Response;
    use League\OpenAPIValidation\PSR7\OperationAddress;
    use League\OpenAPIValidation\PSR7\ValidatorBuilder;
    use League\OpenAPIValidation\Tests\PSR7\BaseValidatorTest;
    
    final class IssueWithNullableMergeTest extends BaseValidatorTest
    {
        public function testNullableMergeOneOf(): void
        {
            $yaml = /** @lang yaml */
                   <<<'YAML'
    openapi: 3.0.0
    paths:
      /api/nullable-merge:
        get:
          description: 'Test'
          responses:
            '200':
              description: 'ok'
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Thing"
    components:
      schemas:
        FooResult:
          type: object
          properties:
            id:
              type: integer
            foo:
              type: string
        BarResult:
          type: object
          nullable: true
          properties:
            id:
              type: integer
            bar:
              type: string
        Thing:
          type: object
          properties:
            result:
              oneOf:
                - $ref: "#/components/schemas/FooResult"
                - $ref: "#/components/schemas/BarResult"
    YAML;
    
            $validator = (new ValidatorBuilder())->fromYaml($yaml)->getResponseValidator();
            $operation = new OperationAddress('/api/nullable-merge', 'get');
    
            $responseContent = /** @lang JSON */
                '
        {
          "result": null
        }
    ';
    
            $response = new Response(200, ['Content-Type' => 'application/json'], $responseContent);
    
            $validator->validate($operation, $response);
    
            $this->addToAssertionCount(1);
        }
    }
    

    FooResult is not nullable. BarResult is nullable. Thing.result can be one of FooResult or BarResult, so my expectation is that null should be an acceptable value. Validation fails with:

    There was 1 error:
    
    1) League\OpenAPIValidation\Tests\FromCommunity\IssueWithNullableMergeTest::testNullableMergeOneOf
    League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody: Body does not match schema for content-type "application/json" for Response [get /api/nullable-merge 200]
    
    /openapi-psr7-validator/src/PSR7/Exception/Validation/AddressValidationFailed.php:28
    /openapi-psr7-validator/src/PSR7/Exception/Validation/InvalidBody.php:19
    /openapi-psr7-validator/src/PSR7/Validators/BodyValidator/UnipartValidator.php:60
    /openapi-psr7-validator/src/PSR7/Validators/BodyValidator/BodyValidator.php:73
    /openapi-psr7-validator/src/PSR7/Validators/ValidatorChain.php:25
    /openapi-psr7-validator/src/PSR7/ResponseValidator.php:42
    /openapi-psr7-validator/tests/FromCommunity/IssueWithNullableMergeTest.php:68
    
    Caused by
    League\OpenAPIValidation\Schema\Exception\KeywordMismatch: Keyword validation failed: Value cannot be null
    
    /openapi-psr7-validator/src/Schema/Exception/KeywordMismatch.php:22
    /openapi-psr7-validator/src/Schema/Keywords/Nullable.php:21
    /openapi-psr7-validator/src/Schema/SchemaValidator.php:60
    /openapi-psr7-validator/src/Schema/Keywords/Properties.php:85
    /openapi-psr7-validator/src/Schema/SchemaValidator.php:139
    /openapi-psr7-validator/src/PSR7/Validators/BodyValidator/UnipartValidator.php:58
    /openapi-psr7-validator/src/PSR7/Validators/BodyValidator/BodyValidator.php:73
    /openapi-psr7-validator/src/PSR7/Validators/ValidatorChain.php:25
    /openapi-psr7-validator/src/PSR7/ResponseValidator.php:42
    /openapi-psr7-validator/tests/FromCommunity/IssueWithNullableMergeTest.php:68
    

    This same issue happens if oneOf: in the above snippet is replaced with anyOf:.

    Swagger UI shows the nullable: true constraint as expected:

    image

    opened by AlbinoDrought 12
  • PHP 8 Support

    PHP 8 Support

    This PR adds support for PHP 8 by removing dflydev/fig-cookies and replacing it with hansott/psr7-cookies and some custom code, and replaces cache/array-adapter with symfony/cache

    Most of the changes are coming from style changes. So I made separate commits. Here is the most relevant commit.

    I think we don't need to wait for https://github.com/cebe/php-openapi/issues/81 to merge this. That library is already compatible with PHP8, only issues were in dev dependencies there.

    Fixes #91

    opened by canvural 12
  • Bad performance in the PathFinder

    Bad performance in the PathFinder

    Hello, First thanks for the great work you've done here.

    I wanted to use this library to validate the requests/responses of our API within our codeception tests. During my experimentation with this project I noticed a performance issue related to the PathFinder service. I can't share my swagger.yaml but I did my tests with a 2MB file.

    The issue is that searching a path is too long, this is because of the PathFinder::findServersForOperation() method. More specifically the instruction $this->openApiSpec->getSerializableData() which takes in average 0.8s.

    The method PathFinder::search() which trigger the previous calls is used in multiple places which result in an average time of 4s to validate both the request and the response.

    Possible solution: When investigating the issue I realized that this part seems unnecessary:

            // 3. Check servers on root level
            if (array_key_exists('servers', (array) $this->openApiSpec->getSerializableData()) &&
                count($this->openApiSpec->servers) > 0) {
                return $this->openApiSpec->servers;
            }
    
            // fallback
            return [new Server(['url' => '/'])];
    

    Indeed in the OpenApi class, the servers property is declared with a default value of [new Server(['url' => '/'])], just as the fallback you used. Therefore there is no need to test if the key actually exists neither as to ensure there is at least 1 server defined and this could simply be:

            return $this->openApiSpec->servers;
    

    I'm not familiar with the OAS3 specification nor as I am with the cebe/php-openapi library, so I didn't want to open a PR without discussing it here first. I don't think my proposition will have any impact since the SpecBaseObject::getSerializableData() method does not affect the state of the object.

    opened by camilledejoye 12
  • Fix BodyValidator for media-range scenario

    Fix BodyValidator for media-range scenario

    This fixes a bug in my previous PR https://github.com/thephpleague/openapi-psr7-validator/pull/8

    This PR:

    • Updates the $mediaTypeSpec references in BodyValidator in the places where it was missing
    • Updates the test with a better use case (Also I just found the validation where skipped in the previous test)

    I am sorry for this, I should have doubled checked the tests. :(

    opened by sunspikes 12
  • [Hotfix][Issue-79] Avoid serialization when resolving servers

    [Hotfix][Issue-79] Avoid serialization when resolving servers

    Fix #79

    Let me know if you think of other scenario to test or if I should include the tests to the PathFinderTest class directly.

    I have split my commits by habit but don't hesitate to ask if you want me to squash them.

    opened by camilledejoye 10
  • Collision between parametrized and not parametrized routes

    Collision between parametrized and not parametrized routes

    For example, 2 routes:

    1. GET /apples/{apple_id}
    2. GET /apples/ready-to-eat
    "apple_id": {
      "in": "path",
      "name": "apple_id",
      "schema": {
        "type": "integer"
      },
      "required": true
    }
    
    $pathFinder = new League\OpenAPIValidation\PSR7\PathFinder($schema, '/apples/ready-to-eat', 'GET');
    $addresses = $pathFinder->search();
    print_r($addresses);
    
    /*
    Array
    (
        [0] => League\OpenAPIValidation\PSR7\OperationAddress Object
            (
                [method:protected] => get
                [path:protected] => /apples/{apple_id}
            )
    
        [1] => League\OpenAPIValidation\PSR7\OperationAddress Object
            (
                [method:protected] => get
                [path:protected] => /apples/ready-to-eat
            )
    
    )
    
    */
    

    PathFinder::search() must return only one route in this case, because apple_id is integer.

    league/openapi-psr7-validator: v0.15.2

    bug 
    opened by dmytro-demchyna 9
  • Validating query arguments of type array

    Validating query arguments of type array

    ref #181

    Hi.

    this pr makes it possible to accept query arguments of an array.

    For example.

    https://example.com?someKey[]=value1&someKey[]=value2

    We will be able to validate someKey[].

    opened by tyamahori 0
  • Error validating query arguments of type array

    Error validating query arguments of type array

    There are several ways to express arrays in query strings:

    The array

    "list": [
        "foo",
        "bar"
    ]
    

    may be serialized into list[]=foo&list[]=bar or list=foo&list=bar.

    Depending on the server backend, the latter variant may be required. When using this format though, request validation breaks:

    https://github.com/thephpleague/openapi-psr7-validator/blob/5f98f98abf37f4533473699ef2ff2b4dc9b8d52e/src/PSR7/Validators/SerializedParameter.php#L174-L176

    Invalid argument supplied for foreach()

    The issue is caused by the usage of the parse_str function, which can only handle PHP-style query args and turns multiple arguments with the same name into a string with the last value ("bar"):

    https://github.com/thephpleague/openapi-psr7-validator/blob/5f98f98abf37f4533473699ef2ff2b4dc9b8d52e/src/PSR7/Validators/QueryArgumentsValidator.php#L74

    This drawback is also mentioned in the user contributed notes of the parse_str documentation.

    The objective would be to properly parse all array query string formats that are mentioned in the OpenAPI Guide.

    opened by mam08ixo 1
  • Validation error when param path is urlencoded

    Validation error when param path is urlencoded

    Hello, i have problem with path param with urlencoded characters <>. When i change implementation of this line to:

    $parsedParams[$name] = urldecode($matches[$name]);

    validation works fine. Without this change i got:

    prev | Object League\OpenAPIValidation\Schema\Exception\KeywordMismatch
    value | COMPONENT_%3COBJECT_JA100:73072%3E:27
    
    params | {componentEntityId: COMPONENT_%3COBJECT_JA100:73072%3E:27, v1: v1}
    
    League\OpenAPIValidation\Schema\Exception\KeywordMismatch
    Keyword validation failed: Data does not match pattern '#^(SECTION|COMPONENT|ACTOR|USER)_<(OBJECT_(\w+):([\d]+))>:(\d+)$#u'
    
    opened by tomasjanda 0
  • oneOf check if only one schema applies should sometimes be ignored if a custom type format is missing

    oneOf check if only one schema applies should sometimes be ignored if a custom type format is missing

    Imagine an OpenAPI schema with a oneOf linking to 2 string definitions and a custom format:

    schema:
      oneOf:
        -
          type: string
          format: custom-format
        -
          type: string
          format: another-custom-format
    

    With the OpenAPI PSR7 validator this will invalidate that 2 schemas are matched unless I provided a custom format validator (as written in the documentation https://github.com/thephpleague/openapi-psr7-validator#custom-type-formats). Shouldn't this be ignored if no custom type format is provided? Or maybe some warning/configurable setting?

    opened by pjordaan 0
  • Fix missing ExceptionInterface

    Fix missing ExceptionInterface

    Hi,

    \Respect\Validation\Exceptions\ExceptionInterface does not exist anymore, and Respect\Validation\Exceptions\Exception extends from \Throwable without adding any additional value.

    This PR removes the unexisting interface usage and simplifies exception catching.

    opened by rogervila 0
  • `readOnly` and `writeOnly` not respected with `allOf`

    `readOnly` and `writeOnly` not respected with `allOf`

    If you have something like this:

    type: object
    required: [id]
    properties
      id:
        allOf:
          - $ref: "#/id"
          - readOnly: true
    

    then not passing "id" through a write request (e.g. POST) is not possible.

    opened by machitgarha 3
Releases(0.18)
Owner
The League of Extraordinary Packages
A group of developers who have banded together to build solid, well tested PHP packages using modern coding standards.
The League of Extraordinary Packages
Generates OpenApi specification for Laravel, Lumen or Dingo using a configuration array and cebe/php-openapi

OpenApi Generator for Laravel, Lumen and Dingo. About The openapi-gen package provides a convenient way to create OpenApi specifications for Laravel,

Jean Dormehl 5 Jan 25, 2022
Http-kernel - The HttpKernel component provides a structured process for converting a Request into a Response.

HttpKernel Component The HttpKernel component provides a structured process for converting a Request into a Response by making use of the EventDispatc

Symfony 7.8k Jan 9, 2023
A simple and flexible PHP middleware dispatcher based on PSR-7, PSR-11, and PSR-15

Woohoo Labs. Harmony Woohoo Labs. Harmony is a PSR-15 compatible middleware dispatcher. Harmony was born to be a totally flexible and almost invisible

Woohoo Labs. 153 Sep 5, 2022
pedre-response is a standard structure of json response

PedreResponse It's very important to use same structure for responses in large projects that PedreResponse package can do it for you. PedreResponse is

Pedram Rezaei 2 Dec 22, 2021
Jane is a set of libraries to generate Models & API Clients based on JsonSchema / OpenAPI specs

Jane is a set of libraries to generate Models & API Clients based on JsonSchema / OpenAPI specs Documentation Documentation is available at http://jan

Jane 438 Dec 18, 2022
Read and write OpenAPI 3.0.x YAML and JSON files and make the content accessible in PHP objects.

php-openapi Read and write OpenAPI 3.0.x YAML and JSON files and make the content accessible in PHP objects. It also provides a CLI tool for validatin

Carsten Brandt 399 Dec 23, 2022
A PSR-15 middleware adapter for react/http

A PSR-15 middleware adapter for react/http Wraps PSR-15 middleware into coroutines using RecoilPHP making them usable within react/http as middleware.

Friends of ReactPHP 22 Nov 12, 2022
Like FormRequests, but for validating against a json-schema

JSON Schema Request Laravels Form Request Validation for JSON Schema documents Installation composer require wt-health/laravel-json-schema-request Us

Webtools Health 1 Feb 3, 2022
Extended response classes for Rest API clients/SDKs

Rest Response Extended response classes for Rest API clients/SDKs. About This package is intended to be component of SDKs so the responses can be easi

Ilesanmi Olawale Adedotun 2 Mar 28, 2022
A simple PHP package for sending messages to Slack, with a focus on ease of use and elegant syntax.

Slack for PHP | A simple PHP package for sending messages to Slack with incoming webhooks, focused on ease-of-use and elegant syntax. supports: PHP 7.

null 128 Nov 28, 2022
A powerful PHP Router for PSR7 messages inspired by the Laravel API.

Rare Router A simple PHP router built on AltoRouter but inspired by the Laravel API. Installation composer require rareloop/router Usage Creating Rou

Rareloop 74 Dec 17, 2022
A request rate limiter for Laravel 5.

Alt Three Throttle An request rate limiter for Laravel 5. Installation This version requires PHP 7.1 or 7.2, and supports Laravel 5.5 - 5.7 only. To g

Alt Three 41 Jan 3, 2022
A based PSR-15 microframework that also sets maximum flexibility with minimum complexity and easy replaceability of the individual components, but also of the framework.

chubbyphp-framework Description A based PSR-15 microframework that also sets maximum flexibility with minimum complexity and easy replaceability of th

chubbyphp 106 Dec 9, 2022
Disable Google's FLoC with help of PSR-15 middleware

Disable Google's FLoC with PSR-15 middleware This package will help you disable Google's FLoC. Installation You can install the package via composer:

P7V 9 Dec 14, 2022
The efficient and elegant, PSR-7 compliant JSON:API 1.1 client library for PHP

Woohoo Labs. Yang Woohoo Labs. Yang is a PHP framework which helps you to communicate with JSON:API servers more easily. Table of Contents Introductio

Woohoo Labs. 160 Oct 16, 2022
PSR-7 middleware foundation for building and dispatching middleware pipelines

laminas-stratigility From "Strata", Latin for "layer", and "agility". This package supersedes and replaces phly/conduit. Stratigility is a port of Sen

Laminas Project 47 Dec 22, 2022
Tukio is a complete and robust implementation of the PSR-14 Event Dispatcher specification

Tukio is a complete and robust implementation of the PSR-14 Event Dispatcher specification. It supports normal and debug Event Dispatchers, both runtime and compiled Providers, complex ordering of Listeners, and attribute-based registration on PHP 8.

Larry Garfield 70 Dec 19, 2022
PSR-15 middleware to geolocate the client using the ip address

middlewares/geolocation ![SensioLabs Insight][ico-sensiolabs] Middleware to geolocate the client using the ip address and Geocoder and save the result

Middlewares 10 Mar 22, 2022
PSR-15 middleware to use Whoops as error handler

middlewares/whoops Middleware to use Whoops as error handler. Requirements PHP >= 7.2 A PSR-7 http library A PSR-15 middleware dispatcher Installation

Middlewares 31 Jun 23, 2022