cleanup, refactoring, splitted validators, tweaks

This commit is contained in:
digitalkaoz 2012-01-02 21:28:58 +01:00
parent 81532ea503
commit b395922ae0
25 changed files with 975 additions and 450 deletions

View File

@ -1,6 +1,25 @@
# JSON Schema for PHP [![Build status...](https://secure.travis-ci.org/justinrainbow/json-schema.png)](http://travis-ci.org/justinrainbow/json-schema)
# JSON Schema for PHP [![Build Status](https://secure.travis-ci.org/digitalkaoz/json-schema.png)](http://travis-ci.org/digitalkaoz/json-schema)
Documentation can be found at http://jsonschema.readthedocs.org/
A PHP Implementation for validating `JSON` Structures against a given `Schema`.
See [json-schema](http://json-schema.org/) for more details.
## Installation
### Library
$ git clone https://github.com/justinrainbow/json-schema.git
### Dependencies
#### via `submodules` (*will use the Symfony ClassLoader Component*)
$ git submodule update --init
#### via [`composer`](https://github.com/composer/composer) (*will use the Composer ClassLoader*)
$ wget http://getcomposer.org/composer.phar
$ php composer.phar install
## Usage
@ -8,19 +27,18 @@ Documentation can be found at http://jsonschema.readthedocs.org/
<?php
$validator = new JsonSchema\Validator();
$result = $validator->validate(json_decode($json), json_decode($schema));
$validator->check(json_decode($json), json_decode($schema));
if ($result->valid) {
if ($validator->isValid()) {
echo "The supplied JSON validates against the schema.\n";
} else {
echo "JSON does not validate. Violations:\n";
foreach ($result->errors as $error) {
echo "[{$error['property']}] {$error['message']}\n";
foreach ($validator->getErrors() as $error) {
echo sprintf("[%s] %s\n",$error['property'], $error['message']);
}
}
```
## Running the tests
$ git submodule update --init
$ phpunit

View File

@ -5,7 +5,7 @@
"homepage": "https://github.com/justinrainbow/json-schema",
"type": "library",
"license": "NewBSD",
"version": "1.0.0",
"version": "1.1.0",
"authors": [
{
"name": "Bruno Prieto Reis",
@ -14,6 +14,14 @@
{
"name": "Justin Rainbow",
"email": "justin.rainbow@gmail.com"
},
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
},
{
"name": "Robert Schönthal",
"email": "seroscho@googlemail.com"
}
],
"require": {

View File

@ -12,9 +12,15 @@
bootstrap="tests/bootstrap.php"
verbose="true"
>
<testsuites>
<testsuite name="JSON Schema Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<testsuites>
<testsuite name="JSON Schema Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/JsonSchema/</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -1,16 +0,0 @@
<?php
/*
* This file is part of JsonSchema.
*
* (c) Bruno Prieto Reis <bruno.p.reis@gmail.com>, Gradua Networks
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema;
class Undefined
{
}

View File

@ -1,397 +1,30 @@
<?php
/*
* This file is part of JsonSchema.
*
* (c) Bruno Prieto Reis <bruno.p.reis@gmail.com>, Gradua Networks
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema;
class Validator
use JsonSchema\Validator\Schema;
use JsonSchema\Validator\Validator as BaseValidator;
/**
* A JsonSchema Validator
*
* @see README.md
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class Validator extends BaseValidator
{
public $checkMode = self::CHECK_MODE_NORMAL;
private $errors = array();
const CHECK_MODE_NORMAL = 1;
const CHECK_MODE_TYPE_CAST = 2;
/**
* Validates a php object against a schema. Both the php object and the schema
* are supposed to be a result of a json_decode call.
* The validation works as defined by the schema proposal in
* http://json-schema.org
* validates the given data against the schema and returns an object containing the results
* Both the php object and the schema are supposed to be a result of a json_decode call.
* The validation works as defined by the schema proposal in http://json-schema.org
*
* @param \stdClass $instance
* @param \stdClass $schema
* @return unknown
* {inheritDoc}
*/
public function validate($instance, $schema = null)
function check($value, $schema = null, $path = null, $i = null)
{
$this->errors = array();
$_changing = false;
// verify passed schema
if ($schema) {
$this->checkProp($instance, $schema, '', '', $_changing);
}
// verify "inline" schema
$propName = '$schema';
if (!$_changing && isset($instance->$propName)) {
$this->checkProp($instance, $instance->$propName, '', '', $_changing);
}
// show results
$obj = new \stdClass();
$obj->valid = ! ((boolean)count($this->errors));
$obj->errors = $this->errors;
return $obj;
$validator = new Schema($this->checkMode);
$validator->check($value, $schema);
$this->addErrors($validator->getErrors());
}
protected function incrementPath($path, $i)
{
if ($path !== '') {
if (is_int($i)) {
$path .= '['.$i.']';
} else if ($i == '') {
$path .= '';
} else {
$path .= '.'.$i;
}
} else {
$path = $i;
}
return $path;
}
protected function checkArray($value, $schema, $path, $i, $_changing)
{
//verify items
if (isset($schema->items)) {
//tuple typing
if (is_array($schema->items)) {
foreach ($value as $k => $v) {
if (array_key_exists($k, $schema->items)) {
$this->checkProp($v, $schema->items[$k], $path, $k, $_changing);
}
else {
// aditional array properties
if (array_key_exists('additionalProperties', $schema)) {
if ($schema->additionalProperties === false) {
$this->adderror(
$path,
'The item '.$i.'['.$k.'] is not defined in the objTypeDef and the objTypeDef does not allow additional properties'
);
}
else {
$this->checkProp($v, $schema->additionalProperties, $path, $k, $_changing);
}
}
}
}//foreach ($value as $k => $v) {
// treat when we have more schema definitions than values
for ($k = count($value); $k < count($schema->items); $k++) {
$this->checkProp(
new Undefined(),
$schema->items[$k], $path, $k, $_changing
);
}
}
// just one type definition for the whole array
else {
foreach ($value as $k => $v) {
$this->checkProp($v, $schema->items, $path, $k, $_changing);
}
}
}
// verify number of array items
if (isset($schema->minItems) && count($value) < $schema->minItems) {
$this->adderror($path,"There must be a minimum of " . $schema->minItems . " in the array");
}
if (isset($schema->maxItems) && count($value) > $schema->maxItems) {
$this->adderror($path,"There must be a maximum of " . $schema->maxItems . " in the array");
}
}
protected function checkProp($value, $schema, $path, $i = '', $_changing = false)
{
if (!is_object($schema)) {
return;
}
$path = $this->incrementPath($path, $i);
// verify readonly
if ($_changing && $schema->readonly) {
$this->adderror($path,'is a readonly field, it can not be changed');
}
// I think a schema cant be an array, only the items property
/*if (is_array($schema)) {
if (!is_array($value)) {
return array(array('property' => $path,'message' => 'An array tuple is required'));
}
for ($a = 0; $a < count($schema); $a++) {
$this->errors = array_merge(
$this->errors,
$this->checkProp($value->$a, $schema->$a, $path, $i, $_changing)
);
return $this->errors;
}
}*/
// if it extends another schema, it must pass that schema as well
if (isset($schema->extends)) {
$this->checkProp($value, $schema->extends, $path, $i, $_changing);
}
// verify required values
if (is_object($value) && $value instanceOf Undefined) {
if (isset($schema->required) && $schema->required) {
$this->adderror($path,"is missing and it is required");
}
} else {
// normal verifications
$this->errors = array_merge(
$this->errors,
$this->checkType(isset($schema->type) ? $schema->type : null , $value, $path)
);
}
if (array_key_exists('disallow', $schema)) {
$errorsBeforeDisallowCheck = $this->errors;
$response = $this->checkType($schema->disallow, $value, $path);
if (
( count($errorsBeforeDisallowCheck) == count($this->errors) ) &&
!count($response)
) {
$this->adderror($path," disallowed value was matched");
}
else {
$this->errors = $errorsBeforeDisallowCheck;
}
}
//verify the itens on an array and min and max number of items.
if (is_array($value)) {
if (
$this->checkMode == $this::CHECK_MODE_TYPE_CAST &&
$schema->type == 'object'
) {
$this->checkObj(
$value,
$schema->properties,
$path,
isset($schema->additionalProperties) ? $schema->additionalProperties : null,
$_changing
);
}
$this->checkArray($value, $schema, $path, $i, $_changing);
} else if (isset($schema->properties) && is_object($value)) {
############ verificar!
$this->checkObj(
$value,
$schema->properties,
$path,
isset($schema->additionalProperties) ? $schema->additionalProperties : null,
$_changing
);
}
// verify a regex pattern
if (isset($schema->pattern) && is_string($value) && !preg_match('/'.$schema->pattern.'/', $value)) {
$this->adderror($path,"does not match the regex pattern " . $schema->pattern);
}
// verify maxLength, minLength, maximum and minimum values
if (isset($schema->maxLength) && is_string($value) && (strlen($value) > $schema->maxLength)) {
$this->adderror($path,"must be at most " . $schema->maxLength . " characters long");
}
if (isset($schema->minLength) && is_string($value) && strlen($value) < $schema->minLength) {
$this->adderror($path,"must be at least " . $schema->minLength . " characters long");
}
if (
isset($schema->minimum) &&
gettype($value) == gettype($schema->minimum) &&
$value < $schema->minimum
) {
$this->adderror($path,"must have a minimum value of " . $schema->minimum);
}
if (isset($schema->maximum) && gettype($value) == gettype($schema->maximum) && $value > $schema->maximum) {
$this->adderror($path,"must have a maximum value of " . $schema->maximum);
}
// verify enum values
if (isset($schema->enum)) {
$found = false;
foreach ($schema->enum as $possibleValue) {
if ($possibleValue == $value) {
$found = true;
break;
}
}
if (!$found) {
$this->adderror($path,"does not have a value in the enumeration " . implode(', ', $schema->enum));
}
}
if (
isset($schema->maxDecimal) &&
( ($value * pow(10, $schema->maxDecimal)) != (int)($value * pow(10, $schema->maxDecimal)) )
) {
$this->adderror($path,"may only have " . $schema->maxDecimal . " digits of decimal places");
}
}
protected function adderror($path, $message)
{
$this->errors[] = array(
'property' => $path,
'message' => $message
);
}
/**
* Take Care: Value is being passed by ref to continue validation with proper format.
* @return array
*/
protected function checkType($type, &$value, $path)
{
if ($type) {
$wrongType = false;
if (is_string($type) && $type !== 'any') {
if ($type == 'null') {
if (!is_null($value)) {
$wrongType = true;
}
}
else {
if ($type == 'number') {
if ($this->checkMode == $this::CHECK_MODE_TYPE_CAST) {
$wrongType = !$this->checkTypeCast($type, $value);
}
else if (!in_array(gettype($value), array('integer','double'))) {
$wrongType = true;
}
} else{
if (
$this->checkMode == $this::CHECK_MODE_TYPE_CAST
&& $type == 'integer'
) {
$wrongType = !$this->checkTypeCast($type, $value);
} else if (
$this->checkMode == $this::CHECK_MODE_TYPE_CAST
&& $type == 'object' && is_array($value)
) {
$wrongType = false;
} else if ($type !== gettype($value)) {
$wrongType = true;
}
}
}
}
if ($wrongType) {
return array(
array(
'property' => $path,
'message' => gettype($value)." value found, but a ".$type." is required"
)
);
}
// Union Types :: for now, just return the message for the last expected type!!
if (is_array($type)) {
$validatedOneType = false;
$errors = array();
foreach ($type as $tp) {
$error = $this->checkType($tp, $value, $path);
if (!count($error)) {
$validatedOneType = true;
break;
} else {
$errors[] = $error;
$errors = $error;
}
}
if (!$validatedOneType) {
return $errors;
}
} else if (is_object($type)) {
$this->checkProp($value, $type, $path);
}
}
return array();
}
/**
* Take Care: Value is being passed by ref to continue validation with proper format.
*/
protected function checkTypeCast($type, &$value)
{
switch ($type) {
case 'integer':
$castValue = (integer)$value;
break;
case 'number':
$castValue = (double)$value;
break;
default:
trigger_error('this method should only be called for the above supported types.');
break;
}
if ((string)$value == (string)$castValue ) {
$res = true;
$value = $castValue;
} else {
$res = false;
}
return $res;
}
protected function checkObj($instance, $objTypeDef, $path, $additionalProp, $_changing)
{
if ($objTypeDef instanceOf \stdClass) {
if (! (($instance instanceOf \stdClass) || is_array($instance)) ) {
$this->errors[] = array(
'property' => $path,
'message' => "an object is required"
);
}
foreach ($objTypeDef as $i => $value) {
$value =
array_key_exists($i, $instance) ?
(is_array($instance) ? $instance[$i] : $instance->$i) :
new Undefined();
$propDef = $objTypeDef->$i;
$this->checkProp($value, $propDef, $path, $i, $_changing);
}
}
// additional properties and requires
foreach ($instance as $i => $value) {
// verify additional properties, when its not allowed
if (!isset($objTypeDef->$i) && ($additionalProp === false) && $i !== '$schema' ) {
$this->errors[] = array(
'property' => $path,
'message' => "The property " . $i . " is not defined in the objTypeDef and the objTypeDef does not allow additional properties"
);
}
// verify requires
if ($objTypeDef && isset($objTypeDef->$i) && isset($objTypeDef->$i->requires)) {
$requires = $objTypeDef->$i->requires;
if (!array_key_exists($requires, $instance)) {
$this->errors[] = array(
'property' => $path,
'message' => "the presence of the property " . $i . " requires that " . $requires . " also be present"
);
}
}
$value = is_array($instance) ? $instance[$i] : $instance->$i;
// To verify additional properties types.
if ($objTypeDef && is_object($objTypeDef) && !isset($objTypeDef->$i)) {
$this->checkProp($value, $additionalProp, $path, $i);
}
// Verify inner schema definitions
$schemaPropName = '$schema';
if (!$_changing && $value && isset($value->$schemaPropName)) {
$this->errors = array_merge(
$this->errors,
checkProp($value, $value->$schemaPropname, $path, $i)
);
}
}
return $this->errors;
}
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace JsonSchema\Validator;
/**
* The Collection Validator, validates an array against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class Collection extends Validator
{
/**
* {inheritDoc}
*/
public function check($value, $schema = null, $path = null, $i = null)
{
// verify minItems
if (isset($schema->minItems) && count($value) < $schema->minItems) {
$this->addError($path, "There must be a minimum of " . $schema->minItems . " in the array");
}
// verify maxItems
if (isset($schema->maxItems) && count($value) > $schema->maxItems) {
$this->addError($path, "There must be a maximum of " . $schema->maxItems . " in the array");
}
// verify uniqueItems
//TODO array_unique doesnt work with objects
if (isset($schema->uniqueItems) && array_unique($value) != $value) {
$this->addError($path, "There are no duplicates allowed in the array");
}
//verify items
if (isset($schema->items)) {
$this->validateItems($value, $schema, $path, $i);
}
}
/**
* validates the items
*
* @param array $value
* @param \stdClass $schema
* @param string $path
* @param string $i
*/
protected function validateItems($value, $schema = null, $path = null, $i = null)
{
if (!is_array($schema->items)) {
// just one type definition for the whole array
foreach ($value as $k => $v) {
$initErrors = $this->getErrors();
//first check if its defined in "items"
if (!isset($schema->additionalItems) || $schema->additionalItems === false) {
$this->checkUndefined($v, $schema->items, $path, $k);
}
//recheck with "additionalItems" if the first test fails
if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) {
$secondErrors = $this->getErrors();
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
}
//reset errors if needed
if (isset($secondErrors) && count($secondErrors) < $this->getErrors()) {
$this->errors = $secondErrors;
} elseif (isset($secondErrors) && count($secondErrors) == count($this->getErrors())) {
$this->errors = $initErrors;
}
}
} else {
//defined item type definitions
foreach ($value as $k => $v) {
if (array_key_exists($k, $schema->items)) {
$this->checkUndefined($v, $schema->items[$k], $path, $k);
} else {
// additional items
if (array_key_exists('additionalItems', $schema) && $schema->additionalItems !== false) {
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
} else {
$this->addError(
$path,
'The item ' . $i . '[' . $k . '] is not defined in the objTypeDef and the objTypeDef does not allow additional properties'
);
}
}
}
// treat when we have more schema definitions than values
for ($k = count($value); $k < count($schema->items); $k++) {
$this->checkUndefined(new Undefined(), $schema->items[$k], $path, $k);
}
}
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace JsonSchema\Validator;
/**
* The Enum Validator, validates an element against a given set of possibilities
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class Enum extends Validator
{
/**
* {inheritDoc}
*/
public function check($element, $schema = null, $path = null, $i = null)
{
foreach ($schema->enum as $possibleValue) {
if ($possibleValue == $element) {
$found = true;
break;
}
}
if (!isset($found)) {
$this->addError($path, "does not have a value in the enumeration " . implode(', ', $schema->enum));
}
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace JsonSchema\Validator;
/**
* The Number Validator, validates an number against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class Number extends Validator
{
/**
* {inheritDoc}
*/
public function check($element, $schema = null, $path = null, $i = null)
{
//verify minimum
if (isset($schema->minimum) && $element < $schema->minimum) {
$this->addError($path, "must have a minimum value of " . $schema->minimum);
}
//verify maximum
if (isset($schema->maximum) && $element > $schema->maximum) {
$this->addError($path, "must have a maximum value of " . $schema->maximum);
}
//verify divisibleBy
if (isset($schema->divisibleBy) && $element % $schema->divisibleBy != 0) {
$this->addError($path, "is not divisible by " . $schema->divisibleBy);
}
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace JsonSchema\Validator;
/**
* The Object Validator, validates an object against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class Object extends Validator
{
/**
* {inheritDoc}
*/
function check($element, $definition = null, $path = null, $additionalProp = null)
{
// validate the definition properties
$this->validateDefinition($element, $definition, $path);
// additional the element properties
$this->validateElement($element, $definition, $path, $additionalProp);
}
/**
* validates the element properties
*
* @param \stdClass $element
* @param \stdClass $objectDefinition
* @param string $path
* @param mixed $additionalProp
*/
public function validateElement($element, $objectDefinition = null, $path = null, $additionalProp = null)
{
foreach ($element as $i => $value) {
$property = $this->getProperty($element, $i, new Undefined());
$definition = $this->getProperty($objectDefinition, $i);
//required property
if ($this->getProperty($definition, 'required') && !$property) {
$this->addError($path, "the property " . $i . " is required");
}
//no additional properties allowed
if ($additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) {
$this->addError($path, "The property " . $i . " is not defined and the definition does not allow additional properties");
}
// additional properties defined
if ($additionalProp && !$definition) {
$this->checkUndefined($value, $additionalProp, $path, $i);
}
// property requires presence of another
$require = $this->getProperty($definition, 'requires');
if ($require && !$this->getProperty($element, $require)) {
$this->addError($path, "the presence of the property " . $i . " requires that " . $require . " also be present");
}
//normal property verification
$this->checkUndefined($value, $definition ? : new \stdClass(), $path, $i);
}
}
/**
* validates the definition properties
*
* @param \stdClass $element
* @param \stdClass $objectDefinition
* @param string $path
*/
public function validateDefinition($element, $objectDefinition = null, $path = null)
{
foreach ($objectDefinition as $i => $value) {
$property = $this->getProperty($element, $i, new Undefined());
$definition = $this->getProperty($objectDefinition, $i);
$this->checkUndefined($property, $definition, $path, $i);
}
}
/**
* retrieves a property from an object or array
*
* @param mixed $element
* @param string $property
* @param mixed $fallback
* @return mixed
*/
protected function getProperty($element, $property, $fallback = null)
{
if (is_array($element) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) {
return array_key_exists($property, $element) ? $element[$property] : $fallback;
} else {
return isset($element->$property) ? $element->$property : $fallback;
}
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace JsonSchema\Validator;
/**
* The Schema Validator, validates an element against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class Schema extends Validator
{
/**
* {inheritDoc}
*/
public function check($element, $schema = null, $path = null, $i = null)
{
if ($schema !== null) {
// passed schema
$this->checkUndefined($element, $schema, '', '');
} elseif (isset($element->{$this->inlineSchemaProperty})) {
// inline schema
$this->checkUndefined($element, $element->{$this->inlineSchemaProperty}, '', '');
} else {
throw new \InvalidArgumentException('no schema found to verify against');
}
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace JsonSchema\Validator;
/**
* The String Validator, validates an string against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class String extends Validator
{
/**
* {inheritDoc}
*/
public function check($element, $schema = null, $path = null, $i = null)
{
// verify maxLength
if (isset($schema->maxLength) && strlen($element) > $schema->maxLength) {
$this->addError($path, "must be at most " . $schema->maxLength . " characters long");
}
//verify minLength
if (isset($schema->minLength) && strlen($element) < $schema->minLength) {
$this->addError($path, "must be at least " . $schema->minLength . " characters long");
}
// verify a regex pattern
if (isset($schema->pattern) && !preg_match('/' . $schema->pattern . '/', $element)) {
$this->addError($path, "does not match the regex pattern " . $schema->pattern);
}
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace JsonSchema\Validator;
/**
* The Type Validator, validates an element against a given type
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class Type extends Validator
{
/**
* {inheritDoc}
*/
function check($value = null, $schema = null, $path = null, $i = null)
{
$type = isset($schema->type) ? $schema->type : null;
$isValid = true;
if (is_array($type)) {
//TODO refactor
$validatedOneType = false;
$errors = array();
foreach ($type as $tp) {
$validator = new Type($this->checkMode);
$subSchema = new \stdClass();
$subSchema->type = $tp;
$validator->check($value, $subSchema, $path, null);
$error = $validator->getErrors();
if (!count($error)) {
$validatedOneType = true;
break;
} else {
$errors = $error;
}
}
if (!$validatedOneType) {
return $this->addErrors($errors);
}
} elseif (is_object($type)) {
$this->checkUndefined($value, $type, $path);
} else {
$isValid = $this->validateType($value, $type);
}
if ($isValid === false) {
$this->addError($path, gettype($value) . " value found, but a " . $type . " is required");
}
}
/**
* verifies that a given value is of a certain type
*
* @param string $type
* @param mixed $value
* @return boolean
* @throws \InvalidArgumentException
*/
protected function validateType($value, $type)
{
//mostly the case for inline schema
if (!$type) {
return true;
}
switch ($type) {
case 'integer' :
return (integer)$value == $value ? true : is_int($value);
case 'number' :
return is_numeric($value);
case 'boolean' :
return is_bool($value);
case 'object' :
return is_object($value);
//return ($this::CHECK_MODE_TYPE_CAST == $this->checkMode) ? is_array($value) : is_object($value);
case 'array' :
return is_array($value);
case 'string' :
return is_string($value);
case 'null' :
return is_null($value);
case 'any' :
return true;
default:
throw new \InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is a invalid type for ' . $type);
}
}
}

View File

@ -0,0 +1,107 @@
<?php
namespace JsonSchema\Validator;
/**
* The Undefined Validator
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class Undefined extends Validator
{
/**
* {inheritDoc}
*/
function check($value, $schema = null, $path = null, $i = null)
{
if (!is_object($schema)) {
return;
}
$path = $this->incrementPath($path, $i);
// check special properties
$this->validateCommonProperties($value, $schema, $path);
// check known types
$this->validateTypes($value, $schema, $path, $i);
}
/**
* validates the value against the types
*
* @param $value
* @param null $schema
* @param null $path
* @param null $i
*/
public function validateTypes($value, $schema = null, $path = null, $i = null)
{
// check array
if (is_array($value)) {
$this->checkArray($value, $schema, $path, $i);
}
// check object
if (is_object($value) && isset($schema->properties)) {
$this->checkObject($value, $schema->properties, $path, isset($schema->additionalProperties) ? $schema->additionalProperties : null);
}
// check string
if (is_string($value)) {
$this->checkString($value, $schema, $path, $i);
}
// check numeric
if (is_numeric($value)) {
$this->checkNumber($value, $schema, $path, $i);
}
// check enum
if (isset($schema->enum)) {
$this->checkEnum($value, $schema, $path, $i);
}
}
/**
* validates common properties
*
* @param $value
* @param null $schema
* @param null $path
* @param null $i
*/
protected function validateCommonProperties($value, $schema = null, $path = null, $i = null)
{
// if it extends another schema, it must pass that schema as well
if (isset($schema->extends)) {
$this->checkUndefined($value, $schema->extends, $path, $i);
}
// verify required values
if (is_object($value) && $value instanceOf Undefined) {
if (isset($schema->required) && $schema->required) {
$this->addError($path, "is missing and it is required");
}
} else {
$this->checkType($value, $schema, $path);
}
//verify disallowed items
if (isset($schema->disallow)) {
$initErrors = $this->getErrors();
$this->checkUndefined($value, $schema->disallow, $path);
//if no new errors were raised it must be a disallowed value
if (count($this->getErrors()) == count($initErrors)) {
$this->addError($path, " disallowed value was matched");
} else {
$this->errors = $initErrors;
}
}
}
}

View File

@ -0,0 +1,198 @@
<?php
namespace JsonSchema\Validator;
/**
* The Base Validator, all Validators should extend this class
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
abstract class Validator implements ValidatorInterface
{
protected $checkMode = self::CHECK_MODE_NORMAL;
protected $errors = array();
protected $inlineSchemaProperty = '$schema';
const CHECK_MODE_NORMAL = 1;
const CHECK_MODE_TYPE_CAST = 2;
/**
* @param int $checkMode
*/
public function __construct($checkMode = self::CHECK_MODE_NORMAL)
{
$this->checkMode = $checkMode;
}
/**
* {inheritDoc}
*/
public function addError($path, $message)
{
$this->errors[] = array(
'property' => $path,
'message' => $message
);
}
/**
* {inheritDoc}
*/
public function addErrors(array $errors)
{
$this->errors = array_merge($this->errors, $errors);
}
/**
* {inheritDoc}
*/
public function getErrors()
{
return array_unique($this->errors, SORT_REGULAR);
}
/**
* bubble down the path
*
* @param string $path
* @param mixed $i
* @return string
*/
protected function incrementPath($path, $i)
{
if ($path !== '') {
if (is_int($i)) {
$path .= '[' . $i . ']';
} else if ($i == '') {
$path .= '';
} else {
$path .= '.' . $i;
}
} else {
$path = $i;
}
return $path;
}
/**
* validates an array
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkArray($value, $schema = null, $path = null, $i = null)
{
$validator = new Collection($this->checkMode);
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* validates an object
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkObject($value, $schema = null, $path = null, $i = null)
{
$validator = new Object($this->checkMode);
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* validates the type of a property
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkType($value, $schema = null, $path = null, $i = null)
{
$validator = new Type($this->checkMode);
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* checks a undefined element
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkUndefined($value, $schema = null, $path = null, $i = null)
{
$validator = new Undefined($this->checkMode);
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* checks a string element
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkString($value, $schema = null, $path = null, $i = null)
{
$validator = new String($this->checkMode);
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* checks a number element
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkNumber($value, $schema = null, $path = null, $i = null)
{
$validator = new Number($this->checkMode);
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* checks a enum element
*
* @param mixed $value
* @param mixed $schema
* @param mixed $path
* @param mixed $i
*/
protected function checkEnum($value, $schema = null, $path = null, $i = null)
{
$validator = new Enum($this->checkMode);
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* {inheritDoc}
*/
public function isValid()
{
return !!!$this->getErrors();
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace JsonSchema\Validator;
/**
* The Validator Interface
*
* @author Robert Schönthal <seroscho@googlemail.com>
*/
interface ValidatorInterface
{
/**
* returns all collected errors
*
* @return array
*/
function getErrors();
/**
* adds errors to this validator
*
* @param array $errors
*/
function addErrors(array $errors);
/**
* adds an error
*
* @param $path
* @param $message
*/
function addError($path, $message);
/**
* checks if the validator has not raised errors
*
* @return boolean
*/
function isValid();
/**
* invokes the validation of an element
*
* @abstract
* @param mixed $value
* @param null $schema
* @param null $path
* @param null $i
*/
function check($value, $schema = null, $path = null, $i = null);
}

View File

@ -25,7 +25,7 @@ class AdditionalPropertiesTest extends BaseTestCase
array(
array(
'property' => '',
'message' => 'The property additionalProp is not defined in the objTypeDef and the objTypeDef does not allow additional properties'
'message' => 'The property additionalProp is not defined and the definition does not allow additional properties'
)
)
),

View File

@ -51,7 +51,22 @@ class ArraysTest extends BaseTestCase
"array":{"type":"array"}
}
}'
)
),
array(
'{
"array":[1,2,"a"]
}',
'{
"type":"object",
"properties":{
"array":{
"type":"array",
"items":{"type":"number"},
"additionalItems": {"type": "string"}
}
}
}'
),
);
}
}

View File

@ -9,36 +9,27 @@ abstract class BaseTestCase extends \PHPUnit_Framework_TestCase
/**
* @dataProvider getInvalidTests
*/
public function testInvalidCases($input, $schema, $checkMode = null, $errors = array())
public function testInvalidCases($input, $schema, $checkMode = Validator::CHECK_MODE_NORMAL, $errors = array())
{
if (null === $checkMode) {
$checkMode = Validator::CHECK_MODE_NORMAL;
}
$validator = new Validator($checkMode);
$validator = new Validator();
$validator->checkMode = $checkMode;
$validator->check(json_decode($input), json_decode($schema));
$result = $validator->validate(json_decode($input), json_decode($schema));
if (array() !== $errors) {
$this->assertEquals($errors, $result->errors, var_export($result, true));
$this->assertEquals($errors, $validator->getErrors(), print_r($validator->getErrors(),true));
}
$this->assertFalse($result->valid, var_export($result, true));
$this->assertFalse($validator->isValid(), print_r($validator->getErrors(), true));
}
/**
* @dataProvider getValidTests
*/
public function testValidCases($input, $schema, $checkMode = null)
public function testValidCases($input, $schema, $checkMode = Validator::CHECK_MODE_NORMAL)
{
if (null === $checkMode) {
$checkMode = Validator::CHECK_MODE_NORMAL;
}
$validator = new Validator($checkMode);
$validator = new Validator();
$validator->checkMode = $checkMode;
$result = $validator->validate(json_decode($input), json_decode($schema));
$this->assertTrue($result->valid, var_export($result, true));
$validator->check(json_decode($input), json_decode($schema));
$this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true));
}
abstract public function getValidTests();

View File

@ -2,7 +2,7 @@
namespace JsonSchema\Tests;
class MaxDecimalTest extends BaseTestCase
class DivisibleByTest extends BaseTestCase
{
public function getInvalidTests()
{
@ -14,7 +14,7 @@ class MaxDecimalTest extends BaseTestCase
'{
"type":"object",
"properties":{
"value":{"type":"number","maxDecimal":3}
"value":{"type":"number","divisibleBy":3}
}
}'
)
@ -26,12 +26,12 @@ class MaxDecimalTest extends BaseTestCase
return array(
array(
'{
"value":5.633
"value":6
}',
'{
"type":"object",
"properties":{
"value":{"type":"number","maxDecimal":3}
"value":{"type":"number","divisibleBy":3}
}
}'
)

View File

@ -20,7 +20,7 @@ class MinItemsMaxItemsTest extends BaseTestCase
),
array(
'{
"value":2,2,5,8,5]
"value":[2,2,5,8,5]
}',
'{
"type":"object",

View File

@ -34,7 +34,7 @@ class PhpTypeCastModeTest extends BaseTestCase
'{
"type":"object",
"properties":{
"a":{"type":"integer","maximum":8}
"a":{"type":"integer","maximum":"8"}
}
}'
)
@ -46,19 +46,31 @@ class PhpTypeCastModeTest extends BaseTestCase
return array(
array(
'{
"a":"9"
"a":"7"
}',
'{
"type":"object",
"properties":{
"a":{"type":"integer","maximum":8.0}
"a":{"type":"integer","maximum":8}
}
}',
Validator::CHECK_MODE_TYPE_CAST
),
array(
'{
"a":"9"
"a":1.337
}',
'{
"type":"object",
"properties":{
"a":{"type":"number","maximum":8.0}
}
}',
Validator::CHECK_MODE_TYPE_CAST
),
array(
'{
"a":"9e42"
}',
'{
"type":"object",

View File

@ -0,0 +1,39 @@
<?php
namespace JsonSchema\Tests;
class ReadOnlyTest extends BaseTestCase
{
public function getInvalidTests()
{
//is readonly really required?
return array(
array(
'{ "number": [] }',
'{
"type":"object",
"properties":{
"number":{"type":"string","readonly":true}
}
}'
)
);
}
public function getValidTests()
{
return array(
array(
'{
"number": "1.4"
}',
'{
"type":"object",
"properties":{
"number":{"type":"string","readonly":true}
}
}'
)
);
}
}

View File

@ -32,6 +32,15 @@ class RequiredPropertyTest extends BaseTestCase
"number":{"type":"string","required":true}
}
}'
),
array(
'{}',
'{
"type":"object",
"properties":{
"number":{"type":"string"}
}
}'
)
);
}

View File

@ -0,0 +1,39 @@
<?php
namespace JsonSchema\Tests;
class UniqueItemsTest extends BaseTestCase
{
public function getInvalidTests()
{
return array(
array(
'[1,2,2]',
'{
"type":"array",
"uniqueItems": true
}'
),
/* array(
'[{"a":"b"},{"a":"c"},{"a":"b"}]',
'{
"type":"array",
"uniqueItems": true
}'
)*/
);
}
public function getValidTests()
{
return array(
array(
'[1,2,3]',
'{
"type":"array",
"uniqueItems": true
}'
)
);
}
}

View File

@ -1,8 +1,17 @@
<?php
require_once __DIR__.'/../vendor/symfony/Component/ClassLoader/UniversalClassLoader.php';
if(is_readable(__DIR__.'/../vendor/.composer/autoload.php')) {
//composer
$loader = require_once(__DIR__.'/../vendor/.composer/autoload.php');
$loader->add('JsonSchema\Tests', __DIR__);
$loader->register();
$loader = new Symfony\Component\ClassLoader\UniversalClassLoader();
$loader->registerNamespace('JsonSchema', __DIR__.'/../src');
$loader->registerNamespace('JsonSchema\Tests', __DIR__);
$loader->register();
} elseif(is_readable(__DIR__.'/../vendor/symfony/Component/ClassLoader/UniversalClassLoader.php')) {
//submodule
require_once __DIR__.'/../vendor/symfony/Component/ClassLoader/UniversalClassLoader.php';
$loader = new Symfony\Component\ClassLoader\UniversalClassLoader();
$loader->registerNamespace('JsonSchema', __DIR__.'/../src');
$loader->registerNamespace('JsonSchema\Tests', __DIR__);
$loader->register();
}