@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/src/validation.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Lexicons } from './lexicons'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
LexRecord,
|
|
4
|
+
LexXrpcProcedure,
|
|
5
|
+
LexXrpcQuery,
|
|
6
|
+
LexXrpcSubscription,
|
|
7
|
+
} from './types'
|
|
3
8
|
import { assertValidOneOf } from './util'
|
|
4
9
|
|
|
5
10
|
import * as ComplexValidators from './validators/complex'
|
|
@@ -12,16 +17,18 @@ export function assertValidRecord(
|
|
|
12
17
|
) {
|
|
13
18
|
const res = ComplexValidators.object(lexicons, 'Record', def.record, value)
|
|
14
19
|
if (!res.success) throw res.error
|
|
20
|
+
return res.value
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
export function assertValidXrpcParams(
|
|
18
24
|
lexicons: Lexicons,
|
|
19
|
-
def: LexXrpcProcedure | LexXrpcQuery,
|
|
25
|
+
def: LexXrpcProcedure | LexXrpcQuery | LexXrpcSubscription,
|
|
20
26
|
value: unknown,
|
|
21
27
|
) {
|
|
22
28
|
if (def.parameters) {
|
|
23
29
|
const res = XrpcValidators.params(lexicons, 'Params', def.parameters, value)
|
|
24
30
|
if (!res.success) throw res.error
|
|
31
|
+
return res.value
|
|
25
32
|
}
|
|
26
33
|
}
|
|
27
34
|
|
|
@@ -32,7 +39,7 @@ export function assertValidXrpcInput(
|
|
|
32
39
|
) {
|
|
33
40
|
if (def.input?.schema) {
|
|
34
41
|
// loop: all input schema definitions
|
|
35
|
-
assertValidOneOf(lexicons, 'Input', def.input.schema, value, true)
|
|
42
|
+
return assertValidOneOf(lexicons, 'Input', def.input.schema, value, true)
|
|
36
43
|
}
|
|
37
44
|
}
|
|
38
45
|
|
|
@@ -43,6 +50,23 @@ export function assertValidXrpcOutput(
|
|
|
43
50
|
) {
|
|
44
51
|
if (def.output?.schema) {
|
|
45
52
|
// loop: all output schema definitions
|
|
46
|
-
assertValidOneOf(lexicons, 'Output', def.output.schema, value, true)
|
|
53
|
+
return assertValidOneOf(lexicons, 'Output', def.output.schema, value, true)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function assertValidXrpcMessage(
|
|
58
|
+
lexicons: Lexicons,
|
|
59
|
+
def: LexXrpcSubscription,
|
|
60
|
+
value: unknown,
|
|
61
|
+
) {
|
|
62
|
+
if (def.message?.schema) {
|
|
63
|
+
// loop: all output schema definitions
|
|
64
|
+
return assertValidOneOf(
|
|
65
|
+
lexicons,
|
|
66
|
+
'Message',
|
|
67
|
+
def.message.schema,
|
|
68
|
+
value,
|
|
69
|
+
true,
|
|
70
|
+
)
|
|
47
71
|
}
|
|
48
72
|
}
|
package/src/validators/blob.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { BlobRef } from '../blob-refs'
|
|
1
2
|
import { Lexicons } from '../lexicons'
|
|
2
3
|
import { LexUserType, ValidationResult, ValidationError } from '../types'
|
|
3
|
-
import { isObj, hasProp } from '../types'
|
|
4
4
|
|
|
5
5
|
export function blob(
|
|
6
6
|
lexicons: Lexicons,
|
|
@@ -8,50 +8,12 @@ export function blob(
|
|
|
8
8
|
def: LexUserType,
|
|
9
9
|
value: unknown,
|
|
10
10
|
): ValidationResult {
|
|
11
|
-
|
|
11
|
+
// check
|
|
12
|
+
if (!value || !(value instanceof BlobRef)) {
|
|
12
13
|
return {
|
|
13
14
|
success: false,
|
|
14
|
-
error: new ValidationError(`${path} should be
|
|
15
|
+
error: new ValidationError(`${path} should be a blob ref`),
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
success: false,
|
|
20
|
-
error: new ValidationError(`${path}/cid should be a string`),
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
if (!hasProp(value, 'mimeType') || typeof value.mimeType !== 'string') {
|
|
24
|
-
return {
|
|
25
|
-
success: false,
|
|
26
|
-
error: new ValidationError(`${path}/mimeType should be a string`),
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return { success: true }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function image(
|
|
33
|
-
lexicons: Lexicons,
|
|
34
|
-
path: string,
|
|
35
|
-
def: LexUserType,
|
|
36
|
-
value: unknown,
|
|
37
|
-
): ValidationResult {
|
|
38
|
-
return blob(lexicons, path, def, value)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function video(
|
|
42
|
-
lexicons: Lexicons,
|
|
43
|
-
path: string,
|
|
44
|
-
def: LexUserType,
|
|
45
|
-
value: unknown,
|
|
46
|
-
): ValidationResult {
|
|
47
|
-
return blob(lexicons, path, def, value)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function audio(
|
|
51
|
-
lexicons: Lexicons,
|
|
52
|
-
path: string,
|
|
53
|
-
def: LexUserType,
|
|
54
|
-
value: unknown,
|
|
55
|
-
): ValidationResult {
|
|
56
|
-
return blob(lexicons, path, def, value)
|
|
18
|
+
return { success: true, value }
|
|
57
19
|
}
|
|
@@ -20,14 +20,14 @@ export function validate(
|
|
|
20
20
|
switch (def.type) {
|
|
21
21
|
case 'boolean':
|
|
22
22
|
return Primitives.boolean(lexicons, path, def, value)
|
|
23
|
-
case 'number':
|
|
24
|
-
return Primitives.number(lexicons, path, def, value)
|
|
25
23
|
case 'integer':
|
|
26
24
|
return Primitives.integer(lexicons, path, def, value)
|
|
27
25
|
case 'string':
|
|
28
26
|
return Primitives.string(lexicons, path, def, value)
|
|
29
|
-
case '
|
|
30
|
-
return Primitives.
|
|
27
|
+
case 'bytes':
|
|
28
|
+
return Primitives.bytes(lexicons, path, def, value)
|
|
29
|
+
case 'cid-link':
|
|
30
|
+
return Primitives.cidLink(lexicons, path, def, value)
|
|
31
31
|
case 'unknown':
|
|
32
32
|
return Primitives.unknown(lexicons, path, def, value)
|
|
33
33
|
case 'object':
|
|
@@ -36,12 +36,6 @@ export function validate(
|
|
|
36
36
|
return array(lexicons, path, def, value)
|
|
37
37
|
case 'blob':
|
|
38
38
|
return Blob.blob(lexicons, path, def, value)
|
|
39
|
-
case 'image':
|
|
40
|
-
return Blob.image(lexicons, path, def, value)
|
|
41
|
-
case 'video':
|
|
42
|
-
return Blob.video(lexicons, path, def, value)
|
|
43
|
-
case 'audio':
|
|
44
|
-
return Blob.audio(lexicons, path, def, value)
|
|
45
39
|
default:
|
|
46
40
|
return {
|
|
47
41
|
success: false,
|
|
@@ -53,11 +47,9 @@ export function validate(
|
|
|
53
47
|
export function array(
|
|
54
48
|
lexicons: Lexicons,
|
|
55
49
|
path: string,
|
|
56
|
-
def:
|
|
50
|
+
def: LexArray,
|
|
57
51
|
value: unknown,
|
|
58
52
|
): ValidationResult {
|
|
59
|
-
def = def as LexArray
|
|
60
|
-
|
|
61
53
|
// type
|
|
62
54
|
if (!Array.isArray(value)) {
|
|
63
55
|
return {
|
|
@@ -101,7 +93,7 @@ export function array(
|
|
|
101
93
|
}
|
|
102
94
|
}
|
|
103
95
|
|
|
104
|
-
return { success: true }
|
|
96
|
+
return { success: true, value }
|
|
105
97
|
}
|
|
106
98
|
|
|
107
99
|
export function object(
|
|
@@ -120,33 +112,40 @@ export function object(
|
|
|
120
112
|
}
|
|
121
113
|
}
|
|
122
114
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
for (const key of def.required) {
|
|
126
|
-
if (!(key in value)) {
|
|
127
|
-
return {
|
|
128
|
-
success: false,
|
|
129
|
-
error: new ValidationError(`${path} must have the property "${key}"`),
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
115
|
+
const requiredProps = new Set(def.required)
|
|
116
|
+
const nullableProps = new Set(def.nullable)
|
|
134
117
|
|
|
135
118
|
// properties
|
|
119
|
+
let resultValue = value
|
|
136
120
|
if (typeof def.properties === 'object') {
|
|
137
121
|
for (const key in def.properties) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
continue // skip- if required, will have already failed
|
|
122
|
+
if (value[key] === null && nullableProps.has(key)) {
|
|
123
|
+
continue
|
|
141
124
|
}
|
|
142
125
|
const propDef = def.properties[key]
|
|
143
126
|
const propPath = `${path}/${key}`
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
127
|
+
const validated = validateOneOf(lexicons, propPath, propDef, value[key])
|
|
128
|
+
const propValue = validated.success ? validated.value : value[key]
|
|
129
|
+
const propIsUndefined = typeof propValue === 'undefined'
|
|
130
|
+
// Return error for bad validation, giving required rule precedence
|
|
131
|
+
if (propIsUndefined && requiredProps.has(key)) {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
error: new ValidationError(`${path} must have the property "${key}"`),
|
|
135
|
+
}
|
|
136
|
+
} else if (!propIsUndefined && !validated.success) {
|
|
137
|
+
return validated
|
|
138
|
+
}
|
|
139
|
+
// Adjust value based on e.g. applied defaults, cloning shallowly if there was a changed value
|
|
140
|
+
if (propValue !== value[key]) {
|
|
141
|
+
if (resultValue === value) {
|
|
142
|
+
// Lazy shallow clone
|
|
143
|
+
resultValue = { ...value }
|
|
144
|
+
}
|
|
145
|
+
resultValue[key] = propValue
|
|
147
146
|
}
|
|
148
147
|
}
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
return { success: true }
|
|
150
|
+
return { success: true, value: resultValue }
|
|
152
151
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { ensureValidAtUri } from '@atproto/uri'
|
|
2
|
+
import { isValidISODateString } from 'iso-datestring-validator'
|
|
3
|
+
import { CID } from 'multiformats/cid'
|
|
4
|
+
import { ValidationResult, ValidationError } from '../types'
|
|
5
|
+
import { ensureValidDid, ensureValidHandle } from '@atproto/identifier'
|
|
6
|
+
import { ensureValidNsid } from '@atproto/nsid'
|
|
7
|
+
import { validateLanguage } from '@atproto/common-web'
|
|
8
|
+
|
|
9
|
+
export function datetime(path: string, value: string): ValidationResult {
|
|
10
|
+
try {
|
|
11
|
+
if (!isValidISODateString(value)) {
|
|
12
|
+
throw new Error()
|
|
13
|
+
}
|
|
14
|
+
} catch {
|
|
15
|
+
return {
|
|
16
|
+
success: false,
|
|
17
|
+
error: new ValidationError(
|
|
18
|
+
`${path} must be an iso8601 formatted datetime`,
|
|
19
|
+
),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { success: true, value }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function uri(path: string, value: string): ValidationResult {
|
|
26
|
+
const isUri = value.match(/^\w+:(?:\/\/)?[^\s/][^\s]*$/) !== null
|
|
27
|
+
if (!isUri) {
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
error: new ValidationError(`${path} must be a uri`),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { success: true, value }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function atUri(path: string, value: string): ValidationResult {
|
|
37
|
+
try {
|
|
38
|
+
ensureValidAtUri(value)
|
|
39
|
+
} catch {
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
error: new ValidationError(`${path} must be a valid at-uri`),
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { success: true, value }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function did(path: string, value: string): ValidationResult {
|
|
49
|
+
try {
|
|
50
|
+
ensureValidDid(value)
|
|
51
|
+
} catch {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: new ValidationError(`${path} must be a valid did`),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { success: true, value }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function handle(path: string, value: string): ValidationResult {
|
|
61
|
+
try {
|
|
62
|
+
ensureValidHandle(value)
|
|
63
|
+
} catch {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: new ValidationError(`${path} must be a valid handle`),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { success: true, value }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function atIdentifier(path: string, value: string): ValidationResult {
|
|
73
|
+
const isDid = did(path, value)
|
|
74
|
+
if (!isDid.success) {
|
|
75
|
+
const isHandle = handle(path, value)
|
|
76
|
+
if (!isHandle.success) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: new ValidationError(`${path} must be a valid did or a handle`),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { success: true, value }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function nsid(path: string, value: string): ValidationResult {
|
|
87
|
+
try {
|
|
88
|
+
ensureValidNsid(value)
|
|
89
|
+
} catch {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
error: new ValidationError(`${path} must be a valid nsid`),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { success: true, value }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function cid(path: string, value: string): ValidationResult {
|
|
99
|
+
try {
|
|
100
|
+
CID.parse(value)
|
|
101
|
+
} catch {
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
error: new ValidationError(`${path} must be a cid string`),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { success: true, value }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// The language format validates well-formed BCP 47 language tags: https://www.rfc-editor.org/info/bcp47
|
|
111
|
+
export function language(path: string, value: string): ValidationResult {
|
|
112
|
+
if (validateLanguage(value)) {
|
|
113
|
+
return { success: true, value }
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
error: new ValidationError(
|
|
118
|
+
`${path} must be a well-formed BCP 47 language tag`,
|
|
119
|
+
),
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { utf8Len, graphemeLen } from '@atproto/common-web'
|
|
2
|
+
import { CID } from 'multiformats/cid'
|
|
2
3
|
import { Lexicons } from '../lexicons'
|
|
4
|
+
import * as formats from './formats'
|
|
3
5
|
import {
|
|
4
6
|
LexUserType,
|
|
5
7
|
LexBoolean,
|
|
6
|
-
LexNumber,
|
|
7
8
|
LexInteger,
|
|
8
9
|
LexString,
|
|
9
|
-
LexDatetime,
|
|
10
10
|
ValidationResult,
|
|
11
11
|
ValidationError,
|
|
12
|
+
LexBytes,
|
|
12
13
|
} from '../types'
|
|
13
14
|
|
|
14
15
|
export function validate(
|
|
@@ -20,14 +21,14 @@ export function validate(
|
|
|
20
21
|
switch (def.type) {
|
|
21
22
|
case 'boolean':
|
|
22
23
|
return boolean(lexicons, path, def, value)
|
|
23
|
-
case 'number':
|
|
24
|
-
return number(lexicons, path, def, value)
|
|
25
24
|
case 'integer':
|
|
26
25
|
return integer(lexicons, path, def, value)
|
|
27
26
|
case 'string':
|
|
28
27
|
return string(lexicons, path, def, value)
|
|
29
|
-
case '
|
|
30
|
-
return
|
|
28
|
+
case 'bytes':
|
|
29
|
+
return bytes(lexicons, path, def, value)
|
|
30
|
+
case 'cid-link':
|
|
31
|
+
return cidLink(lexicons, path, def, value)
|
|
31
32
|
case 'unknown':
|
|
32
33
|
return unknown(lexicons, path, def, value)
|
|
33
34
|
default:
|
|
@@ -48,9 +49,9 @@ export function boolean(
|
|
|
48
49
|
|
|
49
50
|
// type
|
|
50
51
|
const type = typeof value
|
|
51
|
-
if (type
|
|
52
|
+
if (type === 'undefined') {
|
|
52
53
|
if (typeof def.default === 'boolean') {
|
|
53
|
-
return { success: true }
|
|
54
|
+
return { success: true, value: def.default }
|
|
54
55
|
}
|
|
55
56
|
return {
|
|
56
57
|
success: false,
|
|
@@ -73,31 +74,31 @@ export function boolean(
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
return { success: true }
|
|
77
|
+
return { success: true, value }
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
export function
|
|
80
|
+
export function integer(
|
|
80
81
|
lexicons: Lexicons,
|
|
81
82
|
path: string,
|
|
82
83
|
def: LexUserType,
|
|
83
84
|
value: unknown,
|
|
84
85
|
): ValidationResult {
|
|
85
|
-
def = def as
|
|
86
|
+
def = def as LexInteger
|
|
86
87
|
|
|
87
88
|
// type
|
|
88
89
|
const type = typeof value
|
|
89
|
-
if (type
|
|
90
|
+
if (type === 'undefined') {
|
|
90
91
|
if (typeof def.default === 'number') {
|
|
91
|
-
return { success: true }
|
|
92
|
+
return { success: true, value: def.default }
|
|
92
93
|
}
|
|
93
94
|
return {
|
|
94
95
|
success: false,
|
|
95
|
-
error: new ValidationError(`${path} must be
|
|
96
|
+
error: new ValidationError(`${path} must be an integer`),
|
|
96
97
|
}
|
|
97
|
-
} else if (
|
|
98
|
+
} else if (!Number.isInteger(value)) {
|
|
98
99
|
return {
|
|
99
100
|
success: false,
|
|
100
|
-
error: new ValidationError(`${path} must be
|
|
101
|
+
error: new ValidationError(`${path} must be an integer`),
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -147,32 +148,7 @@ export function number(
|
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
return { success: true }
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function integer(
|
|
154
|
-
lexicons: Lexicons,
|
|
155
|
-
path: string,
|
|
156
|
-
def: LexUserType,
|
|
157
|
-
value: unknown,
|
|
158
|
-
): ValidationResult {
|
|
159
|
-
def = def as LexInteger
|
|
160
|
-
|
|
161
|
-
// run number validation
|
|
162
|
-
const numRes = number(lexicons, path, def, value)
|
|
163
|
-
if (!numRes.success) {
|
|
164
|
-
return numRes
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// whole numbers only
|
|
168
|
-
if (!Number.isInteger(value)) {
|
|
169
|
-
return {
|
|
170
|
-
success: false,
|
|
171
|
-
error: new ValidationError(`${path} must be an integer`),
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return { success: true }
|
|
151
|
+
return { success: true, value }
|
|
176
152
|
}
|
|
177
153
|
|
|
178
154
|
export function string(
|
|
@@ -184,16 +160,15 @@ export function string(
|
|
|
184
160
|
def = def as LexString
|
|
185
161
|
|
|
186
162
|
// type
|
|
187
|
-
|
|
188
|
-
if (type == 'undefined') {
|
|
163
|
+
if (typeof value === 'undefined') {
|
|
189
164
|
if (typeof def.default === 'string') {
|
|
190
|
-
return { success: true }
|
|
165
|
+
return { success: true, value: def.default }
|
|
191
166
|
}
|
|
192
167
|
return {
|
|
193
168
|
success: false,
|
|
194
169
|
error: new ValidationError(`${path} must be a string`),
|
|
195
170
|
}
|
|
196
|
-
} else if (
|
|
171
|
+
} else if (typeof value !== 'string') {
|
|
197
172
|
return {
|
|
198
173
|
success: false,
|
|
199
174
|
error: new ValidationError(`${path} must be a string`),
|
|
@@ -224,7 +199,7 @@ export function string(
|
|
|
224
199
|
|
|
225
200
|
// maxLength
|
|
226
201
|
if (typeof def.maxLength === 'number') {
|
|
227
|
-
if ((value
|
|
202
|
+
if (utf8Len(value) > def.maxLength) {
|
|
228
203
|
return {
|
|
229
204
|
success: false,
|
|
230
205
|
error: new ValidationError(
|
|
@@ -236,7 +211,7 @@ export function string(
|
|
|
236
211
|
|
|
237
212
|
// minLength
|
|
238
213
|
if (typeof def.minLength === 'number') {
|
|
239
|
-
if ((value
|
|
214
|
+
if (utf8Len(value) < def.minLength) {
|
|
240
215
|
return {
|
|
241
216
|
success: false,
|
|
242
217
|
error: new ValidationError(
|
|
@@ -246,40 +221,112 @@ export function string(
|
|
|
246
221
|
}
|
|
247
222
|
}
|
|
248
223
|
|
|
249
|
-
|
|
224
|
+
// maxGraphemes
|
|
225
|
+
if (typeof def.maxGraphemes === 'number') {
|
|
226
|
+
if (graphemeLen(value) > def.maxGraphemes) {
|
|
227
|
+
return {
|
|
228
|
+
success: false,
|
|
229
|
+
error: new ValidationError(
|
|
230
|
+
`${path} must not be longer than ${def.maxGraphemes} graphemes`,
|
|
231
|
+
),
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// minGraphemes
|
|
237
|
+
if (typeof def.minGraphemes === 'number') {
|
|
238
|
+
if (graphemeLen(value) < def.minGraphemes) {
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
error: new ValidationError(
|
|
242
|
+
`${path} must not be shorter than ${def.minGraphemes} graphemes`,
|
|
243
|
+
),
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (typeof def.format === 'string') {
|
|
249
|
+
switch (def.format) {
|
|
250
|
+
case 'datetime':
|
|
251
|
+
return formats.datetime(path, value)
|
|
252
|
+
case 'uri':
|
|
253
|
+
return formats.uri(path, value)
|
|
254
|
+
case 'at-uri':
|
|
255
|
+
return formats.atUri(path, value)
|
|
256
|
+
case 'did':
|
|
257
|
+
return formats.did(path, value)
|
|
258
|
+
case 'handle':
|
|
259
|
+
return formats.handle(path, value)
|
|
260
|
+
case 'at-identifier':
|
|
261
|
+
return formats.atIdentifier(path, value)
|
|
262
|
+
case 'nsid':
|
|
263
|
+
return formats.nsid(path, value)
|
|
264
|
+
case 'cid':
|
|
265
|
+
return formats.cid(path, value)
|
|
266
|
+
case 'language':
|
|
267
|
+
return formats.language(path, value)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { success: true, value }
|
|
250
272
|
}
|
|
251
273
|
|
|
252
|
-
export function
|
|
274
|
+
export function bytes(
|
|
253
275
|
lexicons: Lexicons,
|
|
254
276
|
path: string,
|
|
255
277
|
def: LexUserType,
|
|
256
278
|
value: unknown,
|
|
257
279
|
): ValidationResult {
|
|
258
|
-
def = def as
|
|
280
|
+
def = def as LexBytes
|
|
259
281
|
|
|
260
|
-
|
|
261
|
-
const type = typeof value
|
|
262
|
-
if (type !== 'string') {
|
|
282
|
+
if (!value || !(value instanceof Uint8Array)) {
|
|
263
283
|
return {
|
|
264
284
|
success: false,
|
|
265
|
-
error: new ValidationError(`${path} must be a
|
|
285
|
+
error: new ValidationError(`${path} must be a byte array`),
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// maxLength
|
|
290
|
+
if (typeof def.maxLength === 'number') {
|
|
291
|
+
if (value.byteLength > def.maxLength) {
|
|
292
|
+
return {
|
|
293
|
+
success: false,
|
|
294
|
+
error: new ValidationError(
|
|
295
|
+
`${path} must not be larger than ${def.maxLength} bytes`,
|
|
296
|
+
),
|
|
297
|
+
}
|
|
266
298
|
}
|
|
267
299
|
}
|
|
268
300
|
|
|
269
|
-
//
|
|
270
|
-
{
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
301
|
+
// minLength
|
|
302
|
+
if (typeof def.minLength === 'number') {
|
|
303
|
+
if (value.byteLength < def.minLength) {
|
|
304
|
+
return {
|
|
305
|
+
success: false,
|
|
306
|
+
error: new ValidationError(
|
|
307
|
+
`${path} must not be smaller than ${def.minLength} bytes`,
|
|
308
|
+
),
|
|
276
309
|
}
|
|
277
|
-
} catch {
|
|
278
|
-
throw new ValidationError(`${path} must be an iso8601 formatted datetime`)
|
|
279
310
|
}
|
|
280
311
|
}
|
|
281
312
|
|
|
282
|
-
return { success: true }
|
|
313
|
+
return { success: true, value }
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function cidLink(
|
|
317
|
+
lexicons: Lexicons,
|
|
318
|
+
path: string,
|
|
319
|
+
def: LexUserType,
|
|
320
|
+
value: unknown,
|
|
321
|
+
): ValidationResult {
|
|
322
|
+
if (CID.asCID(value) === null) {
|
|
323
|
+
return {
|
|
324
|
+
success: false,
|
|
325
|
+
error: new ValidationError(`${path} must be a CID`),
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return { success: true, value }
|
|
283
330
|
}
|
|
284
331
|
|
|
285
332
|
export function unknown(
|
|
@@ -296,5 +343,5 @@ export function unknown(
|
|
|
296
343
|
}
|
|
297
344
|
}
|
|
298
345
|
|
|
299
|
-
return { success: true }
|
|
346
|
+
return { success: true, value }
|
|
300
347
|
}
|