@atproto/lexicon 0.1.0 → 0.2.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.
package/src/util.ts CHANGED
@@ -7,8 +7,13 @@ import {
7
7
  ValidationResult,
8
8
  isDiscriminatedObject,
9
9
  } from './types'
10
+ import { z } from 'zod'
10
11
 
11
12
  export function toLexUri(str: string, baseUri?: string): string {
13
+ if (str.split('#').length > 2) {
14
+ throw new Error('Uri can only have one hash segment')
15
+ }
16
+
12
17
  if (str.startsWith('lex:')) {
13
18
  return str
14
19
  }
@@ -40,7 +45,7 @@ export function validateOneOf(
40
45
  ),
41
46
  }
42
47
  }
43
- if (!def.refs.includes(toLexUri(value.$type))) {
48
+ if (!refsContainType(def.refs, value.$type)) {
44
49
  if (def.closed) {
45
50
  return {
46
51
  success: false,
@@ -104,3 +109,58 @@ export function toConcreteTypes(
104
109
  return [def]
105
110
  }
106
111
  }
112
+
113
+ export function requiredPropertiesRefinement<
114
+ ObjectType extends {
115
+ required?: string[]
116
+ properties?: Record<string, unknown>
117
+ },
118
+ >(object: ObjectType, ctx: z.RefinementCtx) {
119
+ // Required fields check
120
+ if (object.required === undefined) {
121
+ return
122
+ }
123
+
124
+ if (!Array.isArray(object.required)) {
125
+ ctx.addIssue({
126
+ code: z.ZodIssueCode.invalid_type,
127
+ received: typeof object.required,
128
+ expected: 'array',
129
+ })
130
+ return
131
+ }
132
+
133
+ if (object.properties === undefined) {
134
+ if (object.required.length > 0) {
135
+ ctx.addIssue({
136
+ code: z.ZodIssueCode.custom,
137
+ message: `Required fields defined but no properties defined`,
138
+ })
139
+ }
140
+ return
141
+ }
142
+
143
+ for (const field of object.required) {
144
+ if (object.properties[field] === undefined) {
145
+ ctx.addIssue({
146
+ code: z.ZodIssueCode.custom,
147
+ message: `Required field "${field}" not defined`,
148
+ })
149
+ }
150
+ }
151
+ }
152
+
153
+ // to avoid bugs like #0189 this needs to handle both
154
+ // explicit and implicit #main
155
+ const refsContainType = (refs: string[], type: string) => {
156
+ const lexUri = toLexUri(type)
157
+ if (refs.includes(lexUri)) {
158
+ return true
159
+ }
160
+
161
+ if (lexUri.endsWith('#main')) {
162
+ return refs.includes(lexUri.replace('#main', ''))
163
+ } else {
164
+ return refs.includes(lexUri + '#main')
165
+ }
166
+ }
@@ -20,8 +20,6 @@ export function validate(
20
20
  switch (def.type) {
21
21
  case 'boolean':
22
22
  return Primitives.boolean(lexicons, path, def, value)
23
- case 'float':
24
- return Primitives.float(lexicons, path, def, value)
25
23
  case 'integer':
26
24
  return Primitives.integer(lexicons, path, def, value)
27
25
  case 'string':
@@ -1,9 +1,13 @@
1
- import { ensureValidAtUri } from '@atproto/uri'
2
1
  import { isValidISODateString } from 'iso-datestring-validator'
3
2
  import { CID } from 'multiformats/cid'
4
3
  import { ValidationResult, ValidationError } from '../types'
5
- import { ensureValidDid, ensureValidHandle } from '@atproto/identifier'
6
- import { ensureValidNsid } from '@atproto/nsid'
4
+ import {
5
+ ensureValidDid,
6
+ ensureValidHandle,
7
+ ensureValidNsid,
8
+ ensureValidAtUri,
9
+ } from '@atproto/syntax'
10
+ import { validateLanguage } from '@atproto/common-web'
7
11
 
8
12
  export function datetime(path: string, value: string): ValidationResult {
9
13
  try {
@@ -105,3 +109,16 @@ export function cid(path: string, value: string): ValidationResult {
105
109
  }
106
110
  return { success: true, value }
107
111
  }
112
+
113
+ // The language format validates well-formed BCP 47 language tags: https://www.rfc-editor.org/info/bcp47
114
+ export function language(path: string, value: string): ValidationResult {
115
+ if (validateLanguage(value)) {
116
+ return { success: true, value }
117
+ }
118
+ return {
119
+ success: false,
120
+ error: new ValidationError(
121
+ `${path} must be a well-formed BCP 47 language tag`,
122
+ ),
123
+ }
124
+ }
@@ -5,7 +5,6 @@ import * as formats from './formats'
5
5
  import {
6
6
  LexUserType,
7
7
  LexBoolean,
8
- LexFloat,
9
8
  LexInteger,
10
9
  LexString,
11
10
  ValidationResult,
@@ -22,8 +21,6 @@ export function validate(
22
21
  switch (def.type) {
23
22
  case 'boolean':
24
23
  return boolean(lexicons, path, def, value)
25
- case 'float':
26
- return float(lexicons, path, def, value)
27
24
  case 'integer':
28
25
  return integer(lexicons, path, def, value)
29
26
  case 'string':
@@ -80,13 +77,13 @@ export function boolean(
80
77
  return { success: true, value }
81
78
  }
82
79
 
83
- export function float(
80
+ export function integer(
84
81
  lexicons: Lexicons,
85
82
  path: string,
86
83
  def: LexUserType,
87
84
  value: unknown,
88
85
  ): ValidationResult {
89
- def = def as LexFloat
86
+ def = def as LexInteger
90
87
 
91
88
  // type
92
89
  const type = typeof value
@@ -96,12 +93,12 @@ export function float(
96
93
  }
97
94
  return {
98
95
  success: false,
99
- error: new ValidationError(`${path} must be a number`),
96
+ error: new ValidationError(`${path} must be an integer`),
100
97
  }
101
- } else if (type !== 'number') {
98
+ } else if (!Number.isInteger(value)) {
102
99
  return {
103
100
  success: false,
104
- error: new ValidationError(`${path} must be a number`),
101
+ error: new ValidationError(`${path} must be an integer`),
105
102
  }
106
103
  }
107
104
 
@@ -154,33 +151,6 @@ export function float(
154
151
  return { success: true, value }
155
152
  }
156
153
 
157
- export function integer(
158
- lexicons: Lexicons,
159
- path: string,
160
- def: LexUserType,
161
- value: unknown,
162
- ): ValidationResult {
163
- def = def as LexInteger
164
-
165
- // run number validation
166
- const numRes = float(lexicons, path, def, value)
167
- if (!numRes.success) {
168
- return numRes
169
- } else {
170
- value = numRes.value
171
- }
172
-
173
- // whole numbers only
174
- if (!Number.isInteger(value)) {
175
- return {
176
- success: false,
177
- error: new ValidationError(`${path} must be an integer`),
178
- }
179
- }
180
-
181
- return { success: true, value }
182
- }
183
-
184
154
  export function string(
185
155
  lexicons: Lexicons,
186
156
  path: string,
@@ -293,6 +263,8 @@ export function string(
293
263
  return formats.nsid(path, value)
294
264
  case 'cid':
295
265
  return formats.cid(path, value)
266
+ case 'language':
267
+ return formats.language(path, value)
296
268
  }
297
269
  }
298
270
 
@@ -13,7 +13,6 @@ export default [
13
13
  'object',
14
14
  'array',
15
15
  'boolean',
16
- 'float',
17
16
  'integer',
18
17
  'string',
19
18
  'bytes',
@@ -23,7 +22,6 @@ export default [
23
22
  object: { type: 'ref', ref: '#object' },
24
23
  array: { type: 'array', items: { type: 'string' } },
25
24
  boolean: { type: 'boolean' },
26
- float: { type: 'float' },
27
25
  integer: { type: 'integer' },
28
26
  string: { type: 'string' },
29
27
  bytes: { type: 'bytes' },
@@ -33,12 +31,11 @@ export default [
33
31
  },
34
32
  object: {
35
33
  type: 'object',
36
- required: ['object', 'array', 'boolean', 'float', 'integer', 'string'],
34
+ required: ['object', 'array', 'boolean', 'integer', 'string'],
37
35
  properties: {
38
36
  object: { type: 'ref', ref: '#subobject' },
39
37
  array: { type: 'array', items: { type: 'string' } },
40
38
  boolean: { type: 'boolean' },
41
- float: { type: 'float' },
42
39
  integer: { type: 'integer' },
43
40
  string: { type: 'string' },
44
41
  },
@@ -61,10 +58,9 @@ export default [
61
58
  description: 'A query',
62
59
  parameters: {
63
60
  type: 'params',
64
- required: ['boolean', 'float', 'integer'],
61
+ required: ['boolean', 'integer'],
65
62
  properties: {
66
63
  boolean: { type: 'boolean' },
67
- float: { type: 'float' },
68
64
  integer: { type: 'integer' },
69
65
  string: { type: 'string' },
70
66
  array: { type: 'array', items: { type: 'string' } },
@@ -87,10 +83,9 @@ export default [
87
83
  description: 'A procedure',
88
84
  parameters: {
89
85
  type: 'params',
90
- required: ['boolean', 'float', 'integer'],
86
+ required: ['boolean', 'integer'],
91
87
  properties: {
92
88
  boolean: { type: 'boolean' },
93
- float: { type: 'float' },
94
89
  integer: { type: 'integer' },
95
90
  string: { type: 'string' },
96
91
  array: { type: 'array', items: { type: 'string' } },
@@ -119,7 +114,6 @@ export default [
119
114
  object: { type: 'ref', ref: 'com.example.kitchenSink#object' },
120
115
  array: { type: 'array', items: { type: 'string' } },
121
116
  boolean: { type: 'boolean' },
122
- float: { type: 'float' },
123
117
  integer: { type: 'integer' },
124
118
  string: { type: 'string' },
125
119
  },
@@ -138,7 +132,6 @@ export default [
138
132
  required: ['boolean'],
139
133
  properties: {
140
134
  boolean: { type: 'boolean', default: false },
141
- float: { type: 'float', default: 0 },
142
135
  integer: { type: 'integer', default: 0 },
143
136
  string: { type: 'string', default: '' },
144
137
  object: { type: 'ref', ref: '#object' },
@@ -149,7 +142,6 @@ export default [
149
142
  type: 'object',
150
143
  properties: {
151
144
  boolean: { type: 'boolean', default: true },
152
- float: { type: 'float', default: 1.5 },
153
145
  integer: { type: 'integer', default: 1 },
154
146
  string: { type: 'string', default: 'x' },
155
147
  },
@@ -220,7 +212,7 @@ export default [
220
212
  type: 'array',
221
213
  minLength: 2,
222
214
  maxLength: 4,
223
- items: { type: 'float' },
215
+ items: { type: 'integer' },
224
216
  },
225
217
  },
226
218
  },
@@ -245,61 +237,6 @@ export default [
245
237
  },
246
238
  },
247
239
  },
248
- {
249
- lexicon: 1,
250
- id: 'com.example.floatRange',
251
- defs: {
252
- main: {
253
- type: 'record',
254
- record: {
255
- type: 'object',
256
- properties: {
257
- float: {
258
- type: 'float',
259
- minimum: 2,
260
- maximum: 4,
261
- },
262
- },
263
- },
264
- },
265
- },
266
- },
267
- {
268
- lexicon: 1,
269
- id: 'com.example.floatEnum',
270
- defs: {
271
- main: {
272
- type: 'record',
273
- record: {
274
- type: 'object',
275
- properties: {
276
- float: {
277
- type: 'float',
278
- enum: [1, 1.5, 2],
279
- },
280
- },
281
- },
282
- },
283
- },
284
- },
285
- {
286
- lexicon: 1,
287
- id: 'com.example.floatConst',
288
- defs: {
289
- main: {
290
- type: 'record',
291
- record: {
292
- type: 'object',
293
- properties: {
294
- float: {
295
- type: 'float',
296
- const: 0,
297
- },
298
- },
299
- },
300
- },
301
- },
302
- },
303
240
  {
304
241
  lexicon: 1,
305
242
  id: 'com.example.integerRange',
@@ -549,6 +486,21 @@ export default [
549
486
  },
550
487
  },
551
488
  },
489
+ {
490
+ lexicon: 1,
491
+ id: 'com.example.language',
492
+ defs: {
493
+ main: {
494
+ type: 'record',
495
+ record: {
496
+ type: 'object',
497
+ properties: {
498
+ language: { type: 'string', format: 'language' },
499
+ },
500
+ },
501
+ },
502
+ },
503
+ },
552
504
  {
553
505
  lexicon: 1,
554
506
  id: 'com.example.byteLength',