Skip to content

Commit

Permalink
feat: split type definition (#34)
Browse files Browse the repository at this point in the history
* docs: update comment
* feat: update type def generator
* test: add split code test
  • Loading branch information
Himenon authored Apr 6, 2021
1 parent 631af46 commit 1c8dc40
Show file tree
Hide file tree
Showing 8 changed files with 554 additions and 67 deletions.
68 changes: 56 additions & 12 deletions scripts/testCodeGen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as fs from "fs";
import { posix as path } from "path";

import { CodeGenerator } from "../lib";
import { CodeGenerator, GeneratorTemplate } from "../lib";
import * as Templates from "../lib/templates";

const writeText = (filename: string, text: string): void => {
Expand All @@ -13,7 +13,7 @@ const writeText = (filename: string, text: string): void => {
const generateTypedefCodeOnly = (inputFilename: string, outputFilename: string, isValidate: boolean) => {
const codeGenerator = new CodeGenerator(inputFilename);
if (isValidate) {
codeGenerator.validate({
codeGenerator.validateOpenApiSchema({
logger: { displayLogLines: 1 },
});
}
Expand All @@ -29,14 +29,17 @@ const generateTemplateCodeOnly = (
): void => {
const codeGenerator = new CodeGenerator(inputFilename);
if (isValidate) {
codeGenerator.validate({
codeGenerator.validateOpenApiSchema({
logger: { displayLogLines: 1 },
});
}
const code = codeGenerator.generateCode<Templates.ApiClient.Option>({

const apiClientGeneratorTemplate: GeneratorTemplate<Templates.ApiClient.Option> = {
generator: Templates.ApiClient.generator,
option: option,
});
};

const code = codeGenerator.generateCode([apiClientGeneratorTemplate]);

writeText(outputFilename, code);
};
Expand All @@ -49,18 +52,53 @@ const generateTypedefWithTemplateCode = (
): void => {
const codeGenerator = new CodeGenerator(inputFilename);
if (isValidate) {
codeGenerator.validate({
codeGenerator.validateOpenApiSchema({
logger: { displayLogLines: 1 },
});
}
const code = codeGenerator.generateTypeDefinition<Templates.ApiClient.Option>({
generator: Templates.ApiClient.generator,
option: option,
});

const code = codeGenerator.generateTypeDefinition([
{
generator: () => {
return codeGenerator.getAdditionalTypeStatements();
},
},
{
generator: Templates.ApiClient.generator,
option: option,
},
]);

writeText(outputFilename, code);
};

const generateSplitCode = (inputFilename: string, outputDir: string) => {
const codeGenerator = new CodeGenerator(inputFilename);

const apiClientGeneratorTemplate: GeneratorTemplate<Templates.ApiClient.Option> = {
generator: Templates.ApiClient.generator,
option: { sync: false },
};

const typeDefCode = codeGenerator.generateTypeDefinition();
const apiClientCode = codeGenerator.generateCode([
{
generator: () => {
return [`import { Schemas } from "./types";`];
},
},
{
generator: () => {
return codeGenerator.getAdditionalTypeStatements();
},
},
apiClientGeneratorTemplate,
]);

writeText(path.join(outputDir, "types.ts"), typeDefCode);
writeText(path.join(outputDir, "apiClient.ts"), apiClientCode);
};

const main = () => {
generateTypedefCodeOnly("test/api.test.domain/index.yml", "test/code/typedef-only/api.test.domain.ts", true);
generateTypedefCodeOnly("test/infer.domain/index.yml", "test/code/typedef-only/infer.domain.ts", false);
Expand All @@ -69,9 +107,15 @@ const main = () => {
generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/template-only/sync-api.test.domain.ts", true, { sync: true });
generateTemplateCodeOnly("test/infer.domain/index.yml", "test/code/template-only/infer.domain.ts", false, { sync: true });

generateTypedefWithTemplateCode("test/api.test.domain/index.yml", "test/code/typedef-with-template/api.test.domain.ts", true, { sync: false });
generateTypedefWithTemplateCode("test/api.test.domain/index.yml", "test/code/typedef-with-template/sync-api.test.domain.ts", true, { sync: true });
generateTypedefWithTemplateCode("test/api.test.domain/index.yml", "test/code/typedef-with-template/api.test.domain.ts", true, {
sync: false,
});
generateTypedefWithTemplateCode("test/api.test.domain/index.yml", "test/code/typedef-with-template/sync-api.test.domain.ts", true, {
sync: true,
});
generateTypedefWithTemplateCode("test/infer.domain/index.yml", "test/code/typedef-with-template/infer.domain.ts", false, { sync: false });

generateSplitCode("test/api.test.domain/index.yml", "test/code/split");
};

main();
74 changes: 54 additions & 20 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { EOL } from "os";

import { OpenApiTools, ResolveReference, TsGenerator, Validator, FileSystem } from "./api";
import ts from "typescript";

import * as Api from "./api";
import type * as Types from "./types";

export interface GeneratorTemplate<T> {
Expand All @@ -11,43 +13,75 @@ export interface GeneratorTemplate<T> {
export class CodeGenerator {
private rootSchema: Types.OpenApi.Document;
private resolvedReferenceDocument: Types.OpenApi.Document;
private parser: OpenApiTools.Parser;
private parser: Api.OpenApiTools.Parser;
constructor(private readonly entryPoint: string) {
this.rootSchema = FileSystem.loadJsonOrYaml(entryPoint);
this.resolvedReferenceDocument = ResolveReference.resolve(entryPoint, entryPoint, JSON.parse(JSON.stringify(this.rootSchema)));
this.rootSchema = Api.FileSystem.loadJsonOrYaml(entryPoint);
this.resolvedReferenceDocument = Api.ResolveReference.resolve(entryPoint, entryPoint, JSON.parse(JSON.stringify(this.rootSchema)));
this.parser = this.createParser();
}

private createParser(allowOperationIds?: string[]): OpenApiTools.Parser {
return new OpenApiTools.Parser(this.entryPoint, this.rootSchema, this.resolvedReferenceDocument, {
private createParser(allowOperationIds?: string[]): Api.OpenApiTools.Parser {
return new Api.OpenApiTools.Parser(this.entryPoint, this.rootSchema, this.resolvedReferenceDocument, {
allowOperationIds: allowOperationIds,
});
}

public validate(config?: Types.Validator.Configuration) {
if (!config) {
Validator.validate(this.resolvedReferenceDocument);
/**
* Validate the OpenAPI Schema
*/
public validateOpenApiSchema(option?: Types.Validator.Option) {
if (!option) {
Api.Validator.validate(this.resolvedReferenceDocument);
} else {
Validator.validate(this.resolvedReferenceDocument, config.logger);
Api.Validator.validate(this.resolvedReferenceDocument, option.logger);
}
}

public generateTypeDefinition<T = {}>(generatorTemplate?: GeneratorTemplate<T>): string {
/**
* Provides TypeScript typedefs generated from OpenAPI Schema.
*
* @param generatorTemplate Template for when you want to change the code following a type definition
* @returns String of generated code
*/
public generateTypeDefinition(generatorTemplates?: GeneratorTemplate<any>[]): string {
const create = () => {
const statements = this.parser.getTypeDefinitionStatements();
if (generatorTemplate) {
const statements = this.parser.getOpenApiTypeDefinitionStatements();
generatorTemplates?.forEach(generatorTemplate => {
const payload = this.parser.getCodeGeneratorParamsArray();
const extraStatements = TsGenerator.Utils.convertIntermediateCodes(generatorTemplate.generator(payload, generatorTemplate.option));
return statements.concat(extraStatements);
}
const extraStatements = Api.TsGenerator.Utils.convertIntermediateCodes(generatorTemplate.generator(payload, generatorTemplate.option));
statements.push(...extraStatements);
});
return statements;
};
return [OpenApiTools.Comment.generateLeading(this.resolvedReferenceDocument), TsGenerator.generate(create)].join(EOL + EOL + EOL);
return [Api.OpenApiTools.Comment.generateLeading(this.resolvedReferenceDocument), Api.TsGenerator.generate(create)].join(EOL + EOL + EOL);
}

public generateCode<T>(generatorTemplate: GeneratorTemplate<T>): string {
/**
* Generate code using a template
*
* @param generatorTemplate
* @returns String of generated code
*/
public generateCode(generatorTemplates: GeneratorTemplate<any>[]): string {
const payload = this.parser.getCodeGeneratorParamsArray();
const create = () => TsGenerator.Utils.convertIntermediateCodes(generatorTemplate?.generator(payload, generatorTemplate.option));
return [OpenApiTools.Comment.generateLeading(this.resolvedReferenceDocument), TsGenerator.generate(create)].join(EOL + EOL + EOL);
const create = () => {
return generatorTemplates
.map(generatorTemplate => {
return Api.TsGenerator.Utils.convertIntermediateCodes(generatorTemplate?.generator(payload, generatorTemplate.option));
})
.flat();
};
return [Api.OpenApiTools.Comment.generateLeading(this.resolvedReferenceDocument), Api.TsGenerator.generate(create)].join(EOL + EOL + EOL);
}

/**
* Provides parameters extracted from OpenApi Schema
*/
public getCodeGeneratorParamsArray(): Types.CodeGenerator.Params[] {
return this.parser.getCodeGeneratorParamsArray();
}

public getAdditionalTypeStatements(): ts.Statement[] {
return this.parser.getAdditionalTypeStatements();
}
}
6 changes: 5 additions & 1 deletion src/internal/OpenApiTools/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,11 @@ export class Parser {
return Extractor.generateCodeGeneratorParamsArray(this.store, this.convertContext, this.option.allowOperationIds);
}

public getTypeDefinitionStatements(): ts.Statement[] {
public getOpenApiTypeDefinitionStatements(): ts.Statement[] {
return this.store.getRootStatements();
}

public getAdditionalTypeStatements(): ts.Statement[] {
return this.store.getAdditionalStatements();
}
}
10 changes: 8 additions & 2 deletions src/internal/OpenApiTools/store/Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import Dot from "dot-prop";
import ts from "typescript";

import type { OpenApi } from "../../../types";
import { Factory } from "../../TsGenerator";
import { UnSupportError } from "../../Exception";
import { Factory } from "../../TsGenerator";
import * as Def from "./Definition";
import * as Operation from "./Operation";
import * as State from "./State";
Expand All @@ -25,6 +25,7 @@ export interface Type {
hasStatement: (path: string, types: Structure.DataStructure.Kind[]) => boolean;
addAdditionalStatement: (statements: ts.Statement[]) => void;
getRootStatements: () => ts.Statement[];
getAdditionalStatements: () => ts.Statement[];
updateOperationState: (httpMethod: string, requestUri: string, operationId: string, state: Partial<State.OperationState>) => void;
getNoReferenceOperationState: () => Operation.State;
getPathItem: (localPath: string) => OpenApi.PathItem;
Expand Down Expand Up @@ -75,7 +76,11 @@ export const create = (factory: Factory.Type, rootDocument: OpenApi.Document): T
}
return statements;
}, []);
return statements.concat(state.additionalStatements);
return statements;
};

const getAdditionalStatements = (): ts.Statement[] => {
return state.additionalStatements;
};

return {
Expand All @@ -95,6 +100,7 @@ export const create = (factory: Factory.Type, rootDocument: OpenApi.Document): T
return getChildByPaths(targetPath, kind);
},
getRootStatements,
getAdditionalStatements,
addComponent: (componentName: Def.ComponentName, statement: Structure.ComponentParams): void => {
operator.set(`${componentName}`, Structure.createInstance(statement));
},
Expand Down
2 changes: 1 addition & 1 deletion src/typedef/Validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export interface Logger {
*/
displayLogLines?: number;
}
export interface Configuration {
export interface Option {
logger?: Logger;
}
Loading

0 comments on commit 1c8dc40

Please sign in to comment.