@atproto/lexicon 0.0.4 → 0.2.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.
- package/dist/blob-refs.d.ts +67 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12944 -680
- package/dist/index.js.map +4 -4
- package/dist/lexicons.d.ts +6 -4
- package/dist/serialize.d.ts +12 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/lexicons.d.ts +15 -0
- package/dist/src/record/index.d.ts +4 -0
- package/dist/src/record/schema.d.ts +9 -0
- package/dist/src/record/schemas.d.ts +10 -0
- package/dist/src/record/util.d.ts +1 -0
- package/dist/src/record/validation.d.ts +24 -0
- package/dist/src/record/validator.d.ts +17 -0
- package/dist/src/types.d.ts +30268 -0
- package/dist/src/util.d.ts +6 -0
- package/dist/src/validation.d.ts +6 -0
- package/dist/src/validators/blob.d.ts +6 -0
- package/dist/src/validators/complex.d.ts +5 -0
- package/dist/src/validators/primitives.d.ts +9 -0
- package/dist/src/validators/xrpc.d.ts +3 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/types.d.ts +14999 -23510
- package/dist/util.d.ts +6 -1
- package/dist/validation.d.ts +6 -5
- package/dist/validators/blob.d.ts +0 -3
- package/dist/validators/complex.d.ts +2 -2
- package/dist/validators/formats.d.ts +10 -0
- package/dist/validators/primitives.d.ts +2 -2
- package/dist/validators/xrpc.d.ts +1 -1
- package/package.json +13 -4
- package/src/blob-refs.ts +70 -0
- package/src/index.ts +2 -0
- package/src/lexicons.ts +36 -5
- package/src/serialize.ts +93 -0
- package/src/types.ts +306 -180
- package/src/util.ts +64 -5
- package/src/validation.ts +28 -4
- package/src/validators/blob.ts +5 -43
- package/src/validators/complex.ts +31 -32
- package/src/validators/formats.ts +121 -0
- package/src/validators/primitives.ts +114 -67
- package/src/validators/xrpc.ts +29 -29
- package/tests/_scaffolds/lexicons.ts +178 -51
- package/tests/general.test.ts +496 -177
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.json +3 -1
package/tests/general.test.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import { lexiconDoc, Lexicons } from '../src/index'
|
|
3
|
+
import { object } from '../src/validators/complex'
|
|
2
4
|
import LexiconDocs from './_scaffolds/lexicons'
|
|
3
5
|
|
|
4
6
|
describe('Lexicons collection', () => {
|
|
@@ -40,22 +42,28 @@ describe('General validation', () => {
|
|
|
40
42
|
object: { boolean: true },
|
|
41
43
|
array: ['one', 'two'],
|
|
42
44
|
boolean: true,
|
|
43
|
-
number: 123.45,
|
|
44
45
|
integer: 123,
|
|
45
46
|
string: 'string',
|
|
46
47
|
},
|
|
47
48
|
array: ['one', 'two'],
|
|
48
49
|
boolean: true,
|
|
49
|
-
number: 123.45,
|
|
50
50
|
integer: 123,
|
|
51
51
|
string: 'string',
|
|
52
52
|
datetime: new Date().toISOString(),
|
|
53
|
+
atUri: 'at://did:web:example.com/com.example.test/self',
|
|
54
|
+
did: 'did:web:example.com',
|
|
55
|
+
cid: 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
56
|
+
bytes: new Uint8Array([0, 1, 2, 3]),
|
|
57
|
+
cidLink: CID.parse(
|
|
58
|
+
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
59
|
+
),
|
|
53
60
|
})
|
|
54
61
|
expect(res.success).toBe(true)
|
|
55
62
|
}
|
|
56
63
|
{
|
|
57
64
|
const res = lex.validate('com.example.kitchenSink', {})
|
|
58
65
|
expect(res.success).toBe(false)
|
|
66
|
+
if (res.success) throw new Error('Asserted')
|
|
59
67
|
expect(res.error?.message).toBe('Record must have the property "object"')
|
|
60
68
|
}
|
|
61
69
|
})
|
|
@@ -65,7 +73,6 @@ describe('General validation', () => {
|
|
|
65
73
|
object: { boolean: true },
|
|
66
74
|
array: ['one', 'two'],
|
|
67
75
|
boolean: true,
|
|
68
|
-
number: 123.45,
|
|
69
76
|
integer: 123,
|
|
70
77
|
string: 'string',
|
|
71
78
|
})
|
|
@@ -74,32 +81,206 @@ describe('General validation', () => {
|
|
|
74
81
|
{
|
|
75
82
|
const res = lex.validate('com.example.kitchenSink#object', {})
|
|
76
83
|
expect(res.success).toBe(false)
|
|
84
|
+
if (res.success) throw new Error('Asserted')
|
|
77
85
|
expect(res.error?.message).toBe('Object must have the property "object"')
|
|
78
86
|
}
|
|
79
87
|
})
|
|
88
|
+
it('fails when a required property is missing', () => {
|
|
89
|
+
const schema = {
|
|
90
|
+
lexicon: 1,
|
|
91
|
+
id: 'com.example.kitchenSink',
|
|
92
|
+
defs: {
|
|
93
|
+
test: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
required: ['foo'],
|
|
96
|
+
properties: {},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
expect(() => {
|
|
101
|
+
lexiconDoc.parse(schema)
|
|
102
|
+
}).toThrow('Required field \\"foo\\" not defined')
|
|
103
|
+
})
|
|
104
|
+
it('fails when unknown fields are present', () => {
|
|
105
|
+
const schema = {
|
|
106
|
+
lexicon: 1,
|
|
107
|
+
id: 'com.example.unknownFields',
|
|
108
|
+
defs: {
|
|
109
|
+
test: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
foo: 3,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
expect(() => {
|
|
117
|
+
lexiconDoc.parse(schema)
|
|
118
|
+
}).toThrow("Unrecognized key(s) in object: 'foo'")
|
|
119
|
+
})
|
|
120
|
+
it('fails lexicon parsing when uri is invalid', () => {
|
|
121
|
+
const schema = {
|
|
122
|
+
lexicon: 1,
|
|
123
|
+
id: 'com.example.invalidUri',
|
|
124
|
+
defs: {
|
|
125
|
+
main: {
|
|
126
|
+
type: 'object',
|
|
127
|
+
properties: {
|
|
128
|
+
test: { type: 'ref', ref: 'com.example.invalid#test#test' },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
expect(() => {
|
|
135
|
+
new Lexicons([schema])
|
|
136
|
+
}).toThrow('Uri can only have one hash segment')
|
|
137
|
+
})
|
|
138
|
+
it('fails validation when ref uri has multiple hash segments', () => {
|
|
139
|
+
const schema = {
|
|
140
|
+
lexicon: 1,
|
|
141
|
+
id: 'com.example.invalidUri',
|
|
142
|
+
defs: {
|
|
143
|
+
main: {
|
|
144
|
+
type: 'object',
|
|
145
|
+
properties: {
|
|
146
|
+
test: { type: 'integer' },
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
object: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
required: ['test'],
|
|
152
|
+
properties: {
|
|
153
|
+
test: {
|
|
154
|
+
type: 'union',
|
|
155
|
+
refs: ['com.example.invalidUri'],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
const lexicons = new Lexicons([schema])
|
|
162
|
+
expect(() => {
|
|
163
|
+
lexicons.validate('com.example.invalidUri#object', {
|
|
164
|
+
test: {
|
|
165
|
+
$type: 'com.example.invalidUri#main#main',
|
|
166
|
+
test: 123,
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
}).toThrow('Uri can only have one hash segment')
|
|
170
|
+
})
|
|
171
|
+
it('union handles both implicit and explicit #main', () => {
|
|
172
|
+
const schemas = [
|
|
173
|
+
{
|
|
174
|
+
lexicon: 1,
|
|
175
|
+
id: 'com.example.implicitMain',
|
|
176
|
+
defs: {
|
|
177
|
+
main: {
|
|
178
|
+
type: 'object',
|
|
179
|
+
required: ['test'],
|
|
180
|
+
properties: {
|
|
181
|
+
test: { type: 'string' },
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
lexicon: 1,
|
|
188
|
+
id: 'com.example.testImplicitMain',
|
|
189
|
+
defs: {
|
|
190
|
+
main: {
|
|
191
|
+
type: 'object',
|
|
192
|
+
required: ['union'],
|
|
193
|
+
properties: {
|
|
194
|
+
union: {
|
|
195
|
+
type: 'union',
|
|
196
|
+
refs: ['com.example.implicitMain'],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
lexicon: 1,
|
|
204
|
+
id: 'com.example.testExplicitMain',
|
|
205
|
+
defs: {
|
|
206
|
+
main: {
|
|
207
|
+
type: 'object',
|
|
208
|
+
required: ['union'],
|
|
209
|
+
properties: {
|
|
210
|
+
union: {
|
|
211
|
+
type: 'union',
|
|
212
|
+
refs: ['com.example.implicitMain#main'],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
const lexicon = new Lexicons(schemas)
|
|
221
|
+
|
|
222
|
+
let result = lexicon.validate('com.example.testImplicitMain', {
|
|
223
|
+
union: {
|
|
224
|
+
$type: 'com.example.implicitMain',
|
|
225
|
+
test: 123,
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
expect(result.success).toBeFalsy()
|
|
229
|
+
expect(result['error']?.message).toBe('Object/union/test must be a string')
|
|
230
|
+
|
|
231
|
+
result = lexicon.validate('com.example.testImplicitMain', {
|
|
232
|
+
union: {
|
|
233
|
+
$type: 'com.example.implicitMain#main',
|
|
234
|
+
test: 123,
|
|
235
|
+
},
|
|
236
|
+
})
|
|
237
|
+
expect(result.success).toBeFalsy()
|
|
238
|
+
expect(result['error']?.message).toBe('Object/union/test must be a string')
|
|
239
|
+
|
|
240
|
+
result = lexicon.validate('com.example.testExplicitMain', {
|
|
241
|
+
union: {
|
|
242
|
+
$type: 'com.example.implicitMain',
|
|
243
|
+
test: 123,
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
expect(result.success).toBeFalsy()
|
|
247
|
+
expect(result['error']?.message).toBe('Object/union/test must be a string')
|
|
248
|
+
|
|
249
|
+
result = lexicon.validate('com.example.testExplicitMain', {
|
|
250
|
+
union: {
|
|
251
|
+
$type: 'com.example.implicitMain#main',
|
|
252
|
+
test: 123,
|
|
253
|
+
},
|
|
254
|
+
})
|
|
255
|
+
expect(result.success).toBeFalsy()
|
|
256
|
+
expect(result['error']?.message).toBe('Object/union/test must be a string')
|
|
257
|
+
})
|
|
80
258
|
})
|
|
81
259
|
|
|
82
260
|
describe('Record validation', () => {
|
|
83
261
|
const lex = new Lexicons(LexiconDocs)
|
|
84
262
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
object: {
|
|
89
|
-
object: { boolean: true },
|
|
90
|
-
array: ['one', 'two'],
|
|
91
|
-
boolean: true,
|
|
92
|
-
number: 123.45,
|
|
93
|
-
integer: 123,
|
|
94
|
-
string: 'string',
|
|
95
|
-
},
|
|
263
|
+
const passingSink = {
|
|
264
|
+
$type: 'com.example.kitchenSink',
|
|
265
|
+
object: {
|
|
266
|
+
object: { boolean: true },
|
|
96
267
|
array: ['one', 'two'],
|
|
97
268
|
boolean: true,
|
|
98
|
-
number: 123.45,
|
|
99
269
|
integer: 123,
|
|
100
270
|
string: 'string',
|
|
101
|
-
|
|
102
|
-
|
|
271
|
+
},
|
|
272
|
+
array: ['one', 'two'],
|
|
273
|
+
boolean: true,
|
|
274
|
+
integer: 123,
|
|
275
|
+
string: 'string',
|
|
276
|
+
bytes: new Uint8Array([0, 1, 2, 3]),
|
|
277
|
+
cidLink: CID.parse(
|
|
278
|
+
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
279
|
+
),
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
it('Passes valid schemas', () => {
|
|
283
|
+
lex.assertValidRecord('com.example.kitchenSink', passingSink)
|
|
103
284
|
})
|
|
104
285
|
|
|
105
286
|
it('Fails invalid input types', () => {
|
|
@@ -129,10 +310,22 @@ describe('Record validation', () => {
|
|
|
129
310
|
$type: 'com.example.kitchenSink',
|
|
130
311
|
array: ['one', 'two'],
|
|
131
312
|
boolean: true,
|
|
132
|
-
number: 123.45,
|
|
133
313
|
integer: 123,
|
|
134
314
|
string: 'string',
|
|
135
315
|
datetime: new Date().toISOString(),
|
|
316
|
+
atUri: 'at://did:web:example.com/com.example.test/self',
|
|
317
|
+
did: 'did:web:example.com',
|
|
318
|
+
cid: 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
319
|
+
bytes: new Uint8Array([0, 1, 2, 3]),
|
|
320
|
+
cidLink: CID.parse(
|
|
321
|
+
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
322
|
+
),
|
|
323
|
+
}),
|
|
324
|
+
).toThrow('Record must have the property "object"')
|
|
325
|
+
expect(() =>
|
|
326
|
+
lex.assertValidRecord('com.example.kitchenSink', {
|
|
327
|
+
...passingSink,
|
|
328
|
+
object: undefined,
|
|
136
329
|
}),
|
|
137
330
|
).toThrow('Record must have the property "object"')
|
|
138
331
|
})
|
|
@@ -140,130 +333,49 @@ describe('Record validation', () => {
|
|
|
140
333
|
it('Fails incorrect types', () => {
|
|
141
334
|
expect(() =>
|
|
142
335
|
lex.assertValidRecord('com.example.kitchenSink', {
|
|
143
|
-
|
|
336
|
+
...passingSink,
|
|
144
337
|
object: {
|
|
338
|
+
...passingSink.object,
|
|
145
339
|
object: { boolean: '1234' },
|
|
146
|
-
array: ['one', 'two'],
|
|
147
|
-
boolean: true,
|
|
148
|
-
number: 123.45,
|
|
149
|
-
integer: 123,
|
|
150
|
-
string: 'string',
|
|
151
340
|
},
|
|
152
|
-
array: ['one', 'two'],
|
|
153
|
-
boolean: true,
|
|
154
|
-
number: 123.45,
|
|
155
|
-
integer: 123,
|
|
156
|
-
string: 'string',
|
|
157
|
-
datetime: new Date().toISOString(),
|
|
158
341
|
}),
|
|
159
342
|
).toThrow('Record/object/object/boolean must be a boolean')
|
|
160
343
|
expect(() =>
|
|
161
344
|
lex.assertValidRecord('com.example.kitchenSink', {
|
|
162
|
-
|
|
345
|
+
...passingSink,
|
|
163
346
|
object: true,
|
|
164
|
-
array: ['one', 'two'],
|
|
165
|
-
boolean: true,
|
|
166
|
-
number: 123.45,
|
|
167
|
-
integer: 123,
|
|
168
|
-
string: 'string',
|
|
169
|
-
datetime: new Date().toISOString(),
|
|
170
347
|
}),
|
|
171
348
|
).toThrow('Record/object must be an object')
|
|
172
349
|
expect(() =>
|
|
173
350
|
lex.assertValidRecord('com.example.kitchenSink', {
|
|
174
|
-
|
|
175
|
-
object: {
|
|
176
|
-
object: { boolean: true },
|
|
177
|
-
array: ['one', 'two'],
|
|
178
|
-
boolean: true,
|
|
179
|
-
number: 123.45,
|
|
180
|
-
integer: 123,
|
|
181
|
-
string: 'string',
|
|
182
|
-
},
|
|
351
|
+
...passingSink,
|
|
183
352
|
array: 1234,
|
|
184
|
-
boolean: true,
|
|
185
|
-
number: 123.45,
|
|
186
|
-
integer: 123,
|
|
187
|
-
string: 'string',
|
|
188
|
-
datetime: new Date().toISOString(),
|
|
189
353
|
}),
|
|
190
354
|
).toThrow('Record/array must be an array')
|
|
191
355
|
expect(() =>
|
|
192
356
|
lex.assertValidRecord('com.example.kitchenSink', {
|
|
193
|
-
|
|
194
|
-
object: {
|
|
195
|
-
object: { boolean: true },
|
|
196
|
-
array: ['one', 'two'],
|
|
197
|
-
boolean: true,
|
|
198
|
-
number: 123.45,
|
|
199
|
-
integer: 123,
|
|
200
|
-
string: 'string',
|
|
201
|
-
},
|
|
202
|
-
array: ['one', 'two'],
|
|
203
|
-
boolean: true,
|
|
204
|
-
number: 'string',
|
|
205
|
-
integer: 123,
|
|
206
|
-
string: 'string',
|
|
207
|
-
datetime: new Date().toISOString(),
|
|
208
|
-
}),
|
|
209
|
-
).toThrow('Record/number must be a number')
|
|
210
|
-
expect(() =>
|
|
211
|
-
lex.assertValidRecord('com.example.kitchenSink', {
|
|
212
|
-
$type: 'com.example.kitchenSink',
|
|
213
|
-
object: {
|
|
214
|
-
object: { boolean: true },
|
|
215
|
-
array: ['one', 'two'],
|
|
216
|
-
boolean: true,
|
|
217
|
-
number: 123.45,
|
|
218
|
-
integer: 123,
|
|
219
|
-
string: 'string',
|
|
220
|
-
},
|
|
221
|
-
array: ['one', 'two'],
|
|
222
|
-
boolean: true,
|
|
223
|
-
number: 123.45,
|
|
357
|
+
...passingSink,
|
|
224
358
|
integer: true,
|
|
225
|
-
string: 'string',
|
|
226
|
-
datetime: new Date().toISOString(),
|
|
227
359
|
}),
|
|
228
|
-
).toThrow('Record/integer must be
|
|
360
|
+
).toThrow('Record/integer must be an integer')
|
|
229
361
|
expect(() =>
|
|
230
362
|
lex.assertValidRecord('com.example.kitchenSink', {
|
|
231
|
-
|
|
232
|
-
object: {
|
|
233
|
-
object: { boolean: true },
|
|
234
|
-
array: ['one', 'two'],
|
|
235
|
-
boolean: true,
|
|
236
|
-
number: 123.45,
|
|
237
|
-
integer: 123,
|
|
238
|
-
string: 'string',
|
|
239
|
-
},
|
|
240
|
-
array: ['one', 'two'],
|
|
241
|
-
boolean: true,
|
|
242
|
-
number: 123.45,
|
|
243
|
-
integer: 123,
|
|
363
|
+
...passingSink,
|
|
244
364
|
string: {},
|
|
245
|
-
datetime: new Date().toISOString(),
|
|
246
365
|
}),
|
|
247
366
|
).toThrow('Record/string must be a string')
|
|
248
367
|
expect(() =>
|
|
249
368
|
lex.assertValidRecord('com.example.kitchenSink', {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
object: { boolean: true },
|
|
253
|
-
array: ['one', 'two'],
|
|
254
|
-
boolean: true,
|
|
255
|
-
number: 123.45,
|
|
256
|
-
integer: 123,
|
|
257
|
-
string: 'string',
|
|
258
|
-
},
|
|
259
|
-
array: ['one', 'two'],
|
|
260
|
-
boolean: true,
|
|
261
|
-
number: 123.45,
|
|
262
|
-
integer: 123,
|
|
263
|
-
string: 'string',
|
|
264
|
-
datetime: 1234,
|
|
369
|
+
...passingSink,
|
|
370
|
+
bytes: 1234,
|
|
265
371
|
}),
|
|
266
|
-
).toThrow('Record/
|
|
372
|
+
).toThrow('Record/bytes must be a byte array')
|
|
373
|
+
expect(() =>
|
|
374
|
+
lex.assertValidRecord('com.example.kitchenSink', {
|
|
375
|
+
...passingSink,
|
|
376
|
+
cidLink: 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
377
|
+
}),
|
|
378
|
+
).toThrow('Record/cidLink must be a CID')
|
|
267
379
|
})
|
|
268
380
|
|
|
269
381
|
it('Handles optional properties correctly', () => {
|
|
@@ -272,6 +384,25 @@ describe('Record validation', () => {
|
|
|
272
384
|
})
|
|
273
385
|
})
|
|
274
386
|
|
|
387
|
+
it('Handles default properties correctly', () => {
|
|
388
|
+
const result = lex.assertValidRecord('com.example.default', {
|
|
389
|
+
$type: 'com.example.default',
|
|
390
|
+
object: {},
|
|
391
|
+
})
|
|
392
|
+
expect(result).toEqual({
|
|
393
|
+
$type: 'com.example.default',
|
|
394
|
+
boolean: false,
|
|
395
|
+
integer: 0,
|
|
396
|
+
string: '',
|
|
397
|
+
object: {
|
|
398
|
+
boolean: true,
|
|
399
|
+
integer: 1,
|
|
400
|
+
string: 'x',
|
|
401
|
+
},
|
|
402
|
+
})
|
|
403
|
+
expect(result).not.toHaveProperty('datetime')
|
|
404
|
+
})
|
|
405
|
+
|
|
275
406
|
it('Handles unions correctly', () => {
|
|
276
407
|
lex.assertValidRecord('com.example.union', {
|
|
277
408
|
$type: 'com.example.union',
|
|
@@ -280,7 +411,6 @@ describe('Record validation', () => {
|
|
|
280
411
|
object: { boolean: true },
|
|
281
412
|
array: ['one', 'two'],
|
|
282
413
|
boolean: true,
|
|
283
|
-
number: 123.45,
|
|
284
414
|
integer: 123,
|
|
285
415
|
string: 'string',
|
|
286
416
|
},
|
|
@@ -355,6 +485,21 @@ describe('Record validation', () => {
|
|
|
355
485
|
).toThrow('Record/array must not have more than 4 elements')
|
|
356
486
|
})
|
|
357
487
|
|
|
488
|
+
it('Applies array item constraints', () => {
|
|
489
|
+
expect(() =>
|
|
490
|
+
lex.assertValidRecord('com.example.arrayLength', {
|
|
491
|
+
$type: 'com.example.arrayLength',
|
|
492
|
+
array: [1, '2', 3],
|
|
493
|
+
}),
|
|
494
|
+
).toThrow('Record/array/1 must be an integer')
|
|
495
|
+
expect(() =>
|
|
496
|
+
lex.assertValidRecord('com.example.arrayLength', {
|
|
497
|
+
$type: 'com.example.arrayLength',
|
|
498
|
+
array: [1, undefined, 3],
|
|
499
|
+
}),
|
|
500
|
+
).toThrow('Record/array/1 must be an integer')
|
|
501
|
+
})
|
|
502
|
+
|
|
358
503
|
it('Applies boolean const constraint', () => {
|
|
359
504
|
lex.assertValidRecord('com.example.boolConst', {
|
|
360
505
|
$type: 'com.example.boolConst',
|
|
@@ -368,51 +513,6 @@ describe('Record validation', () => {
|
|
|
368
513
|
).toThrow('Record/boolean must be false')
|
|
369
514
|
})
|
|
370
515
|
|
|
371
|
-
it('Applies number range constraint', () => {
|
|
372
|
-
lex.assertValidRecord('com.example.numberRange', {
|
|
373
|
-
$type: 'com.example.numberRange',
|
|
374
|
-
number: 2.5,
|
|
375
|
-
})
|
|
376
|
-
expect(() =>
|
|
377
|
-
lex.assertValidRecord('com.example.numberRange', {
|
|
378
|
-
$type: 'com.example.numberRange',
|
|
379
|
-
number: 1,
|
|
380
|
-
}),
|
|
381
|
-
).toThrow('Record/number can not be less than 2')
|
|
382
|
-
expect(() =>
|
|
383
|
-
lex.assertValidRecord('com.example.numberRange', {
|
|
384
|
-
$type: 'com.example.numberRange',
|
|
385
|
-
number: 5,
|
|
386
|
-
}),
|
|
387
|
-
).toThrow('Record/number can not be greater than 4')
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
it('Applies number enum constraint', () => {
|
|
391
|
-
lex.assertValidRecord('com.example.numberEnum', {
|
|
392
|
-
$type: 'com.example.numberEnum',
|
|
393
|
-
number: 1.5,
|
|
394
|
-
})
|
|
395
|
-
expect(() =>
|
|
396
|
-
lex.assertValidRecord('com.example.numberEnum', {
|
|
397
|
-
$type: 'com.example.numberEnum',
|
|
398
|
-
number: 0,
|
|
399
|
-
}),
|
|
400
|
-
).toThrow('Record/number must be one of (1|1.5|2)')
|
|
401
|
-
})
|
|
402
|
-
|
|
403
|
-
it('Applies number const constraint', () => {
|
|
404
|
-
lex.assertValidRecord('com.example.numberConst', {
|
|
405
|
-
$type: 'com.example.numberConst',
|
|
406
|
-
number: 0,
|
|
407
|
-
})
|
|
408
|
-
expect(() =>
|
|
409
|
-
lex.assertValidRecord('com.example.numberConst', {
|
|
410
|
-
$type: 'com.example.numberConst',
|
|
411
|
-
number: 1,
|
|
412
|
-
}),
|
|
413
|
-
).toThrow('Record/number must be 0')
|
|
414
|
-
})
|
|
415
|
-
|
|
416
516
|
it('Applies integer range constraint', () => {
|
|
417
517
|
lex.assertValidRecord('com.example.integerRange', {
|
|
418
518
|
$type: 'com.example.integerRange',
|
|
@@ -484,6 +584,31 @@ describe('Record validation', () => {
|
|
|
484
584
|
string: '12345',
|
|
485
585
|
}),
|
|
486
586
|
).toThrow('Record/string must not be longer than 4 characters')
|
|
587
|
+
expect(() =>
|
|
588
|
+
lex.assertValidRecord('com.example.stringLength', {
|
|
589
|
+
$type: 'com.example.stringLength',
|
|
590
|
+
string: '👨👩👧👧',
|
|
591
|
+
}),
|
|
592
|
+
).toThrow('Record/string must not be longer than 4 characters')
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
it('Applies grapheme string length constraint', () => {
|
|
596
|
+
lex.assertValidRecord('com.example.stringLengthGrapheme', {
|
|
597
|
+
$type: 'com.example.stringLengthGrapheme',
|
|
598
|
+
string: '12👨👩👧👧',
|
|
599
|
+
})
|
|
600
|
+
expect(() =>
|
|
601
|
+
lex.assertValidRecord('com.example.stringLengthGrapheme', {
|
|
602
|
+
$type: 'com.example.stringLengthGrapheme',
|
|
603
|
+
string: '👨👩👧👧',
|
|
604
|
+
}),
|
|
605
|
+
).toThrow('Record/string must not be shorter than 2 graphemes')
|
|
606
|
+
expect(() =>
|
|
607
|
+
lex.assertValidRecord('com.example.stringLengthGrapheme', {
|
|
608
|
+
$type: 'com.example.stringLengthGrapheme',
|
|
609
|
+
string: '12345',
|
|
610
|
+
}),
|
|
611
|
+
).toThrow('Record/string must not be longer than 4 graphemes')
|
|
487
612
|
})
|
|
488
613
|
|
|
489
614
|
it('Applies string enum constraint', () => {
|
|
@@ -537,36 +662,230 @@ describe('Record validation', () => {
|
|
|
537
662
|
}),
|
|
538
663
|
).toThrow('Record/datetime must be an iso8601 formatted datetime')
|
|
539
664
|
})
|
|
665
|
+
|
|
666
|
+
it('Applies uri formatting constraint', () => {
|
|
667
|
+
for (const uri of [
|
|
668
|
+
'https://example.com',
|
|
669
|
+
'https://example.com/with/path',
|
|
670
|
+
'https://example.com/with/path?and=query',
|
|
671
|
+
'at://bsky.social',
|
|
672
|
+
'did:example:test',
|
|
673
|
+
]) {
|
|
674
|
+
lex.assertValidRecord('com.example.uri', {
|
|
675
|
+
$type: 'com.example.uri',
|
|
676
|
+
uri,
|
|
677
|
+
})
|
|
678
|
+
}
|
|
679
|
+
expect(() =>
|
|
680
|
+
lex.assertValidRecord('com.example.uri', {
|
|
681
|
+
$type: 'com.example.uri',
|
|
682
|
+
uri: 'not a uri',
|
|
683
|
+
}),
|
|
684
|
+
).toThrow('Record/uri must be a uri')
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
it('Applies at-uri formatting constraint', () => {
|
|
688
|
+
lex.assertValidRecord('com.example.atUri', {
|
|
689
|
+
$type: 'com.example.atUri',
|
|
690
|
+
atUri: 'at://did:web:example.com/com.example.test/self',
|
|
691
|
+
})
|
|
692
|
+
expect(() =>
|
|
693
|
+
lex.assertValidRecord('com.example.atUri', {
|
|
694
|
+
$type: 'com.example.atUri',
|
|
695
|
+
atUri: 'http://not-atproto.com',
|
|
696
|
+
}),
|
|
697
|
+
).toThrow('Record/atUri must be a valid at-uri')
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
it('Applies did formatting constraint', () => {
|
|
701
|
+
lex.assertValidRecord('com.example.did', {
|
|
702
|
+
$type: 'com.example.did',
|
|
703
|
+
did: 'did:web:example.com',
|
|
704
|
+
})
|
|
705
|
+
lex.assertValidRecord('com.example.did', {
|
|
706
|
+
$type: 'com.example.did',
|
|
707
|
+
did: 'did:plc:12345678abcdefghijklmnop',
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
expect(() =>
|
|
711
|
+
lex.assertValidRecord('com.example.did', {
|
|
712
|
+
$type: 'com.example.did',
|
|
713
|
+
did: 'bad did',
|
|
714
|
+
}),
|
|
715
|
+
).toThrow('Record/did must be a valid did')
|
|
716
|
+
expect(() =>
|
|
717
|
+
lex.assertValidRecord('com.example.did', {
|
|
718
|
+
$type: 'com.example.did',
|
|
719
|
+
did: 'did:short',
|
|
720
|
+
}),
|
|
721
|
+
).toThrow('Record/did must be a valid did')
|
|
722
|
+
})
|
|
723
|
+
|
|
724
|
+
it('Applies handle formatting constraint', () => {
|
|
725
|
+
lex.assertValidRecord('com.example.handle', {
|
|
726
|
+
$type: 'com.example.handle',
|
|
727
|
+
handle: 'test.bsky.social',
|
|
728
|
+
})
|
|
729
|
+
lex.assertValidRecord('com.example.handle', {
|
|
730
|
+
$type: 'com.example.handle',
|
|
731
|
+
handle: 'bsky.test',
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
expect(() =>
|
|
735
|
+
lex.assertValidRecord('com.example.handle', {
|
|
736
|
+
$type: 'com.example.handle',
|
|
737
|
+
handle: 'bad handle',
|
|
738
|
+
}),
|
|
739
|
+
).toThrow('Record/handle must be a valid handle')
|
|
740
|
+
expect(() =>
|
|
741
|
+
lex.assertValidRecord('com.example.handle', {
|
|
742
|
+
$type: 'com.example.handle',
|
|
743
|
+
handle: '-bad-.test',
|
|
744
|
+
}),
|
|
745
|
+
).toThrow('Record/handle must be a valid handle')
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
it('Applies at-identifier formatting constraint', () => {
|
|
749
|
+
lex.assertValidRecord('com.example.atIdentifier', {
|
|
750
|
+
$type: 'com.example.atIdentifier',
|
|
751
|
+
atIdentifier: 'bsky.test',
|
|
752
|
+
})
|
|
753
|
+
lex.assertValidRecord('com.example.atIdentifier', {
|
|
754
|
+
$type: 'com.example.atIdentifier',
|
|
755
|
+
atIdentifier: 'did:plc:12345678abcdefghijklmnop',
|
|
756
|
+
})
|
|
757
|
+
|
|
758
|
+
expect(() =>
|
|
759
|
+
lex.assertValidRecord('com.example.atIdentifier', {
|
|
760
|
+
$type: 'com.example.atIdentifier',
|
|
761
|
+
atIdentifier: 'bad id',
|
|
762
|
+
}),
|
|
763
|
+
).toThrow('Record/atIdentifier must be a valid did or a handle')
|
|
764
|
+
expect(() =>
|
|
765
|
+
lex.assertValidRecord('com.example.atIdentifier', {
|
|
766
|
+
$type: 'com.example.atIdentifier',
|
|
767
|
+
atIdentifier: '-bad-.test',
|
|
768
|
+
}),
|
|
769
|
+
).toThrow('Record/atIdentifier must be a valid did or a handle')
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
it('Applies nsid formatting constraint', () => {
|
|
773
|
+
lex.assertValidRecord('com.example.nsid', {
|
|
774
|
+
$type: 'com.example.nsid',
|
|
775
|
+
nsid: 'com.atproto.test',
|
|
776
|
+
})
|
|
777
|
+
lex.assertValidRecord('com.example.nsid', {
|
|
778
|
+
$type: 'com.example.nsid',
|
|
779
|
+
nsid: 'app.bsky.nested.test',
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
expect(() =>
|
|
783
|
+
lex.assertValidRecord('com.example.nsid', {
|
|
784
|
+
$type: 'com.example.nsid',
|
|
785
|
+
nsid: 'bad nsid',
|
|
786
|
+
}),
|
|
787
|
+
).toThrow('Record/nsid must be a valid nsid')
|
|
788
|
+
expect(() =>
|
|
789
|
+
lex.assertValidRecord('com.example.nsid', {
|
|
790
|
+
$type: 'com.example.nsid',
|
|
791
|
+
nsid: 'com.bad-.foo',
|
|
792
|
+
}),
|
|
793
|
+
).toThrow('Record/nsid must be a valid nsid')
|
|
794
|
+
})
|
|
795
|
+
|
|
796
|
+
it('Applies cid formatting constraint', () => {
|
|
797
|
+
lex.assertValidRecord('com.example.cid', {
|
|
798
|
+
$type: 'com.example.cid',
|
|
799
|
+
cid: 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
800
|
+
})
|
|
801
|
+
expect(() =>
|
|
802
|
+
lex.assertValidRecord('com.example.cid', {
|
|
803
|
+
$type: 'com.example.cid',
|
|
804
|
+
cid: 'abapsdofiuwrpoiasdfuaspdfoiu',
|
|
805
|
+
}),
|
|
806
|
+
).toThrow('Record/cid must be a cid string')
|
|
807
|
+
})
|
|
808
|
+
|
|
809
|
+
it('Applies language formatting constraint', () => {
|
|
810
|
+
lex.assertValidRecord('com.example.language', {
|
|
811
|
+
$type: 'com.example.language',
|
|
812
|
+
language: 'en-US-boont',
|
|
813
|
+
})
|
|
814
|
+
expect(() =>
|
|
815
|
+
lex.assertValidRecord('com.example.language', {
|
|
816
|
+
$type: 'com.example.language',
|
|
817
|
+
language: 'not-a-language-',
|
|
818
|
+
}),
|
|
819
|
+
).toThrow('Record/language must be a well-formed BCP 47 language tag')
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
it('Applies bytes length constraints', () => {
|
|
823
|
+
lex.assertValidRecord('com.example.byteLength', {
|
|
824
|
+
$type: 'com.example.byteLength',
|
|
825
|
+
bytes: new Uint8Array([1, 2, 3]),
|
|
826
|
+
})
|
|
827
|
+
expect(() =>
|
|
828
|
+
lex.assertValidRecord('com.example.byteLength', {
|
|
829
|
+
$type: 'com.example.byteLength',
|
|
830
|
+
bytes: new Uint8Array([1]),
|
|
831
|
+
}),
|
|
832
|
+
).toThrow('Record/bytes must not be smaller than 2 bytes')
|
|
833
|
+
expect(() =>
|
|
834
|
+
lex.assertValidRecord('com.example.byteLength', {
|
|
835
|
+
$type: 'com.example.byteLength',
|
|
836
|
+
bytes: new Uint8Array([1, 2, 3, 4, 5]),
|
|
837
|
+
}),
|
|
838
|
+
).toThrow('Record/bytes must not be larger than 4 bytes')
|
|
839
|
+
})
|
|
540
840
|
})
|
|
541
841
|
|
|
542
842
|
describe('XRPC parameter validation', () => {
|
|
543
843
|
const lex = new Lexicons(LexiconDocs)
|
|
544
844
|
|
|
545
845
|
it('Passes valid parameters', () => {
|
|
546
|
-
lex.assertValidXrpcParams('com.example.query', {
|
|
846
|
+
const queryResult = lex.assertValidXrpcParams('com.example.query', {
|
|
847
|
+
boolean: true,
|
|
848
|
+
integer: 123,
|
|
849
|
+
string: 'string',
|
|
850
|
+
array: ['x', 'y'],
|
|
851
|
+
})
|
|
852
|
+
expect(queryResult).toEqual({
|
|
853
|
+
boolean: true,
|
|
854
|
+
integer: 123,
|
|
855
|
+
string: 'string',
|
|
856
|
+
array: ['x', 'y'],
|
|
857
|
+
def: 0,
|
|
858
|
+
})
|
|
859
|
+
const paramResult = lex.assertValidXrpcParams('com.example.procedure', {
|
|
547
860
|
boolean: true,
|
|
548
|
-
number: 123.45,
|
|
549
861
|
integer: 123,
|
|
550
862
|
string: 'string',
|
|
863
|
+
array: ['x', 'y'],
|
|
864
|
+
def: 1,
|
|
551
865
|
})
|
|
552
|
-
|
|
866
|
+
expect(paramResult).toEqual({
|
|
553
867
|
boolean: true,
|
|
554
|
-
number: 123.45,
|
|
555
868
|
integer: 123,
|
|
556
869
|
string: 'string',
|
|
870
|
+
array: ['x', 'y'],
|
|
871
|
+
def: 1,
|
|
557
872
|
})
|
|
558
873
|
})
|
|
559
874
|
|
|
560
875
|
it('Handles required correctly', () => {
|
|
561
876
|
lex.assertValidXrpcParams('com.example.query', {
|
|
562
877
|
boolean: true,
|
|
563
|
-
number: 123.45,
|
|
564
878
|
integer: 123,
|
|
565
879
|
})
|
|
566
880
|
expect(() =>
|
|
567
881
|
lex.assertValidXrpcParams('com.example.query', {
|
|
568
882
|
boolean: true,
|
|
569
|
-
|
|
883
|
+
}),
|
|
884
|
+
).toThrow('Params must have the property "integer"')
|
|
885
|
+
expect(() =>
|
|
886
|
+
lex.assertValidXrpcParams('com.example.query', {
|
|
887
|
+
boolean: true,
|
|
888
|
+
integer: undefined,
|
|
570
889
|
}),
|
|
571
890
|
).toThrow('Params must have the property "integer"')
|
|
572
891
|
})
|
|
@@ -575,19 +894,19 @@ describe('XRPC parameter validation', () => {
|
|
|
575
894
|
expect(() =>
|
|
576
895
|
lex.assertValidXrpcParams('com.example.query', {
|
|
577
896
|
boolean: 'string',
|
|
578
|
-
number: 123.45,
|
|
579
897
|
integer: 123,
|
|
580
898
|
string: 'string',
|
|
581
899
|
}),
|
|
582
900
|
).toThrow('boolean must be a boolean')
|
|
583
901
|
expect(() =>
|
|
584
|
-
lex.assertValidXrpcParams('com.example.
|
|
902
|
+
lex.assertValidXrpcParams('com.example.query', {
|
|
585
903
|
boolean: true,
|
|
586
|
-
|
|
904
|
+
float: 123.45,
|
|
587
905
|
integer: 123,
|
|
588
906
|
string: 'string',
|
|
907
|
+
array: 'x',
|
|
589
908
|
}),
|
|
590
|
-
).toThrow('
|
|
909
|
+
).toThrow('array must be an array')
|
|
591
910
|
})
|
|
592
911
|
})
|
|
593
912
|
|
|
@@ -599,7 +918,7 @@ describe('XRPC input validation', () => {
|
|
|
599
918
|
object: { boolean: true },
|
|
600
919
|
array: ['one', 'two'],
|
|
601
920
|
boolean: true,
|
|
602
|
-
|
|
921
|
+
float: 123.45,
|
|
603
922
|
integer: 123,
|
|
604
923
|
string: 'string',
|
|
605
924
|
})
|
|
@@ -612,7 +931,7 @@ describe('XRPC input validation', () => {
|
|
|
612
931
|
object: { boolean: 'string' },
|
|
613
932
|
array: ['one', 'two'],
|
|
614
933
|
boolean: true,
|
|
615
|
-
|
|
934
|
+
float: 123.45,
|
|
616
935
|
integer: 123,
|
|
617
936
|
string: 'string',
|
|
618
937
|
}),
|
|
@@ -631,7 +950,7 @@ describe('XRPC output validation', () => {
|
|
|
631
950
|
object: { boolean: true },
|
|
632
951
|
array: ['one', 'two'],
|
|
633
952
|
boolean: true,
|
|
634
|
-
|
|
953
|
+
float: 123.45,
|
|
635
954
|
integer: 123,
|
|
636
955
|
string: 'string',
|
|
637
956
|
})
|
|
@@ -639,7 +958,7 @@ describe('XRPC output validation', () => {
|
|
|
639
958
|
object: { boolean: true },
|
|
640
959
|
array: ['one', 'two'],
|
|
641
960
|
boolean: true,
|
|
642
|
-
|
|
961
|
+
float: 123.45,
|
|
643
962
|
integer: 123,
|
|
644
963
|
string: 'string',
|
|
645
964
|
})
|
|
@@ -652,7 +971,7 @@ describe('XRPC output validation', () => {
|
|
|
652
971
|
object: { boolean: 'string' },
|
|
653
972
|
array: ['one', 'two'],
|
|
654
973
|
boolean: true,
|
|
655
|
-
|
|
974
|
+
float: 123.45,
|
|
656
975
|
integer: 123,
|
|
657
976
|
string: 'string',
|
|
658
977
|
}),
|