Skip to content

Commit

Permalink
Release 1.8.1 (#48)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
js2me authored May 25, 2020
1 parent 974e761 commit bcb095a
Show file tree
Hide file tree
Showing 27 changed files with 745 additions and 1,009 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# next release

# 1.8.1

Fixes:
- form data request body (request body content `multipart/form-data`)

Minor:
- inline comments of the data contract type properties
![one line comments](./assets/changelog_assets/one-line-comments.jpg)
- remove `Array<T>` type usage (now the more simple type `T[]`)

# 1.8.0

Features:
Expand Down
Binary file added assets/changelog_assets/one-line-comments.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "swagger-typescript-api",
"version": "1.8.0",
"version": "1.8.1",
"description": "Create typescript api module from swagger schema",
"scripts": {
"cli": "node index.js -r -d -p http://localhost:8080/api/v1/swagger.json -n swagger-test-cli.ts",
Expand Down
3 changes: 2 additions & 1 deletion src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ module.exports = {
if (inline) {
return _(prettified)
.split(/\n/g)
.map(part => _.trim(part))
.map((part) => _.trim(part))
.compact()
.join(" ")
.valueOf();
}

return _.replace(prettified, /\n$/g, "");
},
toInternalCase: (value) => _.camelCase(_.lowerCase(value)),
};
58 changes: 44 additions & 14 deletions src/routes.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const _ = require("lodash");
const { collect } = require("./utils");
const { parseSchema, getRefType } = require("./schema");
const { parseSchema, getRefType, formDataTypes } = require("./schema");
const { checkAndRenameModelName } = require("./modelNames");
const { inlineExtraFormatters } = require("./typeFormatters");
const {
Expand All @@ -25,15 +25,24 @@ const getSchemaFromRequestType = (requestType) => {

if (!content) return null;

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

return contentByType && contentByType.schema;
/* for example: dataType = "multipart/form-data" */
for (const dataType in content) {
if (content[dataType] && content[dataType].schema) {
return {
...content[dataType].schema,
dataType,
};
}
}

return null;
};

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

if (schema) {
Expand Down Expand Up @@ -197,8 +206,11 @@ const parseRoutes = ({ paths }, parsedSchemas) =>
const formDataParams = getRouteParams(parameters, "formData");
const pathParams = getRouteParams(parameters, "path");
const queryParams = getRouteParams(parameters, "query");
const requestBodyType = getSchemaFromRequestType(requestBody);

const hasFormDataParams = formDataParams && formDataParams.length;
const hasFormDataParams = formDataParams && !!formDataParams.length;
let formDataRequestBody =
requestBodyType && requestBodyType.dataType === "multipart/form-data";

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

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

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

const formDataObjectSchema = convertRouteParamsIntoObject(formDataParams);
const formDataObjectSchema = hasFormDataParams
? convertRouteParamsIntoObject(formDataParams)
: formDataRequestBody
? getSchemaFromRequestType(requestBody)
: null;
const queryObjectSchema = convertRouteParamsIntoObject(queryParams);

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

const bodyType = hasFormDataParams
? parseSchema(formDataObjectSchema, null, inlineExtraFormatters).content
: requestBody
? getTypeFromRequestInfo(requestBody, parsedSchemas, operationId)
: null;
let bodyType = null;

if (formDataObjectSchema) {
bodyType = parseSchema(formDataObjectSchema, null, inlineExtraFormatters).content;
} else if (requestBody) {
bodyType = getTypeFromRequestInfo(requestBody, parsedSchemas, operationId);

// TODO: Refactor that.
// It needed for cases when swagger schema is not declared request body type as form data
// but request body data type contains form data types like File
if (formDataTypes.some((formDataType) => _.includes(bodyType, `: ${formDataType}`))) {
formDataRequestBody = true;
}
}

// Gets all in path parameters from route
// Example: someurl.com/{id}/{name}
Expand Down Expand Up @@ -347,7 +372,7 @@ const parseRoutes = ({ paths }, parsedSchemas) =>
moduleName: _.replace(moduleName, /^(\d)/, "v$1"),
security: hasSecurity,
hasQuery,
hasFormDataParams,
hasFormDataParams: hasFormDataParams || formDataRequestBody,
queryType: queryType || "{}",
bodyType: bodyType || "never",
name,
Expand All @@ -366,9 +391,14 @@ const parseRoutes = ({ paths }, parsedSchemas) =>
`${specificArgs.requestParams.name}` +
_.compact([
requestBody && `, ${bodyParamName}`,
hasFormDataParams && `${requestBody ? "" : ", null"}, BodyType.FormData`,
(hasFormDataParams || formDataRequestBody) &&
`${requestBody ? "" : ", null"}, BodyType.FormData`,
hasSecurity &&
`${hasFormDataParams ? "" : `${requestBody ? "" : ", null"}, BodyType.Json`}, true`,
`${
hasFormDataParams || formDataRequestBody
? ""
: `${requestBody ? "" : ", null"}, BodyType.Json`
}, true`,
]).join(""),
};
}),
Expand Down
90 changes: 55 additions & 35 deletions src/schema.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,78 @@
const _ = require("lodash");
const { inlineExtraFormatters } = require("./typeFormatters");
const { isValidName, checkAndRenameModelName } = require("./modelNames");
const { formatDescription } = require("./common");
const { formatDescription, toInternalCase } = require("./common");
const { DEFAULT_PRIMITIVE_TYPE } = require("./constants");
const { config } = require("./config");

const jsTypes = ["number", "boolean", "string", "object"];
const jsEmptyTypes = ["null", "undefined"];
const typeAliases = {
const types = {
/** { type: "integer" } -> { type: "number" } */
integer: "number",
number: "number",
boolean: "boolean",
object: "object",
file: "File",
string: {
$default: "string",

/** formats */
binary: "File",
},
array: ({ items, ...schemaPart }) => {
const { content } = parseSchema(items, null, inlineExtraFormatters);
return checkAndAddNull(schemaPart, `${content}[]`);
},

// TODO: probably it can be needed
// date: "Date",
// dateTime: "Date",
};

const jsEmptyTypes = _.uniq(["null", "undefined"]);
const formDataTypes = _.uniq([types.file, types.string.binary]);

const getTypeAlias = (rawSchema) => {
const schema = rawSchema || {};
const type = toInternalCase(schema.type);
const format = toInternalCase(schema.format);
const typeAlias = _.get(types, [type, format]) || _.get(types, [type, "$default"]) || types[type];

if (_.isFunction(typeAlias)) {
return typeAlias(schema);
}

return typeAlias || type;
};

const findSchemaType = (schema) => {
const getInternalSchemaType = (schema) => {
if (schema.enum) return "enum";
if (schema.properties) return "object";
if (schema.allOf || schema.oneOf || schema.anyOf || schema.not) return "complex";

return "primitive";
};

const nullableExtras = (schema, value) => {
const checkAndAddNull = (schema, value) => {
const { nullable, type } = schema || {};
return nullable || type === "null" ? `${value} | null` : value;
};

const getPrimitiveType = (property) => {
const { type } = property || {};
const primitiveType = typeAliases[type] || type;
return primitiveType ? nullableExtras(property, primitiveType) : DEFAULT_PRIMITIVE_TYPE;
};

const specificObjectTypes = {
array: ({ items, ...schemaPart }) => {
const { content, type } = parseSchema(items, null, inlineExtraFormatters);
return nullableExtras(schemaPart, type === "primitive" ? `${content}[]` : `Array<${content}>`);
},
};

const getRefType = (property) => {
const ref = property && property["$ref"];
return (ref && config.componentsMap[ref]) || null;
};

const getRefTypeName = (property) => {
const refTypeInfo = getRefType(property);
return refTypeInfo && checkAndRenameModelName(refTypeInfo.typeName);
};
const getType = (schema) => {
if (!schema) return DEFAULT_PRIMITIVE_TYPE;

const getType = (property) => {
if (!property) return DEFAULT_PRIMITIVE_TYPE;
const refTypeInfo = getRefType(schema);

if (refTypeInfo) {
return checkAndAddNull(schema, checkAndRenameModelName(refTypeInfo.typeName));
}

const anotherTypeGetter = specificObjectTypes[property.type] || getPrimitiveType;
const refType = getRefTypeName(property);
return refType ? nullableExtras(property, refType) : anotherTypeGetter(property);
const primitiveType = getTypeAlias(schema);
return primitiveType ? checkAndAddNull(schema, primitiveType) : DEFAULT_PRIMITIVE_TYPE;
};

const getObjectTypeContent = (properties) => {
Expand All @@ -64,6 +83,7 @@ const getObjectTypeContent = (properties) => {
: !property.nullable
: !!property.required;
return {
$$raw: property,
description: property.description,
isRequired,
field: `${isValidName(name) ? name : `"${name}"`}${
Expand All @@ -79,17 +99,17 @@ const complexSchemaParsers = {
oneOf: (schema) => {
// T1 | T2
const combined = _.map(schema.oneOf, complexTypeGetter);
return nullableExtras(schema, combined.join(" | "));
return checkAndAddNull(schema, combined.join(" | "));
},
allOf: (schema) => {
// T1 & T2
return nullableExtras(schema, _.map(schema.allOf, complexTypeGetter).join(" & "));
return checkAndAddNull(schema, _.map(schema.allOf, complexTypeGetter).join(" & "));
},
anyOf: (schema) => {
// T1 | T2 | (T1 & T2)
const combined = _.map(schema.anyOf, complexTypeGetter);
const nonEmptyTypesCombined = combined.filter((type) => !jsEmptyTypes.includes(type));
return nullableExtras(
return checkAndAddNull(
schema,
`${combined.join(" | ")}` +
(nonEmptyTypesCombined.length > 1 ? ` | (${nonEmptyTypesCombined.join(" & ")})` : ""),
Expand All @@ -114,8 +134,8 @@ const getComplexType = (schema) => {

const schemaParsers = {
enum: (schema, typeName) => {
const type = getPrimitiveType(schema);
const isIntegerEnum = type === "number";
const type = getType(schema);
const isIntegerEnum = type === types.number;
return {
$parsedSchema: true,
schemaType: "enum",
Expand Down Expand Up @@ -212,7 +232,7 @@ const parseSchema = (rawSchema, typeName, formattersMap) => {
parsedSchema = rawSchema;
} else {
const fixedRawSchema = checkAndFixSchema(rawSchema);
schemaType = findSchemaType(fixedRawSchema);
schemaType = getInternalSchemaType(fixedRawSchema);
parsedSchema = schemaParsers[schemaType](fixedRawSchema, typeName);
}

Expand All @@ -233,6 +253,6 @@ module.exports = {
parseSchemas,
getInlineParseContent,
getType,
getRefTypeName,
getRefType,
formDataTypes,
};
2 changes: 1 addition & 1 deletion src/swagger.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const fixSwaggerScheme = (usage, original) => {
if (!existUsageParam) {
usageRouteParams.push(originalRouteParam);
} else if (originalRouteParam.in === "formData") {
console.log("HERE");
// console.log("HERE");
}
});
});
Expand Down
14 changes: 7 additions & 7 deletions src/typeFormatters.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ const formatters = {
const extraSpace = " ";
const result = `${extraSpace}${part.field};\n`;

const comments = _.compact([part.title, part.description]);
const comments = _.compact([part.title, part.description]).reduce(
(acc, comment) => [...acc, ...comment.split(/\n/g)],
[],
);

const commonText = comments.length
? [
"",
"/**",
...comments.reduce(
(acc, comment) => [...acc, ...comment.split(/\n/g).map((part) => ` * ${part}`)],
[],
),
" */",
...(comments.length === 1
? [`/** ${comments[0]} */`]
: ["/**", ...comments.map((commentPart) => ` * ${commentPart}`), " */"]),
]
.map((part) => `${extraSpace}${part}\n`)
.join("")
Expand Down
Loading

0 comments on commit bcb095a

Please sign in to comment.