Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions packages/build/src/containers/modules/schemaParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { ContainerModule, 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) => {
Expand All @@ -33,4 +34,9 @@ export const schemaParserModule = (options: ISchemaParserOptions) =>

// Schema parser
bind<SchemaParser>(TYPES.SchemaParser).to(SchemaParser).inSingletonScope();

// Middleware
for (const middleware of SchemaParserMiddlewares) {
bind(TYPES.SchemaParserMiddleware).to(middleware);
}
});
1 change: 1 addition & 0 deletions packages/build/src/containers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export const TYPES = {
SchemaReader: Symbol.for('SchemaReader'),
Factory_SchemaReader: Symbol.for('Factory_SchemaReader'),
SchemaParser: Symbol.for('SchemaParser'),
SchemaParserMiddleware: Symbol.for('SchemaParserMiddleware'),
};
2 changes: 1 addition & 1 deletion packages/build/src/lib/schema-parser/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './schema-reader';
export * from './schemaParser';
export { RawAPISchema, SchemaParserMiddleware } from './middleware';
export * from './middleware';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AllTemplateMetadata, APISchema } from '@vulcan-sql/core';
import { SchemaParserMiddleware } from './middleware';
import { APISchema } from '@vulcan-sql/core';
import { RawAPISchema, SchemaParserMiddleware } from './middleware';

interface ErrorCode {
code: string;
Expand All @@ -8,13 +8,11 @@ interface ErrorCode {
}

// Add error code to definition if it is used in query but not defined in schema
export const addMissingErrors =
(allMetadata: AllTemplateMetadata): SchemaParserMiddleware =>
async (schemas, next) => {
export class AddMissingErrors extends SchemaParserMiddleware {
public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
await next();
const transformedSchemas = schemas as APISchema;
const templateName = transformedSchemas.templateSource;
const metadata = allMetadata[templateName];
const metadata = schemas.metadata;
// Skip validation if no metadata found
if (!metadata?.['error.vulcan.com']) return;

Expand All @@ -27,4 +25,5 @@ export const addMissingErrors =
});
}
});
};
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { FieldInType } from '@vulcan-sql/core';
import { SchemaParserMiddleware } from './middleware';
import { RawAPISchema, SchemaParserMiddleware } from './middleware';

// Add the "required" validator when the parameters are in path
export const addRequiredValidatorForPath =
(): SchemaParserMiddleware => async (schemas, next) => {
export class AddRequiredValidatorForPath extends SchemaParserMiddleware {
public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
await next();
const requests = schemas.request || [];
for (const request of requests) {
Expand All @@ -20,4 +20,5 @@ export const addRequiredValidatorForPath =
});
}
}
};
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { AllTemplateMetadata, APISchema } from '@vulcan-sql/core';
import { SchemaParserMiddleware } from './middleware';
import { APISchema } from '@vulcan-sql/core';
import { RawAPISchema, SchemaParserMiddleware } from './middleware';

interface Parameter {
name: string;
lineNo: number;
columnNo: number;
}

export const checkParameter =
(allMetadata: AllTemplateMetadata): SchemaParserMiddleware =>
async (schemas, next) => {
export class CheckParameter extends SchemaParserMiddleware {
public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
await next();
const transformedSchemas = schemas as APISchema;
const templateName = transformedSchemas.templateSource;
const metadata = allMetadata[templateName];
const metadata = schemas.metadata;
// Skip validation if no metadata found
if (!metadata?.['parameter.vulcan.com']) return;

Expand All @@ -31,4 +29,5 @@ export const checkParameter =
);
}
});
};
}
}
28 changes: 21 additions & 7 deletions packages/build/src/lib/schema-parser/middleware/checkValidator.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { SchemaParserMiddleware } from './middleware';
import { RawAPISchema, SchemaParserMiddleware } from './middleware';
import { chain } from 'lodash';
import { APISchema, IValidatorLoader } from '@vulcan-sql/core';
import {
APISchema,
IValidatorLoader,
TYPES as CORE_TYPES,
} from '@vulcan-sql/core';
import { inject } from 'inversify';

export const checkValidator =
(loader: IValidatorLoader): SchemaParserMiddleware =>
async (schemas, next) => {
export class CheckValidator extends SchemaParserMiddleware {
private validatorLoader: IValidatorLoader;

constructor(
@inject(CORE_TYPES.ValidatorLoader) validatorLoader: IValidatorLoader
) {
super();
this.validatorLoader = validatorLoader;
}

public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
await next();
const transformedSchemas = schemas as APISchema;
const validators = chain(transformedSchemas.request)
Expand All @@ -16,9 +29,10 @@ export const checkValidator =
throw new Error('Validator name is required');
}

const validator = await loader.load(validatorRequest.name);
const validator = await this.validatorLoader.load(validatorRequest.name);

// TODO: indicate the detail of error
validator.validateSchema(validatorRequest.args);
}
};
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { SchemaParserMiddleware } from './middleware';
import { RawAPISchema, SchemaParserMiddleware } from './middleware';

export const fallbackErrors =
(): SchemaParserMiddleware => async (schemas, next) => {
export class FallbackErrors extends SchemaParserMiddleware {
public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
if (schemas.errors) return next();
schemas.errors = [];
return next();
};
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { FieldDataType } from '@vulcan-sql/core';
import { DeepPartial } from 'ts-essentials';
import { RawResponseProperty, SchemaParserMiddleware } from './middleware';
import {
RawAPISchema,
RawResponseProperty,
SchemaParserMiddleware,
} from './middleware';

const generateResponsePropertyType = (
property: DeepPartial<RawResponseProperty>
Expand All @@ -16,8 +20,8 @@ const generateResponsePropertyType = (

// Fallback to string when type is not defined.
// TODO: Guess the type by validators.
export const generateDataType =
(): SchemaParserMiddleware => async (schemas, next) => {
export class GenerateDataType extends SchemaParserMiddleware {
public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
await next();
(schemas.request || []).forEach((request) => {
if (!request.type) {
Expand All @@ -27,4 +31,5 @@ export const generateDataType =
(schemas.response || []).forEach((property) =>
generateResponsePropertyType(property)
);
};
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { FieldDataType, FieldInType } from '@vulcan-sql/core';
import { SchemaParserMiddleware } from './middleware';
import { RawAPISchema, SchemaParserMiddleware } from './middleware';

// /user/{id} => {request: [{fieldName: 'id', fieldIn: 'path' ....}]}
export const generatePathParameters =
(): SchemaParserMiddleware => async (schema, next) => {
export class GeneratePathParameters extends SchemaParserMiddleware {
public async handle(schema: RawAPISchema, next: () => Promise<void>) {
await next();
const pattern = /:([^/]+)/g;
const pathParameters: string[] = [];
Expand Down Expand Up @@ -31,4 +31,5 @@ export const generatePathParameters =
})
);
schema.request = request;
};
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { SchemaParserMiddleware } from './middleware';
import { RawAPISchema, SchemaParserMiddleware } from './middleware';

// Use schema sourceName which was generated by schema reader as templateSource when it wan't defined.
// It usually be the path file of schema file.
export const generateTemplateSource =
(): SchemaParserMiddleware => async (schemas, next) => {
export class GenerateTemplateSource extends SchemaParserMiddleware {
public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
if (schemas.templateSource) return next();
schemas.templateSource = schemas.sourceName;
return next();
};
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SchemaParserMiddleware } from './middleware';
import { RawAPISchema, SchemaParserMiddleware } from './middleware';

export const generateUrl =
(): SchemaParserMiddleware => async (schemas, next) => {
export class GenerateUrl extends SchemaParserMiddleware {
public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
if (schemas.urlPath) return next();

let urlPath = schemas.sourceName.toLocaleLowerCase();
Expand All @@ -19,4 +19,5 @@ export const generateUrl =
schemas.urlPath = urlPath;

return next();
};
}
}
48 changes: 35 additions & 13 deletions packages/build/src/lib/schema-parser/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
import { ClassType } from '@vulcan-sql/core';
import { GenerateUrl } from './generateUrl';
import { CheckValidator } from './checkValidator';
import { TransformValidator } from './transformValidator';
import { GenerateTemplateSource } from './generateTemplateSource';
import { CheckParameter } from './checkParameter';
import { AddMissingErrors } from './addMissingErrors';
import { FallbackErrors } from './fallbackErrors';
import { NormalizeFieldIn } from './normalizeFieldIn';
import { GenerateDataType } from './generateDataType';
import { NormalizeDataType } from './normalizeDataType';
import { GeneratePathParameters } from './generatePathParameters';
import { AddRequiredValidatorForPath } from './addRequiredValidatorForPath';
import { SetConstraints } from './setConstraints';
import { SchemaParserMiddleware } from './middleware';
import { ResponseSampler } from './responseSampler';

export * from './middleware';
export * from './generateUrl';
export * from './checkValidator';
export * from './transformValidator';
export * from './generateTemplateSource';
export * from './checkParameter';
export * from './addMissingErrors';
export * from './fallbackErrors';
export * from './normalizeFieldIn';
export * from './generateDataType';
export * from './normalizeDataType';
export * from './generatePathParameters';
export * from './addRequiredValidatorForPath';
export * from './setConstraints';

// The order of middleware here indicates the order of their execution, the first one will be executed first, and so on.
export const SchemaParserMiddlewares: ClassType<SchemaParserMiddleware>[] = [
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we could add a comment to let other members know the array order will become the compose order for middleware executing, or if other members modified the order and some middleware have the relationship of reading/writing data, it will have some influence.

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 added.

// The order of middleware here indicates the order of their execution, the first one will be executed first, and so on.
export const SchemaParserMiddlewares: ClassType<SchemaParserMiddleware>[] = [
  GenerateUrl,
  CheckValidator,
  TransformValidator,
  GenerateTemplateSource,
  CheckParameter,
  AddMissingErrors,
  FallbackErrors,
  NormalizeFieldIn,
  GenerateDataType,
  NormalizeDataType,
  GeneratePathParameters,
  AddRequiredValidatorForPath,
  SetConstraints,
  ResponseSampler,
];

GenerateUrl,
CheckValidator,
TransformValidator,
GenerateTemplateSource,
CheckParameter,
AddMissingErrors,
FallbackErrors,
NormalizeFieldIn,
GenerateDataType,
NormalizeDataType,
GeneratePathParameters,
AddRequiredValidatorForPath,
SetConstraints,
ResponseSampler,
];
10 changes: 8 additions & 2 deletions packages/build/src/lib/schema-parser/middleware/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ResponseProperty,
ValidatorDefinition,
} from '@vulcan-sql/core';
import { injectable } from 'inversify';
import { DeepPartial } from 'ts-essentials';

export interface RawRequestParameter
Expand All @@ -22,8 +23,13 @@ export interface RawAPISchema
sourceName: string;
request?: DeepPartial<RawRequestParameter[]>;
response?: DeepPartial<RawResponseProperty[]>;
metadata?: Record<string, any>;
}

export interface SchemaParserMiddleware {
(schema: RawAPISchema, next: () => Promise<void>): Promise<void>;
@injectable()
export abstract class SchemaParserMiddleware {
abstract handle(
schema: RawAPISchema,
next: () => Promise<void>
): Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { FieldDataType } from '@vulcan-sql/core';
import { DeepPartial } from 'ts-essentials';
import { RawResponseProperty, SchemaParserMiddleware } from './middleware';
import {
RawAPISchema,
RawResponseProperty,
SchemaParserMiddleware,
} from './middleware';

const normalizedResponsePropertyType = (
property: DeepPartial<RawResponseProperty>
Expand All @@ -15,8 +19,8 @@ const normalizedResponsePropertyType = (
};

// type: string => FieldIn FieldDataType.STRING
export const normalizeDataType =
(): SchemaParserMiddleware => async (schemas, next) => {
export class NormalizeDataType extends SchemaParserMiddleware {
public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
// Request
(schemas.request || []).forEach((request) => {
if (request.type) {
Expand All @@ -29,4 +33,5 @@ export const normalizeDataType =
);

return next();
};
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { FieldInType } from '@vulcan-sql/core';
import { SchemaParserMiddleware } from './middleware';
import { RawAPISchema, SchemaParserMiddleware } from './middleware';

// FieldIn: query => FieldIn FieldInType.QUERY
export const normalizeFieldIn =
(): SchemaParserMiddleware => async (schemas, next) => {
export class NormalizeFieldIn extends SchemaParserMiddleware {
public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
(schemas.request || []).forEach((request) => {
if (request.fieldIn) {
request.fieldIn = request.fieldIn.toUpperCase() as FieldInType;
}
});
return next();
};
}
}
Loading