Skip to content

Commit fbef2ab

Browse files
authored
Merge pull request #119 from Canner/feature/add-resources-for-catalog
Feature: Serialize/Deserialize all artifacts
2 parents f7de48f + 3c61116 commit fbef2ab

File tree

7 files changed

+129
-38
lines changed

7 files changed

+129
-38
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
"private": true,
99
"dependencies": {
1010
"@koa/cors": "^3.3.0",
11-
"chokidar": "^3.5.3",
12-
"bluebird": "^3.7.2",
1311
"bcryptjs": "^2.4.3",
12+
"bluebird": "^3.7.2",
1413
"bytes": "^3.1.2",
14+
"chokidar": "^3.5.3",
15+
"class-transformer": "^0.5.1",
1516
"class-validator": "^0.13.2",
1617
"commander": "^9.4.0",
1718
"dayjs": "^1.11.2",

packages/cli/test/init.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ it('Init command with folder path should create default config in target folder'
4040
);
4141
// Assert
4242
expect(config.name).toBe(projectName);
43-
}, 30000);
43+
}, 60000);

packages/core/src/lib/artifact-builder/vulcanArtifactBuilder.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { ArtifactBuilder } from './artifactBuilder';
2-
import { PersistentStore } from '@vulcan-sql/core/models';
2+
import { BuiltArtifact, PersistentStore } from '@vulcan-sql/core/models';
33
import { Serializer } from '@vulcan-sql/core/models';
44
import { inject, injectable } from 'inversify';
55
import { TYPES } from '@vulcan-sql/core/types';
66
import { InternalError } from '../utils';
7+
import { plainToInstance, instanceToPlain } from 'class-transformer';
78

89
@injectable()
910
export class VulcanArtifactBuilder implements ArtifactBuilder {
@@ -22,13 +23,16 @@ export class VulcanArtifactBuilder implements ArtifactBuilder {
2223
}
2324

2425
public async build(): Promise<void> {
25-
const serializedArtifact = this.serializer.serialize(this.artifact);
26+
const artifactInPureObject = instanceToPlain(this.artifact);
27+
const serializedArtifact = this.serializer.serialize(artifactInPureObject);
2628
await this.persistentStore.save(serializedArtifact);
2729
}
2830

2931
public async load(): Promise<void> {
3032
const serializedArtifact = await this.persistentStore.load();
31-
this.artifact = this.serializer.deserialize(serializedArtifact);
33+
const artifactInPureObject =
34+
this.serializer.deserialize(serializedArtifact);
35+
this.artifact = plainToInstance(BuiltArtifact, artifactInPureObject);
3236
}
3337

3438
public getArtifact<T = any>(key: string): T {

packages/core/src/lib/validators/constraints.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { intersection } from 'lodash';
22
import { InternalError } from '../utils';
3+
import { DiscriminatorDescriptor } from 'class-transformer';
34

45
export abstract class Constraint {
6+
abstract __type: string;
7+
58
static Required() {
69
return new RequiredConstraint();
710
}
@@ -38,13 +41,17 @@ export abstract class Constraint {
3841
}
3942

4043
export class RequiredConstraint extends Constraint {
44+
__type = 'Required';
45+
4146
public compose() {
4247
// No matter what other required constraint is, we always return a required constraint
4348
return new RequiredConstraint();
4449
}
4550
}
4651

4752
export class MinValueConstraint extends Constraint {
53+
__type = 'MinValue';
54+
4855
constructor(private minValue: number, private exclusive = false) {
4956
super();
5057
}
@@ -72,6 +79,8 @@ export class MinValueConstraint extends Constraint {
7279
}
7380

7481
export class MaxValueConstraint extends Constraint {
82+
__type = 'MaxValue';
83+
7584
constructor(private maxValue: number, private exclusive = false) {
7685
super();
7786
}
@@ -99,6 +108,8 @@ export class MaxValueConstraint extends Constraint {
99108
}
100109

101110
export class MinLengthConstraint extends Constraint {
111+
__type = 'MinLength';
112+
102113
constructor(private minLength: number) {
103114
super();
104115
}
@@ -115,6 +126,8 @@ export class MinLengthConstraint extends Constraint {
115126
}
116127

117128
export class MaxLengthConstraint extends Constraint {
129+
__type = 'MaxLength';
130+
118131
constructor(private maxLength: number) {
119132
super();
120133
}
@@ -131,6 +144,8 @@ export class MaxLengthConstraint extends Constraint {
131144
}
132145

133146
export class RegexConstraint extends Constraint {
147+
__type = 'Regex';
148+
134149
constructor(private regex: string) {
135150
super();
136151
}
@@ -147,6 +162,8 @@ export class RegexConstraint extends Constraint {
147162
}
148163

149164
export class EnumConstraint<T = string> extends Constraint {
165+
__type = 'Enum';
166+
150167
constructor(private list: Array<T>) {
151168
super();
152169
}
@@ -171,6 +188,8 @@ export type TypeConstraintType =
171188
| 'object';
172189

173190
export class TypeConstraint extends Constraint {
191+
__type = 'Type';
192+
174193
constructor(private type: TypeConstraintType) {
175194
super();
176195
}
@@ -185,3 +204,18 @@ export class TypeConstraint extends Constraint {
185204
);
186205
}
187206
}
207+
208+
// https://github.com/typestack/class-transformer/tree/master#providing-more-than-one-type-option
209+
export const ConstraintDiscriminator: DiscriminatorDescriptor = {
210+
property: '__type',
211+
subTypes: [
212+
{ value: RequiredConstraint, name: 'Required' },
213+
{ value: MinValueConstraint, name: 'MinValue' },
214+
{ value: MaxValueConstraint, name: 'MaxValue' },
215+
{ value: MinLengthConstraint, name: 'MinLength' },
216+
{ value: MaxLengthConstraint, name: 'MaxLength' },
217+
{ value: RegexConstraint, name: 'Regex' },
218+
{ value: EnumConstraint, name: 'Enum' },
219+
{ value: TypeConstraint, name: 'Type' },
220+
],
221+
};

packages/core/src/models/artifact.ts

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ error:
1818
message: 'You are not allowed to access this resource'
1919
*/
2020

21-
import { Constraint } from '../lib/validators/constraints';
21+
// This is the model of our built result
22+
// It will be serialized and deserialized by class-transformer
23+
// https://github.com/typestack/class-transformer/tree/master
24+
// So we should use classes instead of interfaces.
25+
26+
import {
27+
Constraint,
28+
ConstraintDiscriminator,
29+
} from '../lib/validators/constraints';
30+
import { Type } from 'class-transformer';
31+
import 'reflect-metadata';
2232

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

42-
export interface ValidatorDefinition<T = any> {
43-
name: string;
44-
args: T;
52+
export class ValidatorDefinition<T = any> {
53+
name!: string;
54+
args!: T;
4555
}
4656

47-
export interface RequestSchema {
48-
fieldName: string;
57+
export class RequestSchema {
58+
fieldName!: string;
4959
// the field put in query parameter or headers
50-
fieldIn: FieldInType;
51-
description: string;
52-
type: FieldDataType;
53-
validators: Array<ValidatorDefinition>;
54-
constraints: Array<Constraint>;
60+
fieldIn!: FieldInType;
61+
description!: string;
62+
type!: FieldDataType;
63+
validators!: Array<ValidatorDefinition>;
64+
@Type(() => Constraint, {
65+
discriminator: ConstraintDiscriminator,
66+
})
67+
constraints!: Array<Constraint>;
5568
}
5669

57-
export interface ResponseProperty {
58-
name: string;
70+
export class ResponseProperty {
71+
name!: string;
5972
description?: string;
60-
type: FieldDataType | Array<ResponseProperty>;
73+
type!: FieldDataType | Array<ResponseProperty>;
6174
required?: boolean;
6275
}
6376

64-
export interface PaginationSchema {
65-
mode: PaginationMode;
77+
export class PaginationSchema {
78+
mode!: PaginationMode;
6679
// The key name used for do filtering by key for keyset pagination.
6780
keyName?: string;
6881
}
6982

70-
export interface ErrorInfo {
71-
code: string;
72-
message: string;
83+
export class ErrorInfo {
84+
code!: string;
85+
message!: string;
7386
}
7487

75-
export interface Sample {
76-
profile: string;
77-
parameters: Record<string, any>;
88+
export class Sample {
89+
profile!: string;
90+
parameters!: Record<string, any>;
7891
}
7992

80-
export interface APISchema {
93+
export class APISchema {
8194
// graphql operation name
82-
operationName: string;
95+
operationName!: string;
8396
// restful url path
84-
urlPath: string;
97+
urlPath!: string;
8598
// template, could be name or path
86-
templateSource: string;
87-
request: Array<RequestSchema>;
88-
errors: Array<ErrorInfo>;
89-
response: Array<ResponseProperty>;
99+
templateSource!: string;
100+
@Type(() => RequestSchema)
101+
request!: Array<RequestSchema>;
102+
@Type(() => ErrorInfo)
103+
errors!: Array<ErrorInfo>;
104+
@Type(() => ResponseProperty)
105+
response!: Array<ResponseProperty>;
90106
description?: string;
91107
// The pagination strategy that do paginate when querying
92108
// If not set pagination, then API request not provide the field to do it
93109
pagination?: PaginationSchema;
94110
sample?: Sample;
95-
profiles: Array<string>;
111+
profiles!: Array<string>;
96112
}
97113

98-
export interface BuiltArtifact {
99-
apiSchemas: Array<APISchema>;
114+
export class BuiltArtifact {
115+
@Type(() => APISchema)
116+
schemas!: Array<APISchema>;
100117
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { plainToInstance, instanceToPlain } from 'class-transformer';
2+
import { RequestSchema } from '@vulcan-sql/core/models';
3+
import { Constraint, FieldDataType, FieldInType } from '@vulcan-sql/core';
4+
5+
it('Every constraint can be serialize and deserialize', async () => {
6+
// Arrange
7+
const constraints = [
8+
Constraint.Required(),
9+
Constraint.MinValue(10),
10+
Constraint.MaxValue(100, true),
11+
Constraint.MinLength(10),
12+
Constraint.MaxLength(100),
13+
Constraint.Enum([1, 2, 3, 4]),
14+
Constraint.Regex('.+'),
15+
Constraint.Type('array'),
16+
];
17+
const request: RequestSchema = {
18+
fieldName: 'test',
19+
fieldIn: FieldInType.QUERY,
20+
description: '',
21+
type: FieldDataType.BOOLEAN,
22+
validators: [],
23+
constraints,
24+
};
25+
// Act
26+
const plainObject = instanceToPlain(request);
27+
const instance = plainToInstance(RequestSchema, plainObject);
28+
// Assert
29+
expect(instance).toEqual(request);
30+
});

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2247,6 +2247,11 @@ cjs-module-lexer@^1.0.0:
22472247
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
22482248
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
22492249

2250+
class-transformer@^0.5.1:
2251+
version "0.5.1"
2252+
resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336"
2253+
integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==
2254+
22502255
class-utils@^0.3.5:
22512256
version "0.3.6"
22522257
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"

0 commit comments

Comments
 (0)