1#!/usr/bin/env php 2<?php 3/** 4 * JSON schema validator 5 * 6 * @author Christian Weiske <christian.weiske@netresearch.de> 7 */ 8 9/** 10 * Dead simple autoloader 11 * 12 * @param string $className Name of class to load 13 * 14 * @return void 15 */ 16function __autoload($className) 17{ 18 $className = ltrim($className, '\\'); 19 $fileName = ''; 20 $namespace = ''; 21 if ($lastNsPos = strrpos($className, '\\')) { 22 $namespace = substr($className, 0, $lastNsPos); 23 $className = substr($className, $lastNsPos + 1); 24 $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; 25 } 26 $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; 27 if (stream_resolve_include_path($fileName)) { 28 require_once $fileName; 29 } 30} 31 32/** 33 * Show the json parse error that happened last 34 * 35 * @return void 36 */ 37function showJsonError() 38{ 39 $constants = get_defined_constants(true); 40 $json_errors = array(); 41 foreach ($constants['json'] as $name => $value) { 42 if (!strncmp($name, 'JSON_ERROR_', 11)) { 43 $json_errors[$value] = $name; 44 } 45 } 46 47 echo 'JSON parse error: ' . $json_errors[json_last_error()] . "\n"; 48} 49 50function getUrlFromPath($path) 51{ 52 if (parse_url($path, PHP_URL_SCHEME) !== null) { 53 //already an URL 54 return $path; 55 } 56 if ($path{0} == '/') { 57 //absolute path 58 return 'file://' . $path; 59 } 60 61 //relative path: make absolute 62 return 'file://' . getcwd() . '/' . $path; 63} 64 65/** 66 * Take a HTTP header value and split it up into parts. 67 * 68 * @return array Key "_value" contains the main value, all others 69 * as given in the header value 70 */ 71function parseHeaderValue($headerValue) 72{ 73 if (strpos($headerValue, ';') === false) { 74 return array('_value' => $headerValue); 75 } 76 77 $parts = explode(';', $headerValue); 78 $arData = array('_value' => array_shift($parts)); 79 foreach ($parts as $part) { 80 list($name, $value) = explode('=', $part); 81 $arData[$name] = trim($value, ' "\''); 82 } 83 return $arData; 84} 85 86 87// support running this tool from git checkout 88if (is_dir(__DIR__ . '/../src/JsonSchema')) { 89 set_include_path(__DIR__ . '/../src' . PATH_SEPARATOR . get_include_path()); 90} 91 92$arOptions = array(); 93$arArgs = array(); 94array_shift($argv);//script itself 95foreach ($argv as $arg) { 96 if ($arg{0} == '-') { 97 $arOptions[$arg] = true; 98 } else { 99 $arArgs[] = $arg; 100 } 101} 102 103if (count($arArgs) == 0 104 || isset($arOptions['--help']) || isset($arOptions['-h']) 105) { 106 echo <<<HLP 107Validate schema 108Usage: validate-json data.json 109 or: validate-json data.json schema.json 110 111Options: 112 --dump-schema Output full schema and exit 113 --dump-schema-url Output URL of schema 114 -h --help Show this help 115 116HLP; 117 exit(1); 118} 119 120if (count($arArgs) == 1) { 121 $pathData = $arArgs[0]; 122 $pathSchema = null; 123} else { 124 $pathData = $arArgs[0]; 125 $pathSchema = getUrlFromPath($arArgs[1]); 126} 127 128$urlData = getUrlFromPath($pathData); 129 130$context = stream_context_create( 131 array( 132 'http' => array( 133 'header' => array( 134 'Accept: */*', 135 'Connection: Close' 136 ), 137 'max_redirects' => 5 138 ) 139 ) 140); 141$dataString = file_get_contents($pathData, false, $context); 142if ($dataString == '') { 143 echo "Data file is not readable or empty.\n"; 144 exit(3); 145} 146 147$data = json_decode($dataString); 148unset($dataString); 149if ($data === null) { 150 echo "Error loading JSON data file\n"; 151 showJsonError(); 152 exit(5); 153} 154 155if ($pathSchema === null) { 156 if (isset($http_response_header)) { 157 array_shift($http_response_header);//HTTP/1.0 line 158 foreach ($http_response_header as $headerLine) { 159 list($hName, $hValue) = explode(':', $headerLine, 2); 160 $hName = strtolower($hName); 161 if ($hName == 'link') { 162 //Link: <http://example.org/schema#>; rel="describedBy" 163 $hParts = parseHeaderValue($hValue); 164 if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') { 165 $pathSchema = trim($hParts['_value'], ' <>'); 166 } 167 } else if ($hName == 'content-type') { 168 //Content-Type: application/my-media-type+json; 169 // profile=http://example.org/schema# 170 $hParts = parseHeaderValue($hValue); 171 if (isset($hParts['profile'])) { 172 $pathSchema = $hParts['profile']; 173 } 174 175 } 176 } 177 } 178 if (is_object($data) && property_exists($data, '$schema')) { 179 $pathSchema = $data->{'$schema'}; 180 } 181 182 //autodetect schema 183 if ($pathSchema === null) { 184 echo "JSON data must be an object and have a \$schema property.\n"; 185 echo "You can pass the schema file on the command line as well.\n"; 186 echo "Schema autodetection failed.\n"; 187 exit(6); 188 } 189} 190if ($pathSchema{0} == '/') { 191 $pathSchema = 'file://' . $pathSchema; 192} 193 194$resolver = new JsonSchema\Uri\UriResolver(); 195$retriever = new JsonSchema\Uri\UriRetriever(); 196try { 197 $urlSchema = $resolver->resolve($pathSchema, $urlData); 198 199 if (isset($arOptions['--dump-schema-url'])) { 200 echo $urlSchema . "\n"; 201 exit(); 202 } 203} catch (Exception $e) { 204 echo "Error loading JSON schema file\n"; 205 echo $urlSchema . "\n"; 206 echo $e->getMessage() . "\n"; 207 exit(2); 208} 209$refResolver = new JsonSchema\RefResolver($retriever, $resolver); 210$schema = $refResolver->resolve($urlSchema); 211 212if (isset($arOptions['--dump-schema'])) { 213 $options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; 214 echo json_encode($schema, $options) . "\n"; 215 exit(); 216} 217 218try { 219 $validator = new JsonSchema\Validator(); 220 $validator->check($data, $schema); 221 222 if ($validator->isValid()) { 223 echo "OK. The supplied JSON validates against the schema.\n"; 224 } else { 225 echo "JSON does not validate. Violations:\n"; 226 foreach ($validator->getErrors() as $error) { 227 echo sprintf("[%s] %s\n", $error['property'], $error['message']); 228 } 229 exit(23); 230 } 231} catch (Exception $e) { 232 echo "JSON does not validate. Error:\n"; 233 echo $e->getMessage() . "\n"; 234 echo "Error code: " . $e->getCode() . "\n"; 235 exit(24); 236} 237