[Checkins] SVN: z3c.jsonrpc/trunk/ - improve error handling

Roger Ineichen roger at projekt01.ch
Sun Aug 23 16:10:31 EDT 2009


Log message for revision 103118:
  - improve error handling
  - implemented error views concept given from ZopePublication
  - removed old SETUP.cfg and friends
  - adjust error test
  

Changed:
  U   z3c.jsonrpc/trunk/CHANGES.txt
  U   z3c.jsonrpc/trunk/setup.py
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/README.txt
  D   z3c.jsonrpc/trunk/src/z3c/jsonrpc/SETUP.cfg
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/configure.zcml
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/error.py
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/error.zcml
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/exception.py
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/interfaces.py
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/publisher.py
  D   z3c.jsonrpc/trunk/src/z3c/jsonrpc/z3c.jsonrpc-configure.zcml
  D   z3c.jsonrpc/trunk/src/z3c/jsonrpc/z3c.jsonrpc-meta.zcml

-=-
Modified: z3c.jsonrpc/trunk/CHANGES.txt
===================================================================
--- z3c.jsonrpc/trunk/CHANGES.txt	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/CHANGES.txt	2009-08-23 20:10:31 UTC (rev 103118)
@@ -2,6 +2,20 @@
 CHANGES
 =======
 
+0.6.0 (unreleased)
+------------------
+
+- implemented error view concept which will work with ZopePublication
+
+- implemented default error view for known zope and JSON-RPC errors
+
+- use DirectResult in response
+
+- removed unauthenticated error view. This was not working and requires a
+  custom concept supported by the used java script library used at client 
+  side
+
+
 Version 0.5.4 (2009-04-07)
 --------------------------
 

Modified: z3c.jsonrpc/trunk/setup.py
===================================================================
--- z3c.jsonrpc/trunk/setup.py	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/setup.py	2009-08-23 20:10:31 UTC (rev 103118)
@@ -24,7 +24,7 @@
 
 setup (
     name='z3c.jsonrpc',
-    version='0.5.4',
+    version='0.6.0dev',
     author = "Roger Ineichen and the Zope Community",
     author_email = "zope-dev at zope.org",
     description = "JSON RPC server and client implementation for Zope3",

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/README.txt
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/README.txt	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/README.txt	2009-08-23 20:10:31 UTC (rev 103118)
@@ -400,17 +400,18 @@
 Error handling
 --------------
 
-See what happens if the server raises an Exception:
+See what happens if the server raises an Exception. We will get a response
+error with additional error content:
 
   >>> proxy.forceValueError()
   Traceback (most recent call last):
   ...
   ResponseError: Check proxy.error for error message
 
-and the error message is:
+and the error content looks like:
 
   >>> proxy.error
-  {u'message': u'Invalid JSON-RPC', u'code': -32603, u'data': u'ValueError: Something was wrong in server method.'}
+  {u'message': u'Internal error', u'code': -32603, u'data': {u'i18nMessage': u'Internal error'}}
 
 The error property gets reset on the next successfull call:
 

Deleted: z3c.jsonrpc/trunk/src/z3c/jsonrpc/SETUP.cfg
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/SETUP.cfg	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/SETUP.cfg	2009-08-23 20:10:31 UTC (rev 103118)
@@ -1,3 +0,0 @@
-<data-files zopeskel/etc/package-includes>
-  z3c.jsonrpc-*.zcml
-</data-files>

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/configure.zcml
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/configure.zcml	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/configure.zcml	2009-08-23 20:10:31 UTC (rev 103118)
@@ -49,12 +49,15 @@
       permission="zope.Public"
       />
 
-  <!-- Be careful and register the skin namespace for our request and layer.
-       This will prevents that we can simply get the wrong skin namespace
-       based on the __iro__ order of our layer and request interfaces.
-       Note, if we don't choose carefully our layer and request interfaces
-       for our skin, it's possible that we get the skin namespace registered
-       for the browser request -->
+  <!-- If you register your own layer and skin setup, be careful and register
+       the skin namespace below for our JSON-RPC layer. This will prevents
+       that you will get the wrong skin namespace based on the __iro__ order of
+       the layer interfaces. 
+       
+       Since JSON-RPC supports layers and skins it's highly recommended not to
+       mix IJSONRPCLayer and IBrowserRequest layers. Otherwise you have to make
+       sure that the right default skin interface get applied.
+       -->
   <adapter
       name="skin"
       factory=".namespace.skin"

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/error.py
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/error.py	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/error.py	2009-08-23 20:10:31 UTC (rev 103118)
@@ -20,14 +20,100 @@
 
 _ = zope.i18nmessageid.MessageFactory('z3c')
 
+from z3c.jsonrpc import interfaces
 
-class UnauthorizedResponse(object):
-    """Knows how to return error content."""
+# The error codes used since JSON-RPC 2.0
+# See: http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal
+#
+# code   message           Meaning 
+# --------------------------------
+# -32700 Parse error.          Invalid JSON. An error occurred on the server 
+#                              while parsing the JSON text.
+# -32600 Invalid Request.      The received JSON not a valid JSON-RPC Request.
+# -32601 Method not found.     The requested remote-procedure does not exist, 
+#                              is not available.
+# -32602 Invalid params.       Invalid method parameters.
+# -32603 Internal error.       Internal JSON-RPC error.
+# -32099..-32000 Server error. Reserved for implementation-defined server-errors.
 
+
+class JSONRPCErrorView(object):
+    """Generic JSON-RPC error view.
+    
+    This base class is used for error views which are used for error handling
+    by ZopePublication.
+    """
+    
+    zope.interface.implements(interfaces.IJSONRPCErrorView)
+
     def __init__(self, context, request):
         self.context = context
         self.request = request
 
     def __call__(self):
-        errMsg = _('You are not allowed to access this content.')
-        return zope.i18n.translate(errMsg, context=self.request)
+        """Must return itself.
+        
+        This allows us to use the error view in setResult if ZopePublication
+        is adapting an error view to error and request and calls them.
+        """
+        return self
+
+
+class ParseErrorView(JSONRPCErrorView):
+    """Knows the error data for parse errors."""
+
+    code = -32700
+    message = u'Parse error'
+
+    @property
+    def data(self):
+        errMsg = _('Parse error')
+        return {'i18nMessage':zope.i18n.translate(errMsg, context=self.request)}
+
+
+class InvalidRequestErrorView(JSONRPCErrorView):
+    """Knows the error data for invalid request errors."""
+
+    code = -32600
+    message = u'Invalid Request'
+
+    @property
+    def data(self):
+        errMsg = _('Invalid Request')
+        return {'i18nMessage':zope.i18n.translate(errMsg, context=self.request)}
+
+
+class MethodNotFoundView(JSONRPCErrorView):
+    """Knows the error data for NotFound errors."""
+
+    code = -32601
+    message = u'Method not found'
+
+    @property
+    def data(self):
+        errMsg = _('Method not found')
+        return {'i18nMessage':zope.i18n.translate(errMsg, context=self.request)}
+
+
+class InvalidParamsErrorView(JSONRPCErrorView):
+    """Knows the error data for invalid params errors."""
+
+    code = -32602
+    message = u'Invalid params'
+
+    @property
+    def data(self):
+        errMsg = _('Invalid params')
+        return {'i18nMessage':zope.i18n.translate(errMsg, context=self.request)}
+
+
+class InternalErrorView(JSONRPCErrorView):
+    """Knows the error data for invalid params errors."""
+
+    code = -32603
+    message = u'Internal error'
+
+    @property
+    def data(self):
+        errMsg = _('Internal error')
+        return {'i18nMessage':zope.i18n.translate(errMsg, context=self.request)}

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/error.zcml
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/error.zcml	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/error.zcml	2009-08-23 20:10:31 UTC (rev 103118)
@@ -3,41 +3,102 @@
     xmlns="http://namespaces.zope.org/browser"
     i18n_domain="z3c">
 
-  <!-- implement error views for this exceptions
-       The error views should return a 1i8n aware error message as result
-       which get set in setResult instead of using the built in 
-       handleException form the JSONRPCResponse. 
-       See zope.app.publication.zopepublication.ZopePublication
-  -->
-  <defaultView
+  <!-- zope error views used by ZopePublication -->
+  <page
       name="error"
+      class=".error.InternalErrorView"
       for="zope.interface.common.interfaces.IException"
       layer="z3c.jsonrpc.layer.IJSONRPCLayer"
+      permission="zope.Public"
       />
-
-  <defaultView
+  <page
       name="error"
+      class=".error.MethodNotFoundView"
+      for="zope.publisher.interfaces.INotFound"
+      layer="z3c.jsonrpc.layer.IJSONRPCLayer"
+      permission="zope.Public"
+      />
+  <page
+      name="error"
+      class=".error.MethodNotFoundView"
       for="zope.publisher.interfaces.ITraversalException"
       layer="z3c.jsonrpc.layer.IJSONRPCLayer"
+      permission="zope.Public"
       />
+  <page
+      name="error"
+      class=".error.InternalErrorView"
+      for="zope.exceptions.interfaces.IUserError"
+      layer="z3c.jsonrpc.layer.IJSONRPCLayer"
+      permission="zope.Public"
+      />
 
-  <!-- IUnauthorized -->
+
+  <!-- JSON-RPC error views used by ZopePublication -->
   <page
       name="error"
-      class=".error.UnauthorizedResponse"
-      for="zope.security.interfaces.IUnauthorized"
+      class=".error.InternalErrorView"
+      for=".interfaces.IJSONRPCException"
       layer="z3c.jsonrpc.layer.IJSONRPCLayer"
       permission="zope.Public"
       />
+  <page
+      name="error"
+      class=".error.ParseErrorView"
+      for=".exception.ParseError"
+      layer="z3c.jsonrpc.layer.IJSONRPCLayer"
+      permission="zope.Public"
+      />
+  <page
+      name="error"
+      class=".error.InvalidRequestErrorView"
+      for=".exception.InvalidRequest"
+      layer="z3c.jsonrpc.layer.IJSONRPCLayer"
+      permission="zope.Public"
+      />
+  <page
+      name="error"
+      class=".error.MethodNotFoundView"
+      for=".exception.MethodNotFound"
+      layer="z3c.jsonrpc.layer.IJSONRPCLayer"
+      permission="zope.Public"
+      />
+  <page
+      name="error"
+      class=".error.InvalidParamsErrorView"
+      for=".exception.InvalidParams"
+      layer="z3c.jsonrpc.layer.IJSONRPCLayer"
+      permission="zope.Public"
+      />
+  <page
+      name="error"
+      class=".error.InternalErrorView"
+      for=".exception.InternalError"
+      layer="z3c.jsonrpc.layer.IJSONRPCLayer"
+      permission="zope.Public"
+      />
 
+  <!-- default view names -->
   <defaultView
       name="error"
-      for="zope.security.interfaces.IUnauthorized"
+      for=".interfaces.IJSONRPCException"
       layer="z3c.jsonrpc.layer.IJSONRPCLayer"
       />
 
   <defaultView
       name="error"
+      for="zope.interface.common.interfaces.IException"
+      layer="z3c.jsonrpc.layer.IJSONRPCLayer"
+      />
+
+  <defaultView
+      name="error"
+      for="zope.publisher.interfaces.ITraversalException"
+      layer="z3c.jsonrpc.layer.IJSONRPCLayer"
+      />
+
+  <defaultView
+      name="error"
       for="zope.exceptions.interfaces.IUserError"
       layer="z3c.jsonrpc.layer.IJSONRPCLayer"
       />

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/exception.py
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/exception.py	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/exception.py	2009-08-23 20:10:31 UTC (rev 103118)
@@ -15,24 +15,16 @@
 $Id:$
 """
 
+import zope.interface
+from z3c.jsonrpc import interfaces
 
-# The error codes used since JSON-RPC 2.0:
-#
-# code   message           Meaning 
-# --------------------------------
-# -32700 Parse error.          Invalid JSON. An error occurred on the server 
-#                              while parsing the JSON text.
-# -32600 Invalid Request.      The received JSON not a valid JSON-RPC Request.
-# -32601 Method not found.     The requested remote-procedure does not exist, 
-#                              is not available.
-# -32602 Invalid params.       Invalid method parameters.
-# -32603 Internal error.       Internal JSON-RPC error.
-# -32099..-32000 Server error. Reserved for implementation-defined server-errors.
 
 class JSONRPCException(Exception):
     """Base class for JSON-RPC exception."""
 
+    zope.interface.implements(interfaces.IJSONRPCException)
 
+
 class ParseError(JSONRPCException):
     """Invalid JSON. An error occurred on the server while parsing the JSON text."""
 

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/interfaces.py
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/interfaces.py	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/interfaces.py	2009-08-23 20:10:31 UTC (rev 103118)
@@ -16,6 +16,7 @@
 """
 __docformat__ = "reStructuredText"
 
+import zope.schema
 import zope.interface
 from zope.publisher.interfaces import IPublication
 from zope.publisher.interfaces import IPublishTraverse
@@ -60,3 +61,36 @@
     """JSON-RPC request."""
 
     jsonID = zope.interface.Attribute("""JSON-RPC ID for the request""")
+
+
+class IJSONRPCException(zope.interface.Interface):
+    """JSON-RPC error"""
+
+
+class IJSONRPCErrorView(zope.interface.Interface):
+    """Error view base class used by ZopePublications error handling.
+    """
+
+    code = zope.schema.Int(
+        title=u'Error code',
+        description=u'JSON-RPC error code',
+        default=-32603,
+        required=True)
+
+    message = zope.schema.Text(
+        title=u'Error message',
+        description=u'JSON-RPC error message',
+        default=u'Internal error',
+        required=True)
+
+    data = zope.schema.Text(
+        title=u'Error data',
+        description=u'JSON-RPC error data',
+        default=u'',
+        required=True)
+
+    def __init__(self):
+        """Adapts an error and a request."""
+
+    def __call__(self):
+        """Must return itself by calling."""

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/publisher.py
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/publisher.py	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/publisher.py	2009-08-23 20:10:31 UTC (rev 103118)
@@ -28,6 +28,7 @@
 from zope.publisher.http import HTTPRequest
 from zope.publisher.http import HTTPResponse
 from zope.publisher.http import getCharsetUsingRequest
+from zope.publisher.http import DirectResult
 from zope.security.proxy import isinstance
 
 from z3c.json.interfaces import IJSONReader
@@ -264,7 +265,6 @@
 class JSONRPCResponse(HTTPResponse):
     """JSON-RPC Response"""
 
-
     def setResult(self, result):
         """The result dict contains the following key value pairs
 
@@ -286,19 +286,50 @@
         """
         jsonId = self._request.jsonId
         jsonVersion = self._request.jsonVersion
-        result = premarshal(result)
-        if jsonVersion == "1.0":
-            wrapper = {'result': result, 'error': None, 'id': jsonId}
-        elif jsonVersion == "1.1":
-            wrapper = {'version': jsonVersion, 'result': result, 'id': jsonId}
+
+        if interfaces.IJSONRPCErrorView.providedBy(result):
+            if self._request.jsonVersion == "1.0":
+                wrapper = {'result': None,
+                           'error': result.message,
+                           'id': self._request.jsonId}
+            elif self._request.jsonVersion == "1.1":
+                wrapper = {'version': self._request.jsonVersion,
+                           'error': result.message,
+                           'id': self._request.jsonId}
+            else:
+                wrapper = {'jsonrpc': self._request.jsonVersion,
+                           'error': {'code': result.code,
+                                     'message': result.message,
+                                     'data': result.data},
+                           'id': self._request.jsonId}
+    
+            try:
+                json = zope.component.getUtility(IJSONWriter)
+                result = json.write(wrapper)
+                body = self._prepareResult(result)
+                super(JSONRPCResponse, self).setResult(DirectResult((body,)))
+                logger.log(DEBUG, "Exception: %s" % result)
+                # error response is not really an error, it's valid response
+                self.setStatus(200)
+            except:
+                # Catch all exceptions at this point
+                self.handleException(sys.exc_info())
+                return
+
         else:
-            wrapper = {'jsonrpc': jsonVersion, 'result': result, 'id': jsonId}
-        json = zope.component.getUtility(IJSONWriter)
-        encoding = getCharsetUsingRequest(self._request)
-        result = json.write(wrapper)
-        body = self._prepareResult(result)
-        super(JSONRPCResponse,self).setResult(body)
-        logger.log(DEBUG, "%s" % result)
+            result = premarshal(result)
+            if jsonVersion == "1.0":
+                wrapper = {'result': result, 'error': None, 'id': jsonId}
+            elif jsonVersion == "1.1":
+                wrapper = {'version': jsonVersion, 'result': result, 'id': jsonId}
+            else:
+                wrapper = {'jsonrpc': jsonVersion, 'result': result, 'id': jsonId}
+            json = zope.component.getUtility(IJSONWriter)
+            encoding = getCharsetUsingRequest(self._request)
+            result = json.write(wrapper)
+            body = self._prepareResult(result)
+            super(JSONRPCResponse,self).setResult(DirectResult((body,)))
+            logger.log(DEBUG, "%s" % result)
 
     def _prepareResult(self, result):
         # we've asked json to return unicode; result should be unicode
@@ -320,6 +351,10 @@
         return body
 
     def handleException(self, exc_info):
+        # only legacy Exception where we didn't define a view for get handled
+        # by this method. All exceptions where we have a view registered for
+        # get handled by the setResult method based on the given
+        # IJSONRPCErrorView
         t, value = exc_info[:2]
         exc_data = []
         for file, lineno, function, text in traceback.extract_tb(exc_info[2]):
@@ -332,15 +367,14 @@
         if self._request.jsonVersion == "1.0":
             wrapper = {'result': None,
                        'error': s,
-                       'id': self._request.jsonId,}
+                       'id': self._request.jsonId}
         elif self._request.jsonVersion == "1.1":
             wrapper = {'version': self._request.jsonVersion,
                        'error': s,
-                       'id': self._request.jsonId,}
+                       'id': self._request.jsonId}
         else:
-            # TODO: implement better error handling, use the right error codes.
-            # see:
-            # http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal#error-object
+            # this only happens if error handling was running into en error or
+            # if we didn't define an IJSONRPCErrorView for a given error
             wrapper = {'jsonrpc': self._request.jsonVersion,
                        'error': {'code': -32603,
                                  'message': 'Invalid JSON-RPC',
@@ -350,6 +384,6 @@
         json = zope.component.getUtility(IJSONWriter)
         result = json.write(wrapper)
         body = self._prepareResult(result)
-        super(JSONRPCResponse, self).setResult(body)
+        super(JSONRPCResponse, self).setResult(DirectResult((body,)))
         logger.log(DEBUG, "Exception: %s" % result)
         self.setStatus(200)

Deleted: z3c.jsonrpc/trunk/src/z3c/jsonrpc/z3c.jsonrpc-configure.zcml
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/z3c.jsonrpc-configure.zcml	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/z3c.jsonrpc-configure.zcml	2009-08-23 20:10:31 UTC (rev 103118)
@@ -1 +0,0 @@
-<include package="z3c.jsonrpc" />

Deleted: z3c.jsonrpc/trunk/src/z3c/jsonrpc/z3c.jsonrpc-meta.zcml
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/z3c.jsonrpc-meta.zcml	2009-08-23 19:54:32 UTC (rev 103117)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/z3c.jsonrpc-meta.zcml	2009-08-23 20:10:31 UTC (rev 103118)
@@ -1 +0,0 @@
-<include package="z3c.jsonrpc" file="meta.zcml" />



More information about the Checkins mailing list