@fncts/schema 0.0.13 → 0.0.15
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/AST.d.ts +29 -12
- package/Guard.d.ts +9 -0
- package/ParseError.d.ts +40 -3
- package/ParseFailure.d.ts +18 -0
- package/ParseResult.d.ts +2 -1
- package/Parser/api.d.ts +0 -6
- package/Parser/interpreter.d.ts +1 -2
- package/Show.d.ts +1 -1
- package/_cjs/AST.cjs +115 -65
- package/_cjs/AST.cjs.map +1 -1
- package/_cjs/Guard.cjs +278 -0
- package/_cjs/Guard.cjs.map +1 -0
- package/_cjs/ParseError.cjs +77 -3
- package/_cjs/ParseError.cjs.map +1 -1
- package/_cjs/ParseFailure.cjs +28 -0
- package/_cjs/ParseFailure.cjs.map +1 -0
- package/_cjs/ParseResult.cjs +4 -3
- package/_cjs/ParseResult.cjs.map +1 -1
- package/_cjs/Parser/api.cjs +11 -23
- package/_cjs/Parser/api.cjs.map +1 -1
- package/_cjs/Parser/interpreter.cjs +93 -141
- package/_cjs/Parser/interpreter.cjs.map +1 -1
- package/_cjs/Schema/api/conc.cjs +1 -1
- package/_cjs/Schema/api/conc.cjs.map +1 -1
- package/_cjs/Schema/api/hashMap.cjs +1 -1
- package/_cjs/Schema/api/hashMap.cjs.map +1 -1
- package/_cjs/Schema/api/immutableArray.cjs +1 -1
- package/_cjs/Schema/api/immutableArray.cjs.map +1 -1
- package/_cjs/Schema/api/list.cjs +1 -1
- package/_cjs/Schema/api/list.cjs.map +1 -1
- package/_cjs/Schema/api.cjs +27 -28
- package/_cjs/Schema/api.cjs.map +1 -1
- package/_cjs/Show.cjs +23 -12
- package/_cjs/Show.cjs.map +1 -1
- package/_mjs/AST.mjs +107 -61
- package/_mjs/AST.mjs.map +1 -1
- package/_mjs/Guard.mjs +269 -0
- package/_mjs/Guard.mjs.map +1 -0
- package/_mjs/ParseError.mjs +72 -2
- package/_mjs/ParseError.mjs.map +1 -1
- package/_mjs/ParseFailure.mjs +20 -0
- package/_mjs/ParseFailure.mjs.map +1 -0
- package/_mjs/ParseResult.mjs +4 -3
- package/_mjs/ParseResult.mjs.map +1 -1
- package/_mjs/Parser/api.mjs +10 -21
- package/_mjs/Parser/api.mjs.map +1 -1
- package/_mjs/Parser/interpreter.mjs +94 -142
- package/_mjs/Parser/interpreter.mjs.map +1 -1
- package/_mjs/Schema/api/conc.mjs +1 -1
- package/_mjs/Schema/api/conc.mjs.map +1 -1
- package/_mjs/Schema/api/hashMap.mjs +1 -1
- package/_mjs/Schema/api/hashMap.mjs.map +1 -1
- package/_mjs/Schema/api/immutableArray.mjs +1 -1
- package/_mjs/Schema/api/immutableArray.mjs.map +1 -1
- package/_mjs/Schema/api/list.mjs +1 -1
- package/_mjs/Schema/api/list.mjs.map +1 -1
- package/_mjs/Schema/api.mjs +27 -28
- package/_mjs/Schema/api.mjs.map +1 -1
- package/_mjs/Show.mjs +23 -12
- package/_mjs/Show.mjs.map +1 -1
- package/_src/AST.ts +96 -47
- package/_src/Guard.ts +268 -0
- package/_src/ParseError.ts +88 -4
- package/_src/ParseFailure.ts +18 -0
- package/_src/ParseResult.ts +3 -3
- package/_src/Parser/api.ts +8 -21
- package/_src/Parser/interpreter.ts +94 -128
- package/_src/Schema/api/conc.ts +1 -1
- package/_src/Schema/api/hashMap.ts +1 -1
- package/_src/Schema/api/immutableArray.ts +1 -1
- package/_src/Schema/api/list.ts +1 -1
- package/_src/Schema/api.ts +3 -10
- package/_src/Show.ts +28 -15
- package/_src/global.ts +4 -0
- package/global.d.ts +4 -0
- package/package.json +3 -3
package/_src/AST.ts
CHANGED
|
@@ -623,6 +623,11 @@ export function createTuple(
|
|
|
623
623
|
return new Tuple(elements, rest, isReadonly, annotations);
|
|
624
624
|
}
|
|
625
625
|
|
|
626
|
+
/**
|
|
627
|
+
* @tsplus static fncts.schema.ASTOps unknownArray
|
|
628
|
+
*/
|
|
629
|
+
export const unknownArray = AST.createTuple(Vector.empty(), Just(Vector(AST.unknownKeyword)), true);
|
|
630
|
+
|
|
626
631
|
/*
|
|
627
632
|
* PropertySignature
|
|
628
633
|
*/
|
|
@@ -732,6 +737,17 @@ export function createTypeLiteral(
|
|
|
732
737
|
return new TypeLiteral(propertySignatures, indexSignatures, annotations);
|
|
733
738
|
}
|
|
734
739
|
|
|
740
|
+
/**
|
|
741
|
+
* @tsplus static fncts.schema.ASTOps unknownRecord
|
|
742
|
+
*/
|
|
743
|
+
export const unknownRecord = AST.createTypeLiteral(
|
|
744
|
+
Vector.empty(),
|
|
745
|
+
Vector(
|
|
746
|
+
AST.createIndexSignature(AST.stringKeyword, AST.unknownKeyword, true),
|
|
747
|
+
AST.createIndexSignature(AST.symbolKeyword, AST.unknownKeyword, true),
|
|
748
|
+
),
|
|
749
|
+
);
|
|
750
|
+
|
|
735
751
|
/*
|
|
736
752
|
* Union
|
|
737
753
|
*/
|
|
@@ -814,18 +830,20 @@ export class Refinement extends AST {
|
|
|
814
830
|
readonly _tag = ASTTag.Refinement;
|
|
815
831
|
constructor(
|
|
816
832
|
readonly from: AST,
|
|
817
|
-
readonly
|
|
818
|
-
readonly isReversed: boolean,
|
|
833
|
+
readonly predicate: (input: any) => boolean,
|
|
819
834
|
readonly annotations: ASTAnnotationMap = ASTAnnotationMap.empty,
|
|
820
835
|
) {
|
|
821
836
|
super();
|
|
822
837
|
}
|
|
823
838
|
|
|
839
|
+
decode(input: any, options?: ParseOptions): ParseResult<any> {
|
|
840
|
+
return this.predicate(input) ? ParseResult.succeed(input) : ParseResult.fail(ParseError.TypeError(this, input));
|
|
841
|
+
}
|
|
842
|
+
|
|
824
843
|
clone(newProperties: Partial<this>): AST {
|
|
825
844
|
return new Refinement(
|
|
826
845
|
newProperties.from ?? this.from,
|
|
827
|
-
newProperties.
|
|
828
|
-
newProperties.isReversed ?? this.isReversed,
|
|
846
|
+
newProperties.predicate ?? this.predicate,
|
|
829
847
|
newProperties.annotations ?? this.annotations,
|
|
830
848
|
);
|
|
831
849
|
}
|
|
@@ -836,11 +854,10 @@ export class Refinement extends AST {
|
|
|
836
854
|
*/
|
|
837
855
|
export function createRefinement(
|
|
838
856
|
from: AST,
|
|
839
|
-
|
|
840
|
-
isReversed: boolean,
|
|
857
|
+
predicate: (input: any) => boolean,
|
|
841
858
|
annotations?: ASTAnnotationMap,
|
|
842
859
|
): Refinement {
|
|
843
|
-
return new Refinement(from,
|
|
860
|
+
return new Refinement(from, predicate, annotations);
|
|
844
861
|
}
|
|
845
862
|
|
|
846
863
|
export function isRefinement(self: AST): self is Refinement {
|
|
@@ -864,7 +881,6 @@ export class Transform extends AST {
|
|
|
864
881
|
readonly to: AST,
|
|
865
882
|
readonly decode: (input: any, options?: ParseOptions) => ParseResult<any>,
|
|
866
883
|
readonly encode: (input: any, options?: ParseOptions) => ParseResult<any>,
|
|
867
|
-
readonly isReversed: boolean,
|
|
868
884
|
readonly annotations: ASTAnnotationMap = ASTAnnotationMap.empty,
|
|
869
885
|
) {
|
|
870
886
|
super();
|
|
@@ -876,7 +892,6 @@ export class Transform extends AST {
|
|
|
876
892
|
newProperties.to ?? this.to,
|
|
877
893
|
newProperties.decode ?? this.decode,
|
|
878
894
|
newProperties.encode ?? this.encode,
|
|
879
|
-
newProperties.isReversed ?? this.isReversed,
|
|
880
895
|
newProperties.annotations ?? this.annotations,
|
|
881
896
|
);
|
|
882
897
|
}
|
|
@@ -890,10 +905,9 @@ export function createTransform(
|
|
|
890
905
|
to: AST,
|
|
891
906
|
decode: (input: any, options?: ParseOptions) => ParseResult<any>,
|
|
892
907
|
encode: (input: any, options?: ParseOptions) => ParseResult<any>,
|
|
893
|
-
isReversed: boolean,
|
|
894
908
|
annotations?: ASTAnnotationMap,
|
|
895
909
|
): Transform {
|
|
896
|
-
return new Transform(from, getTo(to), decode, encode,
|
|
910
|
+
return new Transform(from, getTo(to), decode, encode, annotations);
|
|
897
911
|
}
|
|
898
912
|
|
|
899
913
|
/*
|
|
@@ -1317,48 +1331,13 @@ export function getTo(ast: AST): AST {
|
|
|
1317
1331
|
case ASTTag.Lazy:
|
|
1318
1332
|
return AST.createLazy(() => getTo(ast.getAST()), ast.annotations);
|
|
1319
1333
|
case ASTTag.Refinement:
|
|
1320
|
-
return AST.createRefinement(getTo(ast.from), ast.
|
|
1334
|
+
return AST.createRefinement(getTo(ast.from), ast.predicate, ast.annotations);
|
|
1321
1335
|
case ASTTag.Transform:
|
|
1322
1336
|
return getTo(ast.to);
|
|
1323
1337
|
}
|
|
1324
1338
|
return ast;
|
|
1325
1339
|
}
|
|
1326
1340
|
|
|
1327
|
-
/**
|
|
1328
|
-
* @tsplus getter fncts.schema.AST reverse
|
|
1329
|
-
*/
|
|
1330
|
-
export function reverse(ast: AST): AST {
|
|
1331
|
-
AST.concrete(ast);
|
|
1332
|
-
switch (ast._tag) {
|
|
1333
|
-
case ASTTag.Declaration:
|
|
1334
|
-
return AST.createDeclaration(ast.typeParameters.map(reverse), ast.type, ast.decode, ast.annotations);
|
|
1335
|
-
case ASTTag.Tuple:
|
|
1336
|
-
return AST.createTuple(
|
|
1337
|
-
ast.elements.map((element) => AST.createElement(reverse(element.type), element.isOptional)),
|
|
1338
|
-
ast.rest.map((restElement) => restElement.map(reverse)),
|
|
1339
|
-
ast.isReadonly,
|
|
1340
|
-
ast.annotations,
|
|
1341
|
-
);
|
|
1342
|
-
case ASTTag.TypeLiteral:
|
|
1343
|
-
return AST.createTypeLiteral(
|
|
1344
|
-
ast.propertySignatures.map((ps) =>
|
|
1345
|
-
AST.createPropertySignature(ps.name, reverse(ps.type), ps.isOptional, ps.isReadonly, ps.annotations),
|
|
1346
|
-
),
|
|
1347
|
-
ast.indexSignatures.map((is) => AST.createIndexSignature(is.parameter, reverse(is.type), is.isReadonly)),
|
|
1348
|
-
ast.annotations,
|
|
1349
|
-
);
|
|
1350
|
-
case ASTTag.Union:
|
|
1351
|
-
return AST.createUnion(ast.types.map(reverse), ast.annotations);
|
|
1352
|
-
case ASTTag.Lazy:
|
|
1353
|
-
return AST.createLazy(() => reverse(ast.getAST()), ast.annotations);
|
|
1354
|
-
case ASTTag.Refinement:
|
|
1355
|
-
return AST.createRefinement(ast.from, ast.decode, !ast.isReversed, ast.annotations);
|
|
1356
|
-
case ASTTag.Transform:
|
|
1357
|
-
return AST.createTransform(reverse(ast.from), ast.to, ast.decode, ast.encode, !ast.isReversed, ast.annotations);
|
|
1358
|
-
}
|
|
1359
|
-
return ast;
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
1341
|
/**
|
|
1363
1342
|
* @tsplus static fncts.schema.AST getCompiler
|
|
1364
1343
|
*/
|
|
@@ -1369,3 +1348,73 @@ export function getCompiler<A>(match: AST.Match<A>): AST.Compiler<A> {
|
|
|
1369
1348
|
});
|
|
1370
1349
|
return compile;
|
|
1371
1350
|
}
|
|
1351
|
+
|
|
1352
|
+
export function getLiterals(ast: AST, isDecoding: boolean): ReadonlyArray<[PropertyKey, Literal]> {
|
|
1353
|
+
AST.concrete(ast);
|
|
1354
|
+
switch (ast._tag) {
|
|
1355
|
+
case ASTTag.Declaration:
|
|
1356
|
+
return getLiterals(ast.type, isDecoding);
|
|
1357
|
+
case ASTTag.TypeLiteral: {
|
|
1358
|
+
const out: Array<[PropertyKey, Literal]> = [];
|
|
1359
|
+
for (let i = 0; i < ast.propertySignatures.length; i++) {
|
|
1360
|
+
const propertySignature = ast.propertySignatures[i]!;
|
|
1361
|
+
if (propertySignature.type.isLiteral() && !propertySignature.isOptional) {
|
|
1362
|
+
out.push([propertySignature.name, propertySignature.type]);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return out;
|
|
1366
|
+
}
|
|
1367
|
+
case ASTTag.Refinement:
|
|
1368
|
+
return getLiterals(ast.from, isDecoding);
|
|
1369
|
+
case ASTTag.Transform:
|
|
1370
|
+
return getLiterals(isDecoding ? ast.from : ast.to, isDecoding);
|
|
1371
|
+
}
|
|
1372
|
+
return [];
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
export function getSearchTree(
|
|
1376
|
+
members: Vector<AST>,
|
|
1377
|
+
isDecoding: boolean,
|
|
1378
|
+
): {
|
|
1379
|
+
keys: {
|
|
1380
|
+
readonly [key: PropertyKey]: {
|
|
1381
|
+
buckets: { [literal: string]: ReadonlyArray<AST> };
|
|
1382
|
+
ast: AST;
|
|
1383
|
+
};
|
|
1384
|
+
};
|
|
1385
|
+
otherwise: ReadonlyArray<AST>;
|
|
1386
|
+
} {
|
|
1387
|
+
const keys: {
|
|
1388
|
+
[key: PropertyKey]: {
|
|
1389
|
+
buckets: { [literal: string]: Array<AST> };
|
|
1390
|
+
ast: AST;
|
|
1391
|
+
};
|
|
1392
|
+
} = {};
|
|
1393
|
+
const otherwise: Array<AST> = [];
|
|
1394
|
+
for (let i = 0; i < members.length; i++) {
|
|
1395
|
+
const member = members[i]!;
|
|
1396
|
+
const tags = getLiterals(member, isDecoding);
|
|
1397
|
+
if (tags.length > 0) {
|
|
1398
|
+
for (let j = 0; j < tags.length; j++) {
|
|
1399
|
+
const [key, literal] = tags[j]!;
|
|
1400
|
+
const hash = String(literal.literal);
|
|
1401
|
+
keys[key]! ||= { buckets: {}, ast: AST.neverKeyword };
|
|
1402
|
+
const buckets = keys[key]!.buckets;
|
|
1403
|
+
if (Object.prototype.hasOwnProperty.call(buckets, hash)) {
|
|
1404
|
+
if (j < tags.length - 1) {
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
buckets[hash]!.push(member);
|
|
1408
|
+
keys[key]!.ast = AST.createUnion(Vector(keys[key]!.ast, literal));
|
|
1409
|
+
} else {
|
|
1410
|
+
buckets[hash]! = [member];
|
|
1411
|
+
keys[key]!.ast = AST.createUnion(Vector(keys[key]!.ast, literal));
|
|
1412
|
+
break;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
} else {
|
|
1416
|
+
otherwise.push(member);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return { keys, otherwise };
|
|
1420
|
+
}
|
package/_src/Guard.ts
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { globalValue } from "@fncts/base/data/Global";
|
|
2
|
+
import { isRecord } from "@fncts/base/util/predicates";
|
|
3
|
+
import { getKeysForIndexSignature, memoize } from "@fncts/schema/utils";
|
|
4
|
+
|
|
5
|
+
import { ASTTag, getSearchTree } from "./AST.js";
|
|
6
|
+
import { parserFor } from "./Parser.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @tsplus getter fncts.schema.Schema is
|
|
10
|
+
*/
|
|
11
|
+
export function is<A>(schema: Schema<A>) {
|
|
12
|
+
return (input: unknown): input is A => {
|
|
13
|
+
return guardFor(schema).is(input);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function guardFor<A>(schema: Schema<A>): Guard<A> {
|
|
18
|
+
return goMemo(schema.ast);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const guardStrict = (value: unknown) => Guard((inp): inp is any => inp === value);
|
|
22
|
+
|
|
23
|
+
const guardMemoMap = globalValue(Symbol.for("fncts.schema.Guard.guardMemoMap"), () => new WeakMap<AST, Guard<any>>());
|
|
24
|
+
|
|
25
|
+
function goMemo(ast: AST): Guard<any> {
|
|
26
|
+
const memo = guardMemoMap.get(ast);
|
|
27
|
+
if (memo) {
|
|
28
|
+
return memo;
|
|
29
|
+
}
|
|
30
|
+
const guard = go(ast);
|
|
31
|
+
guardMemoMap.set(ast, guard);
|
|
32
|
+
return guard;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function go(ast: AST): Guard<any> {
|
|
36
|
+
AST.concrete(ast);
|
|
37
|
+
switch (ast._tag) {
|
|
38
|
+
case ASTTag.Declaration: {
|
|
39
|
+
const parser = parserFor(ast, true);
|
|
40
|
+
return Guard((inp): inp is any =>
|
|
41
|
+
parser(inp).match(
|
|
42
|
+
() => false,
|
|
43
|
+
() => true,
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
case ASTTag.Literal: {
|
|
48
|
+
return Guard((inp): inp is any => inp === ast.literal);
|
|
49
|
+
}
|
|
50
|
+
case ASTTag.UniqueSymbol: {
|
|
51
|
+
return guardStrict(ast.symbol);
|
|
52
|
+
}
|
|
53
|
+
case ASTTag.VoidKeyword:
|
|
54
|
+
case ASTTag.UndefinedKeyword: {
|
|
55
|
+
return guardStrict(undefined);
|
|
56
|
+
}
|
|
57
|
+
case ASTTag.NeverKeyword: {
|
|
58
|
+
return Guard((inp): inp is never => false);
|
|
59
|
+
}
|
|
60
|
+
case ASTTag.UnknownKeyword:
|
|
61
|
+
case ASTTag.AnyKeyword: {
|
|
62
|
+
return Guard((inp): inp is any => true);
|
|
63
|
+
}
|
|
64
|
+
case ASTTag.NumberKeyword: {
|
|
65
|
+
return Guard.number;
|
|
66
|
+
}
|
|
67
|
+
case ASTTag.BooleanKeyword: {
|
|
68
|
+
return Guard.boolean;
|
|
69
|
+
}
|
|
70
|
+
case ASTTag.StringKeyword: {
|
|
71
|
+
return Guard.string;
|
|
72
|
+
}
|
|
73
|
+
case ASTTag.BigIntKeyword: {
|
|
74
|
+
return Guard.bigint;
|
|
75
|
+
}
|
|
76
|
+
case ASTTag.SymbolKeyword: {
|
|
77
|
+
return Guard((inp): inp is symbol => typeof inp === "symbol");
|
|
78
|
+
}
|
|
79
|
+
case ASTTag.ObjectKeyword: {
|
|
80
|
+
return Guard(isObject);
|
|
81
|
+
}
|
|
82
|
+
case ASTTag.TemplateLiteral: {
|
|
83
|
+
const parser = parserFor(ast, true);
|
|
84
|
+
return Guard((inp): inp is any =>
|
|
85
|
+
parser(inp).match(
|
|
86
|
+
() => false,
|
|
87
|
+
() => true,
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
case ASTTag.Tuple: {
|
|
92
|
+
const elements = ast.elements.map((element) => goMemo(element.type));
|
|
93
|
+
const restElements = ast.rest.match(
|
|
94
|
+
() => Vector.empty<Guard<any>>(),
|
|
95
|
+
(rest) => rest.map(goMemo),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return Guard((input): input is any => {
|
|
99
|
+
if (!Array.isArray(input)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let i = 0;
|
|
104
|
+
for (; i < elements.length; i++) {
|
|
105
|
+
if (input.length < i + 1) {
|
|
106
|
+
if (!ast.elements[i]!.isOptional) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
const guard = elements[i]!;
|
|
111
|
+
if (!guard.is(input[i])) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (restElements.length > 0) {
|
|
118
|
+
const head = restElements.unsafeHead!;
|
|
119
|
+
const tail = restElements.tail;
|
|
120
|
+
for (; i < input.length - tail.length; i++) {
|
|
121
|
+
if (!head.is(input[i])) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (let j = 0; j < tail.length; j++) {
|
|
126
|
+
i += j;
|
|
127
|
+
if (input.length < i + 1) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
const guard = tail[j]!;
|
|
131
|
+
if (!guard.is(input[i])) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return true;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
case ASTTag.TypeLiteral: {
|
|
141
|
+
if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) {
|
|
142
|
+
return Guard((input): input is Exclude<typeof input, null> => input !== null);
|
|
143
|
+
}
|
|
144
|
+
const propertySignatureTypes = ast.propertySignatures.map((ps) => goMemo(ps.type));
|
|
145
|
+
const indexSignatures = ast.indexSignatures.map((is) => [goMemo(is.parameter), goMemo(is.type)] as const);
|
|
146
|
+
return Guard((input): input is any => {
|
|
147
|
+
if (!isRecord(input)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const expectedKeys: any = {};
|
|
152
|
+
|
|
153
|
+
console.log(ast.propertySignatures);
|
|
154
|
+
for (let i = 0; i < propertySignatureTypes.length; i++) {
|
|
155
|
+
const ps = ast.propertySignatures[i]!;
|
|
156
|
+
const guard = propertySignatureTypes[i]!;
|
|
157
|
+
const name = ps.name;
|
|
158
|
+
expectedKeys[name] = null;
|
|
159
|
+
if (!Object.prototype.hasOwnProperty.call(input, name)) {
|
|
160
|
+
if (!ps.isOptional) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
if (!guard(input[name])) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (indexSignatures.length > 0) {
|
|
171
|
+
for (let i = 0; i < indexSignatures.length; i++) {
|
|
172
|
+
const [parameter, type] = indexSignatures[i]!;
|
|
173
|
+
const keys = getKeysForIndexSignature(input, ast.indexSignatures[i]!.parameter);
|
|
174
|
+
for (const key of keys) {
|
|
175
|
+
if (Object.prototype.hasOwnProperty.call(expectedKeys, key)) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!parameter(key)) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!type(input[key])) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return true;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
case ASTTag.Union: {
|
|
194
|
+
const searchTree = getSearchTree(ast.types, true);
|
|
195
|
+
const ownKeys = Reflect.ownKeys(searchTree.keys);
|
|
196
|
+
const len = ownKeys.length;
|
|
197
|
+
const otherwise = searchTree.otherwise;
|
|
198
|
+
const map = new Map<any, Guard<any>>();
|
|
199
|
+
ast.types.forEach((ast) => {
|
|
200
|
+
map.set(ast, goMemo(ast));
|
|
201
|
+
});
|
|
202
|
+
return Guard((input): input is any => {
|
|
203
|
+
if (len > 0) {
|
|
204
|
+
if (isRecord(input)) {
|
|
205
|
+
for (let i = 0; i < len; i++) {
|
|
206
|
+
const name = ownKeys[i]!;
|
|
207
|
+
const buckets = searchTree.keys[name]!.buckets;
|
|
208
|
+
if (Object.prototype.hasOwnProperty.call(input, name)) {
|
|
209
|
+
const literal = String(input[name]);
|
|
210
|
+
if (Object.prototype.hasOwnProperty.call(buckets, literal)) {
|
|
211
|
+
const bucket: ReadonlyArray<AST> = buckets[literal]!;
|
|
212
|
+
for (let i = 0; i < bucket.length; i++) {
|
|
213
|
+
if (map.get(bucket[i])!(input)) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
for (let i = 0; i < otherwise.length; i++) {
|
|
223
|
+
if (map.get(otherwise[i])!(input)) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
case ASTTag.Lazy: {
|
|
231
|
+
const f = () => goMemo(ast.getAST());
|
|
232
|
+
const get = memoize<void, Guard<any>>(f);
|
|
233
|
+
return Guard((input): input is any => get()(input));
|
|
234
|
+
}
|
|
235
|
+
case ASTTag.Enum: {
|
|
236
|
+
return Guard((input): input is any => ast.enums.some(([_, value]) => value === input));
|
|
237
|
+
}
|
|
238
|
+
case ASTTag.Refinement: {
|
|
239
|
+
const from = goMemo(ast.from);
|
|
240
|
+
return Guard((input): input is any => {
|
|
241
|
+
if (!from(input)) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
if (!ast.predicate(input)) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
case ASTTag.Transform: {
|
|
251
|
+
return goMemo(ast.to);
|
|
252
|
+
}
|
|
253
|
+
case ASTTag.Validation: {
|
|
254
|
+
const from = goMemo(ast.from);
|
|
255
|
+
return Guard((input): input is any => {
|
|
256
|
+
if (!from(input)) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
for (const validation of ast.validation) {
|
|
260
|
+
if (!validation.validate(input)) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
package/_src/ParseError.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TemplateLiteral, TemplateLiteralSpan, Validation } from "@fncts/schema/AST";
|
|
1
|
+
import type { Refinement, TemplateLiteral, TemplateLiteralSpan, Transform, Validation } from "@fncts/schema/AST";
|
|
2
2
|
|
|
3
3
|
import { showWithOptions } from "@fncts/base/data/Showable";
|
|
4
4
|
import { concrete } from "@fncts/schema/AST";
|
|
@@ -12,6 +12,8 @@ export const enum ParseErrorTag {
|
|
|
12
12
|
Missing,
|
|
13
13
|
Unexpected,
|
|
14
14
|
UnionMember,
|
|
15
|
+
Refinement,
|
|
16
|
+
Transformation,
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -24,8 +26,9 @@ export type ParseError =
|
|
|
24
26
|
| KeyError
|
|
25
27
|
| MissingError
|
|
26
28
|
| UnexpectedError
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
+
| UnionMemberError
|
|
30
|
+
| RefinementError
|
|
31
|
+
| TransformationError;
|
|
29
32
|
|
|
30
33
|
/**
|
|
31
34
|
* @tsplus companion fncts.schema.ParseError.TypeError
|
|
@@ -78,7 +81,7 @@ export class KeyError {
|
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
/**
|
|
81
|
-
* @tsplus static fncts.schema.ParseError.
|
|
84
|
+
* @tsplus static fncts.schema.ParseError.KeyError __call
|
|
82
85
|
* @tsplus static fncts.schema.ParseErrorOps KeyError
|
|
83
86
|
*/
|
|
84
87
|
export function keyError(keyAST: AST, key: any, errors: Vector<ParseError>): ParseError {
|
|
@@ -129,6 +132,58 @@ export function unionMemberError(errors: Vector<ParseError>): ParseError {
|
|
|
129
132
|
return new UnionMemberError(errors);
|
|
130
133
|
}
|
|
131
134
|
|
|
135
|
+
/**
|
|
136
|
+
* @tsplus companion fncts.schema.ParseError.RefinementError
|
|
137
|
+
*/
|
|
138
|
+
export class RefinementError {
|
|
139
|
+
readonly _tag = ParseErrorTag.Refinement;
|
|
140
|
+
constructor(
|
|
141
|
+
readonly ast: Refinement,
|
|
142
|
+
readonly actual: unknown,
|
|
143
|
+
readonly kind: "From" | "Predicate",
|
|
144
|
+
readonly errors: Vector<ParseError>,
|
|
145
|
+
) {}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @tsplus static fncts.schema.ParseError.RefinementError __call
|
|
150
|
+
* @tsplus static fncts.schema.ParseErrorOps RefinementError
|
|
151
|
+
*/
|
|
152
|
+
export function refinementError(
|
|
153
|
+
ast: Refinement,
|
|
154
|
+
actual: unknown,
|
|
155
|
+
kind: "From" | "Predicate",
|
|
156
|
+
errors: Vector<ParseError>,
|
|
157
|
+
): ParseError {
|
|
158
|
+
return new RefinementError(ast, actual, kind, errors);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @tsplus companion fncts.schema.ParseError.TransformationError
|
|
163
|
+
*/
|
|
164
|
+
export class TransformationError {
|
|
165
|
+
readonly _tag = ParseErrorTag.Transformation;
|
|
166
|
+
constructor(
|
|
167
|
+
readonly ast: Transform,
|
|
168
|
+
readonly actual: unknown,
|
|
169
|
+
readonly kind: "Encoded" | "Transformation" | "Type",
|
|
170
|
+
readonly errors: Vector<ParseError>,
|
|
171
|
+
) {}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @tsplus static fncts.schema.ParseError.TransformationError __call
|
|
176
|
+
* @tsplus static fncts.schema.ParseErrorOps TransformationError
|
|
177
|
+
*/
|
|
178
|
+
export function transformationError(
|
|
179
|
+
ast: Transform,
|
|
180
|
+
actual: unknown,
|
|
181
|
+
kind: "Encoded" | "Transformation" | "Type",
|
|
182
|
+
errors: Vector<ParseError>,
|
|
183
|
+
): ParseError {
|
|
184
|
+
return new TransformationError(ast, actual, kind, errors);
|
|
185
|
+
}
|
|
186
|
+
|
|
132
187
|
/**
|
|
133
188
|
* @tsplus static fncts.schema.ParseErrorOps format
|
|
134
189
|
*/
|
|
@@ -153,6 +208,31 @@ function formatTemplateLiteral(ast: TemplateLiteral): string {
|
|
|
153
208
|
return ast.head + ast.spans.map((span) => formatTemplateLiteralSpan(span) + span.literal).join("");
|
|
154
209
|
}
|
|
155
210
|
|
|
211
|
+
function formatRefinementKind(error: RefinementError): string {
|
|
212
|
+
switch (error.kind) {
|
|
213
|
+
case "From": {
|
|
214
|
+
return "From side refinement failure";
|
|
215
|
+
}
|
|
216
|
+
case "Predicate": {
|
|
217
|
+
return "Predicate refinement failure";
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function formatTransformationKind(error: TransformationError): string {
|
|
223
|
+
switch (error.kind) {
|
|
224
|
+
case "Encoded": {
|
|
225
|
+
return "Encoded side transformation failure";
|
|
226
|
+
}
|
|
227
|
+
case "Transformation": {
|
|
228
|
+
return "Transformation process failure";
|
|
229
|
+
}
|
|
230
|
+
case "Type": {
|
|
231
|
+
return "Type side transformation failure";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
156
236
|
function getExpected(ast: AST): Maybe<string> {
|
|
157
237
|
return ast.annotations
|
|
158
238
|
.get(ASTAnnotation.Identifier)
|
|
@@ -243,5 +323,9 @@ function go(error: ParseError): RoseTree<string> {
|
|
|
243
323
|
return RoseTree("is missing");
|
|
244
324
|
case ParseErrorTag.UnionMember:
|
|
245
325
|
return RoseTree("union member", error.errors.map(go));
|
|
326
|
+
case ParseErrorTag.Refinement:
|
|
327
|
+
return RoseTree(formatRefinementKind(error), error.errors.map(go));
|
|
328
|
+
case ParseErrorTag.Transformation:
|
|
329
|
+
return RoseTree(formatTransformationKind(error), error.errors.map(go));
|
|
246
330
|
}
|
|
247
331
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const ParseFailureTypeId = Symbol.for("fncts.schema.ParseFailure");
|
|
2
|
+
export type ParseFailureTypeId = typeof ParseFailureTypeId;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @tsplus type fncts.schema.ParseFailure
|
|
6
|
+
* @tsplus companion fncts.schema.ParseFailureOps
|
|
7
|
+
*/
|
|
8
|
+
export class ParseFailure {
|
|
9
|
+
readonly [ParseFailureTypeId]: ParseFailureTypeId = ParseFailureTypeId;
|
|
10
|
+
constructor(readonly errors: Vector<ParseError>) {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @tsplus static fncts.schema.ParseFailureOps __call
|
|
15
|
+
*/
|
|
16
|
+
export function make(errors: Vector<ParseError>): ParseFailure {
|
|
17
|
+
return new ParseFailure(errors);
|
|
18
|
+
}
|
package/_src/ParseResult.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @tsplus type fncts.schema.ParseResult
|
|
3
3
|
* @tsplus companion fncts.schema.ParseResultOps
|
|
4
4
|
*/
|
|
5
|
-
export interface ParseResult<A> extends Either<
|
|
5
|
+
export interface ParseResult<A> extends Either<ParseFailure, A> {}
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @tsplus static fncts.schema.ParseResultOps succeed
|
|
@@ -15,12 +15,12 @@ export function succeed<A>(value: A): ParseResult<A> {
|
|
|
15
15
|
* @tsplus static fncts.schema.ParseResultOps failures
|
|
16
16
|
*/
|
|
17
17
|
export function failures<A = never>(value: Vector<ParseError>): ParseResult<A> {
|
|
18
|
-
return Either.left(value);
|
|
18
|
+
return Either.left(ParseFailure(value));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* @tsplus static fncts.schema.ParseResultOps fail
|
|
23
23
|
*/
|
|
24
24
|
export function fail<A = never>(value: ParseError): ParseResult<A> {
|
|
25
|
-
return Either.left(Vector(value));
|
|
25
|
+
return Either.left(ParseFailure(Vector(value)));
|
|
26
26
|
}
|