Skip to content

Improved extends and $ref support, added json-schema-test-suite test-cases #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
/vendor
/bin
!/bin/validate-json
composer.phar
testserver/node_modules
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "tests/suite"]
path = tests/suite
url = git://github.com/json-schema/JSON-Schema-Test-Suite.git
46 changes: 37 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,40 @@ See [json-schema](http://json-schema.org/) for more details.

### Library

$ git clone https://github.com/justinrainbow/json-schema.git
git clone git://github.com/Hypercharge/json-schema.git

### Dependencies

#### [`Composer`](https://github.com/composer/composer) (*will use the Composer ClassLoader*)

$ wget http://getcomposer.org/composer.phar
$ php composer.phar install
wget http://getcomposer.org/composer.phar
php composer.phar install

Or if you don't have wget you can do same with curl

curl -o composer.phar http://getcomposer.org/composer.phar
php composer.phar install

## Usage

```php
<?php

// Get the schema and data as objects
$retriever = new JsonSchema\Uri\UriRetriever;
$schema = $retriever->retrieve('file://' . realpath('schema.json'));
$data = json_decode(file_get_contents('data.json'));
$schemaUri = 'file://' . realpath('schema.json');

$retriever = new JsonSchema\Uri\UriRetriever;
$schema = $retriever->retrieve($schemaUri);

// If you use $ref or if you are unsure, resolve those references here
// This modifies the $schema object
$refResolver = new JsonSchema\RefResolver($retriever);
$refResolver->resolve($schema, 'file://' . __DIR__);
$refResolver->resolve($schema, $schemaUri);

// Validate
$validator = new JsonSchema\Validator();
$validator->check($data, $resolvedSchema);
$validator->check($data, $schema);

if ($validator->isValid()) {
echo "The supplied JSON validates against the schema.\n";
Expand All @@ -46,6 +53,27 @@ if ($validator->isValid()) {
}
```

## Running the tests
## Tests

Uses https://github.com/Julian/JSON-Schema-Test-Suite as well as our own. You'll need to update and init the git submodules:

git submodule update --init

install phpunit

php composer.phar install --dev

run tests

./vendor/bin/phpunit.php


install the testserver - install [node.js](http://nodejs.org/) first

cd testserver
npm install

start the testserver

node testserver/server.js

$ phpunit
8 changes: 6 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "justinrainbow/json-schema",
"name": "hypercharge/json-schema",
"description": "A library to validate a json schema.",
"keywords": ["json", "schema"],
"homepage": "https://github.com/justinrainbow/json-schema",
"homepage": "https://github.com/hypercharge/json-schema",
"type": "library",
"license": "BSD-3-Clause",
"authors": [
Expand All @@ -21,6 +21,10 @@
{
"name": "Robert Schönthal",
"email": "seroscho@googlemail.com"
},
{
"name": "Jan Mentzel",
"email": "jan@islandofwaiting.de"
}
],
"require": {
Expand Down
12 changes: 9 additions & 3 deletions src/JsonSchema/Constraints/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,15 @@ public function check($value, $schema = null, $path = null, $i = null)
}

// 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");
if (isset($schema->uniqueItems)) {
$uniquable = $value;
if(is_array($value) && sizeof($value)) {
// array_unique doesnt work with objects of stdClass because of missing stdClass::__toString :-|
$uniquable = array_map(function($e) { return print_r($e, true); }, $value);
}
if(sizeof(array_unique($uniquable)) != sizeof($value)) {
$this->addError($path, "There are no duplicates allowed in the array");
}
}

// Verify items
Expand Down
2 changes: 1 addition & 1 deletion src/JsonSchema/Constraints/Constraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function __construct($checkMode = self::CHECK_MODE_NORMAL, \JsonSchema\Ur
/**
* @return UriRetriever $uriRetriever
*/
public function getUriRetriever()
public function getUriRetriever()
{
if (is_null($this->uriRetriever))
{
Expand Down
5 changes: 2 additions & 3 deletions src/JsonSchema/Constraints/Enum.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ public function check($element, $schema = null, $path = null, $i = null)
if ($element instanceof Undefined && (!isset($schema->required) || !$schema->required)) {
return;
}

if (!in_array($element, $schema->enum)) {
$this->addError($path, "does not have a value in the enumeration " . implode(', ', $schema->enum));
if (!in_array($element, $schema->enum, true)) {
$this->addError($path, "does not have a value in the enumeration " . print_r($schema->enum, true));
}
}
}
7 changes: 4 additions & 3 deletions src/JsonSchema/Constraints/Format.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public function check($element, $schema = null, $path = null, $i = null)

case 'date-time':
if (!$this->validateDateTime($element, 'Y-m-d\TH:i:s\Z') &&
!$this->validateDateTime($element, 'Y-m-d\TH:i:s.u\Z') &&
!$this->validateDateTime($element, 'Y-m-d\TH:i:sP') &&
!$this->validateDateTime($element, 'Y-m-d\TH:i:sO')
) {
Expand All @@ -55,7 +56,7 @@ public function check($element, $schema = null, $path = null, $i = null)
break;

case 'regex':
if (!$this->validateRegex($element, $schema->pattern)) {
if (!$this->validateRegex($element)) {
$this->addError($path, "Invalid regex format");
}
break;
Expand Down Expand Up @@ -125,9 +126,9 @@ protected function validateDateTime($datetime, $format)
return $datetime === $dt->format($format);
}

protected function validateRegex($string, $regex)
protected function validateRegex($regex)
{
return preg_match('/' . $regex . '/', $string);
return false !== preg_match('/' . $regex . '/', 'dummy');
}

protected function validateColor($color)
Expand Down
2 changes: 1 addition & 1 deletion src/JsonSchema/Constraints/Number.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function check($element, $schema = null, $path = null, $i = null)
}

// Verify divisibleBy
if (isset($schema->divisibleBy) && $element % $schema->divisibleBy != 0) {
if (isset($schema->divisibleBy) && fmod($element, $schema->divisibleBy) != 0) {
$this->addError($path, "is not divisible by " . $schema->divisibleBy);
}

Expand Down
79 changes: 72 additions & 7 deletions src/JsonSchema/RefResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public function __construct($retriever = null)
public function fetchRef($ref, $sourceUri)
{
$retriever = $this->getUriRetriever();
//var_dump('ref', $ref, 'sourceUri', $sourceUri);
$jsonSchema = $retriever->retrieve($ref, $sourceUri);
$this->resolve($jsonSchema);

Expand All @@ -52,12 +53,12 @@ public function fetchRef($ref, $sourceUri)
* Return the URI Retriever, defaulting to making a new one if one
* was not yet set.
*
* @return UriRetriever
* @return Uri\UriRetriever
*/
public function getUriRetriever()
{
if (is_null($this->uriRetriever)) {
$this->setUriRetriever(new UriRetriever);
$this->setUriRetriever(new Uri\UriRetriever);
}

return $this->uriRetriever;
Expand All @@ -83,12 +84,15 @@ public function resolve($schema, $sourceUri = null)
return;
}

if (null === $sourceUri && ! empty($schema->id)) {
$sourceUri = $schema->id;
if (!empty($schema->id)) {
$sourceUri = $this->getUriRetriever()->resolve($schema->id, $sourceUri);
}

// Resolve $ref first
$this->resolveRef($schema, $sourceUri);
// Resolve extends first
$this->resolveExtends($schema, $sourceUri);


// These properties are just schemas
// eg. items can be a schema or an array of schemas
Expand Down Expand Up @@ -160,7 +164,6 @@ public function resolveProperty($schema, $propertyName, $sourceUri)
if (! isset($schema->$propertyName)) {
return;
}

$this->resolve($schema->$propertyName, $sourceUri);
}

Expand Down Expand Up @@ -189,13 +192,75 @@ public function resolveRef($schema, $sourceUri)
}
}

/**
* Look for the $ref property in the object. If found, remove the
* reference and augment this object with the contents of another
* schema.
*
* @param object $schema JSON Schema to flesh out
* @param string $sourceUri URI where this schema was located
*/
public function resolveExtends($schema, $sourceUri)
{
if (empty($schema->extends)) {
return;
}
if(is_object($schema->extends)) {
self::merge($schema, $schema->extends);
} else {
if(is_array($schema->extends)) {
foreach($schema->extends as $extends) {
// yeah, some copy paste here
if(is_object($schema)) {
self::merge($schema, $schema);
} else {
$refSchema = $this->fetchRef($extends, $sourceUri);
$refSchema = $this->getUriRetriever()->resolvePointer($refSchema, $extends);
self::merge($schema, $refSchema);
}
}
} else {
$refSchema = $this->fetchRef($schema->extends, $sourceUri);
$refSchema = $this->getUriRetriever()->resolvePointer($refSchema, $schema->extends);
self::merge($schema, $refSchema);
}
}
unset($schema->extends);
}

/**
* recursively merges all fields of $b into $a
* fields in a win
* @param stdObject $a
* @param stdObject $b
* @return void
*/
static function merge($a, $b) {
if(!$b) return;
foreach($b as $k=>$v) {
if(is_object($v)) {
if(!isset($a->$k)) $a->$k = new \stdClass;
self::merge($a->$k, $b->$k);
} elseif(is_array($v)) {
if(!isset($a->$k)) {
$a->$k = $b->$k;
} elseif(is_array($a->$k)) {
$a->$k = array_merge_recursive($b->$k, $a->$k); // a should win
}
} else {
if(isset($a->$k)) continue; // a should win
$a->$k = $b->$k;
}
}
}

/**
* Set URI Retriever for use with the Ref Resolver
*
* @param UriRetriever $retriever
* @param Uri\UriRetriever $retriever
* @return $this for chaining
*/
public function setUriRetriever(UriRetriever $retriever)
public function setUriRetriever(Uri\UriRetriever $retriever)
{
$this->uriRetriever = $retriever;

Expand Down
Loading