Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate accordingly with content-type defined in spec #205

Merged
merged 4 commits into from
Apr 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion connexion/decorators/produces.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ def wrapper(*args, **kwargs):
logger.debug('Endpoint returned a Flask Response', extra={'url': url, 'mimetype': data.mimetype})
return data

data = str(data)
response = flask.current_app.response_class(data, mimetype=self.mimetype) # type: flask.Response
response = self.process_headers(response, headers)

Expand Down
20 changes: 19 additions & 1 deletion connexion/decorators/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import logging
from ..exceptions import NonConformingResponseBody, NonConformingResponseHeaders
from ..problem import problem
from ..utils import produces_json
from .validation import ResponseBodyValidator
from .decorator import BaseDecorator
from jsonschema import ValidationError
Expand Down Expand Up @@ -48,7 +49,7 @@ def validate_response(self, data, status_code, headers):
response_definition = self.operation.resolve_reference(response_definition)
# TODO handle default response definitions

if response_definition and response_definition.get("schema"):
if self.is_json_schema_compatible(response_definition):
schema = response_definition.get("schema")
v = ResponseBodyValidator(schema)
try:
Expand All @@ -69,6 +70,23 @@ def validate_response(self, data, status_code, headers):
% list(set(headers.keys()).symmetric_difference(set(response_definition_header_keys))))
return True

def is_json_schema_compatible(self, response_definition):
"""
Verify if the specified operation responses are JSON schema
compatible.

All operations that specify a JSON schema and have content
type "application/json" or "text/plain" can be validated using
json_schema package.

:type response_definition: dict
:rtype bool
"""
if not response_definition:
return False
return ('schema' in response_definition and
(produces_json([self.mimetype]) or self.mimetype == 'text/plain'))

def __call__(self, function):
"""
:type function: types.FunctionType
Expand Down
15 changes: 11 additions & 4 deletions connexion/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ def security_decorator(self):


class Operation(SecureOperation):
DEFAULT_MIMETYPE = 'application/json'

"""
A single API operation on a path.
"""
Expand Down Expand Up @@ -264,16 +266,21 @@ def _retrieve_reference(self, reference):
return definition

def get_mimetype(self):
if produces_json(self.produces): # endpoint will return json
"""
If the endpoint has no 'produces' then the default is
'application/json'.

:rtype str
"""
if produces_json(self.produces):
try:
return self.produces[0]
except IndexError:
# if the endpoint as no 'produces' then the default is 'application/json'
return 'application/json'
return Operation.DEFAULT_MIMETYPE
elif len(self.produces) == 1:
return self.produces[0]
else:
return None
return Operation.DEFAULT_MIMETYPE

def resolve_parameters(self, parameters):
for param in parameters:
Expand Down
25 changes: 25 additions & 0 deletions tests/api/test_responses.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from struct import unpack

from connexion.decorators.produces import JSONEncoder

Expand Down Expand Up @@ -127,3 +128,27 @@ def default(self, o):
assert resp.status_code == 200
response = json.loads(resp.data.decode())
assert response['theResult'] == 'cool result'


def test_content_type_not_json(simple_app):
app_client = simple_app.app.test_client()

resp = app_client.get('/v1.0/blob-response')
assert resp.status_code == 200

# validate binary content
text, number = unpack('!4sh', resp.data)
assert text == b'cool'
assert number == 8


def test_maybe_blob_or_json(simple_app):
app_client = simple_app.app.test_client()

resp = app_client.get('/v1.0/binary-response')
assert resp.status_code == 200
assert resp.content_type == 'application/octet-stream'
# validate binary content
text, number = unpack('!4sh', resp.data)
assert text == b'cool'
assert number == 8
10 changes: 8 additions & 2 deletions tests/fakeapi/hello.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#!/usr/bin/env python3

from decimal import Decimal

from connexion import problem, request
from connexion import NoContent
from flask import redirect
Expand Down Expand Up @@ -323,3 +321,11 @@ def test_nullable_param_put(contents):

def test_custom_json_response():
return {'theResult': DummyClass()}, 200


def get_blob_data():
return b'cool\x00\x08'


def get_data_as_binary():
return get_blob_data(), 200, {'Content-Type': 'application/octet-stream'}
24 changes: 24 additions & 0 deletions tests/fixtures/simple/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,30 @@ paths:
type: string
description: the number we wanna test

/blob-response:
get:
operationId: fakeapi.hello.get_blob_data
produces:
- "application/octet-stream"
responses:
200:
description: Some blob response
schema:
type: string
format: binary

/binary-response:
get:
operationId: fakeapi.hello.get_data_as_binary
produces:
- "application/octet-stream"
responses:
200:
description: Everything is ok
schema:
type: string


definitions:
new_stack:
type: object
Expand Down