Skip to content

Commit

Permalink
Improve parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
ben221199 committed May 17, 2024
1 parent 593555e commit c953be8
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 29 deletions.
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,49 @@ This yocLibrary enables your project to encode and decode JSON-RPC messages in P
### Serialization

```php
use YOCLIB\JSONRPC\JSONRPCException;
use YOCLIB\JSONRPC\Message;

$message = Message::createRequestMessageV1(123,'getInfo',['payments']); // Create request (version 1.0)
$message = Message::createNotificationMessageV1('notificationEvent',['payed']); // Create notification (version 1.0)
$message = Message::createResponseMessageV1(123,['payments'=>[]]); // Create response (version 1.0)

$object = $message->toObject();

try{
$json = Message::encodeJSON($object);
}catch(JSONRPCException $e){
//Handle encoding exception
}
```

### Deserialization

```php

use YOCLIB\JSONRPC\JSONRPCException;
use YOCLIB\JSONRPC\Message;

$json = file_get_contents('php://input'); // Get request body

try{
$object = Message::decodeJSON($json);
}catch(JSONRPCException $e){
//Handle decoding exception
}

if(Message::isBatch($object)){
foreach($object AS $element){
try{
$message = Message::parse($element);
}catch(JSONRPCException $e){
//Handle message exception
}
}
}else{
try{
$message = Message::parse($object);
}catch(JSONRPCException $e){
//Handle message exception
}
}
```
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^7||^8||^9"
"phpunit/phpunit": "^7||^8||^9",
"phpunit/php-code-coverage": "^9.2"
},
"autoload": {
"psr-4": {
Expand All @@ -33,4 +34,4 @@
"scripts": {
"test": "phpunit"
}
}
}
84 changes: 62 additions & 22 deletions src/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ private function __construct(object $value){
}

/**
* @return string
* @return object
*/
public function toJSON(): string{
return json_encode($this->value);
public function toObject(): object{
return $this->value;
}

/**
Expand Down Expand Up @@ -70,26 +70,51 @@ public static function createResponseMessageV1($id,$result=null,$error=null): Re
]);
}

/**
* @param $object
* @return bool
*/
public static function isBatch($object): bool{
return is_array($object);
}

/**
* @param $object
* @return false|string
* @throws JSONRPCException
*/
public static function encodeJSON($object){
try{
return json_encode($object,JSON_THROW_ON_ERROR);
}catch(JsonException $e){
throw new JSONRPCException('Failed to encode JSON.');
}
}

/**
* @param string $json
* @param bool $strictId
* @return Message[]|array|Message
* @return mixed
* @throws JSONRPCException
*/
public static function parse(string $json,bool $strictId=true){
public static function decodeJSON(string $json){
try{
$message = json_decode($json,false,512,JSON_THROW_ON_ERROR);
return json_decode($json,false,512,JSON_THROW_ON_ERROR);
}catch(JsonException $e){
throw new JSONRPCException('[V1] Failed to decode JSON.');
throw new JSONRPCException('Failed to decode JSON.');
}
if(is_array($message)){
$messages = [];
foreach($message AS $msg){
$messages[] = self::handleMessage($msg,$strictId);
}
return $messages;
}

/**
* @param $object
* @param bool $strictId
* @return Message
* @throws JSONRPCException
*/
public static function parseObject($object,bool $strictId=true){
if(is_object($object)){
return self::handleMessage($object,$strictId);
}
return self::handleMessage($message,$strictId);
throw new JSONRPCException('A message MUST be a JSON object.');
}

/**
Expand All @@ -99,23 +124,38 @@ public static function parse(string $json,bool $strictId=true){
* @throws JSONRPCException
*/
private static function handleMessage($message,bool $strictId=true){
if(isset($message['jsonrpc']) && $message['jsonrpc']==='2.0'){
return self::handleMessageV2($message,$strictId);
if(property_exists($message,'jsonrpc')){
if($message->jsonrpc==='2.0'){
return self::handleMessageV2($message,$strictId);
}
throw new JSONRPCException('Unknown version "'.($message->jsonrpc).'".');
}else{
return self::handleMessageV1($message,$strictId);
}
}

/**
* @param $message
* @param bool $strictId
* @return null
* @throws JSONRPCException
*/
private static function handleMessageV2($message,bool $strictId=true){
return null;
if(self::isRequest($message)){
return null;
}elseif(self::isResponse($message)){
return null;
}else{
throw new JSONRPCException('[V2] Unknown message type.');
}
}


private static function isRequestV1($message): bool{
private static function isRequest($message): bool{
return property_exists($message,'method') || property_exists($message,'params');
}

private static function isResponseV1($message): bool{
private static function isResponse($message): bool{
return property_exists($message,'result') || property_exists($message,'error');
}

Expand Down Expand Up @@ -179,7 +219,7 @@ private static function validateErrorPropertyV1($message){
* @throws JSONRPCException
*/
private static function handleMessageV1($message,bool $strictId=true){
if(self::isRequestV1($message)){
if(self::isRequest($message)){
self::validateMethodPropertyV1($message);
self::validateParamsPropertyV1($message);

Expand All @@ -188,7 +228,7 @@ private static function handleMessageV1($message,bool $strictId=true){
}else{
return new NotificationMessage($message);
}
}elseif(self::isResponseV1($message)){
}elseif(self::isResponse($message)){
self::validateResultPropertyV1($message);
self::validateErrorPropertyV1($message);
if(!is_null($message->result) && !is_null($message->error)){
Expand Down
156 changes: 152 additions & 4 deletions tests/MessageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,163 @@

class MessageTest extends TestCase{

public function testDecodeEmptyJSON(){
$this->expectException(JSONRPCException::class);
$this->expectExceptionMessage('Failed to decode JSON.');

Message::decodeJSON('');
}

/**
* @return void
* @throws JSONRPCException
*/
public function testDecodeJSONString(){
$this->assertEquals('abc',Message::decodeJSON('"abc"'));
}

/**
* @return void
* @throws JSONRPCException
*/
public function testDecodeJSONObject(){
$this->assertEquals((object) [],Message::decodeJSON('{}'));
}

/**
* @return void
* @throws JSONRPCException
*/
public function testDecodeJSONArray(){
$this->assertEquals([],Message::decodeJSON('[]'));
}

public function testIsBatch(){
$this->assertTrue(Message::isBatch([]));

$this->assertFalse(Message::isBatch('abc'));
$this->assertFalse(Message::isBatch(true));
$this->assertFalse(Message::isBatch(false));
$this->assertFalse(Message::isBatch(123));
$this->assertFalse(Message::isBatch(123.456));
$this->assertFalse(Message::isBatch((object) []));
$this->assertFalse(Message::isBatch(null));
}

/**
* @return void
* @throws JSONRPCException
*/
public function testParseObjectString(){
$this->expectException(JSONRPCException::class);
$this->expectExceptionMessage('A message MUST be a JSON object.');

Message::parseObject('abc');
}

/**
* @return void
* @throws JSONRPCException
*/
public function testParseObjectTrue(){
$this->expectException(JSONRPCException::class);
$this->expectExceptionMessage('A message MUST be a JSON object.');

Message::parseObject(true);
}

/**
* @return void
* @throws JSONRPCException
*/
public function testParseObjectFalse(){
$this->expectException(JSONRPCException::class);
$this->expectExceptionMessage('A message MUST be a JSON object.');

Message::parseObject(false);
}

/**
* @return void
* @throws JSONRPCException
*/
public function testParseObjectInteger(){
$this->expectException(JSONRPCException::class);
$this->expectExceptionMessage('A message MUST be a JSON object.');

Message::parseObject(123);
}

/**
* @return void
* @throws JSONRPCException
*/
public function testParseObjectFloat(){
$this->expectException(JSONRPCException::class);
$this->expectExceptionMessage('A message MUST be a JSON object.');

Message::parseObject(123.456);
}

/**
* @return void
* @throws JSONRPCException
*/
public function testParseObjectArray(){
$this->expectException(JSONRPCException::class);
$this->expectExceptionMessage('A message MUST be a JSON object.');

Message::parseObject([]);
}

/**
* @return void
* @throws JSONRPCException
*/
public function testParseEmptyObject(){
$this->expectException(JSONRPCException::class);
$this->expectExceptionMessage('[V1] Unknown message type.');

Message::parseObject((object) []);
}

/**
* @return void
* @throws JSONRPCException
*/
public function testParseVersion2(){
$this->expectException(JSONRPCException::class);
$this->expectExceptionMessage('[V2] Unknown message type.');

Message::parseObject((object) [
'jsonrpc' => '2.0',
]);
}

/**
* @return void
* @throws JSONRPCException
*/
public function testParseUnknownVersion(){
$this->expectException(JSONRPCException::class);
$this->expectExceptionMessage('Unknown version "1.5".');

Message::parseObject((object) [
'jsonrpc' => '1.5',
]);
}

/**
* @return void
* @throws JSONRPCException
*/
public function testMessages(){
$this->assertEquals('{"id":123,"method":"myMethod","params":[]}',Message::createRequestMessageV1(123,'myMethod')->toJSON());
$this->assertEquals('{"id":null,"method":"myMethod","params":[]}',Message::createNotificationMessageV1('myMethod')->toJSON());
$this->assertEquals('{"id":123,"result":"myResult","error":null}',Message::createResponseMessageV1(123,'myResult')->toJSON());
$this->assertEquals('{"id":123,"result":null,"error":"myError"}',Message::createResponseMessageV1(123,null,'myError')->toJSON());
$this->assertEquals((object) ["id"=>123,"method"=>"myMethod","params"=>[]],Message::createRequestMessageV1(123,'myMethod')->toObject());
$this->assertEquals((object) ["id"=>123,"method"=>"myMethod","params"=>["a",1,false,12.34]],Message::createRequestMessageV1(123,'myMethod',['a',1,false,12.34])->toObject());
$this->assertEquals((object) ["id"=>null,"method"=>"myMethod","params"=>[]],Message::createNotificationMessageV1('myMethod')->toObject());
$this->assertEquals((object) ["id"=>null,"method"=>"myMethod","params"=>["b",0,true,34.12]],Message::createNotificationMessageV1('myMethod',['b',0,true,34.12])->toObject());
$this->assertEquals((object) ["id"=>123,"result"=>"myResult","error"=>null],Message::createResponseMessageV1(123,'myResult')->toObject());
$this->assertEquals((object) ["id"=>123,"result"=>null,"error"=>"myError"],Message::createResponseMessageV1(123,null,'myError')->toObject());
}

}

0 comments on commit c953be8

Please sign in to comment.