validate-json 6.7 KB

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