рд╕рд┐рдореНрдлрдиреА 4 рдореЗрдВ JSON RPC рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░реЗрдВ


рд╕рднреА рдХреЛ рдирдорд╕реНрдХрд╛рд░, рдЖрдЬ рд╣рдо рдмрд╛рдд рдХрд░рддреЗ рд╣реИрдВ рдХрд┐ рджреЛрд╕реНрддреЛрдВ рдХреЛ рд╕рд┐рдореНрдлрдиреА 4, JSON RPC рдФрд░ OpenAPI 3 рдХреИрд╕реЗ рдмрдирд╛рдпрд╛ рдЬрд╛рдПред


рдпрд╣ рд▓реЗрдЦ рд╢реБрд░реБрдЖрддреА рд▓реЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рдЕрднрд┐рдкреНрд░реЗрдд рдирд╣реАрдВ рд╣реИ, рдЖрдкрдХреЛ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рд╕рдордЭрдирд╛ рдЪрд╛рд╣рд┐рдП рдХрд┐ рд╕рд┐рдореНрдлрдиреА, рдбрд┐рдкреЗрдбреЗрдВрд╕реА рдЗрдВрдЬреЗрдХреНрд╢рди рдФрд░ рдЕрдиреНрдп "рдбрд░рд╛рд╡рдиреА" рдЪреАрдЬреЛрдВ рдХреЗ рд╕рд╛рде рдХреИрд╕реЗ рдХрд╛рдо рдХрд┐рдпрд╛ рдЬрд╛рдПред


рдЖрдЬ, JSON RPC рдХреЗ рдПрдХ рд╡рд┐рд╢рд┐рд╖реНрдЯ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЛ рджреЗрдЦреЗрдВред


рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди


рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рд╕рд┐рдореНрдлрдиреА рдХреЗ рд▓рд┐рдП рдХрдИ JSON RPC рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╣реИрдВ:



рд╣рдо рдЗрд╕ рд▓реЗрдЦ рдореЗрдВ рдЙрддреНрддрд░рд╛рд░реНрджреНрдз рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХрд░реЗрдВрдЧреЗред рдЗрд╕ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреЗ рдХрдИ рдлрд╛рдпрджреЗ рд╣реИрдВ рдЬрд┐рдиреНрд╣реЛрдВрдиреЗ рдореЗрд░реА рдкрд╕рдВрдж рдХреЛ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд┐рдпрд╛ рд╣реИред


рдЗрд╕реЗ рдХрд┐рд╕реА рднреА рдврд╛рдВрдЪреЗ ( yoanm / php-jsonrpc-server-sdk ) рдХреЗ рд▓рд┐рдП рдмрд╛рдзреНрдп рдХрд┐рдП рдмрд┐рдирд╛ рд╡рд┐рдХрд╕рд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рд╕рд┐рдореНрдлрдиреА рдХреЗ рд▓рд┐рдП рдПрдХ рдмрдВрдбрд▓ рд╣реИ, рдЗрд╕рдореЗрдВ рдХрдИ рдЕрддрд┐рд░рд┐рдХреНрдд рдкреИрдХреЗрдЬ рд╣реИрдВ рдЬреЛ рдЖрдкрдХреЛ рдЗрдирдкреБрдЯ рдХреА рдЬрд╛рдБрдЪ, рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рдкреНрд░рд▓реЗрдЦрди, рдШрдЯрдирд╛рдУрдВ рдФрд░ рдЗрдВрдЯрд░рдлреЗрд╕ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддреЗ рд╣реИрдВ, рдЬреЛ рдкреБрдирд░реНрд╡рд┐рддрд░рдг рдХреЗ рдмрд┐рдирд╛ рдХрд╛рдо рдХреЛ рдкреВрд░рдХ рдХрд░рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реЛред


рд╕реНрдерд╛рдкрдирд╛


рдЖрд░рдВрдн рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╕рд┐рдореНрдлрдиреА / рдХрдВрдХрд╛рд▓ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВред


$ composer create-project symfony/skeleton jsonrpc 

рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдЬрд╛рдПрдВред


 $ cd jsonrpc 

рдФрд░ рдЖрд╡рд╢реНрдпрдХ рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВред


 $ composer require yoanm/symfony-jsonrpc-http-server 

рдЕрдиреБрдХреВрд▓рдиред


 // config/bundles.php return [ ... Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], Yoanm\SymfonyJsonRpcHttpServer\JsonRpcHttpServerBundle::class => ['all' => true], ... ]; 

 # config/routes.yaml json-rpc-endpoint: resource: '@JsonRpcHttpServerBundle/Resources/config/routing/endpoint.xml' 

 # config/packages/json_rpc.yaml json_rpc_http_server: ~ 

рдПрдХ рд╕реЗрд╡рд╛ рдЬреЛрдбрд╝реЗрдВ рдЬреЛ рд╣рдорд╛рд░реЗ рд╕рднреА рддрд░реАрдХреЛрдВ рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░реЗрдЧреАред


 // src/MappingCollector.php <?php namespace App; use Yoanm\JsonRpcServer\Domain\JsonRpcMethodAwareInterface; use Yoanm\JsonRpcServer\Domain\JsonRpcMethodInterface; class MappingCollector implements JsonRpcMethodAwareInterface { /** @var JsonRpcMethodInterface[] */ private $mappingList = []; public function addJsonRpcMethod(string $methodName, JsonRpcMethodInterface $method): void { $this->mappingList[$methodName] = $method; } /** * @return JsonRpcMethodInterface[] */ public function getMappingList() : array { return $this->mappingList; } } 

рдФрд░ рд╕реЗрд╡рд╛рдУрдВ рдХреЗ рд▓рд┐рдП рд╕реЗрд╡рд╛ рдЬреЛрдбрд╝реЗрдВред


 # config/services.yaml services: ... mapping_aware_service: class: App\MappingCollector tags: ['json_rpc_http_server.method_aware'] ... 

рд╡рд┐рдзрд┐ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди


RPC JSON рд╡рд┐рдзрд┐рдпреЛрдВ рдХреЛ services.yaml рдлрд╝рд╛рдЗрд▓ рдореЗрдВ рдирд┐рдпрдорд┐рдд рд╕реЗрд╡рд╛рдУрдВ рдХреЗ рд░реВрдк рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЬрд╛рддрд╛ рд╣реИред рд╣рдо рдкрд╣рд▓реЗ рдкрд┐рдВрдЧ рд╡рд┐рдзрд┐ рдХреЛ рд╕реНрд╡рдпрдВ рд▓рд╛рдЧреВ рдХрд░рддреЗ рд╣реИрдВред


 // src/Method/PingMethod.php <?php namespace App\Method; use Yoanm\JsonRpcServer\Domain\JsonRpcMethodInterface; class PingMethod implements JsonRpcMethodInterface { public function apply(array $paramList = null) { return 'pong'; } } 

рдФрд░ рдПрдХ рд╕реЗрд╡рд╛ рдХреЗ рд░реВрдк рдореЗрдВ рдЬреЛрдбрд╝реЗрдВред


 # config/services.yaml services: ... App\Method\PingMethod: public: false tags: [{ method: 'ping', name: 'json_rpc_http_server.jsonrpc_method' }] ... 

рд╣рдо рдЕрдВрддрд░реНрдирд┐рд╣рд┐рдд рд╡реЗрдм рд╕рд░реНрд╡рд░ рд╕рд┐рдореНрдлрдиреА рд▓реЙрдиреНрдЪ рдХрд░рддреЗ рд╣реИрдВред


 $ symfony serve 

рд╣рдо рдПрдХ рдХреЙрд▓ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░ рд░рд╣реЗ рд╣реИрдВред


 $ curl 'http://127.0.0.1:8000/json-rpc' --data-binary '[{ "jsonrpc":"2.0","method":"ping","params":[],"id" : 1 }]' 

 [ { "jsonrpc": "2.0", "id": 1, "result": "pong" } ] 

рдЕрдм рд╣рдо рдЙрд╕ рдкрджреНрдзрддрд┐ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рддреЗ рд╣реИрдВ рдЬреЛ рдкреИрд░рд╛рдореАрдЯрд░ рдкреНрд░рд╛рдкреНрдд рдХрд░рддрд╛ рд╣реИред рд╣рдо рдЙрддреНрддрд░ рдХреЗ рд░реВрдк рдореЗрдВ рдЗрдирдкреБрдЯ рдбреЗрдЯрд╛ рд╡рд╛рдкрд╕ рдХрд░реЗрдВрдЧреЗред


 // src/Method/ParamsMethod.php <?php namespace App\Method; use Yoanm\JsonRpcServer\Domain\JsonRpcMethodInterface; class ParamsMethod implements JsonRpcMethodInterface { public function apply(array $paramList = null) { return $paramList; } } 

 # config/services.yaml services: ... App\Method\ParamsMethod: public: false tags: [{ method: 'params', name: 'json_rpc_http_server.jsonrpc_method' }] ... 

рдмреБрд▓рд╛рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХреА рдЬрд╛ рд░рд╣реА рд╣реИред


 $ curl 'http://127.0.0.1:8000/json-rpc' --data-binary '[{ "jsonrpc":"2.0","method":"params","params":{"name":"John","age":21},"id" : 1 }]' 

 [ { "jsonrpc": "2.0", "id": 1, "result": { "name": "John", "age": 21 } } ] 

рд╡рд┐рдзрд┐ рдЗрдирдкреБрдЯ рд╕рддреНрдпрд╛рдкрди


рдпрджрд┐ рд╡рд┐рдзрд┐ рдХреЗ рдЗрдирдкреБрдЯ рдкрд░ рдбреЗрдЯрд╛ рдХрд╛ рд╕реНрд╡рдд: рд╕рддреНрдпрд╛рдкрди рдЖрд╡рд╢реНрдпрдХ рд╣реИ, рддреЛ рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ рдкреИрдХреЗрдЬ yoanm / symfony-jsonrpc-params-validator рд╣реИ ред


 $ composer require yoanm/symfony-jsonrpc-params-validator 

рдмрдВрдбрд▓ рдХрдиреЗрдХреНрдЯ рдХрд░реЗрдВред


 // config/bundles.php return [ ... Yoanm\SymfonyJsonRpcParamsValidator\JsonRpcParamsValidatorBundle::class => ['all' => true], ... ]; 

рдЗрдирдкреБрдЯ рд╕рддреНрдпрд╛рдкрд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╡рд╛рд▓реЗ рддрд░реАрдХреЛрдВ рдХреЛ Yoanm \ JsonRpcParamsSymfonyValidator \ Domain \ MethodWithValidatedParamsInterface рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рд▓рд╛рдЧреВ рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред рдЪрд▓рд┐рдП ParamsMethod рд╡рд░реНрдЧ рдХреЛ рдереЛрдбрд╝рд╛ рд╕рдВрд╢реЛрдзрд┐рдд рдХрд░рддреЗ рд╣реИрдВред


 // src/Method/ParamsMethod.php <?php namespace App\Method; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Optional; use Symfony\Component\Validator\Constraints\Positive; use Symfony\Component\Validator\Constraints\Required; use Yoanm\JsonRpcParamsSymfonyValidator\Domain\MethodWithValidatedParamsInterface; use Yoanm\JsonRpcServer\Domain\JsonRpcMethodInterface; class ParamsMethod implements JsonRpcMethodInterface, MethodWithValidatedParamsInterface { public function apply(array $paramList = null) { return $paramList; } public function getParamsConstraint() : Constraint { return new Collection(['fields' => [ 'name' => new Required([ new Length(['min' => 1, 'max' => 32]) ]), 'age' => new Required([ new Positive() ]), 'sex' => new Optional([ new Choice(['f', 'm']) ]), ]]); } } 

рдЕрдм рдпрджрд┐ рд╣рдо рд░рд┐рдХреНрдд рдкреИрд░рд╛рдореАрдЯрд░ рдХреЗ рд╕рд╛рде рдпрд╛ рддреНрд░реБрдЯрд┐рдпреЛрдВ рдХреЗ рд╕рд╛рде рдЕрдиреБрд░реЛрдз рдХреЛ рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рд╣рдореЗрдВ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдореЗрдВ рд╕рдВрдмрдВрдзрд┐рдд рддреНрд░реБрдЯрд┐рдпрд╛рдВ рдкреНрд░рд╛рдкреНрдд рд╣реЛрдВрдЧреАред


 $ curl 'http://127.0.0.1:8000/json-rpc' --data-binary '[{"jsonrpc":"2.0","method":"params","params":[],"id" : 1 }]' 

 [ { "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Invalid params", "data": { "violations": [ { "path": "[name]", "message": "This field is missing.", "code": "2fa2158c-2a7f-484b-98aa-975522539ff8" }, { "path": "[age]", "message": "This field is missing.", "code": "2fa2158c-2a7f-484b-98aa-975522539ff8" } ] } } } ] 

 $ curl 'http://127.0.0.1:8000/json-rpc' --data-binary '[{"jsonrpc":"2.0","method":"params","params":{"name":"John","age":-1},"id" : 1 }]' 

 [ { "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Invalid params", "data": { "violations": [ { "path": "[age]", "message": "This value should be positive.", "code": "778b7ae0-84d3-481a-9dec-35fdb64b1d78" } ] } } } 

 $ curl 'http://127.0.0.1:8000/json-rpc' --data-binary '[{ "jsonrpc":"2.0","method":"params","params":{"name":"John","age":21,"sex":"u"},"id" : 1 }]' 

 [ { "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Invalid params", "data": { "violations": [ { "path": "[sex]", "message": "The value you selected is not a valid choice.", "code": "8e179f1b-97aa-4560-a02f-2a8b42e49df7" } ] } } } ] 

рдСрдЯреЛ рдкреНрд░рд▓реЗрдЦрди


рдПрдХ рдЕрддрд┐рд░рд┐рдХреНрдд рдкреИрдХреЗрдЬ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВред


 composer require yoanm/symfony-jsonrpc-http-server-doc 

рд╣рдо рдмрдВрдбрд▓ рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░рддреЗ рд╣реИрдВред


 // config/bundles.php return [ ... Yoanm\SymfonyJsonRpcHttpServerDoc\JsonRpcHttpServerDocBundle::class => ['all' => true], ... ]; 

 # config/routes.yaml ... json-rpc-endpoint-doc: resource: '@JsonRpcHttpServerDocBundle/Resources/config/routing/endpoint.xml' 

 # config/packages/json_rpc.yaml ... json_rpc_http_server_doc: ~ 

рдЕрдм рдЖрдк JSON рдкреНрд░рд╛рд░реВрдк рдореЗрдВ рдкреНрд░рд▓реЗрдЦрди рдкреНрд░рд╛рдкреНрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред


 $ curl 'http://127.0.0.1:8000/doc' 

рдЬрд╡рд╛рдм рд╣реИ
 { "methods": [ { "identifier": "Params", "name": "params" }, { "identifier": "Ping", "name": "ping" } ], "errors": [ { "id": "ParseError-32700", "title": "Parse error", "type": "object", "properties": { "code": -32700 } }, { "id": "InvalidRequest-32600", "title": "Invalid request", "type": "object", "properties": { "code": -32600 } }, { "id": "MethodNotFound-32601", "title": "Method not found", "type": "object", "properties": { "code": -32601 } }, { "id": "ParamsValidationsError-32602", "title": "Params validations error", "type": "object", "properties": { "code": -32602, "data": { "type": "object", "nullable": true, "required": true, "siblings": { "violations": { "type": "array", "nullable": true, "required": false } } } } }, { "id": "InternalError-32603", "title": "Internal error", "type": "object", "properties": { "code": -32603, "data": { "type": "object", "nullable": true, "required": false, "siblings": { "previous": { "description": "Previous error message", "type": "string", "nullable": true, "required": false } } } } } ], "http": { "host": "127.0.0.1:8000" } } 

рд▓реЗрдХрд┐рди рдРрд╕рд╛ рдХреИрд╕реЗ? рдФрд░ рдЗрдирдкреБрдЯ рдорд╛рдкрджрдВрдбреЛрдВ рдХрд╛ рд╡рд░реНрдгрди рдХрд╣рд╛рдВ рд╣реИ? рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдПрдХ рдФрд░ рдмрдВрдбрд▓ yoanm / symfony-jsonrpc-params-sf-constraints-doc рд▓рдЧрд╛рдПрдВ ред


 $ composer require yoanm/symfony-jsonrpc-params-sf-constraints-doc 

 // config/bundles.php return [ ... Yoanm\SymfonyJsonRpcParamsSfConstraintsDoc\JsonRpcParamsSfConstraintsDocBundle::class => ['all' => true], ... ]; 

рдЕрдм рдпрджрд┐ рд╣рдо рдПрдХ рдЕрдиреБрд░реЛрдз рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рд╣рдореЗрдВ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдкреИрд░рд╛рдореАрдЯрд░ рдХреЗ рд╕рд╛рде JSON рд╡рд┐рдзрд┐рдпрд╛рдВ рдорд┐рд▓реЗрдВрдЧреАред


 $ curl 'http://127.0.0.1:8000/doc' 

рдЬрд╡рд╛рдм рд╣реИ
 { "methods": [ { "identifier": "Params", "name": "params", "params": { "type": "object", "nullable": false, "required": true, "siblings": { "name": { "type": "string", "nullable": true, "required": true, "minLength": 1, "maxLength": 32 }, "age": { "type": "string", "nullable": true, "required": true }, "sex": { "type": "string", "nullable": true, "required": false, "allowedValues": [ "f", "m" ] } } } }, { "identifier": "Ping", "name": "ping" } ], "errors": [ { "id": "ParseError-32700", "title": "Parse error", "type": "object", "properties": { "code": -32700 } }, { "id": "InvalidRequest-32600", "title": "Invalid request", "type": "object", "properties": { "code": -32600 } }, { "id": "MethodNotFound-32601", "title": "Method not found", "type": "object", "properties": { "code": -32601 } }, { "id": "ParamsValidationsError-32602", "title": "Params validations error", "type": "object", "properties": { "code": -32602, "data": { "type": "object", "nullable": true, "required": true, "siblings": { "violations": { "type": "array", "nullable": true, "required": false, "item_validation": { "type": "object", "nullable": true, "required": true, "siblings": { "path": { "type": "string", "nullable": true, "required": true, "example": "[key]" }, "message": { "type": "string", "nullable": true, "required": true }, "code": { "type": "string", "nullable": true, "required": false } } } } } } } }, { "id": "InternalError-32603", "title": "Internal error", "type": "object", "properties": { "code": -32603, "data": { "type": "object", "nullable": true, "required": false, "siblings": { "previous": { "description": "Previous error message", "type": "string", "nullable": true, "required": false } } } } } ], "http": { "host": "127.0.0.1:8000" } } 

рдУрдкрд╛рдкреА рей


JSON рджрд╕реНрддрд╛рд╡реЗрдЬрд╝реАрдХрд░рдг OpenAPI 3 рдорд╛рдирдХ рдХреЗ рд╕рд╛рде рд╕рдВрдЧрдд рд╣реЛрдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЛ yoanm / symfony-jsonrpc-http-server-openapi-doc рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдирд╛ рд╣реЛрдЧрд╛ ред


 $ composer require yoanm/symfony-jsonrpc-http-server-openapi-doc 

рдЕрдиреБрдХреВрд▓рдиред


 // config/bundles.php return [ ... Yoanm\SymfonyJsonRpcHttpServerOpenAPIDoc\JsonRpcHttpServerOpenAPIDocBundle::class => ['all' => true], ... ]; 

рдПрдХ рдирдпрд╛ рдЕрдиреБрд░реЛрдз рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рд╣рдо OpenApi 3 рдкреНрд░рд╛рд░реВрдк рдореЗрдВ JSON рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВрдЧреЗред


 $ curl 'http://127.0.0.1:8000/doc/openapi.json' 

рдЬрд╡рд╛рдм рд╣реИ
 { "openapi": "3.0.0", "servers": [ { "url": "http:\/\/127.0.0.1:8000" } ], "paths": { "\/Params\/..\/json-rpc": { "post": { "summary": "\"params\" json-rpc method", "operationId": "Params", "requestBody": { "required": true, "content": { "application\/json": { "schema": { "allOf": [ { "type": "object", "required": [ "jsonrpc", "method" ], "properties": { "id": { "example": "req_id", "oneOf": [ { "type": "string" }, { "type": "number" } ] }, "jsonrpc": { "type": "string", "example": "2.0" }, "method": { "type": "string" }, "params": { "title": "Method parameters" } } }, { "type": "object", "required": [ "params" ], "properties": { "params": { "$ref": "#\/components\/schemas\/Method-Params-RequestParams" } } }, { "type": "object", "properties": { "method": { "example": "params" } } } ] } } } }, "responses": { "200": { "description": "JSON-RPC response", "content": { "application\/json": { "schema": { "allOf": [ { "type": "object", "required": [ "jsonrpc" ], "properties": { "id": { "example": "req_id", "oneOf": [ { "type": "string" }, { "type": "number" } ] }, "jsonrpc": { "type": "string", "example": "2.0" }, "result": { "title": "Result" }, "error": { "title": "Error" } } }, { "type": "object", "properties": { "result": { "description": "Method result" } } }, { "type": "object", "properties": { "error": { "oneOf": [ { "$ref": "#\/components\/schemas\/ServerError-ParseError-32700" }, { "$ref": "#\/components\/schemas\/ServerError-InvalidRequest-32600" }, { "$ref": "#\/components\/schemas\/ServerError-MethodNotFound-32601" }, { "$ref": "#\/components\/schemas\/ServerError-ParamsValidationsError-32602" }, { "$ref": "#\/components\/schemas\/ServerError-InternalError-32603" } ] } } } ] } } } } } } }, "\/Ping\/..\/json-rpc": { "post": { "summary": "\"ping\" json-rpc method", "operationId": "Ping", "requestBody": { "required": true, "content": { "application\/json": { "schema": { "allOf": [ { "type": "object", "required": [ "jsonrpc", "method" ], "properties": { "id": { "example": "req_id", "oneOf": [ { "type": "string" }, { "type": "number" } ] }, "jsonrpc": { "type": "string", "example": "2.0" }, "method": { "type": "string" }, "params": { "title": "Method parameters" } } }, { "type": "object", "properties": { "method": { "example": "ping" } } } ] } } } }, "responses": { "200": { "description": "JSON-RPC response", "content": { "application\/json": { "schema": { "allOf": [ { "type": "object", "required": [ "jsonrpc" ], "properties": { "id": { "example": "req_id", "oneOf": [ { "type": "string" }, { "type": "number" } ] }, "jsonrpc": { "type": "string", "example": "2.0" }, "result": { "title": "Result" }, "error": { "title": "Error" } } }, { "type": "object", "properties": { "result": { "description": "Method result" } } }, { "type": "object", "properties": { "error": { "oneOf": [ { "$ref": "#\/components\/schemas\/ServerError-ParseError-32700" }, { "$ref": "#\/components\/schemas\/ServerError-InvalidRequest-32600" }, { "$ref": "#\/components\/schemas\/ServerError-MethodNotFound-32601" }, { "$ref": "#\/components\/schemas\/ServerError-ParamsValidationsError-32602" }, { "$ref": "#\/components\/schemas\/ServerError-InternalError-32603" } ] } } } ] } } } } } } } }, "components": { "schemas": { "Method-Params-RequestParams": { "type": "object", "nullable": false, "required": [ "name", "age" ], "properties": { "name": { "type": "string", "nullable": true, "minLength": 1, "maxLength": 32 }, "age": { "type": "string", "nullable": true }, "sex": { "type": "string", "nullable": true, "enum": [ "f", "m" ] } } }, "ServerError-ParseError-32700": { "title": "Parse error", "allOf": [ { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "number" }, "message": { "type": "string" } } }, { "type": "object", "required": [ "code" ], "properties": { "code": { "example": -32700 } } } ] }, "ServerError-InvalidRequest-32600": { "title": "Invalid request", "allOf": [ { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "number" }, "message": { "type": "string" } } }, { "type": "object", "required": [ "code" ], "properties": { "code": { "example": -32600 } } } ] }, "ServerError-MethodNotFound-32601": { "title": "Method not found", "allOf": [ { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "number" }, "message": { "type": "string" } } }, { "type": "object", "required": [ "code" ], "properties": { "code": { "example": -32601 } } } ] }, "ServerError-ParamsValidationsError-32602": { "title": "Params validations error", "allOf": [ { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "number" }, "message": { "type": "string" } } }, { "type": "object", "required": [ "code", "data" ], "properties": { "code": { "example": -32602 }, "data": { "type": "object", "nullable": true, "properties": { "violations": { "type": "array", "nullable": true, "items": { "type": "object", "nullable": true, "required": [ "path", "message" ], "properties": { "path": { "type": "string", "nullable": true, "example": "[key]" }, "message": { "type": "string", "nullable": true }, "code": { "type": "string", "nullable": true } } } } } } } } ] }, "ServerError-InternalError-32603": { "title": "Internal error", "allOf": [ { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "number" }, "message": { "type": "string" } } }, { "type": "object", "required": [ "code" ], "properties": { "code": { "example": -32603 }, "data": { "type": "object", "nullable": true, "properties": { "previous": { "description": "Previous error message", "type": "string", "nullable": true } } } } } ] } } } } 

рд╡рд┐рдзрд┐ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдкреНрд░рд▓реЗрдЦрди


рдХреЛрдИ рдкреВрд░реНрдгрдХрд╛рд▓рд┐рдХ рдХрд╛рд░реНрдпрд╛рддреНрдордХ рдирд╣реАрдВ рд╣реИ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдПрдХ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рд▓рд╛рдЧреВ рдХрд░рдХреЗ) рдЬреЛ рдЖрдкрдХреЛ рдкреНрд░рд▓реЗрдЦрди рдХреЗ рд▓рд┐рдП рд╡рд┐рдзрд┐ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛рдУрдВ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред рд▓реЗрдХрд┐рди рд╕рдВрднрд╛рд╡рдирд╛ рд╣реИ, рдШрдЯрдирд╛рдУрдВ рдХреА рд╕рджрд╕реНрдпрддрд╛ рд▓реЗрдХрд░, рдЖрд╡рд╢реНрдпрдХ рдЬрд╛рдирдХрд╛рд░реА рд╕реНрд╡рдпрдВ рдЬреЛрдбрд╝реЗрдВред


рдПрдХ рд╢реНрд░реЛрддрд╛ рдЬреЛрдбрд╝реЗрдВред


 # config/services.yaml services: ... App\Listener\MethodDocListener: tags: - name: 'kernel.event_listener' event: 'json_rpc_http_server_doc.method_doc_created' method: 'enhanceMethodDoc' - name: 'kernel.event_listener' event: 'json_rpc_http_server_openapi_doc.array_created' method: 'enhanceDoc' ... 

 // src/Listener/MethodDocListener.php <?php namespace App\Listener; use App\Domain\JsonRpcMethodWithDocInterface; use Yoanm\JsonRpcServerDoc\Domain\Model\ErrorDoc; use Yoanm\SymfonyJsonRpcHttpServerDoc\Event\MethodDocCreatedEvent; use Yoanm\SymfonyJsonRpcHttpServerOpenAPIDoc\Event\OpenAPIDocCreatedEvent; class MethodDocListener { public function enhanceMethodDoc(MethodDocCreatedEvent $event) : void { $method = $event->getMethod(); if ($method instanceof JsonRpcMethodWithDocInterface) { $doc = $event->getDoc(); $doc->setResultDoc($method->getDocResponse()); foreach ($method->getDocErrors() as $error) { if ($error instanceof ErrorDoc) { $doc->addCustomError($error); } } $doc->setDescription($method->getDocDescription()); $doc->addTag($method->getDocTag()); } } public function enhanceDoc(OpenAPIDocCreatedEvent $event) { $doc = $event->getOpenAPIDoc(); $doc['info'] = [ 'title' => 'Main title', 'version' => '1.0.0', 'description' => 'Main description' ]; $event->setOpenAPIDoc($doc); } } 

рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рд╕реАрдзреЗ рд╢реНрд░реЛрддрд╛ рдореЗрдВ рддрд░реАрдХреЛрдВ рдХреЗ рдкреНрд░рд▓реЗрдЦрди рдХрд╛ рд╡рд░реНрдгрди рдирд╣реАрдВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдПрдХ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдмрдирд╛рдПрдВрдЧреЗ рдЬреЛ рд╡рд┐рдзрд┐рдпреЛрдВ рдХреЛ рд╕реНрд╡рдпрдВ рд▓рд╛рдЧреВ рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред


 // src/Domain/JsonRpcMethodWithDocInterface.php <?php namespace App\Domain; use Yoanm\JsonRpcServerDoc\Domain\Model\ErrorDoc; use Yoanm\JsonRpcServerDoc\Domain\Model\Type\TypeDoc; interface JsonRpcMethodWithDocInterface { /** * @return TypeDoc */ public function getDocResponse(): TypeDoc; /** * @return ErrorDoc[] */ public function getDocErrors(): array; /** * @return string */ public function getDocDescription(): string; /** * @return string */ public function getDocTag(): string; } 

рдЕрдм рдПрдХ рдирдпрд╛ рддрд░реАрдХрд╛ рдЬреЛрдбрд╝реЗрдВ рдЬрд┐рд╕рдореЗрдВ рдЖрд╡рд╢реНрдпрдХ рдЬрд╛рдирдХрд╛рд░реА рд╣реЛрдЧреАред


 // src/Method/UserMethod.php <?php namespace App\Method; use App\Domain\JsonRpcMethodWithDocInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Optional; use Symfony\Component\Validator\Constraints\Positive; use Symfony\Component\Validator\Constraints\Required; use Yoanm\JsonRpcParamsSymfonyValidator\Domain\MethodWithValidatedParamsInterface; use Yoanm\JsonRpcServer\Domain\JsonRpcMethodInterface; use Yoanm\JsonRpcServerDoc\Domain\Model\ErrorDoc; use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ArrayDoc; use Yoanm\JsonRpcServerDoc\Domain\Model\Type\NumberDoc; use Yoanm\JsonRpcServerDoc\Domain\Model\Type\ObjectDoc; use Yoanm\JsonRpcServerDoc\Domain\Model\Type\StringDoc; use Yoanm\JsonRpcServerDoc\Domain\Model\Type\TypeDoc; class UserMethod implements JsonRpcMethodInterface, MethodWithValidatedParamsInterface, JsonRpcMethodWithDocInterface { public function apply(array $paramList = null) { return [ 'name' => $paramList['name'], 'age' => $paramList['age'], 'sex' => $paramList['sex'] ?? null, ]; } public function getParamsConstraint() : Constraint { return new Collection(['fields' => [ 'name' => new Required([ new Length(['min' => 1, 'max' => 32]) ]), 'age' => new Required([ new Positive() ]), 'sex' => new Optional([ new Choice(['f', 'm']) ]), ]]); } public function getDocDescription(): string { return 'User method'; } public function getDocTag(): string { return 'main'; } public function getDocErrors(): array { return [new ErrorDoc('Error 1', 1)]; } public function getDocResponse(): TypeDoc { $response = new ObjectDoc(); $response->setNullable(false); $response->addSibling((new StringDoc()) ->setNullable(false) ->setDescription('Name of user') ->setName('name') ); $response->addSibling((new NumberDoc()) ->setNullable(false) ->setDescription('Age of user') ->setName('age') ); $response->addSibling((new StringDoc()) ->setNullable(true) ->setDescription('Sex of user') ->setName('sex') ); return $response; } } 

рдПрдХ рдирдИ рд╕реЗрд╡рд╛ рдХреЛ рдкрдВрдЬреАрдХреГрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдордд рднреВрд▓рдирд╛ред


 services: ... App\Method\UserMethod: public: false tags: [{ method: 'user', name: 'json_rpc_http_server.jsonrpc_method' }] ... 

рдЕрдм, /doc/openapi.json рдХреЗ рд▓рд┐рдП рдПрдХ рдирдпрд╛ рдЕрдиреБрд░реЛрдз рдХрд░рддреЗ рд╣реБрдП , рд╣рдореЗрдВ рдирдпрд╛ рдбреЗрдЯрд╛ рдорд┐рд▓рддрд╛ рд╣реИред


 curl 'http://127.0.0.1:8000/doc/openapi.json' 

рдЬрд╡рд╛рдм рд╣реИ
 { "openapi": "3.0.0", "servers": [ { "url": "http:\/\/127.0.0.1:8000" } ], "paths": { ... "\/User\/..\/json-rpc": { "post": { "summary": "\"user\" json-rpc method", "description": "User method", "tags": [ "main" ], ... "responses": { "200": { "description": "JSON-RPC response", "content": { "application\/json": { "schema": { "allOf": [ ... { "type": "object", "properties": { "result": { "$ref": "#\/components\/schemas\/Method-User-Result" } } }, { "type": "object", "properties": { "error": { "oneOf": [ { "$ref": "#\/components\/schemas\/Error-Error11" }, ... ] } } } ] } } } } } } } }, "components": { "schemas": { ... "Method-User-Result": { "type": "object", "nullable": false, "properties": { "name": { "description": "Name of user", "type": "string", "nullable": false }, "age": { "description": "Age of user", "type": "number", "nullable": false }, "sex": { "description": "Sex of user", "type": "string", "nullable": true } } }, "Error-Error11": { "title": "Error 1", "allOf": [ { "type": "object", "required": [ "code", "message" ], "properties": { "code": { "type": "number" }, "message": { "type": "string" } } }, { "type": "object", "required": [ "code" ], "properties": { "code": { "example": 1 } } } ] }, ... } }, "info": { "title": "Main title", "version": "1.0.0", "description": "Main description" } } 

JSON рдкреНрд░рд▓реЗрдЦрди рдХрд╛ рджреГрд╢реНрдп


JSON рд╢рд╛рдВрдд рд╣реИ, рд▓реЗрдХрд┐рди рд▓реЛрдЧ рдЖрдорддреМрд░ рдкрд░ рдЕрдзрд┐рдХ рдорд╛рдирд╡реАрдп рдкрд░рд┐рдгрд╛рдо рджреЗрдЦрдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВред /Doc/openapi.json рдлрд╝рд╛рдЗрд▓ рдмрд╛рд╣рд░реА рджреГрд╢реНрдп рд╕реЗрд╡рд╛рдУрдВ рдЬреИрд╕реЗ рдХрд┐ рд╕реНрд╡реИрдЧрд░ рд╕рдВрдкрд╛рджрдХ рдХреЛ рджреА рдЬрд╛ рд╕рдХрддреА рд╣реИред



рдпрджрд┐ рд╡рд╛рдВрдЫрд┐рдд рд╣реИ, рддреЛ рдЖрдк рд╣рдорд╛рд░реА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдореЗрдВ рд╕реНрд╡реИрдЧрд░ рдпреВрдЖрдИ рд╕реНрдерд╛рдкрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рд╣рдо harmbandstra / swagger-ui-рдмрдВрдбрд▓ рдкреИрдХреЗрдЬ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗред


рд╕рдВрд╕рд╛рдзрдиреЛрдВ рдХреЗ рд╕рд╣реА рдкреНрд░рдХрд╛рд╢рди рдХреЗ рд▓рд┐рдП, рд╣рдо рдХрдВрдкреЛрдЬрд╝рд░ рдХреЗ рд╕рд╛рде рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдЬреЛрдбрд╝рддреЗ рд╣реИрдВред jsonред


  "scripts": { "auto-scripts": { "cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd" }, "post-install-cmd": [ "HarmBandstra\\SwaggerUiBundle\\Composer\\ScriptHandler::linkAssets", "@auto-scripts" ], "post-update-cmd": [ "HarmBandstra\\SwaggerUiBundle\\Composer\\ScriptHandler::linkAssets", "@auto-scripts" ] }, 

рд╣рдо рдкреИрдХреЗрдЬ рдбрд╛рд▓ рдХреЗ рдмрд╛рджред


 $ composer require harmbandstra/swagger-ui-bundle 

рдмрдВрдбрд▓ рдХрдиреЗрдХреНрдЯ рдХрд░реЗрдВред


 // config/bundles.php <?php return [ // ... HarmBandstra\SwaggerUiBundle\HBSwaggerUiBundle::class => ['dev' => true] ]; 

 # config/routes.yaml _swagger-ui: resource: '@HBSwaggerUiBundle/Resources/config/routing.yml' prefix: /docs 

 # config/packages/hb_swagger_ui.yaml hb_swagger_ui: directory: "http://127.0.0.1:8000" files: - "/doc/openapi.json" 

рдЕрдм http://127.0.0.1:8000/docs/ рдХреЗ рд▓рд┐рдВрдХ рдХрд╛ рдЕрдиреБрд╕рд░рдг рдХрд░рддреЗ рд╣реБрдП , рд╣рдо рдПрдХ рд╕реБрдВрджрд░ рд░реВрдк рдореЗрдВ рдкреНрд░рд▓реЗрдЦрди рдкреНрд░рд╛рдкреНрдд рдХрд░рддреЗ рд╣реИрдВред



рдкрд░рд┐рдгрд╛рдо


рд╕рднреА рдЬреЛрдбрд╝рддреЛрдбрд╝реЛрдВ рдХреЗ рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк рд╣рдореЗрдВ рдПрдХ рдХрд╛рдо рдХрд░рдиреЗ рд╡рд╛рд▓рд╛ JSON RPC рд╕рд┐рдореНрдлрдиреА 4 рдкрд░ рдЖрдзрд╛рд░рд┐рдд рдерд╛ рдФрд░ рд╕реНрд╡реИрдЧрд░ UI рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реБрдП рд╡рд┐рдЬрд╝реБрдЕрд▓рд╛рдЗрдЬрд╝реЗрд╢рди рдХреЗ рд╕рд╛рде OpenAPI рдХреЗ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рдкреНрд░рд▓реЗрдЦрдиред


рдЖрдк рд╕рднреА рдХрд╛ рдзрдиреНрдпрд╡рд╛рджред

Source: https://habr.com/ru/post/hi457750/


All Articles