Skip to content

Commit

Permalink
feat(generator-schema): handle combined enums
Browse files Browse the repository at this point in the history
  • Loading branch information
anymaniax committed Nov 12, 2020
1 parent e5d06f9 commit ad11d0f
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 105 deletions.
26 changes: 17 additions & 9 deletions src/core/generators/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SchemaObject } from 'openapi3-ts';
import { SchemaObject, SchemasObject } from 'openapi3-ts';
import { generalJSTypesWithArray } from '../../constants';
import { pascal } from '../../utils/case';
import { generalTypesFilter } from '../../utils/filters';
Expand All @@ -11,28 +11,36 @@ import { getScalar } from '../getters/scalar';
* @param name interface name
* @param schema
*/
export const generateInterface = (name: string, schema: SchemaObject) => {
const { value, imports, schemas } = getScalar(schema, name);
const isEmptyObject = value === '{}';
export const generateInterface = ({
name,
schema,
schemas,
}: {
name: string;
schema: SchemaObject;
schemas: SchemasObject;
}) => {
const scalar = getScalar(schema, name, schemas);
const isEmptyObject = scalar.value === '{}';
const definitionName = pascal(name);

let model = isEmptyObject
? '// tslint:disable-next-line:no-empty-interface\n'
: '';

if (!generalJSTypesWithArray.includes(value)) {
model += `export interface ${definitionName} ${value}\n`;
if (!generalJSTypesWithArray.includes(scalar.value)) {
model += `export interface ${definitionName} ${scalar.value}\n`;
} else {
model += `export type ${definitionName} = ${value};\n`;
model += `export type ${definitionName} = ${scalar.value};\n`;
}

// Filter out imports that refer to the type defined in current file (OpenAPI recursive schema definitions)
const externalModulesImportsOnly = imports.filter(
const externalModulesImportsOnly = scalar.imports.filter(
(importName) => importName !== definitionName,
);

return [
...schemas,
...scalar.schemas,
{
name: definitionName,
model,
Expand Down
41 changes: 20 additions & 21 deletions src/core/generators/schemaDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import isEmpty from 'lodash/isEmpty';
import { SchemaObject } from 'openapi3-ts';
import { SchemasObject } from 'openapi3-ts';
import { GeneratorSchema } from '../../types/generator';
import { pascal, upper } from '../../utils/case';
import { generalTypesFilter } from '../../utils/filters';
Expand All @@ -14,7 +14,7 @@ import { generateInterface } from './interface';
* @param schemas
*/
export const generateSchemasDefinition = (
schemas: SchemaObject = {},
schemas: SchemasObject = {},
): Array<GeneratorSchema> => {
if (isEmpty(schemas)) {
return [];
Expand All @@ -29,27 +29,26 @@ export const generateSchemasDefinition = (
!isReference(schema) &&
!schema.nullable
) {
return [...acc, ...generateInterface(name, schema)];
return [...acc, ...generateInterface({ name, schema, schemas })];
} else {
const { value, imports, isEnum, type, schemas = [] } = resolveValue(
schema,
name,
);
const resolvedValue = resolveValue({ schema, name, schemas });

let output = '';
output += `export type ${pascal(name)} = ${value};\n`;
output += `export type ${pascal(name)} = ${resolvedValue.value};\n`;

if (isEnum) {
const implementation = value.split(' | ').reduce((acc, val) => {
return (
acc +
` ${
type === 'number'
? `${upper(type)}_${val}`
: sanitize(val, false)
}: ${val} as ${pascal(name)},\n`
);
}, '');
if (resolvedValue.isEnum) {
const implementation = resolvedValue.value
.split(' | ')
.reduce((acc, val) => {
return (
acc +
` ${
resolvedValue.type === 'number'
? `${upper(resolvedValue.type)}_${val}`
: sanitize(val, false)
}: ${val} as ${pascal(name)},\n`
);
}, '');

output += `\n\nexport const ${pascal(
name,
Expand All @@ -58,11 +57,11 @@ export const generateSchemasDefinition = (

return [
...acc,
...schemas,
...resolvedValue.schemas,
{
name: pascal(name),
model: output,
imports: generalTypesFilter(imports),
imports: generalTypesFilter(resolvedValue.imports),
},
];
}
Expand Down
29 changes: 19 additions & 10 deletions src/core/getters/array.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SchemaObject } from 'openapi3-ts';
import { SchemaObject, SchemasObject } from 'openapi3-ts';
import { ResolverValue } from '../../types/resolvers';
import { resolveObject } from '../resolvers/object';

Expand All @@ -7,16 +7,25 @@ import { resolveObject } from '../resolvers/object';
*
* @param item item with type === "array"
*/
export const getArray = (item: SchemaObject, name?: string): ResolverValue => {
if (item.items) {
const { value, imports, schemas } = resolveObject(
item.items,
name + 'Item',
);
return {
value: `${value}[]`,
imports,
export const getArray = ({
schema,
name,
schemas,
}: {
schema: SchemaObject;
name?: string;
schemas: SchemasObject;
}): ResolverValue => {
if (schema.items) {
const resolvedObject = resolveObject({
schema: schema.items,
propName: name + 'Item',
schemas,
});
return {
value: `${resolvedObject.value}[]`,
imports: resolvedObject.imports,
schemas: resolvedObject.schemas,
isEnum: false,
type: 'array',
};
Expand Down
53 changes: 53 additions & 0 deletions src/core/getters/combine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ReferenceObject, SchemaObject, SchemasObject } from 'openapi3-ts';
import { ResolverValue } from '../../types/resolvers';
import { pascal } from '../../utils/case';
import { resolveObject } from '../resolvers/object';

export const combineSchemas = ({
name,
items,
schemas,
separator,
}: {
name?: string;
items: (SchemaObject | ReferenceObject)[];
schemas: SchemasObject;
separator: string;
}) => {
const resolvedData = items.reduce<ResolverValue>(
(acc, schema) => {
const propName = name ? name + 'Data' : undefined;
const resolvedValue = resolveObject({
schema,
propName,
schemas,
combined: true,
});
return {
...acc,
value: acc.value
? `${acc.value} ${separator} ${resolvedValue.value}`
: resolvedValue.value,
imports: [...acc.imports, ...resolvedValue.imports],
schemas: [...acc.schemas, ...resolvedValue.schemas],
isEnum: !acc.isEnum ? acc.isEnum : resolvedValue.isEnum,
};
},
{ value: '', imports: [], schemas: [], isEnum: true, type: 'object' },
);

if (resolvedData.isEnum && name) {
const enums = resolvedData.value
.split(' | ')
.map((e) => `...${e}`)
.join(',');
const newEnum = `\n\nexport const ${pascal(name)} = {${enums}}`;
return {
...resolvedData,
value: resolvedData.value + newEnum,
isEnum: false,
};
}

return resolvedData;
};
65 changes: 23 additions & 42 deletions src/core/getters/object.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ReferenceObject, SchemaObject } from 'openapi3-ts';
import { ReferenceObject, SchemaObject, SchemasObject } from 'openapi3-ts';
import { ResolverValue } from '../../types/resolvers';
import { pascal } from '../../utils/case';
import { isBoolean, isReference } from '../../utils/is';
import { resolveObject } from '../resolvers/object';
import { resolveValue } from '../resolvers/value';
import { combineSchemas } from './combine';
import { getKey } from './keys';
import { getRef } from './ref';

Expand All @@ -12,7 +13,11 @@ import { getRef } from './ref';
*
* @param item item with type === "object"
*/
export const getObject = (item: SchemaObject, name?: string): ResolverValue => {
export const getObject = (
item: SchemaObject,
name?: string,
schemas: SchemasObject = {},
): ResolverValue => {
if (isReference(item)) {
const value = getRef(item.$ref);
return {
Expand All @@ -25,54 +30,29 @@ export const getObject = (item: SchemaObject, name?: string): ResolverValue => {
}

if (item.allOf) {
return item.allOf.reduce<ResolverValue>(
(acc, val) => {
const propName = name ? name + 'Data' : undefined;
const resolvedValue = resolveObject(val, propName);

return {
...acc,
value: acc.value
? `${acc.value} & ${resolvedValue.value}`
: resolvedValue.value,
imports: [...acc.imports, ...resolvedValue.imports],
schemas: [...acc.schemas, ...resolvedValue.schemas],
};
},
{ value: '', imports: [], schemas: [], isEnum: false, type: 'object' },
);
return combineSchemas({ items: item.allOf, name, schemas, separator: '&' });
}

if (item.oneOf) {
return item.oneOf.reduce<ResolverValue>(
(acc, val) => {
const propName = name ? name + 'Data' : undefined;
const resolvedValue = resolveObject(val, propName);
return {
...acc,
value: acc.value
? `${acc.value} | ${resolvedValue.value}`
: resolvedValue.value,
imports: [...acc.imports, ...resolvedValue.imports],
schemas: [...acc.schemas, ...resolvedValue.schemas],
};
},
{ value: '', imports: [], schemas: [], isEnum: false, type: 'object' },
);
return combineSchemas({ items: item.oneOf, name, schemas, separator: '|' });
}

if (item.anyOf) {
return combineSchemas({ items: item.anyOf, name, schemas, separator: '|' });
}

if (item.properties) {
return Object.entries(item.properties).reduce<ResolverValue>(
(
acc,
[key, prop]: [string, ReferenceObject | SchemaObject],
[key, schema]: [string, ReferenceObject | SchemaObject],
index,
arr,
) => {
const isRequired = (item.required || []).includes(key);
const propName = name ? name + pascal(key) : undefined;
const resolvedValue = resolveObject(prop, propName);
const isReadOnly = item.readOnly || (prop as SchemaObject).readOnly;
const resolvedValue = resolveObject({ schema, propName, schemas });
const isReadOnly = item.readOnly || (schema as SchemaObject).readOnly;
if (!index) {
acc.value += '{';
}
Expand Down Expand Up @@ -108,14 +88,15 @@ export const getObject = (item: SchemaObject, name?: string): ResolverValue => {
type: 'object',
};
}
const { value, imports = [], schemas = [] } = resolveValue(
item.additionalProperties,
const resolvedValue = resolveValue({
schema: item.additionalProperties,
name,
);
return {
value: `{[key: string]: ${value}}`,
imports,
schemas,
});
return {
value: `{[key: string]: ${resolvedValue.value}}`,
imports: resolvedValue.imports || [],
schemas: resolvedValue.schemas || [],
isEnum: false,
type: 'object',
};
Expand Down
2 changes: 1 addition & 1 deletion src/core/getters/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const getParams = ({

const name = sanitize(nameWithoutSanitize);

const resolvedValue = resolveValue(schema);
const resolvedValue = resolveValue({ schema });

const definition = `${name}${!required || schema.default ? '?' : ''}: ${
resolvedValue.value
Expand Down
2 changes: 1 addition & 1 deletion src/core/getters/queryParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const getQueryParamsTypes = (
schema: SchemaObject;
};

const { value, imports, isEnum, type } = resolveValue(schema!);
const { value, imports, isEnum, type } = resolveValue({ schema: schema! });

const key = getKey(name);

Expand Down
4 changes: 2 additions & 2 deletions src/core/getters/resReqTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const CONTENT_TYPES = [
const getResReqContentTypes = (
type: string,
mediaType: MediaTypeObject,
name?: string,
propName?: string,
) => {
if (!CONTENT_TYPES.includes(type) || !mediaType.schema) {
return {
Expand All @@ -33,7 +33,7 @@ const getResReqContentTypes = (
};
}

return resolveObject(mediaType.schema, name);
return resolveObject({ schema: mediaType.schema, propName });
};

/**
Expand Down

0 comments on commit ad11d0f

Please sign in to comment.