Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Extension loader #25

Merged
merged 20 commits into from
Aug 17, 2022
Merged
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
743ef6b
refactor(core): unite extension loaders
oscar60310 Aug 2, 2022
5c6061b
fix(build): update to fit new validator loaders
oscar60310 Aug 2, 2022
73e5ab9
feat(core): add module name to extension
oscar60310 Aug 3, 2022
b2df344
refactor(serve): unite extension loader
oscar60310 Aug 3, 2022
628fc8d
chore(dbt, ext-utils): update to new extension style
oscar60310 Aug 3, 2022
ad5c7d0
fix(core): let template engine option be optional
oscar60310 Aug 3, 2022
4b8f34b
test(serve): add test for server
oscar60310 Aug 4, 2022
80df5d9
feat(core): support dynamic named extensions
oscar60310 Aug 4, 2022
77a8ac7
feat(core, build): expose spec generator and schema parser
oscar60310 Aug 5, 2022
50c420b
refactor(core): let input validators use @VulcanExtensionId instead o…
oscar60310 Aug 5, 2022
da7f20d
refactor(serve): let formatters use @VulcanExtensionId instead of nam…
oscar60310 Aug 5, 2022
1605c74
feat(core): expose codeLoader to extension list
oscar60310 Aug 5, 2022
528bc59
refactor(core, build): move factory logic to container module
oscar60310 Aug 5, 2022
4da91ce
feat(int-test): add integration testing package
oscar60310 Aug 5, 2022
b36c62f
refactor(core): exporse data source extensions
oscar60310 Aug 5, 2022
b2cfd97
test(serve, int-test): test query from pg-mem
oscar60310 Aug 5, 2022
9bad6f1
fix(core,serve): fix improper imports
oscar60310 Aug 12, 2022
a8c80a6
refactor(build,core): add missing generic for containers binding
oscar60310 Aug 17, 2022
4b905db
refactor(build,core): add missing enums for providers, serializers
oscar60310 Aug 17, 2022
1ef7c5d
refactor(build,core): add missing comments for option interfaces
oscar60310 Aug 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(core, build): expose spec generator and schema parser
- expose spec generator and schema parser.
- add project level option
- add document generator
oscar60310 committed Aug 8, 2022

Verified

This commit was signed with the committer’s verified signature.
DenielGrin Deniel Grin
commit 77a8ac76008e1933bea23520f3d06c31813f0468
14 changes: 12 additions & 2 deletions packages/build/src/containers/container.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Container as InversifyContainer } from 'inversify';
import { Container as CoreContainer } from '@vulcan-sql/core';
import { IBuildOptions } from '@vulcan-sql/build/models';
import { schemaParserModule } from './modules';
import {
documentGeneratorModule,
extensionModule,
schemaParserModule,
} from './modules';

export class Container {
private inversifyContainer?: InversifyContainer;
@@ -18,7 +22,13 @@ export class Container {
this.coreContainer = new CoreContainer();
await this.coreContainer.load(options);
this.inversifyContainer = this.coreContainer.getInversifyContainer();
this.inversifyContainer.load(schemaParserModule(options.schemaParser));
await this.inversifyContainer.loadAsync(
schemaParserModule(options['schema-parser'])
);
await this.inversifyContainer.loadAsync(extensionModule(options));
await this.inversifyContainer.loadAsync(
documentGeneratorModule(options['document-generator'])
);
}

public async unload() {
1 change: 1 addition & 0 deletions packages/build/src/containers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'reflect-metadata';
export * from './types';
export * from './container';
export * from './modules';
22 changes: 22 additions & 0 deletions packages/build/src/containers/modules/documentGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IDocumentGeneratorOptions } from '@vulcan-sql/build/models';
import { AsyncContainerModule } from 'inversify';
import { DocumentGenerator } from '../../lib/document-generator';
import { DocumentGeneratorOptions } from '../../options/documentGenerator';
import { TYPES } from '../types';

export const documentGeneratorModule = (options?: IDocumentGeneratorOptions) =>
new AsyncContainerModule(async (bind) => {
// Options
bind<IDocumentGeneratorOptions>(
TYPES.DocumentGeneratorInputOptions
).toConstantValue(options || ({} as any));
bind(TYPES.DocumentGeneratorOptions)
.to(DocumentGeneratorOptions)
.inSingletonScope();

// Document generator
bind(TYPES.DocumentGenerator).to(DocumentGenerator);
bind(TYPES.Factory_SpecGenerator).toAutoNamedFactory(
TYPES.Extension_SpecGenerator
);
});
19 changes: 19 additions & 0 deletions packages/build/src/containers/modules/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { builtInSchemaReader } from '@vulcan-sql/build/schema-parser';
import { ExtensionLoader } from '@vulcan-sql/core';
import { AsyncContainerModule } from 'inversify';
import { builtInSpecGenerator } from '../../lib/document-generator';
import { IBuildOptions } from '../../models/buildOptions';

export const extensionModule = (options: IBuildOptions) =>
new AsyncContainerModule(async (bind) => {
const loader = new ExtensionLoader(options);
// Internal extension modules

// Schema reader
loader.loadInternalExtensionModule(builtInSchemaReader);

// Spec generator
loader.loadInternalExtensionModule(builtInSpecGenerator);

loader.bindExtensions(bind);
});
2 changes: 2 additions & 0 deletions packages/build/src/containers/modules/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './schemaParser';
export * from './extension';
export * from './documentGenerator';
26 changes: 7 additions & 19 deletions packages/build/src/containers/modules/schemaParser.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
import {
ISchemaParserOptions,
SchemaReaderType,
} from '@vulcan-sql/build/models';
import {
FileSchemaReader,
SchemaParser,
SchemaReader,
} from '@vulcan-sql/build/schema-parser';
import { ContainerModule, interfaces } from 'inversify';
import { ISchemaParserOptions, SchemaReader } from '@vulcan-sql/build/models';
import { SchemaParser } from '@vulcan-sql/build/schema-parser';
import { AsyncContainerModule, interfaces } from 'inversify';
import { SchemaParserOptions } from '../../options/schemaParser';
import { TYPES } from '../types';
import { SchemaParserMiddlewares } from '@vulcan-sql/build/schema-parser/middleware';

export const schemaParserModule = (options: ISchemaParserOptions) =>
new ContainerModule((bind) => {
export const schemaParserModule = (options?: ISchemaParserOptions) =>
new AsyncContainerModule(async (bind) => {
// Options
bind<ISchemaParserOptions>(TYPES.SchemaParserInputOptions).toConstantValue(
options
options || ({} as any)
);
bind<SchemaParserOptions>(TYPES.SchemaParserOptions)
.to(SchemaParserOptions)
.inSingletonScope();

// Schema reader
bind<SchemaReader>(TYPES.SchemaReader)
.to(FileSchemaReader)
.inSingletonScope()
.whenTargetNamed(SchemaReaderType.LocalFile);

bind<interfaces.AutoNamedFactory<SchemaReader>>(
TYPES.Factory_SchemaReader
).toAutoNamedFactory<SchemaReader>(TYPES.SchemaReader);
).toAutoNamedFactory<SchemaReader>(TYPES.Extension_SchemaReader);

// Schema parser
bind<SchemaParser>(TYPES.SchemaParser).to(SchemaParser).inSingletonScope();
10 changes: 9 additions & 1 deletion packages/build/src/containers/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
export const TYPES = {
// Schema
SchemaParserInputOptions: Symbol.for('SchemaParserInputOptions'),
SchemaParserOptions: Symbol.for('SchemaParserOptions'),
SchemaReader: Symbol.for('SchemaReader'),
Factory_SchemaReader: Symbol.for('Factory_SchemaReader'),
SchemaParser: Symbol.for('SchemaParser'),
SchemaParserMiddleware: Symbol.for('SchemaParserMiddleware'),
// Document
DocumentGenerator: Symbol.for('DocumentGenerator'),
DocumentGeneratorInputOptions: Symbol.for('DocumentGeneratorInputOptions'),
DocumentGeneratorOptions: Symbol.for('DocumentGeneratorOptions'),
Factory_SpecGenerator: Symbol.for('Factory_SpecGenerator'),
// Extension
Extension_SchemaReader: Symbol.for('Extension_SchemaReader'),
Extension_SpecGenerator: Symbol.for('Extension_SpecGenerator'),
};
37 changes: 37 additions & 0 deletions packages/build/src/lib/document-generator/documentGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { APISchema } from '@vulcan-sql/core';
import { inject, injectable, interfaces } from 'inversify';
import { TYPES } from '../../containers/types';
import { SpecGenerator } from '../../models/extensions';
import { DocumentGeneratorOptions } from '../../options/documentGenerator';
import * as jsYAML from 'js-yaml';
import * as path from 'path';
import { promises as fs } from 'fs';

@injectable()
export class DocumentGenerator {
private specGenerators: SpecGenerator[];
private folderPath: string;

constructor(
@inject(TYPES.Factory_SpecGenerator)
specGeneratorFactory: interfaces.AutoNamedFactory<SpecGenerator>,
@inject(TYPES.DocumentGeneratorOptions) options: DocumentGeneratorOptions
) {
this.specGenerators = [];
for (const spec of options.specs) {
this.specGenerators.push(specGeneratorFactory(spec));
}
this.folderPath = options.folderPath;
}

public async generateDocuments(schemas: APISchema[]) {
for (const generator of this.specGenerators) {
const spec = generator.getSpec(schemas);
const filePath = path.resolve(
this.folderPath,
`spec-${generator.getExtensionId()}.yaml`
);
await fs.writeFile(filePath, jsYAML.dump(spec), 'utf-8');
}
}
}
2 changes: 2 additions & 0 deletions packages/build/src/lib/document-generator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './spec-generator';
export * from './documentGenerator';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { OAS3SpecGenerator } from './oas3';
export const builtInSpecGenerator = [OAS3SpecGenerator];

export * from './oas3';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SpecGenerator } from '../specGenerator';
import { SpecGenerator } from '../../../../models/extensions/specGenerator';
import * as oas3 from 'openapi3-ts';
import {
APISchema,
@@ -14,19 +14,23 @@ import {
RequestSchema as RequestParameter,
RequiredConstraint,
ResponseProperty,
VulcanExtensionId,
VulcanInternalExtension,
} from '@vulcan-sql/core';
import { isEmpty } from 'lodash';

@VulcanInternalExtension()
@VulcanExtensionId('oas3')
export class OAS3SpecGenerator extends SpecGenerator<oas3.OpenAPIObject> {
// Follow the OpenAPI specification version 3.0.3
// see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md
private oaiVersion = '3.0.3';

public getSpec() {
public getSpec(schemas: APISchema[]) {
const spec: oas3.OpenAPIObject = {
openapi: this.getOAIVersion(),
info: this.getInfo(),
paths: this.getPaths(),
paths: this.getPaths(schemas),
};
return spec;
}
@@ -43,9 +47,8 @@ export class OAS3SpecGenerator extends SpecGenerator<oas3.OpenAPIObject> {
};
}

private getPaths(): oas3.PathsObject {
private getPaths(schemas: APISchema[]): oas3.PathsObject {
const paths: oas3.PathsObject = {};
const schemas = this.getSchemas();
for (const schema of schemas) {
paths[this.convertToOASPath(schema.urlPath)] = this.getPath(schema);
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import { SchemaFormat, SchemaData, SchemaReader } from './schemaReader';
import {
SchemaFormat,
SchemaData,
SchemaReader,
} from '@vulcan-sql/build/models';
import * as glob from 'glob';
import { promises as fs } from 'fs';
import * as path from 'path';
import { inject, injectable } from 'inversify';
import { TYPES } from '@vulcan-sql/build/containers';
import { SchemaParserOptions } from '@vulcan-sql/build/options';
import {
VulcanExtensionId,
VulcanInternalExtension,
TYPES as CORE_TYPES,
} from '@vulcan-sql/core';
import { inject } from 'inversify';
import { TYPES } from '@vulcan-sql/build/containers';

export interface FileSchemaReaderOptions {
folderPath: string;
}

@injectable()
export class FileSchemaReader implements SchemaReader {
@VulcanInternalExtension()
@VulcanExtensionId('LocalFile')
export class FileSchemaReader extends SchemaReader {
private options: SchemaParserOptions;

constructor(@inject(TYPES.SchemaParserOptions) options: SchemaParserOptions) {
constructor(
@inject(TYPES.SchemaParserOptions) options: SchemaParserOptions,
@inject(CORE_TYPES.ExtensionConfig) config: any,
@inject(CORE_TYPES.ExtensionName) moduleName: string
) {
super(config, moduleName);
this.options = options;
}

public async *readSchema(): AsyncGenerator<SchemaData> {
if (!this.options?.folderPath)
throw new Error(`Config schema-parser.folderPath must be defined`);
const files = await this.getSchemaFilePaths();

for (const file of files) {
const fileName = path.relative(this.options.folderPath, file);
const fileName = path.relative(this.options!.folderPath, file);
const { ext } = path.parse(fileName);
const sourceName = fileName.replace(new RegExp(`\\${ext}$`), '');
yield {
@@ -36,7 +53,7 @@ export class FileSchemaReader implements SchemaReader {
private async getSchemaFilePaths(): Promise<string[]> {
return new Promise((resolve, reject) => {
glob(
path.resolve(this.options.folderPath, '**', '*.yaml'),
path.resolve(this.options!.folderPath, '**', '*.yaml'),
{ nodir: true },
(err, files) => {
if (err) return reject(err);
4 changes: 3 additions & 1 deletion packages/build/src/lib/schema-parser/schema-reader/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './schemaReader';
import { FileSchemaReader } from './fileSchemaReader';
export const builtInSchemaReader = [FileSchemaReader];

export * from './fileSchemaReader';
14 changes: 0 additions & 14 deletions packages/build/src/lib/schema-parser/schema-reader/schemaReader.ts

This file was deleted.

7 changes: 5 additions & 2 deletions packages/build/src/lib/schema-parser/schemaParser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { APISchema, AllTemplateMetadata } from '@vulcan-sql/core';
import { SchemaData, SchemaFormat, SchemaReader } from './schema-reader';
import {
SchemaData,
SchemaFormat,
SchemaReader,
} from '@vulcan-sql/build/models';
import * as yaml from 'js-yaml';
import { RawAPISchema, SchemaParserMiddleware } from './middleware';
import * as compose from 'koa-compose';
@@ -12,7 +16,6 @@ import {
} from 'inversify';
import { TYPES } from '@vulcan-sql/build/containers';
import { SchemaParserOptions } from '@vulcan-sql/build/options';

export interface SchemaParseResult {
schemas: APISchema[];
}
1 change: 0 additions & 1 deletion packages/build/src/lib/spec-generator/index.ts

This file was deleted.

30 changes: 0 additions & 30 deletions packages/build/src/lib/spec-generator/specGenerator.ts

This file was deleted.

6 changes: 6 additions & 0 deletions packages/build/src/lib/vulcanBuilder.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import {
TYPES as CORE_TYPES,
VulcanArtifactBuilder,
} from '@vulcan-sql/core';
import { DocumentGenerator } from './document-generator';

export class VulcanBuilder {
public async build(options: IBuildOptions) {
@@ -18,12 +19,17 @@ export class VulcanBuilder {
const artifactBuilder = container.get<VulcanArtifactBuilder>(
CORE_TYPES.ArtifactBuilder
);
const documentGenerator = container.get<DocumentGenerator>(
TYPES.DocumentGenerator
);

const { metadata, templates } = await templateEngine.compile();
const { schemas } = await schemaParser.parse({ metadata });

await artifactBuilder.build({ schemas, templates });

await documentGenerator.generateDocuments(schemas);

await container.unload();
}
}
7 changes: 3 additions & 4 deletions packages/build/src/models/buildOptions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ICoreOptions } from '@vulcan-sql/core';
import { IDocumentGeneratorOptions } from './documentGeneratorOptions';
import { ISchemaParserOptions } from './schemaParserOptions';

export interface IBuildOptions extends ICoreOptions {
name?: string;
description?: string;
version?: string;
schemaParser: ISchemaParserOptions;
'schema-parser'?: ISchemaParserOptions;
'document-generator'?: IDocumentGeneratorOptions;
}
4 changes: 4 additions & 0 deletions packages/build/src/models/documentGeneratorOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IDocumentGeneratorOptions {
specs?: string[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could add some comment to expose what the data provide to spec and give some sample, e.g: oas3

Copy link
Contributor Author

@oscar60310 oscar60310 Aug 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've set the built-in enum as a union type, it might help us to know more about the property.

export enum DocumentGeneratorSpec {
  oas3 = 'oas3',
}

export interface IDocumentGeneratorOptions {
  specs?: (string | DocumentGeneratorSpec)[];
  folderPath: string;
}

BTW, it takes no effect when using the interface:
image

folderPath: string;
}
2 changes: 2 additions & 0 deletions packages/build/src/models/extensions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './schemaReader';
export * from './specGenerator';
18 changes: 18 additions & 0 deletions packages/build/src/models/extensions/schemaReader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ExtensionBase, VulcanExtension } from '@vulcan-sql/core';
import { TYPES } from '../../containers/types';

export enum SchemaFormat {
YAML = 'YAML',
}

export interface SchemaData {
/** The identifier of this schema, we might use this name to mapping SQL sources. */
sourceName: string;
content: string;
type: SchemaFormat;
}

@VulcanExtension(TYPES.Extension_SchemaReader, { enforcedId: true })
export abstract class SchemaReader<C = any> extends ExtensionBase<C> {
abstract readSchema(): AsyncGenerator<SchemaData>;
}
37 changes: 37 additions & 0 deletions packages/build/src/models/extensions/specGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
APISchema,
ExtensionBase,
ProjectOptions,
TYPES as CORE_TYPES,
VulcanExtension,
} from '@vulcan-sql/core';
import { inject } from 'inversify';
import { TYPES } from '../../containers/types';

@VulcanExtension(TYPES.Extension_SpecGenerator, { enforcedId: true })
export abstract class SpecGenerator<T = any, C = any> extends ExtensionBase<C> {
abstract getSpec(schemas: APISchema[]): T;

private projectOption: ProjectOptions;

constructor(
@inject(CORE_TYPES.ProjectOptions) projectOption: ProjectOptions,
@inject(CORE_TYPES.ExtensionName) moduleName: string,
@inject(CORE_TYPES.ExtensionConfig) config: C
) {
super(config, moduleName);
this.projectOption = projectOption;
}

protected getName() {
return this.projectOption.name || 'API Server';
}

protected getDescription() {
return this.projectOption.description;
}

protected getVersion() {
return this.projectOption.version || '0.0.1';
}
}
2 changes: 2 additions & 0 deletions packages/build/src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './buildOptions';
export * from './schemaParserOptions';
export * from './extensions';
export * from './documentGeneratorOptions';
6 changes: 1 addition & 5 deletions packages/build/src/models/schemaParserOptions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
export interface ISchemaParserOptions {
reader: SchemaReaderType;
reader: string;
folderPath: string;
}

export enum SchemaReaderType {
LocalFile = 'LocalFile',
}
28 changes: 28 additions & 0 deletions packages/build/src/options/documentGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { injectable, inject, optional } from 'inversify';
import { TYPES } from '@vulcan-sql/build/containers';
import { IDocumentGeneratorOptions } from '@vulcan-sql/build/models';
import { IsOptional, IsArray, validateSync, IsString } from 'class-validator';

@injectable()
export class DocumentGeneratorOptions implements IDocumentGeneratorOptions {
@IsArray()
@IsOptional()
public readonly specs = ['oas3'];

@IsString()
public readonly folderPath!: string;

constructor(
@inject(TYPES.DocumentGeneratorInputOptions)
@optional()
options: Partial<IDocumentGeneratorOptions> = {}
) {
Object.assign(this, options);
const errors = validateSync(this);
if (errors.length > 0) {
throw new Error(
'Invalid document generator options: ' + errors.join(', ')
);
}
}
}
1 change: 1 addition & 0 deletions packages/build/src/options/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './schemaParser';
export * from './documentGenerator';
7 changes: 2 additions & 5 deletions packages/build/src/options/schemaParser.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { injectable, inject, optional } from 'inversify';
import { TYPES } from '@vulcan-sql/build/containers';
import {
ISchemaParserOptions,
SchemaReaderType,
} from '@vulcan-sql/build/models';
import { ISchemaParserOptions } from '@vulcan-sql/build/models';
import { IsOptional, IsString, validateSync } from 'class-validator';

@injectable()
export class SchemaParserOptions implements ISchemaParserOptions {
@IsString()
public readonly reader: SchemaReaderType = SchemaReaderType.LocalFile;
public readonly reader = 'LocalFile';

@IsString()
@IsOptional()
3 changes: 2 additions & 1 deletion packages/build/test/builder/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
result.json
result.json
spec-*.yaml
10 changes: 7 additions & 3 deletions packages/build/test/builder/builder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { VulcanBuilder } from '../../src';
import * as path from 'path';
import { IBuildOptions, SchemaReaderType } from '@vulcan-sql/build/models';
import { IBuildOptions } from '@vulcan-sql/build/models';

it('Builder.build should work', async () => {
// Arrange
const builder = new VulcanBuilder();
const options: IBuildOptions = {
schemaParser: {
reader: SchemaReaderType.LocalFile,
'schema-parser': {
reader: 'LocalFile',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think maybe you could still keep SchemaReaderType, PersistentStoreType, SerializerType, TemplateProviderType, and use .toString to assign value for preventing typo cases or possible renaming in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I removed the enum for these properties is to avoid casting when using customer extension like below:

const options: IBuildOptions = {
   'schema-parser': {
      reader: 's3' as SchemaReaderType,
    },
}

For avoiding typos in our code, I'll add these enums back, but I prefer to keep the interface the same (string type), so these enums only protect our internal code.

I'll put these enums into models folder, and TypeScript is smart enough to know the affinity between string and enums, so we don't need to use .toString function to cast them 😄

This is the final result:

  const options: IBuildOptions = {
    'schema-parser': {
      reader: SchemaReaderType.LocalFile,
      folderPath: path.resolve(__dirname, 'source'),
    },
    'document-generator': {
      specs: [DocumentGeneratorSpec.oas3],
      folderPath: path.resolve(__dirname),
    },
    artifact: {
      provider: ArtifactBuilderProviderType.LocalFile,
      serializer: ArtifactBuilderSerializerType.JSON,
      filePath: path.resolve(__dirname, 'result.json'),
    },
    template: {
      provider: TemplateProviderType.LocalFile,
      folderPath: path.resolve(__dirname, 'source'),
    },
    extensions: {},
  };

4b905db

folderPath: path.resolve(__dirname, 'source'),
},
'document-generator': {
specs: ['oas3'],
folderPath: path.resolve(__dirname),
},
artifact: {
provider: 'LocalFile',
serializer: 'JSON',
1 change: 1 addition & 0 deletions packages/build/test/document-generator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
spec-*.yaml
34 changes: 34 additions & 0 deletions packages/build/test/document-generator/document-generator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { SpecGenerator } from '@vulcan-sql/build';
import { DocumentGenerator } from '@vulcan-sql/build/doc-generator';
import * as sinon from 'ts-sinon';
import * as path from 'path';
import { promises as fs } from 'fs';
import faker from '@faker-js/faker';

it('Document generator should write YAML files while generating documents', async () => {
// Arrange
const mockSpec = { someSpec: faker.datatype.number() };
const documentGenerator = new DocumentGenerator(
(id: string) => {
const mockSpecGenerator = sinon.stubInterface<SpecGenerator>();
mockSpecGenerator.getSpec.returns(mockSpec);
mockSpecGenerator.getExtensionId.returns(id);
return mockSpecGenerator;
},
{
specs: ['spec1', 'spec2'],
folderPath: __dirname,
}
);

// Act
await documentGenerator.generateDocuments([]);

// Arrange
expect(
await fs.readFile(path.resolve(__dirname, 'spec-spec1.yaml'), 'utf-8')
).toEqual(`someSpec: ${mockSpec.someSpec}\n`);
expect(
await fs.readFile(path.resolve(__dirname, 'spec-spec2.yaml'), 'utf-8')
).toEqual(`someSpec: ${mockSpec.someSpec}\n`);
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { OAS3SpecGenerator } from '@vulcan-sql/build/spec-generator';
import { OAS3SpecGenerator } from '@vulcan-sql/build/doc-generator';

it('Should throw error with invalid fieldIn', async () => {
// Arrange
const generator = new OAS3SpecGenerator(
[
const generator = new OAS3SpecGenerator({}, '', {});
// Act, Arrange
expect(() =>
generator.getSpec([
{
urlPath: '/user',
request: [
@@ -13,19 +15,16 @@ it('Should throw error with invalid fieldIn', async () => {
},
],
} as any,
],
{} as any
);
// Act, Arrange
expect(() => generator.getSpec()).toThrow(
`FieldInType INVALID_VALUE is not supported`
);
])
).toThrow(`FieldInType INVALID_VALUE is not supported`);
});

it('Should throw error with invalid FieldType', async () => {
// Arrange
const generator = new OAS3SpecGenerator(
[
const generator = new OAS3SpecGenerator({}, '', {});
// Act, Arrange
expect(() =>
generator.getSpec([
{
urlPath: '/user',
request: [
@@ -36,11 +35,6 @@ it('Should throw error with invalid FieldType', async () => {
},
],
} as any,
],
{} as any
);
// Act, Arrange
expect(() => generator.getSpec()).toThrow(
`FieldDataType INVALID_VALUE is not supported`
);
])
).toThrow(`FieldDataType INVALID_VALUE is not supported`);
});
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { OAS3SpecGenerator } from '@vulcan-sql/build/spec-generator';
import { OAS3SpecGenerator } from '@vulcan-sql/build/doc-generator';
import { getSchemas, getConfig } from './schema';
import * as jsYaml from 'js-yaml';
import { promises as fs } from 'fs';
import * as path from 'path';
import { get } from 'lodash';

const getGenerator = async () => {
const schema = await getSchemas();
const config = getConfig();
return new OAS3SpecGenerator(schema, config);
return new OAS3SpecGenerator(config, '', {});
};

it('Should generate specification without error', async () => {
// Arrange
const generator = await getGenerator();
const schemas = await getSchemas();
// Act, Arrange
expect(async () => {
const spec = generator.getSpec();
const spec = generator.getSpec(schemas);
await fs.writeFile(
path.resolve(__dirname, 'oas3-spec.yaml'),
jsYaml.dump(spec),
@@ -28,8 +28,9 @@ it('Should generate specification without error', async () => {
it('Parameters in path should be converted to correct format', async () => {
// Arrange
const generator = await getGenerator();
const schemas = await getSchemas();
// Act
const spec = generator.getSpec();
const spec = generator.getSpec(schemas);
// Arrange
expect(Object.keys(spec.paths)[0]).toBe('/user/{id}');
expect(Object.keys(spec.paths)[1]).toBe('/user/{id}/order/{oid}');
@@ -39,8 +40,9 @@ it('Parameters in path should be converted to correct format', async () => {
it('Should extract the correct parameters', async () => {
// Arrange
const generator = await getGenerator();
const schemas = await getSchemas();
// Act
const spec = generator.getSpec();
const spec = generator.getSpec(schemas);
// Arrange
expect(spec.paths['/user/{id}']?.get.parameters[0]).toEqual(
expect.objectContaining({
@@ -93,8 +95,9 @@ it('Should extract the correct parameters', async () => {
it('Should extract the correct response', async () => {
// Arrange
const generator = await getGenerator();
const schemas = await getSchemas();
// Act
const spec = generator.getSpec();
const spec = generator.getSpec(schemas);
// Arrange
expect(
get(
@@ -198,8 +201,9 @@ it('Should extract the correct response', async () => {
it('Should extract correct errors', async () => {
// Arrange
const generator = await getGenerator();
const schemas = await getSchemas();
// Act
const spec = generator.getSpec();
const spec = generator.getSpec(schemas);
// Arrange
expect(
get(
@@ -240,8 +244,9 @@ it('Should extract correct errors', async () => {
it('Should extract correct API description', async () => {
// Arrange
const generator = await getGenerator();
const schemas = await getSchemas();
// Act
const spec = generator.getSpec();
const spec = generator.getSpec(schemas);
// Arrange
expect(get(spec, 'paths./user/{id}.get.description')).toBe(
'Get user information'
Original file line number Diff line number Diff line change
@@ -126,7 +126,7 @@ export const getConfig = (): IBuildOptions => {
// We don't care about the options of these components.
template: {} as any,
artifact: {} as any,
schemaParser: {} as any,
extensions: [] as any,
'schema-parser': {} as any,
extensions: {} as any,
};
};
6 changes: 3 additions & 3 deletions packages/build/test/options/schemaParserOptions.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ISchemaParserOptions, SchemaReaderType, TYPES } from '../../src';
import { ISchemaParserOptions, TYPES } from '../../src';
import { Container } from 'inversify';
import { SchemaParserOptions } from '../../src/options';

@@ -16,7 +16,7 @@ it('Should provide correct default option values', async () => {
// Action
const options = container.get<SchemaParserOptions>(TYPES.SchemaParserOptions);
// Assert
expect(options.reader).toBe(SchemaReaderType.LocalFile);
expect(options.reader).toBe('LocalFile');
});

it('Can override some option properties', async () => {
@@ -28,7 +28,7 @@ it('Can override some option properties', async () => {
});
const options = container.get<SchemaParserOptions>(TYPES.SchemaParserOptions);
// Assert
expect(options.reader).toBe(SchemaReaderType.LocalFile);
expect(options.reader).toBe('LocalFile');
expect(options.folderPath).toBe('./test/schemas');
});

Original file line number Diff line number Diff line change
@@ -1,37 +1,17 @@
import { FileSchemaReader, SchemaData } from '@vulcan-sql/build/schema-parser';
import { SchemaData } from '@vulcan-sql/build/models';
import { FileSchemaReader } from '@vulcan-sql/build/schema-parser';
import * as path from 'path';
import { Container } from 'inversify';
import { TYPES } from '@vulcan-sql/build/containers';
import { SchemaParserOptions } from '@vulcan-sql/build/options';
import {
ISchemaParserOptions,
SchemaReaderType,
} from '@vulcan-sql/build/models';

let container: Container;

beforeEach(() => {
container = new Container();
container
.bind<Partial<ISchemaParserOptions>>(TYPES.SchemaParserInputOptions)
.toConstantValue({
folderPath: path.resolve(__dirname, '../test-schema'),
reader: SchemaReaderType.LocalFile,
});
container
.bind(TYPES.SchemaParserOptions)
.to(SchemaParserOptions)
.inSingletonScope();
container.bind(TYPES.SchemaReader).to(FileSchemaReader).inSingletonScope();
});

afterEach(() => {
container.unbindAll();
});

it('File schema reader should provide correct files and contents', async () => {
// Arrange
const schemaReader = container.get<FileSchemaReader>(TYPES.SchemaReader);
const schemaReader = new FileSchemaReader(
{
folderPath: path.resolve(__dirname, '../test-schema'),
reader: 'LocalFile',
},
{},
''
);
const schemas: SchemaData[] = [];

// Act
Original file line number Diff line number Diff line change
@@ -1,33 +1,5 @@
import { FileSchemaReader } from '@vulcan-sql/build/schema-parser';
import * as path from 'path';
import { SchemaParserOptions } from '@vulcan-sql/build/options';
import { TYPES } from '@vulcan-sql/build/containers';
import {
ISchemaParserOptions,
SchemaReaderType,
} from '@vulcan-sql/build/models';
import { Container } from 'inversify';

let container: Container;

beforeEach(() => {
container = new Container();
container
.bind<Partial<ISchemaParserOptions>>(TYPES.SchemaParserInputOptions)
.toConstantValue({
folderPath: path.resolve(__dirname, '../test-schema'),
reader: SchemaReaderType.LocalFile,
});
container
.bind(TYPES.SchemaParserOptions)
.to(SchemaParserOptions)
.inSingletonScope();
container.bind(TYPES.SchemaReader).to(FileSchemaReader).inSingletonScope();
});

afterEach(() => {
container.unbindAll();
});

jest.mock('glob', () => {
return (
@@ -41,7 +13,14 @@ jest.mock('glob', () => {

it('File schema reader should throw error with file search errors', async () => {
// Arrange
const schemaReader = container.get<FileSchemaReader>(TYPES.SchemaReader);
const schemaReader = new FileSchemaReader(
{
folderPath: path.resolve(__dirname, '../test-schema'),
reader: 'LocalFile',
},
{},
''
);
// Act, Assert
const iter = schemaReader.readSchema();
await expect(iter.next()).rejects.toThrow('mock error');
24 changes: 14 additions & 10 deletions packages/build/test/schema-parser/schemaParser.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { TYPES } from '@vulcan-sql/build/containers';
import { extensionModule, TYPES } from '@vulcan-sql/build/containers';
import {
ISchemaParserOptions,
SchemaReaderType,
} from '@vulcan-sql/build/models';
import { SchemaParserOptions } from '@vulcan-sql/build/options';
import {
SchemaFormat,
SchemaParser,
SchemaReader,
} from '@vulcan-sql/build/schema-parser';
} from '@vulcan-sql/build/models';
import { SchemaParserOptions } from '@vulcan-sql/build/options';
import { SchemaParser } from '@vulcan-sql/build/schema-parser';
import { IValidatorLoader, TYPES as CORE_TYPES } from '@vulcan-sql/core';
import { Container } from 'inversify';
import * as sinon from 'ts-sinon';
@@ -17,19 +14,26 @@ let container: Container;
let stubSchemaReader: sinon.StubbedInstance<SchemaReader>;
let stubValidatorLoader: sinon.StubbedInstance<IValidatorLoader>;

beforeEach(() => {
beforeEach(async () => {
container = new Container();
stubSchemaReader = sinon.stubInterface<SchemaReader>();
stubValidatorLoader = sinon.stubInterface<IValidatorLoader>();

await container.loadAsync(
extensionModule({
schemaParser: {
folderPath: '',
},
} as any)
);

container
.bind(TYPES.Factory_SchemaReader)
.toConstantValue(() => stubSchemaReader);
container
.bind<Partial<ISchemaParserOptions>>(TYPES.SchemaParserInputOptions)
.toConstantValue({
folderPath: '',
reader: SchemaReaderType.LocalFile,
reader: 'LocalFile',
});
container
.bind(TYPES.SchemaParserOptions)
8 changes: 8 additions & 0 deletions packages/core/src/containers/container.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ICoreOptions } from '@vulcan-sql/core/models';
import { Container as InversifyContainer } from 'inversify';
import { ProjectOptions } from '../options';
import { extensionModule } from './modules';
import {
artifactBuilderModule,
executorModule,
templateEngineModule,
validatorLoaderModule,
} from './modules';
import { TYPES } from './types';

export class Container {
private inversifyContainer = new InversifyContainer();
@@ -16,6 +18,12 @@ export class Container {
}

public async load(options: ICoreOptions) {
// Project options
this.inversifyContainer
.bind(TYPES.ProjectInputOptions)
.toConstantValue(options);
this.inversifyContainer.bind(TYPES.ProjectOptions).to(ProjectOptions);

await this.inversifyContainer.loadAsync(
artifactBuilderModule(options.artifact)
);
3 changes: 3 additions & 0 deletions packages/core/src/containers/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export const TYPES = {
// Root options
ProjectOptions: Symbol.for('ProjectOptions'),
ProjectInputOptions: Symbol.for('ProjectInputOptions'),
// Artifact builder
Factory_PersistentStore: Symbol.for('Factory_PersistentStore'),
Factory_Serializer: Symbol.for('Factory_Serializer'),
3 changes: 3 additions & 0 deletions packages/core/src/models/coreOptions.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,9 @@ import { ITemplateEngineOptions } from './templateEngineOptions';
export type ExtensionAliases = Record<string, string | string[]>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding a comment to provide the example and explain the record key and value represented.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I've add comments about module gorups.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks alot !


export interface ICoreOptions {
name?: string;
description?: string;
version?: string;
artifact: IArtifactBuilderOptions;
template?: ITemplateEngineOptions;
/**
10 changes: 5 additions & 5 deletions packages/core/src/models/extensions/base.ts
Original file line number Diff line number Diff line change
@@ -24,14 +24,14 @@ export abstract class ExtensionBase<C = any> {
this.moduleName = moduleName;
}

protected getConfig(): C | undefined {
return this.config;
}

protected getExtensionId(): string | undefined {
public getExtensionId(): string | undefined {
return Reflect.getMetadata(
EXTENSION_IDENTIFIER_METADATA_KEY,
this.constructor
);
}

protected getConfig(): C | undefined {
return this.config;
}
}
1 change: 1 addition & 0 deletions packages/core/src/options/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './artifactBuilder';
export * from './templateEngine';
export * from './project';
32 changes: 32 additions & 0 deletions packages/core/src/options/project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { IsOptional, IsString, validateSync } from 'class-validator';
import { inject, injectable, optional } from 'inversify';
import { TYPES } from '../containers';
import { ICoreOptions } from '../models';

/** Root level options */
@injectable()
export class ProjectOptions implements Partial<ICoreOptions> {
@IsString()
@IsOptional()
public readonly name?: string;

@IsString()
@IsOptional()
public readonly description?: string;

@IsString()
@IsOptional()
public readonly version?: string;

constructor(
@inject(TYPES.ProjectInputOptions)
@optional()
options: Partial<ICoreOptions> = {}
) {
Object.assign(this, options);
const errors = validateSync(this);
if (errors.length > 0) {
throw new Error('Invalid root options: ' + errors.join(', '));
}
}
}
8 changes: 4 additions & 4 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
@@ -26,11 +26,11 @@
"@vulcan-sql/build/schema-parser/*": [
"packages/build/src/lib/schema-parser/*"
],
"@vulcan-sql/build/spec-generator": [
"packages/build/src/lib/spec-generator/index"
"@vulcan-sql/build/doc-generator": [
"packages/build/src/lib/document-generator/index"
],
"@vulcan-sql/build/spec-generator/*": [
"packages/build/src/lib/spec-generator/*"
"@vulcan-sql/build/doc-generator/*": [
"packages/build/src/lib/document-generator/*"
],
"@vulcan-sql/core": ["packages/core/src/index"],
"@vulcan-sql/core/artifact-builder": [