Skip to content
Snippets Groups Projects
Commit 4c7d4fc241a7 authored by Arnaud Vergnet's avatar Arnaud Vergnet :sun_with_face:
Browse files

feat: improve error handling

parent 2af675c2fd36
No related branches found
No related tags found
1 merge request!7feat: use pyramid decorators and rework error handling
Pipeline #132569 passed
from pyramid.httpexceptions import exception_response
import json
import logging
log = logging.getLogger(__name__)
def get_http_error(code: int, title: str, message: str, data: dict = None):
error = exception_response(code)
......@@ -16,4 +20,6 @@
},
}
)
log.debug(f"Encountered an HTTP error: {error.status}")
log.debug(f"Error content: {error.text}")
return error
from json import JSONDecodeError
from marshmallow import Schema, ValidationError
from dataclasses import dataclass
from marshmallow.validate import Length
from dataclasses import dataclass, field
from pyramid.request import Request
from cubicweb_api.httperrors import get_http_error
......@@ -22,8 +23,8 @@
@dataclass(frozen=True)
class RqlParams:
query: str
params: str
query: str = field(metadata=dict(validate=Length(min=1)))
params: dict = field(metadata=dict(required=False, load_default={}))
@dataclass(frozen=True)
......@@ -34,8 +35,8 @@
@dataclass(frozen=True)
class TransactionParams:
uuid: str
uuid: str = field(metadata=dict(validate=Length(min=32)))
@dataclass(frozen=True)
class TransactionExecuteParams(TransactionParams):
......@@ -38,6 +39,6 @@
@dataclass(frozen=True)
class TransactionExecuteParams(TransactionParams):
query: str
params: str
query: str = field(metadata=dict(validate=Length(min=1)))
params: dict = field(metadata=dict(required=False, load_default={}))
......@@ -17,7 +17,7 @@
from enum import Enum
from typing import Union
from cubicweb import AuthenticationError
from cubicweb import AuthenticationError, QueryError
from cubicweb.pyramid.core import CubicWebPyramidRequest
from cubicweb.schema_exporters import JSONSchemaExporter
from cubicweb.server.repository import Repository
......@@ -21,6 +21,8 @@
from cubicweb.pyramid.core import CubicWebPyramidRequest
from cubicweb.schema_exporters import JSONSchemaExporter
from cubicweb.server.repository import Repository
from rql import RQLException
from yams import ValidationError, UnknownType
from pyramid.config import Configurator
from pyramid.request import Request
from pyramid.view import view_config
......@@ -24,4 +26,5 @@
from pyramid.config import Configurator
from pyramid.request import Request
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPError
import logging
......@@ -27,4 +30,5 @@
import logging
import traceback
from cubicweb_api.api_transaction import ApiTransactionsRepository
from cubicweb_api.httperrors import get_http_error
......@@ -79,4 +83,37 @@
)
def print_exception(e: Exception):
traceback.print_exception(type(e), e, e.__traceback__)
def view_exception_handler(func):
"""
Use it as a decorator for any pyramid view to catch AuthenticationError to raise HTTP 401
and any other leftover exceptions to raise HTTP 500.
:param func: The pyramid view function
:return:
"""
def request_wrapper(request: Request):
try:
return func(request)
except HTTPError as e:
return e
except AuthenticationError as e:
print_exception(e)
return get_http_error(401, e.__class__.__name__, str(e))
except Exception as e:
print_exception(e)
# Do not return error content as it could lead to security leaks
raise get_http_error(
500,
"ServerError",
"The server encountered an error. Please contact support.",
)
return request_wrapper
@cw_view_config(route_name=ApiRoutes.schema, request_method="GET")
......@@ -82,2 +119,3 @@
@cw_view_config(route_name=ApiRoutes.schema, request_method="GET")
@view_exception_handler
def schema_route(request: Request):
......@@ -83,3 +121,4 @@
def schema_route(request: Request):
# TODO block this if we are not connected and anon is disabled
repo = get_cw_repo(request)
exporter = JSONSchemaExporter()
......@@ -84,12 +123,7 @@
repo = get_cw_repo(request)
exporter = JSONSchemaExporter()
try:
exported_schema = exporter.export_as_dict(repo.schema)
except Exception as e:
log.error(e)
raise get_http_error(400, e.__class__.__name__, str(e))
else:
return exported_schema
exported_schema = exporter.export_as_dict(repo.schema)
return exported_schema
@cw_view_config(route_name=ApiRoutes.rql)
......@@ -93,8 +127,9 @@
@cw_view_config(route_name=ApiRoutes.rql)
@view_exception_handler
def rql_route(request: Request):
schema = class_schema(RqlParams)()
request_params: RqlParams = get_request_params(request, schema)
query = request_params.query
params = request_params.params
......@@ -96,9 +131,7 @@
def rql_route(request: Request):
schema = class_schema(RqlParams)()
request_params: RqlParams = get_request_params(request, schema)
query = request_params.query
params = request_params.params
if not query:
raise get_http_error(400, "ValidationError", "Query should not be empty.")
try:
result = get_cw_request(request).execute(query, params)
......@@ -103,3 +136,7 @@
try:
result = get_cw_request(request).execute(query, params)
except (RQLException, QueryError, ValidationError, UnknownType) as e:
print_exception(e)
raise get_http_error(400, e.__class__.__name__, str(e))
else:
return result.rows
......@@ -105,7 +142,4 @@
return result.rows
except Exception as e:
log.error(e)
raise get_http_error(400, e.__class__.__name__, str(e))
@cw_view_config(route_name=ApiRoutes.login)
......@@ -109,6 +143,7 @@
@cw_view_config(route_name=ApiRoutes.login)
@view_exception_handler
def login_route(request: Request):
schema = class_schema(LoginParams)()
request_params: LoginParams = get_request_params(request, schema)
......@@ -135,6 +170,7 @@
@cw_view_config(route_name=ApiRoutes.transaction_begin)
@view_exception_handler
def transaction_begin_route(request: Request):
transactions = get_cw_repo(request).api_transactions
user = get_cw_request(request).user
......@@ -142,9 +178,10 @@
@cw_view_config(route_name=ApiRoutes.transaction_execute)
@view_exception_handler
def transaction_execute_route(request: Request):
transactions = get_cw_repo(request).api_transactions
schema = class_schema(TransactionExecuteParams)()
params: TransactionExecuteParams = get_request_params(request, schema)
try:
result = transactions[params.uuid].execute(params.query, params.params)
......@@ -145,7 +182,11 @@
def transaction_execute_route(request: Request):
transactions = get_cw_repo(request).api_transactions
schema = class_schema(TransactionExecuteParams)()
params: TransactionExecuteParams = get_request_params(request, schema)
try:
result = transactions[params.uuid].execute(params.query, params.params)
except (RQLException, QueryError, ValidationError, UnknownType) as e:
print_exception(e)
raise get_http_error(400, e.__class__.__name__, str(e))
else:
return result.rows
......@@ -151,7 +192,4 @@
return result.rows
except Exception as e:
log.error(e)
raise get_http_error(400, e.__class__.__name__, str(e))
@cw_view_config(route_name=ApiRoutes.transaction_commit)
......@@ -155,6 +193,7 @@
@cw_view_config(route_name=ApiRoutes.transaction_commit)
@view_exception_handler
def transaction_commit_route(request: Request):
transactions = get_cw_repo(request).api_transactions
schema = class_schema(TransactionParams)()
......@@ -162,5 +201,9 @@
uuid = params.uuid
try:
commit_result = transactions[uuid].commit()
except (RQLException, QueryError, ValidationError, UnknownType) as e:
print_exception(e)
raise get_http_error(400, e.__class__.__name__, str(e))
else:
transactions[uuid].rollback()
return commit_result
......@@ -165,8 +208,5 @@
transactions[uuid].rollback()
return commit_result
except Exception as e:
log.error(e)
raise get_http_error(400, e.__class__.__name__, str(e))
@cw_view_config(route_name=ApiRoutes.transaction_rollback)
......@@ -170,8 +210,9 @@
@cw_view_config(route_name=ApiRoutes.transaction_rollback)
@view_exception_handler
def transaction_rollback_route(request: Request):
transactions = get_cw_repo(request).api_transactions
schema = class_schema(TransactionParams)()
params: TransactionParams = get_request_params(request, schema)
uuid = params.uuid
......@@ -173,15 +214,11 @@
def transaction_rollback_route(request: Request):
transactions = get_cw_repo(request).api_transactions
schema = class_schema(TransactionParams)()
params: TransactionParams = get_request_params(request, schema)
uuid = params.uuid
try:
rollback_result = transactions[uuid].rollback()
transactions.end_transaction(uuid)
return rollback_result
except Exception as e:
log.error(e)
raise get_http_error(400, e.__class__.__name__, str(e))
rollback_result = transactions[uuid].rollback()
transactions.end_transaction(uuid)
return rollback_result
@cw_view_config(route_name=ApiRoutes.help, request_method="GET")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment