diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml
index 99187f6..abc349c 100644
--- a/.github/workflows/code-quality.yml
+++ b/.github/workflows/code-quality.yml
@@ -10,7 +10,7 @@ jobs:
       with:
         python-version: 3.8
     - run: pip install --upgrade pip
-    - run: pip install types-setuptools "black<23" "pylint<3" "mypy<1" "jsonschema<5" pytest "oslash<1" "aiohttp<4" "aiozmq<1" "django<5" "fastapi<1" "flask<3" "flask-socketio<5.3.1" "pyzmq" "sanic" "tornado<7" "uvicorn<1" "websockets<11"
-    - run: black --diff --check $(git ls-files -- '*.py' ':!:docs/*')
-    - run: pylint $(git ls-files -- '*.py' ':!:docs/*')
+    - run: pip install types-setuptools "black<23" ruff "mypy<2" "jsonschema<5" pytest "returns<1" "aiohttp<4" "aiozmq<1" "django<5" "fastapi<1" "flask<3" "flask-socketio<5.3.1" "pyzmq" "sanic" "tornado<7" "uvicorn<1" "websockets<11"
+    - run: ruff check --select I $(git ls-files -- '*.py' ':!:docs/*')
+    - run: ruff format --check $(git ls-files -- '*.py' ':!:docs/*')
     - run: mypy --strict $(git ls-files -- '*.py' ':!:docs/*')
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e05ac27..f884896 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,38 +3,24 @@ default_language_version:
 exclude: (^docs)
 fail_fast: true
 repos:
-  - repo: https://github.com/ambv/black
-    rev: 22.8.0
+  - repo: https://github.com/astral-sh/ruff-pre-commit
+    rev: v0.4.1
     hooks:
-      - id: black
-        args: [--diff, --check]
-
-  - repo: https://github.com/PyCQA/pylint
-    rev: v2.15.3
-    hooks:
-      - id: pylint
-        additional_dependencies:
-          - oslash<1
-          - aiohttp<4
-          - aiozmq<1
-          - django<5
-          - fastapi<1
-          - flask<3
-          - flask-socketio<5.3.1
-          - jsonschema<5
-          - pytest
-          - pyzmq
-          - sanic
-          - tornado<7
-          - uvicorn<1
-          - websockets<11
+      - id: ruff
+        name: lint with ruff
+      - id: ruff
+        name: sort imports with ruff
+        args: [--select, I, --fix]
+      - id: ruff-format
+        name: format with ruff
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.971
+    rev: v1.2.0
     hooks:
       - id: mypy
         args: [--strict]
         additional_dependencies:
+          - returns<1
           - aiohttp<4
           - aiozmq<1
           - django<5
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92e4296..737b24a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,26 @@
 # jsonrpcserver Change Log
 
+## 6.0.0 (May 18, 2022)
+
+A small release but incrementing the major release number due to a breaking
+change for async use.
+
+Breaking changes:
+
+- Decorate async JSON-RPC methods with `@async_method` instead of `@method`.
+  The reason for this change is due to the typing of the decorator, async
+  functions return a different type (`Awaitable`) to other functions.
+
+Other changes:
+
+- Internally, replaced the Oslash dependency with
+  [Returns](https://github.com/dry-python/returns). Because Oslash is not meant
+  for production use.
+- Use `Ok` instead of `Success` when returning a response. This is to avoid
+  confusion with the Returns library now used internally which has it's own
+  `Success` class. It also matches Jsonrpcclient's `Ok` type. This is not a
+  breaking change, `Success` will still work for now. But use `Ok` instead.
+
 ## 5.0.9 (Sep 15, 2022)
 
 - Remove unncessary `package_data` from setup.py (#243)
diff --git a/README.md b/README.md
index 6d050cb..13d545f 100644
--- a/README.md
+++ b/README.md
@@ -16,11 +16,11 @@ pip install jsonrpcserver
 ```
 
 ```python
-from jsonrpcserver import method, serve, Success
+from jsonrpcserver import method, serve, Ok, Result
 
 @method
-def ping():
-    return Success("pong")
+def ping() -> Result:
+    return Ok("pong")
 
 if __name__ == "__main__":
     serve()
@@ -34,6 +34,6 @@ response = dispatch('{"jsonrpc": "2.0", "method": "ping", "id": 1}')
 
 [Watch a video on how to use it.](https://www.youtube.com/watch?v=3_BMmgJaFHQ)
 
-Full documentation is at [jsonrpcserver.com](https://www.jsonrpcserver.com/).
+Full documentation is in the [wiki](https://github.com/explodinglabs/jsonrpcserver/wiki).
 
 See also: [jsonrpcclient](https://github.com/explodinglabs/jsonrpcclient)
diff --git a/docs/async.md b/docs/async.md
index 296a74f..e52fde8 100644
--- a/docs/async.md
+++ b/docs/async.md
@@ -3,11 +3,11 @@
 Async dispatch is supported.
 
 ```python
-from jsonrpcserver import method, Success, async_dispatch
+from jsonrpcserver import async_dispatch, async_method, Ok, Result
 
-@method
+@async_method
 async def ping() -> Result:
-    return Success("pong")
+    return Ok("pong")
 
 await async_dispatch('{"jsonrpc": "2.0", "method": "ping", "id": 1}')
 ```
diff --git a/docs/conf.py b/docs/conf.py
index f876374..218ef00 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -3,6 +3,7 @@
 # This file only contains a selection of the most common options. For a full
 # list see the documentation:
 # https://www.sphinx-doc.org/en/master/usage/configuration.html
+from typing import List
 
 # -- Path setup --------------------------------------------------------------
 
@@ -38,7 +39,7 @@
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
 # This pattern also affects html_static_path and html_extra_path.
-exclude_patterns = []
+exclude_patterns: List[str] = []
 
 
 # -- Options for HTML output -------------------------------------------------
diff --git a/docs/dispatch.md b/docs/dispatch.md
index c53e342..2278b47 100644
--- a/docs/dispatch.md
+++ b/docs/dispatch.md
@@ -12,15 +12,18 @@ and gives a JSON-RPC response.
 
 ## Optional parameters
 
+The `dispatch` function has some optional parameters that allow you to
+customise how it works.
+
 ### methods
 
-This lets you specify a group of methods to dispatch to. It's an alternative to
-using the `@method` decorator. The value should be a dict mapping function
-names to functions.
+This lets you specify the methods to dispatch to. It's an alternative to using
+the `@method` decorator. The value should be a dict mapping function names to
+functions.
 
 ```python
 def ping():
-    return Success("pong")
+    return Ok("pong")
 
 dispatch(request, methods={"ping": ping})
 ```
@@ -35,7 +38,7 @@ If specified, this will be the first argument to all methods.
 ```python
 @method
 def greet(context, name):
-    return Success(context + " " + name)
+    return Ok(context + " " + name)
 
 >>> dispatch('{"jsonrpc": "2.0", "method": "greet", "params": ["Beau"], "id": 1}', context="Hello")
 '{"jsonrpc": "2.0", "result": "Hello Beau", "id": 1}'
@@ -43,12 +46,32 @@ def greet(context, name):
 
 ### deserializer
 
-A function that parses the request string. Default is `json.loads`.
+A function that parses the JSON request string. Default is `json.loads`.
 
 ```python
 dispatch(request, deserializer=ujson.loads)
 ```
 
+### jsonrpc_validator
+
+A function that validates the request once the JSON string has been parsed. The
+function should raise an exception (any exception) if the request doesn't match
+the JSON-RPC spec (https://www.jsonrpc.org/specification). Default is
+`default_jsonrpc_validator` which uses Jsonschema to validate requests against
+a schema.
+
+To disable JSON-RPC validation, pass `jsonrpc_validator=lambda _: None`, which
+will improve performance because this validation takes around half the dispatch
+time.
+
+### args_validator
+
+A function that validates a request's parameters against the signature of the
+Python function that will be called for it. Note this should not validate the
+_values_ of the parameters, it should simply ensure the parameters match the
+Python function's signature. For reference, see the `validate_args` function in
+`dispatcher.py`, which is the default `args_validator`.
+
 ### serializer
 
 A function that serializes the response string. Default is `json.dumps`.
@@ -56,10 +79,3 @@ A function that serializes the response string. Default is `json.dumps`.
 ```python
 dispatch(request, serializer=ujson.dumps)
 ```
-
-### validator
-
-A function that validates the request once the json has been parsed. The
-function should raise an exception (any exception) if the request doesn't match
-the JSON-RPC spec. Default is `default_validator` which validates the request
-against a schema.
diff --git a/docs/installation.md b/docs/installation.md
index 6c40ec8..d2f3680 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -3,11 +3,11 @@
 Create a `server.py`:
 
 ```python
-from jsonrpcserver import Success, method, serve
+from jsonrpcserver import method, serve, Ok
 
 @method
 def ping():
-    return Success("pong")
+    return Ok("pong")
 
 if __name__ == "__main__":
     serve()
@@ -27,3 +27,6 @@ Test the server:
 $ curl -X POST http://localhost:5000 -d '{"jsonrpc": "2.0", "method": "ping", "id": 1}'
 {"jsonrpc": "2.0", "result": "pong", "id": 1}
 ```
+
+`serve` is good for serving methods in development, but for production use
+`dispatch` instead.
diff --git a/docs/methods.md b/docs/methods.md
index dd5be3b..cba2406 100644
--- a/docs/methods.md
+++ b/docs/methods.md
@@ -4,20 +4,21 @@ Methods are functions that can be called by a JSON-RPC request. To write one,
 decorate a function with `@method`:
 
 ```python
-from jsonrpcserver import method, Result, Success, Error
+from jsonrpcserver import method, Error, Ok, Result
 
 @method
 def ping() -> Result:
-    return Success("pong")
+    return Ok("pong")
 ```
 
-If you don't need to respond with any value simply `return Success()`.
+If you don't need to respond with any value simply `return Ok()`.
 
 ## Responses
 
-Methods return either `Success` or `Error`. These are the [JSON-RPC response
+Methods return either `Ok` or `Error`. These are the [JSON-RPC response
 objects](https://www.jsonrpc.org/specification#response_object) (excluding the
-`jsonrpc` and `id` parts). `Error` takes a code, message, and optionally 'data'.
+`jsonrpc` and `id` parts). `Error` takes a code, message, and optionally
+'data'.
 
 ```python
 @method
@@ -36,7 +37,7 @@ Methods can accept arguments.
 ```python
 @method
 def hello(name: str) -> Result:
-    return Success("Hello " + name)
+    return Ok("Hello " + name)
 ```
 
 Testing it:
@@ -53,13 +54,13 @@ The JSON-RPC error code for this is **-32602**. A shortcut, *InvalidParams*, is
 included so you don't need to remember that.
 
 ```python
-from jsonrpcserver import method, Result, InvalidParams, Success, dispatch
+from jsonrpcserver import dispatch, method, InvalidParams, Ok, Result
 
 @method
 def within_range(num: int) -> Result:
     if num not in range(1, 5):
         return InvalidParams("Value must be 1-5")
-    return Success()
+    return Ok()
 ```
 
 This is the same as saying
diff --git a/examples/aiohttp_server.py b/examples/aiohttp_server.py
index 41982c4..6626a32 100644
--- a/examples/aiohttp_server.py
+++ b/examples/aiohttp_server.py
@@ -1,12 +1,14 @@
 """AioHTTP server"""
+
 from aiohttp import web
-from jsonrpcserver import method, Result, Success, async_dispatch
+
+from jsonrpcserver import Ok, Result, async_dispatch, async_method
 
 
-@method
+@async_method
 async def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 async def handle(request: web.Request) -> web.Response:
diff --git a/examples/aiozmq_server.py b/examples/aiozmq_server.py
index 60ef29d..545bceb 100644
--- a/examples/aiozmq_server.py
+++ b/examples/aiozmq_server.py
@@ -1,15 +1,17 @@
 """AioZMQ server"""
+
 import asyncio
 
 import aiozmq  # type: ignore
 import zmq
-from jsonrpcserver import method, Result, Success, async_dispatch
+
+from jsonrpcserver import Ok, Result, async_dispatch, async_method
 
 
-@method
+@async_method
 async def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 async def main() -> None:
diff --git a/examples/asyncio_server.py b/examples/asyncio_server.py
index d668a76..4352403 100644
--- a/examples/asyncio_server.py
+++ b/examples/asyncio_server.py
@@ -1,15 +1,16 @@
 """Demonstrates processing a batch of 100 requests asynchronously with asyncio."""
+
 import asyncio
 import json
 
-from jsonrpcserver import method, Result, Success, async_dispatch
+from jsonrpcserver import Ok, Result, async_dispatch, async_method
 
 
-@method
+@async_method
 async def sleep_() -> Result:
     """JSON-RPC method"""
     await asyncio.sleep(1)
-    return Success()
+    return Ok()
 
 
 async def handle(req: str) -> None:
diff --git a/examples/django_server.py b/examples/django_server.py
index 52228b9..86f77fd 100644
--- a/examples/django_server.py
+++ b/examples/django_server.py
@@ -1,13 +1,15 @@
 """Django server"""
+
 from django.http import HttpRequest, HttpResponse  # type: ignore
 from django.views.decorators.csrf import csrf_exempt  # type: ignore
-from jsonrpcserver import method, Result, Success, dispatch
+
+from jsonrpcserver import Ok, Result, dispatch, method
 
 
 @method
 def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 @csrf_exempt  # type: ignore
diff --git a/examples/fastapi_server.py b/examples/fastapi_server.py
index 812a636..858ba07 100644
--- a/examples/fastapi_server.py
+++ b/examples/fastapi_server.py
@@ -1,7 +1,9 @@
 """FastAPI server"""
+
+import uvicorn
 from fastapi import FastAPI, Request, Response
-import uvicorn  # type: ignore
-from jsonrpcserver import Result, Success, dispatch, method
+
+from jsonrpcserver import Ok, Result, dispatch, method
 
 app = FastAPI()
 
@@ -9,7 +11,7 @@
 @method
 def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 @app.post("/")
diff --git a/examples/flask_server.py b/examples/flask_server.py
index 24581d9..071b110 100644
--- a/examples/flask_server.py
+++ b/examples/flask_server.py
@@ -1,6 +1,8 @@
 """Flask server"""
+
 from flask import Flask, Response, request
-from jsonrpcserver import method, Result, Success, dispatch
+
+from jsonrpcserver import Ok, Result, dispatch, method
 
 app = Flask(__name__)
 
@@ -8,7 +10,7 @@
 @method
 def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 @app.route("/", methods=["POST"])
diff --git a/examples/http_server.py b/examples/http_server.py
index a240ea8..5b81a2a 100644
--- a/examples/http_server.py
+++ b/examples/http_server.py
@@ -2,15 +2,16 @@
 
 Demonstrates using Python's builtin http.server module to serve JSON-RPC.
 """
+
 from http.server import BaseHTTPRequestHandler, HTTPServer
 
-from jsonrpcserver import method, Result, Success, dispatch
+from jsonrpcserver import Ok, Result, dispatch, method
 
 
 @method
 def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 class TestHttpServer(BaseHTTPRequestHandler):
diff --git a/examples/jsonrpcserver_server.py b/examples/jsonrpcserver_server.py
index 5d87590..55f7db6 100644
--- a/examples/jsonrpcserver_server.py
+++ b/examples/jsonrpcserver_server.py
@@ -2,13 +2,14 @@
 
 Uses jsonrpcserver's built-in "serve" function.
 """
-from jsonrpcserver import method, Result, Success, serve
+
+from jsonrpcserver import Ok, Result, method, serve
 
 
 @method
 def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 if __name__ == "__main__":
diff --git a/examples/sanic_server.py b/examples/sanic_server.py
index 0b4b68a..be80c26 100644
--- a/examples/sanic_server.py
+++ b/examples/sanic_server.py
@@ -1,8 +1,10 @@
 """Sanic server"""
+
 from sanic import Sanic
 from sanic.request import Request
 from sanic.response import HTTPResponse, json
-from jsonrpcserver import Result, Success, dispatch_to_serializable, method
+
+from jsonrpcserver import Ok, Result, dispatch_to_serializable, method
 
 app = Sanic("JSON-RPC app")
 
@@ -10,7 +12,7 @@
 @method
 def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 @app.route("/", methods=["POST"])
diff --git a/examples/socketio_server.py b/examples/socketio_server.py
index 2911f11..7dd4b94 100644
--- a/examples/socketio_server.py
+++ b/examples/socketio_server.py
@@ -1,7 +1,9 @@
 """SocketIO server"""
+
 from flask import Flask, Request
 from flask_socketio import SocketIO, send  # type: ignore
-from jsonrpcserver import method, Result, Success, dispatch
+
+from jsonrpcserver import Ok, Result, dispatch, method
 
 app = Flask(__name__)
 socketio = SocketIO(app)
@@ -10,7 +12,7 @@
 @method
 def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 @socketio.on("message")  # type: ignore
diff --git a/examples/tornado_server.py b/examples/tornado_server.py
index 6a86b35..3a027a4 100644
--- a/examples/tornado_server.py
+++ b/examples/tornado_server.py
@@ -1,14 +1,16 @@
 """Tornado server"""
+
 from typing import Awaitable, Optional
 
 from tornado import ioloop, web
-from jsonrpcserver import method, Result, Success, async_dispatch
+
+from jsonrpcserver import Ok, Result, async_dispatch, async_method
 
 
-@method
+@async_method
 async def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 class MainHandler(web.RequestHandler):
diff --git a/examples/websockets_server.py b/examples/websockets_server.py
index 7367a65..ed8812e 100644
--- a/examples/websockets_server.py
+++ b/examples/websockets_server.py
@@ -1,14 +1,16 @@
 """Websockets server"""
+
 import asyncio
 
 from websockets.server import WebSocketServerProtocol, serve
-from jsonrpcserver import method, Success, Result, async_dispatch
+
+from jsonrpcserver import Ok, Result, async_dispatch, async_method
 
 
-@method
+@async_method
 async def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 async def main(websocket: WebSocketServerProtocol, _: str) -> None:
diff --git a/examples/werkzeug_server.py b/examples/werkzeug_server.py
index e6a1e8f..dcc8590 100644
--- a/examples/werkzeug_server.py
+++ b/examples/werkzeug_server.py
@@ -1,13 +1,15 @@
 """Werkzeug server"""
+
 from werkzeug.serving import run_simple
 from werkzeug.wrappers import Request, Response
-from jsonrpcserver import method, Result, Success, dispatch
+
+from jsonrpcserver import Ok, Result, dispatch, method
 
 
 @method
 def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 @Request.application
diff --git a/examples/zeromq_server.py b/examples/zeromq_server.py
index 6d0c032..d88368c 100644
--- a/examples/zeromq_server.py
+++ b/examples/zeromq_server.py
@@ -1,6 +1,8 @@
 """ZeroMQ server"""
+
 import zmq
-from jsonrpcserver import method, Result, Success, dispatch
+
+from jsonrpcserver import Ok, Result, dispatch, method
 
 socket = zmq.Context().socket(zmq.REP)
 
@@ -8,7 +10,7 @@
 @method
 def ping() -> Result:
     """JSON-RPC method"""
-    return Success("pong")
+    return Ok("pong")
 
 
 if __name__ == "__main__":
diff --git a/jsonrpcserver/__init__.py b/jsonrpcserver/__init__.py
index 9f6c5a6..8136ba0 100644
--- a/jsonrpcserver/__init__.py
+++ b/jsonrpcserver/__init__.py
@@ -1,28 +1,41 @@
-"""Use __all__ so mypy considers these re-exported."""
+"""Jsonrpcserver"""
+
+from returns.result import Result as R
+
+from .async_main import (
+    dispatch as async_dispatch,
+)
+from .async_main import (
+    dispatch_to_response as async_dispatch_to_response,
+)
+from .async_main import (
+    dispatch_to_serializable as async_dispatch_to_serializable,
+)
+from .async_methods import method as async_method
+from .exceptions import JsonRpcError
+from .main import dispatch, dispatch_to_response, dispatch_to_serializable
+from .methods import method
+from .result import Error, ErrorResult, InvalidParams, Ok, SuccessResult
+from .server import serve
+
+Success = Ok  # For backward compatibility - version 5 used Success instead of Ok
+Result = R[SuccessResult, ErrorResult]
+
+
 __all__ = [
     "Error",
     "InvalidParams",
     "JsonRpcError",
+    "Ok",
     "Result",
     "Success",
     "async_dispatch",
     "async_dispatch_to_response",
     "async_dispatch_to_serializable",
+    "async_method",
     "dispatch",
     "dispatch_to_response",
     "dispatch_to_serializable",
     "method",
     "serve",
 ]
-
-
-from .async_main import (
-    dispatch as async_dispatch,
-    dispatch_to_response as async_dispatch_to_response,
-    dispatch_to_serializable as async_dispatch_to_serializable,
-)
-from .exceptions import JsonRpcError
-from .main import dispatch, dispatch_to_response, dispatch_to_serializable
-from .methods import method
-from .result import Error, InvalidParams, Result, Success
-from .server import serve
diff --git a/jsonrpcserver/async_dispatcher.py b/jsonrpcserver/async_dispatcher.py
index 6ce66ae..49ba167 100644
--- a/jsonrpcserver/async_dispatcher.py
+++ b/jsonrpcserver/async_dispatcher.py
@@ -1,12 +1,15 @@
 """Async version of dispatcher.py"""
+
+import asyncio
+import logging
 from functools import partial
+from inspect import signature
 from itertools import starmap
 from typing import Any, Callable, Iterable, Tuple, Union
-import asyncio
-import logging
 
-from oslash.either import Left  # type: ignore
+from returns.result import Failure, Result, Success
 
+from .async_methods import Method, Methods
 from .dispatcher import (
     Deserialized,
     create_request,
@@ -14,18 +17,21 @@
     extract_args,
     extract_kwargs,
     extract_list,
-    get_method,
     not_notification,
     to_response,
-    validate_args,
     validate_request,
     validate_result,
 )
 from .exceptions import JsonRpcError
-from .methods import Method, Methods
 from .request import Request
-from .result import Result, InternalErrorResult, ErrorResult
 from .response import Response, ServerErrorResponse
+from .result import (
+    ErrorResult,
+    InternalErrorResult,
+    InvalidParamsResult,
+    MethodNotFoundResult,
+    SuccessResult,
+)
 from .utils import make_list
 
 logger = logging.getLogger(__name__)
@@ -33,41 +39,66 @@
 # pylint: disable=missing-function-docstring,duplicate-code
 
 
-async def call(request: Request, context: Any, method: Method) -> Result:
+async def call(
+    request: Request, context: Any, method: Method
+) -> Result[SuccessResult, ErrorResult]:
     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))
+        return Failure(ErrorResult(code=exc.code, message=exc.message, data=exc.data))
     except Exception as exc:  # pylint: disable=broad-except
         # Other error inside method - Internal error
         logger.exception(exc)
-        return Left(InternalErrorResult(str(exc)))
+        return Failure(InternalErrorResult(str(exc)))
     return result
 
 
+def validate_args(
+    request: Request, context: Any, func: Method
+) -> Result[Method, ErrorResult]:
+    """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 Failure(InvalidParamsResult(str(exc)))
+    return Success(func)
+
+
+def get_method(methods: Methods, method_name: str) -> Result[Method, ErrorResult]:
+    """Get the requested method from the methods dict.
+
+    Returns: Either the function to be called, or a Method Not Found result.
+    """
+    try:
+        return Success(methods[method_name])
+    except KeyError:
+        return Failure(MethodNotFoundResult(method_name))
+
+
 async def dispatch_request(
     methods: Methods, context: Any, request: Request
-) -> Tuple[Request, Result]:
+) -> Tuple[Request, Result[SuccessResult, ErrorResult]]:
     method = get_method(methods, request.method).bind(
         partial(validate_args, request, context)
     )
     return (
         request,
         method
-        if isinstance(method, Left)
-        else await call(
-            request, context, method._value  # pylint: disable=protected-access
-        ),
+        if isinstance(method, Failure)
+        else await call(request, context, method.unwrap()),
     )
 
 
 async def dispatch_deserialized(
     methods: Methods,
     context: Any,
-    post_process: Callable[[Response], Iterable[Any]],
+    post_process: Callable[[Response], Response],
     deserialized: Deserialized,
 ) -> Union[Response, Iterable[Response], None]:
     results = await asyncio.gather(
@@ -91,7 +122,7 @@ async def dispatch_to_response_pure(
     validator: Callable[[Deserialized], Deserialized],
     methods: Methods,
     context: Any,
-    post_process: Callable[[Response], Iterable[Any]],
+    post_process: Callable[[Response], Response],
     request: str,
 ) -> Union[Response, Iterable[Response], None]:
     try:
@@ -100,14 +131,14 @@ async def dispatch_to_response_pure(
         )
         return (
             post_process(result)
-            if isinstance(result, Left)
+            if isinstance(result, Failure)
             else await dispatch_deserialized(
                 methods,
                 context,
                 post_process,
-                result._value,  # pylint: disable=protected-access
+                result.unwrap(),
             )
         )
     except Exception as exc:  # pylint: disable=broad-except
         logger.exception(exc)
-        return post_process(Left(ServerErrorResponse(str(exc), None)))
+        return post_process(Failure(ServerErrorResponse(str(exc), None)))
diff --git a/jsonrpcserver/async_main.py b/jsonrpcserver/async_main.py
index 35656eb..d34801c 100644
--- a/jsonrpcserver/async_main.py
+++ b/jsonrpcserver/async_main.py
@@ -1,16 +1,16 @@
 """Async version of main.py. The public async functions."""
+
 import json
 from typing import Any, Callable, Dict, Iterable, List, Optional, Union, cast
 
 from .async_dispatcher import dispatch_to_response_pure
+from .async_methods import Methods, global_methods
 from .dispatcher import Deserialized
-from .main import default_validator, default_deserializer
-from .methods import Methods, global_methods
+from .main import default_deserializer, default_jsonrpc_validator
 from .response import Response, to_serializable
 from .sentinels import NOCONTEXT
 from .utils import identity
 
-
 # pylint: disable=missing-function-docstring,duplicate-code
 
 
@@ -20,7 +20,7 @@ async def dispatch_to_response(
     *,
     context: Any = NOCONTEXT,
     deserializer: Callable[[str], Deserialized] = default_deserializer,
-    validator: Callable[[Deserialized], Deserialized] = default_validator,
+    validator: Callable[[Deserialized], Deserialized] = default_jsonrpc_validator,
     post_process: Callable[[Response], Any] = identity,
 ) -> Union[Response, Iterable[Response], None]:
     return await dispatch_to_response_pure(
diff --git a/jsonrpcserver/async_methods.py b/jsonrpcserver/async_methods.py
new file mode 100644
index 0000000..63edfc1
--- /dev/null
+++ b/jsonrpcserver/async_methods.py
@@ -0,0 +1,33 @@
+"""Async methods"""
+
+from typing import Any, Awaitable, Callable, Dict, Optional, cast
+
+from returns.result import Result
+
+from .result import ErrorResult, SuccessResult
+
+Method = Callable[..., Awaitable[Result[SuccessResult, ErrorResult]]]
+Methods = Dict[str, Method]
+global_methods: Methods = {}
+
+
+def method(
+    func: Optional[Method] = None, name: Optional[str] = None
+) -> Callable[..., Awaitable[Any]]:
+    """A decorator to add a function into jsonrpcserver's internal global_methods dict.
+    The global_methods dict will be used by default unless a methods argument is passed
+    to `dispatch`.
+
+    Functions can be renamed by passing a name argument:
+
+        @method(name=bar)
+        def foo():
+            ...
+    """
+
+    def decorator(func_: Method) -> Method:
+        nonlocal name
+        global_methods[name or func_.__name__] = func_
+        return func_
+
+    return decorator(func) if callable(func) else cast(Method, decorator)
diff --git a/jsonrpcserver/dispatcher.py b/jsonrpcserver/dispatcher.py
index c6b6229..2893012 100644
--- a/jsonrpcserver/dispatcher.py
+++ b/jsonrpcserver/dispatcher.py
@@ -1,14 +1,15 @@
 """Dispatcher - does the hard work of this library: parses, validates and dispatches
 requests, providing responses.
 """
+
 # pylint: disable=protected-access
+import logging
 from functools import partial
 from inspect import signature
 from itertools import starmap
 from typing import Any, Callable, Dict, Iterable, List, Tuple, Union
-import logging
 
-from oslash.either import Either, Left, Right  # type: ignore
+from returns.result import Failure, Result, Success
 
 from .exceptions import JsonRpcError
 from .methods import Method, Methods
@@ -26,12 +27,12 @@
     InternalErrorResult,
     InvalidParamsResult,
     MethodNotFoundResult,
-    Result,
     SuccessResult,
 )
 from .sentinels import NOCONTEXT, NOID
 from .utils import compose, make_list
 
+ArgsValidator = Callable[[Any, Request, Method], Result[Method, ErrorResult]]
 Deserialized = Union[Dict[str, Any], List[Dict[str, Any]]]
 
 logger = logging.getLogger(__name__)
@@ -66,7 +67,9 @@ def extract_list(
     return response_list[0]
 
 
-def to_response(request: Request, result: Result) -> Response:
+def to_response(
+    request: Request, result: Result[SuccessResult, ErrorResult]
+) -> Response:
     """Maps a Request plus a Result to a Response. A Response is just a Result plus the
     id from the original Request.
 
@@ -79,9 +82,9 @@ def to_response(request: Request, result: Result) -> 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))
+        Failure(ErrorResponse(**result.failure()._asdict(), id=request.id))
+        if isinstance(result, Failure)
+        else Success(SuccessResponse(**result.unwrap()._asdict(), id=request.id))
     )
 
 
@@ -104,19 +107,25 @@ def extract_kwargs(request: Request) -> Dict[str, Any]:
     return request.params if isinstance(request.params, dict) else {}
 
 
-def validate_result(result: Result) -> None:
+def validate_result(result: Result[SuccessResult, ErrorResult]) -> None:
     """Validate the return value from a method.
 
     Raises an AssertionError if the result returned from a method is invalid.
 
     Returns: None
     """
-    assert (isinstance(result, Left) and isinstance(result._error, ErrorResult)) or (
-        isinstance(result, Right) and isinstance(result._value, SuccessResult)
+    assert (
+        isinstance(result, Failure) and isinstance(result.failure(), ErrorResult)
+    ) or (
+        isinstance(result, Success) and isinstance(result.unwrap(), SuccessResult)
     ), f"The method did not return a valid Result (returned {result!r})"
 
 
-def call(request: Request, context: Any, method: Method) -> Result:
+def call(
+    request: Request,
+    context: Any,
+    method: Method,
+) -> Result[SuccessResult, ErrorResult]:
     """Call the method.
 
     Handles any exceptions raised in the method, being sure to return an Error response.
@@ -132,17 +141,19 @@ def call(request: Request, context: Any, method: Method) -> 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))
+        return Failure(ErrorResult(code=exc.code, message=exc.message, data=exc.data))
     # Any other uncaught exception inside method - internal error.
     except Exception as exc:  # pylint: disable=broad-except
         logger.exception(exc)
-        return Left(InternalErrorResult(str(exc)))
+        return Failure(InternalErrorResult(str(exc)))
     return result
 
 
 def validate_args(
-    request: Request, context: Any, func: Method
-) -> Either[ErrorResult, Method]:
+    request: Request,
+    context: Any,
+    func: Method,
+) -> Result[Method, ErrorResult]:
     """Ensure the method can be called with the arguments given.
 
     Returns: Either the function to be called, or an Invalid Params error result.
@@ -150,34 +161,37 @@ def validate_args(
     try:
         signature(func).bind(*extract_args(request, context), **extract_kwargs(request))
     except TypeError as exc:
-        return Left(InvalidParamsResult(str(exc)))
-    return Right(func)
+        return Failure(InvalidParamsResult(str(exc)))
+    return Success(func)
 
 
-def get_method(methods: Methods, method_name: str) -> Either[ErrorResult, Method]:
+def get_method(methods: Methods, method_name: str) -> Result[Method, ErrorResult]:
     """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])
+        return Success(methods[method_name])
     except KeyError:
-        return Left(MethodNotFoundResult(method_name))
+        return Failure(MethodNotFoundResult(method_name))
 
 
 def dispatch_request(
-    methods: Methods, context: Any, request: Request
-) -> Tuple[Request, Result]:
+    args_validator: ArgsValidator,
+    methods: Methods,
+    context: Any,
+    request: Request,
+) -> Tuple[Request, Result[SuccessResult, ErrorResult]]:
     """Get the method, validates the arguments and calls the method.
 
-    Returns: A tuple containing the Result of the method, along with the original
-        Request. We need the ids from the original request to remove notifications
-        before responding, and  create a Response.
+    Returns: A tuple containing the original Request, and the Result of the method call.
+        We need the ids from the original request to remove notifications before
+        responding, and  create a Response.
     """
     return (
         request,
         get_method(methods, request.method)
-        .bind(partial(validate_args, request, context))
+        .bind(partial(args_validator, request, context))
         .bind(partial(call, request, context)),
     )
 
@@ -198,20 +212,23 @@ def not_notification(request_result: Any) -> bool:
 
 
 def dispatch_deserialized(
+    args_validator: ArgsValidator,
+    post_process: Callable[[Response], Response],
     methods: Methods,
     context: Any,
-    post_process: Callable[[Response], Iterable[Any]],
     deserialized: Deserialized,
 ) -> Union[Response, List[Response], None]:
     """This is simply continuing the pipeline from dispatch_to_response_pure. It exists
     only to be an abstraction, otherwise that function is doing too much. It continues
     on from the request string having been parsed and validated.
 
-    Returns: A Response, a list of Responses, or None. If post_process is passed, it's
+    Returns: A Result, a list of Results, or None. If post_process is passed, it's
         applied to the Response(s).
     """
     results = map(
-        compose(partial(dispatch_request, methods, context), create_request),
+        compose(
+            partial(dispatch_request, args_validator, methods, context), create_request
+        ),
         make_list(deserialized),
     )
     responses = starmap(to_response, filter(not_notification, results))
@@ -219,8 +236,8 @@ def dispatch_deserialized(
 
 
 def validate_request(
-    validator: Callable[[Deserialized], Deserialized], request: Deserialized
-) -> Either[ErrorResponse, Deserialized]:
+    jsonrpc_validator: Callable[[Deserialized], Deserialized], request: Deserialized
+) -> Result[Deserialized, ErrorResponse]:
     """Validate the request against a JSON-RPC schema.
 
     Ensures the parsed request is valid JSON-RPC.
@@ -228,57 +245,59 @@ def validate_request(
     Returns: Either the same request passed in or an Invalid request response.
     """
     try:
-        validator(request)
+        jsonrpc_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
+    # unknown. Any exception raised we assume the request is invalid and return an
     # "invalid request" response.
     except Exception:  # pylint: disable=broad-except
-        return Left(InvalidRequestResponse("The request failed schema validation"))
-    return Right(request)
+        return Failure(InvalidRequestResponse("The request failed schema validation"))
+    return Success(request)
 
 
 def deserialize_request(
     deserializer: Callable[[str], Deserialized], request: str
-) -> Either[ErrorResponse, Deserialized]:
+) -> Result[Deserialized, ErrorResponse]:
     """Parse the JSON request string.
 
     Returns: Either the deserialized request or a "Parse Error" response.
     """
     try:
-        return Right(deserializer(request))
+        return Success(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:  # pylint: disable=broad-except
-        return Left(ParseErrorResponse(str(exc)))
+        return Failure(ParseErrorResponse(str(exc)))
 
 
 def dispatch_to_response_pure(
-    *,
+    args_validator: ArgsValidator,
     deserializer: Callable[[str], Deserialized],
-    validator: Callable[[Deserialized], Deserialized],
+    jsonrpc_validator: Callable[[Deserialized], Deserialized],
+    post_process: Callable[[Response], Response],
     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.
+        notifications or batches of notifications, to indicate that we should
+        not respond.
     """
     try:
         result = deserialize_request(deserializer, request).bind(
-            partial(validate_request, validator)
+            partial(validate_request, jsonrpc_validator)
         )
         return (
             post_process(result)
-            if isinstance(result, Left)
-            else dispatch_deserialized(methods, context, post_process, result._value)
+            if isinstance(result, Failure)
+            else dispatch_deserialized(
+                args_validator, post_process, methods, context, result.unwrap()
+            )
         )
     except Exception as exc:  # pylint: disable=broad-except
         # There was an error with the jsonrpcserver library.
-        logger.exception(exc)
-        return post_process(Left(ServerErrorResponse(str(exc), None)))
+        logging.exception(exc)
+        return post_process(Failure(ServerErrorResponse(str(exc), None)))
diff --git a/jsonrpcserver/exceptions.py b/jsonrpcserver/exceptions.py
index b2e2afb..0314e44 100644
--- a/jsonrpcserver/exceptions.py
+++ b/jsonrpcserver/exceptions.py
@@ -1,5 +1,7 @@
 """Exceptions"""
+
 from typing import Any
+
 from .sentinels import NODATA
 
 
diff --git a/jsonrpcserver/main.py b/jsonrpcserver/main.py
index bbf824b..19070b2 100644
--- a/jsonrpcserver/main.py
+++ b/jsonrpcserver/main.py
@@ -9,36 +9,44 @@
 - dispatch_to_json/dispatch: Returns a JSON-RPC response string (or an empty string for
   notifications).
 """
-from importlib.resources import read_text
-from typing import Any, Callable, Dict, List, Optional, Union, cast
+
 import json
+from importlib.resources import read_text
+from typing import Any, Callable, Dict, List, Union, cast
 
 from jsonschema.validators import validator_for  # type: ignore
 
-from .dispatcher import dispatch_to_response_pure, Deserialized
+from .dispatcher import (
+    ArgsValidator,
+    Deserialized,
+    dispatch_to_response_pure,
+    validate_args,
+)
 from .methods import Methods, global_methods
 from .response import Response, to_dict
 from .sentinels import NOCONTEXT
 from .utils import identity
 
-
+default_args_validator = validate_args
 default_deserializer = json.loads
 
-# Prepare the jsonschema validator. This is global so it loads only once, not every
-# time dispatch is called.
+# Prepare the jsonschema validator. This is global so it loads only once, not every time
+# dispatch is called.
 schema = json.loads(read_text(__package__, "request-schema.json"))
 klass = validator_for(schema)
 klass.check_schema(schema)
-default_validator = klass(schema).validate
+default_jsonrpc_validator = klass(schema).validate
 
 
 def dispatch_to_response(
     request: str,
-    methods: Optional[Methods] = None,
-    *,
+    methods: Methods = global_methods,
     context: Any = NOCONTEXT,
+    args_validator: ArgsValidator = default_args_validator,
     deserializer: Callable[[str], Deserialized] = json.loads,
-    validator: Callable[[Deserialized], Deserialized] = default_validator,
+    jsonrpc_validator: Callable[
+        [Deserialized], Deserialized
+    ] = default_jsonrpc_validator,
     post_process: Callable[[Response], Any] = identity,
 ) -> Union[Response, List[Response], None]:
     """Takes a JSON-RPC request string and dispatches it to method(s), giving Response
@@ -54,9 +62,11 @@ def dispatch_to_response(
             populated with the @method decorator.
         context: If given, will be passed as the first argument to methods.
         deserializer: Function that deserializes the request string.
-        validator: Function that validates the JSON-RPC request. The function should
-            raise an exception if the request is invalid. To disable validation, pass
-            lambda _: None.
+        args_validator: Function that validates that the parameters in the request match
+            the Python function being called.
+        jsonrpc_validator: Function that validates the JSON-RPC request. The function
+            should raise an exception if the request is invalid. To disable validation,
+            pass lambda _: None.
         post_process: Function that will be applied to Responses.
 
     Returns:
@@ -67,12 +77,13 @@ def dispatch_to_response(
        '{"jsonrpc": "2.0", "result": "pong", "id": 1}'
     """
     return dispatch_to_response_pure(
-        deserializer=deserializer,
-        validator=validator,
-        post_process=post_process,
-        context=context,
-        methods=global_methods if methods is None else methods,
-        request=request,
+        args_validator,
+        deserializer,
+        jsonrpc_validator,
+        post_process,
+        methods,
+        context,
+        request,
     )
 
 
@@ -82,9 +93,10 @@ def dispatch_to_serializable(
     """Takes a JSON-RPC request string and dispatches it to method(s), giving responses
     as dicts (or None).
     """
+    kwargs.setdefault("post_process", to_dict)
     return cast(
         Union[Dict[str, Any], List[Dict[str, Any]], None],
-        dispatch_to_response(*args, post_process=to_dict, **kwargs),
+        dispatch_to_response(*args, **kwargs),
     )
 
 
@@ -106,7 +118,7 @@ def dispatch_to_json(
         The rest: Passed through to dispatch_to_serializable.
     """
     response = dispatch_to_serializable(*args, **kwargs)
-    # Better to respond with the empty string instead of json "null", because "null" is
+    # Better to respond with an empty string instead of json "null", because "null" is
     # an invalid JSON-RPC response.
     return "" if response is None else serializer(response)
 
diff --git a/jsonrpcserver/methods.py b/jsonrpcserver/methods.py
index 38d999a..c946a58 100644
--- a/jsonrpcserver/methods.py
+++ b/jsonrpcserver/methods.py
@@ -11,14 +11,17 @@
 Methods can take either positional or named arguments, but not both. This is a
 limitation of JSON-RPC.
 """
+
 from typing import Any, Callable, Dict, Optional, cast
 
-from .result import Result
+from returns.result import Result
+
+from .result import ErrorResult, SuccessResult
 
-Method = Callable[..., Result]
+Method = Callable[..., Result[SuccessResult, ErrorResult]]
 Methods = Dict[str, Method]
 
-global_methods = {}
+global_methods: Methods = {}
 
 
 def method(
diff --git a/jsonrpcserver/request.py b/jsonrpcserver/request.py
index 37f88c1..d82043e 100644
--- a/jsonrpcserver/request.py
+++ b/jsonrpcserver/request.py
@@ -3,6 +3,7 @@
 After parsing a request string, we put the (dict) requests into these Request
 namedtuples, simply because they're nicer to work with.
 """
+
 from typing import Any, Dict, List, NamedTuple, Union
 
 
diff --git a/jsonrpcserver/response.py b/jsonrpcserver/response.py
index 80f388e..3113d05 100644
--- a/jsonrpcserver/response.py
+++ b/jsonrpcserver/response.py
@@ -2,9 +2,10 @@
 
 https://www.jsonrpc.org/specification#response_object
 """
-from typing import Any, Dict, List, Type, NamedTuple, Union
 
-from oslash.either import Either, Left  # type: ignore
+from typing import Any, Dict, List, NamedTuple, Union
+
+from returns.result import Failure, Result
 
 from .codes import (
     ERROR_INVALID_REQUEST,
@@ -37,8 +38,7 @@ class ErrorResponse(NamedTuple):
     id: Any
 
 
-Response = Either[ErrorResponse, SuccessResponse]
-ResponseType = Type[Either[ErrorResponse, SuccessResponse]]
+Response = Result[SuccessResponse, ErrorResponse]
 
 
 def ParseErrorResponse(data: Any) -> ErrorResponse:  # pylint: disable=invalid-name
@@ -92,18 +92,17 @@ def to_success_dict(response: SuccessResponse) -> Dict[str, Any]:
     return {"jsonrpc": "2.0", "result": response.result, "id": response.id}
 
 
-def to_dict(response: ResponseType) -> Dict[str, Any]:
+def to_dict(response: Response) -> Dict[str, Any]:
     """Serialize either an error or success response object to dict"""
-    # pylint: disable=protected-access
     return (
-        to_error_dict(response._error)
-        if isinstance(response, Left)
-        else to_success_dict(response._value)
+        to_error_dict(response.failure())
+        if isinstance(response, Failure)
+        else to_success_dict(response.unwrap())
     )
 
 
 def to_serializable(
-    response: Union[ResponseType, List[ResponseType], None]
+    response: Union[Response, List[Response], None],
 ) -> Union[Deserialized, None]:
     """Serialize a response object (or list of them), to a dict, or list of them."""
     if response is None:
diff --git a/jsonrpcserver/result.py b/jsonrpcserver/result.py
index 9dd78f9..b193880 100644
--- a/jsonrpcserver/result.py
+++ b/jsonrpcserver/result.py
@@ -6,11 +6,13 @@
 
 The public functions are Success, Error and InvalidParams.
 """
+
 from typing import Any, NamedTuple
 
-from oslash.either import Either, Left, Right  # type: ignore
+from returns.result import Failure, Success
+from returns.result import Result as R
 
-from .codes import ERROR_INVALID_PARAMS, ERROR_METHOD_NOT_FOUND, ERROR_INTERNAL_ERROR
+from .codes import ERROR_INTERNAL_ERROR, ERROR_INVALID_PARAMS, ERROR_METHOD_NOT_FOUND
 from .sentinels import NODATA
 
 # pylint: disable=missing-class-docstring,missing-function-docstring,invalid-name
@@ -29,11 +31,13 @@ class ErrorResult(NamedTuple):
     data: Any = NODATA  # The spec says this value may be omitted
 
     def __repr__(self) -> str:
-        return f"ErrorResult(code={self.code!r}, message={self.message!r}, data={self.data!r})"
+        return (
+            f"ErrorResult(code={self.code!r}, message={self.message!r}, "
+            f"data={self.data!r})"
+        )
 
 
-# Union of the two valid result types
-Result = Either[ErrorResult, SuccessResult]
+Result = R[SuccessResult, ErrorResult]
 
 
 # Helpers
@@ -54,16 +58,16 @@ def InvalidParamsResult(data: Any = NODATA) -> ErrorResult:
 # Helpers (the public functions)
 
 
-def Success(*args: Any, **kwargs: Any) -> Either[ErrorResult, SuccessResult]:
-    return Right(SuccessResult(*args, **kwargs))
+def Ok(*args: Any, **kwargs: Any) -> Success[SuccessResult]:
+    return Success(SuccessResult(*args, **kwargs))
 
 
-def Error(*args: Any, **kwargs: Any) -> Either[ErrorResult, SuccessResult]:
-    return Left(ErrorResult(*args, **kwargs))
+def Error(*args: Any, **kwargs: Any) -> Failure[ErrorResult]:
+    return Failure(ErrorResult(*args, **kwargs))
 
 
-def InvalidParams(*args: Any, **kwargs: Any) -> Either[ErrorResult, SuccessResult]:
+def InvalidParams(*args: Any, **kwargs: Any) -> Failure[ErrorResult]:
     """InvalidParams is a shortcut to save you from having to pass the Invalid Params
     JSON-RPC code to Error.
     """
-    return Left(InvalidParamsResult(*args, **kwargs))
+    return Failure(InvalidParamsResult(*args, **kwargs))
diff --git a/jsonrpcserver/server.py b/jsonrpcserver/server.py
index 643b6a7..e783082 100644
--- a/jsonrpcserver/server.py
+++ b/jsonrpcserver/server.py
@@ -1,6 +1,7 @@
 """A simple development server for serving JSON-RPC requests using Python's builtin
 http.server module.
 """
+
 import logging
 from http.server import BaseHTTPRequestHandler, HTTPServer
 
@@ -25,4 +26,11 @@ def do_POST(self) -> None:  # pylint: disable=invalid-name
 def serve(name: str = "", port: int = 5000) -> None:
     """A simple function to serve HTTP requests"""
     logging.info(" * Listening on port %s", port)
-    HTTPServer((name, port), RequestHandler).serve_forever()
+    try:
+        httpd = HTTPServer((name, port), RequestHandler)
+        httpd.serve_forever()
+    except KeyboardInterrupt:
+        pass
+    except Exception:
+        httpd.shutdown()
+        raise
diff --git a/jsonrpcserver/utils.py b/jsonrpcserver/utils.py
index 4ee5a2c..a259799 100644
--- a/jsonrpcserver/utils.py
+++ b/jsonrpcserver/utils.py
@@ -1,4 +1,5 @@
 """Utility functions"""
+
 from functools import reduce
 from typing import Any, Callable, List
 
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 0000000..9bb71fa
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,3 @@
+[mypy]
+plugins =
+  returns.contrib.mypy.returns_plugin
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..5e4171f
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,51 @@
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[project]
+authors = [
+    {name = "Beau Barker", email = "beau@explodinglabs.com"}
+]
+classifiers = [
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11"
+]
+description = "Process JSON-RPC requests"
+license = {file = "LICENSE"}
+name = "jsonrpcserver"
+readme = {file = "README.md", content-type = "text/markdown"}
+requires-python = ">=3.8"
+version = "6.0.0"
+
+[project.urls]
+homepage = "https://www.jsonrpcserver.com"
+repository = "https://github.com/explodinglabs/jsonrpcserver"
+
+[project.optional-dependencies]
+qa = [
+    "pytest",
+    "pytest-asyncio",
+    "pytest-cov",
+    "tox",
+]
+examples = [
+    "aiohttp",
+    "aiozmq",
+    "flask",
+    "flask-socketio",
+    "gmqtt",
+    "pyzmq",
+    "sanic",
+    "tornado",
+    "websockets",
+    "werkzeug",
+]
+
+[tool.setuptools]
+include-package-data = true
+packages = [
+    "jsonrpcserver"
+]
+zip-safe = false
diff --git a/setup.py b/setup.py
deleted file mode 100644
index ebce509..0000000
--- a/setup.py
+++ /dev/null
@@ -1,46 +0,0 @@
-"""setup.py"""
-from setuptools import setup
-
-with open("README.md", encoding="utf-8") as f:
-    README = f.read()
-
-setup(
-    author="Beau Barker",
-    author_email="beau@explodinglabs.com",
-    classifiers=[
-        "Programming Language :: Python :: 3.8",
-        "Programming Language :: Python :: 3.9",
-        "Programming Language :: Python :: 3.10",
-    ],
-    description="Process JSON-RPC requests",
-    extras_require={
-        "examples": [
-            "aiohttp",
-            "aiozmq",
-            "flask",
-            "flask-socketio",
-            "gmqtt",
-            "pyzmq",
-            "tornado",
-            "websockets",
-            "werkzeug",
-        ],
-        "test": [
-            "pytest",
-            "pytest-cov",
-            "tox",
-        ],
-    },
-    include_package_data=True,
-    install_requires=["jsonschema<5", "oslash<1"],
-    license="MIT",
-    long_description=README,
-    long_description_content_type="text/markdown",
-    name="jsonrpcserver",
-    packages=["jsonrpcserver"],
-    url="https://github.com/explodinglabs/jsonrpcserver",
-    version="5.0.9",
-    # Be PEP 561 compliant
-    # https://mypy.readthedocs.io/en/stable/installed_packages.html#making-pep-561-compatible-packages
-    zip_safe=False,
-)
diff --git a/tests/test_async_dispatcher.py b/tests/test_async_dispatcher.py
index 050e96d..b1afbfd 100644
--- a/tests/test_async_dispatcher.py
+++ b/tests/test_async_dispatcher.py
@@ -1,8 +1,9 @@
 """Test async_dispatcher.py"""
+
 from unittest.mock import Mock, patch
-import pytest
 
-from oslash.either import Left, Right  # type: ignore
+import pytest
+from returns.result import Failure, Success
 
 from jsonrpcserver.async_dispatcher import (
     call,
@@ -10,12 +11,12 @@
     dispatch_request,
     dispatch_to_response_pure,
 )
-from jsonrpcserver.main import default_deserializer, default_validator
 from jsonrpcserver.codes import ERROR_INTERNAL_ERROR, ERROR_SERVER_ERROR
 from jsonrpcserver.exceptions import JsonRpcError
+from jsonrpcserver.main import default_deserializer, default_jsonrpc_validator
 from jsonrpcserver.request import Request
 from jsonrpcserver.response import ErrorResponse, SuccessResponse
-from jsonrpcserver.result import ErrorResult, Result, Success, SuccessResult
+from jsonrpcserver.result import ErrorResult, Ok, Result, SuccessResult
 from jsonrpcserver.sentinels import NOCONTEXT, NODATA
 from jsonrpcserver.utils import identity
 
@@ -23,32 +24,32 @@
 
 
 async def ping() -> Result:
-    return Success("pong")
+    return Ok("pong")
 
 
 @pytest.mark.asyncio
 async def test_call() -> None:
-    assert await call(Request("ping", [], 1), NOCONTEXT, ping) == Right(
+    assert await call(Request("ping", [], 1), NOCONTEXT, ping) == Success(
         SuccessResult("pong")
     )
 
 
 @pytest.mark.asyncio
 async def test_call_raising_jsonrpcerror() -> None:
-    def method() -> None:
+    async def method_() -> Result:
         raise JsonRpcError(code=1, message="foo", data=NODATA)
 
-    assert await call(Request("ping", [], 1), NOCONTEXT, method) == Left(
+    assert await call(Request("ping", [], 1), NOCONTEXT, method_) == Failure(
         ErrorResult(1, "foo")
     )
 
 
 @pytest.mark.asyncio
 async def test_call_raising_exception() -> None:
-    def method() -> None:
+    async def method_() -> Result:
         raise ValueError("foo")
 
-    assert await call(Request("ping", [], 1), NOCONTEXT, method) == Left(
+    assert await call(Request("ping", [], 1), NOCONTEXT, method_) == Failure(
         ErrorResult(ERROR_INTERNAL_ERROR, "Internal error", "foo")
     )
 
@@ -58,7 +59,7 @@ async def test_dispatch_request() -> None:
     request = Request("ping", [], 1)
     assert await dispatch_request({"ping": ping}, NOCONTEXT, request) == (
         request,
-        Right(SuccessResult("pong")),
+        Success(SuccessResult("pong")),
     )
 
 
@@ -69,32 +70,32 @@ async def test_dispatch_deserialized() -> None:
         NOCONTEXT,
         identity,
         {"jsonrpc": "2.0", "method": "ping", "id": 1},
-    ) == Right(SuccessResponse("pong", 1))
+    ) == Success(SuccessResponse("pong", 1))
 
 
 @pytest.mark.asyncio
 async def test_dispatch_to_response_pure_success() -> None:
     assert await dispatch_to_response_pure(
         deserializer=default_deserializer,
-        validator=default_validator,
+        validator=default_jsonrpc_validator,
         post_process=identity,
         context=NOCONTEXT,
         methods={"ping": ping},
         request='{"jsonrpc": "2.0", "method": "ping", "id": 1}',
-    ) == Right(SuccessResponse("pong", 1))
+    ) == Success(SuccessResponse("pong", 1))
 
 
 @patch("jsonrpcserver.async_dispatcher.dispatch_request", side_effect=ValueError("foo"))
 @pytest.mark.asyncio
-async def test_dispatch_to_response_pure_server_error(*_: Mock) -> None:
-    async def hello() -> Result:
-        return Success()
+async def test_dispatch_to_response_pure_server_error(_: Mock) -> None:
+    async def ping() -> Result:
+        return Ok()
 
     assert await dispatch_to_response_pure(
         deserializer=default_deserializer,
-        validator=default_validator,
+        validator=default_jsonrpc_validator,
         post_process=identity,
         context=NOCONTEXT,
-        methods={"hello": hello},
-        request='{"jsonrpc": "2.0", "method": "hello", "id": 1}',
-    ) == Left(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "hello", None))
+        methods={"ping": ping},
+        request='{"jsonrpc": "2.0", "method": "ping", "id": 1}',
+    ) == Failure(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "foo", None))
diff --git a/tests/test_async_main.py b/tests/test_async_main.py
index d820182..7686a8e 100644
--- a/tests/test_async_main.py
+++ b/tests/test_async_main.py
@@ -1,28 +1,30 @@
 """Test async_main.py"""
-import pytest
 
-from oslash.either import Right  # type: ignore
+import pytest
+from returns.result import Success
 
 from jsonrpcserver.async_main import (
+    dispatch_to_json,
     dispatch_to_response,
     dispatch_to_serializable,
-    dispatch_to_json,
 )
 from jsonrpcserver.response import SuccessResponse
-from jsonrpcserver.result import Result, Success
+from jsonrpcserver.result import Ok, Result
+
+# pylint: disable=missing-function-docstring
 
 # pylint: disable=missing-function-docstring
 
 
 async def ping() -> Result:
-    return Success("pong")
+    return Ok("pong")
 
 
 @pytest.mark.asyncio
 async def test_dispatch_to_response() -> None:
     assert await dispatch_to_response(
         '{"jsonrpc": "2.0", "method": "ping", "id": 1}', {"ping": ping}
-    ) == Right(SuccessResponse("pong", 1))
+    ) == Success(SuccessResponse("pong", 1))
 
 
 @pytest.mark.asyncio
diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py
index 727bf70..26fe49a 100644
--- a/tests/test_dispatcher.py
+++ b/tests/test_dispatcher.py
@@ -2,12 +2,13 @@
 
 TODO: Add tests for dispatch_requests (non-pure version)
 """
-from typing import Any, Callable, Dict
-from unittest.mock import Mock, patch, sentinel
+
 import json
-import pytest
+from typing import Any, Dict
+from unittest.mock import Mock, patch, sentinel
 
-from oslash.either import Left, Right  # type: ignore
+import pytest
+from returns.result import Failure, Success
 
 from jsonrpcserver.codes import (
     ERROR_INTERNAL_ERROR,
@@ -23,9 +24,9 @@
     dispatch_deserialized,
     dispatch_request,
     dispatch_to_response_pure,
-    extract_list,
     extract_args,
     extract_kwargs,
+    extract_list,
     get_method,
     not_notification,
     to_response,
@@ -33,46 +34,41 @@
     validate_request,
 )
 from jsonrpcserver.exceptions import JsonRpcError
-from jsonrpcserver.main import (
-    default_deserializer,
-    default_validator,
-    dispatch_to_response,
-    dispatch,
-)
-from jsonrpcserver.methods import method
+from jsonrpcserver.main import default_jsonrpc_validator
+from jsonrpcserver.methods import Method
 from jsonrpcserver.request import Request
 from jsonrpcserver.response import ErrorResponse, SuccessResponse
 from jsonrpcserver.result import (
     ErrorResult,
     InvalidParams,
+    Ok,
     Result,
-    Success,
     SuccessResult,
 )
 from jsonrpcserver.sentinels import NOCONTEXT, NODATA, NOID
 from jsonrpcserver.utils import identity
 
-# pylint: disable=missing-function-docstring,missing-class-docstring,too-few-public-methods,unnecessary-lambda-assignment,invalid-name,disallowed-name
-
 
 def ping() -> Result:
-    return Success("pong")
+    return Ok("pong")
 
 
 # extract_list
 
 
 def test_extract_list() -> None:
-    assert extract_list(False, [SuccessResponse("foo", 1)]) == SuccessResponse("foo", 1)
+    assert extract_list(False, [Success(SuccessResponse("foo", 1))]) == Success(
+        SuccessResponse("foo", 1)
+    )
 
 
 def test_extract_list_notification() -> None:
-    assert extract_list(False, [None]) is None
+    assert extract_list(False, []) is None
 
 
 def test_extract_list_batch() -> None:
-    assert extract_list(True, [SuccessResponse("foo", 1)]) == [
-        SuccessResponse("foo", 1)
+    assert extract_list(True, [Success(SuccessResponse("foo", 1))]) == [
+        Success(SuccessResponse("foo", 1))
     ]
 
 
@@ -85,21 +81,21 @@ def test_extract_list_batch_all_notifications() -> None:
 
 def test_to_response_SuccessResult() -> None:
     assert to_response(
-        Request("ping", [], sentinel.id), Right(SuccessResult(sentinel.result))
-    ) == Right(SuccessResponse(sentinel.result, sentinel.id))
+        Request("ping", [], sentinel.id), Success(SuccessResult(sentinel.result))
+    ) == Success(SuccessResponse(sentinel.result, sentinel.id))
 
 
 def test_to_response_ErrorResult() -> None:
     assert (
         to_response(
             Request("ping", [], sentinel.id),
-            Left(
+            Failure(
                 ErrorResult(
                     code=sentinel.code, message=sentinel.message, data=sentinel.data
                 )
             ),
         )
-    ) == Left(
+    ) == Failure(
         ErrorResponse(sentinel.code, sentinel.message, sentinel.data, sentinel.id)
     )
 
@@ -107,18 +103,20 @@ def test_to_response_ErrorResult() -> None:
 def test_to_response_InvalidParams() -> None:
     assert to_response(
         Request("ping", [], sentinel.id), InvalidParams(sentinel.data)
-    ) == Left(ErrorResponse(-32602, "Invalid params", sentinel.data, sentinel.id))
+    ) == Failure(ErrorResponse(-32602, "Invalid params", sentinel.data, sentinel.id))
 
 
 def test_to_response_InvalidParams_no_data() -> None:
-    assert to_response(Request("ping", [], sentinel.id), InvalidParams()) == Left(
+    assert to_response(Request("ping", [], sentinel.id), InvalidParams()) == Failure(
         ErrorResponse(-32602, "Invalid params", NODATA, sentinel.id)
     )
 
 
 def test_to_response_notification() -> None:
     with pytest.raises(AssertionError):
-        to_response(Request("ping", [], NOID), SuccessResult(result=sentinel.result))
+        to_response(
+            Request("ping", [], NOID), Success(SuccessResult(result=sentinel.result))
+        )
 
 
 # extract_args
@@ -139,16 +137,21 @@ def test_extract_kwargs() -> None:
     assert extract_kwargs(Request("ping", {"foo": "bar"}, NOID)) == {"foo": "bar"}
 
 
-# validate_result
+# validate_args
+
+
+def test_validate_args_result_no_arguments() -> None:
+    def f() -> Result:
+        return Ok()
 
+    assert validate_args(Request("f", [], NOID), NOCONTEXT, f) == Success(f)
 
-def test_validate_result_no_arguments() -> None:
-    f = lambda: None
-    assert validate_args(Request("f", [], NOID), NOCONTEXT, f) == Right(f)
 
+def test_validate_args_result_no_arguments_too_many_positionals() -> None:
+    def f() -> Result:
+        return Ok()
 
-def test_validate_result_no_arguments_too_many_positionals() -> None:
-    assert validate_args(Request("f", ["foo"], NOID), NOCONTEXT, lambda: None) == Left(
+    assert validate_args(Request("f", ["foo"], NOID), NOCONTEXT, f) == Failure(
         ErrorResult(
             code=ERROR_INVALID_PARAMS,
             message="Invalid params",
@@ -157,56 +160,69 @@ def test_validate_result_no_arguments_too_many_positionals() -> None:
     )
 
 
-def test_validate_result_positionals() -> None:
-    f = lambda x: None
-    assert validate_args(Request("f", [1], NOID), NOCONTEXT, f) == Right(f)
+def test_validate_args_positionals() -> None:
+    def ping_(_: int) -> Result:
+        return Ok()
 
+    assert validate_args(Request("ping_", [1], NOID), NOCONTEXT, ping_) == Success(
+        ping_
+    )
+
+
+def test_validate_args_positionals_not_passed() -> None:
+    def ping_(name: str) -> Result:
+        return Ok()
 
-def test_validate_result_positionals_not_passed() -> None:
     assert validate_args(
-        Request("f", {"foo": "bar"}, NOID), NOCONTEXT, lambda x: None
-    ) == Left(
+        Request("ping_", {"foo": "bar"}, NOID), NOCONTEXT, ping_
+    ) == Failure(
         ErrorResult(
-            ERROR_INVALID_PARAMS, "Invalid params", "missing a required argument: 'x'"
+            ERROR_INVALID_PARAMS,
+            "Invalid params",
+            "missing a required argument: 'name'",
         )
     )
 
 
-def test_validate_result_keywords() -> None:
-    f = lambda **kwargs: None
-    assert validate_args(Request("f", {"foo": "bar"}, NOID), NOCONTEXT, f) == Right(f)
+def test_validate_args_keywords() -> None:
+    def f(**_: str) -> Result:
+        return Ok()
 
+    assert validate_args(Request("f", {"foo": "bar"}, NOID), NOCONTEXT, f) == Success(f)
 
-def test_validate_result_object_method() -> None:
+
+def test_validate_args_object_method() -> None:
     class FooClass:
-        def f(self, *_: str) -> str:
-            return ""
+        def f(self, *_: str) -> Result:
+            return Ok()
 
     g = FooClass().f
-    assert validate_args(Request("g", ["one", "two"], NOID), NOCONTEXT, g) == Right(g)
+    assert validate_args(Request("g", ["one", "two"], NOID), NOCONTEXT, g) == Success(g)
 
 
 # call
 
 
 def test_call() -> None:
-    assert call(Request("ping", [], 1), NOCONTEXT, ping) == Right(SuccessResult("pong"))
+    assert call(Request("ping", [], 1), NOCONTEXT, ping) == Success(
+        SuccessResult("pong")
+    )
 
 
 def test_call_raising_jsonrpcerror() -> None:
-    def method_() -> None:
+    def method_() -> Result:
         raise JsonRpcError(code=1, message="foo", data=NODATA)
 
-    assert call(Request("ping", [], 1), NOCONTEXT, method_) == Left(
+    assert call(Request("ping", [], 1), NOCONTEXT, method_) == Failure(
         ErrorResult(1, "foo")
     )
 
 
 def test_call_raising_exception() -> None:
-    def method_() -> None:
+    def method_() -> Result:
         raise ValueError("foo")
 
-    assert call(Request("ping", [], 1), NOCONTEXT, method_) == Left(
+    assert call(Request("ping", [], 1), NOCONTEXT, method_) == Failure(
         ErrorResult(ERROR_INTERNAL_ERROR, "Internal error", "foo")
     )
 
@@ -218,12 +234,12 @@ def method_() -> None:
     "argument,value",
     [
         (
-            validate_args(Request("ping", [], 1), NOCONTEXT, ping),
-            Right(ping),
+            Request("ping", [], 1),
+            Success(ping),
         ),
         (
-            validate_args(Request("ping", ["foo"], 1), NOCONTEXT, ping),
-            Left(
+            Request("ping", ["foo"], 1),
+            Failure(
                 ErrorResult(
                     ERROR_INVALID_PARAMS,
                     "Invalid params",
@@ -233,8 +249,8 @@ def method_() -> None:
         ),
     ],
 )
-def test_validate_args(argument: Result, value: Result) -> None:
-    assert argument == value
+def test_validate_args(argument: Request, value: Result) -> None:
+    assert validate_args(argument, NOCONTEXT, ping) == value
 
 
 # get_method
@@ -245,11 +261,11 @@ def test_validate_args(argument: Result, value: Result) -> None:
     [
         (
             get_method({"ping": ping}, "ping"),
-            Right(ping),
+            Success(ping),
         ),
         (
             get_method({"ping": ping}, "non-existant"),
-            Left(
+            Failure(
                 ErrorResult(ERROR_METHOD_NOT_FOUND, "Method not found", "non-existant")
             ),
         ),
@@ -264,18 +280,19 @@ def test_get_method(argument: Result, value: Result) -> None:
 
 def test_dispatch_request() -> None:
     request = Request("ping", [], 1)
-    assert dispatch_request({"ping": ping}, NOCONTEXT, request) == (
+    assert dispatch_request(validate_args, {"ping": ping}, NOCONTEXT, request) == (
         request,
-        Right(SuccessResult("pong")),
+        Success(SuccessResult("pong")),
     )
 
 
 def test_dispatch_request_with_context() -> None:
     def ping_with_context(context: Any) -> Result:
         assert context is sentinel.context
-        return Success()
+        return Ok()
 
     dispatch_request(
+        validate_args,
         {"ping_with_context": ping_with_context},
         sentinel.context,
         Request("ping_with_context", [], 1),
@@ -307,11 +324,12 @@ def test_not_notification_false() -> None:
 
 def test_dispatch_deserialized() -> None:
     assert dispatch_deserialized(
-        methods={"ping": ping},
-        context=NOCONTEXT,
-        post_process=identity,
-        deserialized={"jsonrpc": "2.0", "method": "ping", "id": 1},
-    ) == Right(SuccessResponse("pong", 1))
+        validate_args,
+        identity,
+        {"ping": ping},
+        NOCONTEXT,
+        {"jsonrpc": "2.0", "method": "ping", "id": 1},
+    ) == Success(SuccessResponse("pong", 1))
 
 
 # validate_request
@@ -319,11 +337,11 @@ def test_dispatch_deserialized() -> None:
 
 def test_validate_request() -> None:
     request = {"jsonrpc": "2.0", "method": "ping"}
-    assert validate_request(default_validator, request) == Right(request)
+    assert validate_request(default_jsonrpc_validator, request) == Success(request)
 
 
 def test_validate_request_invalid() -> None:
-    assert validate_request(default_validator, {"jsonrpc": "2.0"}) == Left(
+    assert validate_request(default_jsonrpc_validator, {"jsonrpc": "2.0"}) == Failure(
         ErrorResponse(
             ERROR_INVALID_REQUEST,
             "Invalid request",
@@ -338,29 +356,34 @@ def test_validate_request_invalid() -> None:
 
 def test_dispatch_to_response_pure() -> None:
     assert dispatch_to_response_pure(
-        deserializer=default_deserializer,
-        validator=default_validator,
-        post_process=identity,
-        context=NOCONTEXT,
-        methods={"ping": ping},
-        request='{"jsonrpc": "2.0", "method": "ping", "id": 1}',
-    ) == Right(SuccessResponse("pong", 1))
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"ping": ping},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "ping", "id": 1}',
+    ) == Success(SuccessResponse("pong", 1))
 
 
 def test_dispatch_to_response_pure_parse_error() -> None:
     """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(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"ping": ping},
+        NOCONTEXT,
+        "{",
+    ) == Failure(
         ErrorResponse(
             ERROR_PARSE_ERROR,
             "Parse error",
-            "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
+            (
+                "Expecting property name enclosed in double quotes: "
+                "line 1 column 2 (char 1)"
+            ),
             None,
         )
     )
@@ -371,13 +394,14 @@ def test_dispatch_to_response_pure_invalid_request() -> None:
     notification).
     """
     assert dispatch_to_response_pure(
-        deserializer=default_deserializer,
-        validator=default_validator,
-        post_process=identity,
-        context=NOCONTEXT,
-        methods={"ping": ping},
-        request="{}",
-    ) == Left(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"ping": ping},
+        NOCONTEXT,
+        "{}",
+    ) == Failure(
         ErrorResponse(
             ERROR_INVALID_REQUEST,
             "Invalid request",
@@ -389,29 +413,31 @@ def test_dispatch_to_response_pure_invalid_request() -> None:
 
 def test_dispatch_to_response_pure_method_not_found() -> None:
     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(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "non_existant", "id": 1}',
+    ) == Failure(
         ErrorResponse(ERROR_METHOD_NOT_FOUND, "Method not found", "non_existant", 1)
     )
 
 
 def test_dispatch_to_response_pure_invalid_params_auto() -> None:
     def f(colour: str, size: str) -> Result:  # pylint: disable=unused-argument
-        return Success()
+        return Ok()
 
     assert dispatch_to_response_pure(
-        deserializer=default_deserializer,
-        validator=default_validator,
-        post_process=identity,
-        context=NOCONTEXT,
-        methods={"f": f},
-        request='{"jsonrpc": "2.0", "method": "f", "params": {"colour":"blue"}, "id": 1}',
-    ) == Left(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"f": f},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "f", "params": {"colour":"blue"}, "id": 1}',
+    ) == Failure(
         ErrorResponse(
             ERROR_INVALID_PARAMS,
             "Invalid params",
@@ -425,16 +451,17 @@ def test_dispatch_to_response_pure_invalid_params_explicitly_returned() -> None:
     def foo(colour: str) -> Result:
         if colour not in ("orange", "red", "yellow"):
             return InvalidParams()
-        return Success()
+        return Ok()
 
     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))
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"foo": foo},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "foo", "params": ["blue"], "id": 1}',
+    ) == Failure(ErrorResponse(ERROR_INVALID_PARAMS, "Invalid params", NODATA, 1))
 
 
 def test_dispatch_to_response_pure_internal_error() -> None:
@@ -442,44 +469,47 @@ def foo() -> Result:
         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))
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"foo": foo},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "foo", "id": 1}',
+    ) == Failure(ErrorResponse(ERROR_INTERNAL_ERROR, "Internal error", "foo", 1))
 
 
 @patch("jsonrpcserver.dispatcher.dispatch_request", side_effect=ValueError("foo"))
 def test_dispatch_to_response_pure_server_error(*_: Mock) -> None:
     def foo() -> Result:
-        return Success()
+        return Ok()
 
     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_SERVER_ERROR, "Server error", "foo", None))
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"foo": foo},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "foo", "id": 1}',
+    ) == Failure(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "foo", None))
 
 
 def test_dispatch_to_response_pure_invalid_result() -> None:
     """Methods should return a Result, otherwise we get an Internal Error response."""
 
-    def not_a_result() -> None:
-        return None
+    def not_a_result() -> Result:
+        return None  # type: ignore
 
     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(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"not_a_result": not_a_result},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "not_a_result", "id": 1}',
+    ) == Failure(
         ErrorResponse(
             ERROR_INTERNAL_ERROR,
             "Internal error",
@@ -492,17 +522,18 @@ def not_a_result() -> None:
 def test_dispatch_to_response_pure_raising_exception() -> None:
     """Allow raising an exception to return an error."""
 
-    def raise_exception() -> None:
+    def raise_exception() -> Result:
         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))
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"raise_exception": raise_exception},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "raise_exception", "id": 1}',
+    ) == Failure(ErrorResponse(0, "foo", "bar", 1))
 
 
 # dispatch_to_response_pure -- Notifications
@@ -511,12 +542,13 @@ def raise_exception() -> None:
 def test_dispatch_to_response_pure_notification() -> None:
     assert (
         dispatch_to_response_pure(
-            deserializer=default_deserializer,
-            validator=default_validator,
-            post_process=identity,
-            context=NOCONTEXT,
-            methods={"ping": ping},
-            request='{"jsonrpc": "2.0", "method": "ping"}',
+            validate_args,
+            json.loads,
+            default_jsonrpc_validator,
+            identity,
+            {"ping": ping},
+            NOCONTEXT,
+            '{"jsonrpc": "2.0", "method": "ping"}',
         )
         is None
     )
@@ -525,32 +557,39 @@ def test_dispatch_to_response_pure_notification() -> None:
 def test_dispatch_to_response_pure_notification_parse_error() -> None:
     """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(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"ping": ping},
+        NOCONTEXT,
+        "{",
+    ) == Failure(
         ErrorResponse(
             ERROR_PARSE_ERROR,
             "Parse error",
-            "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
+            (
+                "Expecting property name enclosed in double quotes: "
+                "line 1 column 2 (char 1)"
+            ),
             None,
         )
     )
 
 
 def test_dispatch_to_response_pure_notification_invalid_request() -> None:
-    """Invalid JSON-RPC, must return an error. (impossible to determine if notification)"""
+    """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(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"ping": ping},
+        NOCONTEXT,
+        "{}",
+    ) == Failure(
         ErrorResponse(
             ERROR_INVALID_REQUEST,
             "Invalid request",
@@ -563,12 +602,13 @@ def test_dispatch_to_response_pure_notification_invalid_request() -> None:
 def test_dispatch_to_response_pure_notification_method_not_found() -> None:
     assert (
         dispatch_to_response_pure(
-            deserializer=default_deserializer,
-            validator=default_validator,
-            post_process=identity,
-            context=NOCONTEXT,
-            methods={},
-            request='{"jsonrpc": "2.0", "method": "non_existant"}',
+            validate_args,
+            json.loads,
+            default_jsonrpc_validator,
+            identity,
+            {},
+            NOCONTEXT,
+            '{"jsonrpc": "2.0", "method": "non_existant"}',
         )
         is None
     )
@@ -576,35 +616,37 @@ def test_dispatch_to_response_pure_notification_method_not_found() -> None:
 
 def test_dispatch_to_response_pure_notification_invalid_params_auto() -> None:
     def foo(colour: str, size: str) -> Result:  # pylint: disable=unused-argument
-        return Success()
+        return Ok()
 
     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"}}',
+            validate_args,
+            json.loads,
+            default_jsonrpc_validator,
+            identity,
+            {"foo": foo},
+            NOCONTEXT,
+            '{"jsonrpc": "2.0", "method": "foo", "params": {"colour":"blue"}}',
         )
         is None
     )
 
 
-def test_dispatch_to_response_pure_invalid_params_notification_explicitly_returned() -> None:
+def test_dispatch_to_response_pure_invalid_params_notification_returned() -> None:
     def foo(colour: str) -> Result:
         if colour not in ("orange", "red", "yellow"):
             return InvalidParams()
-        return Success()
+        return Ok()
 
     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"]}',
+            validate_args,
+            json.loads,
+            default_jsonrpc_validator,
+            identity,
+            {"foo": foo},
+            NOCONTEXT,
+            '{"jsonrpc": "2.0", "method": "foo", "params": ["blue"]}',
         )
         is None
     )
@@ -616,12 +658,13 @@ def foo(bar: str) -> Result:
 
     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"}',
+            validate_args,
+            json.loads,
+            default_jsonrpc_validator,
+            identity,
+            {"foo": foo},
+            NOCONTEXT,
+            '{"jsonrpc": "2.0", "method": "foo"}',
         )
         is None
     )
@@ -630,32 +673,34 @@ def foo(bar: str) -> Result:
 @patch("jsonrpcserver.dispatcher.dispatch_request", side_effect=ValueError("foo"))
 def test_dispatch_to_response_pure_notification_server_error(*_: Mock) -> None:
     def foo() -> Result:
-        return Success()
+        return Ok()
 
     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"}',
-    ) == Left(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "foo", None))
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"foo": foo},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "foo"}',
+    ) == Failure(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "foo", None))
 
 
 def test_dispatch_to_response_pure_notification_invalid_result() -> None:
     """Methods should return a Result, otherwise we get an Internal Error response."""
 
-    def not_a_result() -> None:
-        return None
+    def not_a_result() -> Result:
+        return None  # type: ignore
 
     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"}',
+            validate_args,
+            json.loads,
+            default_jsonrpc_validator,
+            identity,
+            {"not_a_result": not_a_result},
+            NOCONTEXT,
+            '{"jsonrpc": "2.0", "method": "not_a_result"}',
         )
         is None
     )
@@ -664,138 +709,130 @@ def not_a_result() -> None:
 def test_dispatch_to_response_pure_notification_raising_exception() -> None:
     """Allow raising an exception to return an error."""
 
-    def raise_exception() -> None:
+    def raise_exception() -> Result:
         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"}',
+            validate_args,
+            json.loads,
+            default_jsonrpc_validator,
+            identity,
+            {"raise_exception": raise_exception},
+            NOCONTEXT,
+            '{"jsonrpc": "2.0", "method": "raise_exception"}',
         )
         is None
     )
 
 
-# dispatch_to_response
-
-
-def test_dispatch_to_response() -> None:
-    response = dispatch_to_response(
-        '{"jsonrpc": "2.0", "method": "ping", "id": 1}', {"ping": ping}
-    )
-    assert response == Right(SuccessResponse("pong", 1))
-
-
-def test_dispatch_to_response_with_global_methods() -> None:
-    @method
-    def ping() -> Result:  # pylint: disable=redefined-outer-name
-        return Success("ping")
-
-    response = dispatch_to_response('{"jsonrpc": "2.0", "method": "ping", "id": 1}')
-    assert response == Right(SuccessResponse("pong", 1))
-
-
 # The remaining tests are direct from the examples in the specification
 
 
 def test_examples_positionals() -> None:
     def subtract(minuend: int, subtrahend: int) -> Result:
-        return Success(minuend - subtrahend)
+        return Ok(minuend - subtrahend)
 
     response = dispatch_to_response_pure(
-        methods={"subtract": subtract},
-        context=NOCONTEXT,
-        validator=default_validator,
-        post_process=identity,
-        deserializer=default_deserializer,
-        request='{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}',
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"subtract": subtract},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}',
     )
-    assert response == Right(SuccessResponse(19, 1))
+    assert response == Success(SuccessResponse(19, 1))
 
     # Second example
     response = dispatch_to_response_pure(
-        methods={"subtract": subtract},
-        context=NOCONTEXT,
-        validator=default_validator,
-        post_process=identity,
-        deserializer=default_deserializer,
-        request='{"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}',
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"subtract": subtract},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}',
     )
-    assert response == Right(SuccessResponse(-19, 2))
+    assert response == Success(SuccessResponse(-19, 2))
 
 
 def test_examples_nameds() -> None:
     def subtract(**kwargs: int) -> Result:
-        return Success(kwargs["minuend"] - kwargs["subtrahend"])
+        return Ok(kwargs["minuend"] - kwargs["subtrahend"])
 
     response = dispatch_to_response_pure(
-        methods={"subtract": subtract},
-        context=NOCONTEXT,
-        validator=default_validator,
-        post_process=identity,
-        deserializer=default_deserializer,
-        request=(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"subtract": subtract},
+        NOCONTEXT,
+        (
             '{"jsonrpc": "2.0", "method": "subtract", '
             '"params": {"subtrahend": 23, "minuend": 42}, "id": 3}'
         ),
     )
-    assert response == Right(SuccessResponse(19, 3))
+    assert response == Success(SuccessResponse(19, 3))
 
     # Second example
     response = dispatch_to_response_pure(
-        methods={"subtract": subtract},
-        context=NOCONTEXT,
-        validator=default_validator,
-        post_process=identity,
-        deserializer=default_deserializer,
-        request=(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"subtract": subtract},
+        NOCONTEXT,
+        (
             '{"jsonrpc": "2.0", "method": "subtract", '
             '"params": {"minuend": 42, "subtrahend": 23}, "id": 4}'
         ),
     )
-    assert response == Right(SuccessResponse(19, 4))
+    assert response == Success(SuccessResponse(19, 4))
 
 
 def test_examples_notification() -> None:
+    def f() -> Result:
+        return Ok()
+
     response = dispatch_to_response_pure(
-        methods={"update": lambda: None, "foobar": lambda: None},
-        context=NOCONTEXT,
-        validator=default_validator,
-        post_process=identity,
-        deserializer=default_deserializer,
-        request='{"jsonrpc": "2.0", "method": "update", "params": [1, 2, 3, 4, 5]}',
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"update": f, "foobar": f},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "update", "params": [1, 2, 3, 4, 5]}',
     )
     assert response is None
 
     # Second example
     response = dispatch_to_response_pure(
-        methods={"update": lambda: None, "foobar": lambda: None},
-        context=NOCONTEXT,
-        validator=default_validator,
-        post_process=identity,
-        deserializer=default_deserializer,
-        request='{"jsonrpc": "2.0", "method": "foobar"}',
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"update": f, "foobar": f},
+        NOCONTEXT,
+        '{"jsonrpc": "2.0", "method": "foobar"}',
     )
     assert response is None
 
 
 def test_examples_invalid_json() -> None:
     response = dispatch_to_response_pure(
-        methods={"ping": ping},
-        context=NOCONTEXT,
-        validator=default_validator,
-        post_process=identity,
-        deserializer=default_deserializer,
-        request=(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"ping": ping},
+        NOCONTEXT,
+        (
             '[{"jsonrpc": "2.0", "method": "sum", '
             '"params": [1,2,4], "id": "1"}, {"jsonrpc": "2.0", "method"]'
         ),
     )
-    assert response == Left(
+    assert response == Failure(
         ErrorResponse(
             ERROR_PARSE_ERROR,
             "Parse error",
@@ -808,14 +845,15 @@ def test_examples_invalid_json() -> None:
 def test_examples_empty_array() -> None:
     # 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(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"ping": ping},
+        NOCONTEXT,
+        "[]",
+    )
+    assert response == Failure(
         ErrorResponse(
             ERROR_INVALID_REQUEST,
             "Invalid request",
@@ -831,14 +869,15 @@ def test_examples_invalid_jsonrpc_batch() -> None:
     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]",
-    )
-    assert response == Left(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"ping": ping},
+        NOCONTEXT,
+        "[1]",
+    )
+    assert response == Failure(
         ErrorResponse(
             ERROR_INVALID_REQUEST,
             "Invalid request",
@@ -854,14 +893,15 @@ def test_examples_multiple_invalid_jsonrpc() -> None:
     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(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        {"ping": ping},
+        NOCONTEXT,
+        "[1, 2, 3]",
+    )
+    assert response == Failure(
         ErrorResponse(
             ERROR_INVALID_REQUEST,
             "Invalid request",
@@ -872,32 +912,42 @@ def test_examples_multiple_invalid_jsonrpc() -> None:
 
 
 def test_examples_mixed_requests_and_notifications() -> None:
-    methods: Dict[str, Callable[..., Any]] = {
-        "sum": lambda *args: Success(sum(args)),
-        "notify_hello": lambda *args: Success(19),
-        "subtract": lambda *args: Success(args[0] - sum(args[1:])),
-        "get_data": lambda: Success(["hello", 5]),
+    """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: Dict[str, Method] = {
+        "sum": lambda *args: Ok(sum(args)),
+        "notify_hello": lambda *args: Ok(19),
+        "subtract": lambda *args: Ok(args[0] - sum(args[1:])),
+        "get_data": lambda: Ok(["hello", 5]),
     }
-    response = dispatch(
-        deserializer=default_deserializer,
-        validator=default_validator,
-        context=NOCONTEXT,
-        methods=methods,
-        request="""[
+    response = dispatch_to_response_pure(
+        validate_args,
+        json.loads,
+        default_jsonrpc_validator,
+        identity,
+        methods,
+        NOCONTEXT,
+        """[
             {"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": "foo.get",
+                "params": {"name": "myself"},
+                "id": "5"
+            },
             {"jsonrpc": "2.0", "method": "get_data", "id": "9"}
         ]""",
     )
-    assert json.loads(response) == [
-        {"jsonrpc": "2.0", "result": 7, "id": "1"},
-        {"jsonrpc": "2.0", "result": 19, "id": "2"},
-        {
-            "jsonrpc": "2.0",
-            "error": {"code": -32601, "message": "Method not found", "data": "foo.get"},
-            "id": "5",
-        },
-        {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"},
+    assert response == [
+        Success(SuccessResponse(7, id="1")),
+        Success(SuccessResponse(19, id="2")),
+        Failure(ErrorResponse(-32601, "Method not found", "foo.get", id="5")),
+        Success(SuccessResponse(["hello", 5], id="9")),
     ]
diff --git a/tests/test_main.py b/tests/test_main.py
index 8b83069..fae47ce 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -1,25 +1,38 @@
 """Test main.py"""
-from oslash.either import Right  # type: ignore
+
+from returns.result import Success
 
 from jsonrpcserver.main import (
+    dispatch_to_json,
     dispatch_to_response,
     dispatch_to_serializable,
-    dispatch_to_json,
 )
+from jsonrpcserver.methods import method
 from jsonrpcserver.response import SuccessResponse
-from jsonrpcserver.result import Result, Success
+from jsonrpcserver.result import Ok, Result
+
+# pylint: disable=missing-function-docstring
 
 # pylint: disable=missing-function-docstring
 
 
 def ping() -> Result:
-    return Success("pong")
+    return Ok("pong")
 
 
 def test_dispatch_to_response() -> None:
     assert dispatch_to_response(
         '{"jsonrpc": "2.0", "method": "ping", "id": 1}', {"ping": ping}
-    ) == Right(SuccessResponse("pong", 1))
+    ) == Success(SuccessResponse("pong", 1))
+
+
+def test_dispatch_to_response_with_global_methods() -> None:
+    @method
+    def ping() -> Result:  # pylint: disable=redefined-outer-name
+        return Ok("pong")
+
+    response = dispatch_to_response('{"jsonrpc": "2.0", "method": "ping", "id": 1}')
+    assert response == Success(SuccessResponse("pong", 1))
 
 
 def test_dispatch_to_serializable() -> None:
diff --git a/tests/test_methods.py b/tests/test_methods.py
index 5649155..4459e7a 100644
--- a/tests/test_methods.py
+++ b/tests/test_methods.py
@@ -1,13 +1,15 @@
 """Test methods.py"""
+
 from jsonrpcserver.methods import global_methods, method
+from jsonrpcserver.result import Ok, Result
 
 # pylint: disable=missing-function-docstring
 
 
 def test_decorator() -> None:
     @method
-    def func() -> None:
-        pass
+    def func() -> Result:
+        return Ok()
 
     assert callable(global_methods["func"])
 
diff --git a/tests/test_request.py b/tests/test_request.py
index 114a34d..67e803b 100644
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -1,4 +1,5 @@
 """Test request.py"""
+
 from jsonrpcserver.request import Request
 
 # pylint: disable=missing-function-docstring
diff --git a/tests/test_response.py b/tests/test_response.py
index 29c2385..7829349 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -1,7 +1,8 @@
 """Test response.py"""
+
 from unittest.mock import sentinel
 
-from oslash.either import Left, Right  # type: ignore
+from returns.result import Failure, Success
 
 from jsonrpcserver.response import (
     ErrorResponse,
@@ -65,7 +66,7 @@ def test_ServerErrorResponse() -> None:
 
 
 def test_to_serializable() -> None:
-    assert to_serializable(Right(SuccessResponse(sentinel.result, sentinel.id))) == {
+    assert to_serializable(Success(SuccessResponse(sentinel.result, sentinel.id))) == {
         "jsonrpc": "2.0",
         "result": sentinel.result,
         "id": sentinel.id,
@@ -77,7 +78,7 @@ def test_to_serializable_None() -> None:
 
 
 def test_to_serializable_SuccessResponse() -> None:
-    assert to_serializable(Right(SuccessResponse(sentinel.result, sentinel.id))) == {
+    assert to_serializable(Success(SuccessResponse(sentinel.result, sentinel.id))) == {
         "jsonrpc": "2.0",
         "result": sentinel.result,
         "id": sentinel.id,
@@ -86,7 +87,9 @@ def test_to_serializable_SuccessResponse() -> None:
 
 def test_to_serializable_ErrorResponse() -> None:
     assert to_serializable(
-        Left(ErrorResponse(sentinel.code, sentinel.message, sentinel.data, sentinel.id))
+        Failure(
+            ErrorResponse(sentinel.code, sentinel.message, sentinel.data, sentinel.id)
+        )
     ) == {
         "jsonrpc": "2.0",
         "error": {
@@ -99,7 +102,9 @@ def test_to_serializable_ErrorResponse() -> None:
 
 
 def test_to_serializable_list() -> None:
-    assert to_serializable([Right(SuccessResponse(sentinel.result, sentinel.id))]) == [
+    assert to_serializable(
+        [Success(SuccessResponse(sentinel.result, sentinel.id))]
+    ) == [
         {
             "jsonrpc": "2.0",
             "result": sentinel.result,
diff --git a/tests/test_result.py b/tests/test_result.py
index 793dbed..797263c 100644
--- a/tests/test_result.py
+++ b/tests/test_result.py
@@ -1,13 +1,14 @@
 """Test result.py"""
+
 from unittest.mock import sentinel
 
-from oslash.either import Left, Right  # type: ignore
+from returns.result import Failure, Success
 
 from jsonrpcserver.result import (
     Error,
     ErrorResult,
     InvalidParamsResult,
-    Success,
+    Ok,
     SuccessResult,
 )
 from jsonrpcserver.sentinels import NODATA
@@ -58,9 +59,9 @@ def test_InvalidParamsResult_with_data() -> None:
     assert result.data == sentinel.data
 
 
-def test_Success() -> None:
-    assert Success() == Right(SuccessResult(None))
+def test_Ok() -> None:
+    assert Ok() == Success(SuccessResult(None))
 
 
 def test_Error() -> None:
-    assert Error(1, "foo", None) == Left(ErrorResult(1, "foo", None))
+    assert Error(1, "foo", None) == Failure(ErrorResult(1, "foo", None))
diff --git a/tests/test_sentinels.py b/tests/test_sentinels.py
index c5c8f02..b1e5ffe 100644
--- a/tests/test_sentinels.py
+++ b/tests/test_sentinels.py
@@ -1,4 +1,5 @@
 """Test sentinels.py"""
+
 from jsonrpcserver.sentinels import Sentinel
 
 # pylint: disable=missing-function-docstring
diff --git a/tests/test_server.py b/tests/test_server.py
index ad37266..46c2b51 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -1,4 +1,5 @@
 """Test server.py"""
+
 from unittest.mock import Mock, patch
 
 from jsonrpcserver.server import serve
diff --git a/tox.ini b/tox.ini
index d8673ad..bd427c4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -11,5 +11,5 @@ setenv = PYTHONDONTWRITEBYTECODE=1
 deps =
     pytest
     pytest-asyncio
-commands = pytest tests
+commands = pytest tests --asyncio-mode=strict
 install_command=pip install --trusted-host=pypi.org --trusted-host=files.pythonhosted.org {opts} {packages}
