[CORD-967] Returning errors in JSON

Change-Id: I35f48f6a8ec1931b1242a78f6777fffc184f54df
diff --git a/xos/coreapi/apihelper.py b/xos/coreapi/apihelper.py
index a2a2a95..f79701f 100644
--- a/xos/coreapi/apihelper.py
+++ b/xos/coreapi/apihelper.py
@@ -26,7 +26,7 @@
                 context = kwargs["context"]
             else:
                 context = args[2]
-            context.set_details(str(e))
+            context.set_details(e.json_detail)
             if (type(e) == XOSPermissionDenied):
                 context.set_code(grpc.StatusCode.PERMISSION_DENIED)
             elif (type(e) == XOSValidationError):
diff --git a/xos/xos/exceptions.py b/xos/xos/exceptions.py
index b3631e4..805944b 100644
--- a/xos/xos/exceptions.py
+++ b/xos/xos/exceptions.py
@@ -1,71 +1,138 @@
+import json
 from rest_framework.exceptions import APIException
 from rest_framework.exceptions import PermissionDenied as RestFrameworkPermissionDenied
 
+def _get_json_error_details(data):
+    """
+    Convert error details to JSON
+    """
+    if isinstance(data, dict):
+        ret = {
+            key: value for key, value in data.items()
+        }
+    elif isinstance(data, list):
+        ret = [
+            item for item in data
+        ]
+
+    return json.dumps(ret)
+
+
 class XOSProgrammingError(APIException):
     status_code=400
     def __init__(self, why="programming error", fields={}):
-        APIException.__init__(self, {"error": "XOSProgrammingError",
-                            "specific_error": why,
-                            "fields": fields})
+        raw_detail = {
+            "error": "XOSProgrammingError",
+            "specific_error": why,
+            "fields": fields
+        }
+        APIException.__init__(self, raw_detail)
+        self.raw_detail = raw_detail
+        self.json_detail = _get_json_error_details(raw_detail)
 
 class XOSPermissionDenied(RestFrameworkPermissionDenied):
     def __init__(self, why="permission error", fields={}):
-        APIException.__init__(self, {"error": "XOSPermissionDenied",
-                            "specific_error": why,
-                            "fields": fields})
+        raw_detail = {
+            "error": "XOSPermissionDenied",
+            "specific_error": why,
+            "fields": fields
+        }
+        APIException.__init__(self, raw_detail)
+        self.raw_detail = raw_detail
+        self.json_detail = _get_json_error_details(raw_detail)
 
 class XOSNotAuthenticated(RestFrameworkPermissionDenied):
     status_code=401
     def __init__(self, why="you must be authenticated to use this api", fields={}):
-        APIException.__init__(self, {"error": "XOSNotAuthenticated",
-                            "specific_error": why,
-                            "fields": fields})
+        raw_detail = {
+            "error": "XOSNotAuthenticated",
+            "specific_error": why,
+            "fields": fields
+        }
+        APIException.__init__(self, raw_detail)
+        self.raw_detail = raw_detail
+        self.json_detail = _get_json_error_details(raw_detail)
 
 class XOSNotFound(RestFrameworkPermissionDenied):
     status_code=404
     def __init__(self, why="object not found", fields={}):
-        APIException.__init__(self, {"error": "XOSNotFound",
-                            "specific_error": why,
-                            "fields": fields})
+        raw_detail = {
+            "error": "XOSNotFound",
+            "specific_error": why,
+            "fields": fields
+        }
+        APIException.__init__(self, raw_detail)
+        self.raw_detail = raw_detail
+        self.json_detail = _get_json_error_details(raw_detail)
 
 class XOSValidationError(APIException):
     status_code=403
     def __init__(self, why="validation error", fields={}):
-        APIException.__init__(self, {"error": "XOSValidationError",
-                            "specific_error": why,
-                            "fields": fields})
+        raw_detail = {
+            "error": "XOSValidationError",
+            "specific_error": why,
+            "fields": fields
+        }
+        APIException.__init__(self, raw_detail)
+        self.raw_detail = raw_detail
+        self.json_detail = _get_json_error_details(raw_detail)
 
 class XOSDuplicateKey(APIException):
     status_code=400
     def __init__(self, why="duplicate key", fields={}):
-        APIException.__init__(self, {"error": "XOSDuplicateKey",
-                            "specific_error": why,
-                            "fields": fields})
+        raw_detail = {
+            "error": "XOSDuplicateKey",
+            "specific_error": why,
+            "fields": fields
+        }
+        APIException.__init__(self, raw_detail)
+        self.raw_detail = raw_detail
+        self.json_detail = _get_json_error_details(raw_detail)
 
 class XOSMissingField(APIException):
     status_code=400
     def __init__(self, why="missing field", fields={}):
-        APIException.__init__(self, {"error": "XOSMissingField",
-                            "specific_error": why,
-                            "fields": fields})
+        raw_detail = {
+            "error": "XOSMissingField",
+            "specific_error": why,
+            "fields": fields
+        }
+        APIException.__init__(self, raw_detail)
+        self.raw_detail = raw_detail
+        self.json_detail = _get_json_error_details(raw_detail)
 
 class XOSConfigurationError(APIException):
     status_code=400
     def __init__(self, why="configuration error", fields={}):
-        APIException.__init__(self, {"error": "XOSConfigurationError",
-                            "specific_error": why,
-                            "fields": fields})
+        raw_detail = {
+            "error": "XOSConfigurationError",
+            "specific_error": why,
+            "fields": fields
+        }
+        APIException.__init__(self, raw_detail)
+        self.raw_detail = raw_detail
+        self.json_detail = _get_json_error_details(raw_detail)
 
 class XOSConflictingField(APIException):
     status_code=400
     def __init__(self, why="conflicting field", fields={}):
-        APIException.__init__(self, {"error": "XOSMissingField",
-                            "specific_error": why,
-                            "fields": fields})
+        raw_detail = {
+            "error": "XOSMissingField",
+            "specific_error": why,
+            "fields": fields
+        }
+        APIException.__init__(self, raw_detail)
+        self.raw_detail = raw_detail
+        self.json_detail = _get_json_error_details(raw_detail)
 
 class XOSServiceUnavailable(APIException):
     status_code=503
     def __init__(self, why="Service temporarily unavailable, try again later", fields={}):
-        APIException.__init__(self, {"error": "XOSServiceUnavailable",
-                            "specific_error": why,
-                            "fields": fields})
+        raw_detail = {
+            "error": "XOSServiceUnavailable",
+            "specific_error": why,
+            "fields": fields
+        }
+        APIException.__init__(self, raw_detail)
+        self.raw_detail = raw_detail
+        self.json_detail = _get_json_error_details(raw_detail)
diff --git a/xos/xos/exceptions_test.py b/xos/xos/exceptions_test.py
new file mode 100644
index 0000000..ec8e448
--- /dev/null
+++ b/xos/xos/exceptions_test.py
@@ -0,0 +1,32 @@
+import unittest
+import sys
+import os
+import inspect
+import json
+sys.path.append(os.path.abspath('..'))
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
+import xos.exceptions
+from xos.exceptions import *
+
+class TestXosExceptions(unittest.TestCase):
+    """
+    Test the conversion from excenption to json
+    """
+    def test_get_json_error_details(self):
+        res = xos.exceptions._get_json_error_details({'foo': 'bar'})
+        assert res == json.dumps({"foo":"bar"})
+
+    def test_exceptions(self):
+        """
+        This test iterate over all the classes in exceptions.py and if they start with XOS
+         validate the json_detail output
+        """
+        for name, item in inspect.getmembers(xos.exceptions):
+            if inspect.isclass(item) and name.startswith('XOS'):
+                e = item('test error', {'foo': 'bar'})
+                res = e.json_detail
+                assert res == json.dumps(
+                    {"fields": {"foo": "bar"}, "specific_error": "test error", "error": name})
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file