@travetto/schema 2.2.3 → 3.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -90
- package/index.ts +1 -3
- package/package.json +3 -8
- package/src/bind-util.ts +6 -6
- package/src/service/registry.ts +14 -6
- package/src/validate/validator.ts +6 -7
- package/support/transform-util.ts +7 -7
- package/support/transformer.schema.ts +4 -5
- package/src/extension/faker.ts +0 -223
package/README.md
CHANGED
|
@@ -284,96 +284,6 @@ export interface ValidationError {
|
|
|
284
284
|
}
|
|
285
285
|
```
|
|
286
286
|
|
|
287
|
-
## Extension - Generation
|
|
288
|
-
In the course of application development, there is often a need to generate fake data on demand. Given all the information that we have about the schemas provided, translating that into data generation is fairly straightforward. The generation utility is built upon [faker](https://github.com/marak/Faker.js/), mapping data types, and various field names into specific [faker](https://github.com/marak/Faker.js/) generation routines.
|
|
289
|
-
|
|
290
|
-
By default all types are mapped as-is:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
* `string`
|
|
294
|
-
* `number`
|
|
295
|
-
* `Date`
|
|
296
|
-
* `boolean`
|
|
297
|
-
* Enumerations as `string` or `number` types.
|
|
298
|
-
* Provided regular expressions:
|
|
299
|
-
* email
|
|
300
|
-
* url
|
|
301
|
-
* telephone
|
|
302
|
-
* postalCode
|
|
303
|
-
* Sub-schemas as registered via [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L12) decorators.
|
|
304
|
-
|
|
305
|
-
In addition to the general types, the code relies upon name matching to provide additional refinement:
|
|
306
|
-
|
|
307
|
-
**Code: Supported Mappings**
|
|
308
|
-
```typescript
|
|
309
|
-
static #namesToType: {
|
|
310
|
-
string: [RegExp, () => string][];
|
|
311
|
-
date: [RegExp, () => Date][];
|
|
312
|
-
} = {
|
|
313
|
-
string: [
|
|
314
|
-
[/^(image|img).*url$/, (): string => faker.image.imageUrl()],
|
|
315
|
-
[/^url$/, (): string => faker.internet.url()],
|
|
316
|
-
[/^email(addr(ress)?)?$/, (): string => faker.internet.email()],
|
|
317
|
-
[/^(tele)?phone(num|number)?$/, (): string => faker.phone.phoneNumber()],
|
|
318
|
-
[/^((postal|zip)code)|zip$/, (): string => faker.address.zipCode()],
|
|
319
|
-
[/f(irst)?name/, (): string => faker.name.firstName()],
|
|
320
|
-
[/l(ast)?name/, (): string => faker.name.lastName()],
|
|
321
|
-
[/^ip(add(ress)?)?$/, (): string => faker.internet.ip()],
|
|
322
|
-
[/^ip(add(ress)?)?(v?)6$/, (): string => faker.internet.ipv6()],
|
|
323
|
-
[/^username$/, (): string => faker.internet.userName()],
|
|
324
|
-
[/^domain(name)?$/, (): string => faker.internet.domainName()],
|
|
325
|
-
[/^file(path|name)?$/, (): string => faker.system.filePath()],
|
|
326
|
-
[/^street(1)?$/, (): string => faker.address.streetAddress()],
|
|
327
|
-
[/^street2$/, (): string => faker.address.secondaryAddress()],
|
|
328
|
-
[/^county$/, (): string => faker.address.county()],
|
|
329
|
-
[/^country$/, (): string => faker.address.country()],
|
|
330
|
-
[/^state$/, (): string => faker.address.state()],
|
|
331
|
-
[/^lon(gitude)?$/, (): string => faker.address.longitude()],
|
|
332
|
-
[/^lat(itude)?$/, (): string => faker.address.latitude()],
|
|
333
|
-
[/(profile).*(image|img)/, (): string => faker.image.avatar()],
|
|
334
|
-
[/(image|img)/, (): string => faker.image.image()],
|
|
335
|
-
[/^company(name)?$/, (): string => faker.company.companyName()],
|
|
336
|
-
[/(desc|description)$/, (): string => faker.lorem.sentences(10)]
|
|
337
|
-
],
|
|
338
|
-
date: [
|
|
339
|
-
[/dob|birth/, (): Date => faker.date.past(60)],
|
|
340
|
-
[/creat(e|ion)/, (): Date => between(-200, -100)],
|
|
341
|
-
[/(update|modif(y|ied))/, (): Date => between(-100, -50)]
|
|
342
|
-
],
|
|
343
|
-
};
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
An example of this would be:
|
|
347
|
-
|
|
348
|
-
**Code: More complex Schema, used with Faker**
|
|
349
|
-
```typescript
|
|
350
|
-
import { Schema, SchemaFakerUtil } from '@travetto/schema';
|
|
351
|
-
|
|
352
|
-
@Schema()
|
|
353
|
-
class Address {
|
|
354
|
-
street1: string;
|
|
355
|
-
street2?: string;
|
|
356
|
-
city: string;
|
|
357
|
-
state: string;
|
|
358
|
-
country: string;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
@Schema()
|
|
362
|
-
class User {
|
|
363
|
-
fName: string;
|
|
364
|
-
lName: string;
|
|
365
|
-
email: string;
|
|
366
|
-
phone: string;
|
|
367
|
-
dob?: Date;
|
|
368
|
-
address: Address;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
export function generate(): User {
|
|
372
|
-
const user = SchemaFakerUtil.generate(User);
|
|
373
|
-
return user;
|
|
374
|
-
}
|
|
375
|
-
```
|
|
376
|
-
|
|
377
287
|
## Custom Types
|
|
378
288
|
When working with the schema, the basic types are easily understood, but some of [Typescript](https://typescriptlang.org)'s more complex constructs are too complex to automate cleanly.
|
|
379
289
|
|
package/index.ts
CHANGED
|
@@ -12,6 +12,4 @@ export * from './src/validate/validator';
|
|
|
12
12
|
export * from './src/validate/error';
|
|
13
13
|
export * from './src/validate/types';
|
|
14
14
|
export * from './src/bind-util';
|
|
15
|
-
export * from './src/types';
|
|
16
|
-
// Named export needed for proxying
|
|
17
|
-
export { SchemaFakerUtil } from './src/extension/faker';
|
|
15
|
+
export * from './src/types';
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/schema",
|
|
3
3
|
"displayName": "Schema",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.0-rc.1",
|
|
5
5
|
"description": "Data type registry for runtime validation, reflection and binding. ",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"schema",
|
|
8
8
|
"ast-transformations",
|
|
9
9
|
"validation",
|
|
10
|
-
"faker",
|
|
11
10
|
"typescript",
|
|
12
11
|
"travetto",
|
|
13
12
|
"decorators"
|
|
@@ -29,12 +28,8 @@
|
|
|
29
28
|
"directory": "module/schema"
|
|
30
29
|
},
|
|
31
30
|
"dependencies": {
|
|
32
|
-
"@travetto/registry": "^
|
|
33
|
-
"@travetto/transformer": "^
|
|
34
|
-
},
|
|
35
|
-
"optionalPeerDependencies": {
|
|
36
|
-
"@types/faker": "^5.5.9",
|
|
37
|
-
"faker": "^5.5.3"
|
|
31
|
+
"@travetto/registry": "^3.0.0-rc.1",
|
|
32
|
+
"@travetto/transformer": "^3.0.0-rc.1"
|
|
38
33
|
},
|
|
39
34
|
"publishConfig": {
|
|
40
35
|
"access": "public"
|
package/src/bind-util.ts
CHANGED
|
@@ -157,13 +157,13 @@ export class BindUtil {
|
|
|
157
157
|
return data as T;
|
|
158
158
|
} else {
|
|
159
159
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
160
|
-
const tgt = new (cls as ConcreteClass<T>)();
|
|
161
|
-
|
|
160
|
+
const tgt = new (cls as ConcreteClass<T>)() as T;
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
162
|
+
SchemaRegistry.ensureInstanceTypeField(cls, tgt as T & { type?: string });
|
|
162
163
|
|
|
163
|
-
for (const
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
delete tgt[k as keyof T];
|
|
164
|
+
for (const k of Object.keys(tgt)) { // Do not retain undefined fields
|
|
165
|
+
if (tgt[k] === undefined) {
|
|
166
|
+
delete tgt[k];
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
package/src/service/registry.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Class, AppError, Util, ClassInstance } from '@travetto/base';
|
|
1
|
+
import { Class, AppError, Util, ClassInstance, ConcreteClass } from '@travetto/base';
|
|
2
2
|
import { MetadataRegistry, RootRegistry, ChangeEvent } from '@travetto/registry';
|
|
3
3
|
|
|
4
4
|
import { ClassList, FieldConfig, ClassConfig, SchemaConfig, ViewFieldsConfig, ViewConfig } from './types';
|
|
@@ -11,6 +11,16 @@ function hasType<T>(o: unknown): o is { type: Class<T> | string } {
|
|
|
11
11
|
return !!o && !Util.isPrimitive(o) && 'type' in (o as object) && !!(o as Record<string, string>)['type'];
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function isWithType<T>(o: T, cfg: ClassConfig | undefined): o is T & { type?: string } {
|
|
15
|
+
return !!cfg && !!cfg.subType && 'type' in cfg.views[AllViewⲐ].schema;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getConstructor<T>(o: T): ConcreteClass<T> {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
20
|
+
return (o as unknown as ClassInstance<T>).constructor;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
14
24
|
/**
|
|
15
25
|
* Schema registry for listening to changes
|
|
16
26
|
*/
|
|
@@ -49,9 +59,8 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
49
59
|
* Ensure type is set properly
|
|
50
60
|
*/
|
|
51
61
|
ensureInstanceTypeField<T>(cls: Class, o: T): void {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
withType.type = this.#computeSubTypeName(cls); // Assign if missing
|
|
62
|
+
if (isWithType(o, this.get(cls)) && !o.type) { // Do we have a type field defined
|
|
63
|
+
o.type = this.#computeSubTypeName(cls); // Assign if missing
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
66
|
|
|
@@ -61,8 +70,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
61
70
|
* @param o Actual instance
|
|
62
71
|
*/
|
|
63
72
|
resolveSubTypeForInstance<T>(cls: Class<T>, o: T): Class {
|
|
64
|
-
|
|
65
|
-
return this.resolveSubType(cls, hasType<T>(o) ? o.type : (o as unknown as ClassInstance<T>).constructor);
|
|
73
|
+
return this.resolveSubType(cls, hasType<T>(o) ? o.type : getConstructor(o));
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
/**
|
|
@@ -38,7 +38,7 @@ export class SchemaValidator {
|
|
|
38
38
|
static #validateSchema<T>(schema: SchemaConfig, o: T, relative: string): ValidationError[] {
|
|
39
39
|
let errors: ValidationError[] = [];
|
|
40
40
|
|
|
41
|
-
const fields
|
|
41
|
+
const fields = Object.keys<SchemaConfig>(schema);
|
|
42
42
|
for (const field of fields) {
|
|
43
43
|
if (schema[field].access !== 'readonly') { // Do not validate readonly fields
|
|
44
44
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -104,10 +104,7 @@ export class SchemaValidator {
|
|
|
104
104
|
* @param key The bounds to check
|
|
105
105
|
* @param value The value to validate
|
|
106
106
|
*/
|
|
107
|
-
static #validateRange(field: FieldConfig, key: 'min' | 'max',
|
|
108
|
-
|
|
109
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
110
|
-
let value = rawValue as string | number | Date;
|
|
107
|
+
static #validateRange(field: FieldConfig, key: 'min' | 'max', value: string | number | Date): boolean {
|
|
111
108
|
|
|
112
109
|
const f = field[key]!;
|
|
113
110
|
if (typeof f.n === 'number') {
|
|
@@ -178,11 +175,13 @@ export class SchemaValidator {
|
|
|
178
175
|
criteria.push(['enum', field.enum]);
|
|
179
176
|
}
|
|
180
177
|
|
|
181
|
-
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
179
|
+
if (field.min && this.#validateRange(field, 'min', value as number)) {
|
|
182
180
|
criteria.push(['min', field.min]);
|
|
183
181
|
}
|
|
184
182
|
|
|
185
|
-
|
|
183
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
184
|
+
if (field.max && this.#validateRange(field, 'max', value as number)) {
|
|
186
185
|
criteria.push(['max', field.max]);
|
|
187
186
|
}
|
|
188
187
|
|
|
@@ -140,34 +140,34 @@ export class SchemaTransformUtil {
|
|
|
140
140
|
params.push(state.factory.createObjectLiteralExpression(attrs));
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
const
|
|
144
|
-
...(node.
|
|
143
|
+
const newModifiers = [
|
|
144
|
+
...(node.modifiers ?? []).filter(x => x !== existing),
|
|
145
145
|
state.createDecorator(FIELD_MOD, 'Field', ...params)
|
|
146
146
|
];
|
|
147
147
|
|
|
148
148
|
if (ts.isPropertyDeclaration(node)) {
|
|
149
149
|
const comments = DocUtil.describeDocs(node);
|
|
150
150
|
if (comments.description) {
|
|
151
|
-
|
|
151
|
+
newModifiers.push(state.createDecorator(COMMON_MOD, 'Describe', state.fromLiteral({
|
|
152
152
|
description: comments.description
|
|
153
153
|
})));
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
157
157
|
return state.factory.updatePropertyDeclaration(node as Exclude<typeof node, T>,
|
|
158
|
-
|
|
158
|
+
newModifiers, node.name, node.questionToken, node.type, node.initializer) as T;
|
|
159
159
|
} else if (ts.isParameter(node)) {
|
|
160
160
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
161
161
|
return state.factory.updateParameterDeclaration(node as Exclude<typeof node, T>,
|
|
162
|
-
|
|
162
|
+
newModifiers, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer) as T;
|
|
163
163
|
} else if (ts.isGetAccessorDeclaration(node)) {
|
|
164
164
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
165
165
|
return state.factory.updateGetAccessorDeclaration(node as Exclude<typeof node, T>,
|
|
166
|
-
|
|
166
|
+
newModifiers, node.name, node.parameters, node.type, node.body) as T;
|
|
167
167
|
} else {
|
|
168
168
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
169
169
|
return state.factory.updateSetAccessorDeclaration(node as Exclude<typeof node, T>,
|
|
170
|
-
|
|
170
|
+
newModifiers, node.name, node.parameters, node.body) as T;
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
|
|
@@ -39,16 +39,16 @@ export class SchemaTransformer {
|
|
|
39
39
|
*/
|
|
40
40
|
@AfterClass('Schema')
|
|
41
41
|
static finalizeSchema(state: AutoState & TransformerState, node: ts.ClassDeclaration): ts.ClassDeclaration {
|
|
42
|
-
const
|
|
42
|
+
const modifiers = (node.modifiers ?? []).slice(0);
|
|
43
43
|
|
|
44
44
|
const comments = DocUtil.describeDocs(node);
|
|
45
45
|
|
|
46
46
|
if (!state.findDecorator(this, node, 'Schema', SCHEMA_MOD)) {
|
|
47
|
-
|
|
47
|
+
modifiers.unshift(state.createDecorator(SCHEMA_MOD, 'Schema'));
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
if (comments.description) {
|
|
51
|
-
|
|
51
|
+
modifiers.push(state.createDecorator(COMMON_MOD, 'Describe', state.fromLiteral({
|
|
52
52
|
title: comments.description
|
|
53
53
|
})));
|
|
54
54
|
}
|
|
@@ -58,8 +58,7 @@ export class SchemaTransformer {
|
|
|
58
58
|
|
|
59
59
|
return state.factory.updateClassDeclaration(
|
|
60
60
|
node,
|
|
61
|
-
|
|
62
|
-
node.modifiers,
|
|
61
|
+
modifiers,
|
|
63
62
|
node.name,
|
|
64
63
|
node.typeParameters,
|
|
65
64
|
node.heritageClauses,
|
package/src/extension/faker.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
// @file-if faker
|
|
2
|
-
import * as fakerType from 'faker';
|
|
3
|
-
|
|
4
|
-
import { Class } from '@travetto/base';
|
|
5
|
-
|
|
6
|
-
import { CommonRegExp } from '../validate/regexp';
|
|
7
|
-
import { FieldConfig } from '../service/types';
|
|
8
|
-
import { SchemaRegistry } from '../service/registry';
|
|
9
|
-
import { BindUtil } from '../bind-util';
|
|
10
|
-
|
|
11
|
-
// Load faker on demand, as its very heavy in loading
|
|
12
|
-
let faker: typeof fakerType = new Proxy(
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
14
|
-
{} as typeof fakerType,
|
|
15
|
-
{
|
|
16
|
-
get: (t, prop, r) => (faker = require('faker'))[prop]
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const DAY_IN_MS = 24 * 60 * 60 * 1000;
|
|
20
|
-
|
|
21
|
-
const between = (fromDays: number, toDays: number): Date =>
|
|
22
|
-
faker.date.between(
|
|
23
|
-
new Date(Date.now() + fromDays * DAY_IN_MS),
|
|
24
|
-
new Date(Date.now() + toDays * DAY_IN_MS)
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Provide a faker utility for generating content
|
|
29
|
-
*/
|
|
30
|
-
export class SchemaFakerUtil {
|
|
31
|
-
|
|
32
|
-
static #stringToReType: [RegExp, () => string][] = [
|
|
33
|
-
[CommonRegExp.email, (): string => faker.internet.email()],
|
|
34
|
-
[CommonRegExp.url, (): string => faker.internet.url()],
|
|
35
|
-
[CommonRegExp.telephone, (): string => faker.phone.phoneNumber()],
|
|
36
|
-
[CommonRegExp.postalCode, (): string => faker.address.zipCode()]
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Mapping of field types to faker utils
|
|
41
|
-
*/
|
|
42
|
-
static #namesToType: {
|
|
43
|
-
string: [RegExp, () => string][];
|
|
44
|
-
date: [RegExp, () => Date][];
|
|
45
|
-
} = {
|
|
46
|
-
string: [
|
|
47
|
-
[/^(image|img).*url$/, (): string => faker.image.imageUrl()],
|
|
48
|
-
[/^url$/, (): string => faker.internet.url()],
|
|
49
|
-
[/^email(addr(ress)?)?$/, (): string => faker.internet.email()],
|
|
50
|
-
[/^(tele)?phone(num|number)?$/, (): string => faker.phone.phoneNumber()],
|
|
51
|
-
[/^((postal|zip)code)|zip$/, (): string => faker.address.zipCode()],
|
|
52
|
-
[/f(irst)?name/, (): string => faker.name.firstName()],
|
|
53
|
-
[/l(ast)?name/, (): string => faker.name.lastName()],
|
|
54
|
-
[/^ip(add(ress)?)?$/, (): string => faker.internet.ip()],
|
|
55
|
-
[/^ip(add(ress)?)?(v?)6$/, (): string => faker.internet.ipv6()],
|
|
56
|
-
[/^username$/, (): string => faker.internet.userName()],
|
|
57
|
-
[/^domain(name)?$/, (): string => faker.internet.domainName()],
|
|
58
|
-
[/^file(path|name)?$/, (): string => faker.system.filePath()],
|
|
59
|
-
[/^street(1)?$/, (): string => faker.address.streetAddress()],
|
|
60
|
-
[/^street2$/, (): string => faker.address.secondaryAddress()],
|
|
61
|
-
[/^county$/, (): string => faker.address.county()],
|
|
62
|
-
[/^country$/, (): string => faker.address.country()],
|
|
63
|
-
[/^state$/, (): string => faker.address.state()],
|
|
64
|
-
[/^lon(gitude)?$/, (): string => faker.address.longitude()],
|
|
65
|
-
[/^lat(itude)?$/, (): string => faker.address.latitude()],
|
|
66
|
-
[/(profile).*(image|img)/, (): string => faker.image.avatar()],
|
|
67
|
-
[/(image|img)/, (): string => faker.image.image()],
|
|
68
|
-
[/^company(name)?$/, (): string => faker.company.companyName()],
|
|
69
|
-
[/(desc|description)$/, (): string => faker.lorem.sentences(10)]
|
|
70
|
-
],
|
|
71
|
-
date: [
|
|
72
|
-
[/dob|birth/, (): Date => faker.date.past(60)],
|
|
73
|
-
[/creat(e|ion)/, (): Date => between(-200, -100)],
|
|
74
|
-
[/(update|modif(y|ied))/, (): Date => between(-100, -50)]
|
|
75
|
-
],
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Get an array of values
|
|
80
|
-
* @param cfg Field configuration
|
|
81
|
-
*/
|
|
82
|
-
static getArrayValue(cfg: FieldConfig): unknown[] {
|
|
83
|
-
const min = cfg.minlength ? cfg.minlength.n : 0;
|
|
84
|
-
const max = cfg.maxlength ? cfg.maxlength.n : 10;
|
|
85
|
-
const size = faker.datatype.number({ min, max });
|
|
86
|
-
const out = [];
|
|
87
|
-
for (let i = 0; i < size; i++) {
|
|
88
|
-
out.push(this.getValue(cfg, true));
|
|
89
|
-
}
|
|
90
|
-
return out;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Get a new number value
|
|
95
|
-
* @param cfg Number config
|
|
96
|
-
*/
|
|
97
|
-
static getNumberValue(cfg: FieldConfig): number {
|
|
98
|
-
let min = cfg.min && typeof cfg.min.n === 'number' ? cfg.min.n : undefined;
|
|
99
|
-
let max = cfg.max && typeof cfg.max.n === 'number' ? cfg.max.n : undefined;
|
|
100
|
-
let precision = cfg.precision;
|
|
101
|
-
|
|
102
|
-
const name = cfg.name.toUpperCase();
|
|
103
|
-
|
|
104
|
-
if (/(price|amt|amount)$/.test(name)) {
|
|
105
|
-
precision = [13, 2];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let offset = 1;
|
|
109
|
-
|
|
110
|
-
if (precision !== undefined) {
|
|
111
|
-
min = min === undefined ? -((10 ** precision[0]) - 1) : min;
|
|
112
|
-
max = max === undefined ? ((10 ** precision[0]) - 1) : max;
|
|
113
|
-
if (precision[1] !== undefined) {
|
|
114
|
-
offset = (10 ** (precision[1] || 0));
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
max = max === undefined ? 1000 : max;
|
|
119
|
-
min = min === undefined ? 0 : min;
|
|
120
|
-
|
|
121
|
-
const range = (max - min) * offset;
|
|
122
|
-
|
|
123
|
-
const val = Math.trunc(Math.random() * range);
|
|
124
|
-
|
|
125
|
-
return (val / offset) + min;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Get a date value
|
|
130
|
-
* @param cfg Field config
|
|
131
|
-
*/
|
|
132
|
-
static getDateValue(cfg: FieldConfig): Date {
|
|
133
|
-
const name = cfg.name.toUpperCase();
|
|
134
|
-
const min = cfg.min && typeof cfg.min.n !== 'number' ? cfg.min.n : undefined;
|
|
135
|
-
const max = cfg.max && typeof cfg.max.n !== 'number' ? cfg.max.n : undefined;
|
|
136
|
-
|
|
137
|
-
if (min !== undefined || max !== undefined) {
|
|
138
|
-
return faker.date.between(min || new Date(Date.now() - (50 * DAY_IN_MS)), max || new Date());
|
|
139
|
-
} else {
|
|
140
|
-
for (const [re, fn] of this.#namesToType.date) {
|
|
141
|
-
if (re.test(name)) {
|
|
142
|
-
return fn();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return faker.date.recent(50);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Get a string value
|
|
151
|
-
* @param cfg Field config
|
|
152
|
-
*/
|
|
153
|
-
static getStringValue(cfg: FieldConfig): string {
|
|
154
|
-
const name = cfg.name.toLowerCase();
|
|
155
|
-
|
|
156
|
-
if (cfg.match) {
|
|
157
|
-
const mre = cfg.match && cfg.match.re;
|
|
158
|
-
for (const [re, fn] of this.#stringToReType) {
|
|
159
|
-
if (mre === re) {
|
|
160
|
-
return fn();
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
for (const [re, fn] of this.#namesToType.string) {
|
|
166
|
-
if (re.test(name)) {
|
|
167
|
-
return fn();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return faker.random.word();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Get a value for a field config
|
|
176
|
-
* @param cfg Field config
|
|
177
|
-
*/
|
|
178
|
-
static getValue(cfg: FieldConfig, subArray = false): unknown {
|
|
179
|
-
if (!subArray && cfg.array) {
|
|
180
|
-
return this.getArrayValue(cfg);
|
|
181
|
-
} else if (cfg.enum) {
|
|
182
|
-
return faker.random.arrayElement(cfg.enum.values);
|
|
183
|
-
} else {
|
|
184
|
-
|
|
185
|
-
const typ = cfg.type;
|
|
186
|
-
|
|
187
|
-
if (typ === Number) {
|
|
188
|
-
return this.getNumberValue(cfg);
|
|
189
|
-
} else if (typ === String) {
|
|
190
|
-
return this.getStringValue(cfg);
|
|
191
|
-
} else if (typ === Date) {
|
|
192
|
-
return this.getDateValue(cfg);
|
|
193
|
-
} else if (typ === Boolean) {
|
|
194
|
-
return faker.datatype.boolean();
|
|
195
|
-
} else if (SchemaRegistry.has(typ)) {
|
|
196
|
-
return this.generate(typ);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Generate a new instance of a class
|
|
203
|
-
* @param cls The class to get an instance of
|
|
204
|
-
* @param view The view to define specifically
|
|
205
|
-
*/
|
|
206
|
-
static generate<T>(cls: Class<T>, view?: string): T {
|
|
207
|
-
const cfg = SchemaRegistry.getViewSchema(cls, view);
|
|
208
|
-
const out: Record<string, unknown> = {};
|
|
209
|
-
|
|
210
|
-
for (const f of cfg.fields) {
|
|
211
|
-
if (f === 'type' || f === 'id') { // Do not set primary fields
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
const fieldConfig = cfg.schema[f];
|
|
215
|
-
if (!fieldConfig.required && (Math.random() < .5)) {
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
out[f] = this.getValue(fieldConfig);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return BindUtil.bindSchema(cls, out, view);
|
|
222
|
-
}
|
|
223
|
-
}
|