serverless-openapi-documenter 0.0.53 → 0.0.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/package.json +1 -1
- package/src/definitionGenerator.js +42 -78
- package/src/schemaHandler.js +189 -0
- package/test/models/models/models-alt.json +17 -0
- package/test/models/models/models.json +20 -0
- package/test/models/models/modelsList-alt.json +17 -0
- package/test/models/models/modelsList.json +20 -0
- package/test/unit/definitionGenerator.spec.js +25 -502
- package/test/unit/schemaHandler.spec.js +700 -0
package/README.md
CHANGED
|
@@ -389,6 +389,23 @@ custom:
|
|
|
389
389
|
|
|
390
390
|
`&ErrorItem` in the above example creates a node anchor (&ErrorItem) to the `ErrorResponse` schema which then can be used in the `PutDocumentResponse` schema via the reference (*ErrorItem). The node anchor needs to be declared first before it can be used elsewhere via the reference, swapping the above example around would result in an error.
|
|
391
391
|
|
|
392
|
+
##### ModelsList - Backwards compatibility
|
|
393
|
+
|
|
394
|
+
It was brought to my attention that an older plugin version allowed the use of `modelsList`. As of 0.0.60, you can continue to use `modelsList` as well as using `models`, however `modelsList` now has to be nested within the `documentation` section. You can write `modelsList` the same way as any of the two styles for [Models](#Models).
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
custom:
|
|
398
|
+
documentation:
|
|
399
|
+
...
|
|
400
|
+
modelsList:
|
|
401
|
+
- name: "ErrorResponse"
|
|
402
|
+
description: "This is an error"
|
|
403
|
+
content:
|
|
404
|
+
application/json:
|
|
405
|
+
schema:
|
|
406
|
+
type: string
|
|
407
|
+
```
|
|
408
|
+
|
|
392
409
|
|
|
393
410
|
#### Functions
|
|
394
411
|
|
|
@@ -605,6 +622,46 @@ functions:
|
|
|
605
622
|
- {}
|
|
606
623
|
```
|
|
607
624
|
|
|
625
|
+
##### private
|
|
626
|
+
|
|
627
|
+
If you use the [private](https://www.serverless.com/framework/docs/providers/aws/events/apigateway#setting-api-keys-for-your-rest-api) property on your event:
|
|
628
|
+
|
|
629
|
+
```yml
|
|
630
|
+
functions:
|
|
631
|
+
getData:
|
|
632
|
+
events:
|
|
633
|
+
- http:
|
|
634
|
+
path: /
|
|
635
|
+
method: get
|
|
636
|
+
private: true
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
It will automatically setup an apiKey security scheme of `x-api-key` attached to that method. You don't need to add this to the [Security Scheme](#securityschemes) in the main documentation. If you have already added a Security Scheme of an `apiKey` with a name of `x-api-key`, it will associate with that key.
|
|
640
|
+
|
|
641
|
+
```yml
|
|
642
|
+
custom:
|
|
643
|
+
documentation:
|
|
644
|
+
securitySchemes:
|
|
645
|
+
my_api_key:
|
|
646
|
+
type: apiKey
|
|
647
|
+
name: x-api-key
|
|
648
|
+
in: header
|
|
649
|
+
security:
|
|
650
|
+
- my_api_key: []
|
|
651
|
+
|
|
652
|
+
functions:
|
|
653
|
+
getData:
|
|
654
|
+
events:
|
|
655
|
+
- http:
|
|
656
|
+
path: /
|
|
657
|
+
method: get
|
|
658
|
+
private: true
|
|
659
|
+
documentation:
|
|
660
|
+
...
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
Will set the Security Scheme to `my_api_key` for that operation.
|
|
664
|
+
|
|
608
665
|
#### `requestModels`
|
|
609
666
|
|
|
610
667
|
The `requestModels` property allows you to define models for the HTTP Request of the function event. You can define a different model for each different `Content-Type`. You can define a reference to the relevant request model named in the `models` section of your configuration (see [Defining Models](#models) section).
|
package/package.json
CHANGED
|
@@ -4,9 +4,8 @@ const path = require('path')
|
|
|
4
4
|
|
|
5
5
|
const { v4: uuid } = require('uuid')
|
|
6
6
|
const validator = require('oas-validator');
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const isEqual = require('lodash.isequal')
|
|
7
|
+
|
|
8
|
+
const SchemaHandler = require('./schemaHandler')
|
|
10
9
|
|
|
11
10
|
class DefinitionGenerator {
|
|
12
11
|
constructor(serverless, options = {}) {
|
|
@@ -25,8 +24,13 @@ class DefinitionGenerator {
|
|
|
25
24
|
|
|
26
25
|
this.openAPI = {
|
|
27
26
|
openapi: this.version,
|
|
27
|
+
components: {
|
|
28
|
+
schemas: {}
|
|
29
|
+
}
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
this.schemaHandler = new SchemaHandler(serverless, this.openAPI)
|
|
33
|
+
|
|
30
34
|
this.operationIds = []
|
|
31
35
|
this.schemaIDs = []
|
|
32
36
|
|
|
@@ -64,6 +68,11 @@ class DefinitionGenerator {
|
|
|
64
68
|
async parse() {
|
|
65
69
|
this.createInfo()
|
|
66
70
|
|
|
71
|
+
await this.schemaHandler.addModelsToOpenAPI()
|
|
72
|
+
.catch(err => {
|
|
73
|
+
throw err
|
|
74
|
+
})
|
|
75
|
+
|
|
67
76
|
if (this.serverless.service.custom.documentation.securitySchemes) {
|
|
68
77
|
this.createSecuritySchemes(this.serverless.service.custom.documentation.securitySchemes)
|
|
69
78
|
|
|
@@ -303,6 +312,29 @@ class DefinitionGenerator {
|
|
|
303
312
|
obj.security = documentation.security
|
|
304
313
|
}
|
|
305
314
|
|
|
315
|
+
if (this.currentEvent?.private && this.currentEvent.private === true) {
|
|
316
|
+
let apiKeyName = 'x-api-key'
|
|
317
|
+
let hasXAPIKey = false
|
|
318
|
+
if (this.openAPI?.components?.[this.componentTypes.securitySchemes]) {
|
|
319
|
+
for (const [schemeName, schemeValue] of Object.entries(this.openAPI.components[this.componentTypes.securitySchemes])) {
|
|
320
|
+
if (schemeValue.type === 'apiKey' && schemeValue.name === 'x-api-key') {
|
|
321
|
+
apiKeyName = schemeName
|
|
322
|
+
hasXAPIKey = true
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (hasXAPIKey === false) {
|
|
328
|
+
this.createSecuritySchemes({[apiKeyName]: {type: 'apiKey', name: apiKeyName, in: 'header'}})
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (obj.security) {
|
|
332
|
+
obj.security.push({[apiKeyName]: []})
|
|
333
|
+
} else {
|
|
334
|
+
obj.security = [{[apiKeyName]: []}]
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
306
338
|
if (Object.keys(documentation).includes('deprecated'))
|
|
307
339
|
obj.deprecated = documentation.deprecated
|
|
308
340
|
|
|
@@ -360,7 +392,8 @@ class DefinitionGenerator {
|
|
|
360
392
|
}
|
|
361
393
|
}
|
|
362
394
|
} else {
|
|
363
|
-
|
|
395
|
+
if (Object.keys(corsHeaders).length)
|
|
396
|
+
obj.headers = corsHeaders
|
|
364
397
|
}
|
|
365
398
|
|
|
366
399
|
Object.assign(responses, { [response.statusCode]: obj })
|
|
@@ -414,7 +447,7 @@ class DefinitionGenerator {
|
|
|
414
447
|
newHeader.description = headers[header].description || ''
|
|
415
448
|
|
|
416
449
|
if (headers[header].schema) {
|
|
417
|
-
const schemaRef = await this.
|
|
450
|
+
const schemaRef = await this.schemaHandler.createSchema(header, headers[header].schema)
|
|
418
451
|
.catch(err => {
|
|
419
452
|
throw err
|
|
420
453
|
})
|
|
@@ -445,7 +478,7 @@ class DefinitionGenerator {
|
|
|
445
478
|
|
|
446
479
|
async createMediaTypeObject(models, type) {
|
|
447
480
|
const mediaTypeObj = {}
|
|
448
|
-
for (const mediaTypeDocumentation of this.
|
|
481
|
+
for (const mediaTypeDocumentation of this.schemaHandler.models) {
|
|
449
482
|
if (models === undefined || models === null) {
|
|
450
483
|
throw new Error(`${this.currentFunctionName} is missing a Response Model for statusCode ${this.currentStatusCode}`)
|
|
451
484
|
}
|
|
@@ -477,10 +510,11 @@ class DefinitionGenerator {
|
|
|
477
510
|
schema = mediaTypeDocumentation.schema
|
|
478
511
|
}
|
|
479
512
|
|
|
480
|
-
const schemaRef = await this.
|
|
513
|
+
const schemaRef = await this.schemaHandler.createSchema(mediaTypeDocumentation.name)
|
|
481
514
|
.catch(err => {
|
|
482
515
|
throw err
|
|
483
516
|
})
|
|
517
|
+
|
|
484
518
|
obj.schema = {
|
|
485
519
|
$ref: schemaRef
|
|
486
520
|
}
|
|
@@ -525,7 +559,7 @@ class DefinitionGenerator {
|
|
|
525
559
|
obj.examples = this.createExamples(param.examples)
|
|
526
560
|
|
|
527
561
|
if (param.schema) {
|
|
528
|
-
const schemaRef = await this.
|
|
562
|
+
const schemaRef = await this.schemaHandler.createSchema(param.name, param.schema)
|
|
529
563
|
.catch(err => {
|
|
530
564
|
throw err
|
|
531
565
|
})
|
|
@@ -539,76 +573,6 @@ class DefinitionGenerator {
|
|
|
539
573
|
return params;
|
|
540
574
|
}
|
|
541
575
|
|
|
542
|
-
async dereferenceSchema(schema) {
|
|
543
|
-
let originalSchema = await $RefParser.bundle(schema, this.refParserOptions)
|
|
544
|
-
.catch(err => {
|
|
545
|
-
console.error(err)
|
|
546
|
-
throw err
|
|
547
|
-
})
|
|
548
|
-
|
|
549
|
-
let deReferencedSchema = await $RefParser.dereference(originalSchema, this.refParserOptions)
|
|
550
|
-
.catch(err => {
|
|
551
|
-
console.error(err)
|
|
552
|
-
throw err
|
|
553
|
-
})
|
|
554
|
-
|
|
555
|
-
// deal with schemas that have been de-referenced poorly: naive
|
|
556
|
-
if (deReferencedSchema?.$ref === '#') {
|
|
557
|
-
const oldRef = originalSchema.$ref
|
|
558
|
-
const path = oldRef.split('/')
|
|
559
|
-
|
|
560
|
-
const pathTitle = path[path.length - 1]
|
|
561
|
-
const referencedProperties = deReferencedSchema.definitions[pathTitle]
|
|
562
|
-
|
|
563
|
-
Object.assign(deReferencedSchema, { ...referencedProperties })
|
|
564
|
-
|
|
565
|
-
delete deReferencedSchema.$ref
|
|
566
|
-
deReferencedSchema = await this.dereferenceSchema(deReferencedSchema)
|
|
567
|
-
.catch((err) => {
|
|
568
|
-
throw err
|
|
569
|
-
})
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
return deReferencedSchema
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
async schemaCreator(schema, name) {
|
|
576
|
-
let schemaName = name
|
|
577
|
-
let finalName = schemaName
|
|
578
|
-
const dereferencedSchema = await this.dereferenceSchema(schema)
|
|
579
|
-
.catch(err => {
|
|
580
|
-
console.error(err)
|
|
581
|
-
throw err
|
|
582
|
-
})
|
|
583
|
-
|
|
584
|
-
const convertedSchemas = SchemaConvertor.convert(dereferencedSchema, schemaName)
|
|
585
|
-
|
|
586
|
-
for (const convertedSchemaName of Object.keys(convertedSchemas.schemas)) {
|
|
587
|
-
const convertedSchema = convertedSchemas.schemas[convertedSchemaName]
|
|
588
|
-
if (this.existsInComponents(convertedSchemaName)) {
|
|
589
|
-
if (this.isTheSameSchema(convertedSchema, convertedSchemaName) === false) {
|
|
590
|
-
if (convertedSchemaName === schemaName) {
|
|
591
|
-
finalName = `${schemaName}-${uuid()}`
|
|
592
|
-
this.addToComponents(this.componentTypes.schemas, convertedSchema, finalName)
|
|
593
|
-
} else
|
|
594
|
-
this.addToComponents(this.componentTypes.schemas, convertedSchema, convertedSchemaName)
|
|
595
|
-
}
|
|
596
|
-
} else {
|
|
597
|
-
this.addToComponents(this.componentTypes.schemas, convertedSchema, convertedSchemaName)
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
return `#/components/schemas/${finalName}`
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
existsInComponents(name) {
|
|
605
|
-
return Boolean(this.openAPI?.components?.schemas?.[name])
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
isTheSameSchema(schema, otherSchemaName) {
|
|
609
|
-
return isEqual(schema, this.openAPI.components.schemas[otherSchemaName])
|
|
610
|
-
}
|
|
611
|
-
|
|
612
576
|
addToComponents(type, schema, name) {
|
|
613
577
|
const schemaObj = {
|
|
614
578
|
[name]: schema
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const $RefParser = require("@apidevtools/json-schema-ref-parser")
|
|
6
|
+
const SchemaConvertor = require('json-schema-for-openapi')
|
|
7
|
+
const isEqual = require('lodash.isequal')
|
|
8
|
+
const { v4: uuid } = require('uuid')
|
|
9
|
+
|
|
10
|
+
class SchemaHandler {
|
|
11
|
+
constructor(serverless, openAPI) {
|
|
12
|
+
this.documentation = serverless.service.custom.documentation
|
|
13
|
+
this.openAPI = openAPI
|
|
14
|
+
|
|
15
|
+
this.modelReferences = {}
|
|
16
|
+
|
|
17
|
+
this.__standardiseModels()
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
this.refParserOptions = require(path.resolve('options', 'ref-parser.js'))
|
|
21
|
+
} catch (err) {
|
|
22
|
+
this.refParserOptions = {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Standardises the models to a specific format
|
|
28
|
+
*/
|
|
29
|
+
__standardiseModels() {
|
|
30
|
+
const standardModel = (model) => {
|
|
31
|
+
if (model.schema) {
|
|
32
|
+
return model
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const contentType = Object.keys(model.content)[0]
|
|
36
|
+
model.contentType = contentType
|
|
37
|
+
model.schema = model.content[contentType].schema
|
|
38
|
+
|
|
39
|
+
return model
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const standardisedModels = this.documentation?.models?.map(standardModel) || []
|
|
43
|
+
const standardisedModelsList = this.documentation?.modelsList?.map(standardModel) || []
|
|
44
|
+
|
|
45
|
+
this.models = standardisedModels.length ? standardisedModels.concat(standardisedModelsList) : standardisedModelsList
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async addModelsToOpenAPI() {
|
|
49
|
+
for (const model of this.models) {
|
|
50
|
+
const modelName = model.name
|
|
51
|
+
const modelSchema = model.schema
|
|
52
|
+
|
|
53
|
+
const dereferencedSchema = await this.__dereferenceSchema(modelSchema)
|
|
54
|
+
.catch(err => {
|
|
55
|
+
if(err.errors) {
|
|
56
|
+
for (const error of err?.errors) {
|
|
57
|
+
if (error.message.includes('HTTP ERROR')) {
|
|
58
|
+
throw err
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return modelSchema
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const convertedSchemas = SchemaConvertor.convert(dereferencedSchema, modelName)
|
|
66
|
+
|
|
67
|
+
for (const [schemaName, schemaValue] of Object.entries(convertedSchemas.schemas)) {
|
|
68
|
+
if (schemaName === modelName) {
|
|
69
|
+
this.modelReferences[schemaName] = `#/components/schemas/${modelName}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.__addToComponents('schemas', schemaValue, schemaName)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async createSchema(name, schema) {
|
|
78
|
+
let originalName = name;
|
|
79
|
+
let finalName = name;
|
|
80
|
+
|
|
81
|
+
if (this.modelReferences[name] && schema === undefined) {
|
|
82
|
+
return this.modelReferences[name]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const dereferencedSchema = await this.__dereferenceSchema(schema)
|
|
86
|
+
.catch(err => {
|
|
87
|
+
throw err
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const convertedSchemas = SchemaConvertor.convert(dereferencedSchema, name)
|
|
91
|
+
|
|
92
|
+
for (const [schemaName, schemaValue] of Object.entries(convertedSchemas.schemas)) {
|
|
93
|
+
if (this.__existsInComponents(schemaName)) {
|
|
94
|
+
if (this.__isTheSameSchema(schemaValue, schemaName) === false) {
|
|
95
|
+
if (schemaName === originalName) {
|
|
96
|
+
finalName = `${schemaName}-${uuid()}`
|
|
97
|
+
this.__addToComponents('schemas', schemaValue, finalName)
|
|
98
|
+
} else {
|
|
99
|
+
this.__addToComponents('schemas', schemaValue, schemaName)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
this.__addToComponents('schemas', schemaValue, schemaName)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return `#/components/schemas/${finalName}`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async __dereferenceSchema(schema) {
|
|
111
|
+
const bundledSchema = await $RefParser.bundle(schema, this.refParserOptions)
|
|
112
|
+
.catch(err => {
|
|
113
|
+
throw err
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
let deReferencedSchema = await $RefParser.dereference(bundledSchema, this.refParserOptions)
|
|
117
|
+
.catch(err => {
|
|
118
|
+
throw err
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// deal with schemas that have been de-referenced poorly: naive
|
|
122
|
+
if (deReferencedSchema?.$ref === '#') {
|
|
123
|
+
const oldRef = bundledSchema.$ref
|
|
124
|
+
const path = oldRef.split('/')
|
|
125
|
+
|
|
126
|
+
const pathTitle = path[path.length - 1]
|
|
127
|
+
const referencedProperties = deReferencedSchema.definitions[pathTitle]
|
|
128
|
+
|
|
129
|
+
Object.assign(deReferencedSchema, { ...referencedProperties })
|
|
130
|
+
|
|
131
|
+
delete deReferencedSchema.$ref
|
|
132
|
+
deReferencedSchema = await this.__dereferenceSchema(deReferencedSchema)
|
|
133
|
+
.catch((err) => {
|
|
134
|
+
throw err
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return deReferencedSchema
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @function existsInComponents
|
|
143
|
+
* @param {string} name - The name of the Schema
|
|
144
|
+
* @returns {boolean} Whether it exists in components already
|
|
145
|
+
*/
|
|
146
|
+
__existsInComponents(name) {
|
|
147
|
+
return Boolean(this.openAPI?.components?.schemas?.[name])
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @function isTheSameSchema
|
|
152
|
+
* @param {object} schema - The schema value
|
|
153
|
+
* @param {string} otherSchemaName - The name of the schema
|
|
154
|
+
* @returns {boolean} Whether the schema provided is the same one as in components already
|
|
155
|
+
*/
|
|
156
|
+
__isTheSameSchema(schema, otherSchemaName) {
|
|
157
|
+
return isEqual(schema, this.openAPI.components.schemas[otherSchemaName])
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @function addToComponents
|
|
162
|
+
* @param {string} type - The component type
|
|
163
|
+
* @param {object} schema - The schema
|
|
164
|
+
* @param {string} name - The name of the schema
|
|
165
|
+
*/
|
|
166
|
+
__addToComponents(type, schema, name) {
|
|
167
|
+
const schemaObj = {
|
|
168
|
+
[name]: schema
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (this.openAPI?.components) {
|
|
172
|
+
if (this.openAPI.components[type]) {
|
|
173
|
+
Object.assign(this.openAPI.components[type], schemaObj)
|
|
174
|
+
} else {
|
|
175
|
+
Object.assign(this.openAPI.components, { [type]: schemaObj })
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
const components = {
|
|
179
|
+
components: {
|
|
180
|
+
[type]: schemaObj
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
Object.assign(this.openAPI, components)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = SchemaHandler;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"models": [
|
|
3
|
+
{
|
|
4
|
+
"name": "ErrorResponse",
|
|
5
|
+
"description": "The Error Response",
|
|
6
|
+
"content": {
|
|
7
|
+
"application/json": {
|
|
8
|
+
"schema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"error": {
|
|
12
|
+
"type": "string"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"modelsList": [
|
|
3
|
+
{
|
|
4
|
+
"name": "ErrorResponse",
|
|
5
|
+
"description": "The Error Response",
|
|
6
|
+
"content": {
|
|
7
|
+
"application/json": {
|
|
8
|
+
"schema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"error": {
|
|
12
|
+
"type": "string"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|