diff --git a/.docs/README.md b/.docs/README.md deleted file mode 100644 index 002ac5a..0000000 --- a/.docs/README.md +++ /dev/null @@ -1,462 +0,0 @@ -# Contributte JSON-RPC - -JSON-RPC 2.0 toolset for [Nette Framework](https://nette.org/) built on top of PSR-7 ([guzzlehttp](https://github.com/guzzle/guzzle)), [justinrainbow/json-schema](https://github.com/justinrainbow/json-schema), [league/flysystem](https://github.com/thephpleague/flysystem) and [predis/predis](https://github.com/predis/predis). - -## Content - -- [Installation](#installation) -- [Configuration](#configuration) - - [Minimal configuration](#minimal-configuration) - - [Full configuration](#full-configuration) - - [Caching](#caching) -- [Commands](#commands) - - [ICommand](#icommand) - - [ICommandDTO](#icommanddto) -- [JSON Schema validation](#json-schema-validation) -- [Request and response format](#request-and-response-format) - - [Request format](#request-format) - - [Success response](#success-response) - - [Error response](#error-response) - - [Batch requests](#batch-requests) -- [Error codes](#error-codes) -- [Examples](#examples) - -## Installation - -Install package using composer. - -```bash -composer require contributte/jsonrpc -``` - -Register prepared [compiler extension](https://doc.nette.org/en/dependency-injection/nette-container) in your `config.neon` file. - -```neon -extensions: - jsonrpc: Contributte\JsonRPC\DI\JsonRPCExtension -``` - -> [!NOTE] -> For Redis caching support, install predis: `composer require predis/predis` - -## Configuration - -### Minimal configuration - -```neon -extensions: - jsonrpc: Contributte\JsonRPC\DI\JsonRPCExtension - -jsonrpc: - methodsMapping: - user.get: App\Command\User\GetUserCommand - user.create: App\Command\User\CreateUserCommand - jsonSchemaFilesDir: %appDir%/../json-schema -``` - -### Full configuration - -Here is the list of all available options with their types. - -```neon -jsonrpc: - methodsMapping: array # Required: Maps method names to command classes - jsonSchemaFilesDir: # Required: Directory containing JSON schema files - projectName: # Optional: Project identifier for cache keys - ttlInSeconds: # Optional: Cache TTL in seconds (default: 31556926 = 1 year) - registerRedisPool: # Optional: Auto-register Redis cache pool (default: true) -``` - -For example: - -```neon -jsonrpc: - methodsMapping: - user.get: App\Command\User\GetUserCommand - user.create: App\Command\User\CreateUserCommand - user.update: App\Command\User\UpdateUserCommand - user.delete: App\Command\User\DeleteUserCommand - feed.getAll: App\Command\Feed\GetAllFeedCommand - jsonSchemaFilesDir: %appDir%/../json-schema - projectName: my-api - ttlInSeconds: 86400 - registerRedisPool: true -``` - -### Caching - -JSON schemas are cached using Redis via a PSR-6 compatible cache pool. The cache is enabled by default when `registerRedisPool` is `true`. - -> [!TIP] -> Cache significantly improves performance by avoiding repeated filesystem reads and JSON parsing of schema files. - -To disable Redis caching: - -```neon -jsonrpc: - registerRedisPool: false -``` - -> [!WARNING] -> When caching is disabled, schemas will be read from disk on every request, which may impact performance. - -## Commands - -Commands implement the business logic for each JSON-RPC method. Each command consists of two parts: `ICommand` and `ICommandDTO`. - -### ICommand - -The `ICommand` interface defines a method handler. - -```php -userRepository->find($commandDTO->getUserId()); - - if ($user === null) { - return new ErrorResponse( - GenericCodes::CODE_INVALID_PARAMS, - 'Invalid params', - 'User not found', - ); - } - - return new SuccessResponse((object) [ - 'id' => $user->getId(), - 'name' => $user->getName(), - 'email' => $user->getEmail(), - ]); - } - -} -``` - -### ICommandDTO - -The `ICommandDTO` interface defines a Data Transfer Object that holds validated request parameters. - -```php -userId); - } - - public function getUserId(): int - { - return $this->userId; - } - -} -``` - -> [!IMPORTANT] -> The `fromValidParams` method receives parameters that have already been validated against the JSON schema. Additional validation can be performed here if needed. - -## JSON Schema validation - -Each method requires a corresponding JSON schema file for parameter validation. Schema files must be named `{method-name}.json` and placed in the `jsonSchemaFilesDir` directory. - -For method `user.get`, create file `user.get.json`: - -```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "userId": { - "type": "integer", - "description": "The user ID to retrieve" - } - }, - "required": ["userId"] -} -``` - -For method `feed.getAll`, create file `feed.getAll.json`: - -```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "pagination": { - "type": "object", - "properties": { - "offset": { - "type": "integer", - "minimum": 0 - }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 100 - } - }, - "required": ["offset", "limit"] - } - }, - "required": ["pagination"] -} -``` - -> [!TIP] -> Take a look at more information about JSON Schema: -> - https://json-schema.org/ -> - https://json-schema.org/understanding-json-schema/ - -## Request and response format - -This library implements the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification). - -### Request format - -```json -{ - "jsonrpc": "2.0", - "method": "user.get", - "params": { - "userId": 123 - }, - "id": "unique-request-id" -} -``` - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `jsonrpc` | string | Yes | Must be `"2.0"` | -| `method` | string | Yes | Method name (e.g., `user.get`) | -| `params` | object | Yes | Method parameters | -| `id` | string\|null | No | Request identifier for matching responses | - -### Success response - -```json -{ - "jsonrpc": "2.0", - "result": { - "id": 123, - "name": "John Doe", - "email": "john@example.com" - }, - "id": "unique-request-id", - "time": "2025-01-15T14:30:00+00:00" -} -``` - -### Error response - -```json -{ - "jsonrpc": "2.0", - "error": { - "code": -32602, - "message": "Invalid params", - "data": { - "reason": "User not found" - } - }, - "id": "unique-request-id", - "time": "2025-01-15T14:30:00+00:00" -} -``` - -### Batch requests - -The library supports batch requests as per JSON-RPC 2.0 specification: - -**Request:** - -```json -[ - { - "jsonrpc": "2.0", - "method": "user.get", - "params": {"userId": 1}, - "id": "1" - }, - { - "jsonrpc": "2.0", - "method": "user.get", - "params": {"userId": 2}, - "id": "2" - } -] -``` - -**Response:** - -```json -[ - { - "jsonrpc": "2.0", - "result": {"id": 1, "name": "Alice"}, - "id": "1", - "time": "2025-01-15T14:30:00+00:00" - }, - { - "jsonrpc": "2.0", - "result": {"id": 2, "name": "Bob"}, - "id": "2", - "time": "2025-01-15T14:30:00+00:00" - } -] -``` - -## Error codes - -The library uses standard JSON-RPC 2.0 error codes: - -| Code | Constant | Message | Description | -|------|----------|---------|-------------| -| `-32700` | `CODE_PARSE_ERROR` | Parse error | Invalid JSON was received | -| `-32600` | `CODE_INVALID_REQUEST` | Invalid Request | The JSON sent is not a valid Request object | -| `-32601` | `CODE_METHOD_NOT_FOUND` | Method not found | The method does not exist / is not available | -| `-32602` | `CODE_INVALID_PARAMS` | Invalid params | Invalid method parameter(s) | -| `-32603` | `CODE_INTERNAL_ERROR` | Internal error | Internal JSON-RPC error | - -These codes are defined in `Contributte\JsonRPC\Response\Enum\GenericCodes`. - -> [!NOTE] -> You can also define custom error codes for your application-specific errors. The specification reserves codes from `-32000` to `-32099` for implementation-defined server errors. - -## Examples - -### Basic presenter implementation - -```php -getHttpRequest()->getRawBody(); - - if ($rawBody === null) { - $this->sendJson( - $this->responseDataBuilder->buildParseError('Empty request body'), - ); - } - - try { - $requestCollection = $this->requestCollectionFactory->create($rawBody); - } catch (\Throwable $e) { - $this->sendJson( - $this->responseDataBuilder->buildParseError($e->getMessage()), - ); - } - - $this->processRequests($requestCollection); - - $this->sendJson( - $this->responseDataBuilder->buildResponseBadge($requestCollection), - ); - } - - private function processRequests(RequestCollection $requestCollection): void - { - foreach ($requestCollection as $request) { - if ($request instanceof ValidFormatRequest) { - try { - $response = $this->requestProcessor->process($request); - } catch (\Throwable $e) { - $response = \Contributte\JsonRPC\Response\Type\ErrorResponse::fromJsonRPCAwareException($e); - } - - $requestCollection[$request] = $response; - } - } - } - -} -``` - -### Project structure - -``` -app/ -├── Command/ -│ ├── User/ -│ │ ├── GetUserCommand.php -│ │ ├── GetUserCommandDTO.php -│ │ ├── CreateUserCommand.php -│ │ └── CreateUserCommandDTO.php -│ └── Feed/ -│ ├── GetAllFeedCommand.php -│ └── GetAllFeedCommandDTO.php -├── Presenters/ -│ └── ApiPresenter.php -└── config/ - └── config.neon - -json-schema/ -├── user.get.json -├── user.create.json -└── feed.getAll.json -``` - -> [!TIP] -> Take a look at more examples in [contributte/jsonrpc](https://github.com/contributte/jsonrpc) repository. diff --git a/README.md b/README.md index e95289c..9c65ea0 100644 --- a/README.md +++ b/README.md @@ -18,24 +18,473 @@ Website 🚀 contributte.org | Contact 👨🏻‍💻 f3l1x.io | Twitter 🐦 @contributte

-## Usage +JSON-RPC 2.0 toolset for Nette Framework built on top of PSR-7, JSON Schema, Flysystem and Predis. -To install the latest version of `contributte/jsonrpc` use [Composer](https://getcomposer.org). +## Versions + +| State | Version | Branch | Nette | PHP | +|--------|----------|----------|--------|---------| +| dev | `^7.1.0` | `master` | `3.2+` | `>=8.2` | +| stable | `^7.0.0` | `master` | `3.2+` | `>=8.2` | + +## Contents + +- [Installation](#installation) +- [Configuration](#configuration) + - [Minimal configuration](#minimal-configuration) + - [Full configuration](#full-configuration) + - [Caching](#caching) +- [Commands](#commands) + - [ICommand](#icommand) + - [ICommandDTO](#icommanddto) +- [JSON Schema validation](#json-schema-validation) +- [Request and response format](#request-and-response-format) + - [Request format](#request-format) + - [Success response](#success-response) + - [Error response](#error-response) + - [Batch requests](#batch-requests) +- [Error codes](#error-codes) +- [Examples](#examples) + +## Installation + +Install package using composer. ```bash composer require contributte/jsonrpc ``` -## Documentation +Register prepared [compiler extension](https://doc.nette.org/en/dependency-injection/nette-container) in your `config.neon` file. -For details on how to use this package, check out our [documentation](.docs). +```neon +extensions: + jsonrpc: Contributte\JsonRPC\DI\JsonRPCExtension +``` -## Versions +> [!NOTE] +> For Redis caching support, install predis: `composer require predis/predis` -| State | Version | Branch | Nette | PHP | -|--------|----------|----------|--------|---------| -| dev | `^7.1.0` | `master` | `3.2+` | `>=8.2` | -| stable | `^7.0.0` | `master` | `3.2+` | `>=8.2` | +## Configuration + +### Minimal configuration + +```neon +extensions: + jsonrpc: Contributte\JsonRPC\DI\JsonRPCExtension + +jsonrpc: + methodsMapping: + user.get: App\Command\User\GetUserCommand + user.create: App\Command\User\CreateUserCommand + jsonSchemaFilesDir: %appDir%/../json-schema +``` + +### Full configuration + +Here is the list of all available options with their types. + +```neon +jsonrpc: + methodsMapping: array # Required: Maps method names to command classes + jsonSchemaFilesDir: # Required: Directory containing JSON schema files + projectName: # Optional: Project identifier for cache keys + ttlInSeconds: # Optional: Cache TTL in seconds (default: 31556926 = 1 year) + registerRedisPool: # Optional: Auto-register Redis cache pool (default: true) +``` + +For example: + +```neon +jsonrpc: + methodsMapping: + user.get: App\Command\User\GetUserCommand + user.create: App\Command\User\CreateUserCommand + user.update: App\Command\User\UpdateUserCommand + user.delete: App\Command\User\DeleteUserCommand + feed.getAll: App\Command\Feed\GetAllFeedCommand + jsonSchemaFilesDir: %appDir%/../json-schema + projectName: my-api + ttlInSeconds: 86400 + registerRedisPool: true +``` + +### Caching + +JSON schemas are cached using Redis via a PSR-6 compatible cache pool. The cache is enabled by default when `registerRedisPool` is `true`. + +> [!TIP] +> Cache significantly improves performance by avoiding repeated filesystem reads and JSON parsing of schema files. + +To disable Redis caching: + +```neon +jsonrpc: + registerRedisPool: false +``` + +> [!WARNING] +> When caching is disabled, schemas will be read from disk on every request, which may impact performance. + +## Commands + +Commands implement the business logic for each JSON-RPC method. Each command consists of two parts: `ICommand` and `ICommandDTO`. + +### ICommand + +The `ICommand` interface defines a method handler. + +```php +userRepository->find($commandDTO->getUserId()); + + if ($user === null) { + return new ErrorResponse( + GenericCodes::CODE_INVALID_PARAMS, + 'Invalid params', + 'User not found', + ); + } + + return new SuccessResponse((object) [ + 'id' => $user->getId(), + 'name' => $user->getName(), + 'email' => $user->getEmail(), + ]); + } + +} +``` + +### ICommandDTO + +The `ICommandDTO` interface defines a Data Transfer Object that holds validated request parameters. + +```php +userId); + } + + public function getUserId(): int + { + return $this->userId; + } + +} +``` + +> [!IMPORTANT] +> The `fromValidParams` method receives parameters that have already been validated against the JSON schema. Additional validation can be performed here if needed. + +## JSON Schema validation + +Each method requires a corresponding JSON schema file for parameter validation. Schema files must be named `{method-name}.json` and placed in the `jsonSchemaFilesDir` directory. + +For method `user.get`, create file `user.get.json`: + +```json +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "userId": { + "type": "integer", + "description": "The user ID to retrieve" + } + }, + "required": ["userId"] +} +``` + +For method `feed.getAll`, create file `feed.getAll.json`: + +```json +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "pagination": { + "type": "object", + "properties": { + "offset": { + "type": "integer", + "minimum": 0 + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + }, + "required": ["offset", "limit"] + } + }, + "required": ["pagination"] +} +``` + +> [!TIP] +> Take a look at more information about JSON Schema: +> - https://json-schema.org/ +> - https://json-schema.org/understanding-json-schema/ + +## Request and response format + +This library implements the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification). + +### Request format + +```json +{ + "jsonrpc": "2.0", + "method": "user.get", + "params": { + "userId": 123 + }, + "id": "unique-request-id" +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `jsonrpc` | string | Yes | Must be `"2.0"` | +| `method` | string | Yes | Method name (e.g., `user.get`) | +| `params` | object | Yes | Method parameters | +| `id` | string\|null | No | Request identifier for matching responses | + +### Success response + +```json +{ + "jsonrpc": "2.0", + "result": { + "id": 123, + "name": "John Doe", + "email": "john@example.com" + }, + "id": "unique-request-id", + "time": "2025-01-15T14:30:00+00:00" +} +``` + +### Error response + +```json +{ + "jsonrpc": "2.0", + "error": { + "code": -32602, + "message": "Invalid params", + "data": { + "reason": "User not found" + } + }, + "id": "unique-request-id", + "time": "2025-01-15T14:30:00+00:00" +} +``` + +### Batch requests + +The library supports batch requests as per JSON-RPC 2.0 specification: + +**Request:** + +```json +[ + { + "jsonrpc": "2.0", + "method": "user.get", + "params": {"userId": 1}, + "id": "1" + }, + { + "jsonrpc": "2.0", + "method": "user.get", + "params": {"userId": 2}, + "id": "2" + } +] +``` + +**Response:** + +```json +[ + { + "jsonrpc": "2.0", + "result": {"id": 1, "name": "Alice"}, + "id": "1", + "time": "2025-01-15T14:30:00+00:00" + }, + { + "jsonrpc": "2.0", + "result": {"id": 2, "name": "Bob"}, + "id": "2", + "time": "2025-01-15T14:30:00+00:00" + } +] +``` + +## Error codes + +The library uses standard JSON-RPC 2.0 error codes: + +| Code | Constant | Message | Description | +|------|----------|---------|-------------| +| `-32700` | `CODE_PARSE_ERROR` | Parse error | Invalid JSON was received | +| `-32600` | `CODE_INVALID_REQUEST` | Invalid Request | The JSON sent is not a valid Request object | +| `-32601` | `CODE_METHOD_NOT_FOUND` | Method not found | The method does not exist / is not available | +| `-32602` | `CODE_INVALID_PARAMS` | Invalid params | Invalid method parameter(s) | +| `-32603` | `CODE_INTERNAL_ERROR` | Internal error | Internal JSON-RPC error | + +These codes are defined in `Contributte\JsonRPC\Response\Enum\GenericCodes`. + +> [!NOTE] +> You can also define custom error codes for your application-specific errors. The specification reserves codes from `-32000` to `-32099` for implementation-defined server errors. + +## Examples + +### Basic presenter implementation + +```php +getHttpRequest()->getRawBody(); + + if ($rawBody === null) { + $this->sendJson( + $this->responseDataBuilder->buildParseError('Empty request body'), + ); + } + + try { + $requestCollection = $this->requestCollectionFactory->create($rawBody); + } catch (\Throwable $e) { + $this->sendJson( + $this->responseDataBuilder->buildParseError($e->getMessage()), + ); + } + + $this->processRequests($requestCollection); + + $this->sendJson( + $this->responseDataBuilder->buildResponseBadge($requestCollection), + ); + } + + private function processRequests(RequestCollection $requestCollection): void + { + foreach ($requestCollection as $request) { + if ($request instanceof ValidFormatRequest) { + try { + $response = $this->requestProcessor->process($request); + } catch (\Throwable $e) { + $response = \Contributte\JsonRPC\Response\Type\ErrorResponse::fromJsonRPCAwareException($e); + } + + $requestCollection[$request] = $response; + } + } + } + +} +``` + +### Project structure + +``` +app/ +├── Command/ +│ ├── User/ +│ │ ├── GetUserCommand.php +│ │ ├── GetUserCommandDTO.php +│ │ ├── CreateUserCommand.php +│ │ └── CreateUserCommandDTO.php +│ └── Feed/ +│ ├── GetAllFeedCommand.php +│ └── GetAllFeedCommandDTO.php +├── Presenters/ +│ └── ApiPresenter.php +└── config/ + └── config.neon + +json-schema/ +├── user.get.json +├── user.create.json +└── feed.getAll.json +``` + +> [!TIP] +> Take a look at more examples in [contributte/jsonrpc](https://github.com/contributte/jsonrpc) repository. ## Development