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 19, 2017
1 parent 325a0f8 commit 37a18d0
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 0 deletions.
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
1 change: 1 addition & 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
10 changes: 10 additions & 0 deletions src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ public static function propertyGet($value, $property)
return $value[$property];
}

public static function propertySet(&$value, $property, $data)
{
if (is_object($value)) {
$value->{$property} = $data;
} else {
$value[$property] = $data;
}
}


public static function propertyExists($value, $property)
{
if (is_object($value)) {
Expand Down
5 changes: 5 additions & 0 deletions src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public static function propertyGet($value, $property)
return $value->{$property};
}

public static function propertySet(&$value, $property, $data)
{
$value->{$property} = $data;
}

public static function propertyExists($value, $property)
{
return property_exists($value, $property);
Expand Down
2 changes: 2 additions & 0 deletions src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public static function isArray($value);

public static function propertyGet($value, $property);

public static function propertySet(&$value, $property, $data);

public static function propertyExists($value, $property);

public static function propertyCount($value);
Expand Down
27 changes: 27 additions & 0 deletions src/JsonSchema/Constraints/UndefinedConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,33 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer
}
}

// Apply default values from schema
if ($coerce && $this->factory->getCheckMode() & self::CHECK_MODE_APPLY_DEFAULTS) {
if ($this->getTypeCheck()->isObject($value) && isset($schema->properties)) {
foreach ($schema->properties as $i => $propertyDefinition) {
if (!$this->getTypeCheck()->propertyExists($value, $i) && isset($propertyDefinition->default)) {
$this->getTypeCheck()->propertySet($value, $i, $propertyDefinition->default);
}
}
} elseif ($this->getTypeCheck()->isArray($value)) {
if (isset($schema->properties)) {
foreach ($schema->properties as $i => $propertyDefinition) {
if (!isset($value[$i]) && isset($propertyDefinition->default)) {
$value[$i] = $propertyDefinition->default;
}
}
} elseif (isset($schema->items)) {
foreach ($schema->items as $i => $itemDefinition) {
if (!isset($value[$i]) && isset($itemDefinition->default)) {
$value[$i] = $itemDefinition->default;
}
}
}
} elseif (($value instanceof UndefinedConstraint || $value === null) && isset($schema->default)) {
$value = $schema->default;
}
}

// Verify required values
if ($this->getTypeCheck()->isObject($value)) {
if (!($value instanceof UndefinedConstraint) && isset($schema->required) && is_array($schema->required)) {
Expand Down
129 changes: 129 additions & 0 deletions tests/Constraints/DefaultPropertiesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?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 array(
array(// default value for entire object
'',
'{"default":"valueOne"}',
'"valueOne"'
),
array(// default value in an empty object
'{}',
'{"properties":{"propertyOne":{"default":"valueOne"}}}',
'{"propertyOne":"valueOne"}'
),
array(// default value for top-level property
'{"propertyOne":"valueOne"}',
'{"properties":{"propertyTwo":{"default":"valueTwo"}}}',
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
),
array(// fulfil required property with a default value
'{"propertyOne":"valueOne"}',
'{"properties":{"propertyTwo":{"required":true,"default":"valueTwo"}}}',
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
),
array(// default value for sub-property
'{"propertyOne":{}}',
'{"properties":{"propertyOne":{"properties":{"propertyTwo":{"default":"valueTwo"}}}}}',
'{"propertyOne":{"propertyTwo":"valueTwo"}}'
),
array(// default value for sub-property with sibling
'{"propertyOne":{"propertyTwo":"valueTwo"}}',
'{"properties":{"propertyOne":{"properties":{"propertyThree":{"default":"valueThree"}}}}}',
'{"propertyOne":{"propertyTwo":"valueTwo","propertyThree":"valueThree"}}'
),
array(// default value for top-level property with type check
'{"propertyOne":"valueOne"}',
'{"properties":{"propertyTwo":{"default":"valueTwo","type":"string"}}}',
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
),
array(// default value for top-level property with v3 required check
'{"propertyOne":"valueOne"}',
'{"properties":{"propertyTwo":{"default":"valueTwo","required":"true"}}}',
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
),
array(// default value for top-level property with v4 required check
'{"propertyOne":"valueOne"}',
'{"properties":{"propertyTwo":{"default":"valueTwo"}},"required":["propertyTwo"]}',
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
),
array(//default value for an already set property
'{"propertyOne":"alreadySetValueOne"}',
'{"properties":{"propertyOne":{"default":"valueOne"}}}',
'{"propertyOne":"alreadySetValueOne"}'
),
array(//default value is required
'{"propertyOne":"valueOne"}',
'{"properties":{"propertyTwo":{"default":"valueTwo","required":true}}}',
'{"propertyOne":"valueOne","propertyTwo":"valueTwo"}'
),
array(//default item value for an array
'["valueOne"]',
'{"type":"array","items":[{},{"type":"string","default":"valueTwo"}]}',
'["valueOne","valueTwo"]'
),
array(//default item value for an empty array
'[]',
'{"type":"array","items":[{"type":"string","default":"valueOne"}]}',
'["valueOne"]'
),
array(//property without a default available
'{"propertyOne":"alreadySetValueOne"}',
'{"properties":{"propertyOne":{"type":"string"}}}',
'{"propertyOne":"alreadySetValueOne"}'
)
);
}

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

if ($validator === null) {
$checkMode = Constraint::CHECK_MODE_APPLY_DEFAULTS;
$validator = new Validator(new Factory(null, null, $checkMode));
}
$validator->coerce($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);

$checkMode = Constraint::CHECK_MODE_APPLY_DEFAULTS | Constraint::CHECK_MODE_TYPE_CAST;
$validator = new Validator(new Factory(null, null, $checkMode));
self::testValidCases($input, $schema, $expectOutput, $validator);
}
}

0 comments on commit 37a18d0

Please sign in to comment.