Skip to content

Commit

Permalink
feat(generator): option to skip guards
Browse files Browse the repository at this point in the history
closes #105
  • Loading branch information
vmasek committed Dec 21, 2020
1 parent 0e1a758 commit f4cc59f
Show file tree
Hide file tree
Showing 80 changed files with 2,028 additions and 82 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ This will download and save the swagger.yaml, and later on use the file to gener
| | specify what particular tags should be generated. Use `,` as the separator for multiple tags |
| | use `all` to emit all as a service per tag |
| `-m`/`--skipModule` | skip creating the index file with module export |
| `-g`/`--skipGuards` | skip creating type guards and guarded service |

<small>\* The author of the commit will be `api-client-generator <[email protected]>`.
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>
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@
"build": "npm run lint && rimraf dist && tsc",
"lint": "tslint -p tsconfig.json -c tslint.json src/**/*.ts",
"gen": "ts-node --files ./src/main.ts",
"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",
"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",
"gen:all:parallel": "concurrently --kill-others-on-fail \"npm:gen-*\"",
"gen-custom": "rimraf ./tests/custom/api && ts-node --files ./src/main.ts -s ./tests/custom/swagger.yaml -o ./tests/custom/api",
"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",
"gen-esquare": "rimraf ./tests/esquare/api && ts-node --files ./src/main.ts -s ./tests/esquare/swagger.yaml -o ./tests/esquare/api",
"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",
"gen-github": "rimraf ./tests/github/api && ts-node --files ./src/main.ts -s ./tests/github/swagger.yaml -o ./tests/github/api -t all",
Expand Down
44 changes: 26 additions & 18 deletions src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,28 +144,32 @@ export async function generateAPIClient(): Promise<string[]> {
{ ...apiTagData, templateType: 'interface' },
clientOutputPath,
),
generateFile(
compiledTemplates['service'],
`guarded-${apiTagData.serviceFileName}.ts`,
{ ...apiTagData, templateType: 'guardedService' },
clientOutputPath,
),
...(!GLOBAL_OPTIONS.skipModuleExport
? [
...(GLOBAL_OPTIONS.skipGuards
? []
: [
generateFile(
compiledTemplates['service'],
`guarded-${apiTagData.serviceFileName}.ts`,
{ ...apiTagData, templateType: 'guardedService' },
clientOutputPath,
),
]),
...(GLOBAL_OPTIONS.skipModuleExport
? []
: [
generateFile(
compiledTemplates['moduleExport'],
`index.ts`,
apiTagData,
{ ...apiTagData, options: GLOBAL_OPTIONS },
clientOutputPath,
),
]
: []),
]),
]);
}),
generateFile(
compiledTemplates['helperTypes'],
`types.ts`,
undefined,
{ options: GLOBAL_OPTIONS },
GLOBAL_OPTIONS.outputPath,
),
generateModels(
Expand All @@ -174,12 +178,16 @@ export async function generateAPIClient(): Promise<string[]> {
allDefinitions,
GLOBAL_OPTIONS.outputPath,
),
generateFile(
compiledTemplates['modelsGuards'],
`index.ts`,
{ definitions: allDefinitions },
join(GLOBAL_OPTIONS.outputPath, MODEL_GUARDS_DIR_NAME),
),
...(GLOBAL_OPTIONS.skipGuards
? []
: [
generateFile(
compiledTemplates['modelsGuards'],
`index.ts`,
{ definitions: allDefinitions },
join(GLOBAL_OPTIONS.outputPath, MODEL_GUARDS_DIR_NAME),
),
]),
]);
}

Expand Down
2 changes: 1 addition & 1 deletion src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function guardOptional(

export function guardDictionary(
name: string,
guard: (name: string) => string,
guard: (name: string) => string | undefined,
): string {
return `Object.values(${name}).every((value: any) => ${guard('value')})`;
}
Expand Down
3 changes: 3 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const optimist = opt
.alias('C', 'commit')
.alias('v', 'verbose')
.alias('t', 'splitPathTags')
.alias('g', 'skipGuards')
.alias('m', 'skipModule')
.describe('s', 'Path to the swagger file')
.describe('o', 'Path where generated files should be emitted')
Expand All @@ -25,6 +26,7 @@ const optimist = opt
'Generates services and models only for the specified tags.' +
' Use `,` (comma) as the separator for multiple tags. Use `all` to emit a service per tag',
)
.describe('g', 'Skip creating type guards and guarded client')
.describe('m', 'Skip creating index file with module export');

const argv = optimist.argv;
Expand All @@ -45,6 +47,7 @@ export const GLOBAL_OPTIONS = {
splitPathTags:
'splitPathTags' in argv ? (argv.splitPathTags || 'all').split(',') : [],
skipModuleExport: argv.skipModule === true || argv.skipModule === 'true',
skipGuards: argv.skipGuards,
} as const;

const generate: typeof generateAPIClient = argv.commit
Expand Down
59 changes: 32 additions & 27 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Parameter as SwaggerParameter,
Reference,
} from 'swagger-schema-official';
import { GLOBAL_OPTIONS } from './main';
import {
Definition,
Method,
Expand Down Expand Up @@ -347,7 +348,7 @@ function parseInterfaceProperties(

const propertyAllOf = propSchema.allOf?.length
? propSchema.allOf.map(allOfItemSchema =>
parseSchema(allOfItemSchema, false, {
parseSchema(allOfItemSchema, {
name: accessProp(name),
isRequired: true,
}),
Expand All @@ -356,7 +357,7 @@ function parseInterfaceProperties(

const isRequired = requiredProps.includes(propName);

const parsedSchema = parseSchema(propSchema, false, {
const parsedSchema = parseSchema(propSchema, {
name: name === ADDITIONAL_PROPERTIES_KEY ? 'value' : accessProp(name),
isRequired,
});
Expand Down Expand Up @@ -390,7 +391,9 @@ function parseInterfaceProperties(
),
};

const propGuard = propertyAllOf.length
const propGuard = GLOBAL_OPTIONS.skipGuards
? undefined
: propertyAllOf.length
? guardOptional(
accessProp(name),
false,
Expand All @@ -404,18 +407,18 @@ function parseInterfaceProperties(

return {
...property,
guard:
name === ADDITIONAL_PROPERTIES_KEY
? guardDictionary('arg', () => propGuard)
: propGuard,
guard: GLOBAL_OPTIONS.skipGuards
? undefined
: name === ADDITIONAL_PROPERTIES_KEY
? guardDictionary('arg', () => propGuard)
: propGuard,
};
})
.sort(compareStringByKey('name')); // tslint:disable-line:no-array-mutation
}

function parseSchema(
property: Schema,
skipGuards: boolean,
{
name,
isRequired,
Expand All @@ -436,7 +439,7 @@ function parseSchema(
return {
type: `(${enumValues.join(' | ')})`,
imports: [],
guard: skipGuards
guard: GLOBAL_OPTIONS.skipGuards
? undefined
: () =>
guardOptional(
Expand All @@ -452,12 +455,14 @@ function parseSchema(
return {
type: 'object',
imports: [],
guard: () =>
guardOptional(
name,
isRequired,
(iterName: string) => `typeof ${iterName} === 'object'`,
),
guard: GLOBAL_OPTIONS.skipGuards
? undefined
: () =>
guardOptional(
name,
isRequired,
(iterName: string) => `typeof ${iterName} === 'object'`,
),
}; // type occurrence of inlined properties as object instead of any (TODO: consider supporting inlined properties)
}

Expand All @@ -467,7 +472,7 @@ function parseSchema(
return {
type: refType,
imports: [refType],
guard: skipGuards
guard: GLOBAL_OPTIONS.skipGuards
? undefined
: () =>
guardOptional(
Expand All @@ -480,7 +485,7 @@ function parseSchema(
}

if (property.items) {
const parsedArrayItemsSchema = parseSchema(property.items, skipGuards, {
const parsedArrayItemsSchema = parseSchema(property.items, {
name: 'item',
isRequired: true,
prefixGuards,
Expand All @@ -490,7 +495,7 @@ function parseSchema(
type: `${parsedArrayItemsSchema.type}[]`,
imports: parsedArrayItemsSchema.imports,
guard:
skipGuards || !parsedArrayItemsSchema.guard
GLOBAL_OPTIONS.skipGuards || !parsedArrayItemsSchema.guard
? undefined
: () =>
guardOptional(name, isRequired, (iterName: string) =>
Expand All @@ -502,7 +507,6 @@ function parseSchema(
if (property.additionalProperties) {
const parsedDictionarySchema = parseSchema(
property.additionalProperties as Schema,
skipGuards,
{ name: 'value', isRequired: true, prefixGuards },
);

Expand All @@ -514,7 +518,7 @@ function parseSchema(
: `{ ${ADDITIONAL_PROPERTIES_KEY}: ${parsedDictionarySchema.type} }`,
imports: parsedDictionarySchema.imports,
guard:
skipGuards || !parsedDictionarySchema.guard
GLOBAL_OPTIONS.skipGuards || !parsedDictionarySchema.guard
? undefined
: () =>
guardOptional(name, isRequired, (iterName: string) =>
Expand All @@ -530,7 +534,7 @@ function parseSchema(
return {
type,
imports: [],
guard: skipGuards
guard: GLOBAL_OPTIONS.skipGuards
? undefined
: () =>
type === 'any'
Expand Down Expand Up @@ -603,7 +607,7 @@ function determineResponseType(response: Response): ParsedSchema {

const nullable =
(schema as Schema & { 'x-nullable'?: boolean })['x-nullable'] || false;
const responseSchema = parseSchema(schema, false, {
const responseSchema = parseSchema(schema, {
name: 'res',
isRequired: true,
prefixGuards: true,
Expand All @@ -617,10 +621,12 @@ function determineResponseType(response: Response): ParsedSchema {
return {
...responseSchema,
type: nullable ? `(${type}) | null` : type,
guard: () =>
nullable
? `(res == null || ${responseSchema.guard?.('')})`
: responseSchema.guard?.('') || '',
guard: GLOBAL_OPTIONS.skipGuards
? undefined
: () =>
nullable
? `(res == null || ${responseSchema.guard?.('')})`
: responseSchema.guard?.('') || '',
};
}

Expand Down Expand Up @@ -660,7 +666,6 @@ function transformParameters(
// tslint:disable-next-line:no-any
(derefParam as any),

false,
{
name,
isRequired: derefParam.required,
Expand Down
4 changes: 2 additions & 2 deletions templates/ngx-helper-types.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ export interface HttpOptions {
export interface APIClientModuleConfig {

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

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

/** Default HTTP options configuration for the API client service */
httpOptions?: DefaultHttpOptions;
Expand Down
13 changes: 6 additions & 7 deletions templates/ngx-module-export.handlebars
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
{{> header}}

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

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

@NgModule({})
export class {{&serviceName}}Module {
Expand All @@ -23,8 +22,8 @@ export class {{&serviceName}}Module {
ngModule: {{&serviceName}}Module,
providers: [
...(config.domain != null ? [{provide: USE_DOMAIN, useValue: config.domain}] : []),
...(config.httpOptions ? [{provide: USE_HTTP_OPTIONS, useValue: config.httpOptions}] : []),
...(config.guardResponses ? [{provide: {{&serviceName}}, useClass: Guarded{{&serviceName}} }] : [{{&serviceName}}]),
...(config.httpOptions ? [{provide: USE_HTTP_OPTIONS, useValue: config.httpOptions}] : []),{{#unless options.skipGuards}}
...(config.guardResponses ? [{provide: {{&serviceName}}, useClass: Guarded{{&serviceName}} }] : [{{&serviceName}}]),{{/unless}}
]
};
}
Expand Down
Loading

0 comments on commit f4cc59f

Please sign in to comment.