Server IP : 213.176.29.180 / Your IP : 18.224.70.239 Web Server : Apache System : Linux 213.176.29.180.hostiran.name 4.18.0-553.22.1.el8_10.x86_64 #1 SMP Tue Sep 24 05:16:59 EDT 2024 x86_64 User : webtaragh ( 1001) PHP Version : 7.4.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON Directory (0750) : /home/webtaragh/ssl/../www/wp-includes/../ |
[ Home ] | [ C0mmand ] | [ Upload File ] |
---|
json-schema/bin/validate-json 0000644 00000014636 14736103246 0012226 0 ustar 00 #!/usr/bin/env php <?php /** * JSON schema validator * * @author Christian Weiske <christian.weiske@netresearch.de> */ /** * Dead simple autoloader * * @param string $className Name of class to load * * @return void */ function __autoload($className) { $className = ltrim($className, '\\'); $fileName = ''; $namespace = ''; if ($lastNsPos = strrpos($className, '\\')) { $namespace = substr($className, 0, $lastNsPos); $className = substr($className, $lastNsPos + 1); $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; } $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; if (stream_resolve_include_path($fileName)) { require_once $fileName; } } /** * Show the json parse error that happened last * * @return void */ function showJsonError() { $constants = get_defined_constants(true); $json_errors = array(); foreach ($constants['json'] as $name => $value) { if (!strncmp($name, 'JSON_ERROR_', 11)) { $json_errors[$value] = $name; } } echo 'JSON parse error: ' . $json_errors[json_last_error()] . "\n"; } function getUrlFromPath($path) { if (parse_url($path, PHP_URL_SCHEME) !== null) { //already an URL return $path; } if ($path{0} == '/') { //absolute path return 'file://' . $path; } //relative path: make absolute return 'file://' . getcwd() . '/' . $path; } /** * Take a HTTP header value and split it up into parts. * * @return array Key "_value" contains the main value, all others * as given in the header value */ function parseHeaderValue($headerValue) { if (strpos($headerValue, ';') === false) { return array('_value' => $headerValue); } $parts = explode(';', $headerValue); $arData = array('_value' => array_shift($parts)); foreach ($parts as $part) { list($name, $value) = explode('=', $part); $arData[$name] = trim($value, ' "\''); } return $arData; } // support running this tool from git checkout if (is_dir(__DIR__ . '/../src/JsonSchema')) { set_include_path(__DIR__ . '/../src' . PATH_SEPARATOR . get_include_path()); } $arOptions = array(); $arArgs = array(); array_shift($argv);//script itself foreach ($argv as $arg) { if ($arg{0} == '-') { $arOptions[$arg] = true; } else { $arArgs[] = $arg; } } if (count($arArgs) == 0 || isset($arOptions['--help']) || isset($arOptions['-h']) ) { echo <<<HLP Validate schema Usage: validate-json data.json or: validate-json data.json schema.json Options: --dump-schema Output full schema and exit --dump-schema-url Output URL of schema -h --help Show this help HLP; exit(1); } if (count($arArgs) == 1) { $pathData = $arArgs[0]; $pathSchema = null; } else { $pathData = $arArgs[0]; $pathSchema = getUrlFromPath($arArgs[1]); } $urlData = getUrlFromPath($pathData); $context = stream_context_create( array( 'http' => array( 'header' => array( 'Accept: */*', 'Connection: Close' ), 'max_redirects' => 5 ) ) ); $dataString = file_get_contents($pathData, false, $context); if ($dataString == '') { echo "Data file is not readable or empty.\n"; exit(3); } $data = json_decode($dataString); unset($dataString); if ($data === null) { echo "Error loading JSON data file\n"; showJsonError(); exit(5); } if ($pathSchema === null) { if (isset($http_response_header)) { array_shift($http_response_header);//HTTP/1.0 line foreach ($http_response_header as $headerLine) { list($hName, $hValue) = explode(':', $headerLine, 2); $hName = strtolower($hName); if ($hName == 'link') { //Link: <http://example.org/schema#>; rel="describedBy" $hParts = parseHeaderValue($hValue); if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') { $pathSchema = trim($hParts['_value'], ' <>'); } } else if ($hName == 'content-type') { //Content-Type: application/my-media-type+json; // profile=http://example.org/schema# $hParts = parseHeaderValue($hValue); if (isset($hParts['profile'])) { $pathSchema = $hParts['profile']; } } } } if (is_object($data) && property_exists($data, '$schema')) { $pathSchema = $data->{'$schema'}; } //autodetect schema if ($pathSchema === null) { echo "JSON data must be an object and have a \$schema property.\n"; echo "You can pass the schema file on the command line as well.\n"; echo "Schema autodetection failed.\n"; exit(6); } } if ($pathSchema{0} == '/') { $pathSchema = 'file://' . $pathSchema; } $resolver = new JsonSchema\Uri\UriResolver(); $retriever = new JsonSchema\Uri\UriRetriever(); try { $urlSchema = $resolver->resolve($pathSchema, $urlData); if (isset($arOptions['--dump-schema-url'])) { echo $urlSchema . "\n"; exit(); } $schema = $retriever->retrieve($urlSchema); if ($schema === null) { echo "Error loading JSON schema file\n"; echo $urlSchema . "\n"; showJsonError(); exit(2); } } catch (Exception $e) { echo "Error loading JSON schema file\n"; echo $urlSchema . "\n"; echo $e->getMessage() . "\n"; exit(2); } $refResolver = new JsonSchema\RefResolver($retriever); $refResolver->resolve($schema, $urlSchema); if (isset($arOptions['--dump-schema'])) { $options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; echo json_encode($schema, $options) . "\n"; exit(); } try { $validator = new JsonSchema\Validator(); $validator->check($data, $schema); if ($validator->isValid()) { echo "OK. The supplied JSON validates against the schema.\n"; } else { echo "JSON does not validate. Violations:\n"; foreach ($validator->getErrors() as $error) { echo sprintf("[%s] %s\n", $error['property'], $error['message']); } exit(23); } } catch (Exception $e) { echo "JSON does not validate. Error:\n"; echo $e->getMessage() . "\n"; echo "Error code: " . $e->getCode() . "\n"; exit(24); } ?> json-schema/src/JsonSchema/RefResolver.php 0000644 00000017717 14736103246 0014566 0 ustar 00 <?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; use JsonSchema\Exception\JsonDecodingException; use JsonSchema\Uri\Retrievers\UriRetrieverInterface; use JsonSchema\Uri\UriRetriever; /** * Take in an object that's a JSON schema and take care of all $ref references * * @author Tyler Akins <fidian@rumkin.com> * @see README.md */ class RefResolver { /** * HACK to prevent too many recursive expansions. * Happens e.g. when you want to validate a schema against the schema * definition. * * @var integer */ protected static $depth = 0; /** * maximum references depth * @var integer */ public static $maxDepth = 7; /** * @var UriRetrieverInterface */ protected $uriRetriever = null; /** * @var object */ protected $rootSchema = null; /** * @param UriRetriever $retriever */ public function __construct($retriever = null) { $this->uriRetriever = $retriever; } /** * Retrieves a given schema given a ref and a source URI * * @param string $ref Reference from schema * @param string $sourceUri URI where original schema was located * @return object Schema */ public function fetchRef($ref, $sourceUri) { $retriever = $this->getUriRetriever(); $jsonSchema = $retriever->retrieve($ref, $sourceUri); $this->resolve($jsonSchema); return $jsonSchema; } /** * Return the URI Retriever, defaulting to making a new one if one * was not yet set. * * @return UriRetriever */ public function getUriRetriever() { if (is_null($this->uriRetriever)) { $this->setUriRetriever(new UriRetriever); } return $this->uriRetriever; } /** * Resolves all $ref references for a given schema. Recurses through * the object to resolve references of any child schemas. * * The 'format' property is omitted because it isn't required for * validation. Theoretically, this class could be extended to look * for URIs in formats: "These custom formats MAY be expressed as * an URI, and this URI MAY reference a schema of that format." * * The 'id' property is not filled in, but that could be made to happen. * * @param object $schema JSON Schema to flesh out * @param string $sourceUri URI where this schema was located */ public function resolve($schema, $sourceUri = null) { if (self::$depth > self::$maxDepth) { self::$depth = 0; throw new JsonDecodingException(JSON_ERROR_DEPTH); } ++self::$depth; if (! is_object($schema)) { --self::$depth; return; } if (null === $sourceUri && ! empty($schema->id)) { $sourceUri = $schema->id; } if (null === $this->rootSchema) { $this->rootSchema = $schema; } // Resolve $ref first $this->resolveRef($schema, $sourceUri); // These properties are just schemas // eg. items can be a schema or an array of schemas foreach (array('additionalItems', 'additionalProperties', 'extends', 'items') as $propertyName) { $this->resolveProperty($schema, $propertyName, $sourceUri); } // These are all potentially arrays that contain schema objects // eg. type can be a value or an array of values/schemas // eg. items can be a schema or an array of schemas foreach (array('disallow', 'extends', 'items', 'type', 'allOf', 'anyOf', 'oneOf') as $propertyName) { $this->resolveArrayOfSchemas($schema, $propertyName, $sourceUri); } // These are all objects containing properties whose values are schemas foreach (array('dependencies', 'patternProperties', 'properties') as $propertyName) { $this->resolveObjectOfSchemas($schema, $propertyName, $sourceUri); } --self::$depth; } /** * Given an object and a property name, that property should be an * array whose values can be schemas. * * @param object $schema JSON Schema to flesh out * @param string $propertyName Property to work on * @param string $sourceUri URI where this schema was located */ public function resolveArrayOfSchemas($schema, $propertyName, $sourceUri) { if (! isset($schema->$propertyName) || ! is_array($schema->$propertyName)) { return; } foreach ($schema->$propertyName as $possiblySchema) { $this->resolve($possiblySchema, $sourceUri); } } /** * Given an object and a property name, that property should be an * object whose properties are schema objects. * * @param object $schema JSON Schema to flesh out * @param string $propertyName Property to work on * @param string $sourceUri URI where this schema was located */ public function resolveObjectOfSchemas($schema, $propertyName, $sourceUri) { if (! isset($schema->$propertyName) || ! is_object($schema->$propertyName)) { return; } foreach (get_object_vars($schema->$propertyName) as $possiblySchema) { $this->resolve($possiblySchema, $sourceUri); } } /** * Given an object and a property name, that property should be a * schema object. * * @param object $schema JSON Schema to flesh out * @param string $propertyName Property to work on * @param string $sourceUri URI where this schema was located */ public function resolveProperty($schema, $propertyName, $sourceUri) { if (! isset($schema->$propertyName)) { return; } $this->resolve($schema->$propertyName, $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 resolveRef($schema, $sourceUri) { $ref = '$ref'; if (empty($schema->$ref)) { return; } $splitRef = explode('#', $schema->$ref, 2); $refDoc = $splitRef[0]; $refPath = null; if (count($splitRef) === 2) { $refPath = explode('/', $splitRef[1]); array_shift($refPath); } if (empty($refDoc) && empty($refPath)) { // TODO: Not yet implemented - root pointer ref, causes recursion issues return; } if (!empty($refDoc)) { $refSchema = $this->fetchRef($refDoc, $sourceUri); } else { $refSchema = $this->rootSchema; } if (null !== $refPath) { $refSchema = $this->resolveRefSegment($refSchema, $refPath); } unset($schema->$ref); // Augment the current $schema object with properties fetched foreach (get_object_vars($refSchema) as $prop => $value) { $schema->$prop = $value; } } /** * Set URI Retriever for use with the Ref Resolver * * @param UriRetriever $retriever * @return $this for chaining */ public function setUriRetriever(UriRetriever $retriever) { $this->uriRetriever = $retriever; return $this; } protected function resolveRefSegment($data, $pathParts) { foreach ($pathParts as $path) { $path = strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%')); if (is_array($data)) { $data = $data[$path]; } else { $data = $data->{$path}; } } return $data; } } json-schema/src/JsonSchema/UriResolverInterface.php 0000644 00000001022 14736103246 0016410 0 ustar 00 <?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; /** * @package JsonSchema */ interface UriResolverInterface { /** * Resolves a URI * * @param string $uri Absolute or relative * @param null|string $baseUri Optional base URI * * @return string Absolute URI */ public function resolve($uri, $baseUri = null); } json-schema/src/JsonSchema/SchemaStorage.php 0000644 00000012453 14736103246 0015045 0 ustar 00 <?php namespace JsonSchema; use JsonSchema\Constraints\BaseConstraint; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\UnresolvableJsonPointerException; use JsonSchema\Uri\UriResolver; use JsonSchema\Uri\UriRetriever; class SchemaStorage implements SchemaStorageInterface { const INTERNAL_PROVIDED_SCHEMA_URI = 'internal://provided-schema/'; protected $uriRetriever; protected $uriResolver; protected $schemas = array(); public function __construct( UriRetrieverInterface $uriRetriever = null, UriResolverInterface $uriResolver = null ) { $this->uriRetriever = $uriRetriever ?: new UriRetriever(); $this->uriResolver = $uriResolver ?: new UriResolver(); } /** * @return UriRetrieverInterface */ public function getUriRetriever() { return $this->uriRetriever; } /** * @return UriResolverInterface */ public function getUriResolver() { return $this->uriResolver; } /** * {@inheritdoc} */ public function addSchema($id, $schema = null) { if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) { // if the schema was user-provided to Validator and is still null, then assume this is // what the user intended, as there's no way for us to retrieve anything else. User-supplied // schemas do not have an associated URI when passed via Validator::validate(). $schema = $this->uriRetriever->retrieve($id); } // cast array schemas to object if (is_array($schema)) { $schema = BaseConstraint::arrayToObjectRecursive($schema); } // workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format) // see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367 if (is_object($schema) && property_exists($schema, 'id')) { if ($schema->id == 'http://json-schema.org/draft-04/schema#') { $schema->properties->id->format = 'uri-reference'; } elseif ($schema->id == 'http://json-schema.org/draft-03/schema#') { $schema->properties->id->format = 'uri-reference'; $schema->properties->{'$ref'}->format = 'uri-reference'; } } // resolve references $this->expandRefs($schema, $id); $this->schemas[$id] = $schema; } /** * Recursively resolve all references against the provided base * * @param mixed $schema * @param string $base */ private function expandRefs(&$schema, $base = null) { if (!is_object($schema)) { if (is_array($schema)) { foreach ($schema as &$member) { $this->expandRefs($member, $base); } } return; } if (property_exists($schema, 'id') && is_string($schema->id) && $base != $schema->id) { $base = $this->uriResolver->resolve($schema->id, $base); } if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) { $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $base)); $schema->{'$ref'} = (string) $refPointer; } foreach ($schema as &$member) { $this->expandRefs($member, $base); } } /** * {@inheritdoc} */ public function getSchema($id) { if (!array_key_exists($id, $this->schemas)) { $this->addSchema($id); } return $this->schemas[$id]; } /** * {@inheritdoc} */ public function resolveRef($ref) { $jsonPointer = new JsonPointer($ref); // resolve filename for pointer $fileName = $jsonPointer->getFilename(); if (!strlen($fileName)) { throw new UnresolvableJsonPointerException(sprintf( "Could not resolve fragment '%s': no file is defined", $jsonPointer->getPropertyPathAsString() )); } // get & process the schema $refSchema = $this->getSchema($fileName); foreach ($jsonPointer->getPropertyPaths() as $path) { if (is_object($refSchema) && property_exists($refSchema, $path)) { $refSchema = $this->resolveRefSchema($refSchema->{$path}); } elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) { $refSchema = $this->resolveRefSchema($refSchema[$path]); } else { throw new UnresolvableJsonPointerException(sprintf( 'File: %s is found, but could not resolve fragment: %s', $jsonPointer->getFilename(), $jsonPointer->getPropertyPathAsString() )); } } return $refSchema; } /** * {@inheritdoc} */ public function resolveRefSchema($refSchema) { if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) { $newSchema = $this->resolveRef($refSchema->{'$ref'}); $refSchema = (object) (get_object_vars($refSchema) + get_object_vars($newSchema)); unset($refSchema->{'$ref'}); } return $refSchema; } } json-schema/src/JsonSchema/Validator.php 0000644 00000005356 14736103246 0014251 0 ustar 00 <?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; use JsonSchema\Constraints\BaseConstraint; use JsonSchema\Constraints\Constraint; /** * A JsonSchema Constraint * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> * * @see README.md */ class Validator extends BaseConstraint { const SCHEMA_MEDIA_TYPE = 'application/schema+json'; const ERROR_NONE = 0x00000000; const ERROR_ALL = 0xFFFFFFFF; const ERROR_DOCUMENT_VALIDATION = 0x00000001; const ERROR_SCHEMA_VALIDATION = 0x00000002; /** * 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. * * Note that the first argument is passed by reference, so you must pass in a variable. */ public function validate(&$value, $schema = null, $checkMode = null) { // make sure $schema is an object if (is_array($schema)) { $schema = self::arrayToObjectRecursive($schema); } // set checkMode $initialCheckMode = $this->factory->getConfig(); if ($checkMode !== null) { $this->factory->setConfig($checkMode); } // add provided schema to SchemaStorage with internal URI to allow internal $ref resolution if (is_object($schema) && property_exists($schema, 'id')) { $schemaURI = $schema->id; } else { $schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI; } $this->factory->getSchemaStorage()->addSchema($schemaURI, $schema); $validator = $this->factory->createInstanceFor('schema'); $validator->check( $value, $this->factory->getSchemaStorage()->getSchema($schemaURI) ); $this->factory->setConfig($initialCheckMode); $this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR)); return $validator->getErrorMask(); } /** * Alias to validate(), to maintain backwards-compatibility with the previous API */ public function check($value, $schema) { return $this->validate($value, $schema); } /** * Alias to validate(), to maintain backwards-compatibility with the previous API */ public function coerce(&$value, $schema) { return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES); } } json-schema/src/JsonSchema/Iterator/ObjectIterator.php 0000644 00000005347 14736103246 0017035 0 ustar 00 <?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\Iterator; /** * @package JsonSchema\Iterator * * @author Joost Nijhuis <jnijhuis81@gmail.com> */ class ObjectIterator implements \Iterator, \Countable { /** @var object */ private $object; /** @var int */ private $position = 0; /** @var array */ private $data = array(); /** @var bool */ private $initialized = false; /** * @param object $object */ public function __construct($object) { $this->object = $object; } /** * {@inheritdoc} */ public function current() { $this->initialize(); return $this->data[$this->position]; } /** * {@inheritdoc} */ public function next() { $this->initialize(); $this->position++; } /** * {@inheritdoc} */ public function key() { $this->initialize(); return $this->position; } /** * {@inheritdoc} */ public function valid() { $this->initialize(); return isset($this->data[$this->position]); } /** * {@inheritdoc} */ public function rewind() { $this->initialize(); $this->position = 0; } /** * {@inheritdoc} */ public function count() { $this->initialize(); return count($this->data); } /** * Initializer */ private function initialize() { if (!$this->initialized) { $this->data = $this->buildDataFromObject($this->object); $this->initialized = true; } } /** * @param object $object * * @return array */ private function buildDataFromObject($object) { $result = array(); $stack = new \SplStack(); $stack->push($object); while (!$stack->isEmpty()) { $current = $stack->pop(); if (is_object($current)) { array_push($result, $current); } foreach ($this->getDataFromItem($current) as $propertyName => $propertyValue) { if (is_object($propertyValue) || is_array($propertyValue)) { $stack->push($propertyValue); } } } return $result; } /** * @param object|array $item * * @return array */ private function getDataFromItem($item) { if (!is_object($item) && !is_array($item)) { return array(); } return is_object($item) ? get_object_vars($item) : $item; } } json-schema/src/JsonSchema/SchemaStorageInterface.php 0000644 00000001442 14736103246 0016662 0 ustar 00 <?php namespace JsonSchema; interface SchemaStorageInterface { /** * Adds schema with given identifier * * @param string $id * @param object $schema */ public function addSchema($id, $schema = null); /** * Returns schema for given identifier, or null if it does not exist * * @param string $id * * @return object */ public function getSchema($id); /** * Returns schema for given reference with all sub-references resolved * * @param string $ref * * @return object */ public function resolveRef($ref); /** * Returns schema referenced by '$ref' property * * @param mixed $refSchema * * @return object */ public function resolveRefSchema($refSchema); } json-schema/src/JsonSchema/Entity/JsonPointer.php 0000644 00000006476 14736103246 0016056 0 ustar 00 <?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\Entity; use JsonSchema\Exception\InvalidArgumentException; /** * @package JsonSchema\Entity * * @author Joost Nijhuis <jnijhuis81@gmail.com> */ class JsonPointer { /** @var string */ private $filename; /** @var string[] */ private $propertyPaths = array(); /** * @var bool Whether the value at this path was set from a schema default */ private $fromDefault = false; /** * @param string $value * * @throws InvalidArgumentException when $value is not a string */ public function __construct($value) { if (!is_string($value)) { throw new InvalidArgumentException('Ref value must be a string'); } $splitRef = explode('#', $value, 2); $this->filename = $splitRef[0]; if (array_key_exists(1, $splitRef)) { $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]); } } /** * @param string $propertyPathString * * @return string[] */ private function decodePropertyPaths($propertyPathString) { $paths = array(); foreach (explode('/', trim($propertyPathString, '/')) as $path) { $path = $this->decodePath($path); if (is_string($path) && '' !== $path) { $paths[] = $path; } } return $paths; } /** * @return array */ private function encodePropertyPaths() { return array_map( array($this, 'encodePath'), $this->getPropertyPaths() ); } /** * @param string $path * * @return string */ private function decodePath($path) { return strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%')); } /** * @param string $path * * @return string */ private function encodePath($path) { return strtr($path, array('/' => '~1', '~' => '~0', '%' => '%25')); } /** * @return string */ public function getFilename() { return $this->filename; } /** * @return string[] */ public function getPropertyPaths() { return $this->propertyPaths; } /** * @param array $propertyPaths * * @return JsonPointer */ public function withPropertyPaths(array $propertyPaths) { $new = clone $this; $new->propertyPaths = $propertyPaths; return $new; } /** * @return string */ public function getPropertyPathAsString() { return rtrim('#/' . implode('/', $this->encodePropertyPaths()), '/'); } /** * @return string */ public function __toString() { return $this->getFilename() . $this->getPropertyPathAsString(); } /** * Mark the value at this path as being set from a schema default */ public function setFromDefault() { $this->fromDefault = true; } /** * Check whether the value at this path was set from a schema default * * @return bool */ public function fromDefault() { return $this->fromDefault; } } json-schema/src/JsonSchema/Rfc3339.php 0000644 00000001566 14736103246 0013357 0 ustar 00 <?php namespace JsonSchema; class Rfc3339 { const REGEX = '/^(\d{4}-\d{2}-\d{2}[T ]{1}\d{2}:\d{2}:\d{2})(\.\d+)?(Z|([+-]\d{2}):?(\d{2}))$/'; /** * Try creating a DateTime instance * * @param string $string * * @return \DateTime|null */ public static function createFromString($string) { if (!preg_match(self::REGEX, strtoupper($string), $matches)) { return null; } $dateAndTime = $matches[1]; $microseconds = $matches[2] ?: '.000000'; $timeZone = 'Z' !== $matches[3] ? $matches[4] . ':' . $matches[5] : '+00:00'; $dateFormat = strpos($dateAndTime, 'T') === false ? 'Y-m-d H:i:s.uP' : 'Y-m-d\TH:i:s.uP'; $dateTime = \DateTime::createFromFormat($dateFormat, $dateAndTime . $microseconds . $timeZone, new \DateTimeZone('UTC')); return $dateTime ?: null; } } json-schema/src/JsonSchema/Uri/UriResolver.php 0000644 00000011073 14736103246 0015335 0 ustar 00 <?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\Uri; use JsonSchema\Exception\UriResolverException; use JsonSchema\UriResolverInterface; /** * Resolves JSON Schema URIs * * @author Sander Coolen <sander@jibber.nl> */ class UriResolver implements UriResolverInterface { /** * Parses a URI into five main components * * @param string $uri * * @return array */ public function parse($uri) { preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match); $components = array(); if (5 < count($match)) { $components = array( 'scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5] ); } if (7 < count($match)) { $components['query'] = $match[7]; } if (9 < count($match)) { $components['fragment'] = $match[9]; } return $components; } /** * Builds a URI based on n array with the main components * * @param array $components * * @return string */ public function generate(array $components) { $uri = $components['scheme'] . '://' . $components['authority'] . $components['path']; if (array_key_exists('query', $components) && strlen($components['query'])) { $uri .= '?' . $components['query']; } if (array_key_exists('fragment', $components)) { $uri .= '#' . $components['fragment']; } return $uri; } /** * {@inheritdoc} */ public function resolve($uri, $baseUri = null) { // treat non-uri base as local file path if ( !is_null($baseUri) && !filter_var($baseUri, \FILTER_VALIDATE_URL) && !preg_match('|^[^/]+://|u', $baseUri) ) { if (is_file($baseUri)) { $baseUri = 'file://' . realpath($baseUri); } elseif (is_dir($baseUri)) { $baseUri = 'file://' . realpath($baseUri) . '/'; } else { $baseUri = 'file://' . getcwd() . '/' . $baseUri; } } if ($uri == '') { return $baseUri; } $components = $this->parse($uri); $path = $components['path']; if (!empty($components['scheme'])) { return $uri; } $baseComponents = $this->parse($baseUri); $basePath = $baseComponents['path']; $baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath); if (isset($components['fragment'])) { $baseComponents['fragment'] = $components['fragment']; } return $this->generate($baseComponents); } /** * Tries to glue a relative path onto an absolute one * * @param string $relativePath * @param string $basePath * * @throws UriResolverException * * @return string Merged path */ public static function combineRelativePathWithBasePath($relativePath, $basePath) { $relativePath = self::normalizePath($relativePath); if ($relativePath == '') { return $basePath; } if ($relativePath[0] == '/') { return $relativePath; } $basePathSegments = explode('/', $basePath); preg_match('|^/?(\.\./(?:\./)*)*|', $relativePath, $match); $numLevelUp = strlen($match[0]) /3 + 1; if ($numLevelUp >= count($basePathSegments)) { throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath)); } $basePathSegments = array_slice($basePathSegments, 0, -$numLevelUp); $path = preg_replace('|^/?(\.\./(\./)*)*|', '', $relativePath); return implode('/', $basePathSegments) . '/' . $path; } /** * Normalizes a URI path component by removing dot-slash and double slashes * * @param string $path * * @return string */ private static function normalizePath($path) { $path = preg_replace('|((?<!\.)\./)*|', '', $path); $path = preg_replace('|//|', '/', $path); return $path; } /** * @param string $uri * * @return bool */ public function isValid($uri) { $components = $this->parse($uri); return !empty($components); } } json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php 0000644 00000001162 14736103246 0020637 0 ustar 00 <?php /** * JsonSchema * * @filesource */ namespace JsonSchema\Uri\Retrievers; /** * AbstractRetriever implements the default shared behavior * that all descendant Retrievers should inherit * * @author Steven Garcia <webwhammy@gmail.com> */ abstract class AbstractRetriever implements UriRetrieverInterface { /** * Media content type * * @var string */ protected $contentType; /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType() */ public function getContentType() { return $this->contentType; } } json-schema/src/JsonSchema/Uri/Retrievers/Curl.php 0000644 00000004157 14736103246 0016120 0 ustar 00 <?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\Uri\Retrievers; use JsonSchema\Exception\RuntimeException; use JsonSchema\Validator; /** * Tries to retrieve JSON schemas from a URI using cURL library * * @author Sander Coolen <sander@jibber.nl> */ class Curl extends AbstractRetriever { protected $messageBody; public function __construct() { if (!function_exists('curl_init')) { // Cannot test this, because curl_init is present on all test platforms plus mock throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore } } /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() */ public function retrieve($uri) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $uri); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: ' . Validator::SCHEMA_MEDIA_TYPE)); $response = curl_exec($ch); if (false === $response) { throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found'); } $this->fetchMessageBody($response); $this->fetchContentType($response); curl_close($ch); return $this->messageBody; } /** * @param string $response cURL HTTP response */ private function fetchMessageBody($response) { preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match); $this->messageBody = $match[1]; } /** * @param string $response cURL HTTP response * * @return bool Whether the Content-Type header was found or not */ protected function fetchContentType($response) { if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) { $this->contentType = trim($match[1]); return true; } return false; } } json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php 0000644 00000005152 14736103246 0020244 0 ustar 00 <?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\Uri\Retrievers; use JsonSchema\Exception\ResourceNotFoundException; /** * Tries to retrieve JSON schemas from a URI using file_get_contents() * * @author Sander Coolen <sander@jibber.nl> */ class FileGetContents extends AbstractRetriever { protected $messageBody; /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() */ public function retrieve($uri) { $errorMessage = null; set_error_handler(function ($errno, $errstr) use (&$errorMessage) { $errorMessage = $errstr; }); $response = file_get_contents($uri); restore_error_handler(); if ($errorMessage) { throw new ResourceNotFoundException($errorMessage); } if (false === $response) { throw new ResourceNotFoundException('JSON schema not found at ' . $uri); } if ($response == '' && substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/' ) { throw new ResourceNotFoundException('JSON schema not found at ' . $uri); } $this->messageBody = $response; if (!empty($http_response_header)) { // $http_response_header cannot be tested, because it's defined in the method's local scope // See http://php.net/manual/en/reserved.variables.httpresponseheader.php for more info. $this->fetchContentType($http_response_header); // @codeCoverageIgnore } else { // @codeCoverageIgnore // Could be a "file://" url or something else - fake up the response $this->contentType = null; } return $this->messageBody; } /** * @param array $headers HTTP Response Headers * * @return bool Whether the Content-Type header was found or not */ private function fetchContentType(array $headers) { foreach ($headers as $header) { if ($this->contentType = self::getContentTypeMatchInHeader($header)) { return true; } } return false; } /** * @param string $header * * @return string|null */ protected static function getContentTypeMatchInHeader($header) { if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) { return trim($match[1]); } return null; } } json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php 0000644 00000002424 14736103246 0020252 0 ustar 00 <?php namespace JsonSchema\Uri\Retrievers; use JsonSchema\Validator; /** * URI retrieved based on a predefined array of schemas * * @example * * $retriever = new PredefinedArray(array( * 'http://acme.com/schemas/person#' => '{ ... }', * 'http://acme.com/schemas/address#' => '{ ... }', * )) * * $schema = $retriever->retrieve('http://acme.com/schemas/person#'); */ class PredefinedArray extends AbstractRetriever { /** * Contains schemas as URI => JSON * * @var array */ private $schemas; /** * Constructor * * @param array $schemas * @param string $contentType */ public function __construct(array $schemas, $contentType = Validator::SCHEMA_MEDIA_TYPE) { $this->schemas = $schemas; $this->contentType = $contentType; } /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() */ public function retrieve($uri) { if (!array_key_exists($uri, $this->schemas)) { throw new \JsonSchema\Exception\ResourceNotFoundException(sprintf( 'The JSON schema "%s" was not found.', $uri )); } return $this->schemas[$uri]; } } json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php 0000644 00000001352 14736103246 0021455 0 ustar 00 <?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\Uri\Retrievers; /** * Interface for URI retrievers * * @author Sander Coolen <sander@jibber.nl> */ interface UriRetrieverInterface { /** * Retrieve a schema from the specified URI * * @param string $uri URI that resolves to a JSON schema * * @throws \JsonSchema\Exception\ResourceNotFoundException * * @return mixed string|null */ public function retrieve($uri); /** * Get media content type * * @return string */ public function getContentType(); } json-schema/src/JsonSchema/Uri/UriRetriever.php 0000644 00000022272 14736103246 0015506 0 ustar 00 <?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\Uri; use JsonSchema\Exception\InvalidSchemaMediaTypeException; use JsonSchema\Exception\JsonDecodingException; use JsonSchema\Exception\ResourceNotFoundException; use JsonSchema\Uri\Retrievers\FileGetContents; use JsonSchema\Uri\Retrievers\UriRetrieverInterface; use JsonSchema\UriRetrieverInterface as BaseUriRetrieverInterface; use JsonSchema\Validator; /** * Retrieves JSON Schema URIs * * @author Tyler Akins <fidian@rumkin.com> */ class UriRetriever implements BaseUriRetrieverInterface { /** * @var array Map of URL translations */ protected $translationMap = array( // use local copies of the spec schemas '|^https?://json-schema.org/draft-(0[34])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json' ); /** * @var array A list of endpoints for media type check exclusion */ protected $allowedInvalidContentTypeEndpoints = array( 'http://json-schema.org/', 'https://json-schema.org/' ); /** * @var null|UriRetrieverInterface */ protected $uriRetriever = null; /** * @var array|object[] * * @see loadSchema */ private $schemaCache = array(); /** * Adds an endpoint to the media type validation exclusion list * * @param string $endpoint */ public function addInvalidContentTypeEndpoint($endpoint) { $this->allowedInvalidContentTypeEndpoints[] = $endpoint; } /** * Guarantee the correct media type was encountered * * @param UriRetrieverInterface $uriRetriever * @param string $uri * * @return bool|void */ public function confirmMediaType($uriRetriever, $uri) { $contentType = $uriRetriever->getContentType(); if (is_null($contentType)) { // Well, we didn't get an invalid one return; } if (in_array($contentType, array(Validator::SCHEMA_MEDIA_TYPE, 'application/json'))) { return; } foreach ($this->allowedInvalidContentTypeEndpoints as $endpoint) { if (strpos($uri, $endpoint) === 0) { return true; } } throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE)); } /** * Get a URI Retriever * * If none is specified, sets a default FileGetContents retriever and * returns that object. * * @return UriRetrieverInterface */ public function getUriRetriever() { if (is_null($this->uriRetriever)) { $this->setUriRetriever(new FileGetContents()); } return $this->uriRetriever; } /** * Resolve a schema based on pointer * * URIs can have a fragment at the end in the format of * #/path/to/object and we are to look up the 'path' property of * the first object then the 'to' and 'object' properties. * * @param object $jsonSchema JSON Schema contents * @param string $uri JSON Schema URI * * @throws ResourceNotFoundException * * @return object JSON Schema after walking down the fragment pieces */ public function resolvePointer($jsonSchema, $uri) { $resolver = new UriResolver(); $parsed = $resolver->parse($uri); if (empty($parsed['fragment'])) { return $jsonSchema; } $path = explode('/', $parsed['fragment']); while ($path) { $pathElement = array_shift($path); if (!empty($pathElement)) { $pathElement = str_replace('~1', '/', $pathElement); $pathElement = str_replace('~0', '~', $pathElement); if (!empty($jsonSchema->$pathElement)) { $jsonSchema = $jsonSchema->$pathElement; } else { throw new ResourceNotFoundException( 'Fragment "' . $parsed['fragment'] . '" not found' . ' in ' . $uri ); } if (!is_object($jsonSchema)) { throw new ResourceNotFoundException( 'Fragment part "' . $pathElement . '" is no object ' . ' in ' . $uri ); } } } return $jsonSchema; } /** * {@inheritdoc} */ public function retrieve($uri, $baseUri = null, $translate = true) { $resolver = new UriResolver(); $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri); //fetch URL without #fragment $arParts = $resolver->parse($resolvedUri); if (isset($arParts['fragment'])) { unset($arParts['fragment']); $fetchUri = $resolver->generate($arParts); } // apply URI translations if ($translate) { $fetchUri = $this->translate($fetchUri); } $jsonSchema = $this->loadSchema($fetchUri); // Use the JSON pointer if specified $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri); if ($jsonSchema instanceof \stdClass) { $jsonSchema->id = $resolvedUri; } return $jsonSchema; } /** * Fetch a schema from the given URI, json-decode it and return it. * Caches schema objects. * * @param string $fetchUri Absolute URI * * @return object JSON schema object */ protected function loadSchema($fetchUri) { if (isset($this->schemaCache[$fetchUri])) { return $this->schemaCache[$fetchUri]; } $uriRetriever = $this->getUriRetriever(); $contents = $this->uriRetriever->retrieve($fetchUri); $this->confirmMediaType($uriRetriever, $fetchUri); $jsonSchema = json_decode($contents); if (JSON_ERROR_NONE < $error = json_last_error()) { throw new JsonDecodingException($error); } $this->schemaCache[$fetchUri] = $jsonSchema; return $jsonSchema; } /** * Set the URI Retriever * * @param UriRetrieverInterface $uriRetriever * * @return $this for chaining */ public function setUriRetriever(UriRetrieverInterface $uriRetriever) { $this->uriRetriever = $uriRetriever; return $this; } /** * Parses a URI into five main components * * @param string $uri * * @return array */ public function parse($uri) { preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match); $components = array(); if (5 < count($match)) { $components = array( 'scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5] ); } if (7 < count($match)) { $components['query'] = $match[7]; } if (9 < count($match)) { $components['fragment'] = $match[9]; } return $components; } /** * Builds a URI based on n array with the main components * * @param array $components * * @return string */ public function generate(array $components) { $uri = $components['scheme'] . '://' . $components['authority'] . $components['path']; if (array_key_exists('query', $components)) { $uri .= $components['query']; } if (array_key_exists('fragment', $components)) { $uri .= $components['fragment']; } return $uri; } /** * Resolves a URI * * @param string $uri Absolute or relative * @param string $baseUri Optional base URI * * @return string */ public function resolve($uri, $baseUri = null) { $components = $this->parse($uri); $path = $components['path']; if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) { return $uri; } $baseComponents = $this->parse($baseUri); $basePath = $baseComponents['path']; $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath); return $this->generate($baseComponents); } /** * @param string $uri * * @return bool */ public function isValid($uri) { $components = $this->parse($uri); return !empty($components); } /** * Set a URL translation rule */ public function setTranslation($from, $to) { $this->translationMap[$from] = $to; } /** * Apply URI translation rules */ public function translate($uri) { foreach ($this->translationMap as $from => $to) { $uri = preg_replace($from, $to, $uri); } // translate references to local files within the json-schema package $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri); return $uri; } } json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php 0000644 00000000524 14736103246 0021375 0 ustar 00 <?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\Exception; /** * Wrapper for the ResourceNotFoundException */ class ResourceNotFoundException extends RuntimeException { } json-schema/src/JsonSchema/Exception/JsonDecodingException.php 0000644 00000002333 14736103246 0020477 0 ustar 00 <?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\Exception; /** * Wrapper for the JsonDecodingException */ class JsonDecodingException extends RuntimeException { public function __construct($code = JSON_ERROR_NONE, \Exception $previous = null) { switch ($code) { case JSON_ERROR_DEPTH: $message = 'The maximum stack depth has been exceeded'; break; case JSON_ERROR_STATE_MISMATCH: $message = 'Invalid or malformed JSON'; break; case JSON_ERROR_CTRL_CHAR: $message = 'Control character error, possibly incorrectly encoded'; break; case JSON_ERROR_UTF8: $message = 'Malformed UTF-8 characters, possibly incorrectly encoded'; break; case JSON_ERROR_SYNTAX: $message = 'JSON syntax is malformed'; break; default: $message = 'Syntax error'; } parent::__construct($message, $code, $previous); } } json-schema/src/JsonSchema/Exception/ValidationException.php 0000644 00000000431 14736103246 0020220 0 ustar 00 <?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\Exception; class ValidationException extends RuntimeException { } json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php 0000644 00000000534 14736103246 0021361 0 ustar 00 <?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\Exception; /** * Wrapper for the InvalidSourceUriException */ class InvalidSourceUriException extends InvalidArgumentException { } json-schema/src/JsonSchema/Exception/ExceptionInterface.php 0000644 00000000111 14736103246 0020021 0 ustar 00 <?php namespace JsonSchema\Exception; interface ExceptionInterface { } json-schema/src/JsonSchema/Exception/UriResolverException.php 0000644 00000000512 14736103246 0020407 0 ustar 00 <?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\Exception; /** * Wrapper for the UriResolverException */ class UriResolverException extends RuntimeException { } json-schema/src/JsonSchema/Exception/InvalidArgumentException.php 0000644 00000000571 14736103246 0021224 0 ustar 00 <?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\Exception; /** * Wrapper for the InvalidArgumentException */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } json-schema/src/JsonSchema/Exception/InvalidConfigException.php 0000644 00000000521 14736103246 0020642 0 ustar 00 <?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\Exception; /** * Wrapper for the ResourceNotFoundException */ class InvalidConfigException extends RuntimeException { } json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php 0000644 00000000527 14736103246 0022445 0 ustar 00 <?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\Exception; /** * Wrapper for the InvalidSchemaMediaType */ class InvalidSchemaMediaTypeException extends RuntimeException { } json-schema/src/JsonSchema/Exception/InvalidSchemaException.php 0000644 00000000516 14736103246 0020641 0 ustar 00 <?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\Exception; /** * Wrapper for the InvalidSchemaMediaType */ class InvalidSchemaException extends RuntimeException { } json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php 0000644 00000000612 14736103246 0022763 0 ustar 00 <?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\Exception; /** * @package JsonSchema\Exception * * @author Joost Nijhuis <jnijhuis81@gmail.com> */ class UnresolvableJsonPointerException extends InvalidArgumentException { } json-schema/src/JsonSchema/Exception/RuntimeException.php 0000644 00000000541 14736103246 0017553 0 ustar 00 <?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\Exception; /** * Wrapper for the RuntimeException */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } json-schema/src/JsonSchema/UriRetrieverInterface.php 0000644 00000001005 14736103246 0016557 0 ustar 00 <?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; /** * @package JsonSchema */ interface UriRetrieverInterface { /** * Retrieve a URI * * @param string $uri JSON Schema URI * @param null|string $baseUri * * @return object JSON Schema contents */ public function retrieve($uri, $baseUri = null); } json-schema/src/JsonSchema/Constraints/ObjectConstraint.php 0000644 00000017617 14736103246 0020111 0 ustar 00 <?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\Constraints; use JsonSchema\Entity\JsonPointer; /** * The ObjectConstraint Constraints, validates an object against a given schema * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class ObjectConstraint extends Constraint { /** * @var array List of properties to which a default value has been applied */ protected $appliedDefaults = array(); /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $properties = null, $additionalProp = null, $patternProperties = null, $appliedDefaults = array()) { if ($element instanceof UndefinedConstraint) { return; } $this->appliedDefaults = $appliedDefaults; $matches = array(); if ($patternProperties) { // validate the element pattern properties $matches = $this->validatePatternProperties($element, $path, $patternProperties); } if ($properties) { // validate the element properties $this->validateProperties($element, $properties, $path); } // validate additional element properties & constraints $this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp); } public function validatePatternProperties($element, JsonPointer $path = null, $patternProperties) { $try = array('/', '#', '+', '~', '%'); $matches = array(); foreach ($patternProperties as $pregex => $schema) { $delimiter = '/'; // Choose delimiter. Necessary for patterns like ^/ , otherwise you get error foreach ($try as $delimiter) { if (strpos($pregex, $delimiter) === false) { // safe to use break; } } // Validate the pattern before using it to test for matches if (@preg_match($delimiter . $pregex . $delimiter . 'u', '') === false) { $this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex)); continue; } foreach ($element as $i => $value) { if (preg_match($delimiter . $pregex . $delimiter . 'u', $i)) { $matches[] = $i; $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults)); } } } return $matches; } /** * Validates the element properties * * @param \StdClass $element Element to validate * @param array $matches Matches from patternProperties (if any) * @param \StdClass $schema ObjectConstraint definition * @param JsonPointer|null $path Current test path * @param \StdClass $properties Properties * @param mixed $additionalProp Additional properties */ public function validateElement($element, $matches, $schema = null, JsonPointer $path = null, $properties = null, $additionalProp = null) { $this->validateMinMaxConstraint($element, $schema, $path); foreach ($element as $i => $value) { $definition = $this->getProperty($properties, $i); // no additional properties allowed if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) { $this->addError($path, 'The property ' . $i . ' is not defined and the definition does not allow additional properties', 'additionalProp'); } // additional properties defined if (!in_array($i, $matches) && $additionalProp && !$definition) { if ($additionalProp === true) { $this->checkUndefined($value, null, $path, $i, in_array($i, $this->appliedDefaults)); } else { $this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults)); } } // 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', 'requires'); } $property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined')); if (is_object($property)) { $this->validateMinMaxConstraint(!($property instanceof UndefinedConstraint) ? $property : $element, $definition, $path); } } } /** * Validates the definition properties * * @param \stdClass $element Element to validate * @param \stdClass $properties Property definitions * @param JsonPointer|null $path Path? */ public function validateProperties(&$element, $properties = null, JsonPointer $path = null) { $undefinedConstraint = $this->factory->createInstanceFor('undefined'); foreach ($properties as $i => $value) { $property = &$this->getProperty($element, $i, $undefinedConstraint); $definition = $this->getProperty($properties, $i); if (is_object($definition)) { // Undefined constraint will check for is_object() and quit if is not - so why pass it? $this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults)); } } } /** * retrieves a property from an object or array * * @param mixed $element Element to validate * @param string $property Property to retrieve * @param mixed $fallback Default value if property is not found * * @return mixed */ protected function &getProperty(&$element, $property, $fallback = null) { if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) { return $element[$property]; } elseif (is_object($element) && property_exists($element, $property)) { return $element->$property; } return $fallback; } /** * validating minimum and maximum property constraints (if present) against an element * * @param \stdClass $element Element to validate * @param \stdClass $objectDefinition ObjectConstraint definition * @param JsonPointer|null $path Path to test? */ protected function validateMinMaxConstraint($element, $objectDefinition, JsonPointer $path = null) { // Verify minimum number of properties if (isset($objectDefinition->minProperties) && !is_object($objectDefinition->minProperties)) { if ($this->getTypeCheck()->propertyCount($element) < $objectDefinition->minProperties) { $this->addError($path, 'Must contain a minimum of ' . $objectDefinition->minProperties . ' properties', 'minProperties', array('minProperties' => $objectDefinition->minProperties)); } } // Verify maximum number of properties if (isset($objectDefinition->maxProperties) && !is_object($objectDefinition->maxProperties)) { if ($this->getTypeCheck()->propertyCount($element) > $objectDefinition->maxProperties) { $this->addError($path, 'Must contain no more than ' . $objectDefinition->maxProperties . ' properties', 'maxProperties', array('maxProperties' => $objectDefinition->maxProperties)); } } } } json-schema/src/JsonSchema/Constraints/NumberConstraint.php 0000644 00000006607 14736103246 0020130 0 ustar 00 <?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\Constraints; use JsonSchema\Entity\JsonPointer; /** * The NumberConstraint Constraints, validates an number against a given schema * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class NumberConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Verify minimum if (isset($schema->exclusiveMinimum)) { if (isset($schema->minimum)) { if ($schema->exclusiveMinimum && $element <= $schema->minimum) { $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum)); } elseif ($element < $schema->minimum) { $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); } } else { $this->addError($path, 'Use of exclusiveMinimum requires presence of minimum', 'missingMinimum'); } } elseif (isset($schema->minimum) && $element < $schema->minimum) { $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); } // Verify maximum if (isset($schema->exclusiveMaximum)) { if (isset($schema->maximum)) { if ($schema->exclusiveMaximum && $element >= $schema->maximum) { $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum)); } elseif ($element > $schema->maximum) { $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); } } else { $this->addError($path, 'Use of exclusiveMaximum requires presence of maximum', 'missingMaximum'); } } elseif (isset($schema->maximum) && $element > $schema->maximum) { $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); } // Verify divisibleBy - Draft v3 if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { $this->addError($path, 'Is not divisible by ' . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy)); } // Verify multipleOf - Draft v4 if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) { $this->addError($path, 'Must be a multiple of ' . $schema->multipleOf, 'multipleOf', array('multipleOf' => $schema->multipleOf)); } $this->checkFormat($element, $schema, $path, $i); } private function fmod($number1, $number2) { $modulus = ($number1 - round($number1 / $number2) * $number2); $precision = 0.0000000001; if (-$precision < $modulus && $modulus < $precision) { return 0.0; } return $modulus; } } json-schema/src/JsonSchema/Constraints/Constraint.php 0000644 00000014530 14736103246 0016751 0 ustar 00 <?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\Constraints; use JsonSchema\Entity\JsonPointer; /** * The Base Constraints, all Validators should extend this class * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ abstract class Constraint extends BaseConstraint implements ConstraintInterface { protected $inlineSchemaProperty = '$schema'; const CHECK_MODE_NONE = 0x00000000; const CHECK_MODE_NORMAL = 0x00000001; const CHECK_MODE_TYPE_CAST = 0x00000002; const CHECK_MODE_COERCE_TYPES = 0x00000004; const CHECK_MODE_APPLY_DEFAULTS = 0x00000008; const CHECK_MODE_EXCEPTIONS = 0x00000010; const CHECK_MODE_DISABLE_FORMAT = 0x00000020; const CHECK_MODE_ONLY_REQUIRED_DEFAULTS = 0x00000080; const CHECK_MODE_VALIDATE_SCHEMA = 0x00000100; /** * Bubble down the path * * @param JsonPointer|null $path Current path * @param mixed $i What to append to the path * * @return JsonPointer; */ protected function incrementPath(JsonPointer $path = null, $i) { $path = $path ?: new JsonPointer(''); $path = $path->withPropertyPaths( array_merge( $path->getPropertyPaths(), array_filter(array($i), 'strlen') ) ); return $path; } /** * Validates an array * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkArray(&$value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('collection'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Validates an object * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $properties * @param mixed $additionalProperties * @param mixed $patternProperties */ protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $properties = null, $additionalProperties = null, $patternProperties = null, $appliedDefaults = array()) { $validator = $this->factory->createInstanceFor('object'); $validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults); $this->addErrors($validator->getErrors()); } /** * Validates the type of a property * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkType(&$value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('type'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a undefined element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkUndefined(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = false) { $validator = $this->factory->createInstanceFor('undefined'); $validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i, $fromDefault); $this->addErrors($validator->getErrors()); } /** * Checks a string element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkString($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('string'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a number element * * @param mixed $value * @param mixed $schema * @param JsonPointer $path * @param mixed $i */ protected function checkNumber($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('number'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a enum element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkEnum($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('enum'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks format of an element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkFormat($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('format'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Get the type check based on the set check mode. * * @return TypeCheck\TypeCheckInterface */ protected function getTypeCheck() { return $this->factory->getTypeCheck(); } /** * @param JsonPointer $pointer * * @return string property path */ protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer) { $result = array_map( function ($path) { return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path); }, $pointer->getPropertyPaths() ); return trim(implode('', $result), '.'); } } json-schema/src/JsonSchema/Constraints/TypeConstraint.php 0000644 00000017446 14736103246 0017624 0 ustar 00 <?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\Constraints; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\InvalidArgumentException; use UnexpectedValueException as StandardUnexpectedValueException; /** * The TypeConstraint Constraints, validates an element against a given type * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class TypeConstraint extends Constraint { /** * @var array|string[] type wordings for validation error messages */ public static $wording = array( 'integer' => 'an integer', 'number' => 'a number', 'boolean' => 'a boolean', 'object' => 'an object', 'array' => 'an array', 'string' => 'a string', 'null' => 'a null', 'any' => null, // validation of 'any' is always true so is not needed in message wording 0 => null, // validation of a false-y value is always true, so not needed as well ); /** * {@inheritdoc} */ public function check(&$value = null, $schema = null, JsonPointer $path = null, $i = null) { $type = isset($schema->type) ? $schema->type : null; $isValid = false; $wording = array(); if (is_array($type)) { $this->validateTypesArray($value, $type, $wording, $isValid, $path); } elseif (is_object($type)) { $this->checkUndefined($value, $type, $path); return; } else { $isValid = $this->validateType($value, $type); } if ($isValid === false) { if (!is_array($type)) { $this->validateTypeNameWording($type); $wording[] = self::$wording[$type]; } $this->addError($path, ucwords(gettype($value)) . ' value found, but ' . $this->implodeWith($wording, ', ', 'or') . ' is required', 'type'); } } /** * Validates the given $value against the array of types in $type. Sets the value * of $isValid to true, if at least one $type mateches the type of $value or the value * passed as $isValid is already true. * * @param mixed $value Value to validate * @param array $type TypeConstraints to check agains * @param array $validTypesWording An array of wordings of the valid types of the array $type * @param bool $isValid The current validation value * @param $path */ protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path) { foreach ($type as $tp) { // $tp can be an object, if it's a schema instead of a simple type, validate it // with a new type constraint if (is_object($tp)) { if (!$isValid) { $validator = $this->factory->createInstanceFor('type'); $subSchema = new \stdClass(); $subSchema->type = $tp; $validator->check($value, $subSchema, $path, null); $error = $validator->getErrors(); $isValid = !(bool) $error; $validTypesWording[] = self::$wording['object']; } } else { $this->validateTypeNameWording($tp); $validTypesWording[] = self::$wording[$tp]; if (!$isValid) { $isValid = $this->validateType($value, $tp); } } } } /** * Implodes the given array like implode() with turned around parameters and with the * difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of * $delimiter. * * @param array $elements The elements to implode * @param string $delimiter The delimiter to use * @param bool $listEnd The last delimiter to use (defaults to $delimiter) * * @return string */ protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false) { if ($listEnd === false || !isset($elements[1])) { return implode($delimiter, $elements); } $lastElement = array_slice($elements, -1); $firsElements = join($delimiter, array_slice($elements, 0, -1)); $implodedElements = array_merge(array($firsElements), $lastElement); return join(" $listEnd ", $implodedElements); } /** * Validates the given $type, if there's an associated self::$wording. If not, throws an * exception. * * @param string $type The type to validate * * @throws StandardUnexpectedValueException */ protected function validateTypeNameWording($type) { if (!isset(self::$wording[$type])) { throw new StandardUnexpectedValueException( sprintf( 'No wording for %s available, expected wordings are: [%s]', var_export($type, true), implode(', ', array_filter(self::$wording))) ); } } /** * Verifies that a given value is of a certain type * * @param mixed $value Value to validate * @param string $type TypeConstraint to check against * * @throws InvalidArgumentException * * @return bool */ protected function validateType(&$value, $type) { //mostly the case for inline schema if (!$type) { return true; } if ('any' === $type) { return true; } if ('object' === $type) { return $this->getTypeCheck()->isObject($value); } if ('array' === $type) { return $this->getTypeCheck()->isArray($value); } $coerce = $this->factory->getConfig(Constraint::CHECK_MODE_COERCE_TYPES); if ('integer' === $type) { if ($coerce) { $value = $this->toInteger($value); } return is_int($value); } if ('number' === $type) { if ($coerce) { $value = $this->toNumber($value); } return is_numeric($value) && !is_string($value); } if ('boolean' === $type) { if ($coerce) { $value = $this->toBoolean($value); } return is_bool($value); } if ('string' === $type) { return is_string($value); } if ('email' === $type) { return is_string($value); } if ('null' === $type) { return is_null($value); } throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type); } /** * Converts a value to boolean. For example, "true" becomes true. * * @param $value The value to convert to boolean * * @return bool|mixed */ protected function toBoolean($value) { if ($value === 'true') { return true; } if ($value === 'false') { return false; } return $value; } /** * Converts a numeric string to a number. For example, "4" becomes 4. * * @param mixed $value the value to convert to a number * * @return int|float|mixed */ protected function toNumber($value) { if (is_numeric($value)) { return $value + 0; // cast to number } return $value; } protected function toInteger($value) { if (is_numeric($value) && (int) $value == $value) { return (int) $value; // cast to number } return $value; } } json-schema/src/JsonSchema/Constraints/BaseConstraint.php 0000644 00000007445 14736103246 0017553 0 ustar 00 <?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\Constraints; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\InvalidArgumentException; use JsonSchema\Exception\ValidationException; use JsonSchema\Validator; /** * A more basic constraint definition - used for the public * interface to avoid exposing library internals. */ class BaseConstraint { /** * @var array Errors */ protected $errors = array(); /** * @var int All error types which have occurred */ protected $errorMask = Validator::ERROR_NONE; /** * @var Factory */ protected $factory; /** * @param Factory $factory */ public function __construct(Factory $factory = null) { $this->factory = $factory ?: new Factory(); } public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null) { $error = array( 'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), 'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'), 'message' => $message, 'constraint' => $constraint, 'context' => $this->factory->getErrorContext(), ); if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) { throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message'])); } if (is_array($more) && count($more) > 0) { $error += $more; } $this->errors[] = $error; $this->errorMask |= $error['context']; } public function addErrors(array $errors) { if ($errors) { $this->errors = array_merge($this->errors, $errors); $errorMask = &$this->errorMask; array_walk($errors, function ($error) use (&$errorMask) { if (isset($error['context'])) { $errorMask |= $error['context']; } }); } } public function getErrors($errorContext = Validator::ERROR_ALL) { if ($errorContext === Validator::ERROR_ALL) { return $this->errors; } return array_filter($this->errors, function ($error) use ($errorContext) { if ($errorContext & $error['context']) { return true; } }); } public function numErrors($errorContext = Validator::ERROR_ALL) { if ($errorContext === Validator::ERROR_ALL) { return count($this->errors); } return count($this->getErrors($errorContext)); } public function isValid() { return !$this->getErrors(); } /** * Clears any reported errors. Should be used between * multiple validation checks. */ public function reset() { $this->errors = array(); $this->errorMask = Validator::ERROR_NONE; } /** * Get the error mask * * @return int */ public function getErrorMask() { return $this->errorMask; } /** * Recursively cast an associative array to an object * * @param array $array * * @return object */ public static function arrayToObjectRecursive($array) { $json = json_encode($array); if (json_last_error() !== \JSON_ERROR_NONE) { $message = 'Unable to encode schema array as JSON'; if (function_exists('json_last_error_msg')) { $message .= ': ' . json_last_error_msg(); } throw new InvalidArgumentException($message); } return (object) json_decode($json); } } json-schema/src/JsonSchema/Constraints/SchemaConstraint.php 0000644 00000007317 14736103246 0020077 0 ustar 00 <?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\Constraints; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\InvalidArgumentException; use JsonSchema\Exception\InvalidSchemaException; use JsonSchema\Exception\RuntimeException; use JsonSchema\Validator; /** * The SchemaConstraint Constraints, validates an element against a given schema * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class SchemaConstraint extends Constraint { const DEFAULT_SCHEMA_SPEC = 'http://json-schema.org/draft-04/schema#'; /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { if ($schema !== null) { // passed schema $validationSchema = $schema; } elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) { // inline schema $validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty); } else { throw new InvalidArgumentException('no schema found to verify against'); } // cast array schemas to object if (is_array($validationSchema)) { $validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema); } // validate schema against whatever is defined in $validationSchema->$schema. If no // schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04). if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) { if (!$this->getTypeCheck()->isObject($validationSchema)) { throw new RuntimeException('Cannot validate the schema of a non-object'); } if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) { $schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema'); } else { $schemaSpec = self::DEFAULT_SCHEMA_SPEC; } // get the spec schema $schemaStorage = $this->factory->getSchemaStorage(); if (!$this->getTypeCheck()->isObject($schemaSpec)) { $schemaSpec = $schemaStorage->getSchema($schemaSpec); } // save error count, config & subtract CHECK_MODE_VALIDATE_SCHEMA $initialErrorCount = $this->numErrors(); $initialConfig = $this->factory->getConfig(); $initialContext = $this->factory->getErrorContext(); $this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS); $this->factory->addConfig(self::CHECK_MODE_TYPE_CAST); $this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION); // validate schema try { $this->check($validationSchema, $schemaSpec); } catch (\Exception $e) { if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) { throw new InvalidSchemaException('Schema did not pass validation', 0, $e); } } if ($this->numErrors() > $initialErrorCount) { $this->addError($path, 'Schema is not valid', 'schema'); } // restore the initial config $this->factory->setConfig($initialConfig); $this->factory->setErrorContext($initialContext); } // validate element against $validationSchema $this->checkUndefined($element, $validationSchema, $path, $i); } } json-schema/src/JsonSchema/Constraints/ConstraintInterface.php 0000644 00000003035 14736103246 0020570 0 ustar 00 <?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\Constraints; use JsonSchema\Entity\JsonPointer; /** * The Constraints Interface * * @author Robert Schönthal <seroscho@googlemail.com> */ interface ConstraintInterface { /** * returns all collected errors * * @return array */ public function getErrors(); /** * adds errors to this validator * * @param array $errors */ public function addErrors(array $errors); /** * adds an error * * @param JsonPointer|null $path * @param string $message * @param string $constraint the constraint/rule that is broken, e.g.: 'minLength' * @param array $more more array elements to add to the error */ public function addError(JsonPointer $path = null, $message, $constraint='', array $more = null); /** * checks if the validator has not raised errors * * @return bool */ public function isValid(); /** * invokes the validation of an element * * @abstract * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i * * @throws \JsonSchema\Exception\ExceptionInterface */ public function check(&$value, $schema = null, JsonPointer $path = null, $i = null); } json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php 0000644 00000037277 14736103246 0020610 0 ustar 00 <?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\Constraints; use JsonSchema\Constraints\TypeCheck\LooseTypeCheck; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\ValidationException; use JsonSchema\Uri\UriResolver; /** * The UndefinedConstraint Constraints * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class UndefinedConstraint extends Constraint { /** * @var array List of properties to which a default value has been applied */ protected $appliedDefaults = array(); /** * {@inheritdoc} */ public function check(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = false) { if (is_null($schema) || !is_object($schema)) { return; } $path = $this->incrementPath($path ?: new JsonPointer(''), $i); if ($fromDefault) { $path->setFromDefault(); } // check special properties $this->validateCommonProperties($value, $schema, $path, $i); // check allOf, anyOf, and oneOf properties $this->validateOfProperties($value, $schema, $path, ''); // check known types $this->validateTypes($value, $schema, $path, $i); } /** * Validates the value against the types * * @param mixed $value * @param mixed $schema * @param JsonPointer $path * @param string $i */ public function validateTypes(&$value, $schema = null, JsonPointer $path, $i = null) { // check array if ($this->getTypeCheck()->isArray($value)) { $this->checkArray($value, $schema, $path, $i); } // check object if (LooseTypeCheck::isObject($value)) { // object processing should always be run on assoc arrays, // so use LooseTypeCheck here even if CHECK_MODE_TYPE_CAST // is not set (i.e. don't use $this->getTypeCheck() here). $this->checkObject( $value, $schema, $path, isset($schema->properties) ? $schema->properties : null, isset($schema->additionalProperties) ? $schema->additionalProperties : null, isset($schema->patternProperties) ? $schema->patternProperties : null, $this->appliedDefaults ); } // 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 mixed $value * @param mixed $schema * @param JsonPointer $path * @param string $i */ protected function validateCommonProperties(&$value, $schema = null, JsonPointer $path, $i = '') { // if it extends another schema, it must pass that schema as well if (isset($schema->extends)) { if (is_string($schema->extends)) { $schema->extends = $this->validateUri($schema, $schema->extends); } if (is_array($schema->extends)) { foreach ($schema->extends as $extends) { $this->checkUndefined($value, $extends, $path, $i); } } else { $this->checkUndefined($value, $schema->extends, $path, $i); } } // Apply default values from schema if (!$path->fromDefault()) { $this->applyDefaultValues($value, $schema, $path); } // Verify required values if ($this->getTypeCheck()->isObject($value)) { if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) { // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...] foreach ($schema->required as $required) { if (!$this->getTypeCheck()->propertyExists($value, $required)) { $this->addError( $this->incrementPath($path ?: new JsonPointer(''), $required), 'The property ' . $required . ' is required', 'required' ); } } } elseif (isset($schema->required) && !is_array($schema->required)) { // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true} if ($schema->required && $value instanceof self) { $propertyPaths = $path->getPropertyPaths(); $propertyName = end($propertyPaths); $this->addError( $path, 'The property ' . $propertyName . ' is required', 'required' ); } } else { // If the value is both undefined and not required, skip remaining checks // in this method which assume an actual, defined instance when validating. if ($value instanceof self) { return; } } } // Verify type if (!($value instanceof self)) { $this->checkType($value, $schema, $path, $i); } // Verify disallowed items if (isset($schema->disallow)) { $initErrors = $this->getErrors(); $typeSchema = new \stdClass(); $typeSchema->type = $schema->disallow; $this->checkType($value, $typeSchema, $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', 'disallow'); } else { $this->errors = $initErrors; } } if (isset($schema->not)) { $initErrors = $this->getErrors(); $this->checkUndefined($value, $schema->not, $path, $i); // if no new errors were raised then the instance validated against the "not" schema if (count($this->getErrors()) == count($initErrors)) { $this->addError($path, 'Matched a schema which it should not', 'not'); } else { $this->errors = $initErrors; } } // Verify that dependencies are met if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) { $this->validateDependencies($value, $schema->dependencies, $path); } } /** * Check whether a default should be applied for this value * * @param mixed $schema * @param mixed $parentSchema * @param bool $requiredOnly * * @return bool */ private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null) { // required-only mode is off if (!$requiredOnly) { return true; } // draft-04 required is set if ( $name !== null && isset($parentSchema->required) && is_array($parentSchema->required) && in_array($name, $parentSchema->required) ) { return true; } // draft-03 required is set if (isset($schema->required) && !is_array($schema->required) && $schema->required) { return true; } // default case return false; } /** * Apply default values * * @param mixed $value * @param mixed $schema * @param JsonPointer $path */ protected function applyDefaultValues(&$value, $schema, $path) { // only apply defaults if feature is enabled if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) { return; } // apply defaults if appropriate $requiredOnly = $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS); if (isset($schema->properties) && LooseTypeCheck::isObject($value)) { // $value is an object or assoc array, and properties are defined - treat as an object foreach ($schema->properties as $currentProperty => $propertyDefinition) { $propertyDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($propertyDefinition); if ( !LooseTypeCheck::propertyExists($value, $currentProperty) && property_exists($propertyDefinition, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema) ) { // assign default value if (is_object($propertyDefinition->default)) { LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default); } else { LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default); } $this->appliedDefaults[] = $currentProperty; } } } elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) { $items = array(); if (LooseTypeCheck::isArray($schema->items)) { $items = $schema->items; } elseif (isset($schema->minItems) && count($value) < $schema->minItems) { $items = array_fill(count($value), $schema->minItems - count($value), $schema->items); } // $value is an array, and items are defined - treat as plain array foreach ($items as $currentItem => $itemDefinition) { $itemDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($itemDefinition); if ( !array_key_exists($currentItem, $value) && property_exists($itemDefinition, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) { if (is_object($itemDefinition->default)) { $value[$currentItem] = clone $itemDefinition->default; } else { $value[$currentItem] = $itemDefinition->default; } } $path->setFromDefault(); } } elseif ( $value instanceof self && property_exists($schema, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $schema)) { // $value is a leaf, not a container - apply the default directly $value = is_object($schema->default) ? clone $schema->default : $schema->default; $path->setFromDefault(); } } /** * Validate allOf, anyOf, and oneOf properties * * @param mixed $value * @param mixed $schema * @param JsonPointer $path * @param string $i */ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '') { // Verify type if ($value instanceof self) { return; } if (isset($schema->allOf)) { $isValid = true; foreach ($schema->allOf as $allOf) { $initErrors = $this->getErrors(); $this->checkUndefined($value, $allOf, $path, $i); $isValid = $isValid && (count($this->getErrors()) == count($initErrors)); } if (!$isValid) { $this->addError($path, 'Failed to match all schemas', 'allOf'); } } if (isset($schema->anyOf)) { $isValid = false; $startErrors = $this->getErrors(); $caughtException = null; foreach ($schema->anyOf as $anyOf) { $initErrors = $this->getErrors(); try { $this->checkUndefined($value, $anyOf, $path, $i); if ($isValid = (count($this->getErrors()) == count($initErrors))) { break; } } catch (ValidationException $e) { $isValid = false; } } if (!$isValid) { $this->addError($path, 'Failed to match at least one schema', 'anyOf'); } else { $this->errors = $startErrors; } } if (isset($schema->oneOf)) { $allErrors = array(); $matchedSchemas = 0; $startErrors = $this->getErrors(); foreach ($schema->oneOf as $oneOf) { try { $this->errors = array(); $this->checkUndefined($value, $oneOf, $path, $i); if (count($this->getErrors()) == 0) { $matchedSchemas++; } $allErrors = array_merge($allErrors, array_values($this->getErrors())); } catch (ValidationException $e) { // deliberately do nothing here - validation failed, but we want to check // other schema options in the OneOf field. } } if ($matchedSchemas !== 1) { $this->addErrors(array_merge($allErrors, $startErrors)); $this->addError($path, 'Failed to match exactly one schema', 'oneOf'); } else { $this->errors = $startErrors; } } } /** * Validate dependencies * * @param mixed $value * @param mixed $dependencies * @param JsonPointer $path * @param string $i */ protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '') { foreach ($dependencies as $key => $dependency) { if ($this->getTypeCheck()->propertyExists($value, $key)) { if (is_string($dependency)) { // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"} if (!$this->getTypeCheck()->propertyExists($value, $dependency)) { $this->addError($path, "$key depends on $dependency and $dependency is missing", 'dependencies'); } } elseif (is_array($dependency)) { // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]} foreach ($dependency as $d) { if (!$this->getTypeCheck()->propertyExists($value, $d)) { $this->addError($path, "$key depends on $d and $d is missing", 'dependencies'); } } } elseif (is_object($dependency)) { // Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}} $this->checkUndefined($value, $dependency, $path, $i); } } } } protected function validateUri($schema, $schemaUri = null) { $resolver = new UriResolver(); $retriever = $this->factory->getUriRetriever(); $jsonSchema = null; if ($resolver->isValid($schemaUri)) { $schemaId = property_exists($schema, 'id') ? $schema->id : null; $jsonSchema = $retriever->retrieve($schemaId, $schemaUri); } return $jsonSchema; } } json-schema/src/JsonSchema/Constraints/EnumConstraint.php 0000644 00000003161 14736103246 0017574 0 ustar 00 <?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\Constraints; use JsonSchema\Entity\JsonPointer; /** * The EnumConstraint Constraints, 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 EnumConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Only validate enum if the attribute exists if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) { return; } $type = gettype($element); foreach ($schema->enum as $enum) { $enumType = gettype($enum); if ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type == 'array' && $enumType == 'object') { if ((object) $element == $enum) { return; } } if ($type === gettype($enum)) { if ($type == 'object') { if ($element == $enum) { return; } } elseif ($element === $enum) { return; } } } $this->addError($path, 'Does not have a value in the enumeration ' . json_encode($schema->enum), 'enum', array('enum' => $schema->enum)); } } json-schema/src/JsonSchema/Constraints/CollectionConstraint.php 0000644 00000011706 14736103246 0020767 0 ustar 00 <?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\Constraints; use JsonSchema\Entity\JsonPointer; /** * The CollectionConstraint Constraints, validates an array against a given schema * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class CollectionConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$value, $schema = null, JsonPointer $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 . ' items in the array', 'minItems', array('minItems' => $schema->minItems)); } // Verify maxItems if (isset($schema->maxItems) && count($value) > $schema->maxItems) { $this->addError($path, 'There must be a maximum of ' . $schema->maxItems . ' items in the array', 'maxItems', array('maxItems' => $schema->maxItems)); } // Verify uniqueItems if (isset($schema->uniqueItems) && $schema->uniqueItems) { $unique = $value; if (is_array($value) && count($value)) { $unique = array_map(function ($e) { return var_export($e, true); }, $value); } if (count(array_unique($unique)) != count($value)) { $this->addError($path, 'There are no duplicates allowed in the array', 'uniqueItems'); } } // Verify items if (isset($schema->items)) { $this->validateItems($value, $schema, $path, $i); } } /** * Validates the items * * @param array $value * @param \stdClass $schema * @param JsonPointer|null $path * @param string $i */ protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null) { if (is_object($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" $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) < count($this->getErrors())) { $this->errors = $secondErrors; } elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) { $this->errors = $initErrors; } } unset($v); /* remove dangling reference to prevent any future bugs * caused by accidentally using $v elsewhere */ } 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 (property_exists($schema, 'additionalItems')) { if ($schema->additionalItems !== false) { $this->checkUndefined($v, $schema->additionalItems, $path, $k); } else { $this->addError( $path, 'The item ' . $i . '[' . $k . '] is not defined and the definition does not allow additional items', 'additionalItems', array('additionalItems' => $schema->additionalItems)); } } else { // Should be valid against an empty schema $this->checkUndefined($v, new \stdClass(), $path, $k); } } } unset($v); /* remove dangling reference to prevent any future bugs * caused by accidentally using $v elsewhere */ // Treat when we have more schema definitions than values, not for empty arrays if (count($value) > 0) { for ($k = count($value); $k < count($schema->items); $k++) { $undefinedInstance = $this->factory->createInstanceFor('undefined'); $this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k); } } } } } json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php 0000644 00000000641 14736103246 0022202 0 ustar 00 <?php namespace JsonSchema\Constraints\TypeCheck; interface TypeCheckInterface { public static function isObject($value); 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); } json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php 0000644 00000001453 14736103246 0021554 0 ustar 00 <?php namespace JsonSchema\Constraints\TypeCheck; class StrictTypeCheck implements TypeCheckInterface { public static function isObject($value) { return is_object($value); } public static function isArray($value) { return is_array($value); } 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); } public static function propertyCount($value) { if (!is_object($value)) { return 0; } return count(get_object_vars($value)); } } json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php 0000644 00000003026 14736103246 0021363 0 ustar 00 <?php namespace JsonSchema\Constraints\TypeCheck; class LooseTypeCheck implements TypeCheckInterface { public static function isObject($value) { return is_object($value) || (is_array($value) && (count($value) == 0 || self::isAssociativeArray($value))); } public static function isArray($value) { return is_array($value) && (count($value) == 0 || !self::isAssociativeArray($value)); } public static function propertyGet($value, $property) { if (is_object($value)) { return $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)) { return property_exists($value, $property); } return array_key_exists($property, $value); } public static function propertyCount($value) { if (is_object($value)) { return count(get_object_vars($value)); } return count($value); } /** * Check if the provided array is associative or not * * @param array $arr * * @return bool */ private static function isAssociativeArray($arr) { return array_keys($arr) !== range(0, count($arr) - 1); } } json-schema/src/JsonSchema/Constraints/StringConstraint.php 0000644 00000003706 14736103246 0020143 0 ustar 00 <?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\Constraints; use JsonSchema\Entity\JsonPointer; /** * The StringConstraint Constraints, validates an string against a given schema * * @author Robert Schönthal <seroscho@googlemail.com> * @author Bruno Prieto Reis <bruno.p.reis@gmail.com> */ class StringConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Verify maxLength if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) { $this->addError($path, 'Must be at most ' . $schema->maxLength . ' characters long', 'maxLength', array( 'maxLength' => $schema->maxLength, )); } //verify minLength if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) { $this->addError($path, 'Must be at least ' . $schema->minLength . ' characters long', 'minLength', array( 'minLength' => $schema->minLength, )); } // Verify a regex pattern if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#u', $element)) { $this->addError($path, 'Does not match the regex pattern ' . $schema->pattern, 'pattern', array( 'pattern' => $schema->pattern, )); } $this->checkFormat($element, $schema, $path, $i); } private function strlen($string) { if (extension_loaded('mbstring')) { return mb_strlen($string, mb_detect_encoding($string)); } // mbstring is present on all test platforms, so strlen() can be ignored for coverage return strlen($string); // @codeCoverageIgnore } } json-schema/src/JsonSchema/Constraints/Factory.php 0000644 00000013106 14736103246 0016232 0 ustar 00 <?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\Constraints; use JsonSchema\Exception\InvalidArgumentException; use JsonSchema\SchemaStorage; use JsonSchema\SchemaStorageInterface; use JsonSchema\Uri\UriRetriever; use JsonSchema\UriRetrieverInterface; use JsonSchema\Validator; /** * Factory for centralize constraint initialization. */ class Factory { /** * @var SchemaStorage */ protected $schemaStorage; /** * @var UriRetriever */ protected $uriRetriever; /** * @var int */ private $checkMode = Constraint::CHECK_MODE_NORMAL; /** * @var TypeCheck\TypeCheckInterface[] */ private $typeCheck = array(); /** * @var int Validation context */ protected $errorContext = Validator::ERROR_DOCUMENT_VALIDATION; /** * @var array */ protected $constraintMap = array( 'array' => 'JsonSchema\Constraints\CollectionConstraint', 'collection' => 'JsonSchema\Constraints\CollectionConstraint', 'object' => 'JsonSchema\Constraints\ObjectConstraint', 'type' => 'JsonSchema\Constraints\TypeConstraint', 'undefined' => 'JsonSchema\Constraints\UndefinedConstraint', 'string' => 'JsonSchema\Constraints\StringConstraint', 'number' => 'JsonSchema\Constraints\NumberConstraint', 'enum' => 'JsonSchema\Constraints\EnumConstraint', 'format' => 'JsonSchema\Constraints\FormatConstraint', 'schema' => 'JsonSchema\Constraints\SchemaConstraint', 'validator' => 'JsonSchema\Validator' ); /** * @var array<ConstraintInterface> */ private $instanceCache = array(); /** * @param SchemaStorage $schemaStorage * @param UriRetrieverInterface $uriRetriever * @param int $checkMode */ public function __construct( SchemaStorageInterface $schemaStorage = null, UriRetrieverInterface $uriRetriever = null, $checkMode = Constraint::CHECK_MODE_NORMAL ) { // set provided config options $this->setConfig($checkMode); $this->uriRetriever = $uriRetriever ?: new UriRetriever(); $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever); } /** * Set config values * * @param int $checkMode Set checkMode options - does not preserve existing flags */ public function setConfig($checkMode = Constraint::CHECK_MODE_NORMAL) { $this->checkMode = $checkMode; } /** * Enable checkMode flags * * @param int $options */ public function addConfig($options) { $this->checkMode |= $options; } /** * Disable checkMode flags * * @param int $options */ public function removeConfig($options) { $this->checkMode &= ~$options; } /** * Get checkMode option * * @param int $options Options to get, if null then return entire bitmask * * @return int */ public function getConfig($options = null) { if ($options === null) { return $this->checkMode; } return $this->checkMode & $options; } /** * @return UriRetrieverInterface */ public function getUriRetriever() { return $this->uriRetriever; } public function getSchemaStorage() { return $this->schemaStorage; } public function getTypeCheck() { if (!isset($this->typeCheck[$this->checkMode])) { $this->typeCheck[$this->checkMode] = ($this->checkMode & Constraint::CHECK_MODE_TYPE_CAST) ? new TypeCheck\LooseTypeCheck() : new TypeCheck\StrictTypeCheck(); } return $this->typeCheck[$this->checkMode]; } /** * @param string $name * @param string $class * * @return Factory */ public function setConstraintClass($name, $class) { // Ensure class exists if (!class_exists($class)) { throw new InvalidArgumentException('Unknown constraint ' . $name); } // Ensure class is appropriate if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) { throw new InvalidArgumentException('Invalid class ' . $name); } $this->constraintMap[$name] = $class; return $this; } /** * Create a constraint instance for the given constraint name. * * @param string $constraintName * * @throws InvalidArgumentException if is not possible create the constraint instance * * @return ConstraintInterface|ObjectConstraint */ public function createInstanceFor($constraintName) { if (!isset($this->constraintMap[$constraintName])) { throw new InvalidArgumentException('Unknown constraint ' . $constraintName); } if (!isset($this->instanceCache[$constraintName])) { $this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this); } return clone $this->instanceCache[$constraintName]; } /** * Get the error context * * @return string */ public function getErrorContext() { return $this->errorContext; } /** * Set the error context * * @param string $validationContext */ public function setErrorContext($errorContext) { $this->errorContext = $errorContext; } } json-schema/src/JsonSchema/Constraints/FormatConstraint.php 0000644 00000021225 14736103246 0020121 0 ustar 00 <?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\Constraints; use JsonSchema\Entity\JsonPointer; use JsonSchema\Rfc3339; /** * Validates against the "format" property * * @author Justin Rainbow <justin.rainbow@gmail.com> * * @see http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23 */ class FormatConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) { return; } switch ($schema->format) { case 'date': if (!$date = $this->validateDateTime($element, 'Y-m-d')) { $this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'time': if (!$this->validateDateTime($element, 'H:i:s')) { $this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'date-time': if (null === Rfc3339::createFromString($element)) { $this->addError($path, sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'utc-millisec': if (!$this->validateDateTime($element, 'U')) { $this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'regex': if (!$this->validateRegex($element)) { $this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format)); } break; case 'color': if (!$this->validateColor($element)) { $this->addError($path, 'Invalid color', 'format', array('format' => $schema->format)); } break; case 'style': if (!$this->validateStyle($element)) { $this->addError($path, 'Invalid style', 'format', array('format' => $schema->format)); } break; case 'phone': if (!$this->validatePhone($element)) { $this->addError($path, 'Invalid phone number', 'format', array('format' => $schema->format)); } break; case 'uri': if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); } break; case 'uriref': case 'uri-reference': if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { // FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but // the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them. // See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information. if (substr($element, 0, 2) === '//') { // network-path reference $validURL = filter_var('scheme:' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); } elseif (substr($element, 0, 1) === '/') { // absolute-path reference $validURL = filter_var('scheme://host' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); } elseif (strlen($element)) { // relative-path reference $pathParts = explode('/', $element, 2); if (strpos($pathParts[0], ':') !== false) { $validURL = null; } else { $validURL = filter_var('scheme://host/' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); } } else { $validURL = null; } if ($validURL === null) { $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); } } break; case 'email': $filterFlags = FILTER_NULL_ON_FAILURE; if (defined('FILTER_FLAG_EMAIL_UNICODE')) { // Only available from PHP >= 7.1.0, so ignore it for coverage checks $filterFlags |= constant('FILTER_FLAG_EMAIL_UNICODE'); // @codeCoverageIgnore } if (null === filter_var($element, FILTER_VALIDATE_EMAIL, $filterFlags)) { $this->addError($path, 'Invalid email', 'format', array('format' => $schema->format)); } break; case 'ip-address': case 'ipv4': if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) { $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); } break; case 'ipv6': if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) { $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); } break; case 'host-name': case 'hostname': if (!$this->validateHostname($element)) { $this->addError($path, 'Invalid hostname', 'format', array('format' => $schema->format)); } break; default: // Empty as it should be: // The value of this keyword is called a format attribute. It MUST be a string. // A format attribute can generally only validate a given set of instance types. // If the type of the instance to validate is not in this set, validation for // this format attribute and instance SHOULD succeed. // http://json-schema.org/latest/json-schema-validation.html#anchor105 break; } } protected function validateDateTime($datetime, $format) { $dt = \DateTime::createFromFormat($format, $datetime); if (!$dt) { return false; } if ($datetime === $dt->format($format)) { return true; } // handles the case where a non-6 digit microsecond datetime is passed // which will fail the above string comparison because the passed // $datetime may be '2000-05-01T12:12:12.123Z' but format() will return // '2000-05-01T12:12:12.123000Z' if ((strpos('u', $format) !== -1) && (preg_match('/\.\d+Z$/', $datetime))) { return true; } return false; } protected function validateRegex($regex) { return false !== @preg_match('/' . $regex . '/u', ''); } protected function validateColor($color) { if (in_array(strtolower($color), array('aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', 'red', 'silver', 'teal', 'white', 'yellow'))) { return true; } return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color); } protected function validateStyle($style) { $properties = explode(';', rtrim($style, ';')); $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); return empty($invalidEntries); } protected function validatePhone($phone) { return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone); } protected function validateHostname($host) { $hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i'; return preg_match($hostnameRegex, $host); } } json-schema/README.md 0000644 00000014172 14736103246 0010245 0 ustar 00 # JSON Schema for PHP [![Build Status](https://travis-ci.org/justinrainbow/json-schema.svg?branch=master)](https://travis-ci.org/justinrainbow/json-schema) [![Latest Stable Version](https://poser.pugx.org/justinrainbow/json-schema/v/stable.png)](https://packagist.org/packages/justinrainbow/json-schema) [![Total Downloads](https://poser.pugx.org/justinrainbow/json-schema/downloads.png)](https://packagist.org/packages/justinrainbow/json-schema) A PHP Implementation for validating `JSON` Structures against a given `Schema`. See [json-schema](http://json-schema.org/) for more details. ## Installation ### Library ```bash git clone https://github.com/justinrainbow/json-schema.git ``` ### Composer [Install PHP Composer](https://getcomposer.org/doc/00-intro.md) ```bash composer require justinrainbow/json-schema ``` ## Usage ```php <?php $data = json_decode(file_get_contents('data.json')); // Validate $validator = new JsonSchema\Validator; $validator->validate($data, (object)['$ref' => 'file://' . realpath('schema.json')]); if ($validator->isValid()) { echo "The supplied JSON validates against the schema.\n"; } else { echo "JSON does not validate. Violations:\n"; foreach ($validator->getErrors() as $error) { echo sprintf("[%s] %s\n", $error['property'], $error['message']); } } ``` ### Type coercion If you're validating data passed to your application via HTTP, you can cast strings and booleans to the expected types defined by your schema: ```php <?php use JsonSchema\SchemaStorage; use JsonSchema\Validator; use JsonSchema\Constraints\Factory; use JsonSchema\Constraints\Constraint; $request = (object)[ 'processRefund'=>"true", 'refundAmount'=>"17" ]; $validator->validate( $request, (object) [ "type"=>"object", "properties"=>(object)[ "processRefund"=>(object)[ "type"=>"boolean" ], "refundAmount"=>(object)[ "type"=>"number" ] ] ], Constraint::CHECK_MODE_COERCE_TYPES ); // validates! is_bool($request->processRefund); // true is_int($request->refundAmount); // true ``` A shorthand method is also available: ```PHP $validator->coerce($request, $schema); // equivalent to $validator->validate($data, $schema, Constraint::CHECK_MODE_COERCE_TYPES); ``` ### 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->validate( $request, (object)[ "type"=>"object", "properties"=>(object)[ "processRefund"=>(object)[ "type"=>"boolean", "default"=>true ] ] ], Constraint::CHECK_MODE_APPLY_DEFAULTS ); //validates, and sets defaults for missing properties is_bool($request->processRefund); // true $request->processRefund; // true ``` ### With inline references ```php <?php use JsonSchema\SchemaStorage; use JsonSchema\Validator; use JsonSchema\Constraints\Factory; $jsonSchema = <<<'JSON' { "type": "object", "properties": { "data": { "oneOf": [ { "$ref": "#/definitions/integerData" }, { "$ref": "#/definitions/stringData" } ] } }, "required": ["data"], "definitions": { "integerData" : { "type": "integer", "minimum" : 0 }, "stringData" : { "type": "string" } } } JSON; // Schema must be decoded before it can be used for validation $jsonSchemaObject = json_decode($jsonSchema); // The SchemaStorage can resolve references, loading additional schemas from file as needed, etc. $schemaStorage = new SchemaStorage(); // This does two things: // 1) Mutates $jsonSchemaObject to normalize the references (to file://mySchema#/definitions/integerData, etc) // 2) Tells $schemaStorage that references to file://mySchema... should be resolved by looking in $jsonSchemaObject $schemaStorage->addSchema('file://mySchema', $jsonSchemaObject); // Provide $schemaStorage to the Validator so that references can be resolved during validation $jsonValidator = new Validator( new Factory($schemaStorage)); // JSON must be decoded before it can be validated $jsonToValidateObject = json_decode('{"data":123}'); // Do validation (use isValid() and getErrors() to check the result) $jsonValidator->validate($jsonToValidateObject, $jsonSchemaObject); ``` ### Configuration Options A number of flags are available to alter the behavior of the validator. These can be passed as the third argument to `Validator::validate()`, or can be provided as the third argument to `Factory::__construct()` if you wish to persist them across multiple `validate()` calls. | Flag | Description | |------|-------------| | `Constraint::CHECK_MODE_NORMAL` | Validate in 'normal' mode - this is the default | | `Constraint::CHECK_MODE_TYPE_CAST` | Enable fuzzy type checking for associative arrays and objects | | `Constraint::CHECK_MODE_COERCE_TYPES` | Convert data types to match the schema where possible | | `Constraint::CHECK_MODE_APPLY_DEFAULTS` | Apply default values from the schema if not set | | `Constraint::CHECK_MODE_ONLY_REQUIRED_DEFAULTS` | When applying defaults, only set values that are required | | `Constraint::CHECK_MODE_EXCEPTIONS` | Throw an exception immediately if validation fails | | `Constraint::CHECK_MODE_DISABLE_FORMAT` | Do not validate "format" constraints | | `Constraint::CHECK_MODE_VALIDATE_SCHEMA` | Validate the schema as well as the provided document | Please note that using `Constraint::CHECK_MODE_COERCE_TYPES` or `Constraint::CHECK_MODE_APPLY_DEFAULTS` will modify your original data. ## Running the tests ```bash composer test # run all unit tests composer testOnly TestClass # run specific unit test class composer testOnly TestClass::testMethod # run specific unit test method composer style-check # check code style for errors composer style-fix # automatically fix code style errors ``` json-schema/dist/schema/json-schema-draft-04.json 0000644 00000010427 14736103246 0015611 0 ustar 00 { "id": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#", "description": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": { "$ref": "#" } }, "positiveInteger": { "type": "integer", "minimum": 0 }, "positiveIntegerDefault0": { "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] }, "simpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } }, "type": "object", "properties": { "id": { "type": "string", "format": "uri" }, "$schema": { "type": "string", "format": "uri" }, "title": { "type": "string" }, "description": { "type": "string" }, "default": {}, "multipleOf": { "type": "number", "minimum": 0, "exclusiveMinimum": true }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "boolean", "default": false }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "boolean", "default": false }, "maxLength": { "$ref": "#/definitions/positiveInteger" }, "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, "pattern": { "type": "string", "format": "regex" }, "additionalItems": { "anyOf": [ { "type": "boolean" }, { "$ref": "#" } ], "default": {} }, "items": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/schemaArray" } ], "default": {} }, "maxItems": { "$ref": "#/definitions/positiveInteger" }, "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, "uniqueItems": { "type": "boolean", "default": false }, "maxProperties": { "$ref": "#/definitions/positiveInteger" }, "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, "required": { "$ref": "#/definitions/stringArray" }, "additionalProperties": { "anyOf": [ { "type": "boolean" }, { "$ref": "#" } ], "default": {} }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "properties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "dependencies": { "type": "object", "additionalProperties": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/stringArray" } ] } }, "enum": { "type": "array", "minItems": 1, "uniqueItems": true }, "type": { "anyOf": [ { "$ref": "#/definitions/simpleTypes" }, { "type": "array", "items": { "$ref": "#/definitions/simpleTypes" }, "minItems": 1, "uniqueItems": true } ] }, "allOf": { "$ref": "#/definitions/schemaArray" }, "anyOf": { "$ref": "#/definitions/schemaArray" }, "oneOf": { "$ref": "#/definitions/schemaArray" }, "not": { "$ref": "#" } }, "dependencies": { "exclusiveMaximum": [ "maximum" ], "exclusiveMinimum": [ "minimum" ] }, "default": {} } json-schema/dist/schema/json-schema-draft-03.json 0000644 00000007427 14736103246 0015616 0 ustar 00 { "$schema": "http://json-schema.org/draft-03/schema#", "id": "http://json-schema.org/draft-03/schema#", "type": "object", "properties": { "type": { "type": [ "string", "array" ], "items": { "type": [ "string", { "$ref": "#" } ] }, "uniqueItems": true, "default": "any" }, "properties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "additionalProperties": { "type": [ { "$ref": "#" }, "boolean" ], "default": {} }, "items": { "type": [ { "$ref": "#" }, "array" ], "items": { "$ref": "#" }, "default": {} }, "additionalItems": { "type": [ { "$ref": "#" }, "boolean" ], "default": {} }, "required": { "type": "boolean", "default": false }, "dependencies": { "type": "object", "additionalProperties": { "type": [ "string", "array", { "$ref": "#" } ], "items": { "type": "string" } }, "default": {} }, "minimum": { "type": "number" }, "maximum": { "type": "number" }, "exclusiveMinimum": { "type": "boolean", "default": false }, "exclusiveMaximum": { "type": "boolean", "default": false }, "minItems": { "type": "integer", "minimum": 0, "default": 0 }, "maxItems": { "type": "integer", "minimum": 0 }, "uniqueItems": { "type": "boolean", "default": false }, "pattern": { "type": "string", "format": "regex" }, "minLength": { "type": "integer", "minimum": 0, "default": 0 }, "maxLength": { "type": "integer" }, "enum": { "type": "array", "minItems": 1, "uniqueItems": true }, "default": { "type": "any" }, "title": { "type": "string" }, "description": { "type": "string" }, "format": { "type": "string" }, "divisibleBy": { "type": "number", "minimum": 0, "exclusiveMinimum": true, "default": 1 }, "disallow": { "type": [ "string", "array" ], "items": { "type": [ "string", { "$ref": "#" } ] }, "uniqueItems": true }, "extends": { "type": [ { "$ref": "#" }, "array" ], "items": { "$ref": "#" }, "default": {} }, "id": { "type": "string", "format": "uri" }, "$ref": { "type": "string", "format": "uri" }, "$schema": { "type": "string", "format": "uri" } }, "dependencies": { "exclusiveMinimum": "minimum", "exclusiveMaximum": "maximum" }, "default": {} } json-schema/LICENSE 0000644 00000002040 14736103246 0007762 0 ustar 00 MIT License Copyright (c) 2016 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.