Skip to content

Commit 0a0de44

Browse files
committed
feat(api-client): reference parameters are dereferenced
1 parent 54f796d commit 0a0de44

File tree

6 files changed

+93
-78
lines changed

6 files changed

+93
-78
lines changed

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
"http",
4848
"httpclient"
4949
],
50+
"engines": {
51+
"node": ">=8.9.1",
52+
"yarn": ">=1.3.2"
53+
},
5054
"license": "MIT",
5155
"scripts": {
5256
"generate": "node ./dist/main.js",
@@ -75,7 +79,7 @@
7579
"rimraf": "^2.5.2",
7680
"standard-version": "^4.3.0",
7781
"swagger-schema-official": "^2.0.0-bab6bed",
78-
"ts-node": "^4.1.0",
82+
"ts-node": "^5.0.1",
7983
"tslint": "^5.9.1",
8084
"tslint-immutable": "^4.5.2",
8185
"typescript": "^2.7.2"

src/helper.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ export function dashCase(text: string = ''): string {
2626
* @returns {string}
2727
*/
2828
export function dereferenceType(refString: string): string {
29-
return refString.replace('#/definitions/', '');
29+
if (!refString) {
30+
return '';
31+
}
32+
33+
return refString.replace(/#\/(?:definitions|parameters)\//, '');
3034
}
3135

3236
/**
@@ -43,7 +47,7 @@ export function removeDuplicateWords(text: string): string {
4347
return text.replace(/^(.{3,})(?=\1)/ig, '');
4448
}
4549

46-
export function toTypescriptType({type, items}: Parameter): string {
50+
export function toTypescriptType(type: string | undefined, items: Parameter | undefined): string {
4751
if (!type) {
4852
return 'any';
4953
}
@@ -75,7 +79,7 @@ export function fileName(name: string = '', type: 'model' | 'enum' = 'model'): s
7579
return `${dashCase(name.replace(/model|enum/i, ''))}.${type}`;
7680
}
7781

78-
export function prefixImportedModels(type: string): string {
82+
export function prefixImportedModels(type: string = ''): string {
7983
return BASIC_TS_TYPE_REGEX.test(type) ? type : `models.${type}`;
8084
}
8185

src/parser.ts

Lines changed: 70 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,43 @@
11
import { Response, Schema, Spec as Swagger } from 'swagger-schema-official';
2-
import { Definition, MethodType, MustacheData, Parameter, Render, RenderFileName } from './types';
2+
import { Definition, Method, MethodType, MustacheData, Parameter, Property, Render, RenderFileName } from './types';
33
import { camelCase, dereferenceType, determineDomain, fileName, prefixImportedModels, toTypescriptType, typeName } from './helper';
44

5+
56
export function createMustacheViewModel(swagger: Swagger): MustacheData {
6-
const authorizedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
77
return {
88
description: swagger.info.description || '',
99
isSecure: !!swagger.securityDefinitions,
1010
swagger: swagger,
1111
domain: determineDomain(swagger),
12-
methods: [].concat.apply([], Object.entries(swagger.paths)
13-
.map(
14-
([path, api]) => Object.entries(api)
15-
.filter(([method, ]) => authorizedMethods.indexOf(method.toUpperCase()) !== -1) // skip unsupported methods
16-
.map(
17-
([method, op]) => ({
18-
path: path.replace(/({.*?})/g, '$$$1'),
19-
methodName: camelCase(
20-
op.operationId
21-
? op.operationId
22-
: console.error('Method name could not be determined, operationID is undefined')
23-
),
24-
methodType: <MethodType>method.toUpperCase(),
25-
summaryLines: op.description ? op.description.split('\n') : [], // description summary is optional
26-
isSecure: swagger.security !== undefined || op.security !== undefined,
27-
parameters: transformParameters(op.parameters),
28-
hasJsonResponse: true,
29-
response: prefixImportedModels(determineResponseType(op.responses)),
30-
})
31-
)
32-
)),
12+
methods: parseMethods(swagger),
3313
definitions: parseDefinitions(swagger.definitions)
3414
};
3515
}
3616

17+
function parseMethods({paths, security}: Swagger): Method[] {
18+
const authorizedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
19+
20+
return [].concat.apply([], Object.entries(paths)
21+
.map(([pathName, apiPath]) => Object.entries(apiPath)
22+
.filter(([methodType,]) => authorizedMethods.indexOf(methodType.toUpperCase()) !== -1) // skip unsupported methods
23+
.map(([methodType, operation]) => ({
24+
path: pathName.replace(/({.*?})/g, '$$$1'),
25+
methodName: camelCase(
26+
operation.operationId
27+
? operation.operationId
28+
: console.error(`Method name could not be determined, path will be used instead of operation id [ ${pathName} ]`)
29+
),
30+
methodType: methodType.toUpperCase() as MethodType,
31+
summaryLines: operation.description ? operation.description.split('\n') : [], // description summary is optional
32+
isSecure: security !== undefined || operation.security !== undefined,
33+
parameters: transformParameters(operation.parameters),
34+
hasJsonResponse: true,
35+
response: prefixImportedModels(determineResponseType(operation.responses)),
36+
})
37+
)
38+
));
39+
}
40+
3741
function parseDefinitions(definitions: { [definitionsName: string]: Schema } = {}): Definition[] {
3842
return Object.entries(definitions).map(([key, definition]) =>
3943
definition.enum && definition.enum.length !== 0
@@ -50,49 +54,51 @@ function defineEnum(enumSchema: (string | boolean | number | {})[], definitionKe
5054
})),
5155
isEnum: true,
5256
imports: [],
53-
renderFileName: (): RenderFileName => ((text: string, render: Render): string => fileName(render(text), 'enum')),
57+
renderFileName: (): RenderFileName => (text: string, render: Render): string => fileName(render(text), 'enum'),
5458
};
5559
}
5660

57-
function defineInterface(schema: Schema, definitionKey: string): Definition {
58-
const properties: Parameter[] = Object.entries<Schema>(schema.properties || {}).map(
59-
([propVal, propIn]: [string, Schema]) => {
60-
const property: Parameter = {
61-
name: propVal,
62-
isRef: '$ref' in propIn || (propIn.type === 'array' && propIn.items && '$ref' in propIn.items),
63-
isArray: propIn.type === 'array',
61+
function parseInterfaceProperties({properties}: Schema = {}): Property[] {
62+
return Object.entries<Schema>(properties || {}).map(
63+
([propName, propSchema]: [string, Schema]) => {
64+
const property: Property = {
65+
name: propName,
66+
isRef: '$ref' in propSchema || (propSchema.type === 'array' && propSchema.items && '$ref' in propSchema.items),
67+
isArray: propSchema.type === 'array',
6468
};
6569

66-
if (Array.isArray(propIn.items)) {
70+
if (Array.isArray(propSchema.items)) {
6771
console.warn('Arrays with type diversity are currently not supported');
6872
property.type = 'any';
6973

7074
return property;
7175
}
7276

7377
if (property.isArray) {
74-
if (propIn.items && propIn.items.$ref) {
75-
property.type = typeName(dereferenceType(propIn.items.$ref));
76-
} else if (propIn.items && propIn.items.type) {
77-
property.type = typeName(propIn.items.type);
78+
if (propSchema.items && propSchema.items.$ref) {
79+
property.type = typeName(dereferenceType(propSchema.items.$ref));
80+
} else if (propSchema.items && propSchema.items.type) {
81+
property.type = typeName(propSchema.items.type);
7882
} else {
79-
property.type = propIn.type;
83+
property.type = propSchema.type;
8084
}
8185
} else {
8286
property.type = typeName(
83-
propIn.$ref
84-
? dereferenceType(propIn.$ref)
85-
: propIn.type
87+
propSchema.$ref
88+
? dereferenceType(propSchema.$ref)
89+
: propSchema.type
8690
);
8791
}
8892

89-
property.typescriptType = toTypescriptType(property);
93+
property.typescriptType = toTypescriptType(property.type, property.items);
9094

9195
return property;
9296
}
93-
)
94-
.sort((a, b) => a.name && b.name ? a.name.localeCompare(b.name) : -1);
97+
).sort((a, b) => a.name && b.name ? a.name.localeCompare(b.name) : -1);
98+
}
9599

100+
function defineInterface(schema: Schema, definitionKey: string): Definition {
101+
const properties: Property[] = parseInterfaceProperties(schema.properties);
96102
const name = typeName(definitionKey);
97103

98104
return {
@@ -139,31 +145,28 @@ function transformParameters(parameters: Parameter[]): Parameter[] {
139145
return Array.isArray(parameters)
140146
// todo: required params
141147
? parameters.map((param) => {
142-
const parameter = {...param};
143-
144-
if ('schema' in param && typeof param.schema.$ref === 'string') {
145-
parameter.type = camelCase(dereferenceType(param.schema.$ref));
146-
}
148+
console.log('==>', param);
147149

148-
parameter.camelCaseName = camelCase(param.name);
149-
parameter.typescriptType = toTypescriptType(parameter);
150-
parameter.importType = prefixImportedModels(parameter.typescriptType);
151-
152-
if (param.in === 'body') {
153-
parameter.isBodyParameter = true;
154-
} else if (param.in === 'path') {
155-
// param is included in method path string interpolation
156-
parameter.isPathParameter = true;
157-
} else if (param.in === 'query' || param.in === 'modelbinding') {
158-
parameter.isQueryParameter = true;
159-
} else if (param.in === 'header') {
160-
parameter.isHeaderParameter = true;
161-
} else if (param.in === 'formData') {
162-
parameter.isFormParameter = true; // TODO: currently unsupported
163-
console.warn(`Form parameters are currently unsupported and will not be generated properly [ ${param.name} ]`);
164-
}
165-
166-
return parameter;
150+
const typescriptType = toTypescriptType(
151+
dereferenceType(param.$ref || ('schema' in param && param.schema.$ref)) || param.type,
152+
param.items
153+
);
154+
const camelCaseName = camelCase(param.name ? param.name : typescriptType); // if name is not defined, use type name
155+
156+
return {
157+
...param,
158+
159+
camelCaseName,
160+
importType: prefixImportedModels(typescriptType),
161+
isBodyParameter: param.in === 'body',
162+
isFormParameter: param.in === 'formData'
163+
? console.warn(`Form parameters are currently unsupported and will not be generated properly [ ${param.name} ]`) || true
164+
: false,
165+
isHeaderParameter: param.in === 'header',
166+
isPathParameter: param.in === 'path',
167+
isQueryParameter: param.in === 'query' || param.in === 'modelbinding',
168+
typescriptType,
169+
};
167170
}
168171
)
169172
: [];

src/types.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export type Render = (text: string) => string;
55

66
export interface Definition {
77
name?: string;
8-
properties: Parameter[];
8+
properties: Property[];
99
imports: string[];
1010
isEnum?: boolean;
1111
renderFileName?(): RenderFileName; // generate dash-case file names to templates
@@ -24,19 +24,23 @@ export type TypescriptBasicTypes = 'string' | 'number' | 'boolean' | 'undefined'
2424
export type In = 'body' | 'path' | 'query' | 'modelbinding' | 'header' | 'formData';
2525
export type MethodType = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
2626

27-
export interface Parameter {
28-
readonly camelCaseName?: string;
29-
readonly isArray?: boolean;
27+
export interface Parameter extends Property {
3028
readonly isBodyParameter?: boolean;
3129
readonly isFormParameter?: boolean;
3230
readonly isHeaderParameter?: boolean;
3331
readonly isPathParameter?: boolean;
34-
readonly isRef?: boolean;
3532
readonly isQueryParameter?: boolean;
33+
}
34+
35+
export interface Property {
36+
readonly camelCaseName?: string;
37+
readonly isArray?: boolean;
38+
readonly isRef?: boolean;
3639
readonly 'in'?: In;
3740
readonly 'enum'?: (string | boolean | number | {})[];
3841
readonly items?: Parameter;
3942
readonly name?: string;
43+
readonly $ref?: string;
4044
readonly schema?: any;
4145
type?: string;
4246
typescriptType?: TypescriptBasicTypes | string;

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"es2015",
88
"es2016",
99
"es2017",
10+
"es2017.object",
1011
"dom"
1112
],
1213
"noLib": false,

tslint.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
"deprecation": true,
4949
"eofline": true,
5050
"encoding": true,
51-
"forin": true,
5251
"import-spacing": true,
5352
"indent": [
5453
true,
@@ -185,7 +184,7 @@
185184
"semicolon": [
186185
true,
187186
"always",
188-
"ignore-bound-class-methods"
187+
"ignore-interfaces"
189188
],
190189
"space-before-function-paren": [
191190
true,

0 commit comments

Comments
 (0)