def test_either_left_functor_law2(self) -> None: """fmap (f . g) x = fmap f (fmap g x)""" def f(x): return x + 10 def g(x): return x * 10 self.assertEqual( Left(42).map(f).map(g), Left(42).map(lambda x: g(f(x))))
async def call(request: Request, context: Any, method: Method) -> Result: try: result = await method(*extract_args(request, context), **extract_kwargs(request)) validate_result(result) except JsonRpcError as exc: return Left( ErrorResult(code=exc.code, message=exc.message, data=exc.data)) except Exception as exc: # Other error inside method - Internal error logging.exception(exc) return Left(InternalErrorResult(str(exc))) return result
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) )
def test_call_raising_exception(): def method(): raise ValueError("foo") assert call(Request("ping", [], 1), NOCONTEXT, method) == Left( ErrorResult(ERROR_INTERNAL_ERROR, "Internal error", "foo") )
def test_call_raising_jsonrpcerror(): def method(): raise JsonRpcError(code=1, message="foo", data=NODATA) assert call(Request("ping", [], 1), NOCONTEXT, method) == Left( ErrorResult(1, "foo") )
def dispatch_to_response_pure( *, deserializer: Callable[[str], Deserialized], validator: Callable[[Deserialized], Deserialized], methods: Methods, context: Any, post_process: Callable[[Response], Iterable[Any]], request: str, ) -> Union[Response, List[Response], None]: """A function from JSON-RPC request string to Response namedtuple(s), (yet to be serialized to json). Returns: A single Response, a list of Responses, or None. None is given for notifications or batches of notifications, to indicate that we should not respond. """ try: result = deserialize_request(deserializer, request).bind( partial(validate_request, validator) ) return ( post_process(result) if isinstance(result, Left) else dispatch_deserialized(methods, context, post_process, result._value) ) except Exception as exc: # There was an error with the jsonrpcserver library. logging.exception(exc) return post_process(Left(ServerErrorResponse(str(exc), None)))
def test_validate_result_positionals_not_passed(): assert validate_args( Request("f", {"foo": "bar"}, NOID), NOCONTEXT, lambda x: None ) == Left( ErrorResult( ERROR_INVALID_PARAMS, "Invalid params", "missing a required argument: 'x'" ) )
def test_validate_result_no_arguments_too_many_positionals(): assert validate_args(Request("f", ["foo"], NOID), NOCONTEXT, lambda: None) == Left( ErrorResult( code=ERROR_INVALID_PARAMS, message="Invalid params", data="too many positional arguments", ) )
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, ) )
def get_method(methods: Methods, method_name: str) -> Either[ErrorResult, Method]: """Get the requested method from the methods dict. Returns: Either the function to be called, or a Method Not Found result. """ try: return Right(methods[method_name]) except KeyError: return Left(MethodNotFoundResult(method_name))
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) )
def validate_args( request: Request, context: Any, func: Method ) -> Either[ErrorResult, Method]: """Ensure the method can be called with the arguments given. Returns: Either the function to be called, or an Invalid Params error result. """ try: signature(func).bind(*extract_args(request, context), **extract_kwargs(request)) except TypeError as exc: return Left(InvalidParamsResult(str(exc))) return Right(func)
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)))
def call(request: Request, context: Any, method: Method) -> Result: """Call the method. Handles any exceptions raised in the method, being sure to return an Error response. Returns: A Result. """ try: result = method(*extract_args(request, context), **extract_kwargs(request)) # validate_result raises AssertionError if the return value is not a valid # Result, which should respond with Internal Error because its a problem in the # method. validate_result(result) # Raising JsonRpcError inside the method is an alternative way of returning an error # response. except JsonRpcError as exc: return Left(ErrorResult(code=exc.code, message=exc.message, data=exc.data)) # Any other uncaught exception inside method - internal error. except Exception as exc: logging.exception(exc) return Left(InternalErrorResult(str(exc))) return result
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, }
def deserialize_request( deserializer: Callable[[str], Deserialized], request: str ) -> Either[ErrorResponse, Deserialized]: """Parse the JSON request string. Returns: Either the deserialized request or a "Parse Error" response. """ try: return Right(deserializer(request)) # Since the deserializer is unknown, the specific exception that will be raised is # also unknown. Any exception raised we assume the request is invalid, return a # parse error response. except Exception as exc: return Left(ParseErrorResponse(str(exc)))
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)) )
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)) )
def validate_request( validator: Callable[[Deserialized], Deserialized], request: Deserialized ) -> Either[ErrorResponse, Deserialized]: """Validate the request against a JSON-RPC schema. Ensures the parsed request is valid JSON-RPC. Returns: Either the same request passed in or an Invalid request response. """ try: validator(request) # Since the validator is unknown, the specific exception that will be raised is also # unknown. Any exception raised we assume the request is invalid and return an # "invalid request" response. except Exception as exc: return Left(InvalidRequestResponse("The request failed schema validation")) return Right(request)
def to_response(request: Request, result: Result) -> Response: """Maps a Request plus a Result to a Response. A Response is just a Result plus the id from the original Request. Raises: AssertionError if the request is a notification. Notifications can't be responded to. If a notification is given and AssertionError is raised, we should respond with Server Error, because notifications should have been removed by this stage. Returns: A Response. """ assert request.id is not NOID return ( Left(ErrorResponse(**result._error._asdict(), id=request.id)) if isinstance(result, Left) else Right(SuccessResponse(**result._value._asdict(), id=request.id)) )
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, ) )
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, ) )
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, ) )
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)) )
async def dispatch_to_response_pure( *, deserializer: Callable[[str], Deserialized], validator: Callable[[Deserialized], Deserialized], methods: Methods, context: Any, post_process: Callable[[Response], Iterable[Any]], request: str, ) -> Union[Response, Iterable[Response], None]: try: result = deserialize_request(deserializer, request).bind( partial(validate_request, validator)) return (post_process(result) if isinstance(result, Left) else await dispatch_deserialized( methods, context, post_process, result._value)) except Exception as exc: logging.exception(exc) return post_process(Left(ServerErrorResponse(str(exc), None)))
def validate_price_range(ctx: dict) -> Either: price_query = price_range if not price_query: return Right(ctx) try: price_query = price_query.strip().split(',') price_query.sort() lower_limit, upper_limit = map(int, price_query) if lower_limit < 0 or upper_limit < 0: raise ValueError price_query = Q(categories__services__price__range=(lower_limit, upper_limit)) return Right( dict(condition=ctx.get('condition').add(price_query, Q.AND))) except ValueError: return Left(dict(error_code=ListDoctors.INVALID_PRICE_RANGE))
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, ) )
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, ) )
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, ) )
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, ) )