Skip to content

Commit c953be8

Browse files
committed
Improve parsing
1 parent 593555e commit c953be8

File tree

4 files changed

+256
-29
lines changed

4 files changed

+256
-29
lines changed

README.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,49 @@ This yocLibrary enables your project to encode and decode JSON-RPC messages in P
1515
### Serialization
1616

1717
```php
18+
use YOCLIB\JSONRPC\JSONRPCException;
19+
use YOCLIB\JSONRPC\Message;
1820

21+
$message = Message::createRequestMessageV1(123,'getInfo',['payments']); // Create request (version 1.0)
22+
$message = Message::createNotificationMessageV1('notificationEvent',['payed']); // Create notification (version 1.0)
23+
$message = Message::createResponseMessageV1(123,['payments'=>[]]); // Create response (version 1.0)
24+
25+
$object = $message->toObject();
26+
27+
try{
28+
$json = Message::encodeJSON($object);
29+
}catch(JSONRPCException $e){
30+
//Handle encoding exception
31+
}
1932
```
2033

2134
### Deserialization
2235

2336
```php
24-
37+
use YOCLIB\JSONRPC\JSONRPCException;
38+
use YOCLIB\JSONRPC\Message;
39+
40+
$json = file_get_contents('php://input'); // Get request body
41+
42+
try{
43+
$object = Message::decodeJSON($json);
44+
}catch(JSONRPCException $e){
45+
//Handle decoding exception
46+
}
47+
48+
if(Message::isBatch($object)){
49+
foreach($object AS $element){
50+
try{
51+
$message = Message::parse($element);
52+
}catch(JSONRPCException $e){
53+
//Handle message exception
54+
}
55+
}
56+
}else{
57+
try{
58+
$message = Message::parse($object);
59+
}catch(JSONRPCException $e){
60+
//Handle message exception
61+
}
62+
}
2563
```

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"ext-json": "*"
1919
},
2020
"require-dev": {
21-
"phpunit/phpunit": "^7||^8||^9"
21+
"phpunit/phpunit": "^7||^8||^9",
22+
"phpunit/php-code-coverage": "^9.2"
2223
},
2324
"autoload": {
2425
"psr-4": {
@@ -33,4 +34,4 @@
3334
"scripts": {
3435
"test": "phpunit"
3536
}
36-
}
37+
}

src/Message.php

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ private function __construct(object $value){
1515
}
1616

1717
/**
18-
* @return string
18+
* @return object
1919
*/
20-
public function toJSON(): string{
21-
return json_encode($this->value);
20+
public function toObject(): object{
21+
return $this->value;
2222
}
2323

2424
/**
@@ -70,26 +70,51 @@ public static function createResponseMessageV1($id,$result=null,$error=null): Re
7070
]);
7171
}
7272

73+
/**
74+
* @param $object
75+
* @return bool
76+
*/
77+
public static function isBatch($object): bool{
78+
return is_array($object);
79+
}
80+
81+
/**
82+
* @param $object
83+
* @return false|string
84+
* @throws JSONRPCException
85+
*/
86+
public static function encodeJSON($object){
87+
try{
88+
return json_encode($object,JSON_THROW_ON_ERROR);
89+
}catch(JsonException $e){
90+
throw new JSONRPCException('Failed to encode JSON.');
91+
}
92+
}
93+
7394
/**
7495
* @param string $json
75-
* @param bool $strictId
76-
* @return Message[]|array|Message
96+
* @return mixed
7797
* @throws JSONRPCException
7898
*/
79-
public static function parse(string $json,bool $strictId=true){
99+
public static function decodeJSON(string $json){
80100
try{
81-
$message = json_decode($json,false,512,JSON_THROW_ON_ERROR);
101+
return json_decode($json,false,512,JSON_THROW_ON_ERROR);
82102
}catch(JsonException $e){
83-
throw new JSONRPCException('[V1] Failed to decode JSON.');
103+
throw new JSONRPCException('Failed to decode JSON.');
84104
}
85-
if(is_array($message)){
86-
$messages = [];
87-
foreach($message AS $msg){
88-
$messages[] = self::handleMessage($msg,$strictId);
89-
}
90-
return $messages;
105+
}
106+
107+
/**
108+
* @param $object
109+
* @param bool $strictId
110+
* @return Message
111+
* @throws JSONRPCException
112+
*/
113+
public static function parseObject($object,bool $strictId=true){
114+
if(is_object($object)){
115+
return self::handleMessage($object,$strictId);
91116
}
92-
return self::handleMessage($message,$strictId);
117+
throw new JSONRPCException('A message MUST be a JSON object.');
93118
}
94119

95120
/**
@@ -99,23 +124,38 @@ public static function parse(string $json,bool $strictId=true){
99124
* @throws JSONRPCException
100125
*/
101126
private static function handleMessage($message,bool $strictId=true){
102-
if(isset($message['jsonrpc']) && $message['jsonrpc']==='2.0'){
103-
return self::handleMessageV2($message,$strictId);
127+
if(property_exists($message,'jsonrpc')){
128+
if($message->jsonrpc==='2.0'){
129+
return self::handleMessageV2($message,$strictId);
130+
}
131+
throw new JSONRPCException('Unknown version "'.($message->jsonrpc).'".');
104132
}else{
105133
return self::handleMessageV1($message,$strictId);
106134
}
107135
}
108136

137+
/**
138+
* @param $message
139+
* @param bool $strictId
140+
* @return null
141+
* @throws JSONRPCException
142+
*/
109143
private static function handleMessageV2($message,bool $strictId=true){
110-
return null;
144+
if(self::isRequest($message)){
145+
return null;
146+
}elseif(self::isResponse($message)){
147+
return null;
148+
}else{
149+
throw new JSONRPCException('[V2] Unknown message type.');
150+
}
111151
}
112152

113153

114-
private static function isRequestV1($message): bool{
154+
private static function isRequest($message): bool{
115155
return property_exists($message,'method') || property_exists($message,'params');
116156
}
117157

118-
private static function isResponseV1($message): bool{
158+
private static function isResponse($message): bool{
119159
return property_exists($message,'result') || property_exists($message,'error');
120160
}
121161

@@ -179,7 +219,7 @@ private static function validateErrorPropertyV1($message){
179219
* @throws JSONRPCException
180220
*/
181221
private static function handleMessageV1($message,bool $strictId=true){
182-
if(self::isRequestV1($message)){
222+
if(self::isRequest($message)){
183223
self::validateMethodPropertyV1($message);
184224
self::validateParamsPropertyV1($message);
185225

@@ -188,7 +228,7 @@ private static function handleMessageV1($message,bool $strictId=true){
188228
}else{
189229
return new NotificationMessage($message);
190230
}
191-
}elseif(self::isResponseV1($message)){
231+
}elseif(self::isResponse($message)){
192232
self::validateResultPropertyV1($message);
193233
self::validateErrorPropertyV1($message);
194234
if(!is_null($message->result) && !is_null($message->error)){

tests/MessageTest.php

Lines changed: 152 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,163 @@
88

99
class MessageTest extends TestCase{
1010

11+
public function testDecodeEmptyJSON(){
12+
$this->expectException(JSONRPCException::class);
13+
$this->expectExceptionMessage('Failed to decode JSON.');
14+
15+
Message::decodeJSON('');
16+
}
17+
18+
/**
19+
* @return void
20+
* @throws JSONRPCException
21+
*/
22+
public function testDecodeJSONString(){
23+
$this->assertEquals('abc',Message::decodeJSON('"abc"'));
24+
}
25+
26+
/**
27+
* @return void
28+
* @throws JSONRPCException
29+
*/
30+
public function testDecodeJSONObject(){
31+
$this->assertEquals((object) [],Message::decodeJSON('{}'));
32+
}
33+
34+
/**
35+
* @return void
36+
* @throws JSONRPCException
37+
*/
38+
public function testDecodeJSONArray(){
39+
$this->assertEquals([],Message::decodeJSON('[]'));
40+
}
41+
42+
public function testIsBatch(){
43+
$this->assertTrue(Message::isBatch([]));
44+
45+
$this->assertFalse(Message::isBatch('abc'));
46+
$this->assertFalse(Message::isBatch(true));
47+
$this->assertFalse(Message::isBatch(false));
48+
$this->assertFalse(Message::isBatch(123));
49+
$this->assertFalse(Message::isBatch(123.456));
50+
$this->assertFalse(Message::isBatch((object) []));
51+
$this->assertFalse(Message::isBatch(null));
52+
}
53+
54+
/**
55+
* @return void
56+
* @throws JSONRPCException
57+
*/
58+
public function testParseObjectString(){
59+
$this->expectException(JSONRPCException::class);
60+
$this->expectExceptionMessage('A message MUST be a JSON object.');
61+
62+
Message::parseObject('abc');
63+
}
64+
65+
/**
66+
* @return void
67+
* @throws JSONRPCException
68+
*/
69+
public function testParseObjectTrue(){
70+
$this->expectException(JSONRPCException::class);
71+
$this->expectExceptionMessage('A message MUST be a JSON object.');
72+
73+
Message::parseObject(true);
74+
}
75+
76+
/**
77+
* @return void
78+
* @throws JSONRPCException
79+
*/
80+
public function testParseObjectFalse(){
81+
$this->expectException(JSONRPCException::class);
82+
$this->expectExceptionMessage('A message MUST be a JSON object.');
83+
84+
Message::parseObject(false);
85+
}
86+
87+
/**
88+
* @return void
89+
* @throws JSONRPCException
90+
*/
91+
public function testParseObjectInteger(){
92+
$this->expectException(JSONRPCException::class);
93+
$this->expectExceptionMessage('A message MUST be a JSON object.');
94+
95+
Message::parseObject(123);
96+
}
97+
98+
/**
99+
* @return void
100+
* @throws JSONRPCException
101+
*/
102+
public function testParseObjectFloat(){
103+
$this->expectException(JSONRPCException::class);
104+
$this->expectExceptionMessage('A message MUST be a JSON object.');
105+
106+
Message::parseObject(123.456);
107+
}
108+
109+
/**
110+
* @return void
111+
* @throws JSONRPCException
112+
*/
113+
public function testParseObjectArray(){
114+
$this->expectException(JSONRPCException::class);
115+
$this->expectExceptionMessage('A message MUST be a JSON object.');
116+
117+
Message::parseObject([]);
118+
}
119+
120+
/**
121+
* @return void
122+
* @throws JSONRPCException
123+
*/
124+
public function testParseEmptyObject(){
125+
$this->expectException(JSONRPCException::class);
126+
$this->expectExceptionMessage('[V1] Unknown message type.');
127+
128+
Message::parseObject((object) []);
129+
}
130+
131+
/**
132+
* @return void
133+
* @throws JSONRPCException
134+
*/
135+
public function testParseVersion2(){
136+
$this->expectException(JSONRPCException::class);
137+
$this->expectExceptionMessage('[V2] Unknown message type.');
138+
139+
Message::parseObject((object) [
140+
'jsonrpc' => '2.0',
141+
]);
142+
}
143+
144+
/**
145+
* @return void
146+
* @throws JSONRPCException
147+
*/
148+
public function testParseUnknownVersion(){
149+
$this->expectException(JSONRPCException::class);
150+
$this->expectExceptionMessage('Unknown version "1.5".');
151+
152+
Message::parseObject((object) [
153+
'jsonrpc' => '1.5',
154+
]);
155+
}
156+
11157
/**
12158
* @return void
13159
* @throws JSONRPCException
14160
*/
15161
public function testMessages(){
16-
$this->assertEquals('{"id":123,"method":"myMethod","params":[]}',Message::createRequestMessageV1(123,'myMethod')->toJSON());
17-
$this->assertEquals('{"id":null,"method":"myMethod","params":[]}',Message::createNotificationMessageV1('myMethod')->toJSON());
18-
$this->assertEquals('{"id":123,"result":"myResult","error":null}',Message::createResponseMessageV1(123,'myResult')->toJSON());
19-
$this->assertEquals('{"id":123,"result":null,"error":"myError"}',Message::createResponseMessageV1(123,null,'myError')->toJSON());
162+
$this->assertEquals((object) ["id"=>123,"method"=>"myMethod","params"=>[]],Message::createRequestMessageV1(123,'myMethod')->toObject());
163+
$this->assertEquals((object) ["id"=>123,"method"=>"myMethod","params"=>["a",1,false,12.34]],Message::createRequestMessageV1(123,'myMethod',['a',1,false,12.34])->toObject());
164+
$this->assertEquals((object) ["id"=>null,"method"=>"myMethod","params"=>[]],Message::createNotificationMessageV1('myMethod')->toObject());
165+
$this->assertEquals((object) ["id"=>null,"method"=>"myMethod","params"=>["b",0,true,34.12]],Message::createNotificationMessageV1('myMethod',['b',0,true,34.12])->toObject());
166+
$this->assertEquals((object) ["id"=>123,"result"=>"myResult","error"=>null],Message::createResponseMessageV1(123,'myResult')->toObject());
167+
$this->assertEquals((object) ["id"=>123,"result"=>null,"error"=>"myError"],Message::createResponseMessageV1(123,null,'myError')->toObject());
20168
}
21169

22170
}

0 commit comments

Comments
 (0)