|
| 1 | +#!/usr/bin/env php |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * PHP OpenAPI validation tool |
| 6 | + * |
| 7 | + * @copyright Copyright (c) 2018 Carsten Brandt <[email protected]> and contributors |
| 8 | + * @license https://github.com/cebe/php-openapi/blob/master/LICENSE |
| 9 | + */ |
| 10 | + |
| 11 | +$composerAutoload = [ |
| 12 | + __DIR__ . '/../vendor/autoload.php', // standalone with "composer install" run |
| 13 | + __DIR__ . '/../../../autoload.php', // script is installed as a composer binary |
| 14 | +]; |
| 15 | +foreach ($composerAutoload as $autoload) { |
| 16 | + if (file_exists($autoload)) { |
| 17 | + require($autoload); |
| 18 | + break; |
| 19 | + } |
| 20 | +} |
| 21 | + |
| 22 | +// Send all errors to stderr |
| 23 | +ini_set('display_errors', 'stderr'); |
| 24 | +// open streams if not in CLI sapi |
| 25 | +defined('STDOUT') or define('STDOUT', fopen('php://stdout', 'w')); |
| 26 | +defined('STDERR') or define('STDERR', fopen('php://stderr', 'w')); |
| 27 | + |
| 28 | +$command = null; |
| 29 | +$inputFile = null; |
| 30 | +$inputFormat = null; |
| 31 | +$outputFile = null; |
| 32 | +$outputFormat = null; |
| 33 | +foreach($argv as $k => $arg) { |
| 34 | + if ($k == 0) { |
| 35 | + continue; |
| 36 | + } |
| 37 | + if ($arg[0] == '-' || $arg === 'help') { |
| 38 | + $arg = explode('=', $arg); |
| 39 | + switch($arg[0]) { |
| 40 | + case '--read-yaml': |
| 41 | + if ($inputFormat === null) { |
| 42 | + $inputFormat = 'yaml'; |
| 43 | + } else { |
| 44 | + error("Conflicting arguments: only one of --read-json or --read-yaml is allowed!", "usage"); |
| 45 | + } |
| 46 | + break; |
| 47 | + case '--read-json': |
| 48 | + if ($inputFormat === null) { |
| 49 | + $inputFormat = 'json'; |
| 50 | + } else { |
| 51 | + error("Conflicting arguments: only one of --read-json or --read-yaml is allowed!", "usage"); |
| 52 | + } |
| 53 | + break; |
| 54 | + case '--write-yaml': |
| 55 | + if ($outputFormat === null) { |
| 56 | + $outputFormat = 'yaml'; |
| 57 | + } else { |
| 58 | + error("Conflicting arguments: only one of --write-json or --write-yaml is allowed!", "usage"); |
| 59 | + } |
| 60 | + break; |
| 61 | + case '--write-json': |
| 62 | + if ($outputFormat === null) { |
| 63 | + $outputFormat = 'json'; |
| 64 | + } else { |
| 65 | + error("Conflicting arguments: only one of --write-json or --write-yaml is allowed!", "usage"); |
| 66 | + } |
| 67 | + break; |
| 68 | + case '-h': |
| 69 | + case '--help': |
| 70 | + case 'help': |
| 71 | + print_formatted( |
| 72 | + "\BPHP OpenAPI 3 tool\C\n" |
| 73 | + . "\B------------------\C\n" |
| 74 | + . "by Carsten Brandt <[email protected]>\n\n", |
| 75 | + STDERR |
| 76 | + ); |
| 77 | + usage(); |
| 78 | + break; |
| 79 | + default: |
| 80 | + error("Unknown argument " . $arg[0], "usage"); |
| 81 | + } |
| 82 | + } else { |
| 83 | + if ($command === null) { |
| 84 | + $command = $arg; |
| 85 | + } elseif ($inputFile === null) { |
| 86 | + $inputFile = $arg; |
| 87 | + } elseif ($outputFile === null) { |
| 88 | + if ($command !== 'convert') { |
| 89 | + error("Too many arguments: " . $arg, "usage"); |
| 90 | + } |
| 91 | + $outputFile = $arg; |
| 92 | + } else { |
| 93 | + error("Too many arguments: " . $arg, "usage"); |
| 94 | + } |
| 95 | + } |
| 96 | +} |
| 97 | +switch ($command) { |
| 98 | + case 'validate': |
| 99 | + |
| 100 | + $openApi = read_input($inputFile, $inputFormat); |
| 101 | + |
| 102 | + // Validate |
| 103 | + |
| 104 | + $openApi->validate(); |
| 105 | + $errors = $openApi->getErrors(); |
| 106 | + |
| 107 | + $validator = new JsonSchema\Validator; |
| 108 | + $validator->validate($openApi->getSerializableData(), (object)['$ref' => 'file://' . dirname(__DIR__) . '/schemas/openapi-v3.0.json']); |
| 109 | + |
| 110 | + if ($validator->isValid() && empty($errors)) { |
| 111 | + print_formatted("The supplied API Description \B\Gvalidates\C against the OpenAPI v3.0 schema.\n", STDERR); |
| 112 | + exit(0); |
| 113 | + } |
| 114 | + |
| 115 | + if (!empty($errors)) { |
| 116 | + print_formatted("\BErrors found while reading the API Description:\C\n", STDERR); |
| 117 | + foreach ($errors as $error) { |
| 118 | + fwrite(STDERR, "- $error\n"); |
| 119 | + } |
| 120 | + } |
| 121 | + if (!$validator->isValid()) { |
| 122 | + print_formatted("\BOpenAPI v3.0 schema violations:\C\n", STDERR); |
| 123 | + foreach ($validator->getErrors() as $error) { |
| 124 | + print_formatted(sprintf("- [\Y%s\C] %s\n", $error['property'], $error['message']), STDERR); |
| 125 | + } |
| 126 | + } |
| 127 | + exit(2); |
| 128 | + |
| 129 | + break; |
| 130 | + case 'convert': |
| 131 | + |
| 132 | + $openApi = read_input($inputFile, $inputFormat); |
| 133 | + |
| 134 | + if ($outputFile === null) { |
| 135 | + if ($outputFormat === null) { |
| 136 | + error("No output fromat specified, please specify --write-json or --write-yaml.", "usage"); |
| 137 | + } elseif ($outputFormat === 'json') { |
| 138 | + fwrite(STDOUT, \cebe\openapi\Writer::writeToJson($openApi)); |
| 139 | + } else { |
| 140 | + fwrite(STDOUT, \cebe\openapi\Writer::writeToYaml($openApi)); |
| 141 | + } |
| 142 | + fclose(STDOUT); |
| 143 | + exit(0); |
| 144 | + } |
| 145 | + |
| 146 | + if ($outputFormat === null) { |
| 147 | + if (strtolower(substr($outputFile, -5, 5)) === '.json') { |
| 148 | + $outputFormat = 'json'; |
| 149 | + } elseif (strtolower(substr($outputFile, -5, 5)) === '.yaml') { |
| 150 | + $outputFormat = 'yaml'; |
| 151 | + } elseif (strtolower(substr($outputFile, -4, 4)) === '.yml') { |
| 152 | + $outputFormat = 'yaml'; |
| 153 | + } else { |
| 154 | + error("Failed to detect output format from file name, please specify --write-json or --write-yaml.", "usage"); |
| 155 | + } |
| 156 | + } |
| 157 | + if ($outputFormat === 'json') { |
| 158 | + \cebe\openapi\Writer::writeToJsonFile($openApi, $outputFile); |
| 159 | + } else { |
| 160 | + \cebe\openapi\Writer::writeToYamlFile($openApi, $outputFile); |
| 161 | + } |
| 162 | + exit(0); |
| 163 | + |
| 164 | + break; |
| 165 | + case null: |
| 166 | + error("No command specified.", "usage"); |
| 167 | + break; |
| 168 | + default: |
| 169 | + error("Unknown command " . $command, "usage"); |
| 170 | +} |
| 171 | + |
| 172 | + |
| 173 | + |
| 174 | +// functions |
| 175 | + |
| 176 | +function read_input($inputFile, $inputFormat) |
| 177 | +{ |
| 178 | + try { |
| 179 | + if ($inputFile === null) { |
| 180 | + $fileContent = file_get_contents("php://stdin"); |
| 181 | + if ($inputFormat === null) { |
| 182 | + $inputFormat = (ltrim($fileContent) === '{' && rtrim($fileContent) === '}') ? 'json' : 'yaml'; |
| 183 | + } |
| 184 | + if ($inputFormat === 'json') { |
| 185 | + $openApi = \cebe\openapi\Reader::readFromJson($fileContent); |
| 186 | + } else { |
| 187 | + $openApi = \cebe\openapi\Reader::readFromYaml($fileContent); |
| 188 | + } |
| 189 | + } else { |
| 190 | + if (!file_exists($inputFile)) { |
| 191 | + error("File does not exist: " . $inputFile); |
| 192 | + } |
| 193 | + if ($inputFormat === null) { |
| 194 | + if (strtolower(substr($inputFile, -5, 5)) === '.json') { |
| 195 | + $inputFormat = 'json'; |
| 196 | + } elseif (strtolower(substr($inputFile, -5, 5)) === '.yaml') { |
| 197 | + $inputFormat = 'yaml'; |
| 198 | + } elseif (strtolower(substr($inputFile, -4, 4)) === '.yml') { |
| 199 | + $inputFormat = 'yaml'; |
| 200 | + } else { |
| 201 | + error("Failed to detect input format from file name, please specify --read-json or --read-yaml.", "usage"); |
| 202 | + } |
| 203 | + } |
| 204 | + if ($inputFormat === 'json') { |
| 205 | + $openApi = \cebe\openapi\Reader::readFromJsonFile(realpath($inputFile)); |
| 206 | + } else { |
| 207 | + $openApi = \cebe\openapi\Reader::readFromYamlFile(realpath($inputFile)); |
| 208 | + } |
| 209 | + } |
| 210 | + } catch (Symfony\Component\Yaml\Exception\ParseException $e) { |
| 211 | + error($e->getMessage()); |
| 212 | + exit(1); |
| 213 | + } |
| 214 | + return $openApi; |
| 215 | +} |
| 216 | + |
| 217 | +/** |
| 218 | + * Display usage information |
| 219 | + */ |
| 220 | +function usage() { |
| 221 | + global $argv; |
| 222 | + $cmd = basename($argv[0]); |
| 223 | + print_formatted(<<<EOF |
| 224 | +Usage: |
| 225 | + $cmd \B<command>\C [\Y<options>\C] [\Ginput.yml\C|\Ginput.json\C] [\Goutput.yml\C|\Goutput.json\C] |
| 226 | +
|
| 227 | + The following commands are available: |
| 228 | +
|
| 229 | + \Bvalidate\C Validate the API description in the specified \Ginput file\C against the OpenAPI v3.0 schema. |
| 230 | + Note: the validation is performed in two steps. The results is composed of |
| 231 | + (1) structural errors found while reading the API description file, and |
| 232 | + (2) violations of the OpenAPI v3.0 schema. |
| 233 | +
|
| 234 | + If no input file is specified input will be read from STDIN. |
| 235 | + The tool will try to auto-detect the content type of the input, but may fail |
| 236 | + to do so, you may specify \Y--read-yaml\C or \Y--read-json\C to force the file type. |
| 237 | +
|
| 238 | + Exits with code 2 on validation errors, 1 on other errors and 0 on success. |
| 239 | +
|
| 240 | + \Bconvert\C Convert a JSON or YAML input file to JSON or YAML output file. |
| 241 | + References are being resolved so the output will be a single specification file. |
| 242 | +
|
| 243 | + If no input file is specified input will be read from STDIN. |
| 244 | + If no output file is specified output will be written to STDOUT. |
| 245 | + The tool will try to auto-detect the content type of the input and output file, but may fail |
| 246 | + to do so, you may specify \Y--read-yaml\C or \Y--read-json\C to force the input file type. |
| 247 | + and \Y--write-yaml\C or \Y--write-json\C to force the output file type. |
| 248 | +
|
| 249 | + \Bhelp\C Shows this usage information. |
| 250 | +
|
| 251 | + Options: |
| 252 | +
|
| 253 | + \Y--read-json\C force reading input as JSON. Auto-detect if not specified. |
| 254 | + \Y--read-yaml\C force reading input as YAML. Auto-detect if not specified. |
| 255 | + \Y--write-json\C force writing output as JSON. Auto-detect if not specified. |
| 256 | + \Y--write-yaml\C force writing output as YAML. Auto-detect if not specified. |
| 257 | +
|
| 258 | +
|
| 259 | +EOF |
| 260 | + , STDERR |
| 261 | +); |
| 262 | + exit(1); |
| 263 | +} |
| 264 | + |
| 265 | +/** |
| 266 | + * Send custom error message to stderr |
| 267 | + * @param $message string |
| 268 | + * @param $callback mixed called before script exit |
| 269 | + * @return void |
| 270 | + */ |
| 271 | +function error($message, $callback = null) { |
| 272 | + print_formatted("\B\RError\C: " . $message . "\n", STDERR); |
| 273 | + if (is_callable($callback)) { |
| 274 | + call_user_func($callback); |
| 275 | + } |
| 276 | + exit(1); |
| 277 | +} |
| 278 | + |
| 279 | +function print_formatted($string, $stream) { |
| 280 | + fwrite($stream, strtr($string, [ |
| 281 | + '\\Y' => "\033[33m", // yellow |
| 282 | + '\\G' => "\033[32m", // green |
| 283 | + '\\R' => "\033[31m", // green |
| 284 | + '\\B' => "\033[1m", // bold |
| 285 | + '\\C' => "\033[0m", // clear |
| 286 | + ])); |
| 287 | +} |
0 commit comments