Skip to content

Commit

Permalink
Add option to apply default values from the schema
Browse files Browse the repository at this point in the history
  • Loading branch information
erayd committed Jan 18, 2017
1 parent 325a0f8 commit 6df4e2b
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 1 deletion.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,38 @@ is_bool($request->processRefund); // true
is_int($request->refundAmount); // true
```

### Default Values

If your schema contains default values, you can have these automatically applied during validation:

```php
<?php

use JsonSchema\Validator;
use JsonSchema\Constraints\Constraint;

$request = (object)[
'refundAmount'=>17
];

$validator = new Validator();

$validator->coerceDefault($request, (object)[
"type"=>"object",
"properties"=>(object)[
"processRefund"=>(object)[
"type"=>"boolean",
"default"=>true
]
]
]); //validates, and sets defaults for missing properties

is_bool($request->processRefund); // true
$request->processRefund; // true
```

*Note that setting default values also enables type coercion.*

### With inline references

```php
Expand Down
1 change: 1 addition & 0 deletions src/JsonSchema/Constraints/Constraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ abstract class Constraint implements ConstraintInterface

const CHECK_MODE_NORMAL = 0x00000001;
const CHECK_MODE_TYPE_CAST = 0x00000002;
const CHECK_MODE_APPLY_DEFAULTS = 0x00000004;

/**
* @var Factory
Expand Down
15 changes: 15 additions & 0 deletions src/JsonSchema/Constraints/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use JsonSchema\SchemaStorageInterface;
use JsonSchema\Uri\UriRetriever;
use JsonSchema\UriRetrieverInterface;
use JsonSchema\Constraints\Constraint;

/**
* Factory for centralize constraint initialization.
Expand Down Expand Up @@ -148,4 +149,18 @@ public function getCheckMode()
{
return $this->checkMode;
}

/**
* Update apply defaults setting in checkmode
*
* @param boolean $applyDefaults
*/
public function setApplyDefaults($applyDefaults = true)
{
if ($applyDefaults) {
$this->checkMode |= Constraint::CHECK_MODE_APPLY_DEFAULTS;
} else {
$this->checkMode &= ~Constraint::CHECK_MODE_APPLY_DEFAULTS;
}
}
}
17 changes: 16 additions & 1 deletion src/JsonSchema/Constraints/UndefinedConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public function validateTypes(&$value, $schema = null, JsonPointer $path, $i = n
}

// check object
if ($this->getTypeCheck()->isObject($value)) {
if (TypeCheck\LooseTypeCheck::isObject($value)) { // Fixes failing assoc tests for default values - currently investigating
//if ($this->getTypeCheck()->isObject($value)) { // to find the root cause of this, noting all other assoc tests pass.
$this->checkObject(
$value,
isset($schema->properties) ? $this->factory->getSchemaStorage()->resolveRefSchema($schema->properties) : $schema,
Expand Down Expand Up @@ -118,6 +119,20 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer
}
}

// Apply default values
if ($coerce && $this->factory->getCheckMode() & self::CHECK_MODE_APPLY_DEFAULTS && isset($schema->properties)) {
$definition = $this->factory->getSchemaStorage()->resolveRefSchema($schema->properties);
foreach ($definition as $i => $propertyDefinition) {
if (isset($propertyDefinition->default)) {
if ($this->getTypeCheck()->isObject($value) && !property_exists($value, $i)) {
$value->$i = $propertyDefinition->default;
} elseif ($this->getTypeCheck()->isArray($value) && !array_key_exists($i, $value)) {
$value[$i] = $propertyDefinition->default;
}
}
}
}

// Verify required values
if ($this->getTypeCheck()->isObject($value)) {
if (!($value instanceof UndefinedConstraint) && isset($schema->required) && is_array($schema->required)) {
Expand Down
13 changes: 13 additions & 0 deletions src/JsonSchema/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,17 @@ public function coerce(&$value, $schema = null, JsonPointer $path = null, $i = n

$this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR));
}

/**
* Does everything that coerce does, but will also set values to their default, if the value is not
* set and a default is available in the schema. Note that the first argumen is passwd by
* reference, so you must pass in a variable.
*
* {@inheritDoc}
*/
public function coerceDefault(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
$this->factory->setApplyDefaults(true);
$this->coerce($value, $schema, $path, $i);
}
}
91 changes: 91 additions & 0 deletions tests/Constraints/DefaultPropertiesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace JsonSchema\Tests\Constraints;
use JsonSchema\SchemaStorage;
use JsonSchema\Validator;
use JsonSchema\Constraints\Constraint;
use JsonSchema\Constraints\Factory;

class DefaultPropertiesTest extends VeryBaseTestCase
{
public function getValidTests()
{
return [
[ // default value for top-level property
'{"propertyOne":"valueOne"}',
'{"properties":{"propertyTwo":{"default":"valueTwo"}}}',
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
],
[ // default value for sub-property
'{"propertyOne":{}}',
'{"properties":{"propertyOne":{"properties":{"propertyTwo":{"default":"valueTwo"}}}}}',
'{"propertyOne":{"propertyTwo":"valueTwo"}}'
],
[ // default value for sub-property with sibling
'{"propertyOne":{"propertyTwo":"valueTwo"}}',
'{"properties":{"propertyOne":{"properties":{"propertyThree":{"default":"valueThree"}}}}}',
'{"propertyOne":{"propertyTwo":"valueTwo","propertyThree":"valueThree"}}'
],
[ // default value for top-level property with type check
'{"propertyOne":"valueOne"}',
'{"properties":{"propertyTwo":{"default":"valueTwo","type":"string"}}}',
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
],
[ // default value for top-level property with v3 required check
'{"propertyOne":"valueOne"}',
'{"properties":{"propertyTwo":{"default":"valueTwo","required":"true"}}}',
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
],
[ // default value for top-level property with v4 required check
'{"propertyOne":"valueOne"}',
'{"properties":{"propertyTwo":{"default":"valueTwo"}},"required":["propertyTwo"]}',
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
],
[ //default value for an already set property
'{"propertyOne":"alreadySetValueOne"}',
'{"properties":{"propertyOne":{"default":"valueOne"}}}',
'{"propertyOne":"alreadySetValueOne"}'
]
];
}

/**
* @dataProvider getValidTests
*/
public function testValidCases($input, $schema, $expectOutput = null)
{
global $assoc;
if (is_string($input)) {
$assoc = false;
$inputDecoded = json_decode($input);
} else {
$assoc = true;
$inputDecoded = $input;
}

$validator = new Validator();
$validator->coerceDefault($inputDecoded, json_decode($schema));

$this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true));

if($expectOutput !== null)
$this->assertEquals($expectOutput, json_encode($inputDecoded));
}

/**
* @dataProvider getValidTests
*/
public function testValidCasesUsingAssoc($input, $schema, $expectOutput = null)
{
$input = json_decode($input, true);
self::testValidCases($input, $schema, $expectOutput);
}

}

0 comments on commit 6df4e2b

Please sign in to comment.