Exemple #1
0
 def test_no_id(self):
     # This is OK - we do respond to notifications with certain errors, such
     # as parse error and invalid request.
     response = ErrorResponse(
         status.HTTP_BAD_REQUEST, None, status.JSONRPC_INVALID_REQUEST_CODE,
         'foo')
     self.assertEqual(None, response['id'])
Exemple #2
0
def test_ErrorResponse():
    response = ErrorResponse(sentinel.code, sentinel.message, sentinel.data,
                             sentinel.id)
    assert response.code is sentinel.code
    assert response.message is sentinel.message
    assert response.data is sentinel.data
    assert response.id is sentinel.id
Exemple #3
0
 def test_str(self):
     response = ErrorResponse(status.HTTP_BAD_REQUEST, 1,
                              status.JSONRPC_INVALID_REQUEST_CODE, 'foo',
                              'bar')
     self.assertEqual(
         '{"jsonrpc": "2.0", "error": {"code": -32600, "message": "foo"}, "id": 1}',
         str(response))
Exemple #4
0
 def test(self):
     response = ErrorResponse(
         status.HTTP_BAD_REQUEST, 1, status.JSONRPC_INVALID_REQUEST_CODE,
         'foo', 'bar')
     self.assertEqual(
         {'jsonrpc': '2.0', 'error': {'code': -32600, 'message': 'foo'}, 'id': 1},
         response)
Exemple #5
0
def test_error_response_no_id():
    # Responding with an error to a Notification - this is OK; we do respond to
    # notifications under certain circumstances, such as "invalid json" and "invalid
    # json-rpc".
    assert (
        str(ErrorResponse("foo", id=None, code=-1, debug=True, http_status=200))
        == '{"jsonrpc": "2.0", "error": {"code": -1, "message": "foo"}, "id": null}'
    )
Exemple #6
0
def test_error_response():
    response = ErrorResponse("foo", id=1, code=-1, http_status=200)
    assert response.code == -1
    assert response.message == "foo"
    assert response.wanted == True
    assert (
        str(response) ==
        '{"jsonrpc": "2.0", "error": {"code": -1, "message": "foo"}, "id": 1}')
Exemple #7
0
def test_validate_request_invalid():
    assert validate_request(default_validator, {"jsonrpc": "2.0"}) == Left(
        ErrorResponse(
            ERROR_INVALID_REQUEST,
            "Invalid request",
            "The request failed schema validation",
            None,
        )
    )
Exemple #8
0
def test_error_response_data_with_debug_enabled():
    assert (
        str(
            ErrorResponse(
                "foo", id=None, code=-1, data="bar", debug=True, http_status=200
            )
        )
        == '{"jsonrpc": "2.0", "error": {"code": -1, "message": "foo", "data": "bar"}, "id": null}'
    )
Exemple #9
0
 def test_debug(self):
     response = ErrorResponse(
         status.HTTP_BAD_REQUEST,
         1,
         status.JSONRPC_INVALID_REQUEST_CODE,
         "foo",
         "bar",
         debug=True,
     )
     self.assertEqual("bar", response["error"]["data"])
Exemple #10
0
def test_error_response_data_with_debug_disabled():
    # The data is not included, because debug is False
    assert (
        str(
            ErrorResponse(
                "foo", id=None, code=-1, data="bar", debug=False, http_status=200
            )
        )
        == '{"jsonrpc": "2.0", "error": {"code": -1, "message": "foo"}, "id": null}'
    )
Exemple #11
0
def test_dispatch_to_response_pure_method_not_found():
    assert dispatch_to_response_pure(
        deserializer=default_deserializer,
        validator=default_validator,
        post_process=identity,
        context=NOCONTEXT,
        methods={},
        request='{"jsonrpc": "2.0", "method": "non_existant", "id": 1}',
    ) == Left(
        ErrorResponse(ERROR_METHOD_NOT_FOUND, "Method not found", "non_existant", 1)
    )
async def test_dispatch_to_response_pure_server_error(*_):
    async def foo():
        return Success()

    assert (await dispatch_to_response_pure(
        deserializer=default_deserializer,
        validator=default_validator,
        post_process=identity,
        context=NOCONTEXT,
        methods={"foo": foo},
        request='{"jsonrpc": "2.0", "method": "foo", "id": 1}',
    ) == Left(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "foo", None)))
Exemple #13
0
def test_to_response_ErrorResult():
    assert (
        to_response(
            Request("ping", [], sentinel.id),
            Left(
                ErrorResult(
                    code=sentinel.code, message=sentinel.message, data=sentinel.data
                )
            ),
        )
    ) == Left(
        ErrorResponse(sentinel.code, sentinel.message, sentinel.data, sentinel.id)
    )
Exemple #14
0
def test_to_serializable_ErrorResponse():
    assert to_serializable(
        Left(
            ErrorResponse(sentinel.code, sentinel.message, sentinel.data,
                          sentinel.id))) == {
                              "jsonrpc": "2.0",
                              "error": {
                                  "code": sentinel.code,
                                  "message": sentinel.message,
                                  "data": sentinel.data,
                              },
                              "id": sentinel.id,
                          }
Exemple #15
0
def test_dispatch_to_response_pure_internal_error():
    def foo():
        raise ValueError("foo")

    assert (
        dispatch_to_response_pure(
            deserializer=default_deserializer,
            validator=default_validator,
            post_process=identity,
            context=NOCONTEXT,
            methods={"foo": foo},
            request='{"jsonrpc": "2.0", "method": "foo", "id": 1}',
        )
        == Left(ErrorResponse(ERROR_INTERNAL_ERROR, "Internal error", "foo", 1))
    )
Exemple #16
0
def test_dispatch_to_response_pure_invalid_params_explicitly_returned():
    def foo(colour: str) -> Result:
        if colour not in ("orange", "red", "yellow"):
            return InvalidParams()

    assert (
        dispatch_to_response_pure(
            deserializer=default_deserializer,
            validator=default_validator,
            post_process=identity,
            context=NOCONTEXT,
            methods={"foo": foo},
            request='{"jsonrpc": "2.0", "method": "foo", "params": ["blue"], "id": 1}',
        )
        == Left(ErrorResponse(ERROR_INVALID_PARAMS, "Invalid params", NODATA, 1))
    )
Exemple #17
0
def test_dispatch_to_response_pure_notification_invalid_request():
    """Invalid JSON-RPC, must return an error. (impossible to determine if notification)"""
    assert dispatch_to_response_pure(
        deserializer=default_deserializer,
        validator=default_validator,
        post_process=identity,
        context=NOCONTEXT,
        methods={"ping": ping},
        request="{}",
    ) == Left(
        ErrorResponse(
            ERROR_INVALID_REQUEST,
            "Invalid request",
            "The request failed schema validation",
            None,
        )
    )
Exemple #18
0
def test_dispatch_to_response_pure_raising_exception():
    """Allow raising an exception to return an error."""

    def raise_exception():
        raise JsonRpcError(code=0, message="foo", data="bar")

    assert (
        dispatch_to_response_pure(
            deserializer=default_deserializer,
            validator=default_validator,
            post_process=identity,
            context=NOCONTEXT,
            methods={"raise_exception": raise_exception},
            request='{"jsonrpc": "2.0", "method": "raise_exception", "id": 1}',
        )
        == Left(ErrorResponse(0, "foo", "bar", 1))
    )
Exemple #19
0
def test_examples_invalid_json():
    response = dispatch_to_response_pure(
        methods={"ping": ping},
        context=NOCONTEXT,
        validator=default_validator,
        post_process=identity,
        deserializer=default_deserializer,
        request='[{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}, {"jsonrpc": "2.0", "method"]',
    )
    assert response == Left(
        ErrorResponse(
            ERROR_PARSE_ERROR,
            "Parse error",
            "Expecting ':' delimiter: line 1 column 96 (char 95)",
            None,
        )
    )
Exemple #20
0
def test_dispatch_to_response_pure_notification_parse_error():
    """Unable to parse, must return an error"""
    assert dispatch_to_response_pure(
        deserializer=default_deserializer,
        validator=default_validator,
        post_process=identity,
        context=NOCONTEXT,
        methods={"ping": ping},
        request="{",
    ) == Left(
        ErrorResponse(
            ERROR_PARSE_ERROR,
            "Parse error",
            "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
            None,
        )
    )
Exemple #21
0
def test_examples_empty_array():
    # This is an invalid JSON-RPC request, should return an error.
    response = dispatch_to_response_pure(
        request="[]",
        methods={"ping": ping},
        context=NOCONTEXT,
        validator=default_validator,
        post_process=identity,
        deserializer=default_deserializer,
    )
    assert response == Left(
        ErrorResponse(
            ERROR_INVALID_REQUEST,
            "Invalid request",
            "The request failed schema validation",
            None,
        )
    )
Exemple #22
0
def test_dispatch_to_response_pure_invalid_params_auto():
    def foo(colour: str, size: str):
        return Success()

    assert dispatch_to_response_pure(
        deserializer=default_deserializer,
        validator=default_validator,
        post_process=identity,
        context=NOCONTEXT,
        methods={"foo": foo},
        request='{"jsonrpc": "2.0", "method": "foo", "params": {"colour":"blue"}, "id": 1}',
    ) == Left(
        ErrorResponse(
            ERROR_INVALID_PARAMS,
            "Invalid params",
            "missing a required argument: 'size'",
            1,
        )
    )
Exemple #23
0
 def test(self):
     response = ErrorResponse(
         status.HTTP_BAD_REQUEST,
         1,
         status.JSONRPC_INVALID_REQUEST_CODE,
         "foo",
         "bar",
     )
     self.assertEqual(
         {
             "jsonrpc": "2.0",
             "error": {
                 "code": -32600,
                 "message": "foo"
             },
             "id": 1
         },
         response,
     )
Exemple #24
0
def test_examples_multiple_invalid_jsonrpc():
    """
    We break the spec here, by not validating each request in the batch individually.
    The examples are expecting a batch response full of error responses.
    """
    response = dispatch_to_response_pure(
        deserializer=default_deserializer,
        validator=default_validator,
        post_process=identity,
        context=NOCONTEXT,
        methods={"ping": ping},
        request="[1, 2, 3]",
    )
    assert response == Left(
        ErrorResponse(
            ERROR_INVALID_REQUEST,
            "Invalid request",
            "The request failed schema validation",
            None,
        )
    )
Exemple #25
0
def test_dispatch_to_response_pure_invalid_result():
    """Methods should return a Result, otherwise we get an Internal Error response."""

    def not_a_result():
        return None

    assert dispatch_to_response_pure(
        deserializer=default_deserializer,
        validator=default_validator,
        post_process=identity,
        context=NOCONTEXT,
        methods={"not_a_result": not_a_result},
        request='{"jsonrpc": "2.0", "method": "not_a_result", "id": 1}',
    ) == Left(
        ErrorResponse(
            ERROR_INTERNAL_ERROR,
            "Internal error",
            "The method did not return a valid Result (returned None)",
            1,
        )
    )
Exemple #26
0
        def handle(self):
            logger = self.server.logger
            poller = select.poll()
            poller.register(self.request.fileno(),
                            select.POLLIN | select.POLLPRI | select.POLLERR)
            while True:
                if poller.poll(500):
                    self.data = self.request.recv(8192).decode()

                    if not self.data:
                        break

                    while self.data[-1] != '\n':
                        self.data += self.request.recv(8192).decode()

                    self.data = self.data.strip()
                else:
                    if self.server.STOP_EVENT.is_set():
                        break
                    else:
                        continue

                lock_acquired = False
                try:
                    if self.server._request_cb is not None:
                        self.server._request_cb(self.data)
                    if self.server._client_lock.acquire(True, 10):
                        lock_acquired = True
                        logger.debug("Dispatching %s" % (self.data))
                        response = dispatcher.dispatch(self.server._methods,
                                                       self.data)
                        logger.debug("Responding with: %s" %
                                     response.json_debug)
                    else:
                        # Send a time out response
                        r = Request(self.data)
                        logger.debug(
                            "Timed out waiting for lock with request = %s" %
                            (self.data))
                        request_id = r.request_id if hasattr(
                            r, 'request_id') else None
                        response = ErrorResponse(
                            http_status=HTTP_STATUS_CODES[408],
                            request_id=request_id,
                            code=-32000,  # Server error
                            message="Timed out waiting for lock")
                except Exception as e:
                    if logger is not None:
                        logger.exception(e)
                finally:
                    if lock_acquired:
                        self.server._client_lock.release()

                try:
                    json_str = json.dumps(response.json_debug) + "\n"
                    msg = json_str.encode()
                    logger.debug("Message length = %d" % len(msg))
                    self.request.sendall(msg)
                except BrokenPipeError:
                    break
                except Exception as e:
                    if logger is not None:
                        logger.exception(e)
Exemple #27
0
def test_error_response_http_status():
    response = ErrorResponse(
        "foo", id=1, code=-1, http_status=status.HTTP_BAD_REQUEST, debug=False
    )
    assert response.http_status == status.HTTP_BAD_REQUEST
Exemple #28
0
def test_examples_mixed_requests_and_notifications():
    """
    We break the spec here. The examples put an invalid jsonrpc request in the
    mix here.  but it's removed to test the rest, because we're not validating
    each request individually. Any invalid jsonrpc will respond with a single
    error message.

    The spec example includes this which invalidates the entire request:
        {"foo": "boo"},
    """
    methods = {
        "sum": lambda *args: Right(SuccessResult(sum(args))),
        "notify_hello": lambda *args: Right(SuccessResult(19)),
        "subtract": lambda *args: Right(SuccessResult(args[0] - sum(args[1:]))),
        "get_data": lambda: Right(SuccessResult(["hello", 5])),
    }
    requests = json.dumps(
        [
            {"jsonrpc": "2.0", "method": "sum", "params": [1, 2, 4], "id": "1"},
            {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
            {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "2"},
            {
                "jsonrpc": "2.0",
                "method": "foo.get",
                "params": {"name": "myself"},
                "id": "5",
            },
            {"jsonrpc": "2.0", "method": "get_data", "id": "9"},
        ]
    )
    response = dispatch_to_response_pure(
        deserializer=default_deserializer,
        validator=default_validator,
        post_process=identity,
        context=NOCONTEXT,
        methods=methods,
        request=requests,
    )
    expected = [
        Right(
            SuccessResponse(result=7, id="1")
        ),  # {"jsonrpc": "2.0", "result": 7, "id": "1"},
        Right(
            SuccessResponse(result=19, id="2")
        ),  # {"jsonrpc": "2.0", "result": 19, "id": "2"},
        Left(
            ErrorResponse(
                code=-32601, message="Method not found", data="foo.get", id="5"
            )
        ),
        # {
        #     "jsonrpc": "2.0",
        #     "error": {"code": -32601, "message": "Method not found", "data": "foo.get"},
        #     "id": "5",
        # },
        Right(
            SuccessResponse(result=["hello", 5], id="9")
        ),  # {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"},
    ]
    # assert isinstance(response, Iterable)
    for r in response:
        assert r in expected
Exemple #29
0
 def test_debug(self):
     config.debug = True
     response = ErrorResponse(status.HTTP_BAD_REQUEST, 1,
                              status.JSONRPC_INVALID_REQUEST_CODE, 'foo',
                              'bar')
     self.assertEqual('bar', response['error']['data'])
def dispatch(methods, request, notification_errors=False):
    """Dispatch JSON-RPC requests to a list of methods::

        r = dispatch([cat], {'jsonrpc': '2.0', 'method': 'cat', 'id': 1})

    The first parameter can be either:

    - A *list* of functions, each identifiable by its ``__name__`` attribute.
    - Or a *dictionary* of name:method pairs.

    When using a **list**, the methods must be identifiable by a ``__name__``
    attribute.

    Functions already have a ``__name__`` attribute::

        >>> def cat():
        ...     return 'meow'
        ...
        >>> cat.__name__
        'cat'
        >>> dispatch([cat], ...)

    Lambdas require setting it::

        >>> cat = lambda: 'meow'
        >>> cat.__name__ = 'cat'
        >>> dispatch([cat], ...)

    As do partials::

        >>> max_ten = partial(min, 10)
        >>> max_ten.__name__ = 'max_ten'
        >>> dispatch([max_ten], ...)

    Alternatively, consider using a **dictionary** instead::

        >>> dispatch({'cat': cat, 'max_ten': max_ten}, ...)

    See the `Methods`_ module for another easy way to build the list of methods.

    :param methods: List or dict of methods to dispatch to.
    :param request:
        JSON-RPC request. This can be in dict or string form.  Byte arrays
        should be `decoded
        <https://docs.python.org/3/library/codecs.html#codecs.decode>`_ first.
    :param notification_errors:
        Should `notifications
        <http://www.jsonrpc.org/specification#notification>`_ get error
        responses? Typically notifications don't receive any response, except
        for "Parse error" and "Invalid request" errors. Enabling this will
        include all other errors such as "Method not found". A notification is
        then similar to many unix commands - *"There was no response, so I can
        assume the request was successful."*
    :returns: A `Response`_ object - either `RequestResponse`_,
              `NotificationResponse`_, or `ErrorResponse`_ if there was a
              problem processing the request. In any case, the return value
              gives you ``body``, ``body_debug``, ``json``, ``json_debug``, and
              ``http_status`` values.
    """

    # Process the request
    r = None
    error = None
    try:
        # Log the request
        request_log.info(str(request))
        # Create request object (also validates the request)
        r = Request(request)
        # Call the requested method
        result = _call(methods, r.method_name, r.args, r.kwargs)
    # Catch any JsonRpcServerError raised (Invalid Request, etc)
    except JsonRpcServerError as e:
        error = e
    # Catch uncaught exceptions, respond with ServerError
    except Exception as e:  # pylint: disable=broad-except
        # Log the uncaught exception
        logger.exception(e)
        # Create an exception object, used to build the response
        error = ServerError(str(e))

    # Now build a response.
    # Error
    if error:
        # Notifications get a non-response - see spec
        if r and r.is_notification and not notification_errors:
            response = NotificationResponse()
        else:
            # Get the 'id' part of the request, to include in error response
            request_id = r.request_id if r else None
            response = ErrorResponse(error.http_status, request_id, error.code,
                                     error.message, error.data)
    # Success
    else:
        # Notifications get a non-response
        if r and r.is_notification:
            response = NotificationResponse()
        else:
            response = RequestResponse(r.request_id, result)

    # Log the response and return it
    response_log.info(response.body,
                      extra={
                          'http_code': response.http_status,
                          'http_reason':
                          HTTP_STATUS_CODES[response.http_status]
                      })
    return response