Skip to content

Commit 22d97c2

Browse files
committed
Merge pull request #75 from netresearch/ASTV-22
Fix for #62, #63, #64: automatically detect schema URL
2 parents 6d6da00 + 97f1612 commit 22d97c2

File tree

1 file changed

+171
-26
lines changed

1 file changed

+171
-26
lines changed

bin/validate-json

Lines changed: 171 additions & 26 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,55 +47,198 @@ 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 < 3) {
55-
echo "Usage: validate-json schema.json data.json\n";
56-
exit(1);
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+
}
57101
}
58102

59-
$pathSchema = $argv[1];
60-
$pathData = $argv[2];
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
61110
62-
if (!is_readable($pathSchema)) {
63-
echo "Schema file is not readable.\n";
64-
exit(2);
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;
117+
exit(1);
65118
}
66119

67-
if (!is_readable($pathData)) {
68-
echo "Data file is not readable.\n";
69-
exit(3);
120+
if (count($arArgs) == 1) {
121+
$pathData = $arArgs[0];
122+
$pathSchema = null;
123+
} else {
124+
$pathData = $arArgs[0];
125+
$pathSchema = getUrlFromPath($arArgs[1]);
70126
}
71127

72-
$data = json_decode(file_get_contents($pathData));
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);
142+
if ($dataString == '') {
143+
echo "Data file is not readable or empty.\n";
144+
exit(3);
145+
}
73146

147+
$data = json_decode($dataString);
148+
unset($dataString);
74149
if ($data === null) {
75150
echo "Error loading JSON data file\n";
76151
showJsonError();
77152
exit(5);
78153
}
79154

80-
$schema = json_decode(file_get_contents($pathSchema));
155+
if ($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+
}
190+
if ($pathSchema{0} == '/') {
191+
$pathSchema = 'file://' . $pathSchema;
192+
}
193+
194+
$resolver = new JsonSchema\Uri\UriResolver();
195+
$retriever = new JsonSchema\Uri\UriRetriever();
196+
try {
197+
$urlSchema = $resolver->resolve($pathSchema, $urlData);
198+
199+
if (isset($arOptions['--dump-schema-url'])) {
200+
echo $urlSchema . "\n";
201+
exit();
202+
}
81203

82-
if ($schema === null) {
204+
$schema = $retriever->retrieve($urlSchema);
205+
if ($schema === null) {
206+
echo "Error loading JSON schema file\n";
207+
echo $urlSchema . "\n";
208+
showJsonError();
209+
exit(2);
210+
}
211+
} catch (Exception $e) {
83212
echo "Error loading JSON schema file\n";
84-
showJsonError();
85-
exit(6);
213+
echo $urlSchema . "\n";
214+
echo $e->getMessage() . "\n";
215+
exit(2);
86216
}
217+
$refResolver = new JsonSchema\RefResolver($retriever);
218+
$refResolver->resolve($schema, $urlSchema);
87219

88-
$validator = new JsonSchema\Validator();
89-
$validator->check($data, $schema);
220+
if (isset($arOptions['--dump-schema'])) {
221+
echo json_encode($schema, JSON_PRETTY_PRINT) . "\n";
222+
exit();
223+
}
90224

91-
if ($validator->isValid()) {
92-
echo "OK. The supplied JSON validates against the schema.\n";
93-
} else {
94-
echo "JSON does not validate. Violations:\n";
95-
foreach ($validator->getErrors() as $error) {
96-
echo sprintf("[%s] %s\n", $error['property'], $error['message']);
225+
try {
226+
$validator = new JsonSchema\Validator();
227+
$validator->check($data, $schema);
228+
229+
if ($validator->isValid()) {
230+
echo "OK. The supplied JSON validates against the schema.\n";
231+
} else {
232+
echo "JSON does not validate. Violations:\n";
233+
foreach ($validator->getErrors() as $error) {
234+
echo sprintf("[%s] %s\n", $error['property'], $error['message']);
235+
}
236+
exit(23);
97237
}
98-
exit(23);
238+
} catch (Exception $e) {
239+
echo "JSON does not validate. Error:\n";
240+
echo $e->getMessage() . "\n";
241+
echo "Error code: " . $e->getCode() . "\n";
242+
exit(24);
99243
}
244+
?>

0 commit comments

Comments
 (0)