-
Notifications
You must be signed in to change notification settings - Fork 5
/
core.lisp
614 lines (568 loc) · 29.4 KB
/
core.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
(in-package :cl-user)
(defpackage :openapi2cl/core
(:use #:cl)
(:import-from #:yason)
(:import-from #:cl-yaml)
(:import-from #:cl-strings)
(:import-from #:kebab)
(:export
#:with-directory-generate-files
#:with-directory-generate
#:with-yaml-generate
#:with-json-generate))
(in-package :openapi2cl/core)
;; NOTE: The generation occurs by building up sexps. Any symbols which
;; are written into these sexps must be prepended by the cl-user
;; namespace; otherwise the symbols in the generated code will be
;; prepended by this package's namespace.
(defun generate (root-path openapi client-name)
"Generates a client and a list of its methods. These are returned in
a 2-tuple.
openapi: the sexp representation of a openapi document.
client-name: the name of the class that hosts all the client methods."
(check-type client-name symbol)
(let* ((host (gethash "host" openapi))
(base-path (gethash "basePath" openapi))
(consumes-media-types (gethash "consumes" openapi))
(produces-media-types (gethash "produces" openapi))
(schemes (or (gethash "schemes" openapi)
'("https")))
(security-schemas (security-schemas-from-hash-table
(resolve-object root-path (gethash "securityDefinitions" openapi))))
(client (generate-client client-name schemes host base-path
consumes-media-types produces-media-types
security-schemas)))
(values
client
(loop for path-name being the hash-keys of (gethash "paths" openapi)
using (hash-value path-operation)
nconc (generate-path-methods client-name path-name path-operation
consumes-media-types security-schemas)))))
(defun with-yaml-generate (root-path openapi-yaml client-name)
"Generate a client and a list of its methods based on a YAML file.
root-path: The root path of the Yaml file.
openapi-yaml: a string containing the YAML representation of a openapi document, or a pathname to a YAML document.
client-name: the name of the class that hosts all the client methods."
(check-type root-path (or string pathname))
(check-type openapi-yaml (or string pathname))
(check-type client-name symbol)
(generate root-path (yaml:parse openapi-yaml) client-name))
(defun with-json-generate (root-path openapi-json client-name)
"Generate a client and a list of its methods based on a YAML file.
root-path: The root path of the Json file.
openapi-json: a string containing the JSON representation of a openapi document, or a pathname to a JSON document.
client-name: the name of the class that hosts all the client methods."
(check-type root-path (or string pathname))
(check-type openapi-json (or string pathname))
(check-type client-name symbol)
(generate root-path (yason:parse openapi-json) client-name))
(defun with-directory-generate (path process-results-fn)
"Given a pathname, process the YAML and JSON files within, and
call `process-results-fn'. This function should take these
arguments: (1) The file-path (2) the client definition (3) the list of
methods for this client."
(check-type process-results-fn function)
(flet ((with-file-generate (file-path)
(let* ((type (pathname-type file-path))
(name (kebab-symbol-from-string (pathname-name file-path)))
(output-path (make-pathname :type "lisp" :defaults file-path)))
(multiple-value-bind (client-def methods-list)
(cond ((string= type "yaml")
(with-yaml-generate (uiop:pathname-directory-pathname file-path) file-path name))
((string= type "json")
(with-json-generate (uiop:pathname-directory-pathname file-path) file-path name)))
(funcall process-results-fn output-path client-def methods-list)))))
(mapcar #'with-file-generate (directory path))))
(defun with-directory-generate-files (input-path output-path package-root &optional preamble)
"Given a pathname, generate lisp files for every specification
encountered and processed.
package-root will be used as the root of each file's package. The
file's name will be appended to this forming the fully qualified
package name."
(check-type package-root symbol)
(flet ((write-defs-out (file-path client-def methods-list)
(let ((file-path (make-pathname :defaults file-path
:directory output-path))
(*package* (find-package :cl-user)))
(with-open-file (stream file-path :direction :output :if-exists :supersede)
(format t "Output path: ~a~%" file-path)
(when preamble (format stream "~a~%~%" preamble))
(when package-root
(dolist (pkg-clause (generate-package-clauses
(intern (string-upcase (format nil "~a/~a" package-root (pathname-name file-path))))
:packages-using '(#:cl)
:packages-import '(#:cl-strings)))
(format stream "~s~%" pkg-clause))
(format stream "~%~%"))
(format stream "~s~%" client-def)
(dolist (m methods-list) (format stream "~%~%~s~%" m))))))
(with-directory-generate input-path #'write-defs-out)))
;;; Unexported
(defun resolve-ref (ref)
"Returns the referenced object"
(check-type ref pathname)
(let ((type (pathname-type ref)))
(when (equalp type "yaml")
(return-from resolve-ref (yaml:parse ref)))
(when (equalp type "json")
(return-from resolve-ref (yason:parse ref)))
(error "cannot resolve references of type ~a" type)))
(defun resolve-object (root-path object)
"If OBJECT is a reference, then return the referenced object.
Otherwise, just return the object."
(check-type root-path (or pathname string))
(check-type object (or hash-table null))
(unless object (return-from resolve-object nil))
(alexandria:if-let (val (gethash "$ref" object))
(resolve-ref (merge-pathnames val root-path))
object))
(defun parameter-name (param)
(check-type param hash-table)
(gethash "name" param))
(defun kebab-symbol-from-string (s &optional package)
(check-type s string)
(if package
(intern (string-upcase (kebab:to-lisp-case s)) package)
(intern (string-upcase (kebab:to-lisp-case s)))))
(defun lisp-parameter-name (param)
"Converts a string parameter name to a symbol for use in generated
code."
(check-type param hash-table)
(kebab-symbol-from-string (parameter-name param)))
(defun parameter-location (param)
(check-type param hash-table)
(gethash "in" param))
(defun parameter-type (param)
(check-type param hash-table)
(gethash "type" param))
(defun parameter-required-p (param)
(check-type param hash-table)
(or (gethash "required" param)
(parameter-location-schema-p param)))
(defun parameter-location-schema-p (param)
(check-type param hash-table)
(gethash "schema" param))
(defun parameter-location-query-p (param)
(check-type param hash-table)
(string= (string-downcase (parameter-location param)) "query"))
(defun parameter-location-header-p (param)
(check-type param hash-table)
(string= (string-downcase (parameter-location param)) "header"))
(defun parameter-location-path-p (param)
(check-type param hash-table)
(string= (string-downcase (parameter-location param)) "path"))
(defun parameter-location-body-p (param)
(check-type param hash-table)
(string= (string-downcase (parameter-location param)) "body"))
(defun parameter-location-form-p (param)
(check-type param hash-table)
(string= (string-downcase (parameter-location param)) "formdata"))
(defun openapi-schemes-p (schemes)
(and (listp schemes)
(= (length schemes)
;; TODO(katco): ws and wss are also valid, but we don't yet handle these.
(length (intersection schemes '("https" "http") :test #'string=)))))
(defun select-scheme (schemes)
"Pick a scheme from a list of options in order of preference."
(check-type schemes list)
(or (find "https" schemes :test #'string=)
(find "http" schemes :test #'string=)))
(defun media-type-form-p (media-type)
(and media-type
(find media-type '("application/x-www-form-urlencoded"
"multipart/form-data")
:test #'string=)))
(defun media-type-subtype (media-type)
(check-type media-type string)
(subseq media-type (+ 1 (or (position #\+ media-type)
(position #\/ media-type)))))
(defun select-media-type (media-types)
(check-type media-types list)
(let ((media-subtypes (mapcar #'media-type-subtype media-types)))
(or (find-if (lambda (e) (or (string= e "json") (string= e "yaml"))) media-subtypes)
(nth (random (length media-subtypes)) media-subtypes))))
(defun generate-package-clauses (package-name &key packages-using packages-import)
(check-type package-name symbol)
(check-type packages-using list)
(check-type packages-import list)
`((in-package :cl-user)
(defpackage ,package-name
,@(when packages-using `((:use ,@packages-using)))
,@(when packages-import
(loop for pkg in packages-import
collect `(:import-from ,pkg))))
(in-package ,package-name)))
(defun generate-path-methods (client-name path-name path global-media-types security-schemas)
(check-type client-name symbol)
(check-type path-name string)
(check-type path hash-table)
(check-type global-media-types list)
(check-type security-schemas list)
(loop for operation-name being the hash-keys of path
using (hash-value operation)
for summary = (gethash "summary" operation)
for description = (gethash "description" operation)
for operation-id = (gethash "operationId" operation)
for produces-media-types = (gethash "produces" operation)
for consumes-media-types = (gethash "consumes" operation)
for responses = (gethash "responses" operation)
for schemes = (gethash "schemes" operation)
for parameters = (gethash "parameters" operation)
for security-requirement = (gethash "security" operation)
;; Synthesized fields
for body-param = (find-if #'parameter-location-schema-p parameters)
for method-name = (if operation-id operation-id (format nil "~a-~a" operation-name path-name))
for required-parameters = (remove body-param (remove-if-not #'parameter-required-p parameters))
for optional-parameters = (remove-if #'parameter-required-p parameters)
;; Enumerate through the known valid operations that we know
;; how to handle. Ignore all else.
unless (find operation-name '("get" "put" "post" "delete"))
do (when body-param
(multiple-value-bind (required-body-params optional-body-params)
(generate-schema-object-parameters (gethash "schema" body-param))
(setf required-parameters (append required-parameters required-body-params)
optional-parameters (append optional-parameters optional-body-params))))
collect (let* ((param-descriptions (generate-parameter-comments (append required-parameters
optional-parameters)))
(method-comment (concatenate 'string
(when summary (format nil "~a~%~%" summary))
(when description (format nil "~a~%~%" description))
(when param-descriptions param-descriptions))))
;; Generate a method for each operation
(generate-operation-method client-name method-name method-comment schemes path-name
operation-name global-media-types consumes-media-types
produces-media-types required-parameters optional-parameters
(select-security-requirement security-schemas
security-requirement)))))
(defun select-security-requirement (security-schemas security-requirements)
"Selects a security requirement from the list of options in an
opinionated way. This algorithm prefers API Keys"
(check-type security-schemas list)
(check-type security-requirements list)
(when security-requirements
(car security-schemas)))
(defun generate-client (client-name schemes host base-path consumes-media-types
produces-media-types security-schemas)
"Generates a client that all the methods will hang off of."
(check-type client-name symbol)
(assert (openapi-schemes-p schemes))
(check-type host string)
(check-type base-path string)
(check-type consumes-media-types list)
(check-type produces-media-types list)
(check-type security-schemas list)
`(defclass ,client-name ()
((cl-user::scheme
:type string
:documentation
"The scheme to use for requests when the operation doesn't
provide a preference of its own."
:initarg :schemes
:initform ,(select-scheme schemes)
:accessor cl-user::scheme)
(cl-user::host
:type string
:documentation
"The host all requests for this client will be sent to."
:initarg :host
:initform ,host
:accessor cl-user::host)
(cl-user::base-path
:type string
:documentation
"The base path that will be prepended to the path of all requests."
:initarg :base-path
:initform ,base-path
:accessor cl-user::base-path)
(cl-user::consumes-media-type
:type string
:documentation
"The media-type to encode parameters to when operations do not declare a media-type."
:initarg :consumes-media-type
,@(when consumes-media-types `(:initform ,(select-media-type consumes-media-types)))
:accessor cl-user::consumes-media-type)
(cl-user::produces-media-type
:type string
:documentation
"The media-type to dencode results from when operations do not declare a media-type."
:initarg :produces-media-type
,@(when produces-media-types `(:initform ,(select-media-type produces-media-types)))
:accessor cl-user::produces-media-type)
(cl-user::http-request
:type function
:documentation
"A function for making HTTP requests. It needs to have the following signature: (lambda (uri &key method additional-headers content-type content parameters multipart-params))"
:initarg :http-request
:accessor cl-user::http-request)
(cl-user::encoder-from-media-type
:type hash-table
:documentation
"A hash table where the keys are media types the client can handle, and the values are functions which encode these media types to strings for inclusion into the http request."
:initarg :encoder-from-media-type
:initform (make-hash-table :test #'equalp)
:accessor cl-user::encoder-from-media-type)
,@(loop for (name . schema) in security-schemas
for lisp-name = (kebab-symbol-from-string (name schema))
when (eq (security-type schema) 'api-key)
collect `(,lisp-name
:type string
:documentation
,(description schema)
:accessor ,lisp-name)))))
(defun generate-operation-method (client-name method-name method-comment schemes path
method global-media-types consumes-media-types produces-media-types
required-parameters optional-parameters security-requirement)
"Generates a method for a generated client. Methods distinguish
between required parameters and optional parameters, and optional
parameters are defined as &key arguments. All arguments are run
through `check-type' to both ensure the correct type is used and that
the provided values meet any defined constraints."
(check-type client-name symbol)
(check-type method-name string)
(check-type method-comment string)
(when schemes (assert (openapi-schemes-p schemes)))
(check-type path string)
(check-type method string)
(check-type global-media-types list)
(check-type consumes-media-types list)
(check-type produces-media-types list)
(check-type required-parameters list)
(check-type optional-parameters list)
(check-type security-requirement list)
(let ((lisp-required-parameters (mapcar #'lisp-parameter-name required-parameters))
(lisp-optional-parameters (mapcar #'lisp-parameter-name optional-parameters)))
;; Generated method begins here
`(defmethod ,(kebab-symbol-from-string method-name) ((cl-user::client ,client-name)
,@lisp-required-parameters
,@(when lisp-optional-parameters
`(&key ,@lisp-optional-parameters)))
,method-comment
,@(generate-check-type (append required-parameters optional-parameters))
,(let ((path-params (append
(remove-if-not #'parameter-location-path-p required-parameters)
(remove-if-not #'parameter-location-path-p optional-parameters)))
(query-params (append
(remove-if-not #'parameter-location-query-p required-parameters)
(remove-if-not #'parameter-location-query-p optional-parameters)))
(headers (append
(remove-if-not #'parameter-location-header-p required-parameters)
(remove-if-not #'parameter-location-header-p optional-parameters)))
(body-params (append
(remove-if-not #'parameter-location-body-p required-parameters)
(remove-if-not #'parameter-location-body-p optional-parameters)))
(form-params (append
(remove-if-not #'parameter-location-form-p required-parameters)
(remove-if-not #'parameter-location-form-p optional-parameters)))
(consumes-media-type (when consumes-media-types (select-media-type consumes-media-types))))
;; If the content-type requests form params, put all the
;; body parameters in the form params list. Otherwise, move
;; all the form parameters into the body list so that they
;; can be encoded properly.
(if (media-type-form-p consumes-media-type)
(setf form-params (append form-params body-params)
body-params nil)
(setf body-params (append body-params form-params)
form-params nil))
`(let (,@(when path-params '((cl-user::path-params (list))))
,@(when (or query-params
(and security-requirement
(eq (security-type (cdr security-requirement)) 'api-key)))
'((cl-user::query-params (list))))
,@(when headers '((cl-user::headers (list))))
,@(when body-params '((cl-user::body-params (list))))
,@(when (and (media-type-form-p consumes-media-type)
form-params)
'((cl-user::form-params (list))))
;; If we can't interpolate the scheme into the
;; request URI's string, grab it from the client.
,@(unless schemes `((cl-user::scheme (cl-user::scheme cl-user::client))))
(cl-user::consumes-media-type ,(if consumes-media-type
consumes-media-type
'(cl-user::consumes-media-type cl-user::client)))
;; If the operation's declared content-type is form
;; we don't need a hash-table to populate the body.
;; If the operation doesn't have a declared
;; content-type, we can't make the decision at
;; gen-time and we must reflect on what's defined in
;; the client.
,@(when (and body-params
(or (not consumes-media-type)
(media-type-form-p consumes-media-type)))
'((cl-user::req-body (make-hash-table)))))
;; Build up alist of query params
,@(generate-http-request-population path-params 'cl-user::path-params)
,@(generate-http-request-population query-params 'cl-user::query-params)
,(when (and security-requirement
(eq (security-type (cdr security-requirement)) 'api-key))
(let ((accessor (kebab-symbol-from-string (car security-requirement)))
(requirement (cdr security-requirement)))
`(setf cl-user::query-params (push (cons ,(name requirement)
(,accessor cl-user::client))
cl-user::query-params))))
,@(generate-http-request-population headers 'cl-user::headers)
,@(if (media-type-form-p consumes-media-type)
(generate-http-request-population form-params 'cl-user::form-params)
(generate-http-request-body-population body-params 'cl-user::req-body))
;; Make the request
(flet ((cl-user::replace-path-params (cl-user::uri cl-user::path-vars)
(funcall (cl-strings:make-template-parser "{" "}") cl-user::uri cl-user::path-vars)))
(funcall (cl-user::http-request cl-user::client)
(format nil ,(concatenate 'string
(if schemes (select-scheme schemes) "~a:")
"//~a~a~a")
cl-user::scheme
(cl-user::host cl-user::client)
(cl-user::base-path cl-user::client)
,(if path-params
`(cl-user::replace-path-params ,path cl-user::path-params)
path))
:method ,(intern (string-upcase method) "KEYWORD")
:content-type cl-user::consumes-media-type
,@(when headers `(:additional-headers cl-user::headers))
,@(when body-params
`(:content (funcall (gethash cl-user::consumes-media-type
(cl-user::encoder-from-media-type cl-user::client))
cl-user::req-body)))
,@(when query-params `(:parameters cl-user::query-params))
,@(when form-params `(:multipart-params cl-user::form-params)))))))))
(defun generate-check-type (parameters)
(check-type parameters list)
(loop for param in parameters
for param-name = (lisp-parameter-name param)
for param-type = (parameter-type param)
for lisp-param-type = (when param-type (kebab-symbol-from-string param-type))
;; Type must both be present and correspond to a check
;; function in order to perform type checking.
when (find lisp-param-type '(string number integer array)
:test #'string=)
collect (if (parameter-required-p param)
`(check-type ,param-name ,lisp-param-type)
`(when ,param-name (check-type ,param-name ,lisp-param-type)))))
(defun generate-http-request-body-population (params req-body-name)
(check-type params list)
(loop for param in params
for param-name = (parameter-name param)
for lisp-param-name = (lisp-parameter-name param)
for set-value-sexp = `(setf (gethash ,param-name ,req-body-name) ,lisp-param-name)
collect
(if (parameter-required-p param) set-value-sexp `(when ,lisp-param-name ,set-value-sexp))))
(defun generate-http-request-population (params request-alist)
"Generates code to populate an alist intended to be passed into an
http-request."
(check-type params list)
(check-type request-alist symbol)
(loop for param in params
for param-name = (parameter-name param)
for lisp-param-name = (lisp-parameter-name param)
collect
(if (parameter-required-p param)
`(setf ,request-alist (push (cons ,param-name ,lisp-param-name) ,request-alist))
`(when ,lisp-param-name (setf ,request-alist (push (cons ,param-name ,lisp-param-name) ,request-alist))))))
(defun generate-parameter-comments (parameters)
"Generates a single string describing a list of parameters for
inclusion in a method's docstring."
(check-type parameters list)
(with-output-to-string (parameter-desc)
(loop for param in parameters
for lisp-param-name = (lisp-parameter-name param)
for param-desc = (gethash "description" param)
when param-desc
do (format parameter-desc "~a: ~a~%" lisp-param-name param-desc))))
(defun generate-schema-object-parameters (schema-object)
"Generates hash-tables which look like openapi parameters from
schema object properties. This is so that we can pass these into
generation code that synthesizes CL method parameters from openapi
parameters."
;; TODO(katco): Create types since properties and parameters have similarities
(check-type schema-object hash-table)
(let ((schema-properties (gethash "properties" schema-object))
(required-parameters (list))
(optional-parameters (list))
(declared-as-required (gethash "required" schema-object)))
(when schema-properties
(loop for prop-name being the hash-keys of schema-properties
using (hash-value prop)
for prop-type = (parameter-type prop)
for prop-desc = (gethash "description" prop)
for faux-param = (make-hash-table :test #'equalp)
do (setf (gethash "required" faux-param) (find prop-name declared-as-required :test #'string=)
(gethash "name" faux-param) prop-name
(gethash "type" faux-param) prop-type
(gethash "in" faux-param) "body"
(gethash "description" faux-param) prop-desc)
(if (find prop-name declared-as-required :test #'string=)
(setf required-parameters (push faux-param required-parameters))
(setf optional-parameters (push faux-param optional-parameters)))))
(values required-parameters optional-parameters)))
;;; Security Scheme Objects
(deftype variable-location () '(member query header path body form-data))
(defclass schema-variable ()
((name
:type string
:documentation
"The name of the header or query parameter to be used."
:initarg :name
:reader name)
(in
:type variable-location
:documentation
"Where in the HTTP request the variable is placed. Valid values
are '(query header path body form-data)"
:initarg :in
:reader in)))
(deftype flow () '(member implicit password application access-code))
(deftype security-type () '(member basic api-key oauth2))
(defun security-schemas-from-hash-table (raw)
"Collects security schemas into an alist of (name . instance)."
(check-type raw (or hash-table null))
(unless raw (return-from security-schemas-from-hash-table (list)))
(loop for schema-name being the hash-keys of raw
for schema being the hash-values of raw
collect (cons
schema-name
(make-instance 'security-schema
:name (gethash "name" schema)
:in (kebab-symbol-from-string (gethash "in" schema) :openapi2cl/core)
:type (kebab-symbol-from-string (gethash "type" schema) :openapi2cl/core)
:description (gethash "description" schema)
:flow (alexandria:when-let (flow (gethash "flow" schema)) (intern flow))
:authorization-url (gethash "authorizationUrl" schema)
:token-url (gethash "tokenUrl" schema)
:scopes (gethash "scopes" schema)))))
(defclass security-schema (schema-variable)
((type
:type security-type
:documentation
"The type of the security scheme."
:initarg :type
:initform (error "type not specified")
:reader security-type)
(description
:type (or string null)
:documentation
"A short description for security scheme."
:initarg :description
:reader description)
(flow
:type (or flow null)
:documentation
"The flow used by the OAuth2 security scheme."
:initarg :flow)
(authorization-url
:type (or string null)
:documentation
"The authorization URL to be used for this flow. This SHOULD be in
the form of a URL."
:initarg :authorization-url)
(token-url
:type (or string null)
:documentation
"The token URL to be used for this flow. This SHOULD be in the
form of a URL."
:initarg :token-url)
(scopes
:documentation
"The available scopes for the OAuth2 security scheme."
:initarg :scopes))
(:documentation
"A security schema that an OpenAPI endpoint accepts."))