wip: Update entities generator

This commit is contained in:
Vlad
2021-02-25 17:40:38 +03:00
parent e31c0c456a
commit 1453c27d87
5 changed files with 405 additions and 441 deletions

View File

@@ -4,15 +4,16 @@ import { OPEN_API_PATH } from '../consts';
import EntitiesGenerator from './src/generateEntities';
import ApisGenerator from './src/generateApis';
import { OpenApi } from './src/utils';
const generateApi = (openApi: Record<string, any>) => {
const generateApi = (openApi: OpenApi) => {
const ent = new EntitiesGenerator(openApi);
ent.save();
const api = new ApisGenerator(openApi);
api.save();
// const api = new ApisGenerator(openApi);
// api.save();
}
const openApiFile = fs.readFileSync(OPEN_API_PATH, 'utf8');
const openApiFile = fs.readFileSync('./scripts/generator/v1.yaml', 'utf8');
generateApi(YAML.parse(openApiFile));

View File

@@ -4,7 +4,7 @@ import * as path from 'path';
import * as morph from 'ts-morph';
import { ENT_DIR } from '../../consts';
import { TYPES, toCamel, schemaParamParser, uncapitalize } from './utils';
import { TYPES, toCamel, schemaParamParser, capitalize, OpenApi, Schema } from './utils';
const { Project, QuoteKind } = morph;
@@ -17,7 +17,6 @@ if (!fs.existsSync(EntDir)) {
class EntitiesGenerator {
project = new Project({
tsConfigFilePath: './tsconfig.json',
addFilesFromTsConfig: false,
manipulationSettings: {
quoteKind: QuoteKind.Single,
usePrefixAndSuffixTextForRename: false,
@@ -25,15 +24,15 @@ class EntitiesGenerator {
},
});
openapi: Record<string, any>;
openapi: OpenApi;
schemas: Record<string, any>;
schemas: Record<string, Schema>;
schemaNames: string[];
entities: morph.SourceFile[] = [];
constructor(openapi: Record<string, any>) {
constructor(openapi: OpenApi) {
this.openapi = openapi;
this.schemas = openapi.components.schemas;
this.schemaNames = Object.keys(this.schemas);
@@ -44,472 +43,395 @@ class EntitiesGenerator {
this.schemaNames.forEach(this.generateEntity);
};
generateEntity = (sName: string) => {
const { properties, type, oneOf } = this.schemas[sName];
generateEntity = (schemaName: string) => {
const { properties, type, oneOf, enum: en } = this.schemas[schemaName];
const notAClass = !properties && TYPES[type as keyof typeof TYPES];
if (oneOf) {
this.generateOneOf(sName);
this.generateOneOf(schemaName);
return;
}
if (en) {
this.generateEnum(schemaName);
return;
}
if (notAClass) {
this.generateEnum(sName);
this.generatePrimitive(schemaName)
} else {
this.generateClass(sName);
this.generateClass(schemaName);
}
};
generateEnum = (sName: string) => {
const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`);
generatePrimitive = (schemaName: string) => {
const entityFile = this.project.createSourceFile(`${EntDir}/${schemaName}.ts`);
entityFile.addStatements([
'// This file was autogenerated. Please do not change.',
'// All changes will be overwrited on commit.',
'',
]);
const { type: schemaType, description, pattern } = this.schemas[schemaName];
if (description) {
entityFile.addStatements(['\n/*', `Description: ${description}`, '*/\n']);
}
const { enum: enumMembers } = this.schemas[sName];
if (pattern) {
entityFile.addStatements(`const pattern = new RegExp('${pattern}')`);
}
const type: string = TYPES[schemaType as keyof typeof TYPES];
const entityClass = entityFile.addClass({
name: schemaName,
isDefaultExport: true,
extends: capitalize(type),
});
const ctor = entityClass.addConstructor({
parameters: [{
name: 'v',
type,
}],
});
ctor.setBodyText((w) => {
const { minLength, minimum, maxLength, maximum } = this.schemas[schemaName];
if (type === 'string') {
if (pattern) {
w.writeLine('if (!v.match(pattern)) {');
w.writeLine(' throw new Error();');
w.writeLine('}');
}
if (typeof minLength === 'number') {
w.writeLine(`if (v.length < ${minLength}) {`);
w.writeLine(' throw new Error();');
w.writeLine('}');
}
if (typeof maxLength === 'number') {
w.writeLine(`if (v.length > ${maxLength}) {`);
w.writeLine(' throw new Error();');
w.writeLine('}');
}
}
if (type === 'number') {
if (typeof minimum === 'number') {
w.writeLine(`if (v.length < ${minimum}) {`);
w.writeLine(' throw new Error();');
w.writeLine('}');
}
if (typeof maximum === 'number') {
w.writeLine(`if (v.length > ${maximum}) {`);
w.writeLine(' throw new Error();');
w.writeLine('}');
}
}
w.writeLine('super(v);');
});
this.entities.push(entityFile);
};
generateEnum = (schemaName: string) => {
const entityFile = this.project.createSourceFile(`${EntDir}/${schemaName}.ts`);
entityFile.addStatements([
'// This file was autogenerated. Please do not change.',
'',
]);
const { enum: enumMembers, description, example } = this.schemas[schemaName];
if (description) {
entityFile.addStatements(['\n/*', `Description: ${description}`, '*/\n']);
}
entityFile.addEnum({
name: sName,
members: enumMembers.map((e: string) => ({ name: e.toUpperCase(), value: e })),
name: schemaName,
members: enumMembers!.map((e: string) => ({ name: e.toUpperCase(), value: e })),
isExported: true,
});
this.entities.push(entityFile);
};
generateOneOf = (sName: string) => {
const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`);
generateOneOf = (schemaName: string) => {
const entityFile = this.project.createSourceFile(`${EntDir}/${schemaName}.ts`);
entityFile.addStatements([
'// This file was autogenerated. Please do not change.',
'// All changes will be overwrited on commit.',
'',
]);
const importEntities: { type: string, isClass: boolean }[] = [];
const entities = this.schemas[sName].oneOf.map((elem: any) => {
const [
pType, isArray, isClass, isImport,
] = schemaParamParser(elem, this.openapi);
importEntities.push({ type: pType, isClass });
return { type: pType, isArray };
const entities = this.schemas[schemaName].oneOf.map((elem: any) => {
const {
type: type, isArray, isClass, isImport,
} = schemaParamParser(elem, this.openapi);
importEntities.push({ type: type, isClass });
return { type: type, isArray };
});
entityFile.addTypeAlias({
name: sName,
name: schemaName,
isExported: true,
type: entities.map((e: any) => e.isArray ? `I${e.type}[]` : `I${e.type}`).join(' | '),
})
// add import
importEntities.sort((a, b) => a.type > b.type ? 1 : -1).forEach((ie) => {
const { type: pType, isClass } = ie;
const { type: type, isClass } = ie;
if (isClass) {
entityFile.addImportDeclaration({
moduleSpecifier: `./${pType}`,
namedImports: [`I${pType}`],
moduleSpecifier: `./${type}`,
namedImports: [`I${type}`],
});
} else {
entityFile.addImportDeclaration({
moduleSpecifier: `./${pType}`,
namedImports: [pType],
moduleSpecifier: `./${type}`,
namedImports: [type],
});
}
});
this.entities.push(entityFile);
}
generateClass = (sName: string) => {
const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`);
generateClass = (schemaName: string) => {
const entityFile = this.project.createSourceFile(`${EntDir}/${schemaName}.ts`);
entityFile.addStatements([
'// This file was autogenerated. Please do not change.',
'// All changes will be overwrited on commit.',
'',
]);
let { properties, required, allOf } = this.schemas[schemaName];
const { properties: sProps, required, $ref, additionalProperties } = this.schemas[sName];
if ($ref) {
const temp = $ref.split('/');
const importSchemaName = `${temp[temp.length - 1]}`;
entityFile.addImportDeclaration({
defaultImport: importSchemaName,
moduleSpecifier: `./${importSchemaName}`,
namedImports: [`I${importSchemaName}`],
});
if (allOf) {
const refLink: string = allOf.find((obj: Record<string, any>) => obj.$ref).$ref;
let ref: any = refLink.split('/')
ref = ref.pop();
const reasign = allOf.find((obj: Record<string, any>) => !obj.$ref);
const newSchema: Schema = { ...this.schemas[ref], ...reasign };
entityFile.addTypeAlias({
name: `I${sName}`,
type: `I${importSchemaName}`,
isExported: true,
})
entityFile.addStatements(`export default ${importSchemaName};`);
this.entities.push(entityFile);
return;
properties = newSchema.properties;
required = newSchema.required;
}
const importEntities: { type: string, isClass: boolean }[] = [];
const entityInterface = entityFile.addInterface({
name: `I${sName}`,
name: `I${schemaName}`,
isExported: true,
});
const sortedSProps = Object.keys(sProps || {}).sort();
const additionalPropsOnly = additionalProperties && sortedSProps.length === 0;
const sortedProperties = Object.keys(properties || {}).sort();
let importEntities: { type: string, isClass: boolean }[] = [];
type SortedPropertiesTypesValues = ReturnType<typeof schemaParamParser> & {
computedType: string;
isRequired: boolean;
}
const sortedPropertiesTypes = sortedProperties.reduce((data, propertyName) => {
const isRequired = !!(required && required.includes(propertyName));
const parsed = schemaParamParser(properties![propertyName], this.openapi);
data[propertyName] = {
...parsed,
isRequired,
computedType: `${parsed.type}${parsed.isArray ? '[]' : ''}${isRequired ? '' : ' | undefined'}`
};
return data;
}, {} as Record<string, SortedPropertiesTypesValues>);
// add server response interface to entityFile
sortedSProps.forEach((sPropName) => {
const [
pType, isArray, isClass, isImport, isAdditional
] = schemaParamParser(sProps[sPropName], this.openapi);
sortedProperties.forEach((propertyName) => {
const {
type, isArray, isClass, isImport
} = sortedPropertiesTypes[propertyName];
if (isImport) {
importEntities.push({ type: pType, isClass });
importEntities.push({ type: type, isClass });
}
const propertyType = isAdditional
? `{ [key: string]: ${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''} }`
: `${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`;
entityInterface.addProperty({
name: sPropName,
type: propertyType,
name: propertyName,
type: `${isClass ? 'I' : ''}${type}${isArray ? '[]' : ''}`,
hasQuestionToken: !(
(required && required.includes(sPropName)) || sProps[sPropName].required
(required && required.includes(propertyName)) || properties![propertyName].required
),
});
});
if (additionalProperties) {
const [
pType, isArray, isClass, isImport, isAdditional
] = schemaParamParser(additionalProperties, this.openapi);
if (isImport) {
importEntities.push({ type: pType, isClass });
}
const type = isAdditional
? `{ [key: string]: ${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''} }`
: `${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`;
entityInterface.addIndexSignature({
keyName: 'key',
keyType: 'string',
returnType: additionalPropsOnly ? type : `${type} | undefined`,
});
}
// add import
const imports: { type: string, isClass: boolean }[] = [];
const types: string[] = [];
importEntities.forEach((i) => {
importEntities = importEntities.filter((i) => {
const { type } = i;
if (!types.includes(type)) {
imports.push(i);
types.push(type);
return true;
}
return false;
});
imports.sort((a, b) => a.type > b.type ? 1 : -1).forEach((ie) => {
const { type: pType, isClass } = ie;
importEntities.sort((a, b) => a.type > b.type ? 1 : -1).forEach((ie) => {
const { type: type, isClass } = ie;
if (isClass) {
entityFile.addImportDeclaration({
defaultImport: pType,
moduleSpecifier: `./${pType}`,
namedImports: [`I${pType}`],
defaultImport: type,
moduleSpecifier: `./${type}`,
namedImports: [`I${type}`],
});
} else {
entityFile.addImportDeclaration({
moduleSpecifier: `./${pType}`,
namedImports: [pType],
moduleSpecifier: `./${type}`,
namedImports: [type],
});
}
});
const entityClass = entityFile.addClass({
name: sName,
name: schemaName,
isDefaultExport: true,
});
// addProperties to class;
sortedSProps.forEach((sPropName) => {
const [pType, isArray, isClass, isImport, isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
const isRequred = (required && required.includes(sPropName))
|| sProps[sPropName].required;
const propertyType = isAdditional
? `{ [key: string]: ${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'} }`
: `${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'}`;
sortedProperties.forEach((propertyName) => {
const { type, isArray, isClass, isEnum, isRequired, computedType } = sortedPropertiesTypes[propertyName];
entityClass.addProperty({
name: `_${sPropName}`,
name: `_${propertyName}`,
isReadonly: true,
type: propertyType,
type: computedType,
});
const getter = entityClass.addGetAccessor({
name: toCamel(sPropName),
returnType: propertyType,
statements: [`return this._${sPropName};`],
name: toCamel(propertyName),
returnType: computedType,
statements: [`return this._${propertyName};`],
});
const { description, example, minItems, maxItems, maxLength, minLength, maximum, minimum } = sProps[sPropName];
const { description, example, minItems, maxItems, maxLength, minLength, maximum, minimum } = properties![propertyName];
if (description || example) {
getter.addJsDoc(`${example ? `Description: ${description}` : ''}${example ? `\nExample: ${example}` : ''}`);
}
if (minItems) {
entityClass.addGetAccessor({
entityClass.addProperty({
isStatic: true,
name: `${toCamel(sPropName)}MinItems`,
statements: [`return ${minItems};`],
isReadonly: true,
name: `${capitalize(toCamel(propertyName))}MinItems`,
initializer: `${minItems}`,
});
}
if (maxItems) {
entityClass.addGetAccessor({
entityClass.addProperty({
isStatic: true,
name: `${toCamel(sPropName)}MaxItems`,
statements: [`return ${maxItems};`],
isReadonly: true,
name: `${capitalize(toCamel(propertyName))}MaxItems`,
initializer: `${maxItems}`,
});
}
if (typeof minLength === 'number') {
entityClass.addGetAccessor({
entityClass.addProperty({
isStatic: true,
name: `${toCamel(sPropName)}MinLength`,
statements: [`return ${minLength};`],
isReadonly: true,
name: `${capitalize(toCamel(propertyName))}MinLength`,
initializer: `${minLength}`,
});
}
if (maxLength) {
entityClass.addGetAccessor({
entityClass.addProperty({
isStatic: true,
name: `${toCamel(sPropName)}MaxLength`,
statements: [`return ${maxLength};`],
isReadonly: true,
name: `${capitalize(toCamel(propertyName))}MaxLength`,
initializer: `${maxLength}`,
});
}
if (typeof minimum === 'number') {
entityClass.addGetAccessor({
entityClass.addProperty({
isStatic: true,
name: `${toCamel(sPropName)}MinValue`,
statements: [`return ${minimum};`],
isReadonly: true,
name: `${capitalize(toCamel(propertyName))}MinValue`,
initializer: `${minimum}`,
});
}
if (maximum) {
entityClass.addGetAccessor({
entityClass.addProperty({
isStatic: true,
name: `${toCamel(sPropName)}MaxValue`,
statements: [`return ${maximum};`],
isReadonly: true,
name: `${capitalize(toCamel(propertyName))}MaxValue`,
initializer: `${maximum}`,
});
}
if (!(isArray && isClass) && !isClass) {
const isEnum = !isClass && isImport;
const isRequired = (required && required.includes(sPropName)) || sProps[sPropName].required;
const { maxLength, minLength, maximum, minimum } = sProps[sPropName];
const haveValidationFields = maxLength || typeof minLength === 'number' || maximum || typeof minimum === 'number';
if (isRequired || haveValidationFields) {
const prop = toCamel(sPropName);
const validateField = entityClass.addMethod({
isStatic: true,
name: `${prop}Validate`,
returnType: `boolean`,
parameters: [{
name: prop,
type: `${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'}`,
}],
})
validateField.setBodyText((w) => {
w.write('return ');
const nonRequiredCall = isRequired ? prop : `!${prop} ? true : ${prop}`;
if (pType === 'string') {
if (isArray) {
w.write(`${nonRequiredCall}.reduce<boolean>((result, p) => result && (typeof p === 'string' && !!p.trim()), true)`);
} else {
if (typeof minLength === 'number' && maxLength) {
w.write(`(${nonRequiredCall}.length >${minLength > 0 ? '=' : ''} ${minLength}) && (${nonRequiredCall}.length <= ${maxLength})`);
}
if (typeof minLength !== 'number' || !maxLength) {
w.write(`${isRequired ? `typeof ${prop} === 'string'` : `!${prop} ? true : typeof ${prop} === 'string'`} && !!${nonRequiredCall}.trim()`);
}
}
} else if (pType === 'number') {
if (isArray) {
w.write(`${nonRequiredCall}.reduce<boolean>((result, p) => result && typeof p === 'number', true)`);
} else {
if (typeof minimum === 'number' && maximum) {
w.write(`${isRequired ? `${prop} >= ${minimum} && ${prop} <= ${maximum}` : `!${prop} ? true : ((${prop} >= ${minimum}) && (${prop} <= ${maximum}))`}`);
}
if (typeof minimum !== 'number' || !maximum) {
w.write(`${isRequired ? `typeof ${prop} === 'number'` : `!${prop} ? true : typeof ${prop} === 'number'`}`);
}
}
} else if (pType === 'boolean') {
w.write(`${isRequired ? `typeof ${prop} === 'boolean'` : `!${prop} ? true : typeof ${prop} === 'boolean'`}`);
} else if (isEnum) {
if (isArray){
w.write(`${nonRequiredCall}.reduce<boolean>((result, p) => result && Object.keys(${pType}).includes(${prop}), true)`);
} else {
w.write(`${isRequired ? `Object.keys(${pType}).includes(${prop})` : `!${prop} ? true : typeof ${prop} === 'boolean'`}`);
}
}
w.write(';');
});
}
}
});
if (additionalProperties) {
const [
pType, isArray, isClass, isImport, isAdditional
] = schemaParamParser(additionalProperties, this.openapi);
const type = `Record<string, ${pType}${isArray ? '[]' : ''}>`;
entityClass.addProperty({
name: additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`,
isReadonly: true,
type: type,
});
}
// add constructor;
const ctor = entityClass.addConstructor({
parameters: [{
name: 'props',
type: `I${sName}`,
type: `I${schemaName}`,
}],
});
ctor.setBodyText((w) => {
if (additionalProperties) {
const [
pType, isArray, isClass, isImport, isAdditional
] = schemaParamParser(additionalProperties, this.openapi);
w.writeLine(`this.${additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`} = Object.entries(props).reduce<Record<string, ${pType}>>((prev, [key, value]) => {`);
if (isClass) {
w.writeLine(` prev[key] = new ${pType}(value!);`);
sortedProperties.forEach((propertyName) => {
const { type, isArray, isClass, isRequired } = sortedPropertiesTypes[propertyName];
const indent = !isRequired ? ' ' : '';
if (!isRequired) {
if ((type === 'boolean' || type === 'number' || type ==='string') && !isClass && !isArray) {
w.writeLine(`if (typeof props.${propertyName} === '${type}') {`);
} else {
w.writeLine(`if (props.${propertyName}) {`);
}
}
if (isArray && isClass) {
w.writeLine(`${indent}this._${propertyName} = props.${propertyName}.map((p) => new ${type}(p));`);
} else if (isClass) {
w.writeLine(`${indent}this._${propertyName} = new ${type}(props.${propertyName});`);
} else {
w.writeLine(' prev[key] = value!;')
}
w.writeLine(' return prev;');
w.writeLine('}, {})');
return;
}
sortedSProps.forEach((sPropName) => {
const [
pType, isArray, isClass, , isAdditional
] = schemaParamParser(sProps[sPropName], this.openapi);
const req = (required && required.includes(sPropName))
|| sProps[sPropName].required;
if (!req) {
if ((pType === 'boolean' || pType === 'number' || pType ==='string') && !isClass && !isArray) {
w.writeLine(`if (typeof props.${sPropName} === '${pType}') {`);
if (type === 'string' && !isArray) {
w.writeLine(`${indent}this._${propertyName} = props.${propertyName}.trim();`);
} else {
w.writeLine(`if (props.${sPropName}) {`);
w.writeLine(`${indent}this._${propertyName} = props.${propertyName};`);
}
}
if (isAdditional) {
if (isArray && isClass) {
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => {
return { ...prev, [key]: new ${pType}(p[key])};
},{}))`);
} else if (isClass) {
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => {
return { ...prev, [key]: new ${pType}(props.${sPropName}[key])};
},{})`);
} else {
if (pType === 'string' && !isArray) {
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => {
return { ...prev, [key]: props.${sPropName}[key].trim()};
},{})`);
} else {
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => {
return { ...prev, [key]: props.${sPropName}[key]};
},{})`);
}
}
} else {
if (isArray && isClass) {
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.map((p) => new ${pType}(p));`);
} else if (isClass) {
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = new ${pType}(props.${sPropName});`);
} else {
if (pType === 'string' && !isArray) {
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.trim();`);
} else {
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName};`);
}
}
}
if (!req) {
if (!isRequired) {
w.writeLine('}');
}
});
});
// add serialize method;
const serialize = entityClass.addMethod({
isStatic: false,
name: 'serialize',
returnType: `I${sName}`,
returnType: `I${schemaName}`,
});
serialize.setBodyText((w) => {
if (additionalProperties) {
const [
pType, isArray, isClass, isImport, isAdditional
] = schemaParamParser(additionalProperties, this.openapi);
w.writeLine(`return Object.entries(this.${additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`}).reduce<Record<string, ${isClass ? 'I' : ''}${pType}>>((prev, [key, value]) => {`);
if (isClass) {
w.writeLine(` prev[key] = value.serialize();`);
} else {
w.writeLine(' prev[key] = value;')
}
w.writeLine(' return prev;');
w.writeLine('}, {})');
return;
}
w.writeLine(`const data: I${sName} = {`);
w.writeLine(`const data: I${schemaName} = {`);
const unReqFields: string[] = [];
sortedSProps.forEach((sPropName) => {
const req = (required && required.includes(sPropName))
|| sProps[sPropName].required;
const [, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
if (!req) {
unReqFields.push(sPropName);
sortedProperties.forEach((propertyName) => {
const {isArray, isClass, isRequired } = sortedPropertiesTypes[propertyName];
if (!isRequired) {
unReqFields.push(propertyName);
return;
}
if (isAdditional) {
if (isArray && isClass) {
w.writeLine(` ${sPropName}: this._${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => ({ ...prev, [key]: p[key].serialize() }))),`);
} else if (isClass) {
w.writeLine(` ${sPropName}: Object.keys(this._${sPropName}).reduce<Record<string, any>>((prev, key) => ({ ...prev, [key]: this._${sPropName}[key].serialize() }), {}),`);
} else {
w.writeLine(` ${sPropName}: Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key] })),`);
}
if (isArray && isClass) {
w.writeLine(` ${propertyName}: this._${propertyName}.map((p) => p.serialize()),`);
} else if (isClass) {
w.writeLine(` ${propertyName}: this._${propertyName}.serialize(),`);
} else {
if (isArray && isClass) {
w.writeLine(` ${sPropName}: this._${sPropName}.map((p) => p.serialize()),`);
} else if (isClass) {
w.writeLine(` ${sPropName}: this._${sPropName}.serialize(),`);
} else {
w.writeLine(` ${sPropName}: this._${sPropName},`);
}
w.writeLine(` ${propertyName}: this._${propertyName},`);
}
});
w.writeLine('};');
unReqFields.forEach((sPropName) => {
const [, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
w.writeLine(`if (typeof this._${sPropName} !== 'undefined') {`);
if (isAdditional) {
if (isArray && isClass) {
w.writeLine(` data.${sPropName} = this._${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => ({ ...prev, [key]: p[key].serialize() }), {}));`);
} else if (isClass) {
w.writeLine(` data.${sPropName} = Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key].serialize() }), {});`);
} else {
w.writeLine(` data.${sPropName} = Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key] }), {});`);
}
unReqFields.forEach((propertyName) => {
const { isArray, isClass } = sortedPropertiesTypes[propertyName];
w.writeLine(`if (typeof this._${propertyName} !== 'undefined') {`);
if (isArray && isClass) {
w.writeLine(` data.${propertyName} = this._${propertyName}.map((p) => p.serialize());`);
} else if (isClass) {
w.writeLine(` data.${propertyName} = this._${propertyName}.serialize();`);
} else {
if (isArray && isClass) {
w.writeLine(` data.${sPropName} = this._${sPropName}.map((p) => p.serialize());`);
} else if (isClass) {
w.writeLine(` data.${sPropName} = this._${sPropName}.serialize();`);
} else {
w.writeLine(` data.${sPropName} = this._${sPropName};`);
}
w.writeLine(` data.${propertyName} = this._${propertyName};`);
}
w.writeLine(`}`);
});
w.writeLine('return data;');
@@ -522,74 +444,55 @@ class EntitiesGenerator {
returnType: `string[]`,
})
validate.setBodyText((w) => {
if (additionalPropsOnly) {
w.writeLine('return []')
return;
}
w.writeLine('const validate = {');
Object.keys(sProps || {}).forEach((sPropName) => {
const [pType, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
w.writeLine('const validateRequired = {');
Object.keys(properties || {}).forEach((propertyName) => {
const { isArray, isClass, type, isRequired } = sortedPropertiesTypes[propertyName];
const { maxLength, minLength, maximum, minimum } = properties![propertyName];
const { maxLength, minLength, maximum, minimum } = sProps[sPropName];
const isRequired = (required && required.includes(sPropName)) || sProps[sPropName].required;
const nonRequiredCall = isRequired ? `this._${sPropName}` : `!this._${sPropName} ? true : this._${sPropName}`;
const nonRequiredCall = isRequired ? `this._${propertyName}` : `!this._${propertyName} ? true : this._${propertyName}`;
if (isArray && isClass) {
w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && p.validate().length === 0, true),`);
} else if (isClass && !isAdditional) {
w.writeLine(` ${sPropName}: ${nonRequiredCall}.validate().length === 0,`);
w.writeLine(` ${propertyName}: ${nonRequiredCall}.reduce<boolean>((result, p) => result && p.validate().length === 0, true),`);
} else if (isClass) {
w.writeLine(` ${propertyName}: ${nonRequiredCall}.validate().length === 0,`);
} else {
if (pType === 'string') {
if (type === 'string') {
if (isArray) {
w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && typeof p === 'string', true),`);
w.writeLine(` ${propertyName}: ${nonRequiredCall}.reduce<boolean>((result, p) => result && typeof p === 'string', true),`);
} else {
if (typeof minLength === 'number' && maxLength) {
w.writeLine(` ${sPropName}: (${nonRequiredCall}.length >${minLength > 0 ? '=' : ''} ${minLength}) && (${nonRequiredCall}.length <= ${maxLength}),`);
w.writeLine(` ${propertyName}: (${nonRequiredCall}.length >${minLength > 0 ? '=' : ''} ${minLength}) && (${nonRequiredCall}.length <= ${maxLength}),`);
}
if (typeof minLength !== 'number' || !maxLength) {
w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'string'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'string'`} && !this._${sPropName} ? true : this._${sPropName},`);
w.writeLine(` ${propertyName}: ${isRequired ? `typeof this._${propertyName} === 'string' && !!this._${propertyName}.trim()` : `!this._${propertyName} ? true : typeof this._${propertyName} === 'string'`},`);
}
}
} else if (pType === 'number') {
} else if (type === 'number') {
if (isArray) {
w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && typeof p === 'number', true),`);
w.writeLine(` ${propertyName}: ${nonRequiredCall}.reduce<boolean>((result, p) => result && typeof p === 'number', true),`);
} else {
if (typeof minimum === 'number' && maximum) {
w.writeLine(` ${sPropName}: ${isRequired ? `this._${sPropName} >= ${minimum} && this._${sPropName} <= ${maximum}` : `!this._${sPropName} ? true : ((this._${sPropName} >= ${minimum}) && (this._${sPropName} <= ${maximum}))`},`);
w.writeLine(` ${propertyName}: ${isRequired ? `this._${propertyName} >= ${minimum} && this._${propertyName} <= ${maximum}` : `!this._${propertyName} ? true : ((this._${propertyName} >= ${minimum}) && (this._${propertyName} <= ${maximum}))`},`);
}
if (typeof minimum !== 'number' || !maximum) {
w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'number'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'number'`},`);
w.writeLine(` ${propertyName}: ${isRequired ? `typeof this._${propertyName} === 'number'` : `!this._${propertyName} ? true : typeof this._${propertyName} === 'number'`},`);
}
}
} else if (pType === 'boolean') {
w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'boolean'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'boolean'`},`);
} else if (type === 'boolean') {
w.writeLine(` ${propertyName}: ${isRequired ? `typeof this._${propertyName} === 'boolean'` : `!this._${propertyName} ? true : typeof this._${propertyName} === 'boolean'`},`);
}
}
});
w.writeLine('};');
w.writeLine('const isError: string[] = [];')
w.writeLine('Object.keys(validate).forEach((key) => {');
w.writeLine(' if (!(validate as any)[key]) {');
w.writeLine(' isError.push(key);');
w.writeLine('const errorInFields: string[] = [];')
w.writeLine('Object.keys(validateRequired).forEach((key) => {');
w.writeLine(' if (!(validateRequired as any)[key]) {');
w.writeLine(' errorInFields.push(key);');
w.writeLine(' }');
w.writeLine('});');
w.writeLine('return isError;');
w.writeLine('return errorInFields;');
});
// add update method;
const update = entityClass.addMethod({
isStatic: false,
name: 'update',
returnType: `${sName}`,
});
update.addParameter({
name: 'props',
type: additionalPropsOnly ? `I${sName}` : `Partial<I${sName}>`,
});
update.setBodyText((w) => { w.writeLine(`return new ${sName}({ ...this.serialize(), ...props });`); });
this.entities.push(entityFile);
};

View File

@@ -19,20 +19,106 @@ const TYPES = {
boolean: 'boolean',
};
export enum SchemaType {
STRING = 'string',
OBJECT = 'object',
ARRAY = 'array',
BOOLEAN = 'boolean',
NUMBER = 'number',
INTEGER = 'integer',
}
export interface Schema {
allOf?: any[];
example?: string;
properties?: Record<string, Schema>;
required?: string[];
description?: string;
enum?: string[];
type: SchemaType;
pattern?: string;
oneOf?: any
items?: Schema;
additionalProperties?: Schema;
$ref?: string;
minItems?: number;
maxItems?: number;
maxLength?: number;
minLength?: number;
maximum?: number;
minimum?: number;
}
export interface Parametr {
description?: string;
example?: string;
in?: 'query' | 'body' | 'headers';
name?: string;
schema?: Schema;
required?: boolean;
}
export interface RequestBody {
content: {
'application/json'?: {
schema: Schema;
example?: string;
};
'text/palin'?: {
example?: string;
}
}
required?: boolean;
}
export interface Response {
content: {
'application/json'?: {
schema: Schema;
example?: string;
};
'text/palin'?: {
example?: string;
}
}
description?: string;
}
export interface Schemas {
parameters: Record<string, Parametr>;
requestBodies: Record<string, RequestBody>;
responses: Record<string, Response>;
schemas: Record<string, Schema>;
}
export interface OpenApi {
components: Schemas;
paths: any;
}
/**
* @param schemaProp: valueof shema.properties[key]
* @param openApi: openapi object
* @returns [propType - basicType or import one, isArray, isClass, isImport]
*/
const schemaParamParser = (schemaProp: any, openApi: any): [string, boolean, boolean, boolean, boolean] => {
interface SchemaParamParserReturn {
type: string;
isArray: boolean;
isClass: boolean;
isImport: boolean;
isAdditional: boolean;
isEnum: boolean;
}
const schemaParamParser = (schemaProp: Schema, openApi: OpenApi): SchemaParamParserReturn => {
let type = '';
let isImport = false;
let isClass = false;
let isArray = false;
let isAdditional = false;
let isEnum = false;
if (schemaProp.$ref || schemaProp.additionalProperties?.$ref) {
const temp = (schemaProp.$ref || schemaProp.additionalProperties?.$ref).split('/');
const temp = (schemaProp.$ref || schemaProp.additionalProperties?.$ref)!.split('/');
if (schemaProp.additionalProperties) {
isAdditional = true;
@@ -40,44 +126,41 @@ const schemaParamParser = (schemaProp: any, openApi: any): [string, boolean, boo
type = `${temp[temp.length - 1]}`;
const cl = openApi ? openApi.components.schemas[type] : {};
const cl = openApi.components.schemas[type];
if (cl.$ref) {
const link = schemaParamParser(cl, openApi);
link.shift();
return [type, ...link] as any;
return {...link, type};
}
if (cl.type === 'string' && cl.enum) {
isImport = true;
isEnum = true;
}
if (cl.type === 'object' && !cl.oneOf) {
isClass = true;
isImport = true;
} else if (cl.type === 'array') {
const temp: any = schemaParamParser(cl.items, openApi);
type = `${temp[0]}`;
const temp = schemaParamParser(cl.items!, openApi);
type = temp.type;
isArray = true;
isClass = isClass || temp[2];
isImport = isImport || temp[3];
isClass = isClass || temp.isClass;
isImport = isImport || temp.isImport;
isEnum = isEnum || temp.isEnum;
}
} else if (schemaProp.type === 'array') {
const temp: any = schemaParamParser(schemaProp.items, openApi);
type = `${temp[0]}`;
const temp = schemaParamParser(schemaProp.items!, openApi);
type = temp.type
isArray = true;
isClass = isClass || temp[2];
isImport = isImport || temp[3];
isClass = isClass || temp.isClass;
isImport = isImport || temp.isImport;
isEnum = isEnum || temp.isEnum;
} else {
type = (TYPES as Record<any, string>)[schemaProp.type];
}
if (!type) {
// TODO: Fix bug with Error fields.
type = 'any';
// throw new Error('Failed to find entity type');
}
return [type, isArray, isClass, isImport, isAdditional];
return { type, isArray, isClass, isImport, isAdditional, isEnum };
};
export { TYPES, toCamel, capitalize, uncapitalize, schemaParamParser };