skir-codemirror-plugin 0.9.0

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 (72) hide show
  1. package/README.md +126 -0
  2. package/dist/codemirror/create_editor_state.d.ts +20 -0
  3. package/dist/codemirror/create_editor_state.d.ts.map +1 -0
  4. package/dist/codemirror/create_editor_state.js +252 -0
  5. package/dist/codemirror/create_editor_state.js.map +1 -0
  6. package/dist/codemirror/enter_key_handler.d.ts +8 -0
  7. package/dist/codemirror/enter_key_handler.d.ts.map +1 -0
  8. package/dist/codemirror/enter_key_handler.js +181 -0
  9. package/dist/codemirror/enter_key_handler.js.map +1 -0
  10. package/dist/codemirror/json_completion.d.ts +4 -0
  11. package/dist/codemirror/json_completion.d.ts.map +1 -0
  12. package/dist/codemirror/json_completion.js +150 -0
  13. package/dist/codemirror/json_completion.js.map +1 -0
  14. package/dist/codemirror/json_linter.d.ts +4 -0
  15. package/dist/codemirror/json_linter.d.ts.map +1 -0
  16. package/dist/codemirror/json_linter.js +277 -0
  17. package/dist/codemirror/json_linter.js.map +1 -0
  18. package/dist/codemirror/json_state.d.ts +16 -0
  19. package/dist/codemirror/json_state.d.ts.map +1 -0
  20. package/dist/codemirror/json_state.js +124 -0
  21. package/dist/codemirror/json_state.js.map +1 -0
  22. package/dist/codemirror/status_bar.d.ts +3 -0
  23. package/dist/codemirror/status_bar.d.ts.map +1 -0
  24. package/dist/codemirror/status_bar.js +123 -0
  25. package/dist/codemirror/status_bar.js.map +1 -0
  26. package/dist/index.d.ts +4 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +2 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/json/json_parser.d.ts +3 -0
  31. package/dist/json/json_parser.d.ts.map +1 -0
  32. package/dist/json/json_parser.js +414 -0
  33. package/dist/json/json_parser.js.map +1 -0
  34. package/dist/json/json_parser.test.d.ts +2 -0
  35. package/dist/json/json_parser.test.d.ts.map +1 -0
  36. package/dist/json/json_parser.test.js +337 -0
  37. package/dist/json/json_parser.test.js.map +1 -0
  38. package/dist/json/schema_validator.d.ts +3 -0
  39. package/dist/json/schema_validator.d.ts.map +1 -0
  40. package/dist/json/schema_validator.js +525 -0
  41. package/dist/json/schema_validator.js.map +1 -0
  42. package/dist/json/schema_validator.test.d.ts +2 -0
  43. package/dist/json/schema_validator.test.d.ts.map +1 -0
  44. package/dist/json/schema_validator.test.js +212 -0
  45. package/dist/json/schema_validator.test.js.map +1 -0
  46. package/dist/json/to_json.d.ts +6 -0
  47. package/dist/json/to_json.d.ts.map +1 -0
  48. package/dist/json/to_json.js +61 -0
  49. package/dist/json/to_json.js.map +1 -0
  50. package/dist/json/to_json.test.d.ts +2 -0
  51. package/dist/json/to_json.test.d.ts.map +1 -0
  52. package/dist/json/to_json.test.js +128 -0
  53. package/dist/json/to_json.test.js.map +1 -0
  54. package/dist/json/types.d.ts +170 -0
  55. package/dist/json/types.d.ts.map +1 -0
  56. package/dist/json/types.js +2 -0
  57. package/dist/json/types.js.map +1 -0
  58. package/package.json +85 -0
  59. package/src/codemirror/create_editor_state.ts +278 -0
  60. package/src/codemirror/enter_key_handler.ts +232 -0
  61. package/src/codemirror/json_completion.ts +182 -0
  62. package/src/codemirror/json_linter.ts +358 -0
  63. package/src/codemirror/json_state.ts +170 -0
  64. package/src/codemirror/status_bar.ts +137 -0
  65. package/src/index.ts +6 -0
  66. package/src/json/json_parser.test.ts +360 -0
  67. package/src/json/json_parser.ts +461 -0
  68. package/src/json/schema_validator.test.ts +230 -0
  69. package/src/json/schema_validator.ts +558 -0
  70. package/src/json/to_json.test.ts +150 -0
  71. package/src/json/to_json.ts +70 -0
  72. package/src/json/types.ts +254 -0
@@ -0,0 +1,461 @@
1
+ import type {
2
+ JsonArray,
3
+ JsonEdit,
4
+ JsonError,
5
+ JsonKey,
6
+ JsonKeyValue,
7
+ JsonObject,
8
+ JsonParseResult,
9
+ JsonValue,
10
+ Segment,
11
+ } from "./types";
12
+
13
+ export function parseJsonValue(input: string): JsonParseResult {
14
+ const tokens = tokenize(input);
15
+ if (tokens.kind === "error") {
16
+ return {
17
+ value: undefined,
18
+ errors: [tokens],
19
+ edits: [],
20
+ };
21
+ }
22
+ const parser = new JsonParser(tokens.tokens, input);
23
+ const parseResult = parser.parseValueOrSkip();
24
+ parser.expectEnd();
25
+
26
+ return {
27
+ value: parseResult,
28
+ errors: parser.errors,
29
+ edits: parser.edits,
30
+ };
31
+ }
32
+
33
+ interface JsonToken {
34
+ segment: Segment;
35
+ jsonCode: string;
36
+ }
37
+
38
+ interface JsonTokens {
39
+ kind: "tokens";
40
+ tokens: JsonToken[];
41
+ }
42
+
43
+ function tokenize(input: string): JsonTokens | JsonError {
44
+ const tokens: JsonToken[] = [];
45
+ let pos = 0;
46
+
47
+ const whitespaceRegex = /[ \t\r\n]*/y;
48
+ const tokenRegex =
49
+ /([[\]{}:,]|(-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)|false|true|null|("(((?=\\)\\(["\\/ bfnrt]|u[0-9a-fA-F]{4}))|[^"\\\0-\x1F\x7F]+)*")|$)/y;
50
+
51
+ while (true) {
52
+ whitespaceRegex.lastIndex = pos;
53
+ whitespaceRegex.exec(input);
54
+ pos = whitespaceRegex.lastIndex;
55
+
56
+ tokenRegex.lastIndex = pos;
57
+ const tokenMatch = tokenRegex.exec(input);
58
+ if (tokenMatch) {
59
+ const tokenText = tokenMatch[0];
60
+ const segment: Segment = {
61
+ start: pos,
62
+ end: pos + tokenText.length,
63
+ };
64
+ const token: JsonToken = { segment, jsonCode: tokenText };
65
+ pos = tokenRegex.lastIndex;
66
+ tokens.push(token);
67
+ if (tokenText === "") {
68
+ return {
69
+ kind: "tokens",
70
+ tokens: tokens,
71
+ };
72
+ }
73
+ } else {
74
+ // Find the next word boundary using unicode support
75
+ const wordBoundaryRegex = /\w*/uy;
76
+ wordBoundaryRegex.lastIndex = pos + 1;
77
+ wordBoundaryRegex.exec(input);
78
+ const end = wordBoundaryRegex.lastIndex || pos + 1;
79
+ return {
80
+ kind: "error",
81
+ message: "not a token",
82
+ segment: { start: pos, end: end },
83
+ };
84
+ }
85
+ }
86
+ }
87
+
88
+ class JsonParser {
89
+ constructor(private tokens: JsonToken[], private readonly input: string) {}
90
+ private tokenIndex = 0;
91
+ errors: JsonError[] = [];
92
+ edits: JsonEdit[] = [];
93
+ private indent = "";
94
+
95
+ parseValueOrSkip(): JsonValue | undefined {
96
+ const token = this.peekToken();
97
+ const firstChar = token.jsonCode ? token.jsonCode[0] : "";
98
+
99
+ switch (firstChar) {
100
+ case "[":
101
+ return this.parseArray();
102
+ case "{":
103
+ return this.parseObject();
104
+ case "n":
105
+ this.nextToken();
106
+ return {
107
+ kind: "literal",
108
+ firstToken: token.segment,
109
+ segment: token.segment,
110
+ jsonCode: "null",
111
+ type: "null",
112
+ };
113
+ case "f":
114
+ this.nextToken();
115
+ return {
116
+ kind: "literal",
117
+ firstToken: token.segment,
118
+ segment: token.segment,
119
+ jsonCode: "false",
120
+ type: "boolean",
121
+ };
122
+ case "t":
123
+ this.nextToken();
124
+ return {
125
+ kind: "literal",
126
+ firstToken: token.segment,
127
+ segment: token.segment,
128
+ jsonCode: "true",
129
+ type: "boolean",
130
+ };
131
+ case '"':
132
+ this.nextToken();
133
+ return {
134
+ kind: "literal",
135
+ firstToken: token.segment,
136
+ segment: token.segment,
137
+ jsonCode: token.jsonCode,
138
+ type: "string",
139
+ };
140
+ case "0":
141
+ case "1":
142
+ case "2":
143
+ case "3":
144
+ case "4":
145
+ case "5":
146
+ case "6":
147
+ case "7":
148
+ case "8":
149
+ case "9":
150
+ case "-":
151
+ this.nextToken();
152
+ return {
153
+ kind: "literal",
154
+ firstToken: token.segment,
155
+ segment: token.segment,
156
+ jsonCode: token.jsonCode,
157
+ type: "number",
158
+ };
159
+ }
160
+
161
+ this.errors.push({
162
+ kind: "error",
163
+ message: "expected: value",
164
+ segment: this.peekToken().segment,
165
+ });
166
+ this.skip();
167
+ return undefined;
168
+ }
169
+
170
+ private parseArray(): JsonArray {
171
+ const leftBracket = this.nextToken();
172
+ const values: JsonValue[] = [];
173
+ while (true) {
174
+ if (this.peekToken().jsonCode === "]") {
175
+ const rightBracket = this.nextToken();
176
+ return {
177
+ kind: "array",
178
+ firstToken: leftBracket.segment,
179
+ segment: {
180
+ start: leftBracket.segment.start,
181
+ end: rightBracket.segment.end,
182
+ },
183
+ values,
184
+ };
185
+ }
186
+ if (this.peekToken().jsonCode === "}") {
187
+ this.errors.push({
188
+ kind: "error",
189
+ message: "expected: ']'",
190
+ segment: this.peekToken().segment,
191
+ });
192
+ const wrongBracket = this.nextToken();
193
+ return {
194
+ kind: "array",
195
+ firstToken: leftBracket.segment,
196
+ segment: {
197
+ start: leftBracket.segment.start,
198
+ end: wrongBracket.segment.end,
199
+ },
200
+ values,
201
+ };
202
+ }
203
+ if (this.peekToken().jsonCode === "") {
204
+ // End of file
205
+ this.expectSymbolOrSkip("]");
206
+ return {
207
+ kind: "array",
208
+ firstToken: leftBracket.segment,
209
+ segment: {
210
+ start: leftBracket.segment.start,
211
+ end: this.peekToken().segment.start,
212
+ },
213
+ values,
214
+ };
215
+ }
216
+ const value = this.parseValueOrSkip();
217
+ if (value) {
218
+ values.push(value);
219
+ }
220
+ if (this.peekToken().jsonCode === ",") {
221
+ const commaToken = this.nextToken();
222
+ // Check if this is a trailing comma
223
+ if (this.peekToken().jsonCode === "]") {
224
+ // Trailing comma - add edit to remove it
225
+ this.edits.push({
226
+ segment: commaToken.segment,
227
+ replacement: "",
228
+ });
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ private parseObject(): JsonObject {
235
+ const leftBracket = this.nextToken();
236
+ const keyValues: { [key: string]: JsonKeyValue } = {};
237
+ const allKeys: JsonKey[] = [];
238
+ while (true) {
239
+ if (this.peekToken().jsonCode === "}") {
240
+ const rightBracket = this.nextToken();
241
+ return {
242
+ kind: "object",
243
+ firstToken: leftBracket.segment,
244
+ segment: {
245
+ start: leftBracket.segment.start,
246
+ end: rightBracket.segment.end,
247
+ },
248
+ keyValues,
249
+ allKeys,
250
+ };
251
+ }
252
+ if (this.peekToken().jsonCode === "]") {
253
+ this.errors.push({
254
+ kind: "error",
255
+ message: "expected: ']'",
256
+ segment: this.peekToken().segment,
257
+ });
258
+ const wrongBracket = this.nextToken();
259
+ return {
260
+ kind: "object",
261
+ firstToken: leftBracket.segment,
262
+ segment: {
263
+ start: leftBracket.segment.start,
264
+ end: wrongBracket.segment.end,
265
+ },
266
+ keyValues,
267
+ allKeys,
268
+ };
269
+ }
270
+ if (this.peekToken().jsonCode === "") {
271
+ // End of file
272
+ this.expectSymbolOrSkip("}");
273
+ return {
274
+ kind: "object",
275
+ firstToken: leftBracket.segment,
276
+ segment: {
277
+ start: leftBracket.segment.start,
278
+ end: this.peekToken().segment.start,
279
+ },
280
+ keyValues,
281
+ allKeys,
282
+ };
283
+ }
284
+ const keyToken = this.peekToken();
285
+ if (!keyToken.jsonCode.startsWith('"')) {
286
+ this.errors.push({
287
+ kind: "error",
288
+ message: "expected: string",
289
+ segment: keyToken.segment,
290
+ });
291
+ this.skip();
292
+ // Consume comma if we're stuck at one to avoid infinite loop
293
+ if (this.peekToken().jsonCode === ",") {
294
+ this.nextToken();
295
+ }
296
+ continue;
297
+ }
298
+ const key = JSON.parse(keyToken.jsonCode) as string;
299
+ allKeys.push({
300
+ key: key,
301
+ keySegment: keyToken.segment,
302
+ });
303
+ this.nextToken();
304
+ if (!this.expectSymbolOrSkip(":")) {
305
+ continue;
306
+ }
307
+ if (keyValues[key]) {
308
+ this.errors.push({
309
+ kind: "error",
310
+ message: "duplicate key",
311
+ segment: keyToken.segment,
312
+ });
313
+ }
314
+ const value = this.parseValueOrSkip();
315
+ if (value) {
316
+ keyValues[key] = {
317
+ keySegment: keyToken.segment,
318
+ key: key,
319
+ value: value,
320
+ };
321
+ }
322
+ if (this.peekToken().jsonCode === ",") {
323
+ const commaToken = this.nextToken();
324
+ // Check if this is a trailing comma
325
+ if (this.peekToken().jsonCode === "}") {
326
+ // Trailing comma - add edit to remove it
327
+ this.edits.push({
328
+ segment: commaToken.segment,
329
+ replacement: "",
330
+ });
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ expectEnd(): void {
337
+ const token = this.nextToken();
338
+ if (token.jsonCode) {
339
+ this.errors.push({
340
+ kind: "error",
341
+ message: "expected: end",
342
+ segment: this.peekToken().segment,
343
+ });
344
+ }
345
+ }
346
+
347
+ private nextToken(): JsonToken {
348
+ const result = this.tokens[this.tokenIndex];
349
+
350
+ // Check the whitespace separator before the current token.
351
+ {
352
+ const previous =
353
+ this.tokenIndex <= 0 ? undefined : this.tokens[this.tokenIndex - 1];
354
+ const separatorSegment: Segment = {
355
+ start: previous ? previous.segment.end : 0,
356
+ end: result.segment.start,
357
+ };
358
+ const actualSeparator = this.input.substring(
359
+ separatorSegment.start,
360
+ separatorSegment.end,
361
+ );
362
+ const expectedSeparator = this.inferWhitespaceSeparator(
363
+ previous?.jsonCode ?? "",
364
+ result.jsonCode,
365
+ );
366
+
367
+ if (actualSeparator !== expectedSeparator.text) {
368
+ this.edits.push({
369
+ segment: separatorSegment,
370
+ replacement: expectedSeparator.text,
371
+ });
372
+ }
373
+ this.indent = expectedSeparator.newIndent ?? this.indent;
374
+ }
375
+
376
+ ++this.tokenIndex;
377
+ return result;
378
+ }
379
+
380
+ private inferWhitespaceSeparator(a: string, b: string): WhitespaceSeparator {
381
+ const { indent } = this;
382
+ if (a === ":") {
383
+ return {
384
+ text: " ",
385
+ };
386
+ } else if (a === "," && b !== "]" && b !== "}") {
387
+ return {
388
+ text: `\n${indent}`,
389
+ };
390
+ } else if (/[0-9"}\]el]$/.test(a) && /^[0-9"{[tfn-]/.test(b)) {
391
+ // a is the end of a JSON value and B is the start of a JSON value
392
+ return {
393
+ text: `,\n${indent}`,
394
+ };
395
+ } else if ((a === "[" && b !== "]") || (a === "{" && b !== "}")) {
396
+ const newIndent = indent + INDENT_UNIT;
397
+ return {
398
+ text: `\n${newIndent}`,
399
+ newIndent: newIndent,
400
+ };
401
+ } else if ((a !== "[" && b === "]") || (a !== "{" && b === "}")) {
402
+ const newIndent = indent.replace(INDENT_UNIT, "");
403
+ return {
404
+ text: `\n${newIndent}`,
405
+ newIndent: newIndent,
406
+ };
407
+ } else {
408
+ return {
409
+ text: "",
410
+ };
411
+ }
412
+ }
413
+
414
+ private peekToken(): JsonToken {
415
+ return this.tokens[this.tokenIndex];
416
+ }
417
+
418
+ private expectSymbolOrSkip(symbol: string): boolean {
419
+ if (this.peekToken().jsonCode !== symbol) {
420
+ this.errors.push({
421
+ kind: "error",
422
+ message: `expected: '${symbol}'`,
423
+ segment: this.peekToken().segment,
424
+ });
425
+ this.skip();
426
+ return false;
427
+ }
428
+ this.nextToken();
429
+ return true;
430
+ }
431
+
432
+ private skip(): void {
433
+ while (true) {
434
+ const tokenJson = this.peekToken().jsonCode;
435
+ if (
436
+ tokenJson === "" ||
437
+ tokenJson === "," ||
438
+ tokenJson === "]" ||
439
+ tokenJson === "}" ||
440
+ tokenJson.startsWith('"')
441
+ ) {
442
+ return;
443
+ } else if (tokenJson === "[") {
444
+ this.parseArray();
445
+ } else if (tokenJson === "{") {
446
+ this.parseObject();
447
+ } else {
448
+ this.nextToken();
449
+ }
450
+ }
451
+ }
452
+ }
453
+
454
+ /// Text to insert between two consecutive tokens.
455
+ interface WhitespaceSeparator {
456
+ /// Matches /(,?)(\n?)( )*/
457
+ text: string;
458
+ newIndent?: string;
459
+ }
460
+
461
+ const INDENT_UNIT = " ";
@@ -0,0 +1,230 @@
1
+ import { expect } from "buckwheat";
2
+ import { describe, it } from "mocha";
3
+ import { parseJsonValue } from "./json_parser.js";
4
+ import { validateSchema } from "./schema_validator.js";
5
+ import { JsonValue, TypeDefinition } from "./types.js";
6
+
7
+ function parse(json: string): JsonValue {
8
+ const result = parseJsonValue(json);
9
+ if (!result.value) {
10
+ throw new Error(`JSON parse error: ${JSON.stringify(result.errors)}`);
11
+ }
12
+ return result.value;
13
+ }
14
+
15
+ describe("schema_validator", () => {
16
+ it("validates primitive int32", () => {
17
+ const schema: TypeDefinition = {
18
+ type: { kind: "primitive", value: "int32" },
19
+ records: [],
20
+ };
21
+ const result = validateSchema(parse("123"), schema);
22
+ expect(result).toMatch({ errors: [] });
23
+ if (result.hints.length === 0) {
24
+ throw new Error("Expected type hints");
25
+ }
26
+ });
27
+
28
+ it("validates primitive string", () => {
29
+ const schema: TypeDefinition = {
30
+ type: { kind: "primitive", value: "string" },
31
+ records: [],
32
+ };
33
+ const result = validateSchema(parse('"hello"'), schema);
34
+ expect(result).toMatch({ errors: [] });
35
+ });
36
+
37
+ it("reports error for type mismatch (int32 vs string)", () => {
38
+ const schema: TypeDefinition = {
39
+ type: { kind: "primitive", value: "int32" },
40
+ records: [],
41
+ };
42
+ const result = validateSchema(parse('"hello"'), schema);
43
+ expect(result.errors).toMatch([
44
+ {
45
+ message: "Expected: int32",
46
+ },
47
+ ]);
48
+ });
49
+
50
+ it("validates array of primitives", () => {
51
+ const schema: TypeDefinition = {
52
+ type: {
53
+ kind: "array",
54
+ value: { item: { kind: "primitive", value: "int32" } },
55
+ },
56
+ records: [],
57
+ };
58
+ const result = validateSchema(parse("[1, 2, 3]"), schema);
59
+ expect(result).toMatch({ errors: [] });
60
+ });
61
+
62
+ it("reports error for invalid array item", () => {
63
+ const schema: TypeDefinition = {
64
+ type: {
65
+ kind: "array",
66
+ value: { item: { kind: "primitive", value: "int32" } },
67
+ },
68
+ records: [],
69
+ };
70
+ const result = validateSchema(parse('[1, "bad", 3]'), schema);
71
+ expect(result.errors).toMatch([
72
+ {
73
+ message: "Expected: int32",
74
+ segment: {
75
+ start: 4,
76
+ end: 9,
77
+ },
78
+ },
79
+ ]);
80
+ });
81
+
82
+ it("validates optional field (present)", () => {
83
+ const schema: TypeDefinition = {
84
+ type: {
85
+ kind: "optional",
86
+ value: { kind: "primitive", value: "int32" },
87
+ },
88
+ records: [],
89
+ };
90
+ const result = validateSchema(parse("123"), schema);
91
+ expect(result).toMatch({ errors: [] });
92
+ });
93
+
94
+ it("validates optional field (null)", () => {
95
+ const schema: TypeDefinition = {
96
+ type: {
97
+ kind: "optional",
98
+ value: { kind: "primitive", value: "int32" },
99
+ },
100
+ records: [],
101
+ };
102
+ const result = validateSchema(parse("null"), schema);
103
+ expect(result).toMatch({ errors: [] });
104
+ });
105
+
106
+ it("validates struct", () => {
107
+ const schema: TypeDefinition = {
108
+ type: { kind: "record", value: "MyStruct" },
109
+ records: [
110
+ {
111
+ kind: "struct",
112
+ id: "MyStruct",
113
+ fields: [
114
+ {
115
+ name: "foo",
116
+ number: 1,
117
+ type: { kind: "primitive", value: "string" },
118
+ },
119
+ {
120
+ name: "bar",
121
+ number: 2,
122
+ type: { kind: "primitive", value: "int32" },
123
+ },
124
+ ],
125
+ },
126
+ ],
127
+ };
128
+ const result = validateSchema(parse('{"foo": "hi", "bar": 123}'), schema);
129
+ expect(result).toMatch({ errors: [] });
130
+ });
131
+
132
+ it("reports unknown field in struct", () => {
133
+ const schema: TypeDefinition = {
134
+ type: { kind: "record", value: "MyStruct" },
135
+ records: [
136
+ {
137
+ kind: "struct",
138
+ id: "MyStruct",
139
+ fields: [],
140
+ },
141
+ ],
142
+ };
143
+ const result = validateSchema(parse('{"unknown": 1}'), schema);
144
+ expect(result.errors).toMatch([
145
+ {
146
+ message: "Unknown field",
147
+ segment: {
148
+ start: 1,
149
+ end: 10,
150
+ },
151
+ },
152
+ ]);
153
+ });
154
+
155
+ it("validates enum (string literal)", () => {
156
+ const schema: TypeDefinition = {
157
+ type: { kind: "record", value: "MyEnum" },
158
+ records: [
159
+ {
160
+ kind: "enum",
161
+ id: "MyEnum",
162
+ variants: [
163
+ { name: "First", number: 1 },
164
+ { name: "Second", number: 2 },
165
+ ],
166
+ },
167
+ ],
168
+ };
169
+ const result = validateSchema(parse('"First"'), schema);
170
+ expect(result).toMatch({ errors: [] });
171
+ });
172
+
173
+ it("reports unknown enum variant", () => {
174
+ const schema: TypeDefinition = {
175
+ type: { kind: "record", value: "MyEnum" },
176
+ records: [
177
+ {
178
+ kind: "enum",
179
+ id: "MyEnum",
180
+ variants: [{ name: "First", number: 1 }],
181
+ },
182
+ ],
183
+ };
184
+ const result = validateSchema(parse('"Unknown"'), schema);
185
+ expect(result.errors).toMatch([
186
+ {
187
+ message: "Unknown variant",
188
+ },
189
+ ]);
190
+ });
191
+
192
+ it("validates enum (object with kind)", () => {
193
+ const schema: TypeDefinition = {
194
+ type: { kind: "record", value: "MyEnum" },
195
+ records: [
196
+ {
197
+ kind: "enum",
198
+ id: "MyEnum",
199
+ variants: [
200
+ {
201
+ name: "complex",
202
+ number: 1,
203
+ type: { kind: "primitive", value: "int32" },
204
+ },
205
+ ],
206
+ },
207
+ ],
208
+ };
209
+
210
+ expect(
211
+ validateSchema(parse('{"kind": "complex", "value": 123}'), schema),
212
+ ).toMatch({ errors: [] });
213
+ expect(
214
+ validateSchema(parse('{"kind": "complex", "value": "foo"}'), schema),
215
+ ).toMatch({
216
+ errors: [
217
+ {
218
+ message: "Expected: int32",
219
+ },
220
+ ],
221
+ });
222
+ expect(validateSchema(parse('{"kind": "complex"}'), schema)).toMatch({
223
+ errors: [
224
+ {
225
+ message: "Missing: 'value'",
226
+ },
227
+ ],
228
+ });
229
+ });
230
+ });