├── .gitignore
├── .scrutinizer.yml
├── .travis.analyze.sh
├── .travis.coverage.sh
├── .travis.yml
├── README.md
├── composer.json
├── phpcs.xml.dist
├── phpmd.xml.dist
├── phpstan.neon
├── phpunit.xml.dist
├── src
├── Decoder
│ ├── Adapter
│ │ └── SymfonyDecoderAdapter.php
│ ├── DecoderInterface.php
│ └── DecoderUtils.php
├── Definition
│ ├── MessageDefinition.php
│ ├── Parameter.php
│ ├── Parameters.php
│ ├── RequestDefinition.php
│ ├── RequestDefinitions.php
│ └── ResponseDefinition.php
├── Factory
│ ├── CachedSchemaFactoryDecorator.php
│ ├── SchemaFactory.php
│ └── SwaggerSchemaFactory.php
├── JsonSchema
│ └── Uri
│ │ └── YamlUriRetriever.php
├── Normalizer
│ └── QueryParamsNormalizer.php
├── Schema.php
└── Validator
│ ├── ConstraintViolation.php
│ ├── Exception
│ └── ConstraintViolations.php
│ └── MessageValidator.php
└── tests
├── Decoder
├── Adapter
│ └── SymfonyDecoderAdapterTest.php
└── DecoderUtilsTest.php
├── Definition
├── RequestDefinitionTest.php
├── RequestDefinitionsTest.php
├── RequestParameterTest.php
├── RequestParametersTest.php
└── ResponseDefinitionTest.php
├── Factory
├── CachedSchemaFactoryDecoratorTest.php
└── SwaggerSchemaFactoryTest.php
├── JsonSchema
└── Uri
│ └── YamlUriRetrieverTest.php
├── Normalizer
└── QueryParamsNormalizerTest.php
├── SchemaTest.php
├── Validator
├── ConstraintViolationTest.php
└── MessageValidatorTest.php
└── fixtures
├── operation-without-an-id.json
├── operation-without-parameters.json
├── operation-without-responses.json
├── petstore.json
├── petstore.txt
├── petstore.yaml
├── request-with-conflicting-locations.json
├── request-without-content-types.json
└── schema-with-default-consumes-and-produces-properties.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | /vendor/
3 | /composer.lock
4 | /build
5 | /phpstan.phar
6 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | tools:
2 | external_code_coverage:
3 | timeout: 600
4 |
5 | build:
6 | environment:
7 | php:
8 | version: 7.2
9 | nodes:
10 | analysis:
11 | tests:
12 | override:
13 | - command: php-scrutinizer-run
14 | - command: phpcs-run --standard=phpcs.xml.dist
15 | use_website_config: false
16 |
--------------------------------------------------------------------------------
/.travis.analyze.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | version() {
6 | printf "%03d%03d" $(echo "$1" | tr '.' ' ');
7 | }
8 |
9 | phpstan_download() {
10 | if [ ! -f "phpstan.phar" ];then
11 | curl -sOL https://github.com/phpstan/phpstan/releases/download/0.10.5/phpstan.phar
12 | fi
13 | }
14 |
15 | phpstan_analyze() {
16 | php ./phpstan.phar analyze --ansi
17 | }
18 |
19 | main() {
20 | PHP_VERSION=$(php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;")
21 | if [ $(version $PHP_VERSION) -ge $(version "7.1") ]; then
22 | phpstan_download
23 | phpstan_analyze
24 | else
25 | echo "[warn] you need at least PHP 7.1 to use analyze the project with phpstan"
26 | fi
27 | }
28 |
29 | main
30 |
--------------------------------------------------------------------------------
/.travis.coverage.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | main() {
6 | wget -q https://scrutinizer-ci.com/ocular.phar
7 | php ocular.phar code-coverage:upload --format=php-clover ./build/phpunit.coverage.xml
8 | }
9 |
10 | main
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | sudo: false
4 |
5 | cache:
6 | directories:
7 | - $HOME/.composer/cache/files
8 |
9 | matrix:
10 | include:
11 | - php: 5.6
12 | env: SYMFONY_VERSION='^2'
13 | - php: 5.6
14 | env: SYMFONY_VERSION='^3'
15 | - php: 7.0
16 | env: SYMFONY_VERSION='^3'
17 | - php: 7.1
18 | env: SYMFONY_VERSION='^3' PHPUNIT_ARGS='--testdox'
19 | - php: 7.2
20 | env: SYMFONY_VERSION='^3' PHPUNIT_ARGS='--testdox'
21 | - php: 7.2
22 | env: SYMFONY_VERSION='^4' COVERAGE='' PHPCS='' PHPSTAN='' PHPUNIT_ARGS='--testdox --coverage-text --coverage-clover=build/phpunit.coverage.xml'
23 |
24 | before_install:
25 | - if [ -z "${COVERAGE+x}" ];then echo 'disabling xdebug'; phpenv config-rm xdebug.ini || true;fi
26 |
27 | before_script:
28 | - composer require --dev --no-update dunglas/symfony-lock=$SYMFONY_VERSION
29 | - composer update $COMPOSER_FLAGS --prefer-dist
30 |
31 | script:
32 | - vendor/bin/phpunit $PHPUNIT_ARGS --disallow-test-output --colors=always
33 | - if [ -n "${PHPCS+x}" ];then php vendor/bin/phpcs;fi
34 | - if [ -n "${PHPSTAN+x}" ];then sh .travis.analyze.sh ;fi
35 |
36 | after_script:
37 | - if [ -n "${COVERAGE+x}" ];then sh .travis.coverage.sh;fi
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # API Validator
2 |
3 | [](https://travis-ci.org/eleven-labs/api-validator)
4 | [](https://scrutinizer-ci.com/g/eleven-labs/api-validator/)
5 | [](https://scrutinizer-ci.com/g/eleven-labs/api-validator/)
6 |
7 | This library provides a set of classes suited to describe a WebService based on the HTTP protocol.
8 |
9 | It can validate [PSR-7 Requests](http://www.php-fig.org/psr/psr-7/) against a schema.
10 |
11 | It's design is heavily inspired by the OpenAPI/Swagger2.0 specifications.
12 |
13 | As of now, it only support the OpenAPi/Swagger2.0 specifications but we plan to
14 | support [RAML 1.0](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/)
15 | and [API Elements (API Blueprint)](https://github.com/apiaryio/api-elements) in the future.
16 |
17 | ## Dependencies
18 |
19 | We rely on the [justinrainbow/json-schema](https://github.com/justinrainbow/json-schema) library
20 | to parse specification files and to validate requests and responses:
21 |
22 | - Request's `headers`, `query`, `uri` and `body` parts.
23 | - Response `headers` and `body` parts.
24 |
25 | ## Usage
26 |
27 | ### Before you start
28 |
29 | You will need to write a **valid** Swagger 2.0 file in order to use this library. Ensure that this file is valid using
30 | the [Swagger Editor](http://editor.swagger.io/).
31 |
32 | You can also validate your specifications
33 | using the [Swagger 2.0 JSONSchema](https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v2.0/schema.json).
34 |
35 | ### Validate a request
36 |
37 | You can validate any PSR-7:
38 |
39 | - Request implementing the `Psr\Http\Message\RequestInterface` interface.
40 | - Response implementing the `Psr\Http\Message\ResponseInterface` interface.
41 |
42 | ```php
43 | createSchema('file://path/to/your/swagger.json');
79 |
80 | // Find the Request Definition in the Schema API
81 | $requestDefinition = $schema->getRequestDefinition(
82 | $schema->findOperationId($request->getMethod(), $request->getUri()->getPath())
83 | );
84 |
85 | // Validate the Request
86 | $messageValidator = new MessageValidator($validator, $decoder);
87 | $messageValidator->validateRequest($request, $requestDefinition);
88 |
89 | // Check if the request has violations
90 | if ($messageValidator->hasViolations()) {
91 | // Get violations and do something with them
92 | $violations = $messageValidator->getViolations();
93 | }
94 |
95 | // Using the message Validator, you can also validate a Response
96 | // It will find the proper ResponseDefinition from a RequestDefinition
97 | $response = new Response(
98 | 200,
99 | ['Content-Type' => 'application/json'],
100 | '{"id": 1}'
101 | );
102 |
103 | $messageValidator->validateResponse($response, $requestDefinition);
104 |
105 | // ...
106 | ```
107 |
108 | ### Working with Symfony HTTPFoundation Requests
109 |
110 | You will need an adapter in order to validate symfony requests.
111 |
112 | We recommend you to use the [symfony/psr-http-message-bridge](https://github.com/symfony/psr-http-message-bridge)
113 |
114 | ### Using the schema
115 |
116 | You can navigate the `ElevenLabs\Api\Schema` to meet other use cases.
117 |
118 | Example:
119 |
120 | ```php
121 | createSchema('file://path/to/your/swagger.json');
125 |
126 | // Find a request definition from an HTTP method and a path.
127 | $requestDefinition = $schema->getRequestDefinition(
128 | $schema->findOperationId('GET', '/pets/1234')
129 | );
130 |
131 | // Get the response definition for the status code 200 (HTTP OK)
132 | $responseDefinition = $requestDefinition->getResponseDefinition(200);
133 |
134 | // From here, you can access the JSON Schema describing the expected response
135 | $responseSchema = $responseDefinition->getBodySchema();
136 | ```
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eleven-labs/api-validator",
3 | "description": "Validate PSR-7 Requests against an OpenAPI/Swagger2 Schema",
4 | "authors": [
5 | {
6 | "name": "Guillem CANAL",
7 | "email": "gcanal@eleven-labs.com"
8 | }
9 | ],
10 | "autoload": {
11 | "psr-4": {
12 | "ElevenLabs\\Api\\": "src/"
13 | }
14 | },
15 | "autoload-dev": {
16 | "psr-4": {
17 | "ElevenLabs\\Api\\": "tests/"
18 | },
19 | "files": [
20 | "vendor/phpunit/phpunit/src/Framework/Assert/Functions.php"
21 | ]
22 | },
23 | "require": {
24 | "php": "^5.6 || ^7.0",
25 | "ext-json": "*",
26 | "psr/http-message": "^1.0",
27 | "psr/cache": "^1.0",
28 | "justinrainbow/json-schema": "^2.0",
29 | "rize/uri-template": "^0.3.0"
30 | },
31 | "require-dev": {
32 | "phpunit/phpunit": "^5.4 || ^7.4",
33 | "guzzlehttp/psr7": "^1.3",
34 | "squizlabs/php_codesniffer": "^3.3",
35 | "symfony/yaml": "^2.7 || ^3.4 || ^4.0",
36 | "symfony/serializer": "^2.7 || ^3.4 || ^4.0"
37 | },
38 | "scripts": {
39 | "test": "vendor/bin/phpunit --disallow-test-output --coverage-text --colors=always",
40 | "cs": "vendor/bin/phpcs",
41 | "phpstan": "sh .travis.analyze.sh"
42 | },
43 | "suggest": {
44 | "symfony/yaml": "Allow the SwaggerSchemaFactory to handle YAML files",
45 | "symfony/serializer": "Implementation that can decode JSON or XML request bodies"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ./src
18 | ./tests
19 |
20 |
21 |
--------------------------------------------------------------------------------
/phpmd.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 0
3 | paths:
4 | - src
5 | - tests
6 |
7 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
22 | tests
23 |
24 |
25 |
26 |
27 |
28 | ./src/
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/Decoder/Adapter/SymfonyDecoderAdapter.php:
--------------------------------------------------------------------------------
1 | decoder = $decoder;
17 | }
18 |
19 | public function decode($data, $format)
20 | {
21 | $context = [];
22 |
23 | if ($format === 'json') {
24 | // the JSON schema validator need an object hierarchy
25 | $context['json_decode_associative'] = false;
26 | }
27 |
28 | $decoded = $this->decoder->decode($data, $format, $context);
29 |
30 | if ($format === 'xml') {
31 | // the JSON schema validator need an object hierarchy
32 | $decoded = json_decode(json_encode($decoded));
33 | }
34 |
35 | return $decoded;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Decoder/DecoderInterface.php:
--------------------------------------------------------------------------------
1 | 'application/x-www-form-urlencoded', 'body' => 'application/json'];
9 |
10 | /**
11 | * Location of the parameter in the request
12 | *
13 | * @var string
14 | */
15 | private $location;
16 |
17 | /**
18 | * @var string
19 | */
20 | private $name;
21 |
22 | /**
23 | * Indicate if the parameter should be present
24 | *
25 | * @var bool
26 | */
27 | private $required;
28 |
29 | /**
30 | * A JSON Schema object
31 | *
32 | * @var \stdClass
33 | */
34 | private $schema;
35 |
36 | public function __construct($location, $name, $required, \stdClass $schema = null)
37 | {
38 | $this->location = $location;
39 | $this->name = $name;
40 | $this->required = $required;
41 | $this->schema = $schema;
42 | }
43 |
44 | /**
45 | * @return string
46 | */
47 | public function getLocation()
48 | {
49 | return $this->location;
50 | }
51 |
52 | /**
53 | * @return string
54 | */
55 | public function getName()
56 | {
57 | return $this->name;
58 | }
59 |
60 | /**
61 | * @return boolean
62 | */
63 | public function isRequired()
64 | {
65 | return $this->required;
66 | }
67 |
68 | /**
69 | * @return \stdClass
70 | */
71 | public function getSchema()
72 | {
73 | return $this->schema;
74 | }
75 |
76 | /**
77 | * @return bool
78 | */
79 | public function hasSchema()
80 | {
81 | return $this->schema !== null;
82 | }
83 |
84 | public function serialize()
85 | {
86 | return serialize([
87 | 'location' => $this->location,
88 | 'name' => $this->name,
89 | 'required' => $this->required,
90 | 'schema' => $this->schema
91 | ]);
92 | }
93 |
94 | public function unserialize($serialized)
95 | {
96 | $data = unserialize($serialized);
97 | $this->location = $data['location'];
98 | $this->name = $data['name'];
99 | $this->required = $data['required'];
100 | $this->schema = $data['schema'];
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Definition/Parameters.php:
--------------------------------------------------------------------------------
1 | parameters = [];
16 | foreach ($parameters as $parameter) {
17 | $this->addParameter($parameter);
18 | }
19 | }
20 |
21 | public function getIterator()
22 | {
23 | return new \ArrayIterator($this->parameters);
24 | }
25 |
26 | public function hasBodySchema()
27 | {
28 | $body = $this->getBody();
29 |
30 | return ($body !== null && $body->hasSchema());
31 | }
32 |
33 | /**
34 | * JSON Schema for a the body
35 | *
36 | * @return \stdClass|null
37 | */
38 | public function getBodySchema()
39 | {
40 | return $this->getBody()->getSchema();
41 | }
42 |
43 | /**
44 | * @return bool
45 | */
46 | public function hasQueryParametersSchema()
47 | {
48 | return ($this->getSchema($this->getQuery()) !== null);
49 | }
50 |
51 | /**
52 | * JSON Schema for a the query parameters
53 | *
54 | * @return \stdClass
55 | */
56 | public function getQueryParametersSchema()
57 | {
58 | return $this->getSchema($this->getQuery());
59 | }
60 |
61 | /**
62 | * @return bool
63 | */
64 | public function hasHeadersSchema()
65 | {
66 | return ($this->getHeadersSchema() !== null);
67 | }
68 |
69 | /**
70 | * JSON Schema for the headers
71 | *
72 | * @return \stdClass
73 | */
74 | public function getHeadersSchema()
75 | {
76 | return $this->getSchema($this->getHeaders());
77 | }
78 |
79 | /**
80 | * @return Parameter[]
81 | */
82 | public function getPath()
83 | {
84 | return $this->findByLocation('path');
85 | }
86 |
87 | /**
88 | * @return Parameter[]
89 | */
90 | public function getQuery()
91 | {
92 | return $this->findByLocation('query');
93 | }
94 |
95 | /**
96 | * @return Parameter[]
97 | */
98 | public function getHeaders()
99 | {
100 | return $this->findByLocation('header');
101 | }
102 |
103 | /**
104 | * @return Parameter|null
105 | */
106 | public function getBody()
107 | {
108 | $match = $this->findByLocation('body');
109 | if (empty($match)) {
110 | return null;
111 | }
112 |
113 | return current($match);
114 | }
115 |
116 | /**
117 | * Get one request parameter by name
118 | *
119 | * @param string $name
120 | * @return Parameter|null
121 | */
122 | public function getByName($name)
123 | {
124 | if (! isset($this->parameters[$name])) {
125 | return null;
126 | }
127 |
128 | return $this->parameters[$name];
129 | }
130 |
131 | /**
132 | * @param Parameter[] $parameters
133 | *
134 | * @return \stdClass|null
135 | */
136 | private function getSchema(array $parameters)
137 | {
138 | if (empty($parameters)) {
139 | return null;
140 | }
141 |
142 | $schema = new \stdClass();
143 | $schema->type = 'object';
144 | $schema->required = [];
145 | $schema->properties = new \stdClass();
146 | foreach ($parameters as $name => $parameter) {
147 | if ($parameter->isRequired()) {
148 | $schema->required[] = $parameter->getName();
149 | }
150 | $schema->properties->{$name} = $parameter->getSchema();
151 | }
152 |
153 | return $schema;
154 | }
155 |
156 | public function serialize()
157 | {
158 | return serialize(['parameters' => $this->parameters]);
159 | }
160 |
161 | public function unserialize($serialized)
162 | {
163 | $data = unserialize($serialized);
164 | $this->parameters = $data['parameters'];
165 | }
166 |
167 | private function findByLocation($location)
168 | {
169 | return array_filter(
170 | $this->parameters,
171 | function (Parameter $parameter) use ($location) {
172 | return $parameter->getLocation() === $location;
173 | }
174 | );
175 | }
176 |
177 | private function addParameter(Parameter $parameter)
178 | {
179 | if (!in_array($parameter->getLocation(), Parameter::LOCATIONS, true)) {
180 | throw new \InvalidArgumentException(
181 | sprintf(
182 | '%s is not a supported parameter location, supported: %s',
183 | $parameter->getLocation(),
184 | implode(', ', Parameter::LOCATIONS)
185 | )
186 | );
187 | }
188 |
189 | $this->parameters[$parameter->getName()] = $parameter;
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/Definition/RequestDefinition.php:
--------------------------------------------------------------------------------
1 | method = $method;
35 | $this->operationId = $operationId;
36 | $this->pathTemplate = $pathTemplate;
37 | $this->parameters = $parameters;
38 | $this->contentTypes = $contentTypes;
39 | foreach ($responses as $response) {
40 | $this->addResponseDefinition($response);
41 | }
42 | }
43 |
44 | /**
45 | * @return string
46 | */
47 | public function getMethod()
48 | {
49 | return $this->method;
50 | }
51 |
52 | /**
53 | * @return string
54 | */
55 | public function getOperationId()
56 | {
57 | return $this->operationId;
58 | }
59 |
60 | /**
61 | * @return string
62 | */
63 | public function getPathTemplate()
64 | {
65 | return $this->pathTemplate;
66 | }
67 |
68 | /**
69 | * @return Parameters
70 | */
71 | public function getRequestParameters()
72 | {
73 | return $this->parameters;
74 | }
75 |
76 | /**
77 | * Supported content types
78 | *
79 | * @return array
80 | */
81 | public function getContentTypes()
82 | {
83 | return $this->contentTypes;
84 | }
85 |
86 | /**
87 | * @param $statusCode
88 | * @return ResponseDefinition
89 | */
90 | public function getResponseDefinition($statusCode)
91 | {
92 | if (isset($this->responses[$statusCode])) {
93 | return $this->responses[$statusCode];
94 | } else if (isset($this->responses['default'])) {
95 | return $this->responses['default'];
96 | } else {
97 | throw new \InvalidArgumentException(
98 | sprintf(
99 | 'No response definition for %s %s is available for status code %s',
100 | $this->method,
101 | $this->pathTemplate,
102 | $statusCode
103 | )
104 | );
105 | }
106 | }
107 |
108 | public function hasBodySchema()
109 | {
110 | return $this->parameters->hasBodySchema();
111 | }
112 |
113 | public function getBodySchema()
114 | {
115 | return $this->parameters->getBodySchema();
116 | }
117 |
118 | public function hasHeadersSchema()
119 | {
120 | return $this->parameters->hasHeadersSchema();
121 | }
122 |
123 | public function getHeadersSchema()
124 | {
125 | return $this->parameters->getHeadersSchema();
126 | }
127 |
128 | public function hasQueryParametersSchema()
129 | {
130 | return $this->parameters->hasQueryParametersSchema();
131 | }
132 |
133 | public function getQueryParametersSchema()
134 | {
135 | return $this->parameters->getQueryParametersSchema();
136 | }
137 |
138 | private function addResponseDefinition(ResponseDefinition $response)
139 | {
140 | $this->responses[$response->getStatusCode()] = $response;
141 | }
142 |
143 | public function serialize()
144 | {
145 | return serialize([
146 | 'method' => $this->method,
147 | 'operationId' => $this->operationId,
148 | 'pathTemplate' => $this->pathTemplate,
149 | 'parameters' => $this->parameters,
150 | 'contentTypes' => $this->contentTypes,
151 | 'responses' => $this->responses
152 | ]);
153 | }
154 |
155 | public function unserialize($serialized)
156 | {
157 | $data = unserialize($serialized);
158 | $this->method = $data['method'];
159 | $this->operationId = $data['operationId'];
160 | $this->pathTemplate = $data['pathTemplate'];
161 | $this->parameters = $data['parameters'];
162 | $this->contentTypes = $data['contentTypes'];
163 | $this->responses = $data['responses'];
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/Definition/RequestDefinitions.php:
--------------------------------------------------------------------------------
1 | addRequestDefinition($requestDefinition);
15 | }
16 | }
17 |
18 | /**
19 | * @param string $operationId
20 | *
21 | * @return RequestDefinition
22 | */
23 | public function getRequestDefinition($operationId)
24 | {
25 | if (!isset($this->definitions[$operationId])) {
26 | throw new \InvalidArgumentException('Unable to find request definition for operationId '.$operationId);
27 | }
28 |
29 | return $this->definitions[$operationId];
30 | }
31 |
32 | private function addRequestDefinition(RequestDefinition $requestDefinition)
33 | {
34 | $this->definitions[$requestDefinition->getOperationId()] = $requestDefinition;
35 | }
36 |
37 | /**
38 | * @return \ArrayIterator|RequestDefinition[]
39 | */
40 | public function getIterator()
41 | {
42 | return new \ArrayIterator($this->definitions);
43 | }
44 |
45 | public function serialize()
46 | {
47 | return serialize([
48 | 'definitions' => $this->definitions
49 | ]);
50 | }
51 |
52 | public function unserialize($serialized)
53 | {
54 | $data = unserialize($serialized);
55 | $this->definitions = $data['definitions'];
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Definition/ResponseDefinition.php:
--------------------------------------------------------------------------------
1 | statusCode = $statusCode;
23 | $this->contentTypes = $allowedContentTypes;
24 | $this->parameters = $parameters;
25 | }
26 |
27 | /**
28 | * @return int
29 | */
30 | public function getStatusCode()
31 | {
32 | return $this->statusCode;
33 | }
34 |
35 | /**
36 | * @return bool
37 | */
38 | public function hasBodySchema()
39 | {
40 | return $this->parameters->hasBodySchema();
41 | }
42 |
43 | /**
44 | * @return \stdClass
45 | */
46 | public function getBodySchema()
47 | {
48 | return $this->parameters->getBodySchema();
49 | }
50 |
51 | public function hasHeadersSchema()
52 | {
53 | return $this->parameters->hasHeadersSchema();
54 | }
55 |
56 | public function getHeadersSchema()
57 | {
58 | return $this->parameters->getHeadersSchema();
59 | }
60 |
61 | /**
62 | * Supported response types
63 | * @return array
64 | */
65 | public function getContentTypes()
66 | {
67 | return $this->contentTypes;
68 | }
69 |
70 | public function serialize()
71 | {
72 | return serialize([
73 | 'statusCode' => $this->statusCode,
74 | 'contentTypes' => $this->contentTypes,
75 | 'parameters' => $this->parameters
76 | ]);
77 | }
78 |
79 | public function unserialize($serialized)
80 | {
81 | $data = unserialize($serialized);
82 | $this->statusCode = $data['statusCode'];
83 | $this->contentTypes = $data['contentTypes'];
84 | $this->parameters = $data['parameters'];
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Factory/CachedSchemaFactoryDecorator.php:
--------------------------------------------------------------------------------
1 | cache = $cache;
20 | $this->schemaFactory = $schemaFactory;
21 | }
22 |
23 | public function createSchema($schemaFile)
24 | {
25 | $cacheKey = hash('sha1', $schemaFile);
26 | $item = $this->cache->getItem($cacheKey);
27 | if ($item->isHit()) {
28 | $schema = $item->get();
29 | } else {
30 | $schema = $this->schemaFactory->createSchema($schemaFile);
31 | $this->cache->save($item->set($schema));
32 | }
33 |
34 | return $schema;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Factory/SchemaFactory.php:
--------------------------------------------------------------------------------
1 | resolveSchemaFile($schemaFile);
29 |
30 | $host = isset($schema->host) ? $schema->host : null;
31 | $basePath = isset($schema->basePath) ? $schema->basePath : '';
32 | $schemes = isset($schema->schemes) ? $schema->schemes : ['http'];
33 |
34 | return new Schema(
35 | $this->createRequestDefinitions($schema),
36 | $basePath,
37 | $host,
38 | $schemes
39 | );
40 | }
41 |
42 | /**
43 | *
44 | * @param string $schemaFile
45 | *
46 | * @return object
47 | */
48 | protected function resolveSchemaFile($schemaFile)
49 | {
50 | $extension = pathinfo($schemaFile, PATHINFO_EXTENSION);
51 | switch ($extension) {
52 | case 'yml':
53 | case 'yaml':
54 | if (!class_exists(Yaml::class)) {
55 | // @codeCoverageIgnoreStart
56 | throw new \InvalidArgumentException(
57 | 'You need to require the "symfony/yaml" component in order to parse yml files'
58 | );
59 | // @codeCoverageIgnoreEnd
60 | }
61 |
62 | $uriRetriever = new YamlUriRetriever();
63 | break;
64 | case 'json':
65 | $uriRetriever = new UriRetriever();
66 | break;
67 | default:
68 | throw new \InvalidArgumentException(
69 | sprintf(
70 | 'file "%s" does not provide a supported extension choose either json, yml or yaml',
71 | $schemaFile
72 | )
73 | );
74 | }
75 |
76 | $refResolver = new RefResolver(
77 | $uriRetriever,
78 | new UriResolver()
79 | );
80 |
81 | return $refResolver->resolve($schemaFile);
82 | }
83 |
84 | /**
85 | * @param \stdClass $schema
86 | * @return RequestDefinitions
87 | */
88 | protected function createRequestDefinitions(\stdClass $schema)
89 | {
90 | $definitions = [];
91 | $defaultConsumedContentTypes = [];
92 | $defaultProducedContentTypes = [];
93 |
94 | if (isset($schema->consumes)) {
95 | $defaultConsumedContentTypes = $schema->consumes;
96 | }
97 | if (isset($schema->produces)) {
98 | $defaultProducedContentTypes = $schema->produces;
99 | }
100 |
101 | $basePath = isset($schema->basePath) ? $schema->basePath : '';
102 |
103 | foreach ($schema->paths as $pathTemplate => $methods) {
104 | foreach ($methods as $method => $definition) {
105 | $method = strtoupper($method);
106 | $contentTypes = $defaultConsumedContentTypes;
107 | if (isset($definition->consumes)) {
108 | $contentTypes = $definition->consumes;
109 | }
110 |
111 | if (!isset($definition->operationId)) {
112 | throw new \LogicException(
113 | sprintf(
114 | 'You need to provide an operationId for %s %s',
115 | $method,
116 | $pathTemplate
117 | )
118 | );
119 | }
120 |
121 | if (empty($contentTypes) && $this->containsBodyParametersLocations($definition)) {
122 | $contentTypes = $this->guessSupportedContentTypes($definition, $pathTemplate);
123 | }
124 |
125 | if (!isset($definition->responses)) {
126 | throw new \LogicException(
127 | sprintf(
128 | 'You need to specify at least one response for %s %s',
129 | $method,
130 | $pathTemplate
131 | )
132 | );
133 | }
134 |
135 | if (!isset($definition->parameters)) {
136 | $definition->parameters = [];
137 | }
138 |
139 | $requestParameters = [];
140 | foreach ($definition->parameters as $parameter) {
141 | $requestParameters[] = $this->createParameter($parameter);
142 | }
143 |
144 | $responseContentTypes = $defaultProducedContentTypes;
145 | if (isset($definition->produces)) {
146 | $responseContentTypes = $definition->produces;
147 | }
148 |
149 | $responseDefinitions = [];
150 | foreach ($definition->responses as $statusCode => $response) {
151 | $responseDefinitions[] = $this->createResponseDefinition(
152 | $statusCode,
153 | $responseContentTypes,
154 | $response
155 | );
156 | }
157 |
158 | $definitions[] = new RequestDefinition(
159 | $method,
160 | $definition->operationId,
161 | $basePath.$pathTemplate,
162 | new Parameters($requestParameters),
163 | $contentTypes,
164 | $responseDefinitions
165 | );
166 | }
167 | }
168 |
169 | return new RequestDefinitions($definitions);
170 | }
171 |
172 | /**
173 | * @return bool
174 | */
175 | private function containsBodyParametersLocations(\stdClass $definition)
176 | {
177 | if (!isset($definition->parameters)) {
178 | return false;
179 | }
180 |
181 | foreach ($definition->parameters as $parameter) {
182 | if (isset($parameter->in) && \in_array($parameter->in, Parameter::BODY_LOCATIONS, true)) {
183 | return true;
184 | }
185 | }
186 |
187 | return false;
188 | }
189 |
190 | /**
191 | * @param \stdClass $definition
192 | * @param string $pathTemplate
193 | *
194 | * @return array
195 | */
196 | private function guessSupportedContentTypes(\stdClass $definition, $pathTemplate)
197 | {
198 | if (!isset($definition->parameters)) {
199 | return [];
200 | }
201 |
202 | $bodyLocations = [];
203 | foreach ($definition->parameters as $parameter) {
204 | if (isset($parameter->in) && \in_array($parameter->in, Parameter::BODY_LOCATIONS, true)) {
205 | $bodyLocations[] = $parameter->in;
206 | }
207 | }
208 |
209 | if (count($bodyLocations) > 1) {
210 | throw new \LogicException(
211 | sprintf(
212 | 'Parameters cannot have %s locations at the same time in %s',
213 | implode(' and ', $bodyLocations),
214 | $pathTemplate
215 | )
216 | );
217 | }
218 |
219 | if (count($bodyLocations) === 1) {
220 | return [Parameter::BODY_LOCATIONS_TYPES[current($bodyLocations)]];
221 | }
222 |
223 | return [];
224 | }
225 |
226 | protected function createResponseDefinition($statusCode, array $defaultProducedContentTypes, \stdClass $response)
227 | {
228 | $allowedContentTypes = $defaultProducedContentTypes;
229 | $parameters = [];
230 | if (isset($response->schema)) {
231 | $parameters[] = $this->createParameter((object) [
232 | 'in' => 'body',
233 | 'name' => 'body',
234 | 'required' => true,
235 | 'schema' => $response->schema
236 | ]);
237 | }
238 |
239 | if (isset($response->headers)) {
240 | foreach ($response->headers as $headerName => $schema) {
241 | $schema->in = 'header';
242 | $schema->name = $headerName;
243 | $schema->required = true;
244 | $parameters[] = $this->createParameter($schema);
245 | }
246 | }
247 |
248 | return new ResponseDefinition($statusCode, $allowedContentTypes, new Parameters($parameters));
249 | }
250 |
251 | /**
252 | * Create a Parameter from a swagger parameter
253 | *
254 | * @param \stdClass $parameter
255 | *
256 | * @return Parameter
257 | */
258 | protected function createParameter(\stdClass $parameter)
259 | {
260 | $parameter = get_object_vars($parameter);
261 | $location = $parameter['in'];
262 | $name = $parameter['name'];
263 | $schema = isset($parameter['schema']) ? $parameter['schema'] : new \stdClass();
264 | $required = isset($parameter['required']) ? $parameter['required'] : false;
265 |
266 | unset($parameter['in']);
267 | unset($parameter['name']);
268 | unset($parameter['required']);
269 | unset($parameter['schema']);
270 |
271 | // Every remaining parameter may be json schema properties
272 | foreach ($parameter as $key => $value) {
273 | $schema->{$key} = $value;
274 | }
275 |
276 | // It's not relevant to validate file type
277 | if (isset($schema->format) && $schema->format === 'file') {
278 | $schema = null;
279 | }
280 |
281 | return new Parameter($location, $name, $required, $schema);
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/src/JsonSchema/Uri/YamlUriRetriever.php:
--------------------------------------------------------------------------------
1 | schemaCache[$fetchUri])) {
21 | return $this->schemaCache[$fetchUri];
22 | }
23 |
24 | $contents = $this->getUriRetriever()->retrieve($fetchUri);
25 |
26 | $contents = Yaml::parse($contents);
27 | $jsonSchema = json_decode(json_encode($contents));
28 |
29 | $this->schemaCache[$fetchUri] = $jsonSchema;
30 |
31 | return $jsonSchema;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Normalizer/QueryParamsNormalizer.php:
--------------------------------------------------------------------------------
1 | properties as $name => $queryParamSchema) {
17 | $type = isset($queryParamSchema->type)
18 | ? $queryParamSchema->type
19 | : 'string';
20 |
21 | if (array_key_exists($name, $queryParams)) {
22 | switch ($type) {
23 | case 'boolean':
24 | if ($queryParams[$name] === 'false') {
25 | $queryParams[$name] = false;
26 | }
27 | if ($queryParams[$name] === 'true') {
28 | $queryParams[$name] = true;
29 | }
30 | if (in_array($queryParams[$name], ['0', '1'])) {
31 | $queryParams[$name] = (bool) $queryParams[$name];
32 | }
33 | break;
34 | case 'integer':
35 | if (is_numeric($queryParams[$name])) {
36 | $queryParams[$name] = (int) $queryParams[$name];
37 | }
38 | break;
39 | case 'number':
40 | if (is_numeric($queryParams[$name])) {
41 | $queryParams[$name] = (float) $queryParams[$name];
42 | }
43 | break;
44 | }
45 |
46 | if (isset($queryParamSchema->collectionFormat)) {
47 | switch ($queryParamSchema->collectionFormat) {
48 | case 'csv':
49 | $separator = ',';
50 | break;
51 | case 'ssv':
52 | $separator = ' ';
53 | break;
54 | case 'pipes':
55 | $separator = '|';
56 | break;
57 | case 'tsv':
58 | $separator = "\t";
59 | break;
60 | default:
61 | throw new \InvalidArgumentException(
62 | sprintf(
63 | '%s is not a supported query collection format',
64 | $queryParamSchema->collectionFormat
65 | )
66 | );
67 | }
68 |
69 | $queryParams[$name] = explode($separator, $queryParams[$name]);
70 | }
71 | }
72 | }
73 |
74 | return $queryParams;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Schema.php:
--------------------------------------------------------------------------------
1 | addRequestDefinition($request);
32 | }
33 | $this->host = $host;
34 | $this->basePath = $basePath;
35 | $this->schemes = $schemes;
36 | }
37 |
38 | /**
39 | * Find the operationId associated to a given path and method
40 | *
41 | * @todo Implement a less expensive finder
42 | * @param string $method An HTTP method
43 | * @param string $path A path (ex: /foo/1)
44 | *
45 | * @return string The operationId
46 | */
47 | public function findOperationId($method, $path)
48 | {
49 | $uriTemplateManager = new UriTemplate();
50 | foreach ($this->requestDefinitions as $requestDefinition) {
51 | if ($requestDefinition->getMethod() !== $method) {
52 | continue;
53 | }
54 | $params = $uriTemplateManager->extract($requestDefinition->getPathTemplate(), $path, true);
55 | if ($params !== null) {
56 | return $requestDefinition->getOperationId();
57 | }
58 | }
59 |
60 | throw new \InvalidArgumentException('Unable to resolve the operationId for path ' . $path);
61 | }
62 |
63 | public function getRequestDefinition($operationId)
64 | {
65 | if (!isset($this->requestDefinitions[$operationId])) {
66 | throw new \InvalidArgumentException('Unable to get the request definition for '.$operationId);
67 | }
68 |
69 | return $this->requestDefinitions[$operationId];
70 | }
71 |
72 | /**
73 | * @return string
74 | */
75 | public function getHost()
76 | {
77 | return $this->host;
78 | }
79 |
80 | /**
81 | * @return string
82 | */
83 | public function getBasePath()
84 | {
85 | return $this->basePath;
86 | }
87 |
88 | /**
89 | * @return array
90 | */
91 | public function getSchemes()
92 | {
93 | return $this->schemes;
94 | }
95 |
96 | public function serialize()
97 | {
98 | return serialize([
99 | 'host' => $this->host,
100 | 'basePath' => $this->basePath,
101 | 'schemes' => $this->schemes,
102 | 'requests' => $this->requestDefinitions
103 | ]);
104 | }
105 |
106 | public function unserialize($serialized)
107 | {
108 | $data = unserialize($serialized);
109 | $this->host = $data['host'];
110 | $this->basePath = $data['basePath'];
111 | $this->schemes = $data['schemes'];
112 | $this->requestDefinitions = $data['requests'];
113 | }
114 |
115 | private function addRequestDefinition(RequestDefinition $request)
116 | {
117 | $this->requestDefinitions[$request->getOperationId()] = $request;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Validator/ConstraintViolation.php:
--------------------------------------------------------------------------------
1 | property = $property;
30 | $this->message = $message;
31 | $this->constraint = $constraint;
32 | $this->location = $location;
33 | }
34 |
35 | /**
36 | * @return string
37 | */
38 | public function getProperty()
39 | {
40 | return $this->property;
41 | }
42 |
43 | /**
44 | * @return string
45 | */
46 | public function getMessage()
47 | {
48 | return $this->message;
49 | }
50 |
51 | /**
52 | * @return string
53 | */
54 | public function getConstraint()
55 | {
56 | return $this->constraint;
57 | }
58 |
59 | /**
60 | * @return string
61 | */
62 | public function getLocation()
63 | {
64 | return $this->location;
65 | }
66 |
67 | public function toArray()
68 | {
69 | return [
70 | 'property' => $this->getProperty(),
71 | 'message' => $this->getMessage(),
72 | 'constraint' => $this->getConstraint(),
73 | 'location' => $this->getLocation()
74 | ];
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Validator/Exception/ConstraintViolations.php:
--------------------------------------------------------------------------------
1 | violations = $violations;
16 | parent::__construct((string) $this);
17 | }
18 |
19 | public function getViolations()
20 | {
21 | return $this->violations;
22 | }
23 |
24 | /**
25 | * @return string
26 | */
27 | public function __toString()
28 | {
29 | $message = "Request constraint violations:\n";
30 | foreach ($this->violations as $violation) {
31 | $message .= sprintf(
32 | "[property]: %s\n[message]: %s\n[constraint]: %s\n[location]: %s\n\n",
33 | $violation->getProperty(),
34 | $violation->getMessage(),
35 | $violation->getConstraint(),
36 | $violation->getLocation()
37 | );
38 | }
39 |
40 | return $message;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Validator/MessageValidator.php:
--------------------------------------------------------------------------------
1 | validator = $validator;
30 | $this->decoder = $decoder;
31 | }
32 |
33 | public function validateRequest(RequestInterface $request, RequestDefinition $definition)
34 | {
35 | if ($definition->hasBodySchema()) {
36 | $contentTypeValid = $this->validateContentType($request, $definition);
37 | if ($contentTypeValid && in_array($request->getMethod(), ['PUT', 'PATCH', 'POST'])) {
38 | $this->validateMessageBody($request, $definition);
39 | }
40 | }
41 |
42 | $this->validateHeaders($request, $definition);
43 | $this->validateQueryParameters($request, $definition);
44 | }
45 |
46 | public function validateResponse(ResponseInterface $response, RequestDefinition $definition)
47 | {
48 | $responseDefinition = $definition->getResponseDefinition($response->getStatusCode());
49 | if ($responseDefinition->hasBodySchema()) {
50 | $contentTypeValid = $this->validateContentType($response, $responseDefinition);
51 | if ($contentTypeValid) {
52 | $this->validateMessageBody($response, $responseDefinition);
53 | }
54 | }
55 |
56 | $this->validateHeaders($response, $responseDefinition);
57 | }
58 |
59 | public function validateMessageBody(MessageInterface $message, MessageDefinition $definition)
60 | {
61 | if ($message instanceof ServerRequestInterface) {
62 | $bodyString = json_encode((array) $message->getParsedBody());
63 | } else {
64 | $bodyString = (string) $message->getBody();
65 | }
66 | if ($bodyString !== '' && $definition->hasBodySchema()) {
67 | $contentType = $message->getHeaderLine('Content-Type');
68 | $decodedBody = $this->decoder->decode(
69 | $bodyString,
70 | DecoderUtils::extractFormatFromContentType($contentType)
71 | );
72 |
73 | $this->validate($decodedBody, $definition->getBodySchema(), 'body');
74 | }
75 | }
76 |
77 | public function validateHeaders(MessageInterface $message, MessageDefinition $definition)
78 | {
79 | if ($definition->hasHeadersSchema()) {
80 | // Transform each header values into a string
81 | $headers = array_map(
82 | function (array $values) {
83 | return implode(', ', $values);
84 | },
85 | $message->getHeaders()
86 | );
87 |
88 | $this->validate(
89 | (object) array_change_key_case($headers, CASE_LOWER),
90 | $definition->getHeadersSchema(),
91 | 'header'
92 | );
93 | }
94 | }
95 |
96 | /**
97 | * Validate an HTTP message content-type against a message definition
98 | *
99 | * @param MessageInterface $message
100 | * @param MessageDefinition $definition
101 | *
102 | * @return bool When the content-type is valid
103 | */
104 | public function validateContentType(MessageInterface $message, MessageDefinition $definition)
105 | {
106 | $contentType = $message->getHeaderLine('Content-Type');
107 | $contentTypes = $definition->getContentTypes();
108 |
109 | if (!in_array($contentType, $contentTypes, true)) {
110 | if ($contentType === '') {
111 | $violationMessage = 'Content-Type should not be empty';
112 | $constraint = 'required';
113 | } else {
114 | $violationMessage = sprintf(
115 | '%s is not a supported content type, supported: %s',
116 | $message->getHeaderLine('Content-Type'),
117 | implode(', ', $contentTypes)
118 | );
119 | $constraint = 'enum';
120 | }
121 |
122 | $this->addViolation(
123 | new ConstraintViolation(
124 | 'Content-Type',
125 | $violationMessage,
126 | $constraint,
127 | 'header'
128 | )
129 | );
130 |
131 | return false;
132 | }
133 |
134 | return true;
135 | }
136 |
137 | public function validateQueryParameters(RequestInterface $request, RequestDefinition $definition)
138 | {
139 | if ($definition->hasQueryParametersSchema()) {
140 | parse_str($request->getUri()->getQuery(), $queryParams);
141 | $schema = $definition->getQueryParametersSchema();
142 | $queryParams = QueryParamsNormalizer::normalize($queryParams, $schema);
143 |
144 | $this->validate(
145 | (object) $queryParams,
146 | $schema,
147 | 'query'
148 | );
149 | }
150 | }
151 |
152 | /**
153 | * @return bool
154 | */
155 | public function hasViolations()
156 | {
157 | return !empty($this->violations);
158 | }
159 |
160 | /**
161 | * @return ConstraintViolation[]
162 | */
163 | public function getViolations()
164 | {
165 | return $this->violations;
166 | }
167 |
168 | /**
169 | * @param mixed $data
170 | * @param \stdClass $schema
171 | * @param string $location (possible values: query, path, body, headers)
172 | */
173 | protected function validate($data, $schema, $location)
174 | {
175 | $this->validator->check($data, $schema);
176 | if (! $this->validator->isValid()) {
177 | $violations = array_map(
178 | function ($error) use ($location) {
179 | return new ConstraintViolation(
180 | $error['property'],
181 | $error['message'],
182 | $error['constraint'],
183 | $location
184 | );
185 | },
186 | $this->validator->getErrors()
187 | );
188 |
189 | foreach ($violations as $violation) {
190 | $this->addViolation($violation);
191 | }
192 | }
193 |
194 | $this->validator->reset();
195 | }
196 |
197 | protected function addViolation(ConstraintViolation $violation)
198 | {
199 | $this->violations[] = $violation;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/tests/Decoder/Adapter/SymfonyDecoderAdapterTest.php:
--------------------------------------------------------------------------------
1 | 0, 'foo' => 'foo1'],
15 | (object) ['@key' => 1, 'foo' => 'foo2'],
16 | ];
17 |
18 | $data = '- foo1
- foo2
';
19 | $decoder = new SymfonyDecoderAdapter(new XmlEncoder());
20 | $actual = $decoder->decode($data, 'xml');
21 |
22 | assertThat($actual, equalTo($expected));
23 | }
24 |
25 | /** @test */
26 | public function itShouldDecodeAJsonStringIntoAnArrayOfObject()
27 | {
28 | $expected = [
29 | (object) ['foo' => 'foo1'],
30 | (object) ['foo' => 'foo2'],
31 | ];
32 |
33 | $data = '[{"foo": "foo1"}, {"foo": "foo2"}]';
34 |
35 | $decoder = new SymfonyDecoderAdapter(new JsonDecode(true));
36 | $actual = $decoder->decode($data, 'json');
37 |
38 | assertThat($actual, equalTo($expected));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Decoder/DecoderUtilsTest.php:
--------------------------------------------------------------------------------
1 | prophesize(ResponseDefinition::class);
28 | $responseDefinition->getStatusCode()->willReturn(200);
29 |
30 | $requestDefinition = new RequestDefinition(
31 | 'GET',
32 | 'getFoo',
33 | '/foo/{id}',
34 | new Parameters([]),
35 | ['application/json'],
36 | [$responseDefinition->reveal()]
37 | );
38 |
39 | assertThat($requestDefinition->getResponseDefinition(200), self::isInstanceOf(ResponseDefinition::class));
40 | }
41 |
42 | /** @test */
43 | public function itProvideAResponseDefinitionUsingDefaultValue()
44 | {
45 | $statusCodes = [200, 'default'];
46 | $responseDefinitions = [];
47 | foreach ($statusCodes as $statusCode) {
48 | $responseDefinition = $this->prophesize(ResponseDefinition::class);
49 | $responseDefinition->getStatusCode()->willReturn($statusCode);
50 | $responseDefinitions[] = $responseDefinition->reveal();
51 | }
52 |
53 | $requestDefinition = new RequestDefinition(
54 | 'GET',
55 | 'getFoo',
56 | '/foo/{id}',
57 | new Parameters([]),
58 | ['application/json'],
59 | $responseDefinitions
60 | );
61 |
62 | assertThat($requestDefinition->getResponseDefinition(500), self::isInstanceOf(ResponseDefinition::class));
63 | }
64 |
65 | /** @test */
66 | public function itThrowAnExceptionWhenNoResponseDefinitionIsFound()
67 | {
68 | $this->expectException(\InvalidArgumentException::class);
69 | $this->expectExceptionMessage('No response definition for GET /foo/{id} is available for status code 200');
70 |
71 | $requestDefinition = new RequestDefinition(
72 | 'GET',
73 | 'getFoo',
74 | '/foo/{id}',
75 | new Parameters([]),
76 | ['application/json'],
77 | []
78 | );
79 |
80 | $requestDefinition->getResponseDefinition(200);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/Definition/RequestDefinitionsTest.php:
--------------------------------------------------------------------------------
1 | prophesize(RequestDefinition::class);
21 | $requestDefinition->getOperationId()->willReturn('getFoo');
22 |
23 | $requestDefinitions = new RequestDefinitions([$requestDefinition->reveal()]);
24 |
25 | assertThat($requestDefinitions->getRequestDefinition('getFoo'), self::isInstanceOf(RequestDefinition::class));
26 | }
27 |
28 | /** @test */
29 | public function itThrowAnExceptionWhenNoRequestDefinitionIsFound()
30 | {
31 | $this->expectException(\InvalidArgumentException::class);
32 | $this->expectExceptionMessage('Unable to find request definition for operationId getFoo');
33 |
34 | $requestDefinitions = new RequestDefinitions([]);
35 | $requestDefinitions->getRequestDefinition('getFoo');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/Definition/RequestParameterTest.php:
--------------------------------------------------------------------------------
1 | hasSchema());
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/Definition/RequestParametersTest.php:
--------------------------------------------------------------------------------
1 | prophesize(Parameter::class);
12 | $requestParameter->getLocation()->willReturn('query');
13 | $requestParameter->getName()->willReturn('foo');
14 |
15 | $requestParameters = new Parameters([$requestParameter->reveal()]);
16 |
17 | assertThat($requestParameters, isInstanceOf(\Traversable::class));
18 | assertThat($requestParameters, containsOnlyInstancesOf(Parameter::class));
19 | }
20 |
21 | /** @test */
22 | public function itCanBeSerialized()
23 | {
24 | $requestParameters = new Parameters([]);
25 | $serialized = serialize($requestParameters);
26 |
27 | assertThat(unserialize($serialized), self::equalTo($requestParameters));
28 | }
29 |
30 | /** @test */
31 | public function itThrowAnExceptionOnUnsupportedParameterLocation()
32 | {
33 | $this->expectException(\InvalidArgumentException::class);
34 | $this->expectExceptionMessage(
35 | 'nowhere is not a supported parameter location, ' .
36 | 'supported: path, header, query, body, formData'
37 | );
38 |
39 | $param = $this->prophesize(Parameter::class);
40 | $param->getName()->willreturn('foo');
41 | $param->getLocation()->willreturn('nowhere');
42 |
43 | new Parameters([$param->reveal()]);
44 | }
45 |
46 | /** @test */
47 | public function itCanResolveARequestParameterByName()
48 | {
49 | $requestParameter = $this->prophesize(Parameter::class);
50 | $requestParameter->getLocation()->willReturn('query');
51 | $requestParameter->getName()->willReturn('foo');
52 |
53 | $requestParameters = new Parameters([$requestParameter->reveal()]);
54 |
55 | assertThat($requestParameters->getByName('foo'), equalTo($requestParameter->reveal()));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Definition/ResponseDefinitionTest.php:
--------------------------------------------------------------------------------
1 | prophesize(Schema::class);
17 |
18 | $item = $this->prophesize(CacheItemInterface::class);
19 | $item->isHit()->shouldBeCalled()->willReturn(false);
20 | $item->set($schema)->shouldBeCalled()->willReturn($item);
21 |
22 | $cache = $this->prophesize(CacheItemPoolInterface::class);
23 | $cache->getItem('3f470a326a5926a2e323aaadd767c0e64302a080')->willReturn($item);
24 | $cache->save($item)->willReturn(true);
25 |
26 | $schemaFactory = $this->prophesize(SchemaFactory::class);
27 | $schemaFactory->createSchema($schemaFile)->willReturn($schema);
28 |
29 | $cachedSchema = new CachedSchemaFactoryDecorator(
30 | $cache->reveal(),
31 | $schemaFactory->reveal()
32 | );
33 |
34 | $expectedSchema = $schema->reveal();
35 | $actualSchema = $cachedSchema->createSchema($schemaFile);
36 |
37 | assertThat($actualSchema, isInstanceOf(Schema::class));
38 | assertThat($actualSchema, equalTo($expectedSchema));
39 | }
40 |
41 | /** @test */
42 | public function itShouldLoadASchemaFromACacheStore()
43 | {
44 | $schemaFile = 'file://fake-schema.yml';
45 | $schema = $this->prophesize(Schema::class);
46 |
47 | $item = $this->prophesize(CacheItemInterface::class);
48 | $item->isHit()->shouldBeCalled()->willReturn(true);
49 | $item->get()->shouldBeCalled()->willReturn($schema);
50 |
51 | $cache = $this->prophesize(CacheItemPoolInterface::class);
52 | $cache->getItem('3f470a326a5926a2e323aaadd767c0e64302a080')->willReturn($item);
53 |
54 | $schemaFactory = $this->prophesize(SchemaFactory::class);
55 | $schemaFactory->createSchema(Argument::any())->shouldNotBeCalled();
56 |
57 | $cachedSchema = new CachedSchemaFactoryDecorator(
58 | $cache->reveal(),
59 | $schemaFactory->reveal()
60 | );
61 |
62 | $expectedSchema = $schema->reveal();
63 | $actualSchema = $cachedSchema->createSchema($schemaFile);
64 |
65 | assertThat($actualSchema, isInstanceOf(Schema::class));
66 | assertThat($actualSchema, equalTo($expectedSchema));
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/Factory/SwaggerSchemaFactoryTest.php:
--------------------------------------------------------------------------------
1 | getPetStoreSchemaJson();
17 |
18 | assertThat($schema, isInstanceOf(Schema::class));
19 | }
20 |
21 | /** @test */
22 | public function itCanCreateASchemaFromAYamlFile()
23 | {
24 | $schema = $this->getPetStoreSchemaYaml();
25 |
26 | assertThat($schema, isInstanceOf(Schema::class));
27 | }
28 |
29 | /** @test */
30 | public function itThrowAnExceptionWhenTheSchemaFileIsNotSupported()
31 | {
32 | $unsupportedFile = 'file://'.dirname(__DIR__).'/fixtures/petstore.txt';
33 |
34 | $this->expectException(\InvalidArgumentException::class);
35 | $this->expectExceptionMessageRegExp('/does not provide a supported extension/');
36 |
37 | (new SwaggerSchemaFactory())->createSchema($unsupportedFile);
38 | }
39 |
40 | /** @test */
41 | public function itShouldHaveSchemaProperties()
42 | {
43 | $schema = $this->getPetStoreSchemaJson();
44 |
45 | assertThat($schema->getHost(), equalTo('petstore.swagger.io'));
46 | assertThat($schema->getBasePath(), equalTo('/v2'));
47 | assertThat($schema->getSchemes(), equalTo(['https', 'http']));
48 | }
49 |
50 | /** @test */
51 | public function itThrowAnExceptionWhenAnOperationDoesNotProvideAnId()
52 | {
53 | $this->expectException(\LogicException::class);
54 | $this->expectExceptionMessage('You need to provide an operationId for GET /something');
55 |
56 | $this->getSchemaFromFile('operation-without-an-id.json');
57 | }
58 |
59 | /** @test */
60 | public function itThrowAnExceptionWhenAnOperationDoesNotProvideResponses()
61 | {
62 | $this->expectException(\LogicException::class);
63 | $this->expectExceptionMessage('You need to specify at least one response for GET /something');
64 |
65 | $this->getSchemaFromFile('operation-without-responses.json');
66 | }
67 |
68 | /** @test */
69 | public function itSupportAnOperationWithoutParameters()
70 | {
71 | $schema = $this->getSchemaFromFile('operation-without-parameters.json');
72 | $definition = $schema->getRequestDefinition('getSomething');
73 |
74 | assertThat($definition->hasHeadersSchema(), isFalse());
75 | assertThat($definition->hasBodySchema(), isFalse());
76 | assertThat($definition->hasQueryParametersSchema(), isFalse());
77 | }
78 |
79 | /** @test */
80 | public function itCanCreateARequestDefinition()
81 | {
82 | $schema = $this->getPetStoreSchemaJson();
83 |
84 | $requestDefinition = $schema->getRequestDefinition('findPetsByStatus');
85 |
86 | assertThat($requestDefinition, isInstanceOf(RequestDefinition::class));
87 | assertThat($requestDefinition->getMethod(), equalTo('GET'));
88 | assertThat($requestDefinition->getOperationId(), equalTo('findPetsByStatus'));
89 | assertThat($requestDefinition->getPathTemplate(), equalTo('/v2/pet/findByStatus'));
90 | assertThat($requestDefinition->getContentTypes(), equalTo([]));
91 | assertThat($requestDefinition->getRequestParameters(), isInstanceOf(Parameters::class));
92 | assertThat($requestDefinition->getResponseDefinition(200), isInstanceOf(ResponseDefinition::class));
93 | assertThat($requestDefinition->getResponseDefinition(400), isInstanceOf(ResponseDefinition::class));
94 | }
95 |
96 | /** @test */
97 | public function itCanCreateARequestBodyParameter()
98 | {
99 | $schema = $this->getPetStoreSchemaJson();
100 |
101 | $requestParameters = $schema->getRequestDefinition('addPet')->getRequestParameters();
102 |
103 | assertThat($requestParameters, isInstanceOf(Parameters::class));
104 | assertThat($requestParameters->getBody(), isInstanceOf(Parameter::class));
105 | assertThat($requestParameters->hasBodySchema(), isTrue());
106 | assertThat($requestParameters->getBodySchema(), isType('object'));
107 | }
108 |
109 | /** @test */
110 | public function itCanCreateRequestPathParameters()
111 | {
112 | $schema = $this->getPetStoreSchemaJson();
113 |
114 | $requestParameters = $schema->getRequestDefinition('getPetById')->getRequestParameters();
115 |
116 | assertThat($requestParameters->getPath(), containsOnlyInstancesOf(Parameter::class));
117 | }
118 |
119 | /** @test */
120 | public function itCanCreateRequestQueryParameters()
121 | {
122 | $schema = $this->getPetStoreSchemaJson();
123 |
124 | $requestParameters = $schema->getRequestDefinition('findPetsByStatus')->getRequestParameters();
125 |
126 | assertThat($requestParameters->getQuery(), containsOnlyInstancesOf(Parameter::class));
127 | assertThat($requestParameters->getQueryParametersSchema(), isType('object'));
128 | }
129 |
130 | /** @test */
131 | public function itCanCreateRequestHeadersParameter()
132 | {
133 | $schema = $this->getPetStoreSchemaJson();
134 |
135 | $requestParameters = $schema->getRequestDefinition('deletePet')->getRequestParameters();
136 |
137 | assertThat($requestParameters->getHeaders(), containsOnlyInstancesOf(Parameter::class));
138 | assertThat($requestParameters->hasHeadersSchema(), isTrue());
139 | assertThat($requestParameters->getHeadersSchema(), isType('object'));
140 | }
141 |
142 | /** @test */
143 | public function itCanCreateAResponseDefinition()
144 | {
145 | $schema = $this->getPetStoreSchemaJson();
146 |
147 | $responseDefinition = $schema->getRequestDefinition('getPetById')->getResponseDefinition(200);
148 |
149 | assertThat($responseDefinition, isInstanceOf(ResponseDefinition::class));
150 | assertThat($responseDefinition->getBodySchema(), isType('object'));
151 | assertThat($responseDefinition->getStatusCode(), equalTo(200));
152 | assertThat($responseDefinition->getContentTypes(), contains('application/json'));
153 | }
154 |
155 | public function itUseTheSchemaDefaultConsumesPropertyWhenNotProvidedByAnOperation()
156 | {
157 | $schema = $this->getSchemaFromFile('schema-with-default-consumes-and-produces-properties.json');
158 | $definition = $schema->getRequestDefinition('postSomething');
159 |
160 | assertThat($definition->getContentTypes(), contains('application/json'));
161 | }
162 |
163 | /** @test */
164 | public function itUseTheSchemaDefaultProducesPropertyWhenNotProvidedByAnOperationResponse()
165 | {
166 | $schema = $this->getSchemaFromFile('schema-with-default-consumes-and-produces-properties.json');
167 | $responseDefinition = $schema
168 | ->getRequestDefinition('postSomething')
169 | ->getResponseDefinition(201);
170 |
171 | assertThat($responseDefinition->getContentTypes(), contains('application/json'));
172 | }
173 |
174 | /**
175 | * @test
176 | * @dataProvider getGuessableContentTypes
177 | */
178 | public function itGuessTheContentTypeFromRequestParameters($operationId, $expectedContentType)
179 | {
180 | $schema = $this->getSchemaFromFile('request-without-content-types.json');
181 |
182 | $definition = $schema->getRequestDefinition($operationId);
183 |
184 | assertThat($definition->getContentTypes(), contains($expectedContentType));
185 | }
186 |
187 | public function getGuessableContentTypes()
188 | {
189 | return [
190 | 'body' => [
191 | 'operationId' => 'postBodyWithoutAContentType',
192 | 'contentType' => 'application/json'
193 | ],
194 | 'formData' => [
195 | 'operationId' => 'postFromDataWithoutAContentType',
196 | 'contentType' => 'application/x-www-form-urlencoded'
197 | ],
198 | ];
199 | }
200 |
201 | /** @test */
202 | public function itFailWhenTryingToGuessTheContentTypeFromARequestWithMultipleBodyLocations()
203 | {
204 | $this->expectException(\LogicException::class);
205 | $this->expectExceptionMessage(
206 | 'Parameters cannot have body and formData locations ' .
207 | 'at the same time in /post/with-conflicting-locations'
208 | );
209 |
210 | $schemaFile = 'file://'.dirname(__DIR__).'/fixtures/request-with-conflicting-locations.json';
211 | (new SwaggerSchemaFactory())->createSchema($schemaFile);
212 | }
213 |
214 | /**
215 | * @return Schema
216 | */
217 | private function getPetStoreSchemaJson()
218 | {
219 | return $this->getSchemaFromFile('petstore.json');
220 | }
221 |
222 | /**
223 | * @return Schema
224 | */
225 | private function getPetStoreSchemaYaml()
226 | {
227 | return $this->getSchemaFromFile('petstore.yaml');
228 | }
229 |
230 | /**
231 | * @param $name
232 | *
233 | * @return Schema
234 | */
235 | private function getSchemaFromFile($name)
236 | {
237 | $schemaFile = 'file://' . dirname(__DIR__) . '/fixtures/'.$name;
238 | $factory = new SwaggerSchemaFactory();
239 |
240 | return $factory->createSchema($schemaFile);
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/tests/JsonSchema/Uri/YamlUriRetrieverTest.php:
--------------------------------------------------------------------------------
1 | retrieve('file://'.__DIR__.'/../../fixtures/petstore.yaml');
12 |
13 | assertThat($object, isType('object'));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/Normalizer/QueryParamsNormalizerTest.php:
--------------------------------------------------------------------------------
1 | toObject([
15 | 'type' => 'object',
16 | 'properties' => [
17 | 'param' => [
18 | 'type' => $schemaType
19 | ]
20 | ]
21 | ]);
22 |
23 | $normalizedValue = QueryParamsNormalizer::normalize(['param' => $actualValue], $jsonSchema);
24 |
25 | assertThat($normalizedValue['param'], equalTo($expectedValue));
26 | }
27 |
28 |
29 | public function getValidQueryParameters()
30 | {
31 | return [
32 | // description => [schemaType, actual, expected]
33 | 'with an integer' => ['integer', '123', 123 ],
34 | 'with a number' => ['number', '12.15', 12.15 ],
35 | 'with true given as a string' => ['boolean', 'true', true ],
36 | 'with true given as a numeric' => ['boolean', '1', true ],
37 | 'with false given as a string' => ['boolean', 'false', false ],
38 | 'with false given as a numeric string' => ['boolean', '0', false ]
39 | ];
40 | }
41 |
42 | /**
43 | * @test
44 | * @dataProvider getValidCollectionFormat
45 | */
46 | public function itTransformCollectionFormatIntoArray($collectionFormat, $rawValue, array $expectedValue)
47 | {
48 | $jsonSchema = $this->toObject([
49 | 'type' => 'object',
50 | 'properties' => [
51 | 'param' => [
52 | 'type' => 'array',
53 | 'items' => [
54 | 'string'
55 | ],
56 | 'collectionFormat' => $collectionFormat
57 | ]
58 | ]
59 | ]);
60 |
61 | $normalizedValue = QueryParamsNormalizer::normalize(['param' => $rawValue], $jsonSchema);
62 |
63 | assertThat($normalizedValue['param'], equalTo($expectedValue));
64 | }
65 |
66 | public function getValidCollectionFormat()
67 | {
68 | return [
69 | 'with csv' => ['csv', 'foo,bar,baz', ['foo','bar','baz']],
70 | 'with ssv' => ['ssv', 'foo bar baz', ['foo','bar','baz']],
71 | 'with pipes' => ['pipes', 'foo|bar|baz', ['foo','bar','baz']],
72 | 'with tabs' => ['tsv', "foo\tbar\tbaz", ['foo','bar','baz']]
73 | ];
74 | }
75 |
76 | /** @test */
77 | public function itThrowAnExceptionOnUnsupportedCollectionFormat()
78 | {
79 | $this->expectException(\InvalidArgumentException::class);
80 | $this->expectExceptionMessage('unknown is not a supported query collection format');
81 |
82 | $jsonSchema = $this->toObject([
83 | 'type' => 'object',
84 | 'properties' => [
85 | 'param' => [
86 | 'type' => 'array',
87 | 'items' => ['string'],
88 | 'collectionFormat' => 'unknown'
89 | ]
90 | ]
91 | ]);
92 |
93 | QueryParamsNormalizer::normalize(['param' => 'foo%bar'], $jsonSchema);
94 | }
95 |
96 | private function toObject(array $array)
97 | {
98 | return json_decode(json_encode($array));
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/tests/SchemaTest.php:
--------------------------------------------------------------------------------
1 | prophesize(RequestDefinition::class);
14 | $request->getMethod()->willReturn('GET');
15 | $request->getPathTemplate()->willReturn('/api/pets/{id}');
16 | $request->getOperationId()->willReturn('getPet');
17 |
18 | $requests = $this->prophesize(RequestDefinitions::class);
19 | $requests->getIterator()->willReturn(new \ArrayIterator([$request->reveal()]));
20 |
21 | $schema = new Schema($requests->reveal());
22 |
23 | $operationId = $schema->findOperationId('GET', '/api/pets/1234');
24 |
25 | assertThat($operationId, equalTo('getPet'));
26 | }
27 |
28 | /** @test */
29 | public function itThrowAnExceptionWhenNoOperationIdCanBeResolved()
30 | {
31 | $this->expectException(\InvalidArgumentException::class);
32 | $this->expectExceptionMessage('Unable to resolve the operationId for path /api/pets/1234');
33 |
34 | $requests = $this->prophesize(RequestDefinitions::class);
35 | $requests->getIterator()->willReturn(new \ArrayIterator());
36 |
37 | $schema = new Schema($requests->reveal(), '/api');
38 | $schema->findOperationId('GET', '/api/pets/1234');
39 | }
40 |
41 | /** @test */
42 | public function itProvideARequestDefinition()
43 | {
44 | $request = $this->prophesize(RequestDefinition::class);
45 | $request->getMethod()->willReturn('GET');
46 | $request->getPathTemplate()->willReturn('/pets/{id}');
47 | $request->getOperationId()->willReturn('getPet');
48 |
49 | $requests = $this->prophesize(RequestDefinitions::class);
50 | $requests->getIterator()->willReturn(new \ArrayIterator([$request->reveal()]));
51 |
52 | $schema = new Schema($requests->reveal(), '/api');
53 | $actual = $schema->getRequestDefinition('getPet');
54 |
55 | assertThat($actual, equalTo($request->reveal()));
56 | }
57 |
58 | /** @test */
59 | public function itThrowAnExceptionWhenNoRequestDefinitionIsFound()
60 | {
61 | $this->expectException(\InvalidArgumentException::class);
62 | $this->expectExceptionMessage('Unable to get the request definition for getPet');
63 |
64 | $requests = $this->prophesize(RequestDefinitions::class);
65 | $requests->getIterator()->willReturn(new \ArrayIterator());
66 |
67 | $schema = new Schema($requests->reveal(), '/api');
68 | $schema->getRequestDefinition('getPet');
69 | }
70 |
71 | /** @test */
72 | public function itCanBeSerialized()
73 | {
74 | $requests = $this->prophesize(RequestDefinitions::class);
75 | $requests->getIterator()->willReturn(new \ArrayIterator());
76 |
77 | $schema = new Schema($requests->reveal());
78 | $serialized = serialize($schema);
79 |
80 | assertThat(unserialize($serialized), equalTo($schema));
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/Validator/ConstraintViolationTest.php:
--------------------------------------------------------------------------------
1 | 'property_one',
12 | 'message' => 'a violation message',
13 | 'constraint' => 'required',
14 | 'location' => 'query'
15 | ];
16 |
17 | $violation = new ConstraintViolation('property_one', 'a violation message', 'required', 'query');
18 |
19 | assertEquals($expectedArray, $violation->toArray());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Validator/MessageValidatorTest.php:
--------------------------------------------------------------------------------
1 | messageValidator = new MessageValidator(
30 | $validator,
31 | $decoder
32 | );
33 | }
34 |
35 | /** @test */
36 | public function itValidateAMessageContentType()
37 | {
38 | $expectedViolations = [
39 | new ConstraintViolation(
40 | 'Content-Type',
41 | 'Content-Type should not be empty',
42 | 'required',
43 | 'header'
44 | )
45 | ];
46 |
47 | $message = $this->prophesize(MessageInterface::class);
48 | $message->getHeaderLine('Content-Type')->willReturn('');
49 |
50 | $definition = $this->prophesize(MessageDefinition::class);
51 | $definition->getContentTypes()->willReturn(['application/json']);
52 |
53 | $this->messageValidator->validateContentType(
54 | $message->reveal(),
55 | $definition->reveal()
56 | );
57 |
58 | assertThat($this->messageValidator->hasViolations(), isTrue());
59 | assertThat($this->messageValidator->getViolations(), containsOnlyInstancesOf(ConstraintViolation::class));
60 | assertThat($this->messageValidator->getViolations(), equalTo($expectedViolations));
61 | }
62 |
63 | /** @test */
64 | public function itValidateAMessageUnsupportedContentType()
65 | {
66 | $expectedViolations = [
67 | new ConstraintViolation(
68 | 'Content-Type',
69 | 'text/plain is not a supported content type, supported: application/json',
70 | 'enum',
71 | 'header'
72 | )
73 | ];
74 |
75 | $message = $this->prophesize(MessageInterface::class);
76 | $message->getHeaderLine('Content-Type')->willReturn('text/plain');
77 |
78 | $definition = $this->prophesize(MessageDefinition::class);
79 | $definition->getContentTypes()->willReturn(['application/json']);
80 |
81 | $this->messageValidator->validateContentType(
82 | $message->reveal(),
83 | $definition->reveal()
84 | );
85 |
86 | assertThat($this->messageValidator->hasViolations(), isTrue());
87 | assertThat($this->messageValidator->getViolations(), containsOnlyInstancesOf(ConstraintViolation::class));
88 | assertThat($this->messageValidator->getViolations(), equalTo($expectedViolations));
89 | }
90 |
91 | /** @test */
92 | public function itValidateAMessageHeaders()
93 | {
94 | $expectedViolation = [
95 | new ConstraintViolation(
96 | 'X-Required-Header',
97 | 'The property X-Required-Header is required',
98 | 'required',
99 | 'header'
100 | )
101 | ];
102 |
103 | $headersSchema = $this->toObject([
104 | 'type' => 'object',
105 | 'required' => ['X-Required-Header'],
106 | 'properties' => [
107 | 'X-Required-Header' => [
108 | 'type' => 'string'
109 | ]
110 | ]
111 | ]);
112 |
113 | $message = $this->prophesize(MessageInterface::class);
114 | $message->getHeaders()->willReturn(['X-Foo' => ['bar', 'baz']]);
115 |
116 | $definition = $this->prophesize(MessageDefinition::class);
117 | $definition->hasHeadersSchema()->willReturn(true);
118 | $definition->getHeadersSchema()->willReturn($headersSchema);
119 |
120 | $this->messageValidator->validateHeaders(
121 | $message->reveal(),
122 | $definition->reveal()
123 | );
124 |
125 | assertThat($this->messageValidator->hasViolations(), isTrue());
126 | assertThat($this->messageValidator->getViolations(), containsOnlyInstancesOf(ConstraintViolation::class));
127 | assertThat($this->messageValidator->getViolations(), equalTo($expectedViolation));
128 | }
129 |
130 | /** @test */
131 | public function itValidateTheRequestBody()
132 | {
133 | $expectedViolation = [
134 | new ConstraintViolation(
135 | 'id',
136 | 'String value found, but an integer is required',
137 | 'type',
138 | 'body'
139 | ),
140 | ];
141 |
142 | $bodySchema = $this->toObject([
143 | 'type' => 'object',
144 | 'properties' => [
145 | 'id' => [
146 | 'type' => 'integer',
147 | 'format' => 'int32'
148 | ]
149 | ]
150 | ]);
151 |
152 | $message = $this->prophesize(MessageInterface::class);
153 | $message->getHeaderLine('Content-Type')->willReturn('application/json');
154 | $message->getBody()->willReturn('{"id": "invalid"}');
155 |
156 | $definition = $this->prophesize(MessageDefinition::class);
157 | $definition->getContentTypes()->willReturn(['application/json']);
158 | $definition->hasBodySchema()->willReturn(true);
159 | $definition->getBodySchema()->willReturn($bodySchema);
160 |
161 | $this->messageValidator->validateMessageBody(
162 | $message->reveal(),
163 | $definition->reveal()
164 | );
165 |
166 | assertThat($this->messageValidator->hasViolations(), isTrue());
167 | assertThat($this->messageValidator->getViolations(), containsOnlyInstancesOf(ConstraintViolation::class));
168 | assertThat($this->messageValidator->getViolations(), equalTo($expectedViolation));
169 | }
170 |
171 | /** @test */
172 | public function itValidateARequestQueryParameters()
173 | {
174 | $expectedViolation = [
175 | new ConstraintViolation(
176 | 'limit',
177 | 'String value found, but an integer is required',
178 | 'type',
179 | 'query'
180 | )
181 | ];
182 |
183 | $queryParametersSchema = $this->toObject([
184 | 'type' => 'object',
185 | 'properties' => [
186 | 'limit' => [
187 | 'type' => 'integer'
188 | ]
189 | ]
190 | ]);
191 |
192 | $requestUri = $this->prophesize(UriInterface::class);
193 | $requestUri->getQuery()->willreturn('limit=invalid');
194 |
195 | $request = $this->prophesize(RequestInterface::class);
196 | $request->getUri()->willReturn($requestUri);
197 |
198 | $definition = $this->prophesize(RequestDefinition::class);
199 | $definition->hasQueryParametersSchema()->willReturn(true);
200 | $definition->getQueryParametersSchema()->willReturn($queryParametersSchema);
201 |
202 | $this->messageValidator->validateQueryParameters(
203 | $request->reveal(),
204 | $definition->reveal()
205 | );
206 |
207 | assertThat($this->messageValidator->hasViolations(), isTrue());
208 | assertThat($this->messageValidator->getViolations(), containsOnlyInstancesOf(ConstraintViolation::class));
209 | assertThat($this->messageValidator->getViolations(), equalTo($expectedViolation));
210 | }
211 |
212 | /** @test */
213 | public function itValidateARequest()
214 | {
215 | $expectedViolations = [
216 | new ConstraintViolation('id', 'String value found, but an integer is required', 'type', 'body'),
217 | new ConstraintViolation('X-Required-Header', 'The property X-Required-Header is required', 'required', 'header'),
218 | new ConstraintViolation('limit', 'String value found, but an integer is required', 'type', 'query'),
219 | ];
220 |
221 | $headersSchema = $this->toObject([
222 | 'type' => 'object',
223 | 'required' => ['X-Required-Header'],
224 | 'properties' => [
225 | 'X-Required-Header' => [
226 | 'type' => 'string'
227 | ]
228 | ]
229 | ]);
230 |
231 | $bodySchema = $this->toObject([
232 | 'type' => 'object',
233 | 'properties' => [
234 | 'id' => [
235 | 'type' => 'integer',
236 | 'format' => 'int32'
237 | ]
238 | ]
239 | ]);
240 |
241 | $queryParametersSchema = $this->toObject([
242 | 'type' => 'object',
243 | 'properties' => [
244 | 'limit' => [
245 | 'type' => 'integer'
246 | ]
247 | ]
248 | ]);
249 |
250 | $uri = $this->prophesize(UriInterface::class);
251 | $uri->getQuery()->willreturn('limit=invalid');
252 |
253 | $request = $this->prophesize(RequestInterface::class);
254 | $request->getMethod()->willReturn('POST');
255 | $request->getUri()->willReturn($uri);
256 | $request->getBody()->willReturn('{"id": "invalid"}');
257 | $request->getHeaderLine('Content-Type')->willReturn('application/json');
258 | $request->getHeaders()->willReturn([]);
259 |
260 | $definition = $this->prophesize(RequestDefinition::class);
261 | $definition->getContentTypes()->willReturn(['application/json']);
262 | $definition->hasBodySchema()->willReturn(true);
263 | $definition->getBodySchema()->willReturn($bodySchema);
264 | $definition->hasHeadersSchema()->willReturn(true);
265 | $definition->getHeadersSchema()->willReturn($headersSchema);
266 | $definition->hasQueryParametersSchema()->willReturn(true);
267 | $definition->getQueryParametersSchema()->willReturn($queryParametersSchema);
268 |
269 | $this->messageValidator->validateRequest(
270 | $request->reveal(),
271 | $definition->reveal()
272 | );
273 |
274 | assertThat($this->messageValidator->hasViolations(), isTrue());
275 | assertThat($this->messageValidator->getViolations(), containsOnlyInstancesOf(ConstraintViolation::class));
276 | assertThat($this->messageValidator->getViolations(), equalTo($expectedViolations));
277 | }
278 |
279 | /** @test */
280 | public function itValidateAResponse()
281 | {
282 | $expectedViolations = [
283 | new ConstraintViolation('id', 'String value found, but an integer is required', 'type', 'body'),
284 | new ConstraintViolation('X-Required-Header', 'The property X-Required-Header is required', 'required', 'header'),
285 | ];
286 |
287 | $headersSchema = $this->toObject([
288 | 'type' => 'object',
289 | 'required' => ['X-Required-Header'],
290 | 'properties' => [
291 | 'X-Required-Header' => [
292 | 'type' => 'string'
293 | ]
294 | ]
295 | ]);
296 |
297 | $bodySchema = $this->toObject([
298 | 'type' => 'object',
299 | 'properties' => [
300 | 'id' => [
301 | 'type' => 'integer',
302 | 'format' => 'int32'
303 | ]
304 | ]
305 | ]);
306 |
307 | $response = $this->prophesize(ResponseInterface::class);
308 | $response->getStatusCode()->willReturn('200');
309 | $response->getBody()->willReturn('{"id": "invalid"}');
310 | $response->getHeaderLine('Content-Type')->willReturn('application/json');
311 | $response->getHeaders()->willReturn([]);
312 |
313 | $responseDefinition = $this->prophesize(RequestDefinition::class);
314 | $responseDefinition->getContentTypes()->willReturn(['application/json']);
315 | $responseDefinition->hasBodySchema()->willReturn(true);
316 | $responseDefinition->getBodySchema()->willReturn($bodySchema);
317 | $responseDefinition->hasHeadersSchema()->willReturn(true);
318 | $responseDefinition->getHeadersSchema()->willReturn($headersSchema);
319 |
320 | $definition = $this->prophesize(RequestDefinition::class);
321 | $definition->getResponseDefinition('200')->willReturn($responseDefinition);
322 |
323 | $this->messageValidator->validateResponse(
324 | $response->reveal(),
325 | $definition->reveal()
326 | );
327 |
328 | assertThat($this->messageValidator->hasViolations(), isTrue());
329 | assertThat($this->messageValidator->getViolations(), containsOnlyInstancesOf(ConstraintViolation::class));
330 | assertThat($this->messageValidator->getViolations(), equalTo($expectedViolations));
331 | }
332 |
333 | private function toObject(array $array)
334 | {
335 | return json_decode(json_encode($array));
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/tests/fixtures/operation-without-an-id.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "host": "domain.tld",
4 | "paths": {
5 | "/something": {
6 | "get": {
7 | "responses": {
8 | "200": {
9 | "description": "successful operation"
10 | }
11 | }
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/fixtures/operation-without-parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "host": "domain.tld",
4 | "paths": {
5 | "/something": {
6 | "get": {
7 | "operationId": "getSomething",
8 | "responses": {
9 | "200": {
10 | "description": "successful operation"
11 | }
12 | }
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/fixtures/operation-without-responses.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "host": "domain.tld",
4 | "paths": {
5 | "/something": {
6 | "get": {
7 | "operationId": "getSomething"
8 | }
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/fixtures/petstore.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "info": {
4 | "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.",
5 | "version": "1.0.0",
6 | "title": "Swagger Petstore",
7 | "termsOfService": "http://swagger.io/terms/",
8 | "contact": {
9 | "email": "apiteam@swagger.io"
10 | },
11 | "license": {
12 | "name": "Apache 2.0",
13 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
14 | }
15 | },
16 | "host": "petstore.swagger.io",
17 | "basePath": "/v2",
18 | "tags": [
19 | {
20 | "name": "pet",
21 | "description": "Everything about your Pets",
22 | "externalDocs": {
23 | "description": "Find out more",
24 | "url": "http://swagger.io"
25 | }
26 | },
27 | {
28 | "name": "store",
29 | "description": "Access to Petstore orders"
30 | },
31 | {
32 | "name": "user",
33 | "description": "Operations about user",
34 | "externalDocs": {
35 | "description": "Find out more about our store",
36 | "url": "http://swagger.io"
37 | }
38 | }
39 | ],
40 | "schemes": [
41 | "https",
42 | "http"
43 | ],
44 | "paths": {
45 | "/pet": {
46 | "post": {
47 | "tags": [
48 | "pet"
49 | ],
50 | "summary": "Add a new pet to the store",
51 | "description": "",
52 | "operationId": "addPet",
53 | "consumes": [
54 | "application/json",
55 | "application/xml"
56 | ],
57 | "produces": [
58 | "application/xml",
59 | "application/json"
60 | ],
61 | "parameters": [
62 | {
63 | "in": "body",
64 | "name": "body",
65 | "description": "Pet object that needs to be added to the store",
66 | "required": true,
67 | "schema": {
68 | "$ref": "#/definitions/Pet"
69 | }
70 | }
71 | ],
72 | "responses": {
73 | "405": {
74 | "description": "Invalid input"
75 | }
76 | },
77 | "security": [
78 | {
79 | "petstore_auth": [
80 | "write:pets",
81 | "read:pets"
82 | ]
83 | }
84 | ]
85 | },
86 | "put": {
87 | "tags": [
88 | "pet"
89 | ],
90 | "summary": "Update an existing pet",
91 | "description": "",
92 | "operationId": "updatePet",
93 | "consumes": [
94 | "application/json",
95 | "application/xml"
96 | ],
97 | "produces": [
98 | "application/xml",
99 | "application/json"
100 | ],
101 | "parameters": [
102 | {
103 | "in": "body",
104 | "name": "body",
105 | "description": "Pet object that needs to be added to the store",
106 | "required": true,
107 | "schema": {
108 | "$ref": "#/definitions/Pet"
109 | }
110 | }
111 | ],
112 | "responses": {
113 | "400": {
114 | "description": "Invalid ID supplied"
115 | },
116 | "404": {
117 | "description": "Pet not found"
118 | },
119 | "405": {
120 | "description": "Validation exception"
121 | }
122 | },
123 | "security": [
124 | {
125 | "petstore_auth": [
126 | "write:pets",
127 | "read:pets"
128 | ]
129 | }
130 | ]
131 | }
132 | },
133 | "/pet/findByStatus": {
134 | "get": {
135 | "tags": [
136 | "pet"
137 | ],
138 | "summary": "Finds Pets by status",
139 | "description": "Multiple status values can be provided with comma separated strings",
140 | "operationId": "findPetsByStatus",
141 | "produces": [
142 | "application/xml",
143 | "application/json"
144 | ],
145 | "parameters": [
146 | {
147 | "name": "status",
148 | "in": "query",
149 | "description": "Status values that need to be considered for filter",
150 | "required": true,
151 | "type": "array",
152 | "items": {
153 | "type": "string",
154 | "enum": [
155 | "available",
156 | "pending",
157 | "sold"
158 | ],
159 | "default": "available"
160 | },
161 | "collectionFormat": "multi"
162 | }
163 | ],
164 | "responses": {
165 | "200": {
166 | "description": "successful operation",
167 | "schema": {
168 | "type": "array",
169 | "items": {
170 | "$ref": "#/definitions/Pet"
171 | }
172 | }
173 | },
174 | "400": {
175 | "description": "Invalid status value"
176 | }
177 | },
178 | "security": [
179 | {
180 | "petstore_auth": [
181 | "write:pets",
182 | "read:pets"
183 | ]
184 | }
185 | ]
186 | }
187 | },
188 | "/pet/findByTags": {
189 | "get": {
190 | "tags": [
191 | "pet"
192 | ],
193 | "summary": "Finds Pets by tags",
194 | "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
195 | "operationId": "findPetsByTags",
196 | "produces": [
197 | "application/xml",
198 | "application/json"
199 | ],
200 | "parameters": [
201 | {
202 | "name": "tags",
203 | "in": "query",
204 | "description": "Tags to filter by",
205 | "required": true,
206 | "type": "array",
207 | "items": {
208 | "type": "string"
209 | },
210 | "collectionFormat": "multi"
211 | }
212 | ],
213 | "responses": {
214 | "200": {
215 | "description": "successful operation",
216 | "schema": {
217 | "type": "array",
218 | "items": {
219 | "$ref": "#/definitions/Pet"
220 | }
221 | }
222 | },
223 | "400": {
224 | "description": "Invalid tag value"
225 | }
226 | },
227 | "security": [
228 | {
229 | "petstore_auth": [
230 | "write:pets",
231 | "read:pets"
232 | ]
233 | }
234 | ],
235 | "deprecated": true
236 | }
237 | },
238 | "/pet/{petId}": {
239 | "get": {
240 | "tags": [
241 | "pet"
242 | ],
243 | "summary": "Find pet by ID",
244 | "description": "Returns a single pet",
245 | "operationId": "getPetById",
246 | "produces": [
247 | "application/xml",
248 | "application/json"
249 | ],
250 | "parameters": [
251 | {
252 | "name": "petId",
253 | "in": "path",
254 | "description": "ID of pet to return",
255 | "required": true,
256 | "type": "integer",
257 | "format": "int64"
258 | }
259 | ],
260 | "responses": {
261 | "200": {
262 | "description": "successful operation",
263 | "schema": {
264 | "$ref": "#/definitions/Pet"
265 | }
266 | },
267 | "400": {
268 | "description": "Invalid ID supplied"
269 | },
270 | "404": {
271 | "description": "Pet not found"
272 | }
273 | },
274 | "security": [
275 | {
276 | "api_key": []
277 | }
278 | ]
279 | },
280 | "post": {
281 | "tags": [
282 | "pet"
283 | ],
284 | "summary": "Updates a pet in the store with form data",
285 | "description": "",
286 | "operationId": "updatePetWithForm",
287 | "consumes": [
288 | "application/x-www-form-urlencoded"
289 | ],
290 | "produces": [
291 | "application/xml",
292 | "application/json"
293 | ],
294 | "parameters": [
295 | {
296 | "name": "petId",
297 | "in": "path",
298 | "description": "ID of pet that needs to be updated",
299 | "required": true,
300 | "type": "integer",
301 | "format": "int64"
302 | },
303 | {
304 | "name": "name",
305 | "in": "formData",
306 | "description": "Updated name of the pet",
307 | "required": false,
308 | "type": "string"
309 | },
310 | {
311 | "name": "status",
312 | "in": "formData",
313 | "description": "Updated status of the pet",
314 | "required": false,
315 | "type": "string"
316 | }
317 | ],
318 | "responses": {
319 | "405": {
320 | "description": "Invalid input"
321 | }
322 | },
323 | "security": [
324 | {
325 | "petstore_auth": [
326 | "write:pets",
327 | "read:pets"
328 | ]
329 | }
330 | ]
331 | },
332 | "delete": {
333 | "tags": [
334 | "pet"
335 | ],
336 | "summary": "Deletes a pet",
337 | "description": "",
338 | "operationId": "deletePet",
339 | "produces": [
340 | "application/xml",
341 | "application/json"
342 | ],
343 | "parameters": [
344 | {
345 | "name": "api_key",
346 | "in": "header",
347 | "required": false,
348 | "type": "string"
349 | },
350 | {
351 | "name": "petId",
352 | "in": "path",
353 | "description": "Pet id to delete",
354 | "required": true,
355 | "type": "integer",
356 | "format": "int64"
357 | }
358 | ],
359 | "responses": {
360 | "400": {
361 | "description": "Invalid ID supplied"
362 | },
363 | "404": {
364 | "description": "Pet not found"
365 | }
366 | },
367 | "security": [
368 | {
369 | "petstore_auth": [
370 | "write:pets",
371 | "read:pets"
372 | ]
373 | }
374 | ]
375 | }
376 | },
377 | "/pet/{petId}/uploadImage": {
378 | "post": {
379 | "tags": [
380 | "pet"
381 | ],
382 | "summary": "uploads an image",
383 | "description": "",
384 | "operationId": "uploadFile",
385 | "consumes": [
386 | "multipart/form-data"
387 | ],
388 | "produces": [
389 | "application/json"
390 | ],
391 | "parameters": [
392 | {
393 | "name": "petId",
394 | "in": "path",
395 | "description": "ID of pet to update",
396 | "required": true,
397 | "type": "integer",
398 | "format": "int64"
399 | },
400 | {
401 | "name": "additionalMetadata",
402 | "in": "formData",
403 | "description": "Additional data to pass to server",
404 | "required": false,
405 | "type": "string"
406 | },
407 | {
408 | "name": "file",
409 | "in": "formData",
410 | "description": "file to upload",
411 | "required": false,
412 | "type": "file"
413 | }
414 | ],
415 | "responses": {
416 | "200": {
417 | "description": "successful operation",
418 | "schema": {
419 | "$ref": "#/definitions/ApiResponse"
420 | }
421 | }
422 | },
423 | "security": [
424 | {
425 | "petstore_auth": [
426 | "write:pets",
427 | "read:pets"
428 | ]
429 | }
430 | ]
431 | }
432 | },
433 | "/store/inventory": {
434 | "get": {
435 | "tags": [
436 | "store"
437 | ],
438 | "summary": "Returns pet inventories by status",
439 | "description": "Returns a map of status codes to quantities",
440 | "operationId": "getInventory",
441 | "produces": [
442 | "application/json"
443 | ],
444 | "parameters": [],
445 | "responses": {
446 | "200": {
447 | "description": "successful operation",
448 | "schema": {
449 | "type": "object",
450 | "additionalProperties": {
451 | "type": "integer",
452 | "format": "int32"
453 | }
454 | }
455 | }
456 | },
457 | "security": [
458 | {
459 | "api_key": []
460 | }
461 | ]
462 | }
463 | },
464 | "/store/order": {
465 | "post": {
466 | "tags": [
467 | "store"
468 | ],
469 | "summary": "Place an order for a pet",
470 | "description": "",
471 | "operationId": "placeOrder",
472 | "produces": [
473 | "application/xml",
474 | "application/json"
475 | ],
476 | "parameters": [
477 | {
478 | "in": "body",
479 | "name": "body",
480 | "description": "order placed for purchasing the pet",
481 | "required": true,
482 | "schema": {
483 | "$ref": "#/definitions/Order"
484 | }
485 | }
486 | ],
487 | "responses": {
488 | "200": {
489 | "description": "successful operation",
490 | "schema": {
491 | "$ref": "#/definitions/Order"
492 | }
493 | },
494 | "400": {
495 | "description": "Invalid Order"
496 | }
497 | }
498 | }
499 | },
500 | "/store/order/{orderId}": {
501 | "get": {
502 | "tags": [
503 | "store"
504 | ],
505 | "summary": "Find purchase order by ID",
506 | "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions",
507 | "operationId": "getOrderById",
508 | "produces": [
509 | "application/xml",
510 | "application/json"
511 | ],
512 | "parameters": [
513 | {
514 | "name": "orderId",
515 | "in": "path",
516 | "description": "ID of pet that needs to be fetched",
517 | "required": true,
518 | "type": "integer",
519 | "maximum": 10.0,
520 | "minimum": 1.0,
521 | "format": "int64"
522 | }
523 | ],
524 | "responses": {
525 | "200": {
526 | "description": "successful operation",
527 | "schema": {
528 | "$ref": "#/definitions/Order"
529 | }
530 | },
531 | "400": {
532 | "description": "Invalid ID supplied"
533 | },
534 | "404": {
535 | "description": "Order not found"
536 | }
537 | }
538 | },
539 | "delete": {
540 | "tags": [
541 | "store"
542 | ],
543 | "summary": "Delete purchase order by ID",
544 | "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors",
545 | "operationId": "deleteOrder",
546 | "produces": [
547 | "application/xml",
548 | "application/json"
549 | ],
550 | "parameters": [
551 | {
552 | "name": "orderId",
553 | "in": "path",
554 | "description": "ID of the order that needs to be deleted",
555 | "required": true,
556 | "type": "integer",
557 | "minimum": 1.0,
558 | "format": "int64"
559 | }
560 | ],
561 | "responses": {
562 | "400": {
563 | "description": "Invalid ID supplied"
564 | },
565 | "404": {
566 | "description": "Order not found"
567 | }
568 | }
569 | }
570 | },
571 | "/user": {
572 | "post": {
573 | "tags": [
574 | "user"
575 | ],
576 | "summary": "Create user",
577 | "description": "This can only be done by the logged in user.",
578 | "operationId": "createUser",
579 | "produces": [
580 | "application/xml",
581 | "application/json"
582 | ],
583 | "parameters": [
584 | {
585 | "in": "body",
586 | "name": "body",
587 | "description": "Created user object",
588 | "required": true,
589 | "schema": {
590 | "$ref": "#/definitions/User"
591 | }
592 | }
593 | ],
594 | "responses": {
595 | "default": {
596 | "description": "successful operation"
597 | }
598 | }
599 | }
600 | },
601 | "/user/createWithArray": {
602 | "post": {
603 | "tags": [
604 | "user"
605 | ],
606 | "summary": "Creates list of users with given input array",
607 | "description": "",
608 | "operationId": "createUsersWithArrayInput",
609 | "produces": [
610 | "application/xml",
611 | "application/json"
612 | ],
613 | "parameters": [
614 | {
615 | "in": "body",
616 | "name": "body",
617 | "description": "List of user object",
618 | "required": true,
619 | "schema": {
620 | "type": "array",
621 | "items": {
622 | "$ref": "#/definitions/User"
623 | }
624 | }
625 | }
626 | ],
627 | "responses": {
628 | "default": {
629 | "description": "successful operation"
630 | }
631 | }
632 | }
633 | },
634 | "/user/createWithList": {
635 | "post": {
636 | "tags": [
637 | "user"
638 | ],
639 | "summary": "Creates list of users with given input array",
640 | "description": "",
641 | "operationId": "createUsersWithListInput",
642 | "produces": [
643 | "application/xml",
644 | "application/json"
645 | ],
646 | "parameters": [
647 | {
648 | "in": "body",
649 | "name": "body",
650 | "description": "List of user object",
651 | "required": true,
652 | "schema": {
653 | "type": "array",
654 | "items": {
655 | "$ref": "#/definitions/User"
656 | }
657 | }
658 | }
659 | ],
660 | "responses": {
661 | "default": {
662 | "description": "successful operation"
663 | }
664 | }
665 | }
666 | },
667 | "/user/login": {
668 | "get": {
669 | "tags": [
670 | "user"
671 | ],
672 | "summary": "Logs user into the system",
673 | "description": "",
674 | "operationId": "loginUser",
675 | "produces": [
676 | "application/xml",
677 | "application/json"
678 | ],
679 | "parameters": [
680 | {
681 | "name": "username",
682 | "in": "query",
683 | "description": "The user name for login",
684 | "required": true,
685 | "type": "string"
686 | },
687 | {
688 | "name": "password",
689 | "in": "query",
690 | "description": "The password for login in clear text",
691 | "required": true,
692 | "type": "string"
693 | }
694 | ],
695 | "responses": {
696 | "200": {
697 | "description": "successful operation",
698 | "schema": {
699 | "type": "string"
700 | },
701 | "headers": {
702 | "X-Rate-Limit": {
703 | "type": "integer",
704 | "format": "int32",
705 | "description": "calls per hour allowed by the user"
706 | },
707 | "X-Expires-After": {
708 | "type": "string",
709 | "format": "date-time",
710 | "description": "date in UTC when token expires"
711 | }
712 | }
713 | },
714 | "400": {
715 | "description": "Invalid username/password supplied"
716 | }
717 | }
718 | }
719 | },
720 | "/user/logout": {
721 | "get": {
722 | "tags": [
723 | "user"
724 | ],
725 | "summary": "Logs out current logged in user session",
726 | "description": "",
727 | "operationId": "logoutUser",
728 | "produces": [
729 | "application/xml",
730 | "application/json"
731 | ],
732 | "parameters": [],
733 | "responses": {
734 | "default": {
735 | "description": "successful operation"
736 | }
737 | }
738 | }
739 | },
740 | "/user/{username}": {
741 | "get": {
742 | "tags": [
743 | "user"
744 | ],
745 | "summary": "Get user by user name",
746 | "description": "",
747 | "operationId": "getUserByName",
748 | "produces": [
749 | "application/xml",
750 | "application/json"
751 | ],
752 | "parameters": [
753 | {
754 | "name": "username",
755 | "in": "path",
756 | "description": "The name that needs to be fetched. Use user1 for testing. ",
757 | "required": true,
758 | "type": "string"
759 | }
760 | ],
761 | "responses": {
762 | "200": {
763 | "description": "successful operation",
764 | "schema": {
765 | "$ref": "#/definitions/User"
766 | }
767 | },
768 | "400": {
769 | "description": "Invalid username supplied"
770 | },
771 | "404": {
772 | "description": "User not found"
773 | }
774 | }
775 | },
776 | "put": {
777 | "tags": [
778 | "user"
779 | ],
780 | "summary": "Updated user",
781 | "description": "This can only be done by the logged in user.",
782 | "operationId": "updateUser",
783 | "produces": [
784 | "application/xml",
785 | "application/json"
786 | ],
787 | "parameters": [
788 | {
789 | "name": "username",
790 | "in": "path",
791 | "description": "name that need to be updated",
792 | "required": true,
793 | "type": "string"
794 | },
795 | {
796 | "in": "body",
797 | "name": "body",
798 | "description": "Updated user object",
799 | "required": true,
800 | "schema": {
801 | "$ref": "#/definitions/User"
802 | }
803 | }
804 | ],
805 | "responses": {
806 | "400": {
807 | "description": "Invalid user supplied"
808 | },
809 | "404": {
810 | "description": "User not found"
811 | }
812 | }
813 | },
814 | "delete": {
815 | "tags": [
816 | "user"
817 | ],
818 | "summary": "Delete user",
819 | "description": "This can only be done by the logged in user.",
820 | "operationId": "deleteUser",
821 | "produces": [
822 | "application/xml",
823 | "application/json"
824 | ],
825 | "parameters": [
826 | {
827 | "name": "username",
828 | "in": "path",
829 | "description": "The name that needs to be deleted",
830 | "required": true,
831 | "type": "string"
832 | }
833 | ],
834 | "responses": {
835 | "400": {
836 | "description": "Invalid username supplied"
837 | },
838 | "404": {
839 | "description": "User not found"
840 | }
841 | }
842 | }
843 | }
844 | },
845 | "securityDefinitions": {
846 | "petstore_auth": {
847 | "type": "oauth2",
848 | "authorizationUrl": "https://petstore.swagger.io/oauth/dialog",
849 | "flow": "implicit",
850 | "scopes": {
851 | "write:pets": "modify pets in your account",
852 | "read:pets": "read your pets"
853 | }
854 | },
855 | "api_key": {
856 | "type": "apiKey",
857 | "name": "api_key",
858 | "in": "header"
859 | }
860 | },
861 | "definitions": {
862 | "Order": {
863 | "type": "object",
864 | "properties": {
865 | "id": {
866 | "type": "integer",
867 | "format": "int64"
868 | },
869 | "petId": {
870 | "type": "integer",
871 | "format": "int64"
872 | },
873 | "quantity": {
874 | "type": "integer",
875 | "format": "int32"
876 | },
877 | "shipDate": {
878 | "type": "string",
879 | "format": "date-time"
880 | },
881 | "status": {
882 | "type": "string",
883 | "description": "Order Status",
884 | "enum": [
885 | "placed",
886 | "approved",
887 | "delivered"
888 | ]
889 | },
890 | "complete": {
891 | "type": "boolean",
892 | "default": false
893 | }
894 | },
895 | "xml": {
896 | "name": "Order"
897 | }
898 | },
899 | "User": {
900 | "type": "object",
901 | "properties": {
902 | "id": {
903 | "type": "integer",
904 | "format": "int64"
905 | },
906 | "username": {
907 | "type": "string"
908 | },
909 | "firstName": {
910 | "type": "string"
911 | },
912 | "lastName": {
913 | "type": "string"
914 | },
915 | "email": {
916 | "type": "string"
917 | },
918 | "password": {
919 | "type": "string"
920 | },
921 | "phone": {
922 | "type": "string"
923 | },
924 | "userStatus": {
925 | "type": "integer",
926 | "format": "int32",
927 | "description": "User Status"
928 | }
929 | },
930 | "xml": {
931 | "name": "User"
932 | }
933 | },
934 | "Category": {
935 | "type": "object",
936 | "properties": {
937 | "id": {
938 | "type": "integer",
939 | "format": "int64"
940 | },
941 | "name": {
942 | "type": "string"
943 | }
944 | },
945 | "xml": {
946 | "name": "Category"
947 | }
948 | },
949 | "Tag": {
950 | "type": "object",
951 | "properties": {
952 | "id": {
953 | "type": "integer",
954 | "format": "int64"
955 | },
956 | "name": {
957 | "type": "string"
958 | }
959 | },
960 | "xml": {
961 | "name": "Tag"
962 | }
963 | },
964 | "ApiResponse": {
965 | "type": "object",
966 | "properties": {
967 | "code": {
968 | "type": "integer",
969 | "format": "int32"
970 | },
971 | "type": {
972 | "type": "string"
973 | },
974 | "message": {
975 | "type": "string"
976 | }
977 | }
978 | },
979 | "Pet": {
980 | "type": "object",
981 | "required": [
982 | "name",
983 | "photoUrls"
984 | ],
985 | "properties": {
986 | "id": {
987 | "type": "integer",
988 | "format": "int64"
989 | },
990 | "category": {
991 | "$ref": "#/definitions/Category"
992 | },
993 | "name": {
994 | "type": "string",
995 | "example": "doggie"
996 | },
997 | "photoUrls": {
998 | "type": "array",
999 | "xml": {
1000 | "name": "photoUrl",
1001 | "wrapped": true
1002 | },
1003 | "items": {
1004 | "type": "string"
1005 | }
1006 | },
1007 | "tags": {
1008 | "type": "array",
1009 | "xml": {
1010 | "name": "tag",
1011 | "wrapped": true
1012 | },
1013 | "items": {
1014 | "$ref": "#/definitions/Tag"
1015 | }
1016 | },
1017 | "status": {
1018 | "type": "string",
1019 | "description": "pet status in the store",
1020 | "enum": [
1021 | "available",
1022 | "pending",
1023 | "sold"
1024 | ]
1025 | }
1026 | },
1027 | "xml": {
1028 | "name": "Pet"
1029 | }
1030 | }
1031 | },
1032 | "externalDocs": {
1033 | "description": "Find out more about Swagger",
1034 | "url": "http://swagger.io"
1035 | }
1036 | }
1037 |
--------------------------------------------------------------------------------
/tests/fixtures/petstore.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eleven-labs/api-validator/e9f016eb86dc8e2afd3baafbea28c08158b7eec3/tests/fixtures/petstore.txt
--------------------------------------------------------------------------------
/tests/fixtures/petstore.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | swagger: "2.0"
3 | info:
4 | description: |
5 | This is a sample server Petstore server. You can find out more about
6 | Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).
7 | For this sample, you can use the api key `special-key` to test the authorization
8 | filters.
9 | version: "1.0.0"
10 | title: "Swagger Petstore"
11 | termsOfService: "http://swagger.io/terms/"
12 | contact:
13 | email: "apiteam@swagger.io"
14 | license:
15 | name: "Apache 2.0"
16 | url: "http://www.apache.org/licenses/LICENSE-2.0.html"
17 | host: "petstore.swagger.io"
18 | basePath: "/v2"
19 | tags:
20 | - name: "pet"
21 | description: "Everything about your Pets"
22 | externalDocs:
23 | description: "Find out more"
24 | url: "http://swagger.io"
25 | - name: "store"
26 | description: "Access to Petstore orders"
27 | - name: "user"
28 | description: "Operations about user"
29 | externalDocs:
30 | description: "Find out more about our store"
31 | url: "http://swagger.io"
32 | schemes:
33 | - "https"
34 | - "http"
35 | paths:
36 | /pet:
37 | post:
38 | tags:
39 | - "pet"
40 | summary: "Add a new pet to the store"
41 | description: ""
42 | operationId: "addPet"
43 | consumes:
44 | - "application/json"
45 | - "application/xml"
46 | produces:
47 | - "application/xml"
48 | - "application/json"
49 | parameters:
50 | - in: "body"
51 | name: "body"
52 | description: "Pet object that needs to be added to the store"
53 | required: true
54 | schema:
55 | $ref: "#/definitions/Pet"
56 | responses:
57 | 405:
58 | description: "Invalid input"
59 | security:
60 | - petstore_auth:
61 | - "write:pets"
62 | - "read:pets"
63 | put:
64 | tags:
65 | - "pet"
66 | summary: "Update an existing pet"
67 | description: ""
68 | operationId: "updatePet"
69 | consumes:
70 | - "application/json"
71 | - "application/xml"
72 | produces:
73 | - "application/xml"
74 | - "application/json"
75 | parameters:
76 | - in: "body"
77 | name: "body"
78 | description: "Pet object that needs to be added to the store"
79 | required: true
80 | schema:
81 | $ref: "#/definitions/Pet"
82 | responses:
83 | 400:
84 | description: "Invalid ID supplied"
85 | 404:
86 | description: "Pet not found"
87 | 405:
88 | description: "Validation exception"
89 | security:
90 | - petstore_auth:
91 | - "write:pets"
92 | - "read:pets"
93 | /pet/findByStatus:
94 | get:
95 | tags:
96 | - "pet"
97 | summary: "Finds Pets by status"
98 | description: "Multiple status values can be provided with comma separated strings"
99 | operationId: "findPetsByStatus"
100 | produces:
101 | - "application/xml"
102 | - "application/json"
103 | parameters:
104 | - name: "status"
105 | in: "query"
106 | description: "Status values that need to be considered for filter"
107 | required: true
108 | type: "array"
109 | items:
110 | type: "string"
111 | enum:
112 | - "available"
113 | - "pending"
114 | - "sold"
115 | default: "available"
116 | collectionFormat: "multi"
117 | responses:
118 | 200:
119 | description: "successful operation"
120 | schema:
121 | type: "array"
122 | items:
123 | $ref: "#/definitions/Pet"
124 | 400:
125 | description: "Invalid status value"
126 | security:
127 | - petstore_auth:
128 | - "write:pets"
129 | - "read:pets"
130 | /pet/findByTags:
131 | get:
132 | tags:
133 | - "pet"
134 | summary: "Finds Pets by tags"
135 | description: |
136 | Muliple tags can be provided with comma separated strings. Use
137 | tag1, tag2, tag3 for testing."
138 | operationId: "findPetsByTags"
139 | produces:
140 | - "application/xml"
141 | - "application/json"
142 | parameters:
143 | - name: "tags"
144 | in: "query"
145 | description: "Tags to filter by"
146 | required: true
147 | type: "array"
148 | items:
149 | type: "string"
150 | collectionFormat: "multi"
151 | responses:
152 | 200:
153 | description: "successful operation"
154 | schema:
155 | type: "array"
156 | items:
157 | $ref: "#/definitions/Pet"
158 | 400:
159 | description: "Invalid tag value"
160 | security:
161 | - petstore_auth:
162 | - "write:pets"
163 | - "read:pets"
164 | deprecated: true
165 | /pet/{petId}:
166 | get:
167 | tags:
168 | - "pet"
169 | summary: "Find pet by ID"
170 | description: "Returns a single pet"
171 | operationId: "getPetById"
172 | produces:
173 | - "application/xml"
174 | - "application/json"
175 | parameters:
176 | - name: "petId"
177 | in: "path"
178 | description: "ID of pet to return"
179 | required: true
180 | type: "integer"
181 | format: "int64"
182 | responses:
183 | 200:
184 | description: "successful operation"
185 | schema:
186 | $ref: "#/definitions/Pet"
187 | 400:
188 | description: "Invalid ID supplied"
189 | 404:
190 | description: "Pet not found"
191 | security:
192 | - api_key: []
193 | post:
194 | tags:
195 | - "pet"
196 | summary: "Updates a pet in the store with form data"
197 | description: ""
198 | operationId: "updatePetWithForm"
199 | consumes:
200 | - "application/x-www-form-urlencoded"
201 | produces:
202 | - "application/xml"
203 | - "application/json"
204 | parameters:
205 | - name: "petId"
206 | in: "path"
207 | description: "ID of pet that needs to be updated"
208 | required: true
209 | type: "integer"
210 | format: "int64"
211 | - name: "name"
212 | in: "formData"
213 | description: "Updated name of the pet"
214 | required: false
215 | type: "string"
216 | - name: "status"
217 | in: "formData"
218 | description: "Updated status of the pet"
219 | required: false
220 | type: "string"
221 | responses:
222 | 405:
223 | description: "Invalid input"
224 | security:
225 | - petstore_auth:
226 | - "write:pets"
227 | - "read:pets"
228 | delete:
229 | tags:
230 | - "pet"
231 | summary: "Deletes a pet"
232 | description: ""
233 | operationId: "deletePet"
234 | produces:
235 | - "application/xml"
236 | - "application/json"
237 | parameters:
238 | - name: "api_key"
239 | in: "header"
240 | required: false
241 | type: "string"
242 | - name: "petId"
243 | in: "path"
244 | description: "Pet id to delete"
245 | required: true
246 | type: "integer"
247 | format: "int64"
248 | responses:
249 | 400:
250 | description: "Invalid ID supplied"
251 | 404:
252 | description: "Pet not found"
253 | security:
254 | - petstore_auth:
255 | - "write:pets"
256 | - "read:pets"
257 | /pet/{petId}/uploadImage:
258 | post:
259 | tags:
260 | - "pet"
261 | summary: "uploads an image"
262 | description: ""
263 | operationId: "uploadFile"
264 | consumes:
265 | - "multipart/form-data"
266 | produces:
267 | - "application/json"
268 | parameters:
269 | - name: "petId"
270 | in: "path"
271 | description: "ID of pet to update"
272 | required: true
273 | type: "integer"
274 | format: "int64"
275 | - name: "additionalMetadata"
276 | in: "formData"
277 | description: "Additional data to pass to server"
278 | required: false
279 | type: "string"
280 | - name: "file"
281 | in: "formData"
282 | description: "file to upload"
283 | required: false
284 | type: "file"
285 | responses:
286 | 200:
287 | description: "successful operation"
288 | schema:
289 | $ref: "#/definitions/ApiResponse"
290 | security:
291 | - petstore_auth:
292 | - "write:pets"
293 | - "read:pets"
294 | /store/inventory:
295 | get:
296 | tags:
297 | - "store"
298 | summary: "Returns pet inventories by status"
299 | description: "Returns a map of status codes to quantities"
300 | operationId: "getInventory"
301 | produces:
302 | - "application/json"
303 | parameters: []
304 | responses:
305 | 200:
306 | description: "successful operation"
307 | schema:
308 | type: "object"
309 | additionalProperties:
310 | type: "integer"
311 | format: "int32"
312 | security:
313 | - api_key: []
314 | /store/order:
315 | post:
316 | tags:
317 | - "store"
318 | summary: "Place an order for a pet"
319 | description: ""
320 | operationId: "placeOrder"
321 | produces:
322 | - "application/xml"
323 | - "application/json"
324 | parameters:
325 | - in: "body"
326 | name: "body"
327 | description: "order placed for purchasing the pet"
328 | required: true
329 | schema:
330 | $ref: "#/definitions/Order"
331 | responses:
332 | 200:
333 | description: "successful operation"
334 | schema:
335 | $ref: "#/definitions/Order"
336 | 400:
337 | description: "Invalid Order"
338 | /store/order/{orderId}:
339 | get:
340 | tags:
341 | - "store"
342 | summary: "Find purchase order by ID"
343 | description: |
344 | For valid response try integer IDs with value >= 1 and <= 10.
345 | Other values will generated exceptions"
346 | operationId: "getOrderById"
347 | produces:
348 | - "application/xml"
349 | - "application/json"
350 | parameters:
351 | - name: "orderId"
352 | in: "path"
353 | description: "ID of pet that needs to be fetched"
354 | required: true
355 | type: "integer"
356 | maximum: 10.0
357 | minimum: 1.0
358 | format: "int64"
359 | responses:
360 | 200:
361 | description: "successful operation"
362 | schema:
363 | $ref: "#/definitions/Order"
364 | 400:
365 | description: "Invalid ID supplied"
366 | 404:
367 | description: "Order not found"
368 | delete:
369 | tags:
370 | - "store"
371 | summary: "Delete purchase order by ID"
372 | description: |
373 | For valid response try integer IDs with positive integer value.
374 | Negative or non-integer values will generate API errors
375 | operationId: "deleteOrder"
376 | produces:
377 | - "application/xml"
378 | - "application/json"
379 | parameters:
380 | - name: "orderId"
381 | in: "path"
382 | description: "ID of the order that needs to be deleted"
383 | required: true
384 | type: "integer"
385 | minimum: 1.0
386 | format: "int64"
387 | responses:
388 | 400:
389 | description: "Invalid ID supplied"
390 | 404:
391 | description: "Order not found"
392 | /user:
393 | post:
394 | tags:
395 | - "user"
396 | summary: "Create user"
397 | description: "This can only be done by the logged in user."
398 | operationId: "createUser"
399 | produces:
400 | - "application/xml"
401 | - "application/json"
402 | parameters:
403 | - in: "body"
404 | name: "body"
405 | description: "Created user object"
406 | required: true
407 | schema:
408 | $ref: "#/definitions/User"
409 | responses:
410 | default:
411 | description: "successful operation"
412 | /user/createWithArray:
413 | post:
414 | tags:
415 | - "user"
416 | summary: "Creates list of users with given input array"
417 | description: ""
418 | operationId: "createUsersWithArrayInput"
419 | produces:
420 | - "application/xml"
421 | - "application/json"
422 | parameters:
423 | - in: "body"
424 | name: "body"
425 | description: "List of user object"
426 | required: true
427 | schema:
428 | type: "array"
429 | items:
430 | $ref: "#/definitions/User"
431 | responses:
432 | default:
433 | description: "successful operation"
434 | /user/createWithList:
435 | post:
436 | tags:
437 | - "user"
438 | summary: "Creates list of users with given input array"
439 | description: ""
440 | operationId: "createUsersWithListInput"
441 | produces:
442 | - "application/xml"
443 | - "application/json"
444 | parameters:
445 | - in: "body"
446 | name: "body"
447 | description: "List of user object"
448 | required: true
449 | schema:
450 | type: "array"
451 | items:
452 | $ref: "#/definitions/User"
453 | responses:
454 | default:
455 | description: "successful operation"
456 | /user/login:
457 | get:
458 | tags:
459 | - "user"
460 | summary: "Logs user into the system"
461 | description: ""
462 | operationId: "loginUser"
463 | produces:
464 | - "application/xml"
465 | - "application/json"
466 | parameters:
467 | - name: "username"
468 | in: "query"
469 | description: "The user name for login"
470 | required: true
471 | type: "string"
472 | - name: "password"
473 | in: "query"
474 | description: "The password for login in clear text"
475 | required: true
476 | type: "string"
477 | responses:
478 | 200:
479 | description: "successful operation"
480 | schema:
481 | type: "string"
482 | headers:
483 | X-Rate-Limit:
484 | type: "integer"
485 | format: "int32"
486 | description: "calls per hour allowed by the user"
487 | X-Expires-After:
488 | type: "string"
489 | format: "date-time"
490 | description: "date in UTC when token expires"
491 | 400:
492 | description: "Invalid username/password supplied"
493 | /user/logout:
494 | get:
495 | tags:
496 | - "user"
497 | summary: "Logs out current logged in user session"
498 | description: ""
499 | operationId: "logoutUser"
500 | produces:
501 | - "application/xml"
502 | - "application/json"
503 | parameters: []
504 | responses:
505 | default:
506 | description: "successful operation"
507 | /user/{username}:
508 | get:
509 | tags:
510 | - "user"
511 | summary: "Get user by user name"
512 | description: ""
513 | operationId: "getUserByName"
514 | produces:
515 | - "application/xml"
516 | - "application/json"
517 | parameters:
518 | - name: "username"
519 | in: "path"
520 | description: "The name that needs to be fetched. Use user1 for testing. "
521 | required: true
522 | type: "string"
523 | responses:
524 | 200:
525 | description: "successful operation"
526 | schema:
527 | $ref: "#/definitions/User"
528 | 400:
529 | description: "Invalid username supplied"
530 | 404:
531 | description: "User not found"
532 | put:
533 | tags:
534 | - "user"
535 | summary: "Updated user"
536 | description: "This can only be done by the logged in user."
537 | operationId: "updateUser"
538 | produces:
539 | - "application/xml"
540 | - "application/json"
541 | parameters:
542 | - name: "username"
543 | in: "path"
544 | description: "name that need to be updated"
545 | required: true
546 | type: "string"
547 | - in: "body"
548 | name: "body"
549 | description: "Updated user object"
550 | required: true
551 | schema:
552 | $ref: "#/definitions/User"
553 | responses:
554 | 400:
555 | description: "Invalid user supplied"
556 | 404:
557 | description: "User not found"
558 | delete:
559 | tags:
560 | - "user"
561 | summary: "Delete user"
562 | description: "This can only be done by the logged in user."
563 | operationId: "deleteUser"
564 | produces:
565 | - "application/xml"
566 | - "application/json"
567 | parameters:
568 | - name: "username"
569 | in: "path"
570 | description: "The name that needs to be deleted"
571 | required: true
572 | type: "string"
573 | responses:
574 | 400:
575 | description: "Invalid username supplied"
576 | 404:
577 | description: "User not found"
578 | securityDefinitions:
579 | petstore_auth:
580 | type: "oauth2"
581 | authorizationUrl: "https://petstore.swagger.io/oauth/dialog"
582 | flow: "implicit"
583 | scopes:
584 | write:pets: "modify pets in your account"
585 | read:pets: "read your pets"
586 | api_key:
587 | type: "apiKey"
588 | name: "api_key"
589 | in: "header"
590 | definitions:
591 | Order:
592 | type: "object"
593 | properties:
594 | id:
595 | type: "integer"
596 | format: "int64"
597 | petId:
598 | type: "integer"
599 | format: "int64"
600 | quantity:
601 | type: "integer"
602 | format: "int32"
603 | shipDate:
604 | type: "string"
605 | format: "date-time"
606 | status:
607 | type: "string"
608 | description: "Order Status"
609 | enum:
610 | - "placed"
611 | - "approved"
612 | - "delivered"
613 | complete:
614 | type: "boolean"
615 | default: false
616 | xml:
617 | name: "Order"
618 | User:
619 | type: "object"
620 | properties:
621 | id:
622 | type: "integer"
623 | format: "int64"
624 | username:
625 | type: "string"
626 | firstName:
627 | type: "string"
628 | lastName:
629 | type: "string"
630 | email:
631 | type: "string"
632 | password:
633 | type: "string"
634 | phone:
635 | type: "string"
636 | userStatus:
637 | type: "integer"
638 | format: "int32"
639 | description: "User Status"
640 | xml:
641 | name: "User"
642 | Category:
643 | type: "object"
644 | properties:
645 | id:
646 | type: "integer"
647 | format: "int64"
648 | name:
649 | type: "string"
650 | xml:
651 | name: "Category"
652 | Tag:
653 | type: "object"
654 | properties:
655 | id:
656 | type: "integer"
657 | format: "int64"
658 | name:
659 | type: "string"
660 | xml:
661 | name: "Tag"
662 | ApiResponse:
663 | type: "object"
664 | properties:
665 | code:
666 | type: "integer"
667 | format: "int32"
668 | type:
669 | type: "string"
670 | message:
671 | type: "string"
672 | Pet:
673 | type: "object"
674 | required:
675 | - "name"
676 | - "photoUrls"
677 | properties:
678 | id:
679 | type: "integer"
680 | format: "int64"
681 | category:
682 | $ref: "#/definitions/Category"
683 | name:
684 | type: "string"
685 | example: "doggie"
686 | photoUrls:
687 | type: "array"
688 | xml:
689 | name: "photoUrl"
690 | wrapped: true
691 | items:
692 | type: "string"
693 | tags:
694 | type: "array"
695 | xml:
696 | name: "tag"
697 | wrapped: true
698 | items:
699 | $ref: "#/definitions/Tag"
700 | status:
701 | type: "string"
702 | description: "pet status in the store"
703 | enum:
704 | - "available"
705 | - "pending"
706 | - "sold"
707 | xml:
708 | name: "Pet"
709 | externalDocs:
710 | description: "Find out more about Swagger"
711 | url: "http://swagger.io"
712 |
--------------------------------------------------------------------------------
/tests/fixtures/request-with-conflicting-locations.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "host": "petstore.swagger.io",
4 | "schemes": [
5 | "https",
6 | "http"
7 | ],
8 | "paths": {
9 | "/post/with-conflicting-locations": {
10 | "post": {
11 | "operationId": "requestWithConflictingLocations",
12 | "produces": ["application/json"],
13 | "parameters": [
14 | {
15 | "in": "body",
16 | "name": "body",
17 | "required": true,
18 | "schema": {
19 | "type": "object"
20 | }
21 | },
22 | {
23 | "in": "formData",
24 | "name": "foo",
25 | "type": "string"
26 | }
27 | ],
28 | "responses": {
29 | "200": {
30 | "description": "successful operation"
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/fixtures/request-without-content-types.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "host": "petstore.swagger.io",
4 | "schemes": [
5 | "https",
6 | "http"
7 | ],
8 | "paths": {
9 | "/post/body-without-a-content-type": {
10 | "post": {
11 | "operationId": "postBodyWithoutAContentType",
12 | "produces": ["application/json"],
13 | "parameters": [
14 | {
15 | "in": "body",
16 | "name": "body",
17 | "required": true,
18 | "schema": {
19 | "type": "object"
20 | }
21 | }
22 | ],
23 | "responses": {
24 | "200": {
25 | "description": "successful operation"
26 | }
27 | }
28 | }
29 | },
30 | "/post/form-data-without-a-content-type": {
31 | "post": {
32 | "operationId": "postFromDataWithoutAContentType",
33 | "produces": ["application/json"],
34 | "parameters": [
35 | {
36 | "in": "formData",
37 | "name": "foo",
38 | "required": true,
39 | "type": "string"
40 | }
41 | ],
42 | "responses": {
43 | "200": {
44 | "description": "successful operation"
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/fixtures/schema-with-default-consumes-and-produces-properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "host": "domain.tld",
4 | "consumes": ["application/json"],
5 | "produces": ["application/json"],
6 | "paths": {
7 | "/something": {
8 | "post": {
9 | "operationId": "postSomething",
10 | "responses": {
11 | "201": {
12 | "description": "created"
13 | }
14 | }
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------