Skip to content

Commit 0e0d478

Browse files
author
Christian Weiske
committed
Fix #62: detect JSON schema from HTTP headers of data file
1 parent 850319d commit 0e0d478

File tree

1 file changed

+156
-38
lines changed

1 file changed

+156
-38
lines changed

bin/validate-json

Lines changed: 156 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ function __autoload($className)
2424
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
2525
}
2626
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
27-
require_once $fileName;
27+
if (stream_resolve_include_path($fileName)) {
28+
require_once $fileName;
29+
}
2830
}
2931

3032
/**
@@ -45,79 +47,195 @@ function showJsonError()
4547
echo 'JSON parse error: ' . $json_errors[json_last_error()] . "\n";
4648
}
4749

50+
function 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+
*/
71+
function 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+
4886

4987
// support running this tool from git checkout
5088
if (is_dir(__DIR__ . '/../src/JsonSchema')) {
5189
set_include_path(__DIR__ . '/../src' . PATH_SEPARATOR . get_include_path());
5290
}
5391

54-
if ($argc < 2) {
55-
echo "Usage: validate-json data.json\n";
56-
echo " or: validate-json data.json schema.json\n";
92+
$arOptions = array();
93+
$arArgs = array();
94+
array_shift($argv);//script itself
95+
foreach ($argv as $arg) {
96+
if ($arg{0} == '-') {
97+
$arOptions[$arg] = true;
98+
} else {
99+
$arArgs[] = $arg;
100+
}
101+
}
102+
103+
if (count($arArgs) == 0
104+
|| isset($arOptions['--help']) || isset($arOptions['-h'])
105+
) {
106+
echo <<<HLP
107+
Validate schema
108+
Usage: validate-json data.json
109+
or: validate-json data.json schema.json
110+
111+
Options:
112+
--dump-schema Output full schema and exit
113+
--dump-schema-url Output URL of schema
114+
-h --help Show this help
115+
116+
HLP;
57117
exit(1);
58118
}
59119

60-
if ($argc == 2) {
61-
$pathData = $argv[1];
120+
if (count($arArgs) == 1) {
121+
$pathData = $arArgs[0];
62122
$pathSchema = null;
63123
} else {
64-
$pathData = $argv[1];
65-
$pathSchema = $argv[2];
124+
$pathData = $arArgs[0];
125+
$pathSchema = $arArgs[1];
66126
}
67127

68-
if (!is_readable($pathData)) {
69-
echo "Data file is not readable.\n";
128+
$urlData = getUrlFromPath($pathData);
129+
130+
$context = stream_context_create(
131+
array(
132+
'http' => array(
133+
'header' => 'Accept: */*',
134+
'max_redirects' => 5
135+
)
136+
)
137+
);
138+
$dataString = file_get_contents($pathData, false, $context);
139+
if ($dataString == '') {
140+
echo "Data file is not readable or empty.\n";
70141
exit(3);
71142
}
72143

73-
$data = json_decode(file_get_contents($pathData));
74-
144+
$data = json_decode($dataString);
145+
unset($dataString);
75146
if ($data === null) {
76147
echo "Error loading JSON data file\n";
77148
showJsonError();
78149
exit(5);
79150
}
80151

81152
if ($pathSchema === null) {
153+
if (isset($http_response_header)) {
154+
array_shift($http_response_header);//HTTP/1.0 line
155+
foreach ($http_response_header as $headerLine) {
156+
list($hName, $hValue) = explode(':', $headerLine, 2);
157+
$hName = strtolower($hName);
158+
if ($hName == 'link') {
159+
//Link: <http://example.org/schema#>; rel="describedBy"
160+
$hParts = parseHeaderValue($hValue);
161+
if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') {
162+
$pathSchema = trim($hParts['_value'], ' <>');
163+
}
164+
} else if ($hName == 'content-type') {
165+
//Content-Type: application/my-media-type+json;
166+
// profile=http://example.org/schema#
167+
$hParts = parseHeaderValue($hValue);
168+
if (isset($hParts['profile'])) {
169+
$pathSchema = $hParts['profile'];
170+
}
171+
172+
}
173+
}
174+
}
175+
if (is_object($data) && property_exists($data, '$schema')) {
176+
$pathSchema = $data->{'$schema'};
177+
}
178+
82179
//autodetect schema
83-
if (!is_object($data) || !property_exists($data, '$schema')) {
180+
if ($pathSchema === null) {
84181
echo "JSON data must be an object and have a \$schema property.\n";
85182
echo "You can pass the schema file on the command line as well.\n";
86183
echo "Schema autodetection failed.\n";
87184
exit(6);
88185
}
89-
$pathSchema = $data->{'$schema'};
90-
if ($pathSchema{0} != '/'
91-
&& parse_url($pathSchema, PHP_URL_SCHEME) === false
92-
) {
93-
$pathSchema = dirname($pathData) . '/' . $pathSchema;
94-
}
95186
}
96-
97-
$schemaData = file_get_contents($pathSchema);
98-
if ($schemaData === false) {
99-
echo "Schema file '$pathSchema' is not readable.\n";
100-
exit(2);
187+
if ($pathSchema{0} == '/') {
188+
$pathSchema = 'file://' . $pathSchema;
101189
}
102190

103-
$schema = json_decode($schemaData);
104-
unset($schemaData);
191+
$resolver = new JsonSchema\Uri\UriResolver();
192+
$retriever = new JsonSchema\Uri\UriRetriever();
193+
try {
194+
$urlSchema = $resolver->resolve($pathSchema, $urlData);
105195

106-
if ($schema === null) {
196+
if (isset($arOptions['--dump-schema-url'])) {
197+
echo $urlSchema . "\n";
198+
exit();
199+
}
200+
201+
$schema = $retriever->retrieve($urlSchema);
202+
if ($schema === null) {
203+
echo "Error loading JSON schema file\n";
204+
echo $urlSchema . "\n";
205+
showJsonError();
206+
exit(2);
207+
}
208+
} catch (Exception $e) {
107209
echo "Error loading JSON schema file\n";
108-
showJsonError();
109-
exit(6);
210+
echo $urlSchema . "\n";
211+
echo $e->getMessage() . "\n";
212+
exit(2);
110213
}
214+
$refResolver = new JsonSchema\RefResolver($retriever);
215+
$refResolver->resolve($schema, $urlSchema);
111216

112-
$validator = new JsonSchema\Validator();
113-
$validator->check($data, $schema);
217+
if (isset($arOptions['--dump-schema'])) {
218+
echo json_encode($schema, JSON_PRETTY_PRINT) . "\n";
219+
exit();
220+
}
114221

115-
if ($validator->isValid()) {
116-
echo "OK. The supplied JSON validates against the schema.\n";
117-
} else {
118-
echo "JSON does not validate. Violations:\n";
119-
foreach ($validator->getErrors() as $error) {
120-
echo sprintf("[%s] %s\n", $error['property'], $error['message']);
222+
try {
223+
$validator = new JsonSchema\Validator();
224+
$validator->check($data, $schema);
225+
226+
if ($validator->isValid()) {
227+
echo "OK. The supplied JSON validates against the schema.\n";
228+
} else {
229+
echo "JSON does not validate. Violations:\n";
230+
foreach ($validator->getErrors() as $error) {
231+
echo sprintf("[%s] %s\n", $error['property'], $error['message']);
232+
}
233+
exit(23);
121234
}
122-
exit(23);
235+
} catch (Exception $e) {
236+
echo "JSON does not validate. Error:\n";
237+
echo $e->getMessage() . "\n";
238+
echo "Error code: " . $e->getCode() . "\n";
239+
exit(24);
123240
}
241+
?>

0 commit comments

Comments
 (0)