1
1
<?php
2
2
// apispec.php -- HotCRP script for generating OpenAPI specification
3
- // Copyright (c) 2006-2022 Eddie Kohler; see LICENSE.
3
+ // Copyright (c) 2006-2024 Eddie Kohler; see LICENSE.
4
4
5
5
if (realpath ($ _SERVER ["PHP_SELF " ]) === __FILE__ ) {
6
6
require_once (dirname (__DIR__ ) . "/src/init.php " );
@@ -22,12 +22,29 @@ class APISpec_Batch {
22
22
private $ schemas ;
23
23
/** @var ?object */
24
24
private $ parameters ;
25
+ /** @var string */
26
+ private $ output_file = "- " ;
25
27
26
28
function __construct (Conf $ conf , $ arg ) {
27
29
$ this ->conf = $ conf ;
28
30
$ this ->user = $ conf ->root_user ();
29
31
$ this ->api_map = $ conf ->api_map ();
30
32
$ this ->j = (object ) [];
33
+
34
+ if (isset ($ arg ["i " ])) {
35
+ if ($ arg ["i " ] === "- " ) {
36
+ $ s = stream_get_contents (STDIN );
37
+ } else {
38
+ $ s = file_get_contents_throw (safe_filename ($ arg ["i " ]));
39
+ }
40
+ if ($ s === false || !is_object ($ this ->j = json_decode ($ s ))) {
41
+ throw new CommandLineException ($ arg ["i " ] . ": Invalid input " );
42
+ }
43
+ $ this ->output_file = $ arg ["i " ];
44
+ }
45
+ if (isset ($ arg ["o " ])) {
46
+ $ this ->output_file = $ arg ["o " ];
47
+ }
31
48
}
32
49
33
50
/** @return int */
@@ -52,7 +69,18 @@ function run() {
52
69
}
53
70
}
54
71
55
- fwrite (STDOUT , json_encode ($ this ->j , JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ) . "\n" );
72
+ if (($ this ->output_file ?? "- " ) === "- " ) {
73
+ $ out = STDOUT ;
74
+ } else {
75
+ $ out = @fopen (safe_filename ($ this ->output_file ), "wb " );
76
+ if (!$ out ) {
77
+ throw error_get_last_as_exception ("{$ this ->output_file }: " );
78
+ }
79
+ }
80
+ fwrite ($ out , json_encode ($ this ->j , JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ) . "\n" );
81
+ if ($ out !== STDOUT ) {
82
+ fclose ($ out );
83
+ }
56
84
return 0 ;
57
85
}
58
86
@@ -124,7 +152,7 @@ private function expand_path_method($path, $method, $known, $j) {
124
152
$ pathj = $ this ->paths ->$ path = $ this ->paths ->$ path ?? (object ) [];
125
153
$ lmethod = strtolower ($ method );
126
154
$ xj = $ pathj ->$ lmethod = $ pathj ->$ lmethod ?? (object ) [];
127
- $ this ->expand_request ($ xj , $ known , $ j );
155
+ $ this ->expand_request ($ xj , $ known , $ j, "{ $ path } . { $ lmethod }" );
128
156
$ this ->expand_response ($ xj , $ j );
129
157
}
130
158
@@ -199,19 +227,20 @@ private function resolve_common_param($name) {
199
227
200
228
/** @param object $x
201
229
* @param array<string,int> $known
202
- * @param object $j */
203
- private function expand_request ($ x , $ known , $ j ) {
230
+ * @param object $j
231
+ * @param string $path */
232
+ private function expand_request ($ x , $ known , $ j , $ path ) {
204
233
$ params = $ body_properties = $ body_required = [];
205
234
$ has_file = false ;
206
235
foreach ($ known as $ name => $ f ) {
207
236
if ($ name === "* " ) {
208
237
// skip
209
238
} else if ($ name === "p " && $ f === (self ::F_REQUIRED | self ::F_PATH )) {
210
- $ params [] = $ this ->resolve_common_param ("p " );
239
+ $ params [" p " ] = $ this ->resolve_common_param ("p " );
211
240
} else if ($ name === "redirect " && $ f === 0 ) {
212
- $ params [] = $ this ->resolve_common_param ("redirect " );
241
+ $ params [" redirect " ] = $ this ->resolve_common_param ("redirect " );
213
242
} else if (($ f & (self ::F_BODY | self ::F_FILE )) === 0 ) {
214
- $ params [] = (object ) [
243
+ $ params [$ name ] = (object ) [
215
244
"name " => $ name ,
216
245
"in " => "query " ,
217
246
"required " => ($ f & self ::F_REQUIRED ) !== 0 ,
@@ -230,7 +259,39 @@ private function expand_request($x, $known, $j) {
230
259
}
231
260
}
232
261
if (!empty ($ params )) {
233
- $ x ->parameters = $ params ;
262
+ $ x ->parameters = $ x ->parameters ?? [];
263
+ $ xparams = [];
264
+ foreach ($ x ->parameters as $ i => $ pj ) {
265
+ if (isset ($ pj ->name ) && is_string ($ pj ->name )) {
266
+ $ xparams [$ pj ->name ] = $ i ;
267
+ } else if (isset ($ pj ->{"\$ref " }) && is_string ($ pj ->{"\$ref " })
268
+ && preg_match ('/\A\#\/components\/parameters\/([^+]*)/ ' , $ pj ->{"\$ref " }, $ m )) {
269
+ $ xparams [$ m [1 ]] = $ i ;
270
+ }
271
+ }
272
+ foreach ($ params as $ n => $ npj ) {
273
+ $ i = $ xparams [$ n ] ?? null ;
274
+ if ($ i === null ) {
275
+ $ x ->parameters [] = $ npj ;
276
+ continue ;
277
+ }
278
+ $ xpj = $ x ->parameters [$ i ];
279
+ if (isset ($ xpj ->{"\$ref " }) !== isset ($ npj ->{"\$ref " })) {
280
+ fwrite (STDERR , "{$ path }.param[ {$ n }]: \$ref status differs \n" );
281
+ } else if (isset ($ xpj ->{"\$ref " })) {
282
+ if ($ xpj ->{"\$ref " } !== $ npj ->{"\$ref " }) {
283
+ fwrite (STDERR , "{$ path }.param[ {$ n }]: \$ref destination differs \n" );
284
+ }
285
+ } else {
286
+ foreach ((array ) $ npj as $ k => $ v ) {
287
+ if (!isset ($ xpj ->$ k )) {
288
+ $ xpj ->$ k = $ v ;
289
+ } else if (is_scalar ($ v ) && $ xpj ->$ k !== $ v ) {
290
+ fwrite (STDERR , "{$ path }.param[ {$ n }]: {$ k } differs \n" );
291
+ }
292
+ }
293
+ }
294
+ }
234
295
}
235
296
if (!empty ($ body_properties )) {
236
297
$ schema = (object ) [
@@ -303,7 +364,9 @@ static function make_args($argv) {
303
364
$ arg = (new Getopt )->long (
304
365
"name:,n: ! " ,
305
366
"config: ! " ,
306
- "help,h ! "
367
+ "help,h ! " ,
368
+ "i:,input: =FILE Modify existing specification in FILE " ,
369
+ "o:,output: =FILE Write specification to FILE "
307
370
)->description ("Generate an OpenAPI specification.
308
371
Usage: php batch/apispec.php " )
309
372
->helpopt ("help " )
0 commit comments