Atproto primitives for Gleam.
atproto
1

Configure Feed

Select the types of activity you want to include in your feed.

⚗️ Tests for everyone!

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