@malloydata/malloy-tag 0.0.339 → 0.0.340
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/dist/index.d.ts +1 -3
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/dist/{peggy/index.d.ts → parser.d.ts} +13 -4
- package/dist/parser.js +181 -0
- package/dist/parser.js.map +1 -0
- package/package.json +13 -6
- package/src/index.ts +1 -3
- package/src/parser.ts +203 -0
- package/CONTEXT.md +0 -173
- package/README.md +0 -0
- package/dist/peggy/dist/peg-tag-parser.d.ts +0 -11
- package/dist/peggy/dist/peg-tag-parser.js +0 -3130
- package/dist/peggy/dist/peg-tag-parser.js.map +0 -1
- package/dist/peggy/index.js +0 -117
- package/dist/peggy/index.js.map +0 -1
- package/dist/peggy/interpreter.d.ts +0 -32
- package/dist/peggy/interpreter.js +0 -208
- package/dist/peggy/interpreter.js.map +0 -1
- package/dist/peggy/statements.d.ts +0 -51
- package/dist/peggy/statements.js +0 -7
- package/dist/peggy/statements.js.map +0 -1
- package/dist/schema.d.ts +0 -41
- package/dist/schema.js +0 -573
- package/dist/schema.js.map +0 -1
- package/dist/schema.spec.d.ts +0 -1
- package/dist/schema.spec.js +0 -980
- package/dist/schema.spec.js.map +0 -1
- package/dist/tags.spec.d.ts +0 -8
- package/dist/tags.spec.js +0 -884
- package/dist/tags.spec.js.map +0 -1
- package/dist/util.spec.d.ts +0 -1
- package/dist/util.spec.js +0 -43
- package/dist/util.spec.js.map +0 -1
- package/src/motly-schema.motly +0 -52
- package/src/peggy/dist/peg-tag-parser.js +0 -2790
- package/src/peggy/index.ts +0 -89
- package/src/peggy/interpreter.ts +0 -265
- package/src/peggy/malloy-tag.peggy +0 -224
- package/src/peggy/statements.ts +0 -49
- package/src/schema.spec.ts +0 -1280
- package/src/schema.ts +0 -852
- package/src/tags.spec.ts +0 -967
- package/src/util.spec.ts +0 -43
- package/tsconfig.json +0 -12
package/dist/schema.spec.js
DELETED
|
@@ -1,980 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
* Copyright Contributors to the Malloy project
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
-
if (k2 === undefined) k2 = k;
|
|
8
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
-
}
|
|
12
|
-
Object.defineProperty(o, k2, desc);
|
|
13
|
-
}) : (function(o, m, k, k2) {
|
|
14
|
-
if (k2 === undefined) k2 = k;
|
|
15
|
-
o[k2] = m[k];
|
|
16
|
-
}));
|
|
17
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
-
}) : function(o, v) {
|
|
20
|
-
o["default"] = v;
|
|
21
|
-
});
|
|
22
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
-
var ownKeys = function(o) {
|
|
24
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
-
var ar = [];
|
|
26
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
-
return ar;
|
|
28
|
-
};
|
|
29
|
-
return ownKeys(o);
|
|
30
|
-
};
|
|
31
|
-
return function (mod) {
|
|
32
|
-
if (mod && mod.__esModule) return mod;
|
|
33
|
-
var result = {};
|
|
34
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
-
__setModuleDefault(result, mod);
|
|
36
|
-
return result;
|
|
37
|
-
};
|
|
38
|
-
})();
|
|
39
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
-
const fs = __importStar(require("fs"));
|
|
41
|
-
const path = __importStar(require("path"));
|
|
42
|
-
const peggy_1 = require("./peggy");
|
|
43
|
-
const schema_1 = require("./schema");
|
|
44
|
-
// Load the meta-schema from file
|
|
45
|
-
const metaSchemaPath = path.join(__dirname, 'motly-schema.motly');
|
|
46
|
-
const metaSchemaSource = fs.readFileSync(metaSchemaPath, 'utf-8');
|
|
47
|
-
const { tag: metaSchema, log: metaSchemaLog } = (0, peggy_1.parseTag)(metaSchemaSource);
|
|
48
|
-
// Fail fast if meta-schema has parse errors
|
|
49
|
-
if (metaSchemaLog.length > 0) {
|
|
50
|
-
throw new Error(`Meta-schema failed to parse:\n${metaSchemaLog.map(e => e.message).join('\n')}`);
|
|
51
|
-
}
|
|
52
|
-
describe('schema validation', () => {
|
|
53
|
-
describe('missing required properties', () => {
|
|
54
|
-
test('errors when required property is missing', () => {
|
|
55
|
-
const { tag } = (0, peggy_1.parseTag)('');
|
|
56
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name=string }');
|
|
57
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
58
|
-
expect(errors).toHaveLength(1);
|
|
59
|
-
expect(errors[0]).toEqual({
|
|
60
|
-
message: "Missing required property 'name'",
|
|
61
|
-
path: ['name'],
|
|
62
|
-
code: 'missing-required',
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
test('errors for multiple missing required properties', () => {
|
|
66
|
-
const { tag } = (0, peggy_1.parseTag)('');
|
|
67
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name=string age=number }');
|
|
68
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
69
|
-
expect(errors).toHaveLength(2);
|
|
70
|
-
expect(errors.map(e => e.path)).toEqual([['name'], ['age']]);
|
|
71
|
-
});
|
|
72
|
-
test('errors for missing nested required property', () => {
|
|
73
|
-
const { tag } = (0, peggy_1.parseTag)('size { width=10 }');
|
|
74
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { size: { Required: { width=number height=number } } }');
|
|
75
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
76
|
-
expect(errors).toHaveLength(1);
|
|
77
|
-
expect(errors[0]).toEqual({
|
|
78
|
-
message: "Missing required property 'height'",
|
|
79
|
-
path: ['size', 'height'],
|
|
80
|
-
code: 'missing-required',
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
describe('wrong type errors', () => {
|
|
85
|
-
test('errors when string expected but number provided', () => {
|
|
86
|
-
const { tag } = (0, peggy_1.parseTag)('name=42');
|
|
87
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name=string }');
|
|
88
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
89
|
-
expect(errors).toHaveLength(1);
|
|
90
|
-
expect(errors[0]).toEqual({
|
|
91
|
-
message: "Property 'name' has wrong type: expected 'string', got 'number'",
|
|
92
|
-
path: ['name'],
|
|
93
|
-
code: 'wrong-type',
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
test('errors when number expected but string provided', () => {
|
|
97
|
-
const { tag } = (0, peggy_1.parseTag)('age=hello');
|
|
98
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { age=number }');
|
|
99
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
100
|
-
expect(errors).toHaveLength(1);
|
|
101
|
-
expect(errors[0]).toEqual({
|
|
102
|
-
message: "Property 'age' has wrong type: expected 'number', got 'string'",
|
|
103
|
-
path: ['age'],
|
|
104
|
-
code: 'wrong-type',
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
test('errors when boolean expected but string provided', () => {
|
|
108
|
-
const { tag } = (0, peggy_1.parseTag)('enabled=yes');
|
|
109
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { enabled=boolean }');
|
|
110
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
111
|
-
expect(errors).toHaveLength(1);
|
|
112
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
113
|
-
expect(errors[0].message).toContain("expected 'boolean'");
|
|
114
|
-
});
|
|
115
|
-
test('errors when date expected but string provided', () => {
|
|
116
|
-
const { tag } = (0, peggy_1.parseTag)('created=yesterday');
|
|
117
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { created=date }');
|
|
118
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
119
|
-
expect(errors).toHaveLength(1);
|
|
120
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
121
|
-
expect(errors[0].message).toContain("expected 'date'");
|
|
122
|
-
});
|
|
123
|
-
test('errors when tag expected but scalar provided', () => {
|
|
124
|
-
const { tag } = (0, peggy_1.parseTag)('config=value');
|
|
125
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { config=tag }');
|
|
126
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
127
|
-
expect(errors).toHaveLength(1);
|
|
128
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
129
|
-
expect(errors[0].message).toContain("expected 'tag'");
|
|
130
|
-
});
|
|
131
|
-
test('validates tag type correctly', () => {
|
|
132
|
-
const { tag } = (0, peggy_1.parseTag)('config { a=1 b=2 }');
|
|
133
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { config=tag }');
|
|
134
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
135
|
-
expect(errors).toHaveLength(0);
|
|
136
|
-
});
|
|
137
|
-
test('validates flag type correctly', () => {
|
|
138
|
-
const { tag } = (0, peggy_1.parseTag)('hidden readonly');
|
|
139
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { hidden=flag readonly=flag }');
|
|
140
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
141
|
-
expect(errors).toHaveLength(0);
|
|
142
|
-
});
|
|
143
|
-
test('errors when flag expected but scalar provided', () => {
|
|
144
|
-
const { tag } = (0, peggy_1.parseTag)('hidden=@true');
|
|
145
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { hidden=flag }');
|
|
146
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
147
|
-
expect(errors).toHaveLength(1);
|
|
148
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
149
|
-
expect(errors[0].message).toContain("expected 'flag'");
|
|
150
|
-
expect(errors[0].message).toContain("got 'boolean'");
|
|
151
|
-
});
|
|
152
|
-
test('errors when flag expected but tag with properties provided', () => {
|
|
153
|
-
const { tag } = (0, peggy_1.parseTag)('hidden { x=1 }');
|
|
154
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { hidden=flag }');
|
|
155
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
156
|
-
expect(errors).toHaveLength(1);
|
|
157
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
158
|
-
expect(errors[0].message).toContain("expected 'flag'");
|
|
159
|
-
expect(errors[0].message).toContain("got 'tag'");
|
|
160
|
-
});
|
|
161
|
-
test('optional flag works when present', () => {
|
|
162
|
-
const { tag } = (0, peggy_1.parseTag)('name=test deprecated');
|
|
163
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
164
|
-
Required: { name=string }
|
|
165
|
-
Optional: { deprecated=flag }
|
|
166
|
-
`);
|
|
167
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
168
|
-
expect(errors).toHaveLength(0);
|
|
169
|
-
});
|
|
170
|
-
test('optional flag works when absent', () => {
|
|
171
|
-
const { tag } = (0, peggy_1.parseTag)('name=test');
|
|
172
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
173
|
-
Required: { name=string }
|
|
174
|
-
Optional: { deprecated=flag }
|
|
175
|
-
`);
|
|
176
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
177
|
-
expect(errors).toHaveLength(0);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
describe('unknown property errors', () => {
|
|
181
|
-
test('errors on unknown property', () => {
|
|
182
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice unknown=value');
|
|
183
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name=string }');
|
|
184
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
185
|
-
expect(errors).toHaveLength(1);
|
|
186
|
-
expect(errors[0]).toEqual({
|
|
187
|
-
message: "Unknown property 'unknown'",
|
|
188
|
-
path: ['unknown'],
|
|
189
|
-
code: 'unknown-property',
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
test('allows unknown properties with Additional flag', () => {
|
|
193
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice extra=value');
|
|
194
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Additional Required: { name=string }');
|
|
195
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
196
|
-
expect(errors).toHaveLength(0);
|
|
197
|
-
});
|
|
198
|
-
test('errors on unknown nested property', () => {
|
|
199
|
-
const { tag } = (0, peggy_1.parseTag)('size { width=10 extra=5 }');
|
|
200
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { size: { Required: { width=number } } }');
|
|
201
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
202
|
-
expect(errors).toHaveLength(1);
|
|
203
|
-
expect(errors[0]).toEqual({
|
|
204
|
-
message: "Unknown property 'extra'",
|
|
205
|
-
path: ['size', 'extra'],
|
|
206
|
-
code: 'unknown-property',
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
describe('valid tags pass validation', () => {
|
|
211
|
-
test('valid tag with required properties', () => {
|
|
212
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice age=30');
|
|
213
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name=string age=number }');
|
|
214
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
215
|
-
expect(errors).toHaveLength(0);
|
|
216
|
-
});
|
|
217
|
-
test('valid tag with optional properties', () => {
|
|
218
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice');
|
|
219
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name=string } Optional: { age=number }');
|
|
220
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
221
|
-
expect(errors).toHaveLength(0);
|
|
222
|
-
});
|
|
223
|
-
test('valid tag with optional property present', () => {
|
|
224
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice age=30');
|
|
225
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name=string } Optional: { age=number }');
|
|
226
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
227
|
-
expect(errors).toHaveLength(0);
|
|
228
|
-
});
|
|
229
|
-
test('valid nested properties', () => {
|
|
230
|
-
const { tag } = (0, peggy_1.parseTag)('size { width=100 height=200 }');
|
|
231
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { size: { Required: { width=number height=number } } }');
|
|
232
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
233
|
-
expect(errors).toHaveLength(0);
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
describe('array type validation', () => {
|
|
237
|
-
test('validates string[] type', () => {
|
|
238
|
-
const { tag } = (0, peggy_1.parseTag)('colors=[red, green, blue]');
|
|
239
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { colors="string[]" }');
|
|
240
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
241
|
-
expect(errors).toHaveLength(0);
|
|
242
|
-
});
|
|
243
|
-
test('errors when string[] expected but number[] provided', () => {
|
|
244
|
-
const { tag } = (0, peggy_1.parseTag)('values=[1, 2, 3]');
|
|
245
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { values="string[]" }');
|
|
246
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
247
|
-
expect(errors).toHaveLength(1);
|
|
248
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
249
|
-
expect(errors[0].message).toContain("expected 'string[]'");
|
|
250
|
-
expect(errors[0].message).toContain("got 'number[]'");
|
|
251
|
-
});
|
|
252
|
-
test('validates number[] type', () => {
|
|
253
|
-
const { tag } = (0, peggy_1.parseTag)('counts=[1, 2, 3]');
|
|
254
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { counts="number[]" }');
|
|
255
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
256
|
-
expect(errors).toHaveLength(0);
|
|
257
|
-
});
|
|
258
|
-
test('validates boolean[] type', () => {
|
|
259
|
-
const { tag } = (0, peggy_1.parseTag)('flags=[@true, @false, @true]');
|
|
260
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { flags="boolean[]" }');
|
|
261
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
262
|
-
expect(errors).toHaveLength(0);
|
|
263
|
-
});
|
|
264
|
-
test('validates date[] type', () => {
|
|
265
|
-
const { tag } = (0, peggy_1.parseTag)('dates=[@2024-01-01, @2024-02-01]');
|
|
266
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { dates="date[]" }');
|
|
267
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
268
|
-
expect(errors).toHaveLength(0);
|
|
269
|
-
});
|
|
270
|
-
test('any[] accepts any array', () => {
|
|
271
|
-
const { tag } = (0, peggy_1.parseTag)('items=[1, 2, 3]');
|
|
272
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { items="any[]" }');
|
|
273
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
274
|
-
expect(errors).toHaveLength(0);
|
|
275
|
-
});
|
|
276
|
-
test('errors when array expected but scalar provided', () => {
|
|
277
|
-
const { tag } = (0, peggy_1.parseTag)('colors=red');
|
|
278
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { colors="string[]" }');
|
|
279
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
280
|
-
expect(errors).toHaveLength(1);
|
|
281
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
282
|
-
});
|
|
283
|
-
test('empty array matches any array type', () => {
|
|
284
|
-
const { tag } = (0, peggy_1.parseTag)('items=[]');
|
|
285
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { items="string[]" }');
|
|
286
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
287
|
-
expect(errors).toHaveLength(0);
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
describe('any type validation', () => {
|
|
291
|
-
test('any accepts string', () => {
|
|
292
|
-
const { tag } = (0, peggy_1.parseTag)('value=hello');
|
|
293
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { value=any }');
|
|
294
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
295
|
-
expect(errors).toHaveLength(0);
|
|
296
|
-
});
|
|
297
|
-
test('any accepts number', () => {
|
|
298
|
-
const { tag } = (0, peggy_1.parseTag)('value=42');
|
|
299
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { value=any }');
|
|
300
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
301
|
-
expect(errors).toHaveLength(0);
|
|
302
|
-
});
|
|
303
|
-
test('any accepts boolean', () => {
|
|
304
|
-
const { tag } = (0, peggy_1.parseTag)('value=@true');
|
|
305
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { value=any }');
|
|
306
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
307
|
-
expect(errors).toHaveLength(0);
|
|
308
|
-
});
|
|
309
|
-
test('any accepts date', () => {
|
|
310
|
-
const { tag } = (0, peggy_1.parseTag)('value=@2024-01-15');
|
|
311
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { value=any }');
|
|
312
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
313
|
-
expect(errors).toHaveLength(0);
|
|
314
|
-
});
|
|
315
|
-
test('any accepts tag', () => {
|
|
316
|
-
const { tag } = (0, peggy_1.parseTag)('value { a=1 }');
|
|
317
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { value=any }');
|
|
318
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
319
|
-
expect(errors).toHaveLength(0);
|
|
320
|
-
});
|
|
321
|
-
test('any accepts array', () => {
|
|
322
|
-
const { tag } = (0, peggy_1.parseTag)('value=[1, 2, 3]');
|
|
323
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { value=any }');
|
|
324
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
325
|
-
expect(errors).toHaveLength(0);
|
|
326
|
-
});
|
|
327
|
-
test('any accepts flag', () => {
|
|
328
|
-
const { tag } = (0, peggy_1.parseTag)('value');
|
|
329
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { value=any }');
|
|
330
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
331
|
-
expect(errors).toHaveLength(0);
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
describe('full form type syntax', () => {
|
|
335
|
-
test('validates with type in full form', () => {
|
|
336
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice');
|
|
337
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name: { Type=string } }');
|
|
338
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
339
|
-
expect(errors).toHaveLength(0);
|
|
340
|
-
});
|
|
341
|
-
test('validates nested with full form', () => {
|
|
342
|
-
const { tag } = (0, peggy_1.parseTag)('person { name=alice age=30 }');
|
|
343
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
344
|
-
Required: {
|
|
345
|
-
person: {
|
|
346
|
-
Type=tag
|
|
347
|
-
Required: {
|
|
348
|
-
name=string
|
|
349
|
-
age=number
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
`);
|
|
354
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
355
|
-
expect(errors).toHaveLength(0);
|
|
356
|
-
});
|
|
357
|
-
test('errors on type mismatch with full form', () => {
|
|
358
|
-
const { tag } = (0, peggy_1.parseTag)('name=42');
|
|
359
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name: { Type=string } }');
|
|
360
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
361
|
-
expect(errors).toHaveLength(1);
|
|
362
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
describe('optional property type validation', () => {
|
|
366
|
-
test('validates type of optional property when present', () => {
|
|
367
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice age=notanumber');
|
|
368
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name=string } Optional: { age=number }');
|
|
369
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
370
|
-
expect(errors).toHaveLength(1);
|
|
371
|
-
expect(errors[0]).toEqual({
|
|
372
|
-
message: "Property 'age' has wrong type: expected 'number', got 'string'",
|
|
373
|
-
path: ['age'],
|
|
374
|
-
code: 'wrong-type',
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
describe('complex schemas', () => {
|
|
379
|
-
test('validates complex nested schema', () => {
|
|
380
|
-
const { tag } = (0, peggy_1.parseTag)(`
|
|
381
|
-
config {
|
|
382
|
-
database {
|
|
383
|
-
host=localhost
|
|
384
|
-
port=5432
|
|
385
|
-
}
|
|
386
|
-
features {
|
|
387
|
-
enabled=@true
|
|
388
|
-
flags=[@true, @false]
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
`);
|
|
392
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
393
|
-
Required: {
|
|
394
|
-
config: {
|
|
395
|
-
Required: {
|
|
396
|
-
database: {
|
|
397
|
-
Required: {
|
|
398
|
-
host=string
|
|
399
|
-
port=number
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
features: {
|
|
403
|
-
Required: {
|
|
404
|
-
enabled=boolean
|
|
405
|
-
}
|
|
406
|
-
Optional: {
|
|
407
|
-
flags="boolean[]"
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
`);
|
|
414
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
415
|
-
expect(errors).toHaveLength(0);
|
|
416
|
-
});
|
|
417
|
-
test('collects multiple errors from complex schema', () => {
|
|
418
|
-
const { tag } = (0, peggy_1.parseTag)(`
|
|
419
|
-
config {
|
|
420
|
-
database {
|
|
421
|
-
host=123
|
|
422
|
-
}
|
|
423
|
-
extra=bad
|
|
424
|
-
}
|
|
425
|
-
`);
|
|
426
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
427
|
-
Required: {
|
|
428
|
-
config: {
|
|
429
|
-
Required: {
|
|
430
|
-
database: {
|
|
431
|
-
Required: {
|
|
432
|
-
host=string
|
|
433
|
-
port=number
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
`);
|
|
440
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
441
|
-
// Should have: wrong type for host, missing port, unknown extra
|
|
442
|
-
expect(errors.length).toBeGreaterThanOrEqual(3);
|
|
443
|
-
expect(errors.map(e => e.code)).toContain('wrong-type');
|
|
444
|
-
expect(errors.map(e => e.code)).toContain('missing-required');
|
|
445
|
-
expect(errors.map(e => e.code)).toContain('unknown-property');
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
describe('edge cases', () => {
|
|
449
|
-
test('empty tag against schema with only optional properties', () => {
|
|
450
|
-
const { tag } = (0, peggy_1.parseTag)('');
|
|
451
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Optional: { name=string }');
|
|
452
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
453
|
-
expect(errors).toHaveLength(0);
|
|
454
|
-
});
|
|
455
|
-
test('empty schema passes any tag', () => {
|
|
456
|
-
const { tag } = (0, peggy_1.parseTag)('anything=goes here=too');
|
|
457
|
-
const { tag: schema } = (0, peggy_1.parseTag)('');
|
|
458
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
459
|
-
// Empty schema = no rules = all lawful
|
|
460
|
-
expect(errors).toHaveLength(0);
|
|
461
|
-
});
|
|
462
|
-
test('empty schema with Additional passes any tag', () => {
|
|
463
|
-
const { tag } = (0, peggy_1.parseTag)('anything=goes here=too');
|
|
464
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Additional');
|
|
465
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
466
|
-
expect(errors).toHaveLength(0);
|
|
467
|
-
});
|
|
468
|
-
test('property with no type in schema allows any value', () => {
|
|
469
|
-
const { tag } = (0, peggy_1.parseTag)('name=42');
|
|
470
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name }');
|
|
471
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
472
|
-
// No type specified, so any value is OK
|
|
473
|
-
expect(errors).toHaveLength(0);
|
|
474
|
-
});
|
|
475
|
-
test('mixed array reports as mixed type', () => {
|
|
476
|
-
const { tag } = (0, peggy_1.parseTag)('items=[1, hello, @true]');
|
|
477
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { items="string[]" }');
|
|
478
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
479
|
-
expect(errors).toHaveLength(1);
|
|
480
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
481
|
-
expect(errors[0].message).toContain("got 'mixed[]'");
|
|
482
|
-
});
|
|
483
|
-
test('any[] accepts mixed arrays', () => {
|
|
484
|
-
const { tag } = (0, peggy_1.parseTag)('items=[1, hello, @true]');
|
|
485
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { items="any[]" }');
|
|
486
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
487
|
-
expect(errors).toHaveLength(0);
|
|
488
|
-
});
|
|
489
|
-
test('value with properties validates only value type', () => {
|
|
490
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice { extra=1 }');
|
|
491
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name=string }');
|
|
492
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
493
|
-
// Only value type is checked; nested properties aren't validated
|
|
494
|
-
// unless schema has required/optional sections
|
|
495
|
-
expect(errors).toHaveLength(0);
|
|
496
|
-
});
|
|
497
|
-
test('value with properties and nested schema (full form)', () => {
|
|
498
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice { extra=1 }');
|
|
499
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
500
|
-
Required: {
|
|
501
|
-
name: {
|
|
502
|
-
Type=string
|
|
503
|
-
Optional: { extra=number }
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
`);
|
|
507
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
508
|
-
expect(errors).toHaveLength(0);
|
|
509
|
-
});
|
|
510
|
-
test('value with properties and nested schema (shorthand)', () => {
|
|
511
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice { extra=1 }');
|
|
512
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
513
|
-
Required: {
|
|
514
|
-
name=string { Optional: { extra=number } }
|
|
515
|
-
}
|
|
516
|
-
`);
|
|
517
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
518
|
-
expect(errors).toHaveLength(0);
|
|
519
|
-
});
|
|
520
|
-
test('arrays of objects have tag element type', () => {
|
|
521
|
-
const { tag } = (0, peggy_1.parseTag)('items=[{a=1}, {b=2}]');
|
|
522
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { items="string[]" }');
|
|
523
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
524
|
-
expect(errors).toHaveLength(1);
|
|
525
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
526
|
-
expect(errors[0].message).toContain("got 'tag[]'");
|
|
527
|
-
});
|
|
528
|
-
test('tag[] validates arrays of objects', () => {
|
|
529
|
-
const { tag } = (0, peggy_1.parseTag)('items=[{a=1}, {b=2}]');
|
|
530
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { items="tag[]" }');
|
|
531
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
532
|
-
expect(errors).toHaveLength(0);
|
|
533
|
-
});
|
|
534
|
-
test('tag[] rejects arrays of scalars', () => {
|
|
535
|
-
const { tag } = (0, peggy_1.parseTag)('items=[1, 2, 3]');
|
|
536
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { items="tag[]" }');
|
|
537
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
538
|
-
expect(errors).toHaveLength(1);
|
|
539
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
540
|
-
expect(errors[0].message).toContain("expected 'tag[]'");
|
|
541
|
-
});
|
|
542
|
-
test('tag[] with element schema validates each element', () => {
|
|
543
|
-
const { tag } = (0, peggy_1.parseTag)('items=[{size=10 color=red}, {size=20 color=blue}]');
|
|
544
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { items="tag[]" { Required: { size=number color=string } } }');
|
|
545
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
546
|
-
expect(errors).toHaveLength(0);
|
|
547
|
-
});
|
|
548
|
-
test('tag[] element schema reports errors with index in path', () => {
|
|
549
|
-
const { tag } = (0, peggy_1.parseTag)('items=[{size=10 color=red}, {size=bad color=blue}]');
|
|
550
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { items="tag[]" { Required: { size=number color=string } } }');
|
|
551
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
552
|
-
expect(errors).toHaveLength(1);
|
|
553
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
554
|
-
expect(errors[0].path).toEqual(['items', '1', 'size']);
|
|
555
|
-
});
|
|
556
|
-
test('tag[] element schema catches missing required', () => {
|
|
557
|
-
const { tag } = (0, peggy_1.parseTag)('items=[{size=10}, {color=blue}]');
|
|
558
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { items="tag[]" { Required: { size=number color=string } } }');
|
|
559
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
560
|
-
expect(errors).toHaveLength(2);
|
|
561
|
-
expect(errors[0].path).toEqual(['items', '0', 'color']);
|
|
562
|
-
expect(errors[1].path).toEqual(['items', '1', 'size']);
|
|
563
|
-
});
|
|
564
|
-
test('tag[] element schema catches unknown properties', () => {
|
|
565
|
-
const { tag } = (0, peggy_1.parseTag)('items=[{size=10 color=red extra=bad}]');
|
|
566
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { items="tag[]" { Required: { size=number color=string } } }');
|
|
567
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
568
|
-
expect(errors).toHaveLength(1);
|
|
569
|
-
expect(errors[0].code).toBe('unknown-property');
|
|
570
|
-
expect(errors[0].path).toEqual(['items', '0', 'extra']);
|
|
571
|
-
});
|
|
572
|
-
test('misspelled type in schema reports invalid-schema error', () => {
|
|
573
|
-
const { tag } = (0, peggy_1.parseTag)('name=42');
|
|
574
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name=stirng }'); // typo
|
|
575
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
576
|
-
expect(errors).toHaveLength(1);
|
|
577
|
-
expect(errors[0].code).toBe('invalid-schema');
|
|
578
|
-
expect(errors[0].message).toContain("Invalid type 'stirng'");
|
|
579
|
-
});
|
|
580
|
-
test('invalid type in full form reports invalid-schema error', () => {
|
|
581
|
-
const { tag } = (0, peggy_1.parseTag)('name=42');
|
|
582
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Required: { name: { Type=nubmer } }'); // typo
|
|
583
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
584
|
-
expect(errors).toHaveLength(1);
|
|
585
|
-
expect(errors[0].code).toBe('invalid-schema');
|
|
586
|
-
expect(errors[0].message).toContain("Invalid type 'nubmer'");
|
|
587
|
-
});
|
|
588
|
-
});
|
|
589
|
-
describe('custom types', () => {
|
|
590
|
-
test('validates using custom type reference', () => {
|
|
591
|
-
const { tag } = (0, peggy_1.parseTag)('person { name=alice age=30 }');
|
|
592
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
593
|
-
Types: {
|
|
594
|
-
personType: {
|
|
595
|
-
Required: { name=string age=number }
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
Required: { person=personType }
|
|
599
|
-
`);
|
|
600
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
601
|
-
expect(errors).toHaveLength(0);
|
|
602
|
-
});
|
|
603
|
-
test('reports error when custom type validation fails', () => {
|
|
604
|
-
const { tag } = (0, peggy_1.parseTag)('person { name=alice }');
|
|
605
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
606
|
-
Types: {
|
|
607
|
-
personType: {
|
|
608
|
-
Required: { name=string age=number }
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
Required: { person=personType }
|
|
612
|
-
`);
|
|
613
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
614
|
-
expect(errors).toHaveLength(1);
|
|
615
|
-
expect(errors[0].code).toBe('missing-required');
|
|
616
|
-
expect(errors[0].path).toEqual(['person', 'age']);
|
|
617
|
-
});
|
|
618
|
-
test('validates array of custom type', () => {
|
|
619
|
-
const { tag } = (0, peggy_1.parseTag)('people=[{name=alice age=30}, {name=bob age=25}]');
|
|
620
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Types: { personType: { Required: { name=string age=number } } } Required: { people="personType[]" }');
|
|
621
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
622
|
-
expect(errors).toHaveLength(0);
|
|
623
|
-
});
|
|
624
|
-
test('reports error in array of custom type with index', () => {
|
|
625
|
-
const { tag } = (0, peggy_1.parseTag)('people=[{name=alice age=30}, {name=bob}]');
|
|
626
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Types: { personType: { Required: { name=string age=number } } } Required: { people="personType[]" }');
|
|
627
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
628
|
-
expect(errors).toHaveLength(1);
|
|
629
|
-
expect(errors[0].code).toBe('missing-required');
|
|
630
|
-
expect(errors[0].path).toEqual(['people', '1', 'age']);
|
|
631
|
-
});
|
|
632
|
-
test('custom type array rejects non-array', () => {
|
|
633
|
-
const { tag } = (0, peggy_1.parseTag)('person { name=alice age=30 }');
|
|
634
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Types: { personType: { Required: { name=string age=number } } } Required: { person="personType[]" }');
|
|
635
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
636
|
-
expect(errors).toHaveLength(1);
|
|
637
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
638
|
-
expect(errors[0].message).toContain("expected 'personType[]'");
|
|
639
|
-
});
|
|
640
|
-
test('recursive type validates nested structure', () => {
|
|
641
|
-
const { tag } = (0, peggy_1.parseTag)('node { value=1 children=[{ value=2 }, { value=3 children=[{ value=4 }] }] }');
|
|
642
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Types: { treeNode: { Required: { value=number } Optional: { children="treeNode[]" } } } Required: { node=treeNode }');
|
|
643
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
644
|
-
expect(errors).toHaveLength(0);
|
|
645
|
-
});
|
|
646
|
-
test('recursive type catches deep errors', () => {
|
|
647
|
-
const { tag } = (0, peggy_1.parseTag)('node { value=1 children=[{ value=2 }, { value=bad }] }');
|
|
648
|
-
const { tag: schema } = (0, peggy_1.parseTag)('Types: { treeNode: { Required: { value=number } Optional: { children="treeNode[]" } } } Required: { node=treeNode }');
|
|
649
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
650
|
-
expect(errors).toHaveLength(1);
|
|
651
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
652
|
-
expect(errors[0].path).toEqual(['node', 'children', '1', 'value']);
|
|
653
|
-
});
|
|
654
|
-
test('multiple custom types in same schema', () => {
|
|
655
|
-
const { tag } = (0, peggy_1.parseTag)(`
|
|
656
|
-
user { name=alice }
|
|
657
|
-
address { city=boston }
|
|
658
|
-
`);
|
|
659
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
660
|
-
Types: {
|
|
661
|
-
userType: { Required: { name=string } }
|
|
662
|
-
addressType: { Required: { city=string } }
|
|
663
|
-
}
|
|
664
|
-
Required: {
|
|
665
|
-
user=userType
|
|
666
|
-
address=addressType
|
|
667
|
-
}
|
|
668
|
-
`);
|
|
669
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
670
|
-
expect(errors).toHaveLength(0);
|
|
671
|
-
});
|
|
672
|
-
test('custom type with type constraint on value', () => {
|
|
673
|
-
const { tag } = (0, peggy_1.parseTag)('config=settings { debug=@true }');
|
|
674
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
675
|
-
Types: {
|
|
676
|
-
configType: {
|
|
677
|
-
Type=string
|
|
678
|
-
Optional: { debug=boolean }
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
Required: { config=configType }
|
|
682
|
-
`);
|
|
683
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
684
|
-
expect(errors).toHaveLength(0);
|
|
685
|
-
});
|
|
686
|
-
test('meta-schema validates itself', () => {
|
|
687
|
-
// The meta-schema loaded from motly-schema.motly should validate against itself
|
|
688
|
-
const errors = (0, schema_1.validateTag)(metaSchema, metaSchema);
|
|
689
|
-
expect(errors).toHaveLength(0);
|
|
690
|
-
});
|
|
691
|
-
test('meta-schema validates a simple schema', () => {
|
|
692
|
-
const { tag: simpleSchema } = (0, peggy_1.parseTag)(`
|
|
693
|
-
Required: { name=string age=number }
|
|
694
|
-
Optional: { email=string }
|
|
695
|
-
`);
|
|
696
|
-
const errors = (0, schema_1.validateTag)(simpleSchema, metaSchema);
|
|
697
|
-
expect(errors).toHaveLength(0);
|
|
698
|
-
});
|
|
699
|
-
test('meta-schema validates schema with custom types', () => {
|
|
700
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
701
|
-
Types: {
|
|
702
|
-
PersonType: {
|
|
703
|
-
Required: { name=string }
|
|
704
|
-
Optional: { age=number }
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
Required: { person=PersonType }
|
|
708
|
-
`);
|
|
709
|
-
const errors = (0, schema_1.validateTag)(schema, metaSchema);
|
|
710
|
-
expect(errors).toHaveLength(0);
|
|
711
|
-
});
|
|
712
|
-
});
|
|
713
|
-
describe('enum types', () => {
|
|
714
|
-
test('validates string enum', () => {
|
|
715
|
-
const { tag } = (0, peggy_1.parseTag)('status=active');
|
|
716
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
717
|
-
Types: { statusType = [pending, active, completed] }
|
|
718
|
-
Required: { status=statusType }
|
|
719
|
-
`);
|
|
720
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
721
|
-
expect(errors).toHaveLength(0);
|
|
722
|
-
});
|
|
723
|
-
test('rejects invalid enum value', () => {
|
|
724
|
-
const { tag } = (0, peggy_1.parseTag)('status=unknown');
|
|
725
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
726
|
-
Types: { statusType = [pending, active, completed] }
|
|
727
|
-
Required: { status=statusType }
|
|
728
|
-
`);
|
|
729
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
730
|
-
expect(errors).toHaveLength(1);
|
|
731
|
-
expect(errors[0].code).toBe('invalid-enum-value');
|
|
732
|
-
expect(errors[0].message).toContain('unknown');
|
|
733
|
-
expect(errors[0].message).toContain('pending, active, completed');
|
|
734
|
-
});
|
|
735
|
-
test('validates numeric enum', () => {
|
|
736
|
-
const { tag } = (0, peggy_1.parseTag)('level=2');
|
|
737
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
738
|
-
Types: { levelType = [1, 2, 3] }
|
|
739
|
-
Required: { level=levelType }
|
|
740
|
-
`);
|
|
741
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
742
|
-
expect(errors).toHaveLength(0);
|
|
743
|
-
});
|
|
744
|
-
test('rejects invalid numeric enum value', () => {
|
|
745
|
-
const { tag } = (0, peggy_1.parseTag)('level=5');
|
|
746
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
747
|
-
Types: { levelType = [1, 2, 3] }
|
|
748
|
-
Required: { level=levelType }
|
|
749
|
-
`);
|
|
750
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
751
|
-
expect(errors).toHaveLength(1);
|
|
752
|
-
expect(errors[0].code).toBe('invalid-enum-value');
|
|
753
|
-
});
|
|
754
|
-
test('validates array of enum type', () => {
|
|
755
|
-
const { tag } = (0, peggy_1.parseTag)('statuses=[active, pending]');
|
|
756
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
757
|
-
Types: { statusType = [pending, active, completed] }
|
|
758
|
-
Required: { statuses="statusType[]" }
|
|
759
|
-
`);
|
|
760
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
761
|
-
expect(errors).toHaveLength(0);
|
|
762
|
-
});
|
|
763
|
-
test('rejects invalid value in enum array', () => {
|
|
764
|
-
const { tag } = (0, peggy_1.parseTag)('statuses=[active, invalid]');
|
|
765
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
766
|
-
Types: { statusType = [pending, active, completed] }
|
|
767
|
-
Required: { statuses="statusType[]" }
|
|
768
|
-
`);
|
|
769
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
770
|
-
expect(errors).toHaveLength(1);
|
|
771
|
-
expect(errors[0].code).toBe('invalid-enum-value');
|
|
772
|
-
expect(errors[0].path).toEqual(['statuses', '1']);
|
|
773
|
-
});
|
|
774
|
-
test('rejects empty enum type', () => {
|
|
775
|
-
const { tag } = (0, peggy_1.parseTag)('status=active');
|
|
776
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
777
|
-
Types: { statusType = [] }
|
|
778
|
-
Required: { status=statusType }
|
|
779
|
-
`);
|
|
780
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
781
|
-
expect(errors).toHaveLength(1);
|
|
782
|
-
expect(errors[0].code).toBe('invalid-schema');
|
|
783
|
-
expect(errors[0].message).toContain('no values');
|
|
784
|
-
});
|
|
785
|
-
test('rejects mixed type enum', () => {
|
|
786
|
-
const { tag } = (0, peggy_1.parseTag)('value=1');
|
|
787
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
788
|
-
Types: { mixedType = [1, two, 3] }
|
|
789
|
-
Required: { value=mixedType }
|
|
790
|
-
`);
|
|
791
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
792
|
-
expect(errors).toHaveLength(1);
|
|
793
|
-
expect(errors[0].code).toBe('invalid-schema');
|
|
794
|
-
expect(errors[0].message).toContain('mixed types');
|
|
795
|
-
});
|
|
796
|
-
});
|
|
797
|
-
describe('pattern types', () => {
|
|
798
|
-
test('validates string matching pattern', () => {
|
|
799
|
-
const { tag } = (0, peggy_1.parseTag)('email="test@example.com"');
|
|
800
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
801
|
-
Types: { emailType.matches = "^[^@]+@[^@]+$" }
|
|
802
|
-
Required: { email=emailType }
|
|
803
|
-
`);
|
|
804
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
805
|
-
expect(errors).toHaveLength(0);
|
|
806
|
-
});
|
|
807
|
-
test('rejects string not matching pattern', () => {
|
|
808
|
-
const { tag } = (0, peggy_1.parseTag)('email="not-an-email"');
|
|
809
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
810
|
-
Types: { emailType.matches = "^[^@]+@[^@]+$" }
|
|
811
|
-
Required: { email=emailType }
|
|
812
|
-
`);
|
|
813
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
814
|
-
expect(errors).toHaveLength(1);
|
|
815
|
-
expect(errors[0].code).toBe('pattern-mismatch');
|
|
816
|
-
});
|
|
817
|
-
test('rejects non-string for pattern type', () => {
|
|
818
|
-
const { tag } = (0, peggy_1.parseTag)('email=123');
|
|
819
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
820
|
-
Types: { emailType.matches = ".*" }
|
|
821
|
-
Required: { email=emailType }
|
|
822
|
-
`);
|
|
823
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
824
|
-
expect(errors).toHaveLength(1);
|
|
825
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
826
|
-
});
|
|
827
|
-
test('validates array of pattern type', () => {
|
|
828
|
-
const { tag } = (0, peggy_1.parseTag)('emails=["a@b.com", "c@d.org"]');
|
|
829
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
830
|
-
Types: { emailType.matches = "^[^@]+@[^@]+$" }
|
|
831
|
-
Required: { emails="emailType[]" }
|
|
832
|
-
`);
|
|
833
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
834
|
-
expect(errors).toHaveLength(0);
|
|
835
|
-
});
|
|
836
|
-
test('rejects invalid value in pattern array', () => {
|
|
837
|
-
const { tag } = (0, peggy_1.parseTag)('emails=["a@b.com", "invalid"]');
|
|
838
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
839
|
-
Types: { emailType.matches = "^[^@]+@[^@]+$" }
|
|
840
|
-
Required: { emails="emailType[]" }
|
|
841
|
-
`);
|
|
842
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
843
|
-
expect(errors).toHaveLength(1);
|
|
844
|
-
expect(errors[0].code).toBe('pattern-mismatch');
|
|
845
|
-
expect(errors[0].path).toEqual(['emails', '1']);
|
|
846
|
-
});
|
|
847
|
-
test('reports error for invalid regex in schema', () => {
|
|
848
|
-
const { tag } = (0, peggy_1.parseTag)('value=test');
|
|
849
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
850
|
-
Types: { badPattern.matches = "[invalid" }
|
|
851
|
-
Required: { value=badPattern }
|
|
852
|
-
`);
|
|
853
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
854
|
-
expect(errors).toHaveLength(1);
|
|
855
|
-
expect(errors[0].code).toBe('invalid-schema');
|
|
856
|
-
});
|
|
857
|
-
});
|
|
858
|
-
describe('oneOf union types', () => {
|
|
859
|
-
test('validates value matching first type in oneOf', () => {
|
|
860
|
-
const { tag } = (0, peggy_1.parseTag)('value=hello');
|
|
861
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
862
|
-
Types: { StringOrNumber.oneOf = [string, number] }
|
|
863
|
-
Required: { value=StringOrNumber }
|
|
864
|
-
`);
|
|
865
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
866
|
-
expect(errors).toHaveLength(0);
|
|
867
|
-
});
|
|
868
|
-
test('validates value matching second type in oneOf', () => {
|
|
869
|
-
const { tag } = (0, peggy_1.parseTag)('value=42');
|
|
870
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
871
|
-
Types: { StringOrNumber.oneOf = [string, number] }
|
|
872
|
-
Required: { value=StringOrNumber }
|
|
873
|
-
`);
|
|
874
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
875
|
-
expect(errors).toHaveLength(0);
|
|
876
|
-
});
|
|
877
|
-
test('rejects value not matching any type in oneOf', () => {
|
|
878
|
-
const { tag } = (0, peggy_1.parseTag)('value=@true');
|
|
879
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
880
|
-
Types: { StringOrNumber.oneOf = [string, number] }
|
|
881
|
-
Required: { value=StringOrNumber }
|
|
882
|
-
`);
|
|
883
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
884
|
-
expect(errors).toHaveLength(1);
|
|
885
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
886
|
-
expect(errors[0].message).toContain('oneOf');
|
|
887
|
-
});
|
|
888
|
-
test('oneOf with custom structural types', () => {
|
|
889
|
-
const { tag } = (0, peggy_1.parseTag)('item { name=test }');
|
|
890
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
891
|
-
Types: {
|
|
892
|
-
TypeA: { Required: { name=string } }
|
|
893
|
-
TypeB: { Required: { count=number } }
|
|
894
|
-
AorB.oneOf = [TypeA, TypeB]
|
|
895
|
-
}
|
|
896
|
-
Required: { item=AorB }
|
|
897
|
-
`);
|
|
898
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
899
|
-
expect(errors).toHaveLength(0);
|
|
900
|
-
});
|
|
901
|
-
test('oneOf with enum type', () => {
|
|
902
|
-
const { tag } = (0, peggy_1.parseTag)('value=active');
|
|
903
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
904
|
-
Types: {
|
|
905
|
-
StatusEnum = [pending, active, completed]
|
|
906
|
-
StringOrStatus.oneOf = [number, StatusEnum]
|
|
907
|
-
}
|
|
908
|
-
Required: { value=StringOrStatus }
|
|
909
|
-
`);
|
|
910
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
911
|
-
expect(errors).toHaveLength(0);
|
|
912
|
-
});
|
|
913
|
-
test('array of oneOf type', () => {
|
|
914
|
-
const { tag } = (0, peggy_1.parseTag)('values=[hello, 42, world]');
|
|
915
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
916
|
-
Types: { StringOrNumber.oneOf = [string, number] }
|
|
917
|
-
Required: { values="StringOrNumber[]" }
|
|
918
|
-
`);
|
|
919
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
920
|
-
expect(errors).toHaveLength(0);
|
|
921
|
-
});
|
|
922
|
-
});
|
|
923
|
-
describe('typed Additional', () => {
|
|
924
|
-
test('Additional with type validates unknown properties', () => {
|
|
925
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice extra1=hello extra2=world');
|
|
926
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
927
|
-
Required: { name=string }
|
|
928
|
-
Additional=string
|
|
929
|
-
`);
|
|
930
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
931
|
-
expect(errors).toHaveLength(0);
|
|
932
|
-
});
|
|
933
|
-
test('Additional with type rejects invalid unknown properties', () => {
|
|
934
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice extra=42');
|
|
935
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
936
|
-
Required: { name=string }
|
|
937
|
-
Additional=string
|
|
938
|
-
`);
|
|
939
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
940
|
-
expect(errors).toHaveLength(1);
|
|
941
|
-
expect(errors[0].code).toBe('wrong-type');
|
|
942
|
-
expect(errors[0].path).toEqual(['extra']);
|
|
943
|
-
});
|
|
944
|
-
test('Additional with custom type', () => {
|
|
945
|
-
const { tag } = (0, peggy_1.parseTag)('name=test prop1 { x=1 } prop2 { x=2 }');
|
|
946
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
947
|
-
Types: {
|
|
948
|
-
PropType: { Required: { x=number } }
|
|
949
|
-
}
|
|
950
|
-
Required: { name=string }
|
|
951
|
-
Additional=PropType
|
|
952
|
-
`);
|
|
953
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
954
|
-
expect(errors).toHaveLength(0);
|
|
955
|
-
});
|
|
956
|
-
test('Additional with custom type catches errors', () => {
|
|
957
|
-
const { tag } = (0, peggy_1.parseTag)('name=test prop1 { x=bad }');
|
|
958
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
959
|
-
Types: {
|
|
960
|
-
PropType: { Required: { x=number } }
|
|
961
|
-
}
|
|
962
|
-
Required: { name=string }
|
|
963
|
-
Additional=PropType
|
|
964
|
-
`);
|
|
965
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
966
|
-
expect(errors).toHaveLength(1);
|
|
967
|
-
expect(errors[0].path).toEqual(['prop1', 'x']);
|
|
968
|
-
});
|
|
969
|
-
test('Additional flag is same as Additional=any', () => {
|
|
970
|
-
const { tag } = (0, peggy_1.parseTag)('name=alice anything=goes here=too');
|
|
971
|
-
const { tag: schema } = (0, peggy_1.parseTag)(`
|
|
972
|
-
Required: { name=string }
|
|
973
|
-
Additional
|
|
974
|
-
`);
|
|
975
|
-
const errors = (0, schema_1.validateTag)(tag, schema);
|
|
976
|
-
expect(errors).toHaveLength(0);
|
|
977
|
-
});
|
|
978
|
-
});
|
|
979
|
-
});
|
|
980
|
-
//# sourceMappingURL=schema.spec.js.map
|