···
1
1
+
// IMPORTS ---------------------------------------------------------------------
2
2
+
3
3
+
import at/cbor
4
4
+
import at/cid.{type Cid}
5
5
+
import gleam/bit_array
6
6
+
import gleam/dynamic/decode.{type Decoder}
7
7
+
import gleam/int
8
8
+
import gleam/list
9
9
+
10
10
+
// TYPES -----------------------------------------------------------------------
11
11
+
12
12
+
///
13
13
+
///
14
14
+
pub opaque type Car {
15
15
+
Car(roots: List(Cid), blocks: List(Block))
16
16
+
}
17
17
+
18
18
+
type CarHeader {
19
19
+
CarHeader(version: Int, roots: List(Cid))
20
20
+
}
21
21
+
22
22
+
///
23
23
+
///
24
24
+
pub type Block {
25
25
+
Block(cid: Cid, data: BitArray)
26
26
+
}
27
27
+
28
28
+
/// Errors that can occur while decoding a [`Car`](#Car) from a `BitArray`.
29
29
+
///
30
30
+
pub type DecodeError {
31
31
+
MalformedInput
32
32
+
UnexpectedEndOfInput
33
33
+
UnsupportedVersion(Int)
34
34
+
InvalidHeader(cbor.DecodeError)
35
35
+
InvalidCid
36
36
+
}
37
37
+
38
38
+
// CONSTRUCTORS ----------------------------------------------------------------
39
39
+
40
40
+
///
41
41
+
///
42
42
+
pub fn from_bit_array(bits: BitArray) -> Result(Car, DecodeError) {
43
43
+
case parse_varint(bits) {
44
44
+
Ok(#(0, _)) -> Error(MalformedInput)
45
45
+
46
46
+
Ok(#(length, remaining)) ->
47
47
+
case remaining {
48
48
+
<<header:bytes-size(length), remaining:bits>> ->
49
49
+
case cbor.parse(header, header_decoder()) {
50
50
+
Ok(CarHeader(version: 1, roots:)) ->
51
51
+
case parse_blocks(remaining, []) {
52
52
+
Ok(blocks) -> Ok(Car(roots:, blocks:))
53
53
+
Error(reason) -> Error(reason)
54
54
+
}
55
55
+
56
56
+
Ok(CarHeader(version:, ..)) -> Error(UnsupportedVersion(version))
57
57
+
58
58
+
Error(reason) -> Error(InvalidHeader(reason))
59
59
+
}
60
60
+
61
61
+
_ -> Error(UnexpectedEndOfInput)
62
62
+
}
63
63
+
64
64
+
Error(reason) -> Error(reason)
65
65
+
}
66
66
+
}
67
67
+
68
68
+
fn header_decoder() -> Decoder(CarHeader) {
69
69
+
use version <- decode.field("version", decode.int)
70
70
+
use roots <- decode.field("roots", decode.list(cid.decoder()))
71
71
+
72
72
+
decode.success(CarHeader(version:, roots:))
73
73
+
}
74
74
+
75
75
+
fn parse_blocks(
76
76
+
bits: BitArray,
77
77
+
blocks: List(Block),
78
78
+
) -> Result(List(Block), DecodeError) {
79
79
+
case bits {
80
80
+
<<>> -> Ok(list.reverse(blocks))
81
81
+
82
82
+
_ ->
83
83
+
case parse_block(bits) {
84
84
+
Ok(#(block, remaining)) -> parse_blocks(remaining, [block, ..blocks])
85
85
+
Error(reason) -> Error(reason)
86
86
+
}
87
87
+
}
88
88
+
}
89
89
+
90
90
+
fn parse_block(bits: BitArray) -> Result(#(Block, BitArray), DecodeError) {
91
91
+
case parse_varint(bits) {
92
92
+
Ok(#(length, _)) if length < 36 -> Error(MalformedInput)
93
93
+
94
94
+
Ok(#(length, remaining)) -> {
95
95
+
let data_size = length - 36
96
96
+
97
97
+
case remaining {
98
98
+
<<cid_bits:bytes-size(36), data:bytes-size(data_size), remaining:bits>> ->
99
99
+
case cid.from_bit_array(cid_bits) {
100
100
+
Ok(cid) -> Ok(#(Block(cid:, data:), remaining))
101
101
+
Error(_) -> Error(InvalidCid)
102
102
+
}
103
103
+
104
104
+
_ -> Error(UnexpectedEndOfInput)
105
105
+
}
106
106
+
}
107
107
+
108
108
+
Error(reason) -> Error(reason)
109
109
+
}
110
110
+
}
111
111
+
112
112
+
fn parse_varint(bits: BitArray) -> Result(#(Int, BitArray), DecodeError) {
113
113
+
do_parse_varint(bits, 1, 0, 0)
114
114
+
}
115
115
+
116
116
+
fn do_parse_varint(
117
117
+
bits: BitArray,
118
118
+
multiplier: Int,
119
119
+
acc: Int,
120
120
+
bytes_read: Int,
121
121
+
) -> Result(#(Int, BitArray), DecodeError) {
122
122
+
case bits {
123
123
+
_ if bytes_read > 8 -> Error(MalformedInput)
124
124
+
125
125
+
<<0:1, value:7, remaining:bits>> ->
126
126
+
Ok(#(acc + value * multiplier, remaining))
127
127
+
128
128
+
<<1:1, value:7, remaining:bits>> ->
129
129
+
do_parse_varint(
130
130
+
remaining,
131
131
+
multiplier * 128,
132
132
+
acc + value * multiplier,
133
133
+
bytes_read + 1,
134
134
+
)
135
135
+
136
136
+
_ -> Error(UnexpectedEndOfInput)
137
137
+
}
138
138
+
}
139
139
+
140
140
+
// QUERIES ---------------------------------------------------------------------
141
141
+
142
142
+
///
143
143
+
///
144
144
+
pub fn roots(car: Car) -> List(Cid) {
145
145
+
car.roots
146
146
+
}
147
147
+
148
148
+
///
149
149
+
///
150
150
+
pub fn blocks(car: Car) -> List(Block) {
151
151
+
car.blocks
152
152
+
}
153
153
+
154
154
+
///
155
155
+
///
156
156
+
pub fn block(car: Car, id: Cid) -> Result(Block, Nil) {
157
157
+
list.find(car.blocks, fn(block) { block.cid == id })
158
158
+
}
159
159
+
160
160
+
// CONVERSIONS -----------------------------------------------------------------
161
161
+
162
162
+
pub fn to_bit_array(roots: List(Cid), blocks: List(Block)) -> BitArray {
163
163
+
let header =
164
164
+
cbor.to_bit_array(
165
165
+
cbor.object([
166
166
+
#("version", cbor.int(1)),
167
167
+
#("roots", cbor.array(roots, cbor.cid)),
168
168
+
]),
169
169
+
)
170
170
+
171
171
+
let bits = <<varint(bit_array.byte_size(header)):bits, header:bits>>
172
172
+
173
173
+
use bits, block <- list.fold(blocks, bits)
174
174
+
let block_bytes = cid.to_bit_array(block.cid)
175
175
+
let block_length =
176
176
+
bit_array.byte_size(block_bytes) + bit_array.byte_size(block.data)
177
177
+
178
178
+
<<bits:bits, varint(block_length):bits, block_bytes:bits, block.data:bits>>
179
179
+
}
180
180
+
181
181
+
fn varint(value: Int) -> BitArray {
182
182
+
case value < 128 {
183
183
+
True -> <<0:1, value:7>>
184
184
+
False -> {
185
185
+
let low = int.bitwise_and(value, 127)
186
186
+
let rest = int.bitwise_shift_right(value, 7)
187
187
+
188
188
+
<<1:1, low:7, varint(rest):bits>>
189
189
+
}
190
190
+
}
191
191
+
}
···
1
1
// IMPORTS ---------------------------------------------------------------------
2
2
3
3
import at/cbor
4
4
-
import at/cid.{type Cid}
4
4
+
import at/cid
5
5
import gleam/bit_array
6
6
-
import gleam/dynamic.{type Dynamic}
7
6
import gleam/dynamic/decode
8
8
-
import gleam/option.{type Option, None}
7
7
+
import gleam/option.{None}
9
8
import gleam/result
10
9
11
10
// ATPROTO INTEROP -------------------------------------------------------------
12
11
// https://github.com/bluesky-social/atproto-interop-tests/blob/main/data-model/data-model-fixtures.json
13
12
14
14
-
type AtprotoInteropNestedFixture {
15
15
-
AtprotoInteropNestedFixture(
16
16
-
string: String,
17
17
-
unicode: String,
18
18
-
integer: Int,
19
19
-
bool: Bool,
20
20
-
null: Option(Dynamic),
21
21
-
array: List(String),
22
22
-
object: AtprotoInteropNestedFixtureInner,
23
23
-
)
24
24
-
}
25
25
-
26
26
-
type AtprotoInteropNestedFixtureInner {
27
27
-
AtprotoInteropNestedFixtureInner(
28
28
-
string: String,
29
29
-
number: Int,
30
30
-
bool: Bool,
31
31
-
arr: List(String),
32
32
-
)
33
33
-
}
34
34
-
35
35
-
pub fn atproto_interop_nested_cbor_test() {
13
13
+
pub fn atproto_interop_core_types_cbor_test() {
36
14
let assert Ok(bits) =
37
37
-
// json: {
15
15
+
// {
38
16
// "string": "abc",
39
17
// "unicode": "a~öñ©⽘☎𓋓😀👨👩👧👧",
40
18
// "integer": 123,
···
66
44
use bool <- decode.field("bool", decode.bool)
67
45
use arr <- decode.field("arr", decode.list(decode.string))
68
46
69
69
-
decode.success(AtprotoInteropNestedFixtureInner(
70
70
-
string:,
71
71
-
number:,
72
72
-
bool:,
73
73
-
arr:,
74
74
-
))
47
47
+
decode.success(#(string, number, bool, arr))
75
48
})
76
49
77
77
-
decode.success(AtprotoInteropNestedFixture(
78
78
-
string:,
79
79
-
unicode:,
80
80
-
integer:,
81
81
-
bool:,
82
82
-
null:,
83
83
-
array:,
84
84
-
object:,
85
85
-
))
50
50
+
decode.success(#(string, unicode, integer, bool, null, array, object))
86
51
})
87
52
88
53
assert fixture
89
89
-
== AtprotoInteropNestedFixture(
90
90
-
string: "abc",
91
91
-
unicode: "a~öñ©⽘☎𓋓😀👨👩👧👧",
92
92
-
integer: 123,
93
93
-
bool: True,
94
94
-
null: None,
95
95
-
array: ["abc", "def", "ghi"],
96
96
-
object: AtprotoInteropNestedFixtureInner(
97
97
-
string: "abc",
98
98
-
number: 123,
99
99
-
bool: True,
100
100
-
arr: ["abc", "def", "ghi"],
101
101
-
),
54
54
+
== #(
55
55
+
"abc",
56
56
+
"a~öñ©⽘☎𓋓😀👨👩👧👧",
57
57
+
123,
58
58
+
True,
59
59
+
None,
60
60
+
["abc", "def", "ghi"],
61
61
+
#("abc", 123, True, ["abc", "def", "ghi"]),
102
62
)
103
63
104
64
assert bits
105
65
== cbor.to_bit_array(
106
66
cbor.object([
107
107
-
#("string", cbor.string(fixture.string)),
108
108
-
#("unicode", cbor.string(fixture.unicode)),
109
109
-
#("integer", cbor.int(fixture.integer)),
110
110
-
#("bool", cbor.bool(fixture.bool)),
111
111
-
#("null", cbor.option(fixture.null, fn(_) { cbor.int(1234) })),
112
112
-
#("array", cbor.array(fixture.array, cbor.string)),
67
67
+
#("string", cbor.string(fixture.0)),
68
68
+
#("unicode", cbor.string(fixture.1)),
69
69
+
#("integer", cbor.int(fixture.2)),
70
70
+
#("bool", cbor.bool(fixture.3)),
71
71
+
#("null", cbor.option(fixture.4, fn(_) { cbor.int(1234) })),
72
72
+
#("array", cbor.array(fixture.5, cbor.string)),
113
73
#("object", {
114
74
cbor.object([
115
115
-
#("string", cbor.string(fixture.object.string)),
116
116
-
#("number", cbor.int(fixture.object.number)),
117
117
-
#("bool", cbor.bool(fixture.object.bool)),
118
118
-
#("arr", cbor.array(fixture.object.arr, cbor.string)),
75
75
+
#("string", cbor.string(fixture.6.0)),
76
76
+
#("number", cbor.int(fixture.6.1)),
77
77
+
#("bool", cbor.bool(fixture.6.2)),
78
78
+
#("arr", cbor.array(fixture.6.3, cbor.string)),
119
79
])
120
80
}),
121
81
]),
122
82
)
123
123
-
}
124
124
-
125
125
-
type AtprotoInteropCidFixture {
126
126
-
AtprotoInteropCidFixture(
127
127
-
a: Cid,
128
128
-
b: BitArray,
129
129
-
c: AtprotoInteropCidFixtureInner,
130
130
-
)
131
131
-
}
132
132
-
133
133
-
type AtprotoInteropCidFixtureInner {
134
134
-
AtprotoInteropCidFixtureInner(ref: Cid, mime_type: String, size: Int)
135
83
}
136
84
137
85
pub fn atproto_interop_cid_cbor_test() {
138
138
-
// json: {
86
86
+
// {
139
87
// "a": {
140
88
// "$link": "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a"
141
89
// },
···
167
115
use size <- decode.field("size", decode.int)
168
116
169
117
case t {
170
170
-
"blob" ->
171
171
-
decode.success(AtprotoInteropCidFixtureInner(
172
172
-
ref:,
173
173
-
mime_type:,
174
174
-
size:,
175
175
-
))
176
176
-
177
177
-
_ ->
178
178
-
decode.failure(
179
179
-
AtprotoInteropCidFixtureInner(ref:, mime_type:, size:),
180
180
-
"$type: \"blob\"",
181
181
-
)
118
118
+
"blob" -> decode.success(#(ref, mime_type, size))
119
119
+
_ -> decode.failure(#(ref, mime_type, size), "$type: \"blob\"")
182
120
}
183
121
})
184
122
185
185
-
decode.success(AtprotoInteropCidFixture(a:, b:, c:))
123
123
+
decode.success(#(a, b, c))
186
124
})
187
125
188
126
assert fixture
189
189
-
== AtprotoInteropCidFixture(
190
190
-
a: "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a"
127
127
+
== #(
128
128
+
"bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a"
191
129
|> cid.from_string
192
130
|> result.unwrap(cid.zero),
193
193
-
b: "nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0"
131
131
+
"nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0"
194
132
|> bit_array.base64_decode
195
133
|> result.unwrap(<<>>),
196
196
-
c: AtprotoInteropCidFixtureInner(
197
197
-
ref: "bafkreiccldh766hwcnuxnf2wh6jgzepf2nlu2lvcllt63eww5p6chi4ity"
134
134
+
#(
135
135
+
"bafkreiccldh766hwcnuxnf2wh6jgzepf2nlu2lvcllt63eww5p6chi4ity"
198
136
|> cid.from_string
199
137
|> result.unwrap(cid.zero),
200
200
-
mime_type: "image/jpeg",
201
201
-
size: 10_000,
138
138
+
"image/jpeg",
139
139
+
10_000,
202
140
),
203
141
)
204
142
205
143
assert bits
206
144
== cbor.to_bit_array(
207
145
cbor.object([
208
208
-
#("a", cbor.cid(fixture.a)),
209
209
-
#("b", cbor.bytes(fixture.b)),
146
146
+
#("a", cbor.cid(fixture.0)),
147
147
+
#("b", cbor.bytes(fixture.1)),
210
148
#("c", {
211
149
cbor.object([
212
150
#("$type", cbor.string("blob")),
213
213
-
#("ref", cbor.cid(fixture.c.ref)),
214
214
-
#("mimeType", cbor.string(fixture.c.mime_type)),
215
215
-
#("size", cbor.int(fixture.c.size)),
151
151
+
#("ref", cbor.cid(fixture.2.0)),
152
152
+
#("mimeType", cbor.string(fixture.2.1)),
153
153
+
#("size", cbor.int(fixture.2.2)),
154
154
+
])
155
155
+
}),
156
156
+
]),
157
157
+
)
158
158
+
}
159
159
+
160
160
+
pub fn atproto_interop_nested_structures_cbor_test() {
161
161
+
// {
162
162
+
// "a": {
163
163
+
// "b": [
164
164
+
// {
165
165
+
// "d": [
166
166
+
// {
167
167
+
// "$link": "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a"
168
168
+
// },
169
169
+
// {
170
170
+
// "$link": "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a"
171
171
+
// }
172
172
+
// ],
173
173
+
// "e": [
174
174
+
// { "$bytes": "nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0" },
175
175
+
// { "$bytes": "iE+sPoHobU9tSIqGI+309LLCcWQIRmEXwxcoDt19tas" }
176
176
+
// ]
177
177
+
// }
178
178
+
// ]
179
179
+
// }
180
180
+
// }
181
181
+
let assert Ok(bits) =
182
182
+
bit_array.base64_decode(
183
183
+
"oWFhoWFigaJhZILYKlglAAFxEiBlBipaWgD8Ftc8aUQjfMvBWxxKcjRIkzaJHQkXQaI50NgqWCUAAXESIGUGKlpaAPwW1zxpRCN8y8FbHEpyNEiTNokdCRdBojnQYWWCWCCcURGO8suLD2qbjkmuof1BPPILYu7Vdvid7r6wGsLMjVggiE+sPoHobU9tSIqGI+309LLCcWQIRmEXwxcoDt19tas",
184
184
+
)
185
185
+
186
186
+
let assert Ok(fixture) =
187
187
+
cbor.parse(bits, {
188
188
+
use a <- decode.field("a", {
189
189
+
use b <- decode.field("b", {
190
190
+
decode.list({
191
191
+
use d <- decode.field("d", decode.list(cid.decoder()))
192
192
+
use e <- decode.field("e", decode.list(decode.bit_array))
193
193
+
194
194
+
decode.success(#(d, e))
195
195
+
})
196
196
+
})
197
197
+
198
198
+
decode.success(b)
199
199
+
})
200
200
+
201
201
+
decode.success(a)
202
202
+
})
203
203
+
204
204
+
assert fixture
205
205
+
== [
206
206
+
#(
207
207
+
[
208
208
+
"bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a"
209
209
+
|> cid.from_string
210
210
+
|> result.unwrap(cid.zero),
211
211
+
"bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a"
212
212
+
|> cid.from_string
213
213
+
|> result.unwrap(cid.zero),
214
214
+
],
215
215
+
[
216
216
+
"nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0"
217
217
+
|> bit_array.base64_decode
218
218
+
|> result.unwrap(<<>>),
219
219
+
"iE+sPoHobU9tSIqGI+309LLCcWQIRmEXwxcoDt19tas"
220
220
+
|> bit_array.base64_decode
221
221
+
|> result.unwrap(<<>>),
222
222
+
],
223
223
+
),
224
224
+
]
225
225
+
226
226
+
assert bits
227
227
+
== cbor.to_bit_array(
228
228
+
cbor.object([
229
229
+
#("a", {
230
230
+
cbor.object([
231
231
+
#("b", {
232
232
+
cbor.array(fixture, fn(element) {
233
233
+
cbor.object([
234
234
+
#("d", cbor.array(element.0, cbor.cid)),
235
235
+
#("e", cbor.array(element.1, cbor.bytes)),
236
236
+
])
237
237
+
})
238
238
+
}),
216
239
])
217
240
}),
218
241
]),
···
1
1
+
// IMPORTS ---------------------------------------------------------------------
2
2
+
1
3
import at/did
2
2
-
import gleam/json
3
4
import gleam/list
4
5
import gleam/uri
5
6
6
6
-
pub fn valid_dids_test() {
7
7
-
let examples = [
8
8
-
"did:plc:ewvi7nxzyoun6zhxrhs64oiz",
9
9
-
"did:plc:7iza6de2dwap2sbkpav7c6c6",
10
10
-
"did:web:user.example.com",
11
11
-
"did:web:john-john.test",
12
12
-
"did:web:8.cn",
13
13
-
"did:web:localhost%3A1234",
14
14
-
]
7
7
+
// FIXTURES --------------------------------------------------------------------
15
8
16
16
-
list.each(examples, fn(example) {
17
17
-
let assert Ok(parsed) = did.from_string(example) as example
18
18
-
assert did.to_string(parsed) == example
19
19
-
})
9
9
+
const valid = [
10
10
+
"did:plc:ewvi7nxzyoun6zhxrhs64oiz",
11
11
+
"did:plc:7iza6de2dwap2sbkpav7c6c6",
12
12
+
"did:web:user.example.com",
13
13
+
"did:web:john-john.test",
14
14
+
"did:web:8.cn",
15
15
+
"did:web:localhost%3A1234",
16
16
+
]
20
17
21
21
-
let assert Ok(localhost) = did.from_string("did:web:localhost%3a3000")
22
22
-
assert did.to_string(localhost) == "did:web:localhost%3A3000"
23
23
-
}
24
24
-
25
25
-
pub fn invalid_dids_test() {
26
26
-
let examples = [
27
27
-
// Only the two blessed atproto methods are supported.
28
28
-
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
29
29
-
// did:plc is exactly 24 lowercase base32 characters
30
30
-
"did:plc:ewvi7nxzyoun6zhxrhs64oi",
31
31
-
"did:plc:EWVI7NXZYOUN6ZHXHRS64OIZ",
32
32
-
"did:plc:ewvi7nxzyoun6zhxrhs64oi0",
33
33
-
// did:web uses lowercase, hostname-level identifiers
34
34
-
"did:web:EXAMPLE.COM",
35
35
-
"did:web:john",
36
36
-
"did:web:john..test",
37
37
-
"did:web:-john.test",
38
38
-
"did:web:john-.test",
39
39
-
"did:web:john.0",
40
40
-
"did:web:bücher.test",
41
41
-
"did:web:example.com:user",
42
42
-
// Reserved TLDs fail atproto resolution policy
43
43
-
"did:web:blah.arpa",
44
44
-
// Ports are only available on localhost in development
45
45
-
"did:web:example.com%3A3000",
46
46
-
"did:web:localhost%3A65536",
47
47
-
]
18
18
+
const invalid = [
19
19
+
// Only the two blessed atproto methods are supported.
20
20
+
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
21
21
+
// did:plc is exactly 24 lowercase base32 characters
22
22
+
"did:plc:ewvi7nxzyoun6zhxrhs64oi",
23
23
+
"did:plc:EWVI7NXZYOUN6ZHXHRS64OIZ",
24
24
+
"did:plc:ewvi7nxzyoun6zhxrhs64oi0",
25
25
+
// did:web uses lowercase, hostname-level identifiers
26
26
+
"did:web:EXAMPLE.COM",
27
27
+
"did:web:john",
28
28
+
"did:web:john..test",
29
29
+
"did:web:-john.test",
30
30
+
"did:web:john-.test",
31
31
+
"did:web:john.0",
32
32
+
"did:web:bücher.test",
33
33
+
"did:web:example.com:user",
34
34
+
// Reserved TLDs fail atproto resolution policy
35
35
+
"did:web:blah.arpa",
36
36
+
// Ports are only available on localhost in development
37
37
+
"did:web:example.com%3A3000",
38
38
+
"did:web:localhost%3A65536",
39
39
+
]
48
40
49
49
-
list.each(examples, fn(example) {
50
50
-
assert did.from_string(example) == Error(Nil) as example
51
51
-
})
52
52
-
}
41
41
+
const uris = [
42
42
+
#(
43
43
+
"did:plc:ewvi7nxzyoun6zhxrhs64oiz",
44
44
+
"https://plc.directory/did:plc:ewvi7nxzyoun6zhxrhs64oiz",
45
45
+
),
46
46
+
#("did:web:example.com", "https://example.com/.well-known/did.json"),
47
47
+
#("did:web:localhost%3A3000", "https://localhost:3000/.well-known/did.json"),
48
48
+
]
53
49
54
54
-
pub fn document_uri_examples_test() {
55
55
-
let examples = [
56
56
-
#(
57
57
-
"did:plc:ewvi7nxzyoun6zhxrhs64oiz",
58
58
-
"https://plc.directory/did:plc:ewvi7nxzyoun6zhxrhs64oiz",
59
59
-
),
60
60
-
#("did:web:example.com", "https://example.com/.well-known/did.json"),
61
61
-
#("did:web:localhost%3A3000", "https://localhost:3000/.well-known/did.json"),
62
62
-
]
50
50
+
//
63
51
64
64
-
use example <- list.each(examples)
65
65
-
let #(input, expected) = example
66
66
-
let assert Ok(parsed) = did.from_string(input)
52
52
+
pub fn atproto_interop_valid_did_test() {
53
53
+
use input <- list.each(valid)
54
54
+
let assert Ok(did) = did.from_string(input) as input
67
55
68
68
-
assert parsed |> did.document_uri |> uri.to_string == expected
56
56
+
assert did.to_string(did) == input
69
57
}
70
58
71
71
-
pub fn json_conversion_and_decoder_test() {
72
72
-
let input = "did:web:example.com"
73
73
-
let assert Ok(parsed) = did.from_string(input)
59
59
+
pub fn atproto_interop_invalid_did_test() {
60
60
+
use input <- list.each(invalid)
61
61
+
62
62
+
assert did.from_string(input) == Error(Nil) as input
63
63
+
}
74
64
75
75
-
assert parsed
76
76
-
|> did.to_json
77
77
-
|> json.to_string
78
78
-
== "\"" <> input <> "\""
65
65
+
//
79
66
80
80
-
let assert Ok(decoded) = json.parse("\"" <> input <> "\"", did.decoder())
81
81
-
assert did.to_string(decoded) == input
67
67
+
pub fn document_uri_examples_test() {
68
68
+
use #(input, expected) <- list.each(uris)
69
69
+
let assert Ok(parsed) = did.from_string(input) as input
82
70
83
83
-
let assert Error(_) = json.parse("\"did:web:invalid\"", did.decoder())
71
71
+
assert parsed |> did.document_uri |> uri.to_string == expected
84
72
}
···
1
1
+
// IMPORTS ---------------------------------------------------------------------
2
2
+
3
3
+
import at/handle
4
4
+
import gleam/list
5
5
+
import gleam/string
6
6
+
7
7
+
// FIXTURES --------------------------------------------------------------------
8
8
+
9
9
+
const valid = [
10
10
+
"A.ISI.EDU",
11
11
+
"XX.LCS.MIT.EDU",
12
12
+
"SRI-NIC.ARPA",
13
13
+
"john.test",
14
14
+
"jan.test",
15
15
+
"a234567890123456789.test",
16
16
+
"john2.test",
17
17
+
"john-john.test",
18
18
+
"john.bsky.app",
19
19
+
"jo.hn",
20
20
+
"a.co",
21
21
+
"a.org",
22
22
+
"joh.n",
23
23
+
"j0.h0",
24
24
+
"jaymome-johnber123456.test",
25
25
+
"jay.mome-johnber123456.test",
26
26
+
"john.test.bsky.app",
27
27
+
"shoooort.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.test",
28
28
+
"short.ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.test",
29
29
+
"john.t",
30
30
+
"laptop.local",
31
31
+
"laptop.arpa",
32
32
+
"xn--ls8h.test",
33
33
+
"xn--bcher-kva.tld",
34
34
+
"xn--3jk.com",
35
35
+
"xn--w3d.com",
36
36
+
"xn--vqb.com",
37
37
+
"xn--ppd.com",
38
38
+
"xn--cs9a.com",
39
39
+
"xn--8r9a.com",
40
40
+
"xn--cfd.com",
41
41
+
"xn--5jk.com",
42
42
+
"xn--2lb.com",
43
43
+
"expyuzz4wqqyqhjn.onion",
44
44
+
"friend.expyuzz4wqqyqhjn.onion",
45
45
+
"g2zyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion",
46
46
+
"friend.g2zyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion",
47
47
+
"friend.g2zyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion",
48
48
+
"2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion",
49
49
+
"friend.2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion",
50
50
+
"12345.test",
51
51
+
"8.cn",
52
52
+
"4chan.org",
53
53
+
"4chan.o-g",
54
54
+
"blah.4chan.org",
55
55
+
"thing.a01",
56
56
+
"120.0.0.1.com",
57
57
+
"0john.test",
58
58
+
"9sta--ck.com",
59
59
+
"99stack.com",
60
60
+
"0ohn.test",
61
61
+
"john.t--t",
62
62
+
"thing.0aa.thing",
63
63
+
"stack.com",
64
64
+
"sta-ck.com",
65
65
+
"sta---ck.com",
66
66
+
"sta--ck9.com",
67
67
+
"stack99.com",
68
68
+
"sta99ck.com",
69
69
+
"google.com.uk",
70
70
+
"google.co.in",
71
71
+
"google.com",
72
72
+
"maselkowski.pl",
73
73
+
"m.maselkowski.pl",
74
74
+
"xn--masekowski-d0b.pl",
75
75
+
"xn--fiqa61au8b7zsevnm8ak20mc4a87e.xn--fiqs8s",
76
76
+
"xn--stackoverflow.com",
77
77
+
"stackoverflow.xn--com",
78
78
+
"stackoverflow.co.uk",
79
79
+
"xn--masekowski-d0b.pl",
80
80
+
"xn--fiqa61au8b7zsevnm8ak20mc4a87e.xn--fiqs8s",
81
81
+
]
82
82
+
83
83
+
const invalid = [
84
84
+
"did:thing.test",
85
85
+
"did:thing",
86
86
+
"john-.test",
87
87
+
"john.0",
88
88
+
"john.-",
89
89
+
"xn--bcher-.tld",
90
90
+
"john..test",
91
91
+
"jo_hn.test",
92
92
+
"-john.test",
93
93
+
".john.test",
94
94
+
"jo!hn.test",
95
95
+
"jo%hn.test",
96
96
+
"jo&hn.test",
97
97
+
"jo@hn.test",
98
98
+
"jo*hn.test",
99
99
+
"jo|hn.test",
100
100
+
"jo:hn.test",
101
101
+
"jo/hn.test",
102
102
+
"john💩.test",
103
103
+
"bücher.test",
104
104
+
"john .test",
105
105
+
"john.test.",
106
106
+
"john",
107
107
+
"john.",
108
108
+
".john",
109
109
+
"john.test.",
110
110
+
".john.test",
111
111
+
" john.test",
112
112
+
"john.test ",
113
113
+
"joh-.test",
114
114
+
"john.-est",
115
115
+
"john.tes-",
116
116
+
"shoooort.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.loooooooooooooooooooooooooong.test",
117
117
+
"short.oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.test",
118
118
+
"org",
119
119
+
"ai",
120
120
+
"gg",
121
121
+
"io",
122
122
+
"cn.8",
123
123
+
"thing.0aa",
124
124
+
"thing.0aa",
125
125
+
"127.0.0.1",
126
126
+
"192.168.0.142",
127
127
+
"fe80::7325:8a97:c100:94b",
128
128
+
"2600:3c03::f03c:9100:feb0:af1f",
129
129
+
"-notvalid.at-all",
130
130
+
"-thing.com",
131
131
+
"www.masełkowski.pl.com",
132
132
+
]
133
133
+
134
134
+
// INTEROP TESTS ---------------------------------------------------------------
135
135
+
136
136
+
pub fn atproto_interop_valid_handle_test() {
137
137
+
use input <- list.each(valid)
138
138
+
let assert Ok(handle) = handle.from_string(input) as input
139
139
+
140
140
+
assert handle.to_string(handle) == string.lowercase(input)
141
141
+
}
142
142
+
143
143
+
pub fn atproto_interop_invalid_handle_test() {
144
144
+
use input <- list.each(invalid)
145
145
+
146
146
+
assert handle.from_string(input) == Error(Nil) as input
147
147
+
}
···
1
1
+
// IMPORTS ---------------------------------------------------------------------
2
2
+
3
3
+
import at/rkey
4
4
+
import gleam/list
5
5
+
6
6
+
// FIXTURES --------------------------------------------------------------------
7
7
+
8
8
+
const valid = [
9
9
+
"self",
10
10
+
"example.com",
11
11
+
"~1.2-3_",
12
12
+
"dHJ1ZQ",
13
13
+
"_",
14
14
+
"literal:self",
15
15
+
"pre:fix",
16
16
+
":",
17
17
+
"-",
18
18
+
"_",
19
19
+
"~",
20
20
+
"...",
21
21
+
"self.",
22
22
+
"lang:",
23
23
+
":lang",
24
24
+
"oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo",
25
25
+
]
26
26
+
27
27
+
const invalid = [
28
28
+
"alpha/beta",
29
29
+
".",
30
30
+
"..",
31
31
+
"#extra",
32
32
+
"@handle",
33
33
+
"any space",
34
34
+
"any+space",
35
35
+
"number[3]",
36
36
+
"number(3)",
37
37
+
"\"quote\"",
38
38
+
"dHJ1ZQ==",
39
39
+
"ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo",
40
40
+
]
41
41
+
42
42
+
// INTEROP TESTS ---------------------------------------------------------------
43
43
+
44
44
+
pub fn atproto_interop_valid_rkey_test() {
45
45
+
use input <- list.each(valid)
46
46
+
let assert Ok(rkey) = rkey.from_string(input) as input
47
47
+
48
48
+
assert rkey.to_string(rkey) == input
49
49
+
}
50
50
+
51
51
+
pub fn atproto_interop_invalid_rkey_test() {
52
52
+
use input <- list.each(invalid)
53
53
+
54
54
+
assert rkey.from_string(input) == Error(Nil) as input
55
55
+
}
···
1
1
+
// IMPORTS ---------------------------------------------------------------------
2
2
+
3
3
+
import at/uri
4
4
+
import gleam/list
5
5
+
6
6
+
// FIXTURES --------------------------------------------------------------------
7
7
+
8
8
+
const valid = [
9
9
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz",
10
10
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post",
11
11
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post",
12
12
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/a",
13
13
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/asdf-123",
14
14
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/asdf123",
15
15
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/asdf123",
16
16
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo",
17
17
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/record",
18
18
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/record",
19
19
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.postV2",
20
20
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/io.nsid.someFunc/_",
21
21
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/io.nsid.someFunc/-",
22
22
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/io.nsid.someFunc/:",
23
23
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/io.nsid.someFunc/...",
24
24
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/io.nsid.someFunc/~",
25
25
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/io.nsid.someFunc/lang:",
26
26
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/io.nsid.someFunc/record-key",
27
27
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/io.nsid.someFunc/self.",
28
28
+
29
29
+
"at://did:web:user.example.com",
30
30
+
"at://did:web:john-john.test",
31
31
+
"at://did:web:8.cn",
32
32
+
"at://did:web:localhost%3A1234",
33
33
+
34
34
+
"at://user.bsky.social",
35
35
+
"at://user.bsky.social",
36
36
+
]
37
37
+
38
38
+
const invalid = [
39
39
+
"a://did:plc:ewvi7nxzyoun6zhxrhs64oiz",
40
40
+
"at//did:plc:ewvi7nxzyoun6zhxrhs64oiz",
41
41
+
"at:/a/did:plc:ewvi7nxzyoun6zhxrhs64oiz",
42
42
+
"at:/did:plc:ewvi7nxzyoun6zhxrhs64oiz",
43
43
+
"AT://did:plc:ewvi7nxzyoun6zhxrhs64oiz",
44
44
+
"http://did:plc:ewvi7nxzyoun6zhxrhs64oiz",
45
45
+
"://did:plc:ewvi7nxzyoun6zhxrhs64oiz",
46
46
+
"at:did:plc:ewvi7nxzyoun6zhxrhs64oiz",
47
47
+
"at:/did:plc:ewvi7nxzyoun6zhxrhs64oiz",
48
48
+
"at:///did:plc:ewvi7nxzyoun6zhxrhs64oiz",
49
49
+
"at://:/did:plc:ewvi7nxzyoun6zhxrhs64oiz",
50
50
+
"at:/ /did:plc:ewvi7nxzyoun6zhxrhs64oiz",
51
51
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz ",
52
52
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/ ",
53
53
+
" at://did:plc:ewvi7nxzyoun6zhxrhs64oiz",
54
54
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post ",
55
55
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post# ",
56
56
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post#/ ",
57
57
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post#/frag ",
58
58
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post#fr ag",
59
59
+
"//did:plc:ewvi7nxzyoun6zhxrhs64oiz",
60
60
+
"at://name",
61
61
+
"at://name.0",
62
62
+
"at://diD:plc:ewvi7nxzyoun6zhxrhs64oiz",
63
63
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.p@st",
64
64
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.p$st",
65
65
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.p%st",
66
66
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.p&st",
67
67
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.p()t",
68
68
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed_post",
69
69
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/-com.atproto.feed.post",
70
70
+
"at://did:plc:asdf@123/com.atproto.feed.post",
71
71
+
"at://DID:plc:ewvi7nxzyoun6zhxrhs64oiz",
72
72
+
"at://user.bsky.123",
73
73
+
"at://bsky",
74
74
+
"at://did:plc:",
75
75
+
"at://did:plc:",
76
76
+
"at://frag",
77
77
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo",
78
78
+
"at://user.bsky.social//",
79
79
+
"at://user.bsky.social//com.atproto.feed.post",
80
80
+
"at://user.bsky.social/com.atproto.feed.post//",
81
81
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/asdf123/more/more',",
82
82
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/short/stuff",
83
83
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/12345",
84
84
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/",
85
85
+
"at://user.bsky.social/",
86
86
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/",
87
87
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/record/",
88
88
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/record/#/frag",
89
89
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/asdf123/asdf",
90
90
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz#",
91
91
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz##",
92
92
+
"#at://did:plc:ewvi7nxzyoun6zhxrhs64oiz",
93
93
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz#/asdf#/asdf",
94
94
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/%23",
95
95
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/$@!*)(:,;~.sdf123",
96
96
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/~'sdf123\")",
97
97
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/$",
98
98
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/@",
99
99
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/!",
100
100
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/*",
101
101
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/(",
102
102
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/,",
103
103
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/;",
104
104
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/abc%30123",
105
105
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/%30",
106
106
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/%3",
107
107
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/%",
108
108
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/%zz",
109
109
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/%%%",
110
110
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/.",
111
111
+
"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/com.atproto.feed.post/..",
112
112
+
]
113
113
+
114
114
+
// INTEROP TESTS ---------------------------------------------------------------
115
115
+
116
116
+
pub fn atproto_interop_valid_uri_test() {
117
117
+
use input <- list.each(valid)
118
118
+
let assert Ok(uri) = uri.from_string(input) as input
119
119
+
120
120
+
assert uri.to_string(uri) == input
121
121
+
}
122
122
+
123
123
+
pub fn atproto_interop_invalid_uri_test() {
124
124
+
use input <- list.each(invalid)
125
125
+
126
126
+
assert uri.from_string(input) == Error(Nil) as input
127
127
+
}
···
1
1
+
// IMPORTS ---------------------------------------------------------------------
2
2
+
1
3
import gleeunit
4
4
+
5
5
+
// MAIN ------------------------------------------------------------------------
2
6
3
7
pub fn main() -> Nil {
4
8
gleeunit.main()