Skip to content

Commit

Permalink
Merge pull request #119 from Canner/feature/add-resources-for-catalog
Browse files Browse the repository at this point in the history
Feature: Serialize/Deserialize all artifacts
  • Loading branch information
kokokuo authored Oct 21, 2022
2 parents f7de48f + 3c61116 commit fbef2ab
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 38 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
"private": true,
"dependencies": {
"@koa/cors": "^3.3.0",
"chokidar": "^3.5.3",
"bluebird": "^3.7.2",
"bcryptjs": "^2.4.3",
"bluebird": "^3.7.2",
"bytes": "^3.1.2",
"chokidar": "^3.5.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"commander": "^9.4.0",
"dayjs": "^1.11.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/test/init.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ it('Init command with folder path should create default config in target folder'
);
// Assert
expect(config.name).toBe(projectName);
}, 30000);
}, 60000);
10 changes: 7 additions & 3 deletions packages/core/src/lib/artifact-builder/vulcanArtifactBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ArtifactBuilder } from './artifactBuilder';
import { PersistentStore } from '@vulcan-sql/core/models';
import { BuiltArtifact, PersistentStore } from '@vulcan-sql/core/models';
import { Serializer } from '@vulcan-sql/core/models';
import { inject, injectable } from 'inversify';
import { TYPES } from '@vulcan-sql/core/types';
import { InternalError } from '../utils';
import { plainToInstance, instanceToPlain } from 'class-transformer';

@injectable()
export class VulcanArtifactBuilder implements ArtifactBuilder {
Expand All @@ -22,13 +23,16 @@ export class VulcanArtifactBuilder implements ArtifactBuilder {
}

public async build(): Promise<void> {
const serializedArtifact = this.serializer.serialize(this.artifact);
const artifactInPureObject = instanceToPlain(this.artifact);
const serializedArtifact = this.serializer.serialize(artifactInPureObject);
await this.persistentStore.save(serializedArtifact);
}

public async load(): Promise<void> {
const serializedArtifact = await this.persistentStore.load();
this.artifact = this.serializer.deserialize(serializedArtifact);
const artifactInPureObject =
this.serializer.deserialize(serializedArtifact);
this.artifact = plainToInstance(BuiltArtifact, artifactInPureObject);
}

public getArtifact<T = any>(key: string): T {
Expand Down
34 changes: 34 additions & 0 deletions packages/core/src/lib/validators/constraints.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { intersection } from 'lodash';
import { InternalError } from '../utils';
import { DiscriminatorDescriptor } from 'class-transformer';

export abstract class Constraint {
abstract __type: string;

static Required() {
return new RequiredConstraint();
}
Expand Down Expand Up @@ -38,13 +41,17 @@ export abstract class Constraint {
}

export class RequiredConstraint extends Constraint {
__type = 'Required';

public compose() {
// No matter what other required constraint is, we always return a required constraint
return new RequiredConstraint();
}
}

export class MinValueConstraint extends Constraint {
__type = 'MinValue';

constructor(private minValue: number, private exclusive = false) {
super();
}
Expand Down Expand Up @@ -72,6 +79,8 @@ export class MinValueConstraint extends Constraint {
}

export class MaxValueConstraint extends Constraint {
__type = 'MaxValue';

constructor(private maxValue: number, private exclusive = false) {
super();
}
Expand Down Expand Up @@ -99,6 +108,8 @@ export class MaxValueConstraint extends Constraint {
}

export class MinLengthConstraint extends Constraint {
__type = 'MinLength';

constructor(private minLength: number) {
super();
}
Expand All @@ -115,6 +126,8 @@ export class MinLengthConstraint extends Constraint {
}

export class MaxLengthConstraint extends Constraint {
__type = 'MaxLength';

constructor(private maxLength: number) {
super();
}
Expand All @@ -131,6 +144,8 @@ export class MaxLengthConstraint extends Constraint {
}

export class RegexConstraint extends Constraint {
__type = 'Regex';

constructor(private regex: string) {
super();
}
Expand All @@ -147,6 +162,8 @@ export class RegexConstraint extends Constraint {
}

export class EnumConstraint<T = string> extends Constraint {
__type = 'Enum';

constructor(private list: Array<T>) {
super();
}
Expand All @@ -171,6 +188,8 @@ export type TypeConstraintType =
| 'object';

export class TypeConstraint extends Constraint {
__type = 'Type';

constructor(private type: TypeConstraintType) {
super();
}
Expand All @@ -185,3 +204,18 @@ export class TypeConstraint extends Constraint {
);
}
}

// https://github.com/typestack/class-transformer/tree/master#providing-more-than-one-type-option
export const ConstraintDiscriminator: DiscriminatorDescriptor = {
property: '__type',
subTypes: [
{ value: RequiredConstraint, name: 'Required' },
{ value: MinValueConstraint, name: 'MinValue' },
{ value: MaxValueConstraint, name: 'MaxValue' },
{ value: MinLengthConstraint, name: 'MinLength' },
{ value: MaxLengthConstraint, name: 'MaxLength' },
{ value: RegexConstraint, name: 'Regex' },
{ value: EnumConstraint, name: 'Enum' },
{ value: TypeConstraint, name: 'Type' },
],
};
81 changes: 49 additions & 32 deletions packages/core/src/models/artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,17 @@ error:
message: 'You are not allowed to access this resource'
*/

import { Constraint } from '../lib/validators/constraints';
// This is the model of our built result
// It will be serialized and deserialized by class-transformer
// https://github.com/typestack/class-transformer/tree/master
// So we should use classes instead of interfaces.

import {
Constraint,
ConstraintDiscriminator,
} from '../lib/validators/constraints';
import { Type } from 'class-transformer';
import 'reflect-metadata';

// Pagination mode should always be UPPERCASE because schema parser will transform the user inputs.
export enum PaginationMode {
Expand All @@ -39,62 +49,69 @@ export enum FieldDataType {
STRING = 'STRING',
}

export interface ValidatorDefinition<T = any> {
name: string;
args: T;
export class ValidatorDefinition<T = any> {
name!: string;
args!: T;
}

export interface RequestSchema {
fieldName: string;
export class RequestSchema {
fieldName!: string;
// the field put in query parameter or headers
fieldIn: FieldInType;
description: string;
type: FieldDataType;
validators: Array<ValidatorDefinition>;
constraints: Array<Constraint>;
fieldIn!: FieldInType;
description!: string;
type!: FieldDataType;
validators!: Array<ValidatorDefinition>;
@Type(() => Constraint, {
discriminator: ConstraintDiscriminator,
})
constraints!: Array<Constraint>;
}

export interface ResponseProperty {
name: string;
export class ResponseProperty {
name!: string;
description?: string;
type: FieldDataType | Array<ResponseProperty>;
type!: FieldDataType | Array<ResponseProperty>;
required?: boolean;
}

export interface PaginationSchema {
mode: PaginationMode;
export class PaginationSchema {
mode!: PaginationMode;
// The key name used for do filtering by key for keyset pagination.
keyName?: string;
}

export interface ErrorInfo {
code: string;
message: string;
export class ErrorInfo {
code!: string;
message!: string;
}

export interface Sample {
profile: string;
parameters: Record<string, any>;
export class Sample {
profile!: string;
parameters!: Record<string, any>;
}

export interface APISchema {
export class APISchema {
// graphql operation name
operationName: string;
operationName!: string;
// restful url path
urlPath: string;
urlPath!: string;
// template, could be name or path
templateSource: string;
request: Array<RequestSchema>;
errors: Array<ErrorInfo>;
response: Array<ResponseProperty>;
templateSource!: string;
@Type(() => RequestSchema)
request!: Array<RequestSchema>;
@Type(() => ErrorInfo)
errors!: Array<ErrorInfo>;
@Type(() => ResponseProperty)
response!: Array<ResponseProperty>;
description?: string;
// The pagination strategy that do paginate when querying
// If not set pagination, then API request not provide the field to do it
pagination?: PaginationSchema;
sample?: Sample;
profiles: Array<string>;
profiles!: Array<string>;
}

export interface BuiltArtifact {
apiSchemas: Array<APISchema>;
export class BuiltArtifact {
@Type(() => APISchema)
schemas!: Array<APISchema>;
}
30 changes: 30 additions & 0 deletions packages/core/test/validators/constraint-serialize.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { plainToInstance, instanceToPlain } from 'class-transformer';
import { RequestSchema } from '@vulcan-sql/core/models';
import { Constraint, FieldDataType, FieldInType } from '@vulcan-sql/core';

it('Every constraint can be serialize and deserialize', async () => {
// Arrange
const constraints = [
Constraint.Required(),
Constraint.MinValue(10),
Constraint.MaxValue(100, true),
Constraint.MinLength(10),
Constraint.MaxLength(100),
Constraint.Enum([1, 2, 3, 4]),
Constraint.Regex('.+'),
Constraint.Type('array'),
];
const request: RequestSchema = {
fieldName: 'test',
fieldIn: FieldInType.QUERY,
description: '',
type: FieldDataType.BOOLEAN,
validators: [],
constraints,
};
// Act
const plainObject = instanceToPlain(request);
const instance = plainToInstance(RequestSchema, plainObject);
// Assert
expect(instance).toEqual(request);
});
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2247,6 +2247,11 @@ cjs-module-lexer@^1.0.0:
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==

class-transformer@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336"
integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==

class-utils@^0.3.5:
version "0.3.6"
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
Expand Down

0 comments on commit fbef2ab

Please sign in to comment.