diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py index f6c3ecc3f..0234e6820 100644 --- a/endpoints/api/__init__.py +++ b/endpoints/api/__init__.py @@ -1,6 +1,7 @@ import logging import json +from app import app from flask import Blueprint, request, make_response, jsonify from flask.ext.restful import Resource, abort, Api, reqparse from flask.ext.restful.utils.cors import crossdomain @@ -52,6 +53,11 @@ class InvalidRequest(ApiException): 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): def __init__(self, error_description, payload=None): ApiException.__init__(self, 'invalid_token', 401, error_description, payload) @@ -286,6 +292,25 @@ def validate_json_request(schema_name): 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): data = kwargs.copy() message = 'Request error.' diff --git a/endpoints/api/discovery.py b/endpoints/api/discovery.py index 47181dc9f..db61a14b2 100644 --- a/endpoints/api/discovery.py +++ b/endpoints/api/discovery.py @@ -94,12 +94,18 @@ def swagger_route_data(include_internal=False, compact=False): new_operation = { 'method': method_name, - 'nickname': method_metadata(method, 'nickname') + 'nickname': method_metadata(method, 'nickname') or '(unnamed)' } 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({ - 'type': 'void', + 'type': response_type, 'summary': method.__doc__.strip() if method.__doc__ else '', 'parameters': parameters, }) diff --git a/endpoints/api/user.py b/endpoints/api/user.py index 8ba2c0507..3bafe40b4 100644 --- a/endpoints/api/user.py +++ b/endpoints/api/user.py @@ -8,7 +8,8 @@ from flask.ext.principal import identity_changed, AnonymousIdentity from app import app, billing as stripe, authentication from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error, 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.common import common_login from data import model @@ -50,13 +51,13 @@ def user_view(user): 'verified': user.verified, 'anonymous': False, 'username': user.username, - 'email': user.email, 'gravatar': compute_hash(user.email), } user_admin = UserAdminPermission(user.username) if user_admin.can(): user_response.update({ + 'email': user.email, 'organizations': [org_view(o) for o in organizations], 'logins': [login_view(login) for login in logins], '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) @nickname('getLoggedInUser') + @define_json_response('UserView') def get(self): """ Get user information for the authenticated user. """ user = get_authenticated_user() @@ -146,6 +188,7 @@ class User(ApiResource): @nickname('changeUserDetails') @internal_only @validate_json_request('UpdateUser') + @define_json_response('UserView') def put(self): """ Update a users details such as password or email. """ user = get_authenticated_user()