@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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const assert = require("assert");
|
|
4
|
+
const Schema = require("../src/index");
|
|
5
|
+
describe("nullable field", () => {
|
|
6
|
+
describe("atom", () => {
|
|
7
|
+
const schema = Schema.object({
|
|
8
|
+
name: Schema.string(),
|
|
9
|
+
age: Schema.string().transformer((v) => parseInt(v)),
|
|
10
|
+
});
|
|
11
|
+
const nullableSchema = Schema.object({
|
|
12
|
+
name: Schema.string(),
|
|
13
|
+
age: Schema.string()
|
|
14
|
+
.nullable()
|
|
15
|
+
.transformer((v) => parseInt(v)),
|
|
16
|
+
});
|
|
17
|
+
const o = {
|
|
18
|
+
name: "jack",
|
|
19
|
+
age: null,
|
|
20
|
+
};
|
|
21
|
+
it("is null -- only check", () => {
|
|
22
|
+
assert.ok(Schema.check(schema, o) !== null);
|
|
23
|
+
assert.ok(Schema.check(nullableSchema, o) === null);
|
|
24
|
+
});
|
|
25
|
+
it("is null -- transform", () => {
|
|
26
|
+
const [error, _] = Schema.transform(schema, o);
|
|
27
|
+
assert.ok(error !== null);
|
|
28
|
+
const [__, m] = Schema.transform(nullableSchema, o);
|
|
29
|
+
assert.ok(m !== null);
|
|
30
|
+
assert.ok(m.age === null);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe("array", () => {
|
|
34
|
+
const schema = Schema.object({
|
|
35
|
+
name: Schema.string(),
|
|
36
|
+
friends: Schema.array(Schema.number()),
|
|
37
|
+
});
|
|
38
|
+
const nullableSchema = Schema.object({
|
|
39
|
+
name: Schema.string(),
|
|
40
|
+
friends: Schema.array(Schema.number()).nullable(),
|
|
41
|
+
});
|
|
42
|
+
const o = {
|
|
43
|
+
name: "jack",
|
|
44
|
+
friends: null,
|
|
45
|
+
};
|
|
46
|
+
it("is null -- only check", () => {
|
|
47
|
+
assert.ok(Schema.check(schema, o) !== null);
|
|
48
|
+
assert.ok(Schema.check(nullableSchema, o) === null);
|
|
49
|
+
});
|
|
50
|
+
it("is null -- transform", () => {
|
|
51
|
+
const [error, _] = Schema.transform(schema, o);
|
|
52
|
+
assert.ok(error !== null);
|
|
53
|
+
const [__, m] = Schema.transform(nullableSchema, o);
|
|
54
|
+
assert.ok(m !== null);
|
|
55
|
+
assert.ok(m.friends === null);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
it("pass null to a non-nullable schema", () => {
|
|
60
|
+
const arraySchema = Schema.array(Schema.string());
|
|
61
|
+
const objSchema = Schema.object({
|
|
62
|
+
name: Schema.string(),
|
|
63
|
+
});
|
|
64
|
+
assert.strictEqual(Schema.check(objSchema, null), "is not an object");
|
|
65
|
+
const [_, m] = Schema.transform(objSchema, null);
|
|
66
|
+
assert.ok(m === null);
|
|
67
|
+
assert.strictEqual(Schema.check(arraySchema, null), "is not an Array");
|
|
68
|
+
assert.strictEqual(Schema.check(Schema.string(), null), "is not a string");
|
|
69
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const Schema = require("../src/index");
|
|
4
|
+
const util_1 = require("../src/util");
|
|
5
|
+
describe("tuple", () => {
|
|
6
|
+
const colorSchema = Schema.tuple([
|
|
7
|
+
Schema.object({
|
|
8
|
+
name: Schema.string(),
|
|
9
|
+
}).nullable(),
|
|
10
|
+
Schema.number(),
|
|
11
|
+
Schema.number(),
|
|
12
|
+
Schema.number(),
|
|
13
|
+
]);
|
|
14
|
+
it("type", () => {
|
|
15
|
+
const n1 = null;
|
|
16
|
+
const n2 = { name: "" };
|
|
17
|
+
//@ts-expect-error
|
|
18
|
+
const n3 = {};
|
|
19
|
+
const b = 5;
|
|
20
|
+
//@ts-expect-error
|
|
21
|
+
const b1 = null;
|
|
22
|
+
const e1 = Schema.check(colorSchema, [{ name: "red" }, 255, 0]);
|
|
23
|
+
(0, util_1.assert)(e1 === "tuple size error", "e1");
|
|
24
|
+
const e2 = Schema.check(colorSchema, [{ name: "red" }, 255, "0", 0]);
|
|
25
|
+
(0, util_1.assert)(e2 === "tuple item 2 is wrong (is not a number)", "e2");
|
|
26
|
+
const e3 = Schema.check(colorSchema, [{ name: "red" }, 255, 0, 0]);
|
|
27
|
+
(0, util_1.assert)(e3 === null, "e3");
|
|
28
|
+
});
|
|
29
|
+
it("compound", () => {
|
|
30
|
+
const car = Schema.object({
|
|
31
|
+
name: Schema.string(),
|
|
32
|
+
color: Schema.tuple([
|
|
33
|
+
Schema.object({
|
|
34
|
+
name: Schema.string(),
|
|
35
|
+
}).nullable(),
|
|
36
|
+
Schema.number().transformer((v) => v.toString()),
|
|
37
|
+
Schema.number().transformer((v) => v.toString()),
|
|
38
|
+
Schema.number().transformer((v) => v.toString()),
|
|
39
|
+
]),
|
|
40
|
+
});
|
|
41
|
+
const [e, c] = Schema.transform(car, {
|
|
42
|
+
name: "toyota",
|
|
43
|
+
color: [{ name: "red" }, 255, 0, 0],
|
|
44
|
+
});
|
|
45
|
+
(0, util_1.assert)(c !== null, "e1");
|
|
46
|
+
(0, util_1.assert)(c.color[0].name === "red", "e2");
|
|
47
|
+
(0, util_1.assert)(c.color[1] === "255", "e3");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const m = require("../src/index");
|
|
4
|
+
const util_1 = require("../src/util");
|
|
5
|
+
describe("tuple", () => {
|
|
6
|
+
it("setting usecase", () => {
|
|
7
|
+
const setting = m.union([
|
|
8
|
+
m.object({
|
|
9
|
+
type: m.enums(["position"]),
|
|
10
|
+
value: m.enums(["left", "right"]),
|
|
11
|
+
}),
|
|
12
|
+
m.object({
|
|
13
|
+
type: m.enums(["width"]),
|
|
14
|
+
value: m.number(),
|
|
15
|
+
}),
|
|
16
|
+
]);
|
|
17
|
+
const a = { type: "position", value: "left" };
|
|
18
|
+
const b = { type: "width", value: 5 };
|
|
19
|
+
//@ts-expect-error
|
|
20
|
+
const c = { type: "position", value: "top" };
|
|
21
|
+
//@ts-expect-error
|
|
22
|
+
const d = { type: "height", value: 5 };
|
|
23
|
+
const datas = [
|
|
24
|
+
{ type: "position", value: "left" },
|
|
25
|
+
{ type: "position", value: "top" },
|
|
26
|
+
{ type: "width", value: 5 },
|
|
27
|
+
];
|
|
28
|
+
const valids = datas
|
|
29
|
+
.map((i) => m.transform(setting, i)[1])
|
|
30
|
+
.filter((i) => i !== null);
|
|
31
|
+
(0, util_1.assert)(valids.length === 2, "");
|
|
32
|
+
});
|
|
33
|
+
it("simple", () => {
|
|
34
|
+
const color = m.object({
|
|
35
|
+
name: m.string(),
|
|
36
|
+
rgb: m.tuple([m.number(), m.number(), m.number()]),
|
|
37
|
+
});
|
|
38
|
+
const schema = m.union([
|
|
39
|
+
m.string(),
|
|
40
|
+
m.boolean().transformer((_) => 1),
|
|
41
|
+
m.array(color),
|
|
42
|
+
]);
|
|
43
|
+
const a = "";
|
|
44
|
+
const b = 1;
|
|
45
|
+
const c = [{ name: "red", rgb: [1, 2, 3] }];
|
|
46
|
+
//@ts-expect-error
|
|
47
|
+
const d = true;
|
|
48
|
+
//@ts-expect-error
|
|
49
|
+
const e = { name: "red", rgb: [1, 2, 3] };
|
|
50
|
+
//@ts-expect-error
|
|
51
|
+
const f = [{ name: "red", rgb: [1, 2, 3, 4] }];
|
|
52
|
+
(0, util_1.assert)(m.check(schema, "") === null, "c1");
|
|
53
|
+
(0, util_1.assert)(m.check(schema, false) === null, "c2");
|
|
54
|
+
(0, util_1.assert)(m.check(schema, 1) !== null, "c3");
|
|
55
|
+
(0, util_1.assert)(m.transform(schema, true)[1] === 1, "t1");
|
|
56
|
+
(0, util_1.assert)(m.transform(schema, [{ name: "red", rgb: [1, 2, 3] }])[1][0].name ===
|
|
57
|
+
"red", "t2");
|
|
58
|
+
assertOk(m.transform(schema, true), "t3");
|
|
59
|
+
assertOk(m.transform(schema, [{ name: "red", rgb: [1, 2, 3] }]), "t4");
|
|
60
|
+
assertError(m.transform(schema, 1), "t5");
|
|
61
|
+
assertError(m.transform(schema, [{ name: "red", rgb: [1, 2, "3"] }]), "t6");
|
|
62
|
+
});
|
|
63
|
+
it("compound", () => {
|
|
64
|
+
const carSchema = m.object({
|
|
65
|
+
name: m.string(),
|
|
66
|
+
color: m.union([
|
|
67
|
+
m.object({
|
|
68
|
+
name: m.enums(["red"]),
|
|
69
|
+
}),
|
|
70
|
+
m.object({
|
|
71
|
+
name: m.enums(["black"]),
|
|
72
|
+
}),
|
|
73
|
+
]),
|
|
74
|
+
});
|
|
75
|
+
const a = { name: "toyota", color: { name: "red" } };
|
|
76
|
+
//@ts-expect-error
|
|
77
|
+
const b = { name: "toyota", color: { name: "blue" } };
|
|
78
|
+
assertOk(m.transform(carSchema, { name: "toyota", color: { name: "red" } }), "e1");
|
|
79
|
+
assertError(m.transform(carSchema, { name: "toyota", color: { name: "blue" } }), "e2");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
function assertOk(result, msg) {
|
|
83
|
+
const [error, data] = result;
|
|
84
|
+
(0, util_1.assert)(error === null, msg);
|
|
85
|
+
}
|
|
86
|
+
function assertError(result, msg) {
|
|
87
|
+
const [error, data] = result;
|
|
88
|
+
(0, util_1.assert)(error !== null, msg);
|
|
89
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leon740727/type-schema",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dst/src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "npx mocha dst/test"
|
|
8
|
+
},
|
|
9
|
+
"author": "",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/node": "^14.14.34",
|
|
13
|
+
"@types/ramda": "^0.27.38",
|
|
14
|
+
"mocha": "^8.3.2",
|
|
15
|
+
"typescript": "^4.1.5"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"ramda": "^0.27.1",
|
|
19
|
+
"types": "https://github.com/leon740727/types.git#semver:~0.3.2"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/check.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { not, toPairs, zip } from "ramda";
|
|
2
|
+
import { Optional } from "types";
|
|
3
|
+
import {
|
|
4
|
+
Schema,
|
|
5
|
+
SchemaType,
|
|
6
|
+
AtomSchema,
|
|
7
|
+
InnerSchemaForObjectSchema,
|
|
8
|
+
} from "./type";
|
|
9
|
+
import { assert, pair } from "./util";
|
|
10
|
+
|
|
11
|
+
type Error = {
|
|
12
|
+
paths: string[];
|
|
13
|
+
msg: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function check(schema: Schema, value): Optional<string> {
|
|
17
|
+
return _check(schema, value).map(error2string);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function error2string(error: Error) {
|
|
21
|
+
if (error.paths.length > 0) {
|
|
22
|
+
return `obj.${error.paths.join(".")} ${error.msg}`;
|
|
23
|
+
} else {
|
|
24
|
+
return error.msg;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function _check(schema: Schema, value): Optional<Error> {
|
|
29
|
+
if (value === null && schema.isNullable) {
|
|
30
|
+
return Optional.empty();
|
|
31
|
+
}
|
|
32
|
+
if (value === undefined && schema.isOptional) {
|
|
33
|
+
return Optional.empty();
|
|
34
|
+
}
|
|
35
|
+
if (schema.type === SchemaType.object) {
|
|
36
|
+
if (typeof value === "object" && value !== null) {
|
|
37
|
+
// typeof null === 'object'
|
|
38
|
+
assert(schema.innerSchema, "object inner schema is null or undefined");
|
|
39
|
+
return checkObject(schema.innerSchema, value);
|
|
40
|
+
} else {
|
|
41
|
+
return Optional.of({
|
|
42
|
+
paths: [],
|
|
43
|
+
msg: "is not an object",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
} else if (schema.type === SchemaType.array) {
|
|
47
|
+
if (value instanceof Array) {
|
|
48
|
+
return checkArray(schema.itemSchema, value);
|
|
49
|
+
} else {
|
|
50
|
+
return Optional.of({
|
|
51
|
+
paths: [],
|
|
52
|
+
msg: "is not an Array",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
} else if (schema.type === SchemaType.tuple) {
|
|
56
|
+
if (value instanceof Array) {
|
|
57
|
+
assert(schema.innerSchema, "tuple inner schema is null or undefined");
|
|
58
|
+
return checkTuple(schema.innerSchema, value);
|
|
59
|
+
} else {
|
|
60
|
+
return Optional.of({
|
|
61
|
+
paths: [],
|
|
62
|
+
msg: "is not a tuple",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
} else if (schema.type === SchemaType.union) {
|
|
66
|
+
assert(schema.innerSchema, "union inner schema is null or undefined");
|
|
67
|
+
return checkUnion(schema.innerSchema, value);
|
|
68
|
+
} else {
|
|
69
|
+
return checkAtom(schema, value);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function checkObject(
|
|
74
|
+
schema: InnerSchemaForObjectSchema,
|
|
75
|
+
value
|
|
76
|
+
): Optional<Error> {
|
|
77
|
+
// schema 沒有規範到的欄位不檢查
|
|
78
|
+
const toChecks: [string, Schema][] = toPairs(schema)
|
|
79
|
+
.filter(([field, schema]) => {
|
|
80
|
+
return (
|
|
81
|
+
schema.isOptional === false ||
|
|
82
|
+
(schema.isOptional && value[field] !== undefined)
|
|
83
|
+
);
|
|
84
|
+
})
|
|
85
|
+
.map(([field, schema]) => [field, schema]);
|
|
86
|
+
|
|
87
|
+
const errors = toChecks.map(([field, schema]) => {
|
|
88
|
+
return _check(schema, value[field]).map((error) => ({
|
|
89
|
+
paths: [field].concat(error.paths),
|
|
90
|
+
msg: error.msg,
|
|
91
|
+
}));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return Optional.of(Optional.filter(errors)[0]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function checkArray(schema: Schema, value: any[]): Optional<Error> {
|
|
98
|
+
const errors = value.map((v, idx) => {
|
|
99
|
+
return _check(schema, v).map((error) => pair(idx, error));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return Optional.of(Optional.filter(errors)[0]).map(([idx, error]) => ({
|
|
103
|
+
paths: [],
|
|
104
|
+
msg: `array item ${idx} is wrong (${error2string(error)})`,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function checkTuple(schemas: Schema[], values: any[]): Optional<Error> {
|
|
109
|
+
if (schemas.length !== values.length) {
|
|
110
|
+
return Optional.of({ paths: [], msg: "tuple size error" });
|
|
111
|
+
} else {
|
|
112
|
+
const errors = zip(schemas, values).map(([schema, value], idx) =>
|
|
113
|
+
_check(schema, value).map((error) => ({
|
|
114
|
+
paths: [],
|
|
115
|
+
msg: `tuple item ${idx} is wrong (${error2string(error)})`,
|
|
116
|
+
}))
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return Optional.of(Optional.filter(errors)[0]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function checkUnion(schemas: Schema[], value: any): Optional<Error> {
|
|
124
|
+
const success = schemas.reduce((success, schema) => {
|
|
125
|
+
if (success) {
|
|
126
|
+
return success;
|
|
127
|
+
} else {
|
|
128
|
+
const error = _check(schema, value);
|
|
129
|
+
return not(error.present);
|
|
130
|
+
}
|
|
131
|
+
}, false);
|
|
132
|
+
if (success) {
|
|
133
|
+
return Optional.empty();
|
|
134
|
+
} else {
|
|
135
|
+
return Optional.of({
|
|
136
|
+
paths: [],
|
|
137
|
+
msg: "not in union",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function checkAtom(schema: AtomSchema<any, any>, value): Optional<Error> {
|
|
143
|
+
return Optional.of(schema.isa(value)).map((msg) => ({
|
|
144
|
+
paths: [],
|
|
145
|
+
msg,
|
|
146
|
+
}));
|
|
147
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Schema, build } from "./type";
|
|
2
|
+
import { check as _check } from "./check";
|
|
3
|
+
import { transform as _transform } from "./transform";
|
|
4
|
+
|
|
5
|
+
export { Schema, SchemaType } from "./type";
|
|
6
|
+
export const value = Schema.value;
|
|
7
|
+
export const object = Schema.object;
|
|
8
|
+
export const array = Schema.array;
|
|
9
|
+
export const tuple = Schema.tuple;
|
|
10
|
+
export const union = Schema.union;
|
|
11
|
+
export type buildType<
|
|
12
|
+
T extends Schema,
|
|
13
|
+
transformed extends boolean = true
|
|
14
|
+
> = build<T, transformed>;
|
|
15
|
+
|
|
16
|
+
export function check(schema: Schema, value): string | null {
|
|
17
|
+
return _check(schema, value).orNull();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Result<R> = [string, null] | [null, R];
|
|
21
|
+
export function transform<S extends Schema>(
|
|
22
|
+
schema: S,
|
|
23
|
+
value
|
|
24
|
+
): Result<build<S, true>> {
|
|
25
|
+
return _transform(schema, value).either(
|
|
26
|
+
//@ts-ignore
|
|
27
|
+
(error) => [error, null] as Result<build<S, true>>,
|
|
28
|
+
(value) => [null, value]
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// helper
|
|
33
|
+
export function any() {
|
|
34
|
+
return value<any>((v) => null);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function string() {
|
|
38
|
+
return value<string>((v) =>
|
|
39
|
+
typeof v === "string" ? null : "is not a string"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function number() {
|
|
44
|
+
return value<number>((v) =>
|
|
45
|
+
typeof v === "number" ? null : "is not a number"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function boolean() {
|
|
50
|
+
return value<boolean>((v) =>
|
|
51
|
+
typeof v === "boolean" ? null : "is not a boolean"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function bigint() {
|
|
56
|
+
return value<bigint>((v) =>
|
|
57
|
+
typeof v === "bigint" ? null : "is not a bigint"
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function date() {
|
|
62
|
+
return value<Date>((v) => (v instanceof Date ? null : "is not a Date"));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function enums<T extends number | string>(valids: T[]) {
|
|
66
|
+
return Schema.value<T>((v) =>
|
|
67
|
+
valids.includes(v)
|
|
68
|
+
? null
|
|
69
|
+
: `not a valid enum value, value should be one of [${valids}]`
|
|
70
|
+
);
|
|
71
|
+
}
|
package/src/transform.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { fromPairs, mergeRight, toPairs, zip } from "ramda";
|
|
2
|
+
import { Result } from "types";
|
|
3
|
+
import { Schema, SchemaType, build, fetchAtom, fetchObject } from "./type";
|
|
4
|
+
import { check } from "./check";
|
|
5
|
+
import { assert, pair } from "./util";
|
|
6
|
+
|
|
7
|
+
export function transform<S extends Schema>(
|
|
8
|
+
schema: S,
|
|
9
|
+
value
|
|
10
|
+
): Result<string, build<S, true>> {
|
|
11
|
+
return check(schema, value)
|
|
12
|
+
.map((error) => Result.fail<string, build<S, true>>(error))
|
|
13
|
+
.orExec(() => Result.ok(_transform(schema, value)));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function _transform<S extends Schema>(schema: S, value): build<S, true> {
|
|
17
|
+
if (value === null) {
|
|
18
|
+
// check() 已經確認過 null 是合法的
|
|
19
|
+
return null as any;
|
|
20
|
+
}
|
|
21
|
+
if (value === undefined) {
|
|
22
|
+
return undefined as any;
|
|
23
|
+
}
|
|
24
|
+
if (schema.type === SchemaType.atom) {
|
|
25
|
+
return transformAtom(schema, value);
|
|
26
|
+
} else if (schema.type === SchemaType.array) {
|
|
27
|
+
return transformArray(schema, value);
|
|
28
|
+
} else if (schema.type === SchemaType.tuple) {
|
|
29
|
+
return transformTuple(schema, value);
|
|
30
|
+
} else if (schema.type === SchemaType.union) {
|
|
31
|
+
return transformUnion(schema, value);
|
|
32
|
+
} else {
|
|
33
|
+
return transformObject(schema, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function transformAtom<S extends Schema>(schema: S, value): build<S, true> {
|
|
38
|
+
return (schema as fetchAtom<S>).transform(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function transformArray<S extends Schema>(
|
|
42
|
+
schema: S,
|
|
43
|
+
values: any[]
|
|
44
|
+
): build<S, true> {
|
|
45
|
+
assert(schema.type === SchemaType.array, "");
|
|
46
|
+
//@ts-ignore
|
|
47
|
+
return values.map((value) => _transform(schema.itemSchema, value)) as any;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function transformTuple<S extends Schema>(
|
|
51
|
+
schema: S,
|
|
52
|
+
values: any[]
|
|
53
|
+
): build<S, true> {
|
|
54
|
+
assert(schema.type === SchemaType.tuple, "");
|
|
55
|
+
assert(schema.innerSchema, "");
|
|
56
|
+
return zip(schema.innerSchema, values).map(([schema, value]) =>
|
|
57
|
+
_transform(schema, value)
|
|
58
|
+
) as any;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function transformUnion<S extends Schema>(
|
|
62
|
+
schema: S,
|
|
63
|
+
value: any
|
|
64
|
+
): build<S, true> {
|
|
65
|
+
assert(schema.type === SchemaType.union, "");
|
|
66
|
+
assert(schema.innerSchema, "");
|
|
67
|
+
// 這裡不能用 _transform
|
|
68
|
+
// 因為 union 裡面的 schema 可能是錯的,但 _transform 假設每一個 schema 都是對的
|
|
69
|
+
const results = schema.innerSchema.map((schema) => transform(schema, value));
|
|
70
|
+
const oks = results.filter((i) => i.ok).map((i) => i.orError());
|
|
71
|
+
assert(oks.length > 0, "");
|
|
72
|
+
return oks[0];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function transformObject<S extends Schema>(
|
|
76
|
+
schema: S,
|
|
77
|
+
value: { [field: string]: any }
|
|
78
|
+
): build<S, true> {
|
|
79
|
+
const innerSchema = (schema as fetchObject<Schema>).innerSchema;
|
|
80
|
+
assert(innerSchema, "object inner schema is null or undefined");
|
|
81
|
+
const todos: [string, Schema][] = toPairs(innerSchema)
|
|
82
|
+
.filter(([field, schema]) => {
|
|
83
|
+
return (
|
|
84
|
+
schema.isOptional === false ||
|
|
85
|
+
(schema.isOptional && value[field] !== undefined)
|
|
86
|
+
);
|
|
87
|
+
})
|
|
88
|
+
.map(([field, schema]) => [field, schema]);
|
|
89
|
+
|
|
90
|
+
const v2 = fromPairs(
|
|
91
|
+
todos.map(([field, schema]) =>
|
|
92
|
+
pair(field, _transform(schema, value[field]))
|
|
93
|
+
)
|
|
94
|
+
);
|
|
95
|
+
return mergeRight(value, v2) as any;
|
|
96
|
+
}
|