@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.
Files changed (76) hide show
  1. package/AST.d.ts +29 -12
  2. package/Guard.d.ts +9 -0
  3. package/ParseError.d.ts +40 -3
  4. package/ParseFailure.d.ts +18 -0
  5. package/ParseResult.d.ts +2 -1
  6. package/Parser/api.d.ts +0 -6
  7. package/Parser/interpreter.d.ts +1 -2
  8. package/Show.d.ts +1 -1
  9. package/_cjs/AST.cjs +115 -65
  10. package/_cjs/AST.cjs.map +1 -1
  11. package/_cjs/Guard.cjs +278 -0
  12. package/_cjs/Guard.cjs.map +1 -0
  13. package/_cjs/ParseError.cjs +77 -3
  14. package/_cjs/ParseError.cjs.map +1 -1
  15. package/_cjs/ParseFailure.cjs +28 -0
  16. package/_cjs/ParseFailure.cjs.map +1 -0
  17. package/_cjs/ParseResult.cjs +4 -3
  18. package/_cjs/ParseResult.cjs.map +1 -1
  19. package/_cjs/Parser/api.cjs +11 -23
  20. package/_cjs/Parser/api.cjs.map +1 -1
  21. package/_cjs/Parser/interpreter.cjs +93 -141
  22. package/_cjs/Parser/interpreter.cjs.map +1 -1
  23. package/_cjs/Schema/api/conc.cjs +1 -1
  24. package/_cjs/Schema/api/conc.cjs.map +1 -1
  25. package/_cjs/Schema/api/hashMap.cjs +1 -1
  26. package/_cjs/Schema/api/hashMap.cjs.map +1 -1
  27. package/_cjs/Schema/api/immutableArray.cjs +1 -1
  28. package/_cjs/Schema/api/immutableArray.cjs.map +1 -1
  29. package/_cjs/Schema/api/list.cjs +1 -1
  30. package/_cjs/Schema/api/list.cjs.map +1 -1
  31. package/_cjs/Schema/api.cjs +27 -28
  32. package/_cjs/Schema/api.cjs.map +1 -1
  33. package/_cjs/Show.cjs +23 -12
  34. package/_cjs/Show.cjs.map +1 -1
  35. package/_mjs/AST.mjs +107 -61
  36. package/_mjs/AST.mjs.map +1 -1
  37. package/_mjs/Guard.mjs +269 -0
  38. package/_mjs/Guard.mjs.map +1 -0
  39. package/_mjs/ParseError.mjs +72 -2
  40. package/_mjs/ParseError.mjs.map +1 -1
  41. package/_mjs/ParseFailure.mjs +20 -0
  42. package/_mjs/ParseFailure.mjs.map +1 -0
  43. package/_mjs/ParseResult.mjs +4 -3
  44. package/_mjs/ParseResult.mjs.map +1 -1
  45. package/_mjs/Parser/api.mjs +10 -21
  46. package/_mjs/Parser/api.mjs.map +1 -1
  47. package/_mjs/Parser/interpreter.mjs +94 -142
  48. package/_mjs/Parser/interpreter.mjs.map +1 -1
  49. package/_mjs/Schema/api/conc.mjs +1 -1
  50. package/_mjs/Schema/api/conc.mjs.map +1 -1
  51. package/_mjs/Schema/api/hashMap.mjs +1 -1
  52. package/_mjs/Schema/api/hashMap.mjs.map +1 -1
  53. package/_mjs/Schema/api/immutableArray.mjs +1 -1
  54. package/_mjs/Schema/api/immutableArray.mjs.map +1 -1
  55. package/_mjs/Schema/api/list.mjs +1 -1
  56. package/_mjs/Schema/api/list.mjs.map +1 -1
  57. package/_mjs/Schema/api.mjs +27 -28
  58. package/_mjs/Schema/api.mjs.map +1 -1
  59. package/_mjs/Show.mjs +23 -12
  60. package/_mjs/Show.mjs.map +1 -1
  61. package/_src/AST.ts +96 -47
  62. package/_src/Guard.ts +268 -0
  63. package/_src/ParseError.ts +88 -4
  64. package/_src/ParseFailure.ts +18 -0
  65. package/_src/ParseResult.ts +3 -3
  66. package/_src/Parser/api.ts +8 -21
  67. package/_src/Parser/interpreter.ts +94 -128
  68. package/_src/Schema/api/conc.ts +1 -1
  69. package/_src/Schema/api/hashMap.ts +1 -1
  70. package/_src/Schema/api/immutableArray.ts +1 -1
  71. package/_src/Schema/api/list.ts +1 -1
  72. package/_src/Schema/api.ts +3 -10
  73. package/_src/Show.ts +28 -15
  74. package/_src/global.ts +4 -0
  75. package/global.d.ts +4 -0
  76. 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 decode: (input: any, options?: ParseOptions) => ParseResult<any>,
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.decode ?? this.decode,
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
- decode: (input: any, options?: ParseOptions) => ParseResult<any>,
840
- isReversed: boolean,
857
+ predicate: (input: any) => boolean,
841
858
  annotations?: ASTAnnotationMap,
842
859
  ): Refinement {
843
- return new Refinement(from, decode, isReversed, annotations);
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, isReversed, annotations);
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.decode, false, ast.annotations);
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
+ }
@@ -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
- | UnexpectedError
28
- | UnionMemberError;
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.IndexError __call
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
+ }
@@ -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<Vector<ParseError>, A> {}
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
  }