Add response schema validation (only when in TESTING mode) and add one schema. More will be added in a followup CL
This commit is contained in:
parent
4fd249589d
commit
6f1a4030b6
3 changed files with 78 additions and 4 deletions
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from app import app
|
||||||
from flask import Blueprint, request, make_response, jsonify
|
from flask import Blueprint, request, make_response, jsonify
|
||||||
from flask.ext.restful import Resource, abort, Api, reqparse
|
from flask.ext.restful import Resource, abort, Api, reqparse
|
||||||
from flask.ext.restful.utils.cors import crossdomain
|
from flask.ext.restful.utils.cors import crossdomain
|
||||||
|
@ -52,6 +53,11 @@ class InvalidRequest(ApiException):
|
||||||
ApiException.__init__(self, 'invalid_request', 400, error_description, payload)
|
ApiException.__init__(self, 'invalid_request', 400, error_description, payload)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidResponse(ApiException):
|
||||||
|
def __init__(self, error_description, payload=None):
|
||||||
|
ApiException.__init__(self, 'invalid_response', 500, error_description, payload)
|
||||||
|
|
||||||
|
|
||||||
class InvalidToken(ApiException):
|
class InvalidToken(ApiException):
|
||||||
def __init__(self, error_description, payload=None):
|
def __init__(self, error_description, payload=None):
|
||||||
ApiException.__init__(self, 'invalid_token', 401, error_description, payload)
|
ApiException.__init__(self, 'invalid_token', 401, error_description, payload)
|
||||||
|
@ -286,6 +292,25 @@ def validate_json_request(schema_name):
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def define_json_response(schema_name):
|
||||||
|
def wrapper(func):
|
||||||
|
@add_method_metadata('response_schema', schema_name)
|
||||||
|
@wraps(func)
|
||||||
|
def wrapped(self, *args, **kwargs):
|
||||||
|
schema = self.schemas[schema_name]
|
||||||
|
try:
|
||||||
|
resp = func(self, *args, **kwargs)
|
||||||
|
|
||||||
|
if app.config['TESTING']:
|
||||||
|
validate(resp, schema)
|
||||||
|
|
||||||
|
return resp
|
||||||
|
except ValidationError as ex:
|
||||||
|
raise InvalidResponse(ex.message)
|
||||||
|
return wrapped
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def request_error(exception=None, **kwargs):
|
def request_error(exception=None, **kwargs):
|
||||||
data = kwargs.copy()
|
data = kwargs.copy()
|
||||||
message = 'Request error.'
|
message = 'Request error.'
|
||||||
|
|
|
@ -94,12 +94,18 @@ def swagger_route_data(include_internal=False, compact=False):
|
||||||
|
|
||||||
new_operation = {
|
new_operation = {
|
||||||
'method': method_name,
|
'method': method_name,
|
||||||
'nickname': method_metadata(method, 'nickname')
|
'nickname': method_metadata(method, 'nickname') or '(unnamed)'
|
||||||
}
|
}
|
||||||
|
|
||||||
if not compact:
|
if not compact:
|
||||||
|
response_type = 'void'
|
||||||
|
res_schema_name = method_metadata(method, 'response_schema')
|
||||||
|
if res_schema_name:
|
||||||
|
models[res_schema_name] = view_class.schemas[res_schema_name]
|
||||||
|
response_type = res_schema_name
|
||||||
|
|
||||||
new_operation.update({
|
new_operation.update({
|
||||||
'type': 'void',
|
'type': response_type,
|
||||||
'summary': method.__doc__.strip() if method.__doc__ else '',
|
'summary': method.__doc__.strip() if method.__doc__ else '',
|
||||||
'parameters': parameters,
|
'parameters': parameters,
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,8 @@ from flask.ext.principal import identity_changed, AnonymousIdentity
|
||||||
from app import app, billing as stripe, authentication
|
from app import app, billing as stripe, authentication
|
||||||
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
|
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
|
||||||
log_action, internal_only, NotFound, require_user_admin, path_param,
|
log_action, internal_only, NotFound, require_user_admin, path_param,
|
||||||
InvalidToken, require_scope, format_date, hide_if, show_if, license_error)
|
InvalidToken, require_scope, format_date, hide_if, show_if, license_error,
|
||||||
|
define_json_response)
|
||||||
from endpoints.api.subscribe import subscribe
|
from endpoints.api.subscribe import subscribe
|
||||||
from endpoints.common import common_login
|
from endpoints.common import common_login
|
||||||
from data import model
|
from data import model
|
||||||
|
@ -50,13 +51,13 @@ def user_view(user):
|
||||||
'verified': user.verified,
|
'verified': user.verified,
|
||||||
'anonymous': False,
|
'anonymous': False,
|
||||||
'username': user.username,
|
'username': user.username,
|
||||||
'email': user.email,
|
|
||||||
'gravatar': compute_hash(user.email),
|
'gravatar': compute_hash(user.email),
|
||||||
}
|
}
|
||||||
|
|
||||||
user_admin = UserAdminPermission(user.username)
|
user_admin = UserAdminPermission(user.username)
|
||||||
if user_admin.can():
|
if user_admin.can():
|
||||||
user_response.update({
|
user_response.update({
|
||||||
|
'email': user.email,
|
||||||
'organizations': [org_view(o) for o in organizations],
|
'organizations': [org_view(o) for o in organizations],
|
||||||
'logins': [login_view(login) for login in logins],
|
'logins': [login_view(login) for login in logins],
|
||||||
'can_create_repo': True,
|
'can_create_repo': True,
|
||||||
|
@ -130,10 +131,51 @@ class User(ApiResource):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'UserView': {
|
||||||
|
'id': 'UserView',
|
||||||
|
'type': 'object',
|
||||||
|
'description': 'Describes a user',
|
||||||
|
'required': ['verified', 'anonymous', 'gravatar'],
|
||||||
|
'properties': {
|
||||||
|
'verified': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'Whether the user\'s email address has been verified'
|
||||||
|
},
|
||||||
|
'anonymous': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'true if this user data represents a guest user'
|
||||||
|
},
|
||||||
|
'email': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': 'The user\'s email address',
|
||||||
|
},
|
||||||
|
'gravatar': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': 'Gravatar hash representing the user\'s icon'
|
||||||
|
},
|
||||||
|
'organizations': {
|
||||||
|
'type': 'array',
|
||||||
|
'description': 'Information about the organizations in which the user is a member'
|
||||||
|
},
|
||||||
|
'logins': {
|
||||||
|
'type': 'array',
|
||||||
|
'description': 'The list of external login providers against which the user has authenticated'
|
||||||
|
},
|
||||||
|
'can_create_repo': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'Whether the user has permission to create repositories'
|
||||||
|
},
|
||||||
|
'preferred_namespace': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'If true, the user\'s namespace is the preferred namespace to display'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@require_scope(scopes.READ_USER)
|
@require_scope(scopes.READ_USER)
|
||||||
@nickname('getLoggedInUser')
|
@nickname('getLoggedInUser')
|
||||||
|
@define_json_response('UserView')
|
||||||
def get(self):
|
def get(self):
|
||||||
""" Get user information for the authenticated user. """
|
""" Get user information for the authenticated user. """
|
||||||
user = get_authenticated_user()
|
user = get_authenticated_user()
|
||||||
|
@ -146,6 +188,7 @@ class User(ApiResource):
|
||||||
@nickname('changeUserDetails')
|
@nickname('changeUserDetails')
|
||||||
@internal_only
|
@internal_only
|
||||||
@validate_json_request('UpdateUser')
|
@validate_json_request('UpdateUser')
|
||||||
|
@define_json_response('UserView')
|
||||||
def put(self):
|
def put(self):
|
||||||
""" Update a users details such as password or email. """
|
""" Update a users details such as password or email. """
|
||||||
user = get_authenticated_user()
|
user = get_authenticated_user()
|
||||||
|
|
Reference in a new issue