-
-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #172 from gsteel/json-validator
Introduce `IsJsonString` validator
- Loading branch information
Showing
6 changed files
with
366 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# IsJsonString | ||
|
||
`Laminas\Validator\IsJsonString` allows you to validate whether a given value is a string that will be successfully decoded by `json_decode`. | ||
|
||
## Basic Usage | ||
|
||
```php | ||
$validator = new Laminas\Validator\IsJsonString(); | ||
$input = '{"some":"json"}'; | ||
|
||
if ($validator->isValid($input)) { | ||
// $input can be successfully decoded | ||
} else { | ||
// $input is not a valid JSON string | ||
} | ||
``` | ||
|
||
## Restricting Acceptable JSON Types | ||
|
||
`json_decode` accepts numeric strings representing integers and floating point numbers, booleans, arrays and objects. | ||
You can restrict what is considered valid input using the `allow` option of the validator. | ||
|
||
```php | ||
use Laminas\Validator\IsJsonString; | ||
|
||
$validator = new IsJsonString([ | ||
'allow' => IsJsonString::ALLOW_ALL ^ IsJsonString::ALLOW_BOOL, | ||
]); | ||
|
||
$validator->isValid('true'); // false | ||
``` | ||
|
||
The `allow` option is a bit mask of the `ALLOW_*` constants in `IsJsonString`: | ||
|
||
- `IsJsonString::ALLOW_INT` - Accept integer such as `1` | ||
- `IsJsonString::ALLOW_FLOAT` - Accept floating-point value such as `1.234` | ||
- `IsJsonString::ALLOW_BOOL` - Accept `true` and `false` | ||
- `IsJsonString::ALLOW_ARRAY` - Accept JSON arrays such as `["One", "Two"]` | ||
- `IsJsonString::ALLOW_OBJECT` - Accept JSON objects such as `{"Some":"Object"}` | ||
- `IsJsonString::ALLOW_ALL` - A convenience constant allowing all of the above _(Also the default)_. | ||
|
||
The `allow` option also has a companion setter method `setAllow`. For example, to only accept arrays and objects: | ||
|
||
```php | ||
use Laminas\Validator\IsJsonString; | ||
|
||
$validator = new IsJsonString(); | ||
$validator->setAllow(IsJsonString::ALLOW_ARRAY | IsJsonString::ALLOW_OBJECT); | ||
$validator->isValid('1.234'); // false | ||
``` | ||
|
||
## Restricting Max Object or Array Nesting Level | ||
|
||
If you wish to restrict the nesting level of arrays and objects that are considered valid, the validator accepts a `maxDepth` option. The default value of this option is `512` - the same default value as `json_decode`. | ||
|
||
```php | ||
$validator = new Laminas\Validator\IsJsonString(['maxDepth' => 2]); | ||
$validator->isValid('{"nested": {"object: "here"}}'); // false | ||
``` | ||
|
||
Again, the max nesting level allowed has a companion setter method: | ||
|
||
```php | ||
$validator = new Laminas\Validator\IsJsonString(); | ||
$validator->setMaxDepth(10); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Laminas\Validator; | ||
|
||
use JsonException; | ||
|
||
use function gettype; | ||
use function is_float; | ||
use function is_int; | ||
use function is_numeric; | ||
use function is_string; | ||
use function json_decode; | ||
use function str_starts_with; | ||
|
||
use const JSON_ERROR_DEPTH; | ||
use const JSON_THROW_ON_ERROR; | ||
|
||
/** | ||
* @psalm-type CustomOptions = array{ | ||
* allow: int-mask-of<self::ALLOW_*>, | ||
* maxDepth: positive-int, | ||
* } | ||
* @psalm-import-type AbstractOptions from AbstractValidator | ||
* @psalm-type Options = AbstractOptions|CustomOptions | ||
*/ | ||
final class IsJsonString extends AbstractValidator | ||
{ | ||
public const ERROR_NOT_STRING = 'errorNotString'; | ||
public const ERROR_TYPE_NOT_ALLOWED = 'errorTypeNotAllowed'; | ||
public const ERROR_MAX_DEPTH_EXCEEDED = 'errorMaxDepthExceeded'; | ||
public const ERROR_INVALID_JSON = 'errorInvalidJson'; | ||
|
||
public const ALLOW_INT = 0b0000001; | ||
public const ALLOW_FLOAT = 0b0000010; | ||
public const ALLOW_BOOL = 0b0000100; | ||
public const ALLOW_ARRAY = 0b0001000; | ||
public const ALLOW_OBJECT = 0b0010000; | ||
public const ALLOW_ALL = 0b0011111; | ||
|
||
/** @var array<self::ERROR_*, non-empty-string> */ | ||
protected $messageTemplates = [ | ||
self::ERROR_NOT_STRING => 'Expected a string but %type% was received', | ||
self::ERROR_TYPE_NOT_ALLOWED => 'Received a JSON %type% but this type is not acceptable', | ||
self::ERROR_MAX_DEPTH_EXCEEDED => 'The decoded JSON payload exceeds the allowed depth of %maxDepth%', | ||
self::ERROR_INVALID_JSON => 'An invalid JSON payload was received', | ||
]; | ||
|
||
/** @var array<string, string> */ | ||
protected $messageVariables = [ | ||
'type' => 'type', | ||
'maxDepth' => 'maxDepth', | ||
]; | ||
|
||
protected ?string $type = null; | ||
/** @var int-mask-of<self::ALLOW_*> */ | ||
protected int $allow = self::ALLOW_ALL; | ||
/** @var positive-int */ | ||
protected int $maxDepth = 512; | ||
|
||
/** @param int-mask-of<self::ALLOW_*> $type */ | ||
public function setAllow(int $type): void | ||
{ | ||
$this->allow = $type; | ||
} | ||
|
||
/** @param positive-int $maxDepth */ | ||
public function setMaxDepth(int $maxDepth): void | ||
{ | ||
$this->maxDepth = $maxDepth; | ||
} | ||
|
||
public function isValid(mixed $value): bool | ||
{ | ||
if (! is_string($value)) { | ||
$this->error(self::ERROR_NOT_STRING); | ||
$this->type = gettype($value); | ||
|
||
return false; | ||
} | ||
|
||
if (is_numeric($value)) { | ||
/** @psalm-var mixed $value */ | ||
$value = json_decode($value); | ||
|
||
if (is_int($value) && ! $this->isAllowed(self::ALLOW_INT)) { | ||
$this->error(self::ERROR_TYPE_NOT_ALLOWED); | ||
$this->type = 'int'; | ||
|
||
return false; | ||
} | ||
|
||
if (is_float($value) && ! $this->isAllowed(self::ALLOW_FLOAT)) { | ||
$this->error(self::ERROR_TYPE_NOT_ALLOWED); | ||
$this->type = 'float'; | ||
|
||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
if ($value === 'true' || $value === 'false') { | ||
if (! $this->isAllowed(self::ALLOW_BOOL)) { | ||
$this->error(self::ERROR_TYPE_NOT_ALLOWED); | ||
$this->type = 'boolean'; | ||
|
||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
if (str_starts_with($value, '[') && ! $this->isAllowed(self::ALLOW_ARRAY)) { | ||
$this->error(self::ERROR_TYPE_NOT_ALLOWED); | ||
$this->type = 'array'; | ||
|
||
return false; | ||
} | ||
|
||
if (str_starts_with($value, '{') && ! $this->isAllowed(self::ALLOW_OBJECT)) { | ||
$this->error(self::ERROR_TYPE_NOT_ALLOWED); | ||
$this->type = 'object'; | ||
|
||
return false; | ||
} | ||
|
||
try { | ||
/** @psalm-suppress UnusedFunctionCall */ | ||
json_decode($value, true, $this->maxDepth, JSON_THROW_ON_ERROR); | ||
|
||
return true; | ||
} catch (JsonException $e) { | ||
if ($e->getCode() === JSON_ERROR_DEPTH) { | ||
$this->error(self::ERROR_MAX_DEPTH_EXCEEDED); | ||
|
||
return false; | ||
} | ||
|
||
$this->error(self::ERROR_INVALID_JSON); | ||
|
||
return false; | ||
} | ||
} | ||
|
||
/** @param self::ALLOW_* $flag */ | ||
private function isAllowed(int $flag): bool | ||
{ | ||
return ($this->allow & $flag) === $flag; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.