Skip to content

Commit f4cc59f

Browse files
committed
feat(generator): option to skip guards
closes #105
1 parent 0e1a758 commit f4cc59f

File tree

80 files changed

+2028
-82
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+2028
-82
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ This will download and save the swagger.yaml, and later on use the file to gener
117117
| | specify what particular tags should be generated. Use `,` as the separator for multiple tags |
118118
| | use `all` to emit all as a service per tag |
119119
| `-m`/`--skipModule` | skip creating the index file with module export |
120+
| `-g`/`--skipGuards` | skip creating type guards and guarded service |
120121

121122
<small>\* The author of the commit will be `api-client-generator <[email protected]>`.
122123
If there are any staged changes in your repository, the generator will halt pre-generation with an error to prevent including your changes in the automatic commit.\*</small>

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@
5454
"build": "npm run lint && rimraf dist && tsc",
5555
"lint": "tslint -p tsconfig.json -c tslint.json src/**/*.ts",
5656
"gen": "ts-node --files ./src/main.ts",
57-
"gen:all": "npm run gen-custom && npm run gen-esquare && npm run gen-gcloud-firestore && npm run gen-github && npm run gen-filtered-api && npm run gen-pet-store && npm run gen-with-all-tags",
57+
"gen:all": "npm run gen-custom && npm run gen-custom-without-guards && npm run gen-esquare && npm run gen-gcloud-firestore && npm run gen-github && npm run gen-filtered-api && npm run gen-pet-store && npm run gen-with-all-tags",
5858
"gen:all:parallel": "concurrently --kill-others-on-fail \"npm:gen-*\"",
5959
"gen-custom": "rimraf ./tests/custom/api && ts-node --files ./src/main.ts -s ./tests/custom/swagger.yaml -o ./tests/custom/api",
60+
"gen-custom-without-guards": "rimraf ./tests/custom-without-guards/api && ts-node --files ./src/main.ts -s ./tests/custom/swagger.yaml -o ./tests/custom-without-guards/api -g",
6061
"gen-esquare": "rimraf ./tests/esquare/api && ts-node --files ./src/main.ts -s ./tests/esquare/swagger.yaml -o ./tests/esquare/api",
6162
"gen-gcloud-firestore": "rimraf ./tests/gcloud-firestore/api && ts-node --files ./src/main.ts -s ./tests/gcloud-firestore/swagger.yaml -o ./tests/gcloud-firestore/api",
6263
"gen-github": "rimraf ./tests/github/api && ts-node --files ./src/main.ts -s ./tests/github/swagger.yaml -o ./tests/github/api -t all",

src/generator.ts

+26-18
Original file line numberDiff line numberDiff line change
@@ -144,28 +144,32 @@ export async function generateAPIClient(): Promise<string[]> {
144144
{ ...apiTagData, templateType: 'interface' },
145145
clientOutputPath,
146146
),
147-
generateFile(
148-
compiledTemplates['service'],
149-
`guarded-${apiTagData.serviceFileName}.ts`,
150-
{ ...apiTagData, templateType: 'guardedService' },
151-
clientOutputPath,
152-
),
153-
...(!GLOBAL_OPTIONS.skipModuleExport
154-
? [
147+
...(GLOBAL_OPTIONS.skipGuards
148+
? []
149+
: [
150+
generateFile(
151+
compiledTemplates['service'],
152+
`guarded-${apiTagData.serviceFileName}.ts`,
153+
{ ...apiTagData, templateType: 'guardedService' },
154+
clientOutputPath,
155+
),
156+
]),
157+
...(GLOBAL_OPTIONS.skipModuleExport
158+
? []
159+
: [
155160
generateFile(
156161
compiledTemplates['moduleExport'],
157162
`index.ts`,
158-
apiTagData,
163+
{ ...apiTagData, options: GLOBAL_OPTIONS },
159164
clientOutputPath,
160165
),
161-
]
162-
: []),
166+
]),
163167
]);
164168
}),
165169
generateFile(
166170
compiledTemplates['helperTypes'],
167171
`types.ts`,
168-
undefined,
172+
{ options: GLOBAL_OPTIONS },
169173
GLOBAL_OPTIONS.outputPath,
170174
),
171175
generateModels(
@@ -174,12 +178,16 @@ export async function generateAPIClient(): Promise<string[]> {
174178
allDefinitions,
175179
GLOBAL_OPTIONS.outputPath,
176180
),
177-
generateFile(
178-
compiledTemplates['modelsGuards'],
179-
`index.ts`,
180-
{ definitions: allDefinitions },
181-
join(GLOBAL_OPTIONS.outputPath, MODEL_GUARDS_DIR_NAME),
182-
),
181+
...(GLOBAL_OPTIONS.skipGuards
182+
? []
183+
: [
184+
generateFile(
185+
compiledTemplates['modelsGuards'],
186+
`index.ts`,
187+
{ definitions: allDefinitions },
188+
join(GLOBAL_OPTIONS.outputPath, MODEL_GUARDS_DIR_NAME),
189+
),
190+
]),
183191
]);
184192
}
185193

src/helper.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export function guardOptional(
9898

9999
export function guardDictionary(
100100
name: string,
101-
guard: (name: string) => string,
101+
guard: (name: string) => string | undefined,
102102
): string {
103103
return `Object.values(${name}).every((value: any) => ${guard('value')})`;
104104
}

src/main.ts

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const optimist = opt
1515
.alias('C', 'commit')
1616
.alias('v', 'verbose')
1717
.alias('t', 'splitPathTags')
18+
.alias('g', 'skipGuards')
1819
.alias('m', 'skipModule')
1920
.describe('s', 'Path to the swagger file')
2021
.describe('o', 'Path where generated files should be emitted')
@@ -25,6 +26,7 @@ const optimist = opt
2526
'Generates services and models only for the specified tags.' +
2627
' Use `,` (comma) as the separator for multiple tags. Use `all` to emit a service per tag',
2728
)
29+
.describe('g', 'Skip creating type guards and guarded client')
2830
.describe('m', 'Skip creating index file with module export');
2931

3032
const argv = optimist.argv;
@@ -45,6 +47,7 @@ export const GLOBAL_OPTIONS = {
4547
splitPathTags:
4648
'splitPathTags' in argv ? (argv.splitPathTags || 'all').split(',') : [],
4749
skipModuleExport: argv.skipModule === true || argv.skipModule === 'true',
50+
skipGuards: argv.skipGuards,
4851
} as const;
4952

5053
const generate: typeof generateAPIClient = argv.commit

src/parser.ts

+32-27
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Parameter as SwaggerParameter,
88
Reference,
99
} from 'swagger-schema-official';
10+
import { GLOBAL_OPTIONS } from './main';
1011
import {
1112
Definition,
1213
Method,
@@ -347,7 +348,7 @@ function parseInterfaceProperties(
347348

348349
const propertyAllOf = propSchema.allOf?.length
349350
? propSchema.allOf.map(allOfItemSchema =>
350-
parseSchema(allOfItemSchema, false, {
351+
parseSchema(allOfItemSchema, {
351352
name: accessProp(name),
352353
isRequired: true,
353354
}),
@@ -356,7 +357,7 @@ function parseInterfaceProperties(
356357

357358
const isRequired = requiredProps.includes(propName);
358359

359-
const parsedSchema = parseSchema(propSchema, false, {
360+
const parsedSchema = parseSchema(propSchema, {
360361
name: name === ADDITIONAL_PROPERTIES_KEY ? 'value' : accessProp(name),
361362
isRequired,
362363
});
@@ -390,7 +391,9 @@ function parseInterfaceProperties(
390391
),
391392
};
392393

393-
const propGuard = propertyAllOf.length
394+
const propGuard = GLOBAL_OPTIONS.skipGuards
395+
? undefined
396+
: propertyAllOf.length
394397
? guardOptional(
395398
accessProp(name),
396399
false,
@@ -404,18 +407,18 @@ function parseInterfaceProperties(
404407

405408
return {
406409
...property,
407-
guard:
408-
name === ADDITIONAL_PROPERTIES_KEY
409-
? guardDictionary('arg', () => propGuard)
410-
: propGuard,
410+
guard: GLOBAL_OPTIONS.skipGuards
411+
? undefined
412+
: name === ADDITIONAL_PROPERTIES_KEY
413+
? guardDictionary('arg', () => propGuard)
414+
: propGuard,
411415
};
412416
})
413417
.sort(compareStringByKey('name')); // tslint:disable-line:no-array-mutation
414418
}
415419

416420
function parseSchema(
417421
property: Schema,
418-
skipGuards: boolean,
419422
{
420423
name,
421424
isRequired,
@@ -436,7 +439,7 @@ function parseSchema(
436439
return {
437440
type: `(${enumValues.join(' | ')})`,
438441
imports: [],
439-
guard: skipGuards
442+
guard: GLOBAL_OPTIONS.skipGuards
440443
? undefined
441444
: () =>
442445
guardOptional(
@@ -452,12 +455,14 @@ function parseSchema(
452455
return {
453456
type: 'object',
454457
imports: [],
455-
guard: () =>
456-
guardOptional(
457-
name,
458-
isRequired,
459-
(iterName: string) => `typeof ${iterName} === 'object'`,
460-
),
458+
guard: GLOBAL_OPTIONS.skipGuards
459+
? undefined
460+
: () =>
461+
guardOptional(
462+
name,
463+
isRequired,
464+
(iterName: string) => `typeof ${iterName} === 'object'`,
465+
),
461466
}; // type occurrence of inlined properties as object instead of any (TODO: consider supporting inlined properties)
462467
}
463468

@@ -467,7 +472,7 @@ function parseSchema(
467472
return {
468473
type: refType,
469474
imports: [refType],
470-
guard: skipGuards
475+
guard: GLOBAL_OPTIONS.skipGuards
471476
? undefined
472477
: () =>
473478
guardOptional(
@@ -480,7 +485,7 @@ function parseSchema(
480485
}
481486

482487
if (property.items) {
483-
const parsedArrayItemsSchema = parseSchema(property.items, skipGuards, {
488+
const parsedArrayItemsSchema = parseSchema(property.items, {
484489
name: 'item',
485490
isRequired: true,
486491
prefixGuards,
@@ -490,7 +495,7 @@ function parseSchema(
490495
type: `${parsedArrayItemsSchema.type}[]`,
491496
imports: parsedArrayItemsSchema.imports,
492497
guard:
493-
skipGuards || !parsedArrayItemsSchema.guard
498+
GLOBAL_OPTIONS.skipGuards || !parsedArrayItemsSchema.guard
494499
? undefined
495500
: () =>
496501
guardOptional(name, isRequired, (iterName: string) =>
@@ -502,7 +507,6 @@ function parseSchema(
502507
if (property.additionalProperties) {
503508
const parsedDictionarySchema = parseSchema(
504509
property.additionalProperties as Schema,
505-
skipGuards,
506510
{ name: 'value', isRequired: true, prefixGuards },
507511
);
508512

@@ -514,7 +518,7 @@ function parseSchema(
514518
: `{ ${ADDITIONAL_PROPERTIES_KEY}: ${parsedDictionarySchema.type} }`,
515519
imports: parsedDictionarySchema.imports,
516520
guard:
517-
skipGuards || !parsedDictionarySchema.guard
521+
GLOBAL_OPTIONS.skipGuards || !parsedDictionarySchema.guard
518522
? undefined
519523
: () =>
520524
guardOptional(name, isRequired, (iterName: string) =>
@@ -530,7 +534,7 @@ function parseSchema(
530534
return {
531535
type,
532536
imports: [],
533-
guard: skipGuards
537+
guard: GLOBAL_OPTIONS.skipGuards
534538
? undefined
535539
: () =>
536540
type === 'any'
@@ -603,7 +607,7 @@ function determineResponseType(response: Response): ParsedSchema {
603607

604608
const nullable =
605609
(schema as Schema & { 'x-nullable'?: boolean })['x-nullable'] || false;
606-
const responseSchema = parseSchema(schema, false, {
610+
const responseSchema = parseSchema(schema, {
607611
name: 'res',
608612
isRequired: true,
609613
prefixGuards: true,
@@ -617,10 +621,12 @@ function determineResponseType(response: Response): ParsedSchema {
617621
return {
618622
...responseSchema,
619623
type: nullable ? `(${type}) | null` : type,
620-
guard: () =>
621-
nullable
622-
? `(res == null || ${responseSchema.guard?.('')})`
623-
: responseSchema.guard?.('') || '',
624+
guard: GLOBAL_OPTIONS.skipGuards
625+
? undefined
626+
: () =>
627+
nullable
628+
? `(res == null || ${responseSchema.guard?.('')})`
629+
: responseSchema.guard?.('') || '',
624630
};
625631
}
626632

@@ -660,7 +666,6 @@ function transformParameters(
660666
// tslint:disable-next-line:no-any
661667
(derefParam as any),
662668

663-
false,
664669
{
665670
name,
666671
isRequired: derefParam.required,

templates/ngx-helper-types.handlebars

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ export interface HttpOptions {
4444
export interface APIClientModuleConfig {
4545

4646
/** Domain URL that will be used as the base for all of the API client service requests */
47-
domain?: string;
47+
domain?: string;{{#unless options.skipGuards}}
4848

4949
/** Flag to enable/disable response type guards */
50-
guardResponses?: boolean; // validate responses with type guards
50+
guardResponses?: boolean; // validate responses with type guards{{/unless}}
5151

5252
/** Default HTTP options configuration for the API client service */
5353
httpOptions?: DefaultHttpOptions;

templates/ngx-module-export.handlebars

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
{{> header}}
22

33
import { NgModule, ModuleWithProviders } from '@angular/core';
4-
import { {{&serviceName}}, USE_DOMAIN, USE_HTTP_OPTIONS } from './{{&serviceFileName}}';
5-
import { Guarded{{&serviceName}} } from './guarded-{{&serviceFileName}}';
4+
import { {{#unless options.skipGuards}}{{&serviceName}}, {{/unless}}USE_DOMAIN, USE_HTTP_OPTIONS } from './{{&serviceFileName}}';{{#unless options.skipGuards}}
5+
import { Guarded{{&serviceName}} } from './guarded-{{&serviceFileName}}';{{/unless}}
66
import { APIClientModuleConfig } from '{{#if serviceTag}}../.{{/if}}./types';
77

8-
export { {{&serviceName}} } from './{{&serviceFileName}}';
9-
export { {{&interfaceName}} } from './{{&interfaceFileName}}';
10-
export { Guarded{{&serviceName}} } from './guarded-{{&serviceFileName}}';
8+
export { {{&interfaceName}} } from './{{&interfaceFileName}}';{{#unless options.skipGuards}}
9+
export { Guarded{{&serviceName}} } from './guarded-{{&serviceFileName}}';{{/unless}}
1110

1211
@NgModule({})
1312
export class {{&serviceName}}Module {
@@ -23,8 +22,8 @@ export class {{&serviceName}}Module {
2322
ngModule: {{&serviceName}}Module,
2423
providers: [
2524
...(config.domain != null ? [{provide: USE_DOMAIN, useValue: config.domain}] : []),
26-
...(config.httpOptions ? [{provide: USE_HTTP_OPTIONS, useValue: config.httpOptions}] : []),
27-
...(config.guardResponses ? [{provide: {{&serviceName}}, useClass: Guarded{{&serviceName}} }] : [{{&serviceName}}]),
25+
...(config.httpOptions ? [{provide: USE_HTTP_OPTIONS, useValue: config.httpOptions}] : []),{{#unless options.skipGuards}}
26+
...(config.guardResponses ? [{provide: {{&serviceName}}, useClass: Guarded{{&serviceName}} }] : [{{&serviceName}}]),{{/unless}}
2827
]
2928
};
3029
}

0 commit comments

Comments
 (0)