Skip to content

Commit fb1beb5

Browse files
committed
improve error output when converting schemas
1 parent a204926 commit fb1beb5

File tree

1 file changed

+203
-170
lines changed

1 file changed

+203
-170
lines changed

src/schemaHandler.js

Lines changed: 203 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,195 +1,228 @@
1-
'use strict'
1+
"use strict";
22

3-
const path = require('path')
3+
const path = require("path");
44

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')
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");
99

1010
class SchemaHandler {
11-
constructor(serverless, openAPI) {
12-
this.apiGatewayModels = serverless.service?.provider?.apiGateway?.request?.schemas || {}
13-
this.documentation = serverless.service.custom.documentation
14-
this.openAPI = openAPI
11+
constructor(serverless, openAPI) {
12+
this.apiGatewayModels =
13+
serverless.service?.provider?.apiGateway?.request?.schemas || {};
14+
this.documentation = serverless.service.custom.documentation;
15+
this.openAPI = openAPI;
1516

16-
this.modelReferences = {}
17+
this.modelReferences = {};
1718

18-
this.__standardiseModels()
19+
this.__standardiseModels();
1920

20-
try {
21-
this.refParserOptions = require(path.resolve('options', 'ref-parser.js'))
22-
} catch (err) {
23-
this.refParserOptions = {}
24-
}
21+
try {
22+
this.refParserOptions = require(path.resolve("options", "ref-parser.js"));
23+
} catch (err) {
24+
this.refParserOptions = {};
2525
}
26-
27-
/**
28-
* Standardises the models to a specific format
29-
*/
30-
__standardiseModels() {
31-
const standardModel = (model) => {
32-
if (model.schema) {
33-
return model
26+
}
27+
28+
/**
29+
* Standardises the models to a specific format
30+
*/
31+
__standardiseModels() {
32+
const standardModel = (model) => {
33+
if (model.schema) {
34+
return model;
35+
}
36+
37+
const contentType = Object.keys(model.content)[0];
38+
model.contentType = contentType;
39+
model.schema = model.content[contentType].schema;
40+
41+
return model;
42+
};
43+
44+
const standardisedModels =
45+
this.documentation?.models?.map(standardModel) || [];
46+
const standardisedModelsList =
47+
this.documentation?.modelsList?.map(standardModel) || [];
48+
49+
const standardisedGatewayModels =
50+
Object.keys(this.apiGatewayModels).flatMap((key) => {
51+
const gatewayModel = this.apiGatewayModels[key];
52+
return standardModel(gatewayModel);
53+
}) || [];
54+
55+
this.models = standardisedModels.concat(
56+
standardisedModelsList,
57+
standardisedGatewayModels
58+
);
59+
}
60+
61+
async addModelsToOpenAPI() {
62+
for (const model of this.models) {
63+
const modelName = model.name;
64+
const modelSchema = model.schema;
65+
66+
const dereferencedSchema = await this.__dereferenceSchema(
67+
modelSchema
68+
).catch((err) => {
69+
if (err.errors) {
70+
for (const error of err?.errors) {
71+
if (error.message.includes("HTTP ERROR")) {
72+
throw err;
3473
}
35-
36-
const contentType = Object.keys(model.content)[0]
37-
model.contentType = contentType
38-
model.schema = model.content[contentType].schema
39-
40-
return model
74+
}
4175
}
42-
43-
const standardisedModels = this.documentation?.models?.map(standardModel) || []
44-
const standardisedModelsList = this.documentation?.modelsList?.map(standardModel) || []
45-
46-
const standardisedGatewayModels = Object.keys(this.apiGatewayModels).flatMap(key => {
47-
const gatewayModel = this.apiGatewayModels[key]
48-
return standardModel(gatewayModel)
49-
}) || []
50-
51-
this.models = standardisedModels.concat(standardisedModelsList, standardisedGatewayModels)
52-
}
53-
54-
async addModelsToOpenAPI() {
55-
for (const model of this.models) {
56-
const modelName = model.name
57-
const modelSchema = model.schema
58-
59-
const dereferencedSchema = await this.__dereferenceSchema(modelSchema)
60-
.catch(err => {
61-
if(err.errors) {
62-
for (const error of err?.errors) {
63-
if (error.message.includes('HTTP ERROR')) {
64-
throw err
65-
}
66-
}
67-
}
68-
return modelSchema
69-
})
70-
71-
const convertedSchemas = SchemaConvertor.convert(dereferencedSchema, modelName)
72-
73-
for (const [schemaName, schemaValue] of Object.entries(convertedSchemas.schemas)) {
74-
if (schemaName === modelName) {
75-
this.modelReferences[schemaName] = `#/components/schemas/${modelName}`
76-
}
77-
78-
this.__addToComponents('schemas', schemaValue, schemaName)
79-
}
76+
return modelSchema;
77+
});
78+
79+
const convertedSchemas = SchemaConvertor.convert(
80+
dereferencedSchema,
81+
modelName
82+
);
83+
84+
if (
85+
typeof convertedSchemas.schemas === "object" &&
86+
!Array.isArray(convertedSchemas.schemas) &&
87+
convertedSchemas.schemas !== null
88+
) {
89+
for (const [schemaName, schemaValue] of Object.entries(
90+
convertedSchemas.schemas
91+
)) {
92+
if (schemaName === modelName) {
93+
this.modelReferences[
94+
schemaName
95+
] = `#/components/schemas/${modelName}`;
96+
}
97+
98+
this.__addToComponents("schemas", schemaValue, schemaName);
8099
}
100+
} else {
101+
throw new Error(
102+
`There was an error converting the ${
103+
model.name
104+
} schema. Model received looks like: \n\n${JSON.stringify(model)}`
105+
);
106+
}
81107
}
108+
}
82109

83-
async createSchema(name, schema) {
84-
let originalName = name;
85-
let finalName = name;
86-
87-
if (this.modelReferences[name] && schema === undefined) {
88-
return this.modelReferences[name]
89-
}
90-
91-
const dereferencedSchema = await this.__dereferenceSchema(schema)
92-
.catch(err => {
93-
throw err
94-
})
95-
96-
const convertedSchemas = SchemaConvertor.convert(dereferencedSchema, name)
97-
98-
for (const [schemaName, schemaValue] of Object.entries(convertedSchemas.schemas)) {
99-
if (this.__existsInComponents(schemaName)) {
100-
if (this.__isTheSameSchema(schemaValue, schemaName) === false) {
101-
if (schemaName === originalName) {
102-
finalName = `${schemaName}-${uuid()}`
103-
this.__addToComponents('schemas', schemaValue, finalName)
104-
} else {
105-
this.__addToComponents('schemas', schemaValue, schemaName)
106-
}
107-
}
108-
} else {
109-
this.__addToComponents('schemas', schemaValue, schemaName)
110-
}
111-
}
110+
async createSchema(name, schema) {
111+
let originalName = name;
112+
let finalName = name;
112113

113-
return `#/components/schemas/${finalName}`
114+
if (this.modelReferences[name] && schema === undefined) {
115+
return this.modelReferences[name];
114116
}
115117

116-
async __dereferenceSchema(schema) {
117-
const bundledSchema = await $RefParser.bundle(schema, this.refParserOptions)
118-
.catch(err => {
119-
throw err
120-
})
121-
122-
let deReferencedSchema = await $RefParser.dereference(bundledSchema, this.refParserOptions)
123-
.catch(err => {
124-
throw err
125-
})
126-
127-
// deal with schemas that have been de-referenced poorly: naive
128-
if (deReferencedSchema?.$ref === '#') {
129-
const oldRef = bundledSchema.$ref
130-
const path = oldRef.split('/')
131-
132-
const pathTitle = path[path.length - 1]
133-
const referencedProperties = deReferencedSchema.definitions[pathTitle]
134-
135-
Object.assign(deReferencedSchema, { ...referencedProperties })
136-
137-
delete deReferencedSchema.$ref
138-
deReferencedSchema = await this.__dereferenceSchema(deReferencedSchema)
139-
.catch((err) => {
140-
throw err
141-
})
118+
const dereferencedSchema = await this.__dereferenceSchema(schema).catch(
119+
(err) => {
120+
throw err;
121+
}
122+
);
123+
124+
const convertedSchemas = SchemaConvertor.convert(dereferencedSchema, name);
125+
126+
for (const [schemaName, schemaValue] of Object.entries(
127+
convertedSchemas.schemas
128+
)) {
129+
if (this.__existsInComponents(schemaName)) {
130+
if (this.__isTheSameSchema(schemaValue, schemaName) === false) {
131+
if (schemaName === originalName) {
132+
finalName = `${schemaName}-${uuid()}`;
133+
this.__addToComponents("schemas", schemaValue, finalName);
134+
} else {
135+
this.__addToComponents("schemas", schemaValue, schemaName);
136+
}
142137
}
143-
144-
return deReferencedSchema
145-
}
146-
147-
/**
148-
* @function existsInComponents
149-
* @param {string} name - The name of the Schema
150-
* @returns {boolean} Whether it exists in components already
151-
*/
152-
__existsInComponents(name) {
153-
return Boolean(this.openAPI?.components?.schemas?.[name])
138+
} else {
139+
this.__addToComponents("schemas", schemaValue, schemaName);
140+
}
154141
}
155142

156-
/**
157-
* @function isTheSameSchema
158-
* @param {object} schema - The schema value
159-
* @param {string} otherSchemaName - The name of the schema
160-
* @returns {boolean} Whether the schema provided is the same one as in components already
161-
*/
162-
__isTheSameSchema(schema, otherSchemaName) {
163-
return isEqual(schema, this.openAPI.components.schemas[otherSchemaName])
143+
return `#/components/schemas/${finalName}`;
144+
}
145+
146+
async __dereferenceSchema(schema) {
147+
const bundledSchema = await $RefParser
148+
.bundle(schema, this.refParserOptions)
149+
.catch((err) => {
150+
throw err;
151+
});
152+
153+
let deReferencedSchema = await $RefParser
154+
.dereference(bundledSchema, this.refParserOptions)
155+
.catch((err) => {
156+
throw err;
157+
});
158+
159+
// deal with schemas that have been de-referenced poorly: naive
160+
if (deReferencedSchema?.$ref === "#") {
161+
const oldRef = bundledSchema.$ref;
162+
const path = oldRef.split("/");
163+
164+
const pathTitle = path[path.length - 1];
165+
const referencedProperties = deReferencedSchema.definitions[pathTitle];
166+
167+
Object.assign(deReferencedSchema, { ...referencedProperties });
168+
169+
delete deReferencedSchema.$ref;
170+
deReferencedSchema = await this.__dereferenceSchema(
171+
deReferencedSchema
172+
).catch((err) => {
173+
throw err;
174+
});
164175
}
165176

166-
/**
167-
* @function addToComponents
168-
* @param {string} type - The component type
169-
* @param {object} schema - The schema
170-
* @param {string} name - The name of the schema
171-
*/
172-
__addToComponents(type, schema, name) {
173-
const schemaObj = {
174-
[name]: schema
175-
}
176-
177-
if (this.openAPI?.components) {
178-
if (this.openAPI.components[type]) {
179-
Object.assign(this.openAPI.components[type], schemaObj)
180-
} else {
181-
Object.assign(this.openAPI.components, { [type]: schemaObj })
182-
}
183-
} else {
184-
const components = {
185-
components: {
186-
[type]: schemaObj
187-
}
188-
}
189-
190-
Object.assign(this.openAPI, components)
191-
}
177+
return deReferencedSchema;
178+
}
179+
180+
/**
181+
* @function existsInComponents
182+
* @param {string} name - The name of the Schema
183+
* @returns {boolean} Whether it exists in components already
184+
*/
185+
__existsInComponents(name) {
186+
return Boolean(this.openAPI?.components?.schemas?.[name]);
187+
}
188+
189+
/**
190+
* @function isTheSameSchema
191+
* @param {object} schema - The schema value
192+
* @param {string} otherSchemaName - The name of the schema
193+
* @returns {boolean} Whether the schema provided is the same one as in components already
194+
*/
195+
__isTheSameSchema(schema, otherSchemaName) {
196+
return isEqual(schema, this.openAPI.components.schemas[otherSchemaName]);
197+
}
198+
199+
/**
200+
* @function addToComponents
201+
* @param {string} type - The component type
202+
* @param {object} schema - The schema
203+
* @param {string} name - The name of the schema
204+
*/
205+
__addToComponents(type, schema, name) {
206+
const schemaObj = {
207+
[name]: schema,
208+
};
209+
210+
if (this.openAPI?.components) {
211+
if (this.openAPI.components[type]) {
212+
Object.assign(this.openAPI.components[type], schemaObj);
213+
} else {
214+
Object.assign(this.openAPI.components, { [type]: schemaObj });
215+
}
216+
} else {
217+
const components = {
218+
components: {
219+
[type]: schemaObj,
220+
},
221+
};
222+
223+
Object.assign(this.openAPI, components);
192224
}
225+
}
193226
}
194227

195228
module.exports = SchemaHandler;

0 commit comments

Comments
 (0)