@fncts/schema 0.0.5 → 0.0.6

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 (151) hide show
  1. package/AST.d.ts +528 -0
  2. package/ASTAnnotation.d.ts +82 -0
  3. package/ASTAnnotationMap.d.ts +14 -0
  4. package/Gen.d.ts +15 -0
  5. package/InvalidInterpretationError.d.ts +5 -0
  6. package/ParseError.d.ts +107 -0
  7. package/ParseResult.d.ts +24 -0
  8. package/Parser/api.d.ts +42 -0
  9. package/Parser/definition.d.ts +22 -0
  10. package/Parser/interpreter.d.ts +6 -0
  11. package/Parser.d.ts +2 -0
  12. package/Schema/api/conc.d.ts +18 -0
  13. package/Schema/api/hashMap.d.ts +21 -0
  14. package/Schema/api/immutableArray.d.ts +23 -0
  15. package/Schema/api/list.d.ts +23 -0
  16. package/Schema/api/maybe.d.ts +21 -0
  17. package/Schema/api.d.ts +243 -0
  18. package/Schema/definition.d.ts +29 -0
  19. package/Schema/derivations.d.ts +70 -0
  20. package/Schema.d.ts +2 -144
  21. package/_cjs/AST.cjs +1171 -0
  22. package/_cjs/AST.cjs.map +1 -0
  23. package/_cjs/ASTAnnotation.cjs +111 -0
  24. package/_cjs/ASTAnnotation.cjs.map +1 -0
  25. package/_cjs/ASTAnnotationMap.cjs +35 -0
  26. package/_cjs/ASTAnnotationMap.cjs.map +1 -0
  27. package/_cjs/Gen.cjs +185 -0
  28. package/_cjs/Gen.cjs.map +1 -0
  29. package/_cjs/InvalidInterpretationError.cjs +18 -0
  30. package/_cjs/InvalidInterpretationError.cjs.map +1 -0
  31. package/_cjs/ParseError.cjs +222 -0
  32. package/_cjs/ParseError.cjs.map +1 -0
  33. package/_cjs/{Decoder.cjs → ParseResult.cjs} +24 -22
  34. package/_cjs/ParseResult.cjs.map +1 -0
  35. package/_cjs/Parser/api.cjs +80 -0
  36. package/_cjs/Parser/api.cjs.map +1 -0
  37. package/_cjs/{Guard.cjs → Parser/definition.cjs} +17 -22
  38. package/_cjs/Parser/definition.cjs.map +1 -0
  39. package/_cjs/Parser/interpreter.cjs +409 -0
  40. package/_cjs/Parser/interpreter.cjs.map +1 -0
  41. package/_cjs/Parser.cjs +28 -0
  42. package/_cjs/Parser.cjs.map +1 -0
  43. package/_cjs/Schema/api/conc.cjs +84 -0
  44. package/_cjs/Schema/api/conc.cjs.map +1 -0
  45. package/_cjs/Schema/api/hashMap.cjs +161 -0
  46. package/_cjs/Schema/api/hashMap.cjs.map +1 -0
  47. package/_cjs/Schema/api/immutableArray.cjs +90 -0
  48. package/_cjs/Schema/api/immutableArray.cjs.map +1 -0
  49. package/_cjs/Schema/api/list.cjs +98 -0
  50. package/_cjs/Schema/api/list.cjs.map +1 -0
  51. package/_cjs/Schema/api/maybe.cjs +75 -0
  52. package/_cjs/Schema/api/maybe.cjs.map +1 -0
  53. package/_cjs/Schema/api.cjs +424 -0
  54. package/_cjs/Schema/api.cjs.map +1 -0
  55. package/_cjs/Schema/definition.cjs +26 -0
  56. package/_cjs/Schema/definition.cjs.map +1 -0
  57. package/_cjs/Schema/derivations.cjs +108 -0
  58. package/_cjs/Schema/derivations.cjs.map +1 -0
  59. package/_cjs/Schema.cjs +20 -237
  60. package/_cjs/Schema.cjs.map +1 -1
  61. package/_cjs/utils.cjs +52 -0
  62. package/_cjs/utils.cjs.map +1 -0
  63. package/_mjs/AST.mjs +1060 -0
  64. package/_mjs/AST.mjs.map +1 -0
  65. package/_mjs/ASTAnnotation.mjs +80 -0
  66. package/_mjs/ASTAnnotation.mjs.map +1 -0
  67. package/_mjs/ASTAnnotationMap.mjs +27 -0
  68. package/_mjs/ASTAnnotationMap.mjs.map +1 -0
  69. package/_mjs/Gen.mjs +176 -0
  70. package/_mjs/Gen.mjs.map +1 -0
  71. package/_mjs/InvalidInterpretationError.mjs +10 -0
  72. package/_mjs/InvalidInterpretationError.mjs.map +1 -0
  73. package/_mjs/ParseError.mjs +200 -0
  74. package/_mjs/ParseError.mjs.map +1 -0
  75. package/_mjs/ParseResult.mjs +21 -0
  76. package/_mjs/ParseResult.mjs.map +1 -0
  77. package/_mjs/Parser/api.mjs +67 -0
  78. package/_mjs/Parser/api.mjs.map +1 -0
  79. package/_mjs/Parser/definition.mjs +15 -0
  80. package/_mjs/Parser/definition.mjs.map +1 -0
  81. package/_mjs/Parser/interpreter.mjs +401 -0
  82. package/_mjs/Parser/interpreter.mjs.map +1 -0
  83. package/_mjs/Parser.mjs +4 -0
  84. package/_mjs/Parser.mjs.map +1 -0
  85. package/_mjs/Schema/api/conc.mjs +72 -0
  86. package/_mjs/Schema/api/conc.mjs.map +1 -0
  87. package/_mjs/Schema/api/hashMap.mjs +150 -0
  88. package/_mjs/Schema/api/hashMap.mjs.map +1 -0
  89. package/_mjs/Schema/api/immutableArray.mjs +79 -0
  90. package/_mjs/Schema/api/immutableArray.mjs.map +1 -0
  91. package/_mjs/Schema/api/list.mjs +87 -0
  92. package/_mjs/Schema/api/list.mjs.map +1 -0
  93. package/_mjs/Schema/api/maybe.mjs +64 -0
  94. package/_mjs/Schema/api/maybe.mjs.map +1 -0
  95. package/_mjs/Schema/api.mjs +367 -0
  96. package/_mjs/Schema/api.mjs.map +1 -0
  97. package/_mjs/Schema/definition.mjs +16 -0
  98. package/_mjs/Schema/definition.mjs.map +1 -0
  99. package/_mjs/Schema/derivations.mjs +94 -0
  100. package/_mjs/Schema/derivations.mjs.map +1 -0
  101. package/_mjs/Schema.mjs +3 -212
  102. package/_mjs/Schema.mjs.map +1 -1
  103. package/_mjs/utils.mjs +41 -0
  104. package/_mjs/utils.mjs.map +1 -0
  105. package/_src/AST.ts +1353 -0
  106. package/_src/ASTAnnotation.ts +98 -0
  107. package/_src/ASTAnnotationMap.ts +38 -0
  108. package/_src/Gen.ts +171 -0
  109. package/_src/InvalidInterpretationError.ts +6 -0
  110. package/_src/ParseError.ts +237 -0
  111. package/_src/ParseResult.ts +26 -0
  112. package/_src/Parser/api.ts +71 -0
  113. package/_src/Parser/definition.ts +24 -0
  114. package/_src/Parser/interpreter.ts +442 -0
  115. package/_src/Parser.ts +4 -0
  116. package/_src/Schema/api/conc.ts +78 -0
  117. package/_src/Schema/api/hashMap.ts +184 -0
  118. package/_src/Schema/api/immutableArray.ts +88 -0
  119. package/_src/Schema/api/list.ts +96 -0
  120. package/_src/Schema/api/maybe.ts +68 -0
  121. package/_src/Schema/api.ts +530 -0
  122. package/_src/Schema/definition.ts +32 -0
  123. package/_src/Schema/derivations.ts +185 -0
  124. package/_src/Schema.ts +4 -254
  125. package/_src/global.ts +53 -0
  126. package/_src/utils.ts +48 -0
  127. package/global.d.ts +52 -0
  128. package/package.json +2 -2
  129. package/utils.d.ts +8 -0
  130. package/Decoder.d.ts +0 -3
  131. package/Encoder.d.ts +0 -4
  132. package/Guard.d.ts +0 -3
  133. package/Schemable.d.ts +0 -39
  134. package/_cjs/Decoder.cjs.map +0 -1
  135. package/_cjs/Encoder.cjs +0 -45
  136. package/_cjs/Encoder.cjs.map +0 -1
  137. package/_cjs/Guard.cjs.map +0 -1
  138. package/_cjs/Schemable.cjs +0 -6
  139. package/_cjs/Schemable.cjs.map +0 -1
  140. package/_mjs/Decoder.mjs +0 -20
  141. package/_mjs/Decoder.mjs.map +0 -1
  142. package/_mjs/Encoder.mjs +0 -36
  143. package/_mjs/Encoder.mjs.map +0 -1
  144. package/_mjs/Guard.mjs +0 -20
  145. package/_mjs/Guard.mjs.map +0 -1
  146. package/_mjs/Schemable.mjs +0 -2
  147. package/_mjs/Schemable.mjs.map +0 -1
  148. package/_src/Decoder.ts +0 -20
  149. package/_src/Encoder.ts +0 -38
  150. package/_src/Guard.ts +0 -20
  151. package/_src/Schemable.ts +0 -46
@@ -0,0 +1,98 @@
1
+ import type { Validation } from "@fncts/base/data/Branded";
2
+
3
+ export const ASTAnnotationVariance = Symbol.for("fncts.schema.ASTAnnotation.Variance");
4
+ export type ASTAnnotationVariance = typeof ASTAnnotationVariance;
5
+
6
+ export const ASTAnnotationTypeId = Symbol.for("fncts.schema.ASTAnnotation");
7
+ export type ASTAnnotationTypeId = typeof ASTAnnotationTypeId;
8
+
9
+ /**
10
+ * @tsplus type fncts.schema.ASTAnnotation
11
+ * @tsplus companion fncts.schema.ASTAnnotationOps
12
+ */
13
+ export class ASTAnnotation<V> implements Hashable, Equatable {
14
+ readonly [ASTAnnotationTypeId]: ASTAnnotationTypeId = ASTAnnotationTypeId;
15
+ declare [ASTAnnotationVariance]: {
16
+ readonly _V: (_: never) => V;
17
+ };
18
+ constructor(readonly tag: Tag<V>, readonly identifier: string, readonly combine: (v1: V, v2: V) => V) {}
19
+ get [Symbol.hash]() {
20
+ return Hashable.combine(Hashable.string(this.identifier), Hashable.unknown(this.tag));
21
+ }
22
+ [Symbol.equals](that: unknown) {
23
+ return isASTAnnotation(that) && Equatable.strictEquals(this.tag, that.tag) && this.identifier === that.identifier;
24
+ }
25
+ }
26
+
27
+ export function isASTAnnotation(u: unknown): u is ASTAnnotation<unknown> {
28
+ return isObject(u) && ASTAnnotationTypeId in u;
29
+ }
30
+
31
+ export const TitleTag = Tag<string>();
32
+
33
+ /**
34
+ * @tsplus static fncts.schema.ASTAnnotationOps Title
35
+ */
36
+ export const Title = new ASTAnnotation(TitleTag, "Title", (_, a) => a);
37
+
38
+ export const IdentifierTag = Tag<string>();
39
+
40
+ /**
41
+ * @tsplus static fncts.schema.ASTAnnotationOps Identifier
42
+ */
43
+ export const Identifier = new ASTAnnotation(IdentifierTag, "Identifier", (_, a) => a);
44
+
45
+ export const DescriptionTag = Tag<string>();
46
+
47
+ /**
48
+ * @tsplus static fncts.schema.ASTAnnotationOps Description
49
+ */
50
+ export const Description = new ASTAnnotation(DescriptionTag, "Description", (_, a) => a);
51
+
52
+ export const MessageTag = Tag<(_: unknown) => string>();
53
+
54
+ /**
55
+ * @tsplus static fncts.schema.ASTAnnotationOps Message
56
+ */
57
+ export const Message = new ASTAnnotation(MessageTag, "Message", (_, a) => a);
58
+
59
+ export const BrandTag = Tag<Vector<Validation<any, any>>>();
60
+
61
+ /**
62
+ * @tsplus static fncts.schema.ASTAnnotationOps Brand
63
+ */
64
+ export const Brand = new ASTAnnotation(BrandTag, "Brand", (a, b) => a.concat(b));
65
+
66
+ export const OptionalTag = Tag<boolean>();
67
+
68
+ /**
69
+ * @tsplus static fncts.schema.ASTAnnotationOps Optional
70
+ */
71
+ export const Optional = new ASTAnnotation(OptionalTag, "Optional", (_, b) => b);
72
+
73
+ export const ParseOptionalTag = Tag<boolean>();
74
+
75
+ /**
76
+ * @tsplus static fncts.schema.ASTAnnotationOps ParseOptional
77
+ */
78
+ export const ParseOptional = new ASTAnnotation(ParseOptionalTag, "ParseOptional", (_, b) => b);
79
+
80
+ export type Hook<A> = (...typeParameters: ReadonlyArray<A>) => A;
81
+
82
+ export function hook(handler: (...typeParameters: ReadonlyArray<any>) => any): Hook<any> {
83
+ return handler;
84
+ }
85
+
86
+ export const ParserHookTag = Tag<Hook<any>>();
87
+
88
+ /**
89
+ * @tsplus static fncts.schema.ASTAnnotationOps ParserHook
90
+ */
91
+ export const ParserHook = new ASTAnnotation(ParserHookTag, "ParserHook", (_, b) => b);
92
+
93
+ export const GenHookTag = Tag<Hook<any>>();
94
+
95
+ /**
96
+ * @tsplus static fncts.schema.ASTAnnotationOps GenHook
97
+ */
98
+ export const GenHook = new ASTAnnotation(GenHookTag, "GenHook", (_, b) => b);
@@ -0,0 +1,38 @@
1
+ import type { ASTAnnotation } from "./ASTAnnotation.js";
2
+
3
+ export class ASTAnnotationMap {
4
+ constructor(readonly map: HashMap<ASTAnnotation<any>, any>) {}
5
+ combine(that: ASTAnnotationMap): ASTAnnotationMap {
6
+ return new ASTAnnotationMap(
7
+ Conc.from(this.map)
8
+ .concat(Conc.from(that.map))
9
+ .foldLeft(HashMap.empty(), (acc, [k, v]) =>
10
+ acc.set(
11
+ k,
12
+ acc.get(k).match(
13
+ () => v,
14
+ (_) => k.combine(_, v),
15
+ ),
16
+ ),
17
+ ),
18
+ );
19
+ }
20
+ get<V>(key: ASTAnnotation<V>): Maybe<V> {
21
+ return this.map.get(key);
22
+ }
23
+ private overwrite<V>(key: ASTAnnotation<V>, value: V): ASTAnnotationMap {
24
+ return new ASTAnnotationMap(this.map.set(key, value));
25
+ }
26
+ private update<V>(key: ASTAnnotation<V>, f: (v: Maybe<V>) => V): ASTAnnotationMap {
27
+ return this.overwrite(key, f(this.get(key)));
28
+ }
29
+ annotate<V>(key: ASTAnnotation<V>, value: V): ASTAnnotationMap {
30
+ return this.update(key, (v) =>
31
+ v.match(
32
+ () => value,
33
+ (v) => key.combine(v, value),
34
+ ),
35
+ );
36
+ }
37
+ static empty: ASTAnnotationMap = new ASTAnnotationMap(HashMap.empty());
38
+ }
package/_src/Gen.ts ADDED
@@ -0,0 +1,171 @@
1
+ import type { Hook } from "@fncts/schema/ASTAnnotation";
2
+ import type { Sized } from "@fncts/test/control/Sized";
3
+
4
+ import { ASTTag } from "@fncts/schema/AST";
5
+ import { ASTAnnotation } from "@fncts/schema/ASTAnnotation";
6
+ import { InvalidInterpretationError } from "@fncts/schema/InvalidInterpretationError";
7
+ import { memoize } from "@fncts/schema/utils";
8
+
9
+ /**
10
+ * @tsplus getter fncts.schema.Schema genFrom
11
+ */
12
+ export function genFrom<A>(self: Schema<A>): Gen<Sized, unknown> {
13
+ return go(self.ast.getFrom);
14
+ }
15
+
16
+ /**
17
+ * @tsplus getter fncts.schema.Schema genTo
18
+ */
19
+ export function genTo<A>(self: Schema<A>): Gen<Sized, A> {
20
+ return go(self.ast);
21
+ }
22
+
23
+ function getHook(ast: AST): Maybe<Hook<Gen<Sized, any>>> {
24
+ return ast.annotations.get(ASTAnnotation.GenHook);
25
+ }
26
+
27
+ function record<K extends PropertyKey, V>(
28
+ key: Gen<Sized, K>,
29
+ value: Gen<Sized, V>,
30
+ ): Gen<Sized, { readonly [k in K]: V }> {
31
+ return Gen.tuple(key, value)
32
+ .arrayWith({ maxLength: 10 })
33
+ .map((tuples) => {
34
+ const out: { [k in K]: V } = {} as any;
35
+ for (const [k, v] of tuples) {
36
+ out[k] = v;
37
+ }
38
+ return out;
39
+ });
40
+ }
41
+
42
+ const go = memoize(function go(ast: AST): Gen<Sized, any> {
43
+ AST.concrete(ast);
44
+ switch (ast._tag) {
45
+ case ASTTag.Declaration:
46
+ return getHook(ast).match(
47
+ () => go(ast.type),
48
+ (hook) => hook(...ast.typeParameters.map(go)),
49
+ );
50
+ case ASTTag.Literal:
51
+ return Gen.constant(ast.literal);
52
+ case ASTTag.UniqueSymbol:
53
+ return Gen.constant(ast.symbol);
54
+ case ASTTag.UndefinedKeyword:
55
+ return Gen.constant(undefined);
56
+ case ASTTag.VoidKeyword:
57
+ return Gen.constant(undefined);
58
+ case ASTTag.NeverKeyword:
59
+ return Gen.fromIO(IO.haltNow(new InvalidInterpretationError("cannot build a Gen for `never`")));
60
+ case ASTTag.UnknownKeyword:
61
+ // TODO: Gen.anything
62
+ return Gen.constant("unknown");
63
+ case ASTTag.AnyKeyword:
64
+ // TODO: Gen.anything
65
+ return Gen.constant("any");
66
+ case ASTTag.StringKeyword:
67
+ return Gen.fullUnicodeString();
68
+ case ASTTag.NumberKeyword:
69
+ return Gen.float;
70
+ case ASTTag.BooleanKeyword:
71
+ return Gen.boolean;
72
+ case ASTTag.BigIntKeyword:
73
+ return Gen.bigInt;
74
+ case ASTTag.SymbolKeyword:
75
+ return Gen.fullUnicodeString().map((s) => Symbol.for(s));
76
+ case ASTTag.ObjectKeyword:
77
+ // TODO: Gen.anything
78
+ return Gen.constant({});
79
+ case ASTTag.TemplateLiteral: {
80
+ const components: Array<Gen<Sized, string>> = [Gen.constant(ast.head)];
81
+ for (const span of ast.spans) {
82
+ components.push(Gen.fullUnicodeString({ maxLength: 5 }));
83
+ components.push(Gen.constant(span.literal));
84
+ }
85
+ return Gen.tuple(...components).map((spans) => spans.join(""));
86
+ }
87
+ case ASTTag.Tuple: {
88
+ const elements = ast.elements.map((e) => go(e.type));
89
+ const rest = ast.rest.map((restElement) => restElement.map(go));
90
+ let output = Gen.tuple(...elements);
91
+ if (elements.length > 0 && rest.isNothing()) {
92
+ const firstOptionalIndex = ast.elements.findIndex((e) => e.isOptional);
93
+ if (firstOptionalIndex !== -1) {
94
+ output = output.flatMap((as) =>
95
+ Gen.intWith({ min: firstOptionalIndex, max: elements.length - 1 }).map((i) => as.slice(0, i)),
96
+ );
97
+ }
98
+ }
99
+ if (rest.isJust()) {
100
+ const head = rest.value.unsafeHead!;
101
+ const tail = rest.value.tail;
102
+ output = output.flatMap((as) => head.arrayWith({ maxLength: 5 }).map((rest) => [...as, ...rest]));
103
+ for (let j = 0; j < tail.length; j++) {
104
+ output = output.flatMap((as) => tail[j]!.map((a) => [...as, a]));
105
+ }
106
+ }
107
+ return output as Gen<Sized, any>;
108
+ }
109
+ case ASTTag.TypeLiteral: {
110
+ const propertySignatureTypes = ast.propertySignatures.map((ps) => go(ps.type));
111
+ const indexSignatures = ast.indexSignatures.map((is) => [go(is.parameter), go(is.type)] as const);
112
+ const gens: any = {};
113
+ const requiredGens: Record<PropertyKey, Gen<any, any>> = {};
114
+ const optionalGens: Record<PropertyKey, Gen<any, any>> = {};
115
+ for (let i = 0; i < propertySignatureTypes.length; i++) {
116
+ const ps = ast.propertySignatures[i]!;
117
+ const name = ps.name;
118
+ if (!ps.isOptional) {
119
+ requiredGens[name] = propertySignatureTypes[i]!;
120
+ } else {
121
+ optionalGens[name] = propertySignatureTypes[i]!;
122
+ }
123
+ }
124
+ let output = Gen.struct(requiredGens).zipWith(Gen.partial(optionalGens), (a, b) => ({ ...a, ...b }));
125
+ for (let i = 0; i < indexSignatures.length; i++) {
126
+ const parameter = indexSignatures[i]![0]!;
127
+ const type = indexSignatures[i]![1]!;
128
+ output = output.flatMap((o) => {
129
+ return record(parameter, type).map((d) => ({ ...d, ...o }));
130
+ });
131
+ }
132
+ return output;
133
+ }
134
+ case ASTTag.Union: {
135
+ const types = ast.types.map(go);
136
+ return Gen.oneOf(...types) as Gen<Sized, any>;
137
+ }
138
+ case ASTTag.Lazy: {
139
+ return getHook(ast).match(
140
+ () => {
141
+ const f = () => go(ast.getAST());
142
+ const get = memoize<typeof f, Gen<any, any>>(f);
143
+ return Gen.defer(() => get(f));
144
+ },
145
+ (handler) => handler(),
146
+ );
147
+ }
148
+ case ASTTag.Enum: {
149
+ if (ast.enums.length === 0) {
150
+ return Gen.fromIO(IO.haltNow(new InvalidInterpretationError("cannot build a Gen for an empty enum")));
151
+ }
152
+ return Gen.oneOf(...ast.enums.map(([_, value]) => Gen.constant(value))) as Gen<Sized, any>;
153
+ }
154
+ case ASTTag.Refinement: {
155
+ const from = go(ast.from);
156
+ return getHook(ast).match(
157
+ () => from.filter((a) => ast.decode(a).isRight()),
158
+ (handler) => handler(from),
159
+ );
160
+ }
161
+ case ASTTag.Transform:
162
+ return go(ast.to);
163
+ case ASTTag.Validation: {
164
+ const from = go(ast.from);
165
+ return getHook(ast).match(
166
+ () => from.filter((a) => ast.validation.every((v) => v.validate(a))),
167
+ (handler) => handler(from),
168
+ );
169
+ }
170
+ }
171
+ });
@@ -0,0 +1,6 @@
1
+ export const InvalidInterpretationErrorTypeId = Symbol.for("fncts.schema.InvalidInterpretationError");
2
+ export type InvalidInterpretationErrorTypeId = typeof InvalidInterpretationErrorTypeId;
3
+
4
+ export class InvalidInterpretationError extends Error {
5
+ readonly [InvalidInterpretationErrorTypeId]: InvalidInterpretationErrorTypeId = InvalidInterpretationErrorTypeId;
6
+ }
@@ -0,0 +1,237 @@
1
+ import type { TemplateLiteral, TemplateLiteralSpan, Validation } from "@fncts/schema/AST";
2
+
3
+ import { showWithOptions } from "@fncts/base/data/Showable";
4
+ import { concrete } from "@fncts/schema/AST";
5
+ import { ASTTag } from "@fncts/schema/AST";
6
+ import { ASTAnnotation } from "@fncts/schema/ASTAnnotation";
7
+
8
+ export const enum ParseErrorTag {
9
+ Type,
10
+ Index,
11
+ Key,
12
+ Missing,
13
+ Unexpected,
14
+ UnionMember,
15
+ }
16
+
17
+ /**
18
+ * @tsplus type fncts.schema.ParseError
19
+ * @tsplus companion fncts.schema.ParseErrorOps
20
+ */
21
+ export type ParseError =
22
+ | TypeError
23
+ | IndexError
24
+ | KeyError
25
+ | MissingError
26
+ | UnexpectedError
27
+ | UnexpectedError
28
+ | UnionMemberError;
29
+
30
+ /**
31
+ * @tsplus companion fncts.schema.ParseError.TypeError
32
+ */
33
+ export class TypeError {
34
+ readonly _tag = ParseErrorTag.Type;
35
+ constructor(readonly expected: AST, readonly actual: unknown) {}
36
+ }
37
+
38
+ /**
39
+ * @tsplus static fncts.schema.ParseError.TypeError __call
40
+ * @tsplus static fncts.schema.ParseErrorOps TypeError
41
+ */
42
+ export function typeError(expected: AST, actual: unknown): ParseError {
43
+ return new TypeError(expected, actual);
44
+ }
45
+
46
+ /**
47
+ * @tsplus companion fncts.schema.ParseError.IndexError
48
+ */
49
+ export class IndexError {
50
+ readonly _tag = ParseErrorTag.Index;
51
+ constructor(readonly index: number, readonly errors: Vector<ParseError>) {}
52
+ }
53
+
54
+ /**
55
+ * @tsplus static fncts.schema.ParseError.IndexError __call
56
+ * @tsplus static fncts.schema.ParseErrorOps IndexError
57
+ */
58
+ export function indexError(index: number, errors: Vector<ParseError>): ParseError {
59
+ return new IndexError(index, errors);
60
+ }
61
+
62
+ /**
63
+ * @tsplus companion fncts.schema.ParseError.KeyError
64
+ */
65
+ export class KeyError {
66
+ readonly _tag = ParseErrorTag.Key;
67
+ constructor(readonly keyAST: AST, readonly key: any, readonly errors: Vector<ParseError>) {}
68
+ }
69
+
70
+ /**
71
+ * @tsplus static fncts.schema.ParseError.IndexError __call
72
+ * @tsplus static fncts.schema.ParseErrorOps KeyError
73
+ */
74
+ export function keyError(keyAST: AST, key: any, errors: Vector<ParseError>): ParseError {
75
+ return new KeyError(keyAST, key, errors);
76
+ }
77
+
78
+ /**
79
+ * @tsplus companion fncts.schema.ParseError.MissingError
80
+ */
81
+ export class MissingError {
82
+ readonly _tag = ParseErrorTag.Missing;
83
+ }
84
+
85
+ /**
86
+ * @tsplus static fncts.schema.ParseErrorOps MissingError
87
+ */
88
+ export const missingError = new MissingError();
89
+
90
+ /**
91
+ * @tsplus companion fncts.schema.ParseError.UnexpectedError
92
+ */
93
+ export class UnexpectedError {
94
+ readonly _tag = ParseErrorTag.Unexpected;
95
+ constructor(readonly actual: unknown) {}
96
+ }
97
+
98
+ /**
99
+ * @tsplus static fncts.schema.ParseError.UnexpectedError __call
100
+ * @tsplus static fncts.schema.ParseErrorOps UnexpectedError
101
+ */
102
+ export function unexpectedError(actual: unknown): ParseError {
103
+ return new UnexpectedError(actual);
104
+ }
105
+
106
+ /**
107
+ * @tsplus companion fncts.schema.ParseError.UnionMemberError
108
+ */
109
+ export class UnionMemberError {
110
+ readonly _tag = ParseErrorTag.UnionMember;
111
+ constructor(readonly errors: Vector<ParseError>) {}
112
+ }
113
+
114
+ /**
115
+ * @tsplus static fncts.schema.ParseError.UnionMemberError __call
116
+ * @tsplus static fncts.schema.ParseErrorOps UnionMemberError
117
+ */
118
+ export function unionMemberError(errors: Vector<ParseError>): ParseError {
119
+ return new UnionMemberError(errors);
120
+ }
121
+
122
+ /**
123
+ * @tsplus static fncts.schema.ParseErrorOps format
124
+ */
125
+ export function format(errors: Vector<ParseError>): string {
126
+ return RoseTree(`${errors.length} error(s) found`, errors.map(go)).draw;
127
+ }
128
+
129
+ function formatActual(actual: unknown): string {
130
+ return showWithOptions(actual, { colors: false });
131
+ }
132
+
133
+ function formatTemplateLiteralSpan(span: TemplateLiteralSpan): string {
134
+ switch (span.type._tag) {
135
+ case ASTTag.StringKeyword:
136
+ return "${string}";
137
+ case ASTTag.NumberKeyword:
138
+ return "${number}";
139
+ }
140
+ }
141
+
142
+ function formatTemplateLiteral(ast: TemplateLiteral): string {
143
+ return ast.head + ast.spans.map((span) => formatTemplateLiteralSpan(span) + span.literal).join("");
144
+ }
145
+
146
+ function getExpected(ast: AST): Maybe<string> {
147
+ return ast.annotations
148
+ .get(ASTAnnotation.Identifier)
149
+ .orElse(ast.annotations.get(ASTAnnotation.Title))
150
+ .flatMap((title) =>
151
+ ast.annotations.get(ASTAnnotation.Description).match(
152
+ () => Just(title),
153
+ (description) => Just(`${title} (${description})`),
154
+ ),
155
+ );
156
+ }
157
+
158
+ function getMissedBrands(ast: Validation): string {
159
+ return ast.validation.map((validation) => validation.name).join(" & ");
160
+ }
161
+
162
+ function formatExpected(ast: AST): string {
163
+ concrete(ast);
164
+ switch (ast._tag) {
165
+ case ASTTag.StringKeyword:
166
+ return getExpected(ast).getOrElse("string");
167
+ case ASTTag.NumberKeyword:
168
+ return getExpected(ast).getOrElse("number");
169
+ case ASTTag.BooleanKeyword:
170
+ return getExpected(ast).getOrElse("boolean");
171
+ case ASTTag.BigIntKeyword:
172
+ return getExpected(ast).getOrElse("bigint");
173
+ case ASTTag.UndefinedKeyword:
174
+ return getExpected(ast).getOrElse("undefined");
175
+ case ASTTag.SymbolKeyword:
176
+ return getExpected(ast).getOrElse("symbol");
177
+ case ASTTag.ObjectKeyword:
178
+ return getExpected(ast).getOrElse("object");
179
+ case ASTTag.AnyKeyword:
180
+ return getExpected(ast).getOrElse("any");
181
+ case ASTTag.UnknownKeyword:
182
+ return getExpected(ast).getOrElse("unknown");
183
+ case ASTTag.VoidKeyword:
184
+ return getExpected(ast).getOrElse("void");
185
+ case ASTTag.NeverKeyword:
186
+ return getExpected(ast).getOrElse("never");
187
+ case ASTTag.Literal:
188
+ return getExpected(ast).getOrElse(formatActual(ast.literal));
189
+ case ASTTag.UniqueSymbol:
190
+ return getExpected(ast).getOrElse(formatActual(ast.symbol));
191
+ case ASTTag.Union:
192
+ return ast.types.map(formatExpected).join(" or ");
193
+ case ASTTag.Refinement:
194
+ return getExpected(ast).getOrElse("refinement");
195
+ case ASTTag.TemplateLiteral:
196
+ return getExpected(ast).getOrElse(formatTemplateLiteral(ast));
197
+ case ASTTag.Tuple:
198
+ return getExpected(ast).getOrElse("tuple or array");
199
+ case ASTTag.TypeLiteral:
200
+ return getExpected(ast).getOrElse("type literal");
201
+ case ASTTag.Enum:
202
+ return getExpected(ast).getOrElse(ast.enums.map(([_, value]) => JSON.stringify(value)).join(" | "));
203
+ case ASTTag.Lazy:
204
+ return getExpected(ast).getOrElse("<anonymous lazy schema>");
205
+ case ASTTag.Declaration:
206
+ return getExpected(ast).getOrElse("<anonymous Declaration schema>");
207
+ case ASTTag.Transform:
208
+ return `a parsable value from ${formatExpected(ast.from)} to ${formatExpected(ast.to)}`;
209
+ case ASTTag.Validation:
210
+ return getExpected(ast).match(
211
+ () => getMissedBrands(ast),
212
+ (expected) => `${expected} with validation(s) ${getMissedBrands(ast)}`,
213
+ );
214
+ }
215
+ }
216
+
217
+ function go(error: ParseError): RoseTree<string> {
218
+ switch (error._tag) {
219
+ case ParseErrorTag.Type:
220
+ return RoseTree(
221
+ error.expected.annotations
222
+ .get(ASTAnnotation.Message)
223
+ .map((f) => f(error.actual))
224
+ .getOrElse(`Expected ${formatExpected(error.expected)}, actual ${formatActual(error.actual)}`),
225
+ );
226
+ case ParseErrorTag.Index:
227
+ return RoseTree(`index ${error.index}`, error.errors.map(go));
228
+ case ParseErrorTag.Unexpected:
229
+ return RoseTree("is unexpected");
230
+ case ParseErrorTag.Key:
231
+ return RoseTree(`key ${formatActual(error.key)}`, error.errors.map(go));
232
+ case ParseErrorTag.Missing:
233
+ return RoseTree("is missing");
234
+ case ParseErrorTag.UnionMember:
235
+ return RoseTree("union member", error.errors.map(go));
236
+ }
237
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @tsplus type fncts.schema.ParseResult
3
+ * @tsplus companion fncts.schema.ParseResultOps
4
+ */
5
+ export interface ParseResult<A> extends Either<Vector<ParseError>, A> {}
6
+
7
+ /**
8
+ * @tsplus static fncts.schema.ParseResultOps succeed
9
+ */
10
+ export function succeed<A>(value: A): ParseResult<A> {
11
+ return Either.right(value);
12
+ }
13
+
14
+ /**
15
+ * @tsplus static fncts.schema.ParseResultOps failures
16
+ */
17
+ export function failures<A = never>(value: Vector<ParseError>): ParseResult<A> {
18
+ return Either.left(value);
19
+ }
20
+
21
+ /**
22
+ * @tsplus static fncts.schema.ParseResultOps fail
23
+ */
24
+ export function fail<A = never>(value: ParseError): ParseResult<A> {
25
+ return Either.left(Vector(value));
26
+ }
@@ -0,0 +1,71 @@
1
+ import type { ParseOptions } from "../AST.js";
2
+
3
+ import { parserFor } from "./interpreter.js";
4
+
5
+ /**
6
+ * @tsplus getter fncts.schema.Schema decode
7
+ * @tsplus getter fncts.schema.Parser decode
8
+ */
9
+ export function decode<A>(schema: Schema<A>): Parser<A> {
10
+ return parserFor(schema.ast);
11
+ }
12
+
13
+ /**
14
+ * @tsplus getter fncts.schema.Schema decodeMaybe
15
+ * @tsplus getter fncts.schema.Parser decodeMaybe
16
+ */
17
+ export function decodeMaybe<A>(schema: Schema<A>): <A>(input: A, options?: ParseOptions) => Maybe<A> {
18
+ return parseMaybe(schema.ast.getTo);
19
+ }
20
+
21
+ /**
22
+ * @tsplus getter fncts.schema.Schema encode
23
+ * @tsplus getter fncts.schema.Parser encode
24
+ */
25
+ export function encode<A>(schema: Schema<A>): <A>(input: A, options?: ParseOptions) => ParseResult<unknown> {
26
+ return parserFor(schema.ast.reverse);
27
+ }
28
+
29
+ /**
30
+ * @tsplus getter fncts.schema.Schema encodeMaybe
31
+ * @tsplus getter fncts.schema.Parser encodeMaybe
32
+ */
33
+ export function encodeMaybe<A>(schema: Schema<A>): <A>(input: A, options?: ParseOptions) => Maybe<unknown> {
34
+ return parseMaybe(schema.ast.reverse);
35
+ }
36
+
37
+ /**
38
+ * @tsplus getter fncts.schema.Schema is
39
+ * @tsplus getter fncts.schema.Parser is
40
+ */
41
+ export function is<A>(schema: Schema<A>) {
42
+ return (input: unknown, options?: ParseOptions): input is A => {
43
+ return parserFor(schema.ast)(input, options).isRight();
44
+ };
45
+ }
46
+
47
+ function parseMaybe(ast: AST) {
48
+ const parse = parserFor(ast);
49
+ return (input: unknown, options?: ParseOptions): Maybe<any> => {
50
+ return parse(input, options).toMaybe;
51
+ };
52
+ }
53
+
54
+ function parseOrThrow(ast: AST) {
55
+ const parser = parserFor(ast);
56
+ return (input: unknown, options?: ParseOptions) => {
57
+ return parser(input, options).match((errors) => {
58
+ throw new Error(ParseError.format(errors));
59
+ }, Function.identity);
60
+ };
61
+ }
62
+
63
+ /**
64
+ * @tsplus getter fncts.schema.Schema asserts
65
+ * @tsplus getter fncts.schema.Parser asserts
66
+ */
67
+ export function asserts<A>(schema: Schema<A>) {
68
+ return (input: unknown, options?: ParseOptions): asserts input is A => {
69
+ parseOrThrow(schema.ast.getTo)(input, options);
70
+ };
71
+ }
@@ -0,0 +1,24 @@
1
+ import type { ParseOptions } from "../AST.js";
2
+
3
+ /**
4
+ * @tsplus type fncts.schema.Parser
5
+ * @tsplus companion fncts.schema.ParserOps
6
+ * @tsplus no-inherit fncts.schema.Schema fncts.schema.SchemaOps
7
+ */
8
+ export interface Parser<in out A> {
9
+ (input: unknown, options?: ParseOptions): ParseResult<A>;
10
+ }
11
+
12
+ /**
13
+ * @tsplus static fncts.schema.ParserOps make
14
+ */
15
+ export function make<A>(parse: Parser<A>): Parser<A> {
16
+ return parse;
17
+ }
18
+
19
+ /**
20
+ * @tsplus static fncts.schema.ParserOps fromRefinement
21
+ */
22
+ export function fromRefinement<A>(ast: AST, refinement: Refinement<unknown, A>): Parser<A> {
23
+ return (u) => (refinement(u) ? ParseResult.succeed(u) : ParseResult.fail(ParseError.TypeError(ast, u)));
24
+ }