yoanm/jsonrpc-server-sdk

Server SDK to convert a json-rpc request string into json-rpc response string

v3.3.2 2023-05-06 06:13 UTC

README

License Code size Dependabot Status

Scrutinizer Build Status Scrutinizer Code Quality Codacy Badge

CI codecov

Latest Stable Version Packagist PHP version

Simple server SDK to convert a json-rpc request string into json-rpc response string.

See yoanm/symfony-jsonrpc-http-server for automatic dependency injection.

See yoanm/jsonrpc-params-symfony-validator-sdk for params validation.

See yoanm/jsonrpc-server-doc-sdk for documentation generation.

How to use

Sdk requires only two things :

Sdk optionally provide :

  • Events dispatch
  • Params validation

Simple Example

JSON-RPC Method

use Yoanm\JsonRpcServer\Domain\JsonRpcMethodInterface;

class DummyMethod implements JsonRpcMethodInterface
{
    /**
     * {@inheritdoc}
     */
    public function apply(array $paramList = null)
    {
        // Handle the request
        ...
        // Then return a result
        return [
            'status' => 'done',
        ];
        // Or
        return null;
        // Or
        return 12345;
    }
}

Array method resolver (simple example)

You can use the one used for behat tests or this Psr11 method resolver as example

use Yoanm\JsonRpcServer\Domain\JsonRpcMethodInterface;
use Yoanm\JsonRpcServer\Domain\JsonRpcMethodResolverInterface;

class ArrayMethodResolver implements JsonRpcMethodResolverInterface
{
    /** @var JsonRpcMethodInterface[] */
    private $methodList = [];

    /**
     * {@inheritdoc}
     */
    public function resolve(string $methodName) : ?JsonRpcMethodInterface
    {
        return array_key_exists($methodName, $this->methodList)
            ? $this->methodList[$methodName]
            : null
        ;
    }

    /**
     * @param JsonRpcMethodInterface $method
     * @param string                 $methodName
     */
    public function addMethod(JsonRpcMethodInterface $method, string $methodName)
    {
        $this->methodList[$methodName] = $method;
    }
}

Then add your method to the resolver and create the endpoint :

use Yoanm\JsonRpcServer\App\Creator\ResponseCreator;
use Yoanm\JsonRpcServer\App\Handler\ExceptionHandler;
use Yoanm\JsonRpcServer\App\Handler\JsonRpcRequestHandler;
use Yoanm\JsonRpcServer\App\Serialization\JsonRpcCallDenormalizer;
use Yoanm\JsonRpcServer\App\Serialization\JsonRpcCallResponseNormalizer;
use Yoanm\JsonRpcServer\App\Serialization\JsonRpcCallSerializer;
use Yoanm\JsonRpcServer\App\Serialization\JsonRpcRequestDenormalizer;
use Yoanm\JsonRpcServer\App\Serialization\JsonRpcResponseErrorNormalizer;
use Yoanm\JsonRpcServer\App\Serialization\JsonRpcResponseNormalizer;
use Yoanm\JsonRpcServer\Infra\Endpoint\JsonRpcEndpoint;

$resolver = new ArrayMethodResolver();
$resolver->addMethod('dummy-method', new DummyMethod());

$jsonRpcSerializer = new JsonRpcCallSerializer(
    new JsonRpcCallDenormalizer(
        new JsonRpcRequestDenormalizer()
    ),
    new JsonRpcCallResponseNormalizer(
        new JsonRpcResponseNormalizer() 
        // Or `new JsonRpcResponseNormalizer(new JsonRpcResponseErrorNormalizer())` for debug purpose
        // To also dump arguments, be sure 'zend.exception_ignore_args' ini option is not at true/1
    )
);
$responseCreator = new ResponseCreator();
$requestHandler = new JsonRpcRequestHandler($resolver, $responseCreator);
$exceptionHandler = new ExceptionHandler($responseCreator);

$endpoint = new JsonRpcEndpoint($jsonRpcSerializer, $requestHandler, $exceptionHandler);

Once endpoint is ready, you can send it request string :

$requestString = <<<JSONRPC
{
    "jsonrpc": "2.0",
    "id": 1
    "method": "dummy-method"
}
JSONRPC;

$responseString = $endpoint->index($requestString);

$responseString will be the following string depending of method returned value :

  • {"jsonrpc":"2.0","id":1,"result":{"status":"done"}}
  • {"jsonrpc":"2.0","id":1,"result":null}
  • {"jsonrpc":"2.0","id":1,"result":12345}

Events dispatch example

Simple event dispatcher

You can use the one used for behat tests as example

use Yoanm\JsonRpcServer\Domain\Event\JsonRpcServerEvent;
use Yoanm\JsonRpcServer\Domain\JsonRpcServerDispatcherInterface;

/**
 * Class SimpleDispatcher
 */
class SimpleDispatcher implements JsonRpcServerDispatcherInterface
{
    /** @var callable[] */
    private $listenerList = [];

    /**
     * {@inheritdoc}
     */
    public function dispatchJsonRpcEvent(string $eventName, JsonRpcServerEvent $event = null) : void
    {
        if (!array_key_exists($eventName, $this->listenerList)) {
            return;
        }

        foreach ($this->listenerList[$eventName] as $listener) {
            $listener($event, $eventName);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function addJsonRpcListener(string $eventName, $listener) : void
    {
        $this->listenerList[$eventName][] = $listener;
    }
}

Then bind your listeners to your dispatcher:

use Yoanm\JsonRpcServer\Domain\Event\Acknowledge\OnRequestReceivedEvent;
use Yoanm\JsonRpcServer\Domain\Event\Acknowledge\OnResponseSendingEvent;
use Yoanm\JsonRpcServer\Domain\Event\Action\OnMethodSuccessEvent;

$dispatcher = new SimpleDispatcher();

$listener = function ($event, $eventName) {
    echo sprintf(
        'Received %s with event class "%s"',
        $eventName,
        get_class($event)
    );
};

$dispatcher->addJsonRpcListener(OnRequestReceivedEvent::EVENT_NAME, $listener);
$dispatcher->addJsonRpcListener(OnResponseSendingEvent::EVENT_NAME, $listener);
$dispatcher->addJsonRpcListener(OnMethodSuccessEvent::EVENT_NAME, $listener);

And bind dispatcher like following :

$endpoint->setJsonRpcServerDispatcher($dispatcher);
$requestHandler->setJsonRpcServerDispatcher($dispatcher);
$exceptionHandler->setJsonRpcServerDispatcher($dispatcher);

Events dispatched

Basic request lifecycle
  • json_rpc_server_skd.on_request_received / Acknowledge\OnRequestReceivedEvent

    Dispatched when a request has been passed to the endpoint and successfully deserialized.

    N.B. : Lonely cases where this event is not dispatched are when the request string is not a valid JSON-RPC request.

    It include :

    • Parse error exception (malformed json string)

    • For simple request only, in case of Invalid request (not an object / missing required properties / ...).

      ⚠️ For batch request containing Invalid SubRequest, this event will still be dispatched

  • Either

    • json_rpc_server_skd.on_method_success / Action\OnMethodSuccessEvent

      Dispatched only in case JSON-RPC method has been successfully executed.

    • json_rpc_server_skd.on_method_failure / Action\OnMethodFailureEvent

      Dispatched only in case JSON-RPC method throw an exception during execution.

  • json_rpc_server_skd.on_response_sending / Acknowledge\OnResponseSendingEvent

    Dispatched when a response has been successfully serialized by the endpoint and will be returned.

Additional events
Batch request
Exception

json_rpc_server_skd.on_exception / Action\OnExceptionEvent

Dispatched when an exception occurred during sdk execution

Action vs Acknowledge events
Acknowledge

They have only an acknowledge purpose.

They are grouped under Yoanm\JsonRpcServer\Domain\Event\Acknowledge namespace.

Action

They allow you to override stuffs.

They are grouped under Yoanm\JsonRpcServer\Domain\Event\Action namespace.

Here, the list :

Params validation example

You can use this JSON-RPC params symfony validator as example

To validate params for a given method, do the following :

use Yoanm\JsonRpcServer\Domain\JsonRpcMethodInterface;
use Yoanm\JsonRpcServer\Domain\JsonRpcMethodParamsValidatorInterface;
use Yoanm\JsonRpcServer\Domain\Model\JsonRpcRequest;

$validator = new class implements JsonRpcMethodParamsValidatorInterface
{
    public function validate(JsonRpcRequest $jsonRpcRequest, JsonRpcMethodInterface $method) : array
    {
        if (!(/** Skip unexpected method */)) {
            return [];
        }

        // Create your violations based on what you want
        $paramList = $jsonRpcRequest->getParamList();
        $violation = "???";

        return [$violation];
    }
};

$requestHandler->setMethodParamsValidator($validator);

Contributing

See contributing note