Skip to content

Commit 9ac4203

Browse files
committed
Responses schema validation
1 parent cab6dd4 commit 9ac4203

File tree

3 files changed

+180
-1
lines changed

3 files changed

+180
-1
lines changed

openapi_spec_validator/validation/validators.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ def _iter_operation_errors(
140140
)
141141
self.operation_ids_registry.append(operation_id)
142142

143+
if "responses" in operation:
144+
responses = operation / "responses"
145+
yield from self._iter_responses_errors(responses)
146+
143147
names = []
144148

145149
parameters = None
@@ -161,6 +165,29 @@ def _iter_operation_errors(
161165
)
162166
return
163167

168+
def _iter_responses_errors(self, responses: Spec) -> Iterator[ValidationError]:
169+
for response_code, response in responses.items():
170+
yield from self._iter_response_errors(response_code, response)
171+
172+
def _iter_response_errors(self, response_code: str, response: Spec) -> Iterator[ValidationError]:
173+
# openapi 2
174+
if "schema" in response:
175+
schema = response / "schema"
176+
yield from self._iter_schema_errors(schema)
177+
# openapi 3
178+
if "content" in response:
179+
content = response / "content"
180+
yield from self._iter_content_errors(content)
181+
182+
def _iter_content_errors(self, content: Spec) -> Iterator[ValidationError]:
183+
for mimetype, media_type in content.items():
184+
yield from self._iter_media_type_errors(mimetype, media_type)
185+
186+
def _iter_media_type_errors(self, mimetype: str, media_type: Spec) -> Iterator[ValidationError]:
187+
if "schema" in media_type:
188+
schema = media_type / "schema"
189+
yield from self._iter_schema_errors(schema)
190+
164191
def _get_path_param_names(self, params: Spec) -> Iterator[str]:
165192
for param in params:
166193
if param["in"] == "path":
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
swagger: "2.0"
2+
info:
3+
version: 1.0.0
4+
title: Swagger Petstore
5+
license:
6+
name: MIT
7+
host: petstore.swagger.io
8+
basePath: /v1
9+
schemes:
10+
- http
11+
consumes:
12+
- application/json
13+
produces:
14+
- application/json
15+
paths:
16+
/pets:
17+
get:
18+
summary: List all pets
19+
operationId: listPets
20+
tags:
21+
- pets
22+
parameters:
23+
- name: limit
24+
in: query
25+
description: How many items to return at one time (max 100)
26+
required: false
27+
type: integer
28+
format: int32
29+
responses:
30+
200:
31+
description: A paged array of pets
32+
headers:
33+
x-next:
34+
type: string
35+
description: A link to the next page of responses
36+
schema:
37+
$ref: 'definitions/Pets'
38+
default:
39+
description: unexpected error
40+
schema:
41+
$ref: '#/definitions/'
42+
post:
43+
summary: Create a pet
44+
operationId: createPets
45+
tags:
46+
- pets
47+
responses:
48+
'201':
49+
description: Null response
50+
default:
51+
description: unexpected error
52+
schema:
53+
$ref: '#/definitions/Error'
54+
/pets/{petId}:
55+
get:
56+
summary: Info for a specific pet
57+
operationId: showPetById
58+
tags:
59+
- pets
60+
parameters:
61+
- name: petId
62+
in: path
63+
required: true
64+
description: The id of the pet to retrieve
65+
type: string
66+
responses:
67+
'200':
68+
description: Expected response to a valid request
69+
schema:
70+
$ref: '#/definitions/Pets'
71+
default:
72+
description: unexpected error
73+
schema:
74+
$ref: '#/definitions/Error'
75+
definitions:
76+
Pet:
77+
required:
78+
- id
79+
- name
80+
properties:
81+
id:
82+
type: integer
83+
format: int64
84+
name:
85+
type: string
86+
tag:
87+
type: string
88+
Pets:
89+
type: array
90+
items:
91+
$ref: '#/definitions/Pet'
92+
Error:
93+
required:
94+
- code
95+
- message
96+
properties:
97+
code:
98+
type: integer
99+
format: int32
100+
message:
101+
type: string

tests/integration/validation/test_validators.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,59 @@
11
import pytest
22

3+
from jsonschema.exceptions import RefResolutionError
4+
35
from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
46

57

8+
class TestLocalOpenAPIv2Validator:
9+
10+
LOCAL_SOURCE_DIRECTORY = "data/v2.0/"
11+
12+
def local_test_suite_file_path(self, test_file):
13+
return f"{self.LOCAL_SOURCE_DIRECTORY}{test_file}"
14+
15+
@pytest.mark.parametrize(
16+
"spec_file",
17+
[
18+
"petstore.yaml",
19+
],
20+
)
21+
def test_valid(self, factory, validator_v2, spec_file):
22+
spec_path = self.local_test_suite_file_path(spec_file)
23+
spec = factory.spec_from_file(spec_path)
24+
spec_url = factory.spec_file_url(spec_path)
25+
26+
return validator_v2.validate(spec, spec_url=spec_url)
27+
28+
@pytest.mark.parametrize(
29+
"spec_file",
30+
[
31+
"empty.yaml",
32+
],
33+
)
34+
def test_validation_failed(self, factory, validator_v2, spec_file):
35+
spec_path = self.local_test_suite_file_path(spec_file)
36+
spec = factory.spec_from_file(spec_path)
37+
spec_url = factory.spec_file_url(spec_path)
38+
39+
with pytest.raises(OpenAPIValidationError):
40+
validator_v2.validate(spec, spec_url=spec_url)
41+
42+
@pytest.mark.parametrize(
43+
"spec_file",
44+
[
45+
"missing-reference.yaml",
46+
],
47+
)
48+
def test_ref_failed(self, factory, validator_v2, spec_file):
49+
spec_path = self.local_test_suite_file_path(spec_file)
50+
spec = factory.spec_from_file(spec_path)
51+
spec_url = factory.spec_file_url(spec_path)
52+
53+
with pytest.raises(RefResolutionError):
54+
validator_v2.validate(spec, spec_url=spec_url)
55+
56+
657
class TestLocalOpenAPIv30Validator:
758

859
LOCAL_SOURCE_DIRECTORY = "data/v3.0/"
@@ -31,7 +82,7 @@ def test_valid(self, factory, validator_v30, spec_file):
3182
"empty.yaml",
3283
],
3384
)
34-
def test_falied(self, factory, validator_v30, spec_file):
85+
def test_failed(self, factory, validator_v30, spec_file):
3586
spec_path = self.local_test_suite_file_path(spec_file)
3687
spec = factory.spec_from_file(spec_path)
3788
spec_url = factory.spec_file_url(spec_path)

0 commit comments

Comments
 (0)