Skip to content

Commit bcb095a

Browse files
authored
Release 1.8.1 (#48)
* fix: form data request body (type: string, format: binary) * chore: remove Array type usage (Array<t1 | t2> -> (t1 | t2)[]) * chore(internal): add "toInternalCase" util * chore: one line comments for fields in types * bump: up version to 1.8.1; docs: update CHANGELOG * refactor: type aliases; fix: form data request bodies * chore: add test schema with formdata
1 parent 974e761 commit bcb095a

27 files changed

+745
-1009
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# next release
22

3+
# 1.8.1
4+
5+
Fixes:
6+
- form data request body (request body content `multipart/form-data`)
7+
8+
Minor:
9+
- inline comments of the data contract type properties
10+
![one line comments](./assets/changelog_assets/one-line-comments.jpg)
11+
- remove `Array<T>` type usage (now the more simple type `T[]`)
12+
313
# 1.8.0
414

515
Features:
62.6 KB
Loading

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "swagger-typescript-api",
3-
"version": "1.8.0",
3+
"version": "1.8.1",
44
"description": "Create typescript api module from swagger schema",
55
"scripts": {
66
"cli": "node index.js -r -d -p http://localhost:8080/api/v1/swagger.json -n swagger-test-cli.ts",

src/common.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ module.exports = {
1515
if (inline) {
1616
return _(prettified)
1717
.split(/\n/g)
18-
.map(part => _.trim(part))
18+
.map((part) => _.trim(part))
1919
.compact()
2020
.join(" ")
2121
.valueOf();
2222
}
2323

2424
return _.replace(prettified, /\n$/g, "");
2525
},
26+
toInternalCase: (value) => _.camelCase(_.lowerCase(value)),
2627
};

src/routes.js

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const _ = require("lodash");
22
const { collect } = require("./utils");
3-
const { parseSchema, getRefType } = require("./schema");
3+
const { parseSchema, getRefType, formDataTypes } = require("./schema");
44
const { checkAndRenameModelName } = require("./modelNames");
55
const { inlineExtraFormatters } = require("./typeFormatters");
66
const {
@@ -25,15 +25,24 @@ const getSchemaFromRequestType = (requestType) => {
2525

2626
if (!content) return null;
2727

28-
const contentByType = _.find(content, (contentByType) => contentByType.schema);
28+
/* content: { "multipart/form-data": { schema: {...} }, "application/json": { schema: {...} } } */
2929

30-
return contentByType && contentByType.schema;
30+
/* for example: dataType = "multipart/form-data" */
31+
for (const dataType in content) {
32+
if (content[dataType] && content[dataType].schema) {
33+
return {
34+
...content[dataType].schema,
35+
dataType,
36+
};
37+
}
38+
}
39+
40+
return null;
3141
};
3242

3343
const getTypeFromRequestInfo = (requestInfo, parsedSchemas, operationId, contentType) => {
3444
// TODO: make more flexible pick schema without content type
3545
const schema = getSchemaFromRequestType(requestInfo);
36-
// const refType = getRefTypeName(requestInfo);
3746
const refTypeInfo = getRefType(requestInfo);
3847

3948
if (schema) {
@@ -197,8 +206,11 @@ const parseRoutes = ({ paths }, parsedSchemas) =>
197206
const formDataParams = getRouteParams(parameters, "formData");
198207
const pathParams = getRouteParams(parameters, "path");
199208
const queryParams = getRouteParams(parameters, "query");
209+
const requestBodyType = getSchemaFromRequestType(requestBody);
200210

201-
const hasFormDataParams = formDataParams && formDataParams.length;
211+
const hasFormDataParams = formDataParams && !!formDataParams.length;
212+
let formDataRequestBody =
213+
requestBodyType && requestBodyType.dataType === "multipart/form-data";
202214

203215
const moduleName = _.camelCase(_.compact(_.split(route, "/"))[0]);
204216

@@ -207,7 +219,11 @@ const parseRoutes = ({ paths }, parsedSchemas) =>
207219

208220
const responsesTypes = getTypesFromResponses(responses, parsedSchemas, operationId);
209221

210-
const formDataObjectSchema = convertRouteParamsIntoObject(formDataParams);
222+
const formDataObjectSchema = hasFormDataParams
223+
? convertRouteParamsIntoObject(formDataParams)
224+
: formDataRequestBody
225+
? getSchemaFromRequestType(requestBody)
226+
: null;
211227
const queryObjectSchema = convertRouteParamsIntoObject(queryParams);
212228

213229
const bodyParamName =
@@ -217,11 +233,20 @@ const parseRoutes = ({ paths }, parsedSchemas) =>
217233
? parseSchema(queryObjectSchema, null, inlineExtraFormatters).content
218234
: null;
219235

220-
const bodyType = hasFormDataParams
221-
? parseSchema(formDataObjectSchema, null, inlineExtraFormatters).content
222-
: requestBody
223-
? getTypeFromRequestInfo(requestBody, parsedSchemas, operationId)
224-
: null;
236+
let bodyType = null;
237+
238+
if (formDataObjectSchema) {
239+
bodyType = parseSchema(formDataObjectSchema, null, inlineExtraFormatters).content;
240+
} else if (requestBody) {
241+
bodyType = getTypeFromRequestInfo(requestBody, parsedSchemas, operationId);
242+
243+
// TODO: Refactor that.
244+
// It needed for cases when swagger schema is not declared request body type as form data
245+
// but request body data type contains form data types like File
246+
if (formDataTypes.some((formDataType) => _.includes(bodyType, `: ${formDataType}`))) {
247+
formDataRequestBody = true;
248+
}
249+
}
225250

226251
// Gets all in path parameters from route
227252
// Example: someurl.com/{id}/{name}
@@ -347,7 +372,7 @@ const parseRoutes = ({ paths }, parsedSchemas) =>
347372
moduleName: _.replace(moduleName, /^(\d)/, "v$1"),
348373
security: hasSecurity,
349374
hasQuery,
350-
hasFormDataParams,
375+
hasFormDataParams: hasFormDataParams || formDataRequestBody,
351376
queryType: queryType || "{}",
352377
bodyType: bodyType || "never",
353378
name,
@@ -366,9 +391,14 @@ const parseRoutes = ({ paths }, parsedSchemas) =>
366391
`${specificArgs.requestParams.name}` +
367392
_.compact([
368393
requestBody && `, ${bodyParamName}`,
369-
hasFormDataParams && `${requestBody ? "" : ", null"}, BodyType.FormData`,
394+
(hasFormDataParams || formDataRequestBody) &&
395+
`${requestBody ? "" : ", null"}, BodyType.FormData`,
370396
hasSecurity &&
371-
`${hasFormDataParams ? "" : `${requestBody ? "" : ", null"}, BodyType.Json`}, true`,
397+
`${
398+
hasFormDataParams || formDataRequestBody
399+
? ""
400+
: `${requestBody ? "" : ", null"}, BodyType.Json`
401+
}, true`,
372402
]).join(""),
373403
};
374404
}),

src/schema.js

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,78 @@
11
const _ = require("lodash");
22
const { inlineExtraFormatters } = require("./typeFormatters");
33
const { isValidName, checkAndRenameModelName } = require("./modelNames");
4-
const { formatDescription } = require("./common");
4+
const { formatDescription, toInternalCase } = require("./common");
55
const { DEFAULT_PRIMITIVE_TYPE } = require("./constants");
66
const { config } = require("./config");
77

8-
const jsTypes = ["number", "boolean", "string", "object"];
9-
const jsEmptyTypes = ["null", "undefined"];
10-
const typeAliases = {
8+
const types = {
9+
/** { type: "integer" } -> { type: "number" } */
1110
integer: "number",
11+
number: "number",
12+
boolean: "boolean",
13+
object: "object",
1214
file: "File",
15+
string: {
16+
$default: "string",
17+
18+
/** formats */
19+
binary: "File",
20+
},
21+
array: ({ items, ...schemaPart }) => {
22+
const { content } = parseSchema(items, null, inlineExtraFormatters);
23+
return checkAndAddNull(schemaPart, `${content}[]`);
24+
},
25+
26+
// TODO: probably it can be needed
27+
// date: "Date",
28+
// dateTime: "Date",
29+
};
30+
31+
const jsEmptyTypes = _.uniq(["null", "undefined"]);
32+
const formDataTypes = _.uniq([types.file, types.string.binary]);
33+
34+
const getTypeAlias = (rawSchema) => {
35+
const schema = rawSchema || {};
36+
const type = toInternalCase(schema.type);
37+
const format = toInternalCase(schema.format);
38+
const typeAlias = _.get(types, [type, format]) || _.get(types, [type, "$default"]) || types[type];
39+
40+
if (_.isFunction(typeAlias)) {
41+
return typeAlias(schema);
42+
}
43+
44+
return typeAlias || type;
1345
};
1446

15-
const findSchemaType = (schema) => {
47+
const getInternalSchemaType = (schema) => {
1648
if (schema.enum) return "enum";
1749
if (schema.properties) return "object";
1850
if (schema.allOf || schema.oneOf || schema.anyOf || schema.not) return "complex";
1951

2052
return "primitive";
2153
};
2254

23-
const nullableExtras = (schema, value) => {
55+
const checkAndAddNull = (schema, value) => {
2456
const { nullable, type } = schema || {};
2557
return nullable || type === "null" ? `${value} | null` : value;
2658
};
2759

28-
const getPrimitiveType = (property) => {
29-
const { type } = property || {};
30-
const primitiveType = typeAliases[type] || type;
31-
return primitiveType ? nullableExtras(property, primitiveType) : DEFAULT_PRIMITIVE_TYPE;
32-
};
33-
34-
const specificObjectTypes = {
35-
array: ({ items, ...schemaPart }) => {
36-
const { content, type } = parseSchema(items, null, inlineExtraFormatters);
37-
return nullableExtras(schemaPart, type === "primitive" ? `${content}[]` : `Array<${content}>`);
38-
},
39-
};
40-
4160
const getRefType = (property) => {
4261
const ref = property && property["$ref"];
4362
return (ref && config.componentsMap[ref]) || null;
4463
};
4564

46-
const getRefTypeName = (property) => {
47-
const refTypeInfo = getRefType(property);
48-
return refTypeInfo && checkAndRenameModelName(refTypeInfo.typeName);
49-
};
65+
const getType = (schema) => {
66+
if (!schema) return DEFAULT_PRIMITIVE_TYPE;
5067

51-
const getType = (property) => {
52-
if (!property) return DEFAULT_PRIMITIVE_TYPE;
68+
const refTypeInfo = getRefType(schema);
69+
70+
if (refTypeInfo) {
71+
return checkAndAddNull(schema, checkAndRenameModelName(refTypeInfo.typeName));
72+
}
5373

54-
const anotherTypeGetter = specificObjectTypes[property.type] || getPrimitiveType;
55-
const refType = getRefTypeName(property);
56-
return refType ? nullableExtras(property, refType) : anotherTypeGetter(property);
74+
const primitiveType = getTypeAlias(schema);
75+
return primitiveType ? checkAndAddNull(schema, primitiveType) : DEFAULT_PRIMITIVE_TYPE;
5776
};
5877

5978
const getObjectTypeContent = (properties) => {
@@ -64,6 +83,7 @@ const getObjectTypeContent = (properties) => {
6483
: !property.nullable
6584
: !!property.required;
6685
return {
86+
$$raw: property,
6787
description: property.description,
6888
isRequired,
6989
field: `${isValidName(name) ? name : `"${name}"`}${
@@ -79,17 +99,17 @@ const complexSchemaParsers = {
7999
oneOf: (schema) => {
80100
// T1 | T2
81101
const combined = _.map(schema.oneOf, complexTypeGetter);
82-
return nullableExtras(schema, combined.join(" | "));
102+
return checkAndAddNull(schema, combined.join(" | "));
83103
},
84104
allOf: (schema) => {
85105
// T1 & T2
86-
return nullableExtras(schema, _.map(schema.allOf, complexTypeGetter).join(" & "));
106+
return checkAndAddNull(schema, _.map(schema.allOf, complexTypeGetter).join(" & "));
87107
},
88108
anyOf: (schema) => {
89109
// T1 | T2 | (T1 & T2)
90110
const combined = _.map(schema.anyOf, complexTypeGetter);
91111
const nonEmptyTypesCombined = combined.filter((type) => !jsEmptyTypes.includes(type));
92-
return nullableExtras(
112+
return checkAndAddNull(
93113
schema,
94114
`${combined.join(" | ")}` +
95115
(nonEmptyTypesCombined.length > 1 ? ` | (${nonEmptyTypesCombined.join(" & ")})` : ""),
@@ -114,8 +134,8 @@ const getComplexType = (schema) => {
114134

115135
const schemaParsers = {
116136
enum: (schema, typeName) => {
117-
const type = getPrimitiveType(schema);
118-
const isIntegerEnum = type === "number";
137+
const type = getType(schema);
138+
const isIntegerEnum = type === types.number;
119139
return {
120140
$parsedSchema: true,
121141
schemaType: "enum",
@@ -212,7 +232,7 @@ const parseSchema = (rawSchema, typeName, formattersMap) => {
212232
parsedSchema = rawSchema;
213233
} else {
214234
const fixedRawSchema = checkAndFixSchema(rawSchema);
215-
schemaType = findSchemaType(fixedRawSchema);
235+
schemaType = getInternalSchemaType(fixedRawSchema);
216236
parsedSchema = schemaParsers[schemaType](fixedRawSchema, typeName);
217237
}
218238

@@ -233,6 +253,6 @@ module.exports = {
233253
parseSchemas,
234254
getInlineParseContent,
235255
getType,
236-
getRefTypeName,
237256
getRefType,
257+
formDataTypes,
238258
};

src/swagger.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const fixSwaggerScheme = (usage, original) => {
8585
if (!existUsageParam) {
8686
usageRouteParams.push(originalRouteParam);
8787
} else if (originalRouteParam.in === "formData") {
88-
console.log("HERE");
88+
// console.log("HERE");
8989
}
9090
});
9191
});

src/typeFormatters.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ const formatters = {
99
const extraSpace = " ";
1010
const result = `${extraSpace}${part.field};\n`;
1111

12-
const comments = _.compact([part.title, part.description]);
12+
const comments = _.compact([part.title, part.description]).reduce(
13+
(acc, comment) => [...acc, ...comment.split(/\n/g)],
14+
[],
15+
);
1316

1417
const commonText = comments.length
1518
? [
1619
"",
17-
"/**",
18-
...comments.reduce(
19-
(acc, comment) => [...acc, ...comment.split(/\n/g).map((part) => ` * ${part}`)],
20-
[],
21-
),
22-
" */",
20+
...(comments.length === 1
21+
? [`/** ${comments[0]} */`]
22+
: ["/**", ...comments.map((commentPart) => ` * ${commentPart}`), " */"]),
2323
]
2424
.map((part) => `${extraSpace}${part}\n`)
2525
.join("")

0 commit comments

Comments
 (0)