Skip to content
This repository was archived by the owner on Oct 23, 2023. It is now read-only.
Merged
83 changes: 39 additions & 44 deletions beacon_api/api/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,35 @@
from ..conf import CONFIG_INFO


class BeaconError(Exception):
"""BeaconError Exception specific class.
def process_exception_data(request, host, error_code, error):
"""Return request data as dictionary.

Generates custom exception messages based on request parameters.
"""

def __init__(self, request, host, error_code, error):
"""Return request data as dictionary."""
self.data = {'beaconId': '.'.join(reversed(host.split('.'))),
"apiVersion": __apiVersion__,
'exists': None,
'error': {'errorCode': error_code,
'errorMessage': error},
'alleleRequest': {'referenceName': request.get("referenceName", None),
'referenceBases': request.get("referenceBases", None),
'includeDatasetResponses': request.get("includeDatasetResponses", "NONE"),
'assemblyId': request.get("assemblyId", None)},
# showing empty datasetsAlleRsponse as no datasets found
# A null/None would represent no data while empty array represents
# none found or error and corresponds with exists null/None
'datasetAlleleResponses': []}
# include datasetIds only if they are specified
# as per specification if they don't exist all datatsets will be queried
# Only one of `alternateBases` or `variantType` is required, validated by schema
oneof_fields = ["alternateBases", "variantType", "start", "end", "startMin", "startMax",
"endMin", "endMax", "datasetIds"]
self.data['alleleRequest'].update({k: request.get(k) for k in oneof_fields if k in request})
return self.data


class BeaconBadRequest(BeaconError):
data = {'beaconId': '.'.join(reversed(host.split('.'))),
"apiVersion": __apiVersion__,
'exists': None,
'error': {'errorCode': error_code,
'errorMessage': error},
'alleleRequest': {'referenceName': request.get("referenceName", None),
'referenceBases': request.get("referenceBases", None),
'includeDatasetResponses': request.get("includeDatasetResponses", "NONE"),
'assemblyId': request.get("assemblyId", None)},
# showing empty datasetsAlleRsponse as no datasets found
# A null/None would represent no data while empty array represents
# none found or error and corresponds with exists null/None
'datasetAlleleResponses': []}
# include datasetIds only if they are specified
# as per specification if they don't exist all datatsets will be queried
# Only one of `alternateBases` or `variantType` is required, validated by schema
oneof_fields = ["alternateBases", "variantType", "start", "end", "startMin", "startMax",
"endMin", "endMax", "datasetIds"]
data['alleleRequest'].update({k: request.get(k) for k in oneof_fields if k in request})

return data


class BeaconBadRequest(web.HTTPBadRequest):
"""Exception returns with 400 code and a custom error message.

The method is called if one of the required parameters are missing or invalid.
Expand All @@ -49,13 +47,12 @@ class BeaconBadRequest(BeaconError):

def __init__(self, request, host, error):
"""Return custom bad request exception."""
data = super().__init__(request, host, 400, error)

LOG.error(f'400 ERROR MESSAGE: {error}')
raise web.HTTPBadRequest(content_type="application/json", text=json.dumps(data))
data = process_exception_data(request, host, 400, error)
super().__init__(text=json.dumps(data), content_type="application/json")
LOG.error(f'401 ERROR MESSAGE: {error}')


class BeaconUnauthorised(BeaconError):
class BeaconUnauthorised(web.HTTPUnauthorized):
"""HTTP Exception returns with 401 code with a custom error message.

The method is called if the user is not registered or if the token from the authentication has expired.
Expand All @@ -64,17 +61,17 @@ class BeaconUnauthorised(BeaconError):

def __init__(self, request, host, error, error_message):
"""Return custom unauthorized exception."""
data = super().__init__(request, host, 401, error)
data = process_exception_data(request, host, 401, error)
headers_401 = {"WWW-Authenticate": f"Bearer realm=\"{CONFIG_INFO.url}\"\n\
error=\"{error}\"\n\
error_description=\"{error_message}\""}
super().__init__(content_type="application/json", text=json.dumps(data),
# we use auth scheme Bearer by default
headers=headers_401)
LOG.error(f'401 ERROR MESSAGE: {error}')
raise web.HTTPUnauthorized(content_type="application/json", text=json.dumps(data),
# we use auth scheme Bearer by default
headers=headers_401)


class BeaconForbidden(BeaconError):
class BeaconForbidden(web.HTTPForbidden):
"""HTTP Exception returns with 403 code with the error message.

`'Resource not granted for authenticated user or resource protected for all users.'`.
Expand All @@ -84,13 +81,12 @@ class BeaconForbidden(BeaconError):

def __init__(self, request, host, error):
"""Return custom forbidden exception."""
data = super().__init__(request, host, 403, error)

data = process_exception_data(request, host, 403, error)
super().__init__(content_type="application/json", text=json.dumps(data))
LOG.error(f'403 ERROR MESSAGE: {error}')
raise web.HTTPForbidden(content_type="application/json", text=json.dumps(data))


class BeaconServerError(BeaconError):
class BeaconServerError(web.HTTPInternalServerError):
"""HTTP Exception returns with 500 code with the error message.

The 500 error is not specified by the Beacon API, thus as simple error would do.
Expand All @@ -100,6 +96,5 @@ def __init__(self, error):
"""Return custom forbidden exception."""
data = {'errorCode': 500,
'errorMessage': error}

super().__init__(content_type="application/json", text=json.dumps(data))
LOG.error(f'500 ERROR MESSAGE: {error}')
raise web.HTTPInternalServerError(content_type="application/json", text=json.dumps(data))
2 changes: 1 addition & 1 deletion beacon_api/conf/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
title=GA4GHBeacon at CSC

# Version of the Beacon implementation
version=1.4.1
version=1.5.0

# Author of this software
author=CSC developers
Expand Down
Loading