Skip to content

Commit

Permalink
All JsonRpc2 Exception was made translatable.
Browse files Browse the repository at this point in the history
Added Yii error logging for response errors
  • Loading branch information
cranetm committed Jan 17, 2017
1 parent 4248eab commit 2e3eeea
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 43 deletions.
16 changes: 9 additions & 7 deletions JsonRpc2/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public function runAction($id, $params = [])
$resultData = null;
if (empty($requests)) {
$isBatch = false;
$resultData = [$this->formatResponse(null, new Exception("Invalid Request", Exception::INVALID_REQUEST))];
$resultData = [$this->formatResponse(null, new Exception(Yii::t('yii', 'Invalid Request'), Exception::INVALID_REQUEST))];
} else {
foreach ($requests as $request) {
if($response = $this->getActionResponse($request))
Expand Down Expand Up @@ -88,7 +88,7 @@ private function getActionResponse($requestObject)
$error = $e;
}
} catch (\Exception $e) {
$error = new Exception("Internal error", Exception::INTERNAL_ERROR);
$error = new Exception(Yii::t('yii', 'Internal error'), Exception::INTERNAL_ERROR);
}

if (!isset($this->requestObject->id) && (empty($error) || !in_array($error->getCode(), [Exception::PARSE_ERROR, Exception::INVALID_REQUEST])))
Expand All @@ -112,7 +112,7 @@ public function createAction($id)
{
$action = parent::createAction($id);
if (empty($action))
throw new Exception("Method not found", Exception::METHOD_NOT_FOUND);
throw new Exception(Yii::t('yii', 'Method not found').' '.$id, Exception::METHOD_NOT_FOUND);

$this->prepareActionParams($action);

Expand Down Expand Up @@ -216,13 +216,13 @@ private function initRequest($id)
private function parseAndValidateRequestObject($requestObject)
{
if (null === $requestObject)
throw new Exception("Parse error", Exception::PARSE_ERROR);
throw new Exception(Yii::t('yii', 'Parse error'), Exception::PARSE_ERROR);

if (!is_object($requestObject)
|| !isset($requestObject->jsonrpc) || $requestObject->jsonrpc !== '2.0'
|| empty($requestObject->method) || "string" != gettype($requestObject->method)
)
throw new Exception("Invalid Request", Exception::INVALID_REQUEST);
throw new Exception(Yii::t('yii', 'Invalid Request'), Exception::INVALID_REQUEST);

$this->requestObject = $requestObject;
if (!isset($this->requestObject->params))
Expand Down Expand Up @@ -372,10 +372,12 @@ public function formatResponse($result = null, Exception $error = null, $id = nu
'id' => $id,
];

if (!empty($error))
if (!empty($error)) {
\Yii::error($error, 'jsonrpc');
$resultArray['error'] = $error->toArray();
else
} else {
$resultArray['result'] = $result;
}

return $resultArray;
}
Expand Down
5 changes: 1 addition & 4 deletions JsonRpc2/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,7 @@ protected function validate()

protected function throwError($message)
{
if ($this->value->parent instanceof JsonRpc2\Dto)
throw new Exception("In class ".get_class($this->value->parent). " " . $message, Exception::INVALID_PARAMS, $this->getErrorData());
else
throw new Exception($message, Exception::INVALID_PARAMS, $this->getErrorData());
throw new Exception($message, Exception::INVALID_PARAMS, $this->getErrorData());
}

protected function getErrorData()
Expand Down
19 changes: 17 additions & 2 deletions JsonRpc2/Validator/ValidateInArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,25 @@ protected function validate()
if (!empty($matches) && in_array($type, ["integer", "double", "float", "string"])) {
eval("\$restrictions = {$matches[1]};");
if (!is_array($restrictions))
throw new Exception(get_class($this).": Invalid syntax in {$this->value->name} tag @inArray{$matches[2]}", Exception::INTERNAL_ERROR);
throw new Exception(
\Yii::t('yii', "{className}: Invalid syntax in {valueName} tag @inArray{restrictions}",
[
'className' => get_class($this->value->parent),
'valueName' => $this->value->getFullName(),
'restrictions' => $matches[2],
]
),
Exception::INTERNAL_ERROR);

if (!empty($restrictions) && !in_array($this->value->data, $restrictions))
$this->throwError(sprintf("For property '{$this->value->name}' value '{$this->value->data}' is not allowed. Allowed values is '%s'", implode("','", $restrictions)));
$this->throwError(
\Yii::t('yii', "Value '{valueData}' is not allowed for {valueName}. Allowed values is '{restrictions}'",
[
'valueName' => $this->value->getFullName(),
'valueData' => $this->value->data,
'restrictions' => implode("','", $restrictions),
]
));
}
}

Expand Down
7 changes: 6 additions & 1 deletion JsonRpc2/Validator/ValidateMaxSize.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ protected function validate()
|| $type === "string" && mb_strlen($this->value->data) > $maxSize
|| in_array($type, ["integer", "double", "float"]) && $this->value->data > $maxSize
) {
$this->throwError("For property '{$this->value->name}' allowed max size is {$maxSize}");
$this->throwError(
\Yii::t('yii', 'For {valueName} allowed max size is {maxSize}',
['valueName' => $this->value->getFullName(), 'maxSize' => $maxSize]
)
);

}
}
}
6 changes: 5 additions & 1 deletion JsonRpc2/Validator/ValidateMinSize.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ protected function validate()
|| $type === "string" && mb_strlen($this->value->data) < $minSize
|| in_array($type, ["integer", "double", "float"]) && $this->value->data < $minSize
) {
$this->throwError("For property '{$this->value->name}' allowed min size is {$minSize}");
$this->throwError(
\Yii::t('yii', 'For {valueName} allowed min size is {minSize}',
['valueName' => $this->value->getFullName(), 'minSize' => $minSize]
)
);
}
}
}
4 changes: 3 additions & 1 deletion JsonRpc2/Validator/ValidateNotNull.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class ValidateNotNull extends JsonRpc2\Validator
protected function validate()
{
if (null === $this->value->data)
$this->throwError("Property '{$this->value->name}' is required and cannot be Null.");
$this->throwError(
\Yii::t('yii', "{valueName} is required and cannot be Null.", ['valueName' => $this->value->getFullName()])
);
}
}
8 changes: 7 additions & 1 deletion JsonRpc2/Validator/ValidateNull.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ class ValidateNull extends JsonRpc2\Validator
*/
protected function validate()
{
throw new Exception("@Null for '{$this->value->name}' is deprecated. All values can be NULL by default. Please use @NotNull for required properties.", Exception::INVALID_PARAMS);
throw new Exception(
\Yii::t('yii',
'@Null for {valueName} is deprecated. All values can be NULL by default. Please use @NotNull for required properties.',
['valueName' => $this->value->getFullName()]
),
Exception::INVALID_PARAMS
);
}
}
29 changes: 23 additions & 6 deletions JsonRpc2/Validator/ValidateVar.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use JsonRpc2;
use JsonRpc2\Exception;
use JsonRpc2\Helper;

class ValidateVar extends JsonRpc2\Validator
{
Expand Down Expand Up @@ -33,15 +32,25 @@ private function bringValueToType($parent, $type, $value)
$typeParts = explode("[]", $type);
$singleType = current($typeParts);
if (count($typeParts) > 2)
throw new Exception(sprintf("In %s type '$type' is invalid", get_class($parent)), Exception::INTERNAL_ERROR);
throw new Exception(
\Yii::t('yii', 'In {className} type \'{type}\' is invalid',
['className' => get_class($parent), 'type' => $type]
),
Exception::INTERNAL_ERROR
);

//for array type
if (count($typeParts) === 2) {
if (!is_array($value)) {
if ($parent instanceof \JsonRpc2\Dto)
throw new Exception(sprintf("In %s value has type %s, but array expected", get_class($parent), gettype($value)), Exception::INTERNAL_ERROR);
throw new Exception(
\Yii::t('yii', 'In {className} value has type \'{type}\', but array expected',
['className' => get_class($parent), 'type' => gettype($value)]
),
Exception::INTERNAL_ERROR
);
else
throw new Exception("Value has type %s, but array expected", gettype($value), Exception::INTERNAL_ERROR);
throw new Exception(\Yii::t('yii', 'Value has type \'{type}\', but array expected', ['type' => gettype($value)]), Exception::INTERNAL_ERROR);
}

foreach ($value as $key=>$childValue) {
Expand All @@ -56,7 +65,12 @@ private function bringValueToType($parent, $type, $value)
}
if (class_exists($type)) {
if (!is_subclass_of($type, '\\JsonRpc2\\Dto'))
throw new Exception(sprintf("In %s class '%s' MUST be instance of '\\JsonRpc2\\Dto'", get_class($parent), $type), Exception::INTERNAL_ERROR);
throw new Exception(
\Yii::t('yii', 'In {className} class \'{type}\' MUST be instance of \'\\JsonRpc2\\Dto\'',
['className' => get_class($parent), 'type' => $type]
),
Exception::INTERNAL_ERROR
);
return new $type($value);
} else {
if (is_array($value) || $value instanceof \stdClass) {
Expand All @@ -71,7 +85,10 @@ private function bringValueToType($parent, $type, $value)
case "double":
return (float)$value;
case "array":
throw new Exception("Parameter type 'array' is deprecated. Use square brackets with simply types or DTO based classes instead.", Exception::INTERNAL_ERROR);
throw new Exception(
\Yii::t('yii', 'Parameter type \'array\' is deprecated. Use square brackets with simply types or DTO based classes instead.'),
Exception::INTERNAL_ERROR
);
case "bool":
return (bool)$value;
}
Expand Down
18 changes: 17 additions & 1 deletion JsonRpc2/Validator/Value.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace JsonRpc2\Validator;

use JsonRpc2\Dto;

class Value
{
/**
Expand Down Expand Up @@ -32,4 +34,18 @@ public function getType()
{
return gettype($this->data);
}
}

/**
* Return value's name with DTO's class prefix if exists
* @return string
*/
public function getFullName()
{
if ($this->parent instanceof Dto)
return get_class($this->parent) . '::$' . $this->name;
elseif ('result' === $this->name)
return 'Result';

return '$' . $this->name;
}
}
97 changes: 78 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
- [Example 4](#example-4)
- [Response data validation](#response-data-validation)
- [Example 5](#example-5)
- [Null values and @null tags](#null-values-and-null-tags)
- [Value restrictions and @inArray tag](#value-restrictions-and-inarray-tag)
- [Validators](#validators)
- [Null values and @notNull tag](#null-values-and-not-null-tag)
- [Value restrictions and @inArray tag](#value-restrictions-and-inarray-tag)
- [Limit value with @minSize & @maxSize tags](#minsize-maxsize-tags)
- [CORS Support](#cors-support)

## Validation features:
Expand All @@ -21,8 +23,10 @@
2. Validation for params types<br/>
2.1 Using DTOs as structured type<br/>
2.2 Using square brackets for array types like string[], int[], bool[] or for DTO: ClassName[]<br/>
3. @null tag to allowing null values (by default all data brings to specific type)
4. @inArray tag to restrict values like @inArray["red","brown","yellow"]. Works only with string and int datatypes.
3. Validators:
3.1 @notNull tag to deny null values (make it required)
3.2 @inArray tag to restrict values like @inArray["red","brown","yellow"]. Works only with string and int datatypes.
3.3 @minSize & @maxSize to limit length for strings or values for numbers.


## Using
Expand Down Expand Up @@ -288,7 +292,7 @@ class User extends Dto
public $type = 'user';

/** @var string */
public $rights;
public $rights="";
}
~~~

Expand Down Expand Up @@ -325,21 +329,45 @@ Every element of array from response will be converted to User DTO:
~~~
> Even if some values is missing in response array, data brings to User type with all variables described in DTO
## Null values and @null tags
By default null types are not allowed and all null values are converted to specific types:
+ string - ""
+ int/float - 0
+ bool - false
+ DTO - empty object (if default value exists, it will use)
+ array - []
## Validators
There are set of validators which can change value or check it for some rules (or both).
To use it just write it name in phpdoc in the new line after needed variable or property.
For example:
~~~php
/**
* @var int
* @inArray[1,2]
*/
public $example=0;
~~~
Here we have _inArray_ validator for _$example_ property.
Script tries to find class with name _JsonRpc2\Validator\ValidateInArray_ and run _validate()_ method(see JsonRpc2\Validator folder).
If validation error occurs, response object will have data property with explanation
~~~javascript
{
"cause":"rights", // failed property
"type":"inArray", // validator name
"value":0, // passed value
"restriction":'"1","2"' // active restrictions
}
~~~
But in many cases you need a null value and you need to add tag @null in the next line after the description of the type of data.
### Null values and @notNull tags
DTO's values can be NULL by default if is not initialized(same as in php).
If you need default empty value but not NULL, define it and if you pass NULL defined default value will use(like empty string in example):
~~~php
/**
* @var string
*/
public $rights="";
~~~
But in many cases you need required value which must be passed in DTO. In this case clear default value(if exists) and use tag @notNull .

Let's update User's rights variable to be nullable
Let's update User's rights variable to be not NULL and without default value(required)
~~~php
/**
* @var string
* @null
* @notNull
*/
public $rights;
~~~
Expand All @@ -348,11 +376,11 @@ Let's update User's rights variable to be nullable
{"jsonrpc": "2.0","id": 1,"method": "get-users","params": []}

//response
{"jsonrpc":"2.0","id":1,"result":[{"id":1,"name":"Marco Polo","type":"admin","rights":null},{"id":234,"name":"John Doe","type":"user","rights":"settings"}]}
{"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"JsonRpc2\\Dto\\User::$rights is required and cannot be Null.","data":{"cause":"rights","value":null,"type":"notNull","restriction":""}}}
~~~
As we can see, rights variable for Marco Polo is null now.
As we can see, rights variable now required.

## Value restrictions and @inArray tag
### Value restrictions and @inArray tag
There are many cases where the value may be limited to several variants and should be validated for their presence. <br/>
How it works?<br/>
Let's make restrictions for variable User's rights and try to make request.
Expand All @@ -369,7 +397,7 @@ Let's make restrictions for variable User's rights and try to make request.
{"jsonrpc": "2.0","id": 1,"method": "get-users","params": []}

//response
{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"string value '' is not allowed. Allowed values is 'dashboard','settings'","data":null},"result":[]}
{"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"Value '' is not allowed for JsonRpc2\\Dto\\User::$rights property. Allowed values is 'dashboard','settings'","data":{"cause":"rights","value":"","type":"inArray","restriction":"\"dashboard\",\"settings\""}}}
~~~
Ups... there is error occurs for Marco Polo and about null value in rights which converts to string and became empty string "".<br/>
But there are restrictions with no empty strings (["dashboard","settings"]) so we have an error.<br/>
Expand All @@ -390,6 +418,37 @@ And response will be
{"jsonrpc":"2.0","id":1,"result":[{"id":1,"name":"Marco Polo","type":"admin","rights":"dashboard"},{"id":234,"name":"John Doe","type":"user","rights":"settings"}]}
~~~

### Limit value with @minSize & @maxSize tags
This tags limit length for strings or values for numbers.
~~~php
//can be used together
/**
* @var string
* @minSize 15
* @maxSize 50
*/
public $name;

//...or separatly
/**
* @var string
* @minSize 15
*/
public $name;

//for numbers
/**
* @var int
* @minSize 100
* @minSize 500
*/
public $money;
~~~
If values is out of this ranges, you will have an error
~~~javascript
{"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"For JsonRpc2\Dto\User::$money allowed min size is 100","data":{"cause":"money","value":37,"type":"minSize","restriction":"100"}}}
~~~

## CORS Support
Extention supports CORS requests from 1.2.5 release.
You may use CORS filter by attaching it as a behavior to a controller, just follow instructions [here](http://www.yiiframework.com/doc-2.0/yii-filters-cors.html)
Expand Down

0 comments on commit 2e3eeea

Please sign in to comment.