@leon740727/type-schema 0.0.3
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/dst/src/check.d.ts +3 -0
- package/dst/src/check.js +133 -0
- package/dst/src/index.d.ts +18 -0
- package/dst/src/index.js +55 -0
- package/dst/src/transform.d.ts +3 -0
- package/dst/src/transform.js +73 -0
- package/dst/src/type.d.ts +122 -0
- package/dst/src/type.js +150 -0
- package/dst/src/util.d.ts +19 -0
- package/dst/src/util.js +16 -0
- package/dst/test/enums.d.ts +1 -0
- package/dst/test/enums.js +22 -0
- package/dst/test/nullable.d.ts +1 -0
- package/dst/test/nullable.js +69 -0
- package/dst/test/tuple.d.ts +1 -0
- package/dst/test/tuple.js +49 -0
- package/dst/test/union.d.ts +1 -0
- package/dst/test/union.js +89 -0
- package/package.json +21 -0
- package/src/check.ts +147 -0
- package/src/index.ts +71 -0
- package/src/transform.ts +96 -0
- package/src/type.ts +367 -0
- package/src/util.ts +35 -0
- package/test/enums.ts +32 -0
- package/test/nullable.ts +84 -0
- package/test/tuple.ts +63 -0
- package/test/union.ts +112 -0
- package/tsconfig.json +17 -0
package/src/type.ts
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { mergeRight } from "ramda";
|
|
2
|
+
import { addQuestionMarks, flatten, resolveTuple } from "./util";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* schema 有幾種類別
|
|
6
|
+
* 1. atom: 沒有 inner schema 描述的值。例如 number, string ...
|
|
7
|
+
* 注意,array 或 object 也可以是 atom 的,只要其值沒有另外的 schema 描述
|
|
8
|
+
* 2. compound: 裡面的值有另外的 inner schema 來描述。又分成二種 object, array
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export enum SchemaType {
|
|
12
|
+
atom,
|
|
13
|
+
array, // compound array
|
|
14
|
+
object, // compound object
|
|
15
|
+
tuple,
|
|
16
|
+
union,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Attr = { [field: string]: any };
|
|
20
|
+
|
|
21
|
+
export class AtomSchema<VT, VT2> {
|
|
22
|
+
constructor(
|
|
23
|
+
readonly type: SchemaType.atom,
|
|
24
|
+
readonly value: VT,
|
|
25
|
+
readonly isNullable: boolean,
|
|
26
|
+
readonly isOptional: boolean,
|
|
27
|
+
readonly isa: (v) => string | null, // 傳回錯誤訊息
|
|
28
|
+
readonly transform: (v: VT) => VT2,
|
|
29
|
+
readonly attr: Attr // 額外附加的屬性
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
nullable() {
|
|
33
|
+
return new AtomSchema<VT | null, VT2>(
|
|
34
|
+
SchemaType.atom,
|
|
35
|
+
this.value,
|
|
36
|
+
true,
|
|
37
|
+
this.isOptional,
|
|
38
|
+
this.isa,
|
|
39
|
+
this.transform,
|
|
40
|
+
this.attr
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
optional() {
|
|
45
|
+
return new AtomSchema<VT | undefined, VT2>(
|
|
46
|
+
SchemaType.atom,
|
|
47
|
+
this.value,
|
|
48
|
+
this.isNullable,
|
|
49
|
+
true,
|
|
50
|
+
this.isa,
|
|
51
|
+
this.transform,
|
|
52
|
+
this.attr
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
transformer<T2>(fn: (v: NonNullable<VT>) => T2) {
|
|
57
|
+
return new AtomSchema<VT, T2>(
|
|
58
|
+
SchemaType.atom,
|
|
59
|
+
this.value,
|
|
60
|
+
this.isNullable,
|
|
61
|
+
this.isOptional,
|
|
62
|
+
this.isa,
|
|
63
|
+
fn,
|
|
64
|
+
this.attr
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
set(attr: Attr) {
|
|
69
|
+
const newAttr = mergeRight(this.attr, attr);
|
|
70
|
+
return new AtomSchema<VT, VT2>(
|
|
71
|
+
SchemaType.atom,
|
|
72
|
+
this.value,
|
|
73
|
+
this.isNullable,
|
|
74
|
+
this.isOptional,
|
|
75
|
+
this.isa,
|
|
76
|
+
this.transform,
|
|
77
|
+
newAttr
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class ArraySchema<
|
|
83
|
+
InnerSchema extends InnerSchemaForArraySchema | null | undefined
|
|
84
|
+
> {
|
|
85
|
+
readonly innerSchema: InnerSchema;
|
|
86
|
+
|
|
87
|
+
constructor(
|
|
88
|
+
readonly type: SchemaType.array,
|
|
89
|
+
readonly itemSchema: Schema,
|
|
90
|
+
readonly isNullable: boolean,
|
|
91
|
+
readonly isOptional: boolean,
|
|
92
|
+
readonly attr: Attr // 額外附加的屬性
|
|
93
|
+
) {}
|
|
94
|
+
|
|
95
|
+
nullable() {
|
|
96
|
+
return new ArraySchema<InnerSchema | null>(
|
|
97
|
+
SchemaType.array,
|
|
98
|
+
this.itemSchema,
|
|
99
|
+
true,
|
|
100
|
+
this.isOptional,
|
|
101
|
+
this.attr
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
optional() {
|
|
106
|
+
return new ArraySchema<InnerSchema | undefined>(
|
|
107
|
+
SchemaType.array,
|
|
108
|
+
this.itemSchema,
|
|
109
|
+
this.isNullable,
|
|
110
|
+
true,
|
|
111
|
+
this.attr
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
set(attr: Attr) {
|
|
116
|
+
const newAttr = mergeRight(this.attr, attr);
|
|
117
|
+
return new ArraySchema<InnerSchema>(
|
|
118
|
+
SchemaType.array,
|
|
119
|
+
this.itemSchema,
|
|
120
|
+
this.isNullable,
|
|
121
|
+
this.isOptional,
|
|
122
|
+
newAttr
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export class ObjectSchema<
|
|
128
|
+
InnerSchema extends InnerSchemaForObjectSchema | null | undefined
|
|
129
|
+
> {
|
|
130
|
+
constructor(
|
|
131
|
+
readonly type: SchemaType.object,
|
|
132
|
+
readonly innerSchema: InnerSchema,
|
|
133
|
+
readonly isNullable: boolean,
|
|
134
|
+
readonly isOptional: boolean,
|
|
135
|
+
readonly attr: Attr // 額外附加的屬性
|
|
136
|
+
) {}
|
|
137
|
+
|
|
138
|
+
nullable() {
|
|
139
|
+
return new ObjectSchema<InnerSchema | null>(
|
|
140
|
+
SchemaType.object,
|
|
141
|
+
this.innerSchema,
|
|
142
|
+
true,
|
|
143
|
+
this.isOptional,
|
|
144
|
+
this.attr
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
optional() {
|
|
149
|
+
return new ObjectSchema<InnerSchema | undefined>(
|
|
150
|
+
SchemaType.object,
|
|
151
|
+
this.innerSchema,
|
|
152
|
+
this.isNullable,
|
|
153
|
+
true,
|
|
154
|
+
this.attr
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
set(attr: Attr) {
|
|
159
|
+
const newAttr = mergeRight(this.attr, attr);
|
|
160
|
+
return new ObjectSchema<InnerSchema>(
|
|
161
|
+
SchemaType.object,
|
|
162
|
+
this.innerSchema,
|
|
163
|
+
this.isNullable,
|
|
164
|
+
this.isOptional,
|
|
165
|
+
newAttr
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
class TupleSchema<
|
|
171
|
+
InnerSchema extends [Schema, ...Schema[]] | null | undefined
|
|
172
|
+
> {
|
|
173
|
+
constructor(
|
|
174
|
+
readonly type: SchemaType.tuple,
|
|
175
|
+
readonly innerSchema: InnerSchema,
|
|
176
|
+
readonly isNullable: boolean,
|
|
177
|
+
readonly isOptional: boolean,
|
|
178
|
+
readonly attr: Attr // 額外附加的屬性
|
|
179
|
+
) {}
|
|
180
|
+
|
|
181
|
+
nullable() {
|
|
182
|
+
return new TupleSchema<InnerSchema | null>(
|
|
183
|
+
SchemaType.tuple,
|
|
184
|
+
this.innerSchema,
|
|
185
|
+
true,
|
|
186
|
+
this.isOptional,
|
|
187
|
+
this.attr
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
optional() {
|
|
192
|
+
return new TupleSchema<InnerSchema | undefined>(
|
|
193
|
+
SchemaType.tuple,
|
|
194
|
+
this.innerSchema,
|
|
195
|
+
this.isNullable,
|
|
196
|
+
true,
|
|
197
|
+
this.attr
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
set(attr: Attr) {
|
|
202
|
+
const newAttr = mergeRight(this.attr, attr);
|
|
203
|
+
return new TupleSchema<InnerSchema>(
|
|
204
|
+
SchemaType.tuple,
|
|
205
|
+
this.innerSchema,
|
|
206
|
+
this.isNullable,
|
|
207
|
+
this.isOptional,
|
|
208
|
+
newAttr
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
class UnionSchema<InnerSchema extends Schema[] | null | undefined> {
|
|
214
|
+
constructor(
|
|
215
|
+
readonly type: SchemaType.union,
|
|
216
|
+
readonly innerSchema: InnerSchema,
|
|
217
|
+
readonly isNullable: boolean,
|
|
218
|
+
readonly isOptional: boolean,
|
|
219
|
+
readonly attr: Attr // 額外附加的屬性
|
|
220
|
+
) {}
|
|
221
|
+
|
|
222
|
+
nullable() {
|
|
223
|
+
return new UnionSchema<InnerSchema | null>(
|
|
224
|
+
SchemaType.union,
|
|
225
|
+
this.innerSchema,
|
|
226
|
+
true,
|
|
227
|
+
this.isOptional,
|
|
228
|
+
this.attr
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
optional() {
|
|
233
|
+
return new UnionSchema<InnerSchema | undefined>(
|
|
234
|
+
SchemaType.union,
|
|
235
|
+
this.innerSchema,
|
|
236
|
+
this.isNullable,
|
|
237
|
+
true,
|
|
238
|
+
this.attr
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
set(attr: Attr) {
|
|
243
|
+
const newAttr = mergeRight(this.attr, attr);
|
|
244
|
+
return new UnionSchema<InnerSchema>(
|
|
245
|
+
SchemaType.union,
|
|
246
|
+
this.innerSchema,
|
|
247
|
+
this.isNullable,
|
|
248
|
+
this.isOptional,
|
|
249
|
+
newAttr
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export type Schema =
|
|
255
|
+
| AtomSchema<any, any>
|
|
256
|
+
| ArraySchema<InnerSchemaForArraySchema | null | undefined>
|
|
257
|
+
| TupleSchema<[Schema, ...Schema[]] | null | undefined>
|
|
258
|
+
| UnionSchema<Schema[] | null | undefined>
|
|
259
|
+
| ObjectSchema<InnerSchemaForObjectSchema | null | undefined>;
|
|
260
|
+
|
|
261
|
+
type InnerSchemaForArraySchema = Schema[];
|
|
262
|
+
|
|
263
|
+
export type InnerSchemaForObjectSchema = {
|
|
264
|
+
[field: string]: Schema;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export namespace Schema {
|
|
268
|
+
export function value<T>(isa: (v) => string | null) {
|
|
269
|
+
return new AtomSchema<T, T>(
|
|
270
|
+
SchemaType.atom,
|
|
271
|
+
null as any,
|
|
272
|
+
false,
|
|
273
|
+
false,
|
|
274
|
+
isa,
|
|
275
|
+
(v: T) => v,
|
|
276
|
+
{}
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function array<S extends Schema>(itemSchema: S) {
|
|
281
|
+
return new ArraySchema<S[]>(SchemaType.array, itemSchema, false, false, {});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function object<S extends InnerSchemaForObjectSchema>(innerSchema: S) {
|
|
285
|
+
return new ObjectSchema<S>(
|
|
286
|
+
SchemaType.object,
|
|
287
|
+
innerSchema,
|
|
288
|
+
false,
|
|
289
|
+
false,
|
|
290
|
+
{}
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function tuple<tuple extends [Schema, ...Schema[]]>(schemas: tuple) {
|
|
295
|
+
return new TupleSchema<tuple>(SchemaType.tuple, schemas, false, false, {});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function union<union extends Schema[]>(schemas: union) {
|
|
299
|
+
return new UnionSchema<union>(SchemaType.union, schemas, false, false, {});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export type fetchAtom<T extends Schema> = T extends { type: SchemaType.atom }
|
|
304
|
+
? T
|
|
305
|
+
: never;
|
|
306
|
+
export type fetchArray<T extends Schema> = T extends { type: SchemaType.array }
|
|
307
|
+
? T
|
|
308
|
+
: never;
|
|
309
|
+
export type fetchObject<T extends Schema> = T extends {
|
|
310
|
+
type: SchemaType.object;
|
|
311
|
+
}
|
|
312
|
+
? T
|
|
313
|
+
: never;
|
|
314
|
+
export type fetchTuple<T extends Schema> = Extract<T, TupleSchema<any>>;
|
|
315
|
+
export type fetchUnion<T extends Schema> = Extract<T, UnionSchema<any>>;
|
|
316
|
+
|
|
317
|
+
export type build<S extends Schema, transformed extends boolean> = S extends {
|
|
318
|
+
type: SchemaType.object;
|
|
319
|
+
}
|
|
320
|
+
? buildObj<fetchObject<S>["innerSchema"], transformed>
|
|
321
|
+
: S extends { type: SchemaType.array }
|
|
322
|
+
? buildArray<fetchArray<S>["innerSchema"], transformed>
|
|
323
|
+
: S extends { type: SchemaType.tuple }
|
|
324
|
+
? buildTuple<fetchTuple<S>["innerSchema"], transformed>
|
|
325
|
+
: S extends { type: SchemaType.union }
|
|
326
|
+
? buildUnion<fetchUnion<S>["innerSchema"], transformed>
|
|
327
|
+
: S extends { type: SchemaType.atom }
|
|
328
|
+
? buildAtom<fetchAtom<S>, transformed>
|
|
329
|
+
: never;
|
|
330
|
+
|
|
331
|
+
type buildObj<
|
|
332
|
+
OS extends InnerSchemaForObjectSchema | null | undefined,
|
|
333
|
+
transformed extends boolean
|
|
334
|
+
> = OS extends InnerSchemaForObjectSchema
|
|
335
|
+
? flatten<addQuestionMarks<{ [f in keyof OS]: build<OS[f], transformed> }>>
|
|
336
|
+
: OS;
|
|
337
|
+
|
|
338
|
+
type buildArray<
|
|
339
|
+
S extends InnerSchemaForArraySchema | null | undefined,
|
|
340
|
+
transformed extends boolean
|
|
341
|
+
> = S extends InnerSchemaForArraySchema ? build<S[number], transformed>[] : S;
|
|
342
|
+
|
|
343
|
+
type buildTuple<
|
|
344
|
+
S extends any[] | null | undefined,
|
|
345
|
+
transformed extends boolean
|
|
346
|
+
> = S extends [Schema, ...Schema[]]
|
|
347
|
+
? [
|
|
348
|
+
build<resolveTuple<S>[0], transformed>,
|
|
349
|
+
...buildTuple<resolveTuple<S>[1], transformed>
|
|
350
|
+
]
|
|
351
|
+
: S;
|
|
352
|
+
|
|
353
|
+
type buildUnion<
|
|
354
|
+
S extends Schema[] | null | undefined,
|
|
355
|
+
transformed extends boolean
|
|
356
|
+
> = S extends Schema[] ? build<S[number], transformed> : S;
|
|
357
|
+
|
|
358
|
+
type buildAtom<
|
|
359
|
+
S extends Schema | null | undefined,
|
|
360
|
+
transformed extends boolean
|
|
361
|
+
> = S extends Schema
|
|
362
|
+
? transformed extends true
|
|
363
|
+
?
|
|
364
|
+
| ReturnType<fetchAtom<S>["transform"]>
|
|
365
|
+
| Extract<fetchAtom<S>["value"], null | undefined>
|
|
366
|
+
: fetchAtom<S>["value"]
|
|
367
|
+
: S;
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// ref: https://github.com/colinhacks/zod
|
|
2
|
+
|
|
3
|
+
export type flatten<T> = identity<{ [k in keyof T]: T[k] }>;
|
|
4
|
+
|
|
5
|
+
export type addQuestionMarks<T extends object> = {
|
|
6
|
+
[K in requiredKeys<T>]: T[K];
|
|
7
|
+
} & {
|
|
8
|
+
[K in optionalKeys<T>]?: T[K];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type resolveTuple<tuple> = tuple extends [infer h, ...infer r]
|
|
12
|
+
? [h, r]
|
|
13
|
+
: never;
|
|
14
|
+
|
|
15
|
+
type identity<T> = T;
|
|
16
|
+
|
|
17
|
+
type optionalKeys<T extends object> = {
|
|
18
|
+
[k in keyof T]: undefined extends T[k] ? k : never;
|
|
19
|
+
}[keyof T];
|
|
20
|
+
|
|
21
|
+
type requiredKeys<T extends object> = {
|
|
22
|
+
[k in keyof T]: undefined extends T[k] ? never : k;
|
|
23
|
+
}[keyof T];
|
|
24
|
+
|
|
25
|
+
class AssertError extends Error {}
|
|
26
|
+
|
|
27
|
+
export function assert(condition: unknown, message: string): asserts condition {
|
|
28
|
+
if (!condition) {
|
|
29
|
+
throw new AssertError(message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function pair<A, B>(a: A, b: B): [A, B] {
|
|
34
|
+
return [a, b];
|
|
35
|
+
}
|
package/test/enums.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as assert from "assert";
|
|
2
|
+
import * as Schema from "../src/index";
|
|
3
|
+
|
|
4
|
+
declare const describe, it;
|
|
5
|
+
|
|
6
|
+
describe("enums", () => {
|
|
7
|
+
const schema = Schema.enums([1, 2, "yes", "no"]);
|
|
8
|
+
type Types = Schema.buildType<typeof schema>;
|
|
9
|
+
|
|
10
|
+
it("type", () => {
|
|
11
|
+
const t1: Types = 1;
|
|
12
|
+
const t2: Types = 2;
|
|
13
|
+
const t3: Types = "yes";
|
|
14
|
+
const t4: Types = "no";
|
|
15
|
+
//@ts-expect-error
|
|
16
|
+
const t5: Types = 3;
|
|
17
|
+
//@ts-expect-error
|
|
18
|
+
const t6: Types = "error";
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("transform", () => {
|
|
22
|
+
assert.strictEqual(Schema.transform(schema, 2)[0], null);
|
|
23
|
+
assert.strictEqual(
|
|
24
|
+
Schema.transform(schema, 3)[0],
|
|
25
|
+
"not a valid enum value, value should be one of [1,2,yes,no]"
|
|
26
|
+
);
|
|
27
|
+
assert.strictEqual(
|
|
28
|
+
Schema.transform(schema, "e")[0],
|
|
29
|
+
"not a valid enum value, value should be one of [1,2,yes,no]"
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
});
|
package/test/nullable.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as assert from "assert";
|
|
2
|
+
import * as Schema from "../src/index";
|
|
3
|
+
|
|
4
|
+
declare const describe, it;
|
|
5
|
+
|
|
6
|
+
describe("nullable field", () => {
|
|
7
|
+
describe("atom", () => {
|
|
8
|
+
const schema = Schema.object({
|
|
9
|
+
name: Schema.string(),
|
|
10
|
+
age: Schema.string().transformer((v) => parseInt(v)),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const nullableSchema = Schema.object({
|
|
14
|
+
name: Schema.string(),
|
|
15
|
+
age: Schema.string()
|
|
16
|
+
.nullable()
|
|
17
|
+
.transformer((v) => parseInt(v)),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const o = {
|
|
21
|
+
name: "jack",
|
|
22
|
+
age: null,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
it("is null -- only check", () => {
|
|
26
|
+
assert.ok(Schema.check(schema, o) !== null);
|
|
27
|
+
assert.ok(Schema.check(nullableSchema, o) === null);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("is null -- transform", () => {
|
|
31
|
+
const [error, _] = Schema.transform(schema, o);
|
|
32
|
+
assert.ok(error !== null);
|
|
33
|
+
|
|
34
|
+
const [__, m] = Schema.transform(nullableSchema, o);
|
|
35
|
+
assert.ok(m !== null);
|
|
36
|
+
assert.ok(m.age === null);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("array", () => {
|
|
41
|
+
const schema = Schema.object({
|
|
42
|
+
name: Schema.string(),
|
|
43
|
+
friends: Schema.array(Schema.number()),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const nullableSchema = Schema.object({
|
|
47
|
+
name: Schema.string(),
|
|
48
|
+
friends: Schema.array(Schema.number()).nullable(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const o = {
|
|
52
|
+
name: "jack",
|
|
53
|
+
friends: null,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
it("is null -- only check", () => {
|
|
57
|
+
assert.ok(Schema.check(schema, o) !== null);
|
|
58
|
+
assert.ok(Schema.check(nullableSchema, o) === null);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("is null -- transform", () => {
|
|
62
|
+
const [error, _] = Schema.transform(schema, o);
|
|
63
|
+
assert.ok(error !== null);
|
|
64
|
+
|
|
65
|
+
const [__, m] = Schema.transform(nullableSchema, o);
|
|
66
|
+
assert.ok(m !== null);
|
|
67
|
+
assert.ok(m.friends === null);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("pass null to a non-nullable schema", () => {
|
|
73
|
+
const arraySchema = Schema.array(Schema.string());
|
|
74
|
+
|
|
75
|
+
const objSchema = Schema.object({
|
|
76
|
+
name: Schema.string(),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
assert.strictEqual(Schema.check(objSchema, null), "is not an object");
|
|
80
|
+
const [_, m] = Schema.transform(objSchema, null);
|
|
81
|
+
assert.ok(m === null);
|
|
82
|
+
assert.strictEqual(Schema.check(arraySchema, null), "is not an Array");
|
|
83
|
+
assert.strictEqual(Schema.check(Schema.string(), null), "is not a string");
|
|
84
|
+
});
|
package/test/tuple.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as Schema from "../src/index";
|
|
2
|
+
import { assert } from "../src/util";
|
|
3
|
+
|
|
4
|
+
declare const describe, it;
|
|
5
|
+
|
|
6
|
+
describe("tuple", () => {
|
|
7
|
+
const colorSchema = Schema.tuple([
|
|
8
|
+
Schema.object({
|
|
9
|
+
name: Schema.string(),
|
|
10
|
+
}).nullable(),
|
|
11
|
+
Schema.number(),
|
|
12
|
+
Schema.number(),
|
|
13
|
+
Schema.number(),
|
|
14
|
+
]);
|
|
15
|
+
type color = Schema.buildType<typeof colorSchema>;
|
|
16
|
+
|
|
17
|
+
it("type", () => {
|
|
18
|
+
type name = color[0];
|
|
19
|
+
type r = color[1];
|
|
20
|
+
type g = color[2];
|
|
21
|
+
type b = color[3];
|
|
22
|
+
//@ts-expect-error
|
|
23
|
+
type x = color[4];
|
|
24
|
+
|
|
25
|
+
const n1: name = null;
|
|
26
|
+
const n2: name = { name: "" };
|
|
27
|
+
//@ts-expect-error
|
|
28
|
+
const n3: name = {};
|
|
29
|
+
|
|
30
|
+
const b: b = 5;
|
|
31
|
+
//@ts-expect-error
|
|
32
|
+
const b1: b = null;
|
|
33
|
+
|
|
34
|
+
const e1 = Schema.check(colorSchema, [{ name: "red" }, 255, 0]);
|
|
35
|
+
assert(e1 === "tuple size error", "e1");
|
|
36
|
+
const e2 = Schema.check(colorSchema, [{ name: "red" }, 255, "0", 0]);
|
|
37
|
+
assert(e2 === "tuple item 2 is wrong (is not a number)", "e2");
|
|
38
|
+
const e3 = Schema.check(colorSchema, [{ name: "red" }, 255, 0, 0]);
|
|
39
|
+
assert(e3 === null, "e3");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("compound", () => {
|
|
43
|
+
const car = Schema.object({
|
|
44
|
+
name: Schema.string(),
|
|
45
|
+
color: Schema.tuple([
|
|
46
|
+
Schema.object({
|
|
47
|
+
name: Schema.string(),
|
|
48
|
+
}).nullable(),
|
|
49
|
+
Schema.number().transformer((v) => v.toString()),
|
|
50
|
+
Schema.number().transformer((v) => v.toString()),
|
|
51
|
+
Schema.number().transformer((v) => v.toString()),
|
|
52
|
+
]),
|
|
53
|
+
});
|
|
54
|
+
type car = Schema.buildType<typeof car>;
|
|
55
|
+
const [e, c] = Schema.transform(car, {
|
|
56
|
+
name: "toyota",
|
|
57
|
+
color: [{ name: "red" }, 255, 0, 0],
|
|
58
|
+
});
|
|
59
|
+
assert(c !== null, "e1");
|
|
60
|
+
assert(c.color[0]!.name === "red", "e2");
|
|
61
|
+
assert(c.color[1] === "255", "e3");
|
|
62
|
+
});
|
|
63
|
+
});
|
package/test/union.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as m from "../src/index";
|
|
2
|
+
import { assert } from "../src/util";
|
|
3
|
+
|
|
4
|
+
declare const describe, it;
|
|
5
|
+
|
|
6
|
+
describe("tuple", () => {
|
|
7
|
+
it("setting usecase", () => {
|
|
8
|
+
const setting = m.union([
|
|
9
|
+
m.object({
|
|
10
|
+
type: m.enums<"position">(["position"]),
|
|
11
|
+
value: m.enums<"left" | "right">(["left", "right"]),
|
|
12
|
+
}),
|
|
13
|
+
m.object({
|
|
14
|
+
type: m.enums<"width">(["width"]),
|
|
15
|
+
value: m.number(),
|
|
16
|
+
}),
|
|
17
|
+
]);
|
|
18
|
+
type setting = m.buildType<typeof setting>;
|
|
19
|
+
const a: setting = { type: "position", value: "left" };
|
|
20
|
+
const b: setting = { type: "width", value: 5 };
|
|
21
|
+
//@ts-expect-error
|
|
22
|
+
const c: setting = { type: "position", value: "top" };
|
|
23
|
+
//@ts-expect-error
|
|
24
|
+
const d: setting = { type: "height", value: 5 };
|
|
25
|
+
|
|
26
|
+
const datas = [
|
|
27
|
+
{ type: "position", value: "left" },
|
|
28
|
+
{ type: "position", value: "top" },
|
|
29
|
+
{ type: "width", value: 5 },
|
|
30
|
+
];
|
|
31
|
+
const valids = datas
|
|
32
|
+
.map((i) => m.transform(setting, i)[1])
|
|
33
|
+
.filter((i): i is NonNullable<typeof i> => i !== null);
|
|
34
|
+
assert(valids.length === 2, "");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("simple", () => {
|
|
38
|
+
const color = m.object({
|
|
39
|
+
name: m.string(),
|
|
40
|
+
rgb: m.tuple([m.number(), m.number(), m.number()]),
|
|
41
|
+
});
|
|
42
|
+
const schema = m.union([
|
|
43
|
+
m.string(),
|
|
44
|
+
m.boolean().transformer((_) => 1),
|
|
45
|
+
m.array(color),
|
|
46
|
+
]);
|
|
47
|
+
type u = m.buildType<typeof schema>;
|
|
48
|
+
|
|
49
|
+
const a: u = "";
|
|
50
|
+
const b: u = 1;
|
|
51
|
+
const c: u = [{ name: "red", rgb: [1, 2, 3] }];
|
|
52
|
+
//@ts-expect-error
|
|
53
|
+
const d: u = true;
|
|
54
|
+
//@ts-expect-error
|
|
55
|
+
const e: u = { name: "red", rgb: [1, 2, 3] };
|
|
56
|
+
//@ts-expect-error
|
|
57
|
+
const f: u = [{ name: "red", rgb: [1, 2, 3, 4] }];
|
|
58
|
+
|
|
59
|
+
assert(m.check(schema, "") === null, "c1");
|
|
60
|
+
assert(m.check(schema, false) === null, "c2");
|
|
61
|
+
assert(m.check(schema, 1) !== null, "c3");
|
|
62
|
+
|
|
63
|
+
assert(m.transform(schema, true)[1] === 1, "t1");
|
|
64
|
+
assert(
|
|
65
|
+
m.transform(schema, [{ name: "red", rgb: [1, 2, 3] }])[1]![0].name ===
|
|
66
|
+
"red",
|
|
67
|
+
"t2"
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
assertOk(m.transform(schema, true), "t3");
|
|
71
|
+
assertOk(m.transform(schema, [{ name: "red", rgb: [1, 2, 3] }]), "t4");
|
|
72
|
+
assertError(m.transform(schema, 1), "t5");
|
|
73
|
+
assertError(m.transform(schema, [{ name: "red", rgb: [1, 2, "3"] }]), "t6");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("compound", () => {
|
|
77
|
+
const carSchema = m.object({
|
|
78
|
+
name: m.string(),
|
|
79
|
+
color: m.union([
|
|
80
|
+
m.object({
|
|
81
|
+
name: m.enums<"red">(["red"]),
|
|
82
|
+
}),
|
|
83
|
+
m.object({
|
|
84
|
+
name: m.enums<"black">(["black"]),
|
|
85
|
+
}),
|
|
86
|
+
]),
|
|
87
|
+
});
|
|
88
|
+
type car = m.buildType<typeof carSchema>;
|
|
89
|
+
const a: car = { name: "toyota", color: { name: "red" } };
|
|
90
|
+
//@ts-expect-error
|
|
91
|
+
const b: car = { name: "toyota", color: { name: "blue" } };
|
|
92
|
+
|
|
93
|
+
assertOk(
|
|
94
|
+
m.transform(carSchema, { name: "toyota", color: { name: "red" } }),
|
|
95
|
+
"e1"
|
|
96
|
+
);
|
|
97
|
+
assertError(
|
|
98
|
+
m.transform(carSchema, { name: "toyota", color: { name: "blue" } }),
|
|
99
|
+
"e2"
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function assertOk<data>(result: [null, data] | [string, null], msg: string) {
|
|
105
|
+
const [error, data] = result;
|
|
106
|
+
assert(error === null, msg);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function assertError<data>(result: [null, data] | [string, null], msg: string) {
|
|
110
|
+
const [error, data] = result;
|
|
111
|
+
assert(error !== null, msg);
|
|
112
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"outDir": "dst/",
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"strictNullChecks": true,
|
|
6
|
+
"target": "ES6",
|
|
7
|
+
"module": "commonjs",
|
|
8
|
+
// "jsx": "react",
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src/index.ts",
|
|
12
|
+
"test.ts",
|
|
13
|
+
],
|
|
14
|
+
"include": [
|
|
15
|
+
"test",
|
|
16
|
+
],
|
|
17
|
+
}
|