Skip to content

Commit

Permalink
Merge pull request #205 from rafaelcaricio/verify-content-type
Browse files Browse the repository at this point in the history
Validate accordingly with content-type defined in spec
  • Loading branch information
jmcs committed Apr 7, 2016
2 parents 40053ac + 5c4d12f commit 9391aa1
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 8 deletions.
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

0 comments on commit 9391aa1

Please sign in to comment.