@atproto/lexicon 0.0.1

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 (42) hide show
  1. package/README.md +31 -0
  2. package/build.js +22 -0
  3. package/dist/index.d.ts +126 -0
  4. package/dist/index.js +3897 -0
  5. package/dist/index.js.map +7 -0
  6. package/dist/src/index.d.ts +2 -0
  7. package/dist/src/lexicons.d.ts +15 -0
  8. package/dist/src/record/index.d.ts +4 -0
  9. package/dist/src/record/schema.d.ts +9 -0
  10. package/dist/src/record/schemas.d.ts +10 -0
  11. package/dist/src/record/util.d.ts +1 -0
  12. package/dist/src/record/validation.d.ts +24 -0
  13. package/dist/src/record/validator.d.ts +17 -0
  14. package/dist/src/record-validator.d.ts +17 -0
  15. package/dist/src/schema.d.ts +9 -0
  16. package/dist/src/schemas.d.ts +10 -0
  17. package/dist/src/types.d.ts +30268 -0
  18. package/dist/src/util.d.ts +6 -0
  19. package/dist/src/validation.d.ts +6 -0
  20. package/dist/src/validators/blob.d.ts +6 -0
  21. package/dist/src/validators/complex.d.ts +5 -0
  22. package/dist/src/validators/primitives.d.ts +9 -0
  23. package/dist/src/validators/xrpc.d.ts +3 -0
  24. package/dist/src/view-validator.d.ts +13 -0
  25. package/dist/tsconfig.build.tsbuildinfo +1 -0
  26. package/dist/types.d.ts +73 -0
  27. package/dist/types.js +35 -0
  28. package/jest.config.js +6 -0
  29. package/package.json +21 -0
  30. package/src/index.ts +2 -0
  31. package/src/lexicons.ts +203 -0
  32. package/src/types.ts +318 -0
  33. package/src/util.ts +107 -0
  34. package/src/validation.ts +48 -0
  35. package/src/validators/blob.ts +57 -0
  36. package/src/validators/complex.ts +152 -0
  37. package/src/validators/primitives.ts +300 -0
  38. package/src/validators/xrpc.ts +50 -0
  39. package/tests/_scaffolds/lexicons.ts +379 -0
  40. package/tests/general.test.ts +611 -0
  41. package/tsconfig.build.json +4 -0
  42. package/tsconfig.json +11 -0
@@ -0,0 +1,300 @@
1
+ import { Lexicons } from '../lexicons'
2
+ import {
3
+ LexUserType,
4
+ LexBoolean,
5
+ LexNumber,
6
+ LexInteger,
7
+ LexString,
8
+ LexDatetime,
9
+ ValidationResult,
10
+ ValidationError,
11
+ } from '../types'
12
+
13
+ export function validate(
14
+ lexicons: Lexicons,
15
+ path: string,
16
+ def: LexUserType,
17
+ value: unknown,
18
+ ): ValidationResult {
19
+ switch (def.type) {
20
+ case 'boolean':
21
+ return boolean(lexicons, path, def, value)
22
+ case 'number':
23
+ return number(lexicons, path, def, value)
24
+ case 'integer':
25
+ return integer(lexicons, path, def, value)
26
+ case 'string':
27
+ return string(lexicons, path, def, value)
28
+ case 'datetime':
29
+ return datetime(lexicons, path, def, value)
30
+ case 'unknown':
31
+ return unknown(lexicons, path, def, value)
32
+ default:
33
+ return {
34
+ success: false,
35
+ error: new ValidationError(`Unexpected lexicon type: ${def.type}`),
36
+ }
37
+ }
38
+ }
39
+
40
+ export function boolean(
41
+ lexicons: Lexicons,
42
+ path: string,
43
+ def: LexUserType,
44
+ value: unknown,
45
+ ): ValidationResult {
46
+ def = def as LexBoolean
47
+
48
+ // type
49
+ const type = typeof value
50
+ if (type == 'undefined') {
51
+ if (typeof def.default === 'boolean') {
52
+ return { success: true }
53
+ }
54
+ return {
55
+ success: false,
56
+ error: new ValidationError(`${path} must be a boolean`),
57
+ }
58
+ } else if (type !== 'boolean') {
59
+ return {
60
+ success: false,
61
+ error: new ValidationError(`${path} must be a boolean`),
62
+ }
63
+ }
64
+
65
+ // const
66
+ if (typeof def.const === 'boolean') {
67
+ if (value !== def.const) {
68
+ return {
69
+ success: false,
70
+ error: new ValidationError(`${path} must be ${def.const}`),
71
+ }
72
+ }
73
+ }
74
+
75
+ return { success: true }
76
+ }
77
+
78
+ export function number(
79
+ lexicons: Lexicons,
80
+ path: string,
81
+ def: LexUserType,
82
+ value: unknown,
83
+ ): ValidationResult {
84
+ def = def as LexNumber
85
+
86
+ // type
87
+ const type = typeof value
88
+ if (type == 'undefined') {
89
+ if (typeof def.default === 'number') {
90
+ return { success: true }
91
+ }
92
+ return {
93
+ success: false,
94
+ error: new ValidationError(`${path} must be a number`),
95
+ }
96
+ } else if (type !== 'number') {
97
+ return {
98
+ success: false,
99
+ error: new ValidationError(`${path} must be a number`),
100
+ }
101
+ }
102
+
103
+ // const
104
+ if (typeof def.const === 'number') {
105
+ if (value !== def.const) {
106
+ return {
107
+ success: false,
108
+ error: new ValidationError(`${path} must be ${def.const}`),
109
+ }
110
+ }
111
+ }
112
+
113
+ // enum
114
+ if (Array.isArray(def.enum)) {
115
+ if (!def.enum.includes(value as number)) {
116
+ return {
117
+ success: false,
118
+ error: new ValidationError(
119
+ `${path} must be one of (${def.enum.join('|')})`,
120
+ ),
121
+ }
122
+ }
123
+ }
124
+
125
+ // maximum
126
+ if (typeof def.maximum === 'number') {
127
+ if ((value as number) > def.maximum) {
128
+ return {
129
+ success: false,
130
+ error: new ValidationError(
131
+ `${path} can not be greater than ${def.maximum}`,
132
+ ),
133
+ }
134
+ }
135
+ }
136
+
137
+ // minimum
138
+ if (typeof def.minimum === 'number') {
139
+ if ((value as number) < def.minimum) {
140
+ return {
141
+ success: false,
142
+ error: new ValidationError(
143
+ `${path} can not be less than ${def.minimum}`,
144
+ ),
145
+ }
146
+ }
147
+ }
148
+
149
+ return { success: true }
150
+ }
151
+
152
+ export function integer(
153
+ lexicons: Lexicons,
154
+ path: string,
155
+ def: LexUserType,
156
+ value: unknown,
157
+ ): ValidationResult {
158
+ def = def as LexInteger
159
+
160
+ // run number validation
161
+ const numRes = number(lexicons, path, def, value)
162
+ if (!numRes.success) {
163
+ return numRes
164
+ }
165
+
166
+ // whole numbers only
167
+ if (!Number.isInteger(value)) {
168
+ return {
169
+ success: false,
170
+ error: new ValidationError(`${path} must be an integer`),
171
+ }
172
+ }
173
+
174
+ return { success: true }
175
+ }
176
+
177
+ export function string(
178
+ lexicons: Lexicons,
179
+ path: string,
180
+ def: LexUserType,
181
+ value: unknown,
182
+ ): ValidationResult {
183
+ def = def as LexString
184
+
185
+ // type
186
+ const type = typeof value
187
+ if (type == 'undefined') {
188
+ if (typeof def.default === 'string') {
189
+ return { success: true }
190
+ }
191
+ return {
192
+ success: false,
193
+ error: new ValidationError(`${path} must be a string`),
194
+ }
195
+ } else if (type !== 'string') {
196
+ return {
197
+ success: false,
198
+ error: new ValidationError(`${path} must be a string`),
199
+ }
200
+ }
201
+
202
+ // const
203
+ if (typeof def.const === 'string') {
204
+ if (value !== def.const) {
205
+ return {
206
+ success: false,
207
+ error: new ValidationError(`${path} must be ${def.const}`),
208
+ }
209
+ }
210
+ }
211
+
212
+ // enum
213
+ if (Array.isArray(def.enum)) {
214
+ if (!def.enum.includes(value as string)) {
215
+ return {
216
+ success: false,
217
+ error: new ValidationError(
218
+ `${path} must be one of (${def.enum.join('|')})`,
219
+ ),
220
+ }
221
+ }
222
+ }
223
+
224
+ // maxLength
225
+ if (typeof def.maxLength === 'number') {
226
+ if ((value as string).length > def.maxLength) {
227
+ return {
228
+ success: false,
229
+ error: new ValidationError(
230
+ `${path} must not be longer than ${def.maxLength} characters`,
231
+ ),
232
+ }
233
+ }
234
+ }
235
+
236
+ // minLength
237
+ if (typeof def.minLength === 'number') {
238
+ if ((value as string).length < def.minLength) {
239
+ return {
240
+ success: false,
241
+ error: new ValidationError(
242
+ `${path} must not be shorter than ${def.minLength} characters`,
243
+ ),
244
+ }
245
+ }
246
+ }
247
+
248
+ return { success: true }
249
+ }
250
+
251
+ export function datetime(
252
+ lexicons: Lexicons,
253
+ path: string,
254
+ def: LexUserType,
255
+ value: unknown,
256
+ ): ValidationResult {
257
+ def = def as LexDatetime
258
+
259
+ // type
260
+ const type = typeof value
261
+ if (type !== 'string') {
262
+ return {
263
+ success: false,
264
+ error: new ValidationError(`${path} must be a string`),
265
+ }
266
+ }
267
+
268
+ // valid iso-8601
269
+ {
270
+ try {
271
+ const date = new Date(Date.parse(value as string))
272
+ if (value !== date.toISOString()) {
273
+ throw new ValidationError(
274
+ `${path} must be an iso8601 formatted datetime`,
275
+ )
276
+ }
277
+ } catch {
278
+ throw new ValidationError(`${path} must be an iso8601 formatted datetime`)
279
+ }
280
+ }
281
+
282
+ return { success: true }
283
+ }
284
+
285
+ export function unknown(
286
+ lexicons: Lexicons,
287
+ path: string,
288
+ def: LexUserType,
289
+ value: unknown,
290
+ ): ValidationResult {
291
+ // type
292
+ if (!value || typeof value !== 'object') {
293
+ return {
294
+ success: false,
295
+ error: new ValidationError(`${path} must be an object`),
296
+ }
297
+ }
298
+
299
+ return { success: true }
300
+ }
@@ -0,0 +1,50 @@
1
+ import { Lexicons } from '../lexicons'
2
+ import { LexXrpcParameters, ValidationResult, ValidationError } from '../types'
3
+
4
+ import * as PrimitiveValidators from './primitives'
5
+
6
+ export function params(
7
+ lexicons: Lexicons,
8
+ path: string,
9
+ def: LexXrpcParameters,
10
+ value: unknown,
11
+ ): ValidationResult {
12
+ def = def as LexXrpcParameters
13
+
14
+ // type
15
+ if (!value || typeof value !== 'object') {
16
+ // in this case, we just fall back to an object
17
+ value = {}
18
+ }
19
+
20
+ // required
21
+ if (Array.isArray(def.required)) {
22
+ for (const key of def.required) {
23
+ if (!(key in (value as Record<string, unknown>))) {
24
+ return {
25
+ success: false,
26
+ error: new ValidationError(`${path} must have the property "${key}"`),
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ // properties
33
+ for (const key in def.properties) {
34
+ if (typeof (value as Record<string, unknown>)[key] === 'undefined') {
35
+ continue // skip- if required, will have already failed
36
+ }
37
+ const paramDef = def.properties[key]
38
+ const res = PrimitiveValidators.validate(
39
+ lexicons,
40
+ key,
41
+ paramDef,
42
+ (value as Record<string, unknown>)[key],
43
+ )
44
+ if (!res.success) {
45
+ return res
46
+ }
47
+ }
48
+
49
+ return { success: true }
50
+ }
@@ -0,0 +1,379 @@
1
+ export default [
2
+ {
3
+ lexicon: 1,
4
+ id: 'com.example.kitchenSink',
5
+ defs: {
6
+ main: {
7
+ type: 'record',
8
+ description: 'A record',
9
+ key: 'tid',
10
+ record: {
11
+ type: 'object',
12
+ required: [
13
+ 'object',
14
+ 'array',
15
+ 'boolean',
16
+ 'number',
17
+ 'integer',
18
+ 'string',
19
+ 'datetime',
20
+ ],
21
+ properties: {
22
+ object: { type: 'ref', ref: '#object' },
23
+ array: { type: 'array', items: { type: 'string' } },
24
+ boolean: { type: 'boolean' },
25
+ number: { type: 'number' },
26
+ integer: { type: 'integer' },
27
+ string: { type: 'string' },
28
+ datetime: { type: 'datetime' },
29
+ },
30
+ },
31
+ },
32
+ object: {
33
+ type: 'object',
34
+ required: ['object', 'array', 'boolean', 'number', 'integer', 'string'],
35
+ properties: {
36
+ object: { type: 'ref', ref: '#subobject' },
37
+ array: { type: 'array', items: { type: 'string' } },
38
+ boolean: { type: 'boolean' },
39
+ number: { type: 'number' },
40
+ integer: { type: 'integer' },
41
+ string: { type: 'string' },
42
+ },
43
+ },
44
+ subobject: {
45
+ type: 'object',
46
+ required: ['boolean'],
47
+ properties: {
48
+ boolean: { type: 'boolean' },
49
+ },
50
+ },
51
+ },
52
+ },
53
+ {
54
+ lexicon: 1,
55
+ id: 'com.example.query',
56
+ defs: {
57
+ main: {
58
+ type: 'query',
59
+ description: 'A query',
60
+ parameters: {
61
+ type: 'params',
62
+ required: ['boolean', 'number', 'integer'],
63
+ properties: {
64
+ boolean: { type: 'boolean' },
65
+ number: { type: 'number' },
66
+ integer: { type: 'integer' },
67
+ string: { type: 'string' },
68
+ },
69
+ },
70
+ output: {
71
+ encoding: 'application/json',
72
+ schema: { type: 'ref', ref: 'com.example.kitchenSink#object' },
73
+ },
74
+ },
75
+ },
76
+ },
77
+ {
78
+ lexicon: 1,
79
+ id: 'com.example.procedure',
80
+ defs: {
81
+ main: {
82
+ type: 'procedure',
83
+ description: 'A procedure',
84
+ parameters: {
85
+ type: 'params',
86
+ required: ['boolean', 'number', 'integer'],
87
+ properties: {
88
+ boolean: { type: 'boolean' },
89
+ number: { type: 'number' },
90
+ integer: { type: 'integer' },
91
+ string: { type: 'string' },
92
+ },
93
+ },
94
+ input: {
95
+ encoding: 'application/json',
96
+ schema: { type: 'ref', ref: 'com.example.kitchenSink#object' },
97
+ },
98
+ output: {
99
+ encoding: 'application/json',
100
+ schema: { type: 'ref', ref: 'com.example.kitchenSink#object' },
101
+ },
102
+ },
103
+ },
104
+ },
105
+ {
106
+ lexicon: 1,
107
+ id: 'com.example.optional',
108
+ defs: {
109
+ main: {
110
+ type: 'record',
111
+ record: {
112
+ type: 'object',
113
+ properties: {
114
+ object: { type: 'ref', ref: 'com.example.kitchenSink#object' },
115
+ array: { type: 'array', items: { type: 'string' } },
116
+ boolean: { type: 'boolean' },
117
+ number: { type: 'number' },
118
+ integer: { type: 'integer' },
119
+ string: { type: 'string' },
120
+ },
121
+ },
122
+ },
123
+ },
124
+ },
125
+ {
126
+ lexicon: 1,
127
+ id: 'com.example.union',
128
+ defs: {
129
+ main: {
130
+ type: 'record',
131
+ description: 'A record',
132
+ key: 'tid',
133
+ record: {
134
+ type: 'object',
135
+ required: ['unionOpen', 'unionClosed'],
136
+ properties: {
137
+ unionOpen: {
138
+ type: 'union',
139
+ refs: [
140
+ 'com.example.kitchenSink#object',
141
+ 'com.example.kitchenSink#subobject',
142
+ ],
143
+ },
144
+ unionClosed: {
145
+ type: 'union',
146
+ closed: true,
147
+ refs: [
148
+ 'com.example.kitchenSink#object',
149
+ 'com.example.kitchenSink#subobject',
150
+ ],
151
+ },
152
+ },
153
+ },
154
+ },
155
+ },
156
+ },
157
+ {
158
+ lexicon: 1,
159
+ id: 'com.example.unknown',
160
+ defs: {
161
+ main: {
162
+ type: 'record',
163
+ description: 'A record',
164
+ key: 'tid',
165
+ record: {
166
+ type: 'object',
167
+ required: ['unknown'],
168
+ properties: {
169
+ unknown: { type: 'unknown' },
170
+ optUnknown: { type: 'unknown' },
171
+ },
172
+ },
173
+ },
174
+ },
175
+ },
176
+ {
177
+ lexicon: 1,
178
+ id: 'com.example.arrayLength',
179
+ defs: {
180
+ main: {
181
+ type: 'record',
182
+ record: {
183
+ type: 'object',
184
+ properties: {
185
+ array: {
186
+ type: 'array',
187
+ minLength: 2,
188
+ maxLength: 4,
189
+ items: { type: 'number' },
190
+ },
191
+ },
192
+ },
193
+ },
194
+ },
195
+ },
196
+ {
197
+ lexicon: 1,
198
+ id: 'com.example.boolConst',
199
+ defs: {
200
+ main: {
201
+ type: 'record',
202
+ record: {
203
+ type: 'object',
204
+ properties: {
205
+ boolean: {
206
+ type: 'boolean',
207
+ const: false,
208
+ },
209
+ },
210
+ },
211
+ },
212
+ },
213
+ },
214
+ {
215
+ lexicon: 1,
216
+ id: 'com.example.numberRange',
217
+ defs: {
218
+ main: {
219
+ type: 'record',
220
+ record: {
221
+ type: 'object',
222
+ properties: {
223
+ number: {
224
+ type: 'number',
225
+ minimum: 2,
226
+ maximum: 4,
227
+ },
228
+ },
229
+ },
230
+ },
231
+ },
232
+ },
233
+ {
234
+ lexicon: 1,
235
+ id: 'com.example.numberEnum',
236
+ defs: {
237
+ main: {
238
+ type: 'record',
239
+ record: {
240
+ type: 'object',
241
+ properties: {
242
+ number: {
243
+ type: 'number',
244
+ enum: [1, 1.5, 2],
245
+ },
246
+ },
247
+ },
248
+ },
249
+ },
250
+ },
251
+ {
252
+ lexicon: 1,
253
+ id: 'com.example.numberConst',
254
+ defs: {
255
+ main: {
256
+ type: 'record',
257
+ record: {
258
+ type: 'object',
259
+ properties: {
260
+ number: {
261
+ type: 'number',
262
+ const: 0,
263
+ },
264
+ },
265
+ },
266
+ },
267
+ },
268
+ },
269
+ {
270
+ lexicon: 1,
271
+ id: 'com.example.integerRange',
272
+ defs: {
273
+ main: {
274
+ type: 'record',
275
+ record: {
276
+ type: 'object',
277
+ properties: {
278
+ integer: {
279
+ type: 'integer',
280
+ minimum: 2,
281
+ maximum: 4,
282
+ },
283
+ },
284
+ },
285
+ },
286
+ },
287
+ },
288
+ {
289
+ lexicon: 1,
290
+ id: 'com.example.integerEnum',
291
+ defs: {
292
+ main: {
293
+ type: 'record',
294
+ record: {
295
+ type: 'object',
296
+ properties: {
297
+ integer: {
298
+ type: 'integer',
299
+ enum: [1, 2],
300
+ },
301
+ },
302
+ },
303
+ },
304
+ },
305
+ },
306
+ {
307
+ lexicon: 1,
308
+ id: 'com.example.integerConst',
309
+ defs: {
310
+ main: {
311
+ type: 'record',
312
+ record: {
313
+ type: 'object',
314
+ properties: {
315
+ integer: {
316
+ type: 'integer',
317
+ const: 0,
318
+ },
319
+ },
320
+ },
321
+ },
322
+ },
323
+ },
324
+ {
325
+ lexicon: 1,
326
+ id: 'com.example.stringLength',
327
+ defs: {
328
+ main: {
329
+ type: 'record',
330
+ record: {
331
+ type: 'object',
332
+ properties: {
333
+ string: {
334
+ type: 'string',
335
+ minLength: 2,
336
+ maxLength: 4,
337
+ },
338
+ },
339
+ },
340
+ },
341
+ },
342
+ },
343
+ {
344
+ lexicon: 1,
345
+ id: 'com.example.stringEnum',
346
+ defs: {
347
+ main: {
348
+ type: 'record',
349
+ record: {
350
+ type: 'object',
351
+ properties: {
352
+ string: {
353
+ type: 'string',
354
+ enum: ['a', 'b'],
355
+ },
356
+ },
357
+ },
358
+ },
359
+ },
360
+ },
361
+ {
362
+ lexicon: 1,
363
+ id: 'com.example.stringConst',
364
+ defs: {
365
+ main: {
366
+ type: 'record',
367
+ record: {
368
+ type: 'object',
369
+ properties: {
370
+ string: {
371
+ type: 'string',
372
+ const: 'a',
373
+ },
374
+ },
375
+ },
376
+ },
377
+ },
378
+ },
379
+ ]