");
148 | });
149 | });
150 |
--------------------------------------------------------------------------------
/src/transform/transformToCodeWithMustache.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from "fs";
2 | import * as Mustache from "mustache";
3 | import {} from "mustache";
4 | import { assign, identity } from "lodash";
5 | import { Template } from "../options/options";
6 | import { join } from "path";
7 |
8 | export const DEFAULT_TEMPLATE_PATH = join(__dirname, "..", "..", "templates");
9 |
10 | export type Templates = Record
;
11 |
12 | type Renderer = {
13 | readonly render: (
14 | template: string,
15 | view: any,
16 | partials?: any,
17 | tags?: string[]
18 | ) => string;
19 | escape?: (value: string) => string;
20 | };
21 |
22 | export function transformToCodeWithMustache(
23 | data: T,
24 | templates: Partial,
25 | additionalViewOptions: Partial = {},
26 | codeRenderer: Renderer = Mustache
27 | ): string {
28 | // Ensure we don't encode special characters
29 | codeRenderer.escape = identity;
30 |
31 | const loadedTemplates = loadTemplates(templates);
32 |
33 | return codeRenderer.render(
34 | loadedTemplates.class,
35 | assign(data, additionalViewOptions),
36 | loadedTemplates
37 | );
38 | }
39 |
40 | function loadTemplates(template: Partial = {}): Templates {
41 | return {
42 | class:
43 | template.class ||
44 | readFileSync(join(DEFAULT_TEMPLATE_PATH, "class.mustache"), "utf-8"),
45 | method:
46 | template.method ||
47 | readFileSync(join(DEFAULT_TEMPLATE_PATH, "method.mustache"), "utf-8"),
48 | type:
49 | template.type ||
50 | readFileSync(join(DEFAULT_TEMPLATE_PATH, "type.mustache"), "utf-8")
51 | };
52 | }
53 |
--------------------------------------------------------------------------------
/src/type-mappers/any.ts:
--------------------------------------------------------------------------------
1 | import { TypeSpec, makeTypeSpecFromSwaggerType } from "../typespec";
2 | import { SwaggerType } from "../swagger/Swagger";
3 |
4 | export interface AnyTypeSpec extends TypeSpec {
5 | readonly tsType: "any";
6 | readonly isAtomic: true;
7 | }
8 |
9 | export const isAnyTypeSpec = (swaggerType: SwaggerType): boolean =>
10 | swaggerType.minItems >= 0 &&
11 | swaggerType.hasOwnProperty("title") &&
12 | !swaggerType.$ref;
13 |
14 | export function makeAnyTypeSpec(swaggerType: SwaggerType): AnyTypeSpec {
15 | return {
16 | ...makeTypeSpecFromSwaggerType(swaggerType),
17 | tsType: "any",
18 | isAtomic: true
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/src/type-mappers/array.ts:
--------------------------------------------------------------------------------
1 | import { convertType } from "../typescript";
2 | import { TypeSpec, makeTypeSpecFromSwaggerType } from "../typespec";
3 | import {
4 | CollectionFormat,
5 | SwaggerArray,
6 | SwaggerType
7 | } from "../swagger/Swagger";
8 | import { Swagger } from "../swagger/Swagger";
9 |
10 | export interface ArrayTypeSpec extends TypeSpec {
11 | readonly tsType: string;
12 | readonly isAtomic: false;
13 | readonly isArray: true;
14 | readonly elementType: TypeSpec;
15 | readonly collectionFormat: CollectionFormat;
16 | }
17 |
18 | export function makeArrayTypeSpec(
19 | swaggerType: SwaggerArray,
20 | swagger: Swagger
21 | ): ArrayTypeSpec {
22 | const elementTypeSpec = convertType(swaggerType.items, swagger);
23 |
24 | return {
25 | ...makeTypeSpecFromSwaggerType(swaggerType),
26 | elementType: elementTypeSpec,
27 | tsType: `Array<${elementTypeSpec.target ||
28 | elementTypeSpec.tsType ||
29 | "any"}>`,
30 | isArray: true,
31 | isAtomic: false,
32 | // Normally, the default value is "csv". To be backward compatible to older versions, the standard is set to multi.
33 | // @todo set to csv for the next major version.
34 | collectionFormat: swaggerType.collectionFormat || "multi"
35 | };
36 | }
37 |
38 | export function isArray(swaggerType: SwaggerType): swaggerType is SwaggerArray {
39 | return swaggerType.type === "array";
40 | }
41 |
--------------------------------------------------------------------------------
/src/type-mappers/boolean.ts:
--------------------------------------------------------------------------------
1 | import { TypeSpec, makeTypeSpecFromSwaggerType } from "../typespec";
2 | import { SwaggerBoolean, SwaggerType } from "../swagger/Swagger";
3 |
4 | export interface BooleanTypeSpec extends TypeSpec {
5 | readonly tsType: "boolean";
6 | readonly isAtomic: true;
7 | }
8 |
9 | export function makeBooleanTypeSpec(
10 | swaggerType: SwaggerBoolean
11 | ): BooleanTypeSpec {
12 | return {
13 | ...makeTypeSpecFromSwaggerType(swaggerType),
14 | tsType: "boolean",
15 | isAtomic: true
16 | };
17 | }
18 |
19 | export function isBoolean(
20 | swaggerType: SwaggerType
21 | ): swaggerType is SwaggerBoolean {
22 | return swaggerType.type === "boolean";
23 | }
24 |
--------------------------------------------------------------------------------
/src/type-mappers/enum.ts:
--------------------------------------------------------------------------------
1 | import { makeTypeSpecFromSwaggerType, TypeSpec } from "../typespec";
2 | import { SwaggerEnum, SwaggerType } from "../swagger/Swagger";
3 |
4 | export interface EnumTypeSpec extends TypeSpec {
5 | readonly tsType: string;
6 | readonly isAtomic: true;
7 | readonly isEnum: true;
8 | readonly enum: ReadonlyArray;
9 | }
10 |
11 | export function makeEnumTypeSpec(swaggerType: SwaggerEnum): EnumTypeSpec {
12 | return {
13 | ...makeTypeSpecFromSwaggerType(swaggerType),
14 | tsType: swaggerType.enum.map(str => JSON.stringify(str)).join(" | "),
15 | enum: swaggerType.enum,
16 | isEnum: true,
17 | isAtomic: true
18 | };
19 | }
20 |
21 | export function isEnum(swaggerType: SwaggerType): swaggerType is SwaggerEnum {
22 | return Boolean(
23 | swaggerType.hasOwnProperty("enum") && (swaggerType as { enum?: any }).enum
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/type-mappers/number.ts:
--------------------------------------------------------------------------------
1 | import { TypeSpec, makeTypeSpecFromSwaggerType } from "../typespec";
2 | import { SwaggerNumber, SwaggerType } from "../swagger/Swagger";
3 |
4 | export interface NumberTypeSpec extends TypeSpec {
5 | readonly tsType: "number";
6 | readonly isAtomic: true;
7 | }
8 |
9 | export function makeNumberTypeSpec(swaggerType: SwaggerNumber): NumberTypeSpec {
10 | return {
11 | ...makeTypeSpecFromSwaggerType(swaggerType),
12 | tsType: "number",
13 | isAtomic: true
14 | };
15 | }
16 |
17 | export function isNumber(
18 | swaggerType: SwaggerType
19 | ): swaggerType is SwaggerNumber {
20 | return swaggerType.type === "number" || swaggerType.type === "integer";
21 | }
22 |
--------------------------------------------------------------------------------
/src/type-mappers/object.ts:
--------------------------------------------------------------------------------
1 | import { makeTypeSpecFromSwaggerType, TypeSpec } from "../typespec";
2 | import {
3 | map,
4 | filter,
5 | flatten,
6 | includes,
7 | concat,
8 | isArray,
9 | uniqBy,
10 | reverse
11 | } from "lodash";
12 | import { SwaggerType } from "../swagger/Swagger";
13 | import { Swagger } from "../swagger/Swagger";
14 | import { convertType } from "../typescript";
15 | import { makeAnyTypeSpec } from "./any";
16 |
17 | export interface ObjectTypeSpec extends TypeSpec {
18 | readonly tsType: "object";
19 | readonly isAtomic: false;
20 | readonly isObject: true;
21 | readonly requiredPropertyNames: ReadonlyArray;
22 | readonly properties: ReadonlyArray;
23 | readonly hasAdditionalProperties: boolean;
24 | readonly additionalPropertiesType: TypeSpec | undefined;
25 | }
26 |
27 | // see: https://support.reprezen.com/support/solutions/articles/6000162892-support-for-additionalproperties-in-swagger-2-0-schemas
28 | export function extractAdditionalPropertiesType(
29 | swaggerType: SwaggerType,
30 | swagger: Swagger
31 | ): TypeSpec | undefined {
32 | if (swaggerType.type && swaggerType.type !== "object") {
33 | return undefined;
34 | }
35 | if (swaggerType.additionalProperties === false) {
36 | return undefined;
37 | }
38 | if (
39 | swaggerType.additionalProperties === undefined ||
40 | swaggerType.additionalProperties === true
41 | ) {
42 | // is there an easier way to make an "any" type?
43 | return makeAnyTypeSpec({
44 | type: "object",
45 | required: [],
46 | minItems: 0,
47 | title: "any",
48 | properties: {}
49 | });
50 | }
51 | return convertType(swaggerType.additionalProperties, swagger);
52 | }
53 |
54 | export function makeObjectTypeSpec(
55 | swaggerType: SwaggerType,
56 | swagger: Swagger
57 | ): ObjectTypeSpec {
58 | // TODO: We threat everything that reaches this point as an object but not the required properties? (Removing the check for object makes the tests fail)
59 | // NOTE: object might not be set, because everything without a type is treated as object.
60 | const requiredPropertyNames =
61 | (!swaggerType.type || swaggerType.type === "object") &&
62 | isArray(swaggerType.required)
63 | ? swaggerType.required
64 | : [];
65 |
66 | // Some special handling is needed to support overlapping properties. The list of properties must be reversed to get the
67 | // overriding properties first. Only then can we filter out any duplicates. To get the original order back, the array
68 | // is reversed once more
69 | const allProperties = concat(
70 | getAllOfProperties(swaggerType, swagger),
71 | getObjectProperties(swaggerType, swagger, requiredPropertyNames)
72 | );
73 | const uniqueProperties = uniqBy(reverse(allProperties), "name");
74 | const properties = reverse(uniqueProperties);
75 |
76 | const addPropsType = extractAdditionalPropertiesType(swaggerType, swagger);
77 |
78 | return {
79 | ...makeTypeSpecFromSwaggerType(swaggerType),
80 | tsType: "object",
81 | isObject: true,
82 | isAtomic: false,
83 | properties,
84 | requiredPropertyNames,
85 | hasAdditionalProperties: addPropsType !== undefined,
86 | additionalPropertiesType: addPropsType
87 | };
88 | }
89 |
90 | function getObjectProperties(
91 | swaggerType: SwaggerType,
92 | swagger: Swagger,
93 | requiredPropertyNames: ReadonlyArray
94 | ): TypeSpec[] {
95 | return map(swaggerType.properties, (propertyType, propertyName) => ({
96 | ...convertType(propertyType, swagger),
97 | name: propertyName,
98 | isRequired: includes(requiredPropertyNames, propertyName)
99 | }));
100 | }
101 |
102 | function getAllOfProperties(
103 | swaggerType: SwaggerType,
104 | swagger: Swagger
105 | ): TypeSpec[] {
106 | if (!swaggerType.allOf) {
107 | return [];
108 | }
109 |
110 | return flatten(
111 | map(swaggerType.allOf, ref => {
112 | if (!ref.$ref) {
113 | const property = convertType(ref, swagger);
114 | return filter(property.properties);
115 | }
116 |
117 | const refSegments = ref.$ref.split("/");
118 | const name = refSegments[refSegments.length - 1];
119 |
120 | return flatten(
121 | filter(
122 | map(
123 | filter(
124 | swagger.definitions,
125 | (__, definitionName) => definitionName === name
126 | ),
127 | definition => {
128 | const property = convertType(definition, swagger);
129 |
130 | return property.properties;
131 | }
132 | ),
133 | isArray
134 | )
135 | );
136 | })
137 | );
138 | }
139 |
--------------------------------------------------------------------------------
/src/type-mappers/reference.ts:
--------------------------------------------------------------------------------
1 | import { makeTypeSpecFromSwaggerType, TypeSpec } from "../typespec";
2 | import { isString } from "lodash";
3 | import { SwaggerReference, SwaggerType } from "../swagger/Swagger";
4 |
5 | export interface ReferenceTypeSpec extends TypeSpec {
6 | readonly tsType: "ref";
7 | readonly target: string;
8 | readonly isRef: true;
9 | }
10 |
11 | export function makeReferenceTypeSpec(
12 | swaggerType: SwaggerReference
13 | ): ReferenceTypeSpec {
14 | return {
15 | ...makeTypeSpecFromSwaggerType(swaggerType),
16 | target: swaggerType.$ref.substring(swaggerType.$ref.lastIndexOf("/") + 1),
17 | tsType: "ref",
18 | isRef: true
19 | };
20 | }
21 |
22 | export function isReference(
23 | swaggerType: SwaggerType
24 | ): swaggerType is SwaggerReference {
25 | return isString(swaggerType.$ref);
26 | }
27 |
--------------------------------------------------------------------------------
/src/type-mappers/schema.ts:
--------------------------------------------------------------------------------
1 | import { SwaggerType, SwaggerSchema } from "../swagger/Swagger";
2 |
3 | export function isSchema(
4 | swaggerType: SwaggerType
5 | ): swaggerType is SwaggerSchema {
6 | return swaggerType.hasOwnProperty("schema");
7 | }
8 |
--------------------------------------------------------------------------------
/src/type-mappers/string.ts:
--------------------------------------------------------------------------------
1 | import { makeTypeSpecFromSwaggerType, TypeSpec } from "../typespec";
2 | import { SwaggerString, SwaggerType } from "../swagger/Swagger";
3 |
4 | export interface StringTypeSpec extends TypeSpec {
5 | readonly tsType: "string";
6 | readonly isAtomic: true;
7 | }
8 |
9 | export function makeStringTypeSpec(swaggerType: SwaggerString): StringTypeSpec {
10 | return {
11 | ...makeTypeSpecFromSwaggerType(swaggerType),
12 | tsType: "string",
13 | isAtomic: true
14 | };
15 | }
16 |
17 | export function isString(
18 | swaggerType: SwaggerType
19 | ): swaggerType is SwaggerString {
20 | return swaggerType.type === "string";
21 | }
22 |
--------------------------------------------------------------------------------
/src/type-mappers/void.ts:
--------------------------------------------------------------------------------
1 | import { makeTypeSpecFromSwaggerType, TypeSpec } from "../typespec";
2 | import { SwaggerType } from "../swagger/Swagger";
3 |
4 | export interface VoidTypeSpec extends TypeSpec {
5 | readonly tsType: "void";
6 | }
7 |
8 | /**
9 | * Determines if a plain swagger schema SwaggerType should be annotated as a typescript VoidTypeSpec.
10 | *
11 | * 3.0 spec
12 | * To indicate the response body is empty, do not specify a content for the response
13 | * https://swagger.io/docs/specification/describing-responses/#empty
14 | *
15 | * 2.0 spec
16 | * To indicate the response body is empty, do not specify a schema for the response.
17 | * Swagger treats no schema as a response without a body.
18 | * https://swagger.io/docs/specification/2-0/describing-responses/
19 | *
20 | * @param {SwaggerType} swaggerType - The SwaggerType.
21 | * @returns {boolean} A boolean indicating that the SwaggerType should be annotated as a void typescript type.
22 | */
23 | export const isVoidType = (swaggerType: SwaggerType): boolean =>
24 | Object.keys(swaggerType).length !== 0 &&
25 | (swaggerType.type as string | undefined) === undefined &&
26 | (swaggerType.required as boolean | string[] | undefined) === undefined &&
27 | swaggerType.$ref === undefined &&
28 | swaggerType.allOf === undefined &&
29 | (swaggerType.minItems as number | undefined) === undefined &&
30 | swaggerType.properties === undefined;
31 |
32 | /**
33 | * Converts a plain swagger schema SwaggerType to an annotated typescript swagger VoidTypeSpec.
34 | * By adding void type typescript type annotations.
35 | *
36 | * @param {SwaggerType} swaggerType - A SwaggerType.
37 | * @returns {VoidTypeSpec} A VoidTypeSpec.
38 | */
39 | export const makeVoidTypeSpec = (swaggerType: SwaggerType): VoidTypeSpec => ({
40 | ...makeTypeSpecFromSwaggerType(swaggerType),
41 | tsType: "void",
42 | isAtomic: true
43 | });
44 |
--------------------------------------------------------------------------------
/src/typescript.test.ts:
--------------------------------------------------------------------------------
1 | import { convertType } from "./typescript";
2 | import {
3 | makeFakeSwagger,
4 | makeSwaggerType,
5 | makeEmptyTypeSpec
6 | } from "./test-helpers/testHelpers";
7 | import { Swagger } from "./swagger/Swagger";
8 | import { SwaggerType } from "./swagger/Swagger";
9 | import { TypeSpec } from "./typespec";
10 |
11 | describe("convertType", () => {
12 | let swagger: Swagger;
13 | let swaggerType: SwaggerType;
14 | let emptyTypeSpecWithDefaults: TypeSpec;
15 |
16 | beforeEach(() => {
17 | swagger = makeFakeSwagger();
18 | emptyTypeSpecWithDefaults = makeEmptyTypeSpec();
19 | });
20 |
21 | describe("reference", () => {
22 | it("returns a reference object", () => {
23 | swaggerType = makeSwaggerType({
24 | $ref: "https://microsoft.com/api/users",
25 | type: "reference"
26 | });
27 |
28 | expect(convertType(swaggerType, swagger)).toEqual({
29 | ...emptyTypeSpecWithDefaults,
30 | target: "users",
31 | tsType: "ref",
32 | isRef: true
33 | });
34 | });
35 | });
36 |
37 | describe("enum", () => {
38 | it("correctly converts an enum type", () => {
39 | swaggerType = makeSwaggerType({
40 | enum: ["Marius", "Mark", "Mathieu"],
41 | type: "enum"
42 | });
43 |
44 | expect(convertType(swaggerType, swagger)).toEqual({
45 | ...emptyTypeSpecWithDefaults,
46 | tsType: '"Marius" | "Mark" | "Mathieu"',
47 | isAtomic: true,
48 | isEnum: true,
49 | enum: ["Marius", "Mark", "Mathieu"]
50 | });
51 | });
52 |
53 | it("correctly passes through typespec properties", () => {
54 | swaggerType = makeSwaggerType({
55 | description: "namesStartingWithM",
56 | required: true,
57 | enum: ["Marius", "Mark", "Mathieu"],
58 | type: "enum"
59 | });
60 |
61 | expect(convertType(swaggerType, swagger)).toEqual({
62 | ...emptyTypeSpecWithDefaults,
63 | description: "namesStartingWithM",
64 | isRequired: true,
65 | isNullable: false,
66 | tsType: '"Marius" | "Mark" | "Mathieu"',
67 | isAtomic: true,
68 | isEnum: true,
69 | enum: ["Marius", "Mark", "Mathieu"]
70 | });
71 | });
72 | });
73 |
74 | describe("string", () => {
75 | it("correctly converts an string type", () => {
76 | swaggerType = makeSwaggerType({ type: "string" });
77 |
78 | expect(convertType(swaggerType, swagger)).toEqual({
79 | ...emptyTypeSpecWithDefaults,
80 | tsType: "string",
81 | isAtomic: true
82 | });
83 | });
84 |
85 | it("correctly passes through typespec properties", () => {
86 | swaggerType = makeSwaggerType({
87 | description: "The description of a string property",
88 | required: false,
89 | type: "string"
90 | });
91 |
92 | expect(convertType(swaggerType, swagger)).toEqual({
93 | ...emptyTypeSpecWithDefaults,
94 | description: "The description of a string property",
95 | isRequired: false,
96 | isNullable: true,
97 | tsType: "string",
98 | isAtomic: true
99 | });
100 | });
101 | });
102 |
103 | describe("number", () => {
104 | it("correctly converts an number type", () => {
105 | swaggerType = makeSwaggerType({ type: "number" });
106 |
107 | expect(convertType(swaggerType, swagger)).toEqual({
108 | ...emptyTypeSpecWithDefaults,
109 | tsType: "number",
110 | isAtomic: true
111 | });
112 | });
113 |
114 | it("correctly passes through typespec properties", () => {
115 | swaggerType = makeSwaggerType({
116 | description: "The description of a number property",
117 | required: false,
118 | type: "number"
119 | });
120 |
121 | expect(convertType(swaggerType, swagger)).toEqual({
122 | ...emptyTypeSpecWithDefaults,
123 | description: "The description of a number property",
124 | isRequired: false,
125 | isNullable: true,
126 | tsType: "number",
127 | isAtomic: true
128 | });
129 | });
130 | });
131 |
132 | describe("boolean", () => {
133 | it("correctly converts an boolean type", () => {
134 | swaggerType = makeSwaggerType({ type: "boolean" });
135 |
136 | expect(convertType(swaggerType, swagger)).toEqual({
137 | ...emptyTypeSpecWithDefaults,
138 | tsType: "boolean",
139 | isAtomic: true
140 | });
141 | });
142 |
143 | it("correctly passes through typespec properties", () => {
144 | swaggerType = makeSwaggerType({
145 | description: "The description of a boolean property",
146 | required: false,
147 | type: "boolean"
148 | });
149 |
150 | expect(convertType(swaggerType, swagger)).toEqual({
151 | ...emptyTypeSpecWithDefaults,
152 | description: "The description of a boolean property",
153 | isRequired: false,
154 | isNullable: true,
155 | tsType: "boolean",
156 | isAtomic: true
157 | });
158 | });
159 | });
160 |
161 | describe("array", () => {
162 | it("correctly converts an array type", () => {
163 | swaggerType = makeSwaggerType({
164 | type: "array",
165 | items: makeSwaggerType({ type: "number" })
166 | });
167 |
168 | expect(convertType(swaggerType, swagger)).toEqual({
169 | ...emptyTypeSpecWithDefaults,
170 | tsType: "Array",
171 | isAtomic: false,
172 | isArray: true,
173 | collectionFormat: "multi",
174 | elementType: {
175 | ...emptyTypeSpecWithDefaults,
176 | tsType: "number",
177 | isAtomic: true
178 | }
179 | });
180 | });
181 |
182 | it("correctly passes through typespec properties", () => {
183 | swaggerType = makeSwaggerType({
184 | description: "The description of a array property",
185 | required: false,
186 | type: "array",
187 | collectionFormat: "csv",
188 | items: makeSwaggerType({
189 | type: "object",
190 | required: false,
191 | additionalProperties: false
192 | })
193 | });
194 |
195 | expect(convertType(swaggerType, swagger)).toEqual({
196 | ...emptyTypeSpecWithDefaults,
197 | description: "The description of a array property",
198 | isRequired: false,
199 | isNullable: true,
200 | isArray: true,
201 | collectionFormat: "csv",
202 | tsType: "Array