🐀 ATproto library for gleam
atproto library gleam
35

Configure Feed

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

possum/nsid: add nsid module

kacaii.dev (Jul 11, 2026, 11:52 AM -0300) fae3aade 734fb0bb

+95
+1
CHANGELOG.md
··· 9 9 10 10 ### Added 11 11 12 + - `nsid` module for validating atproto namespaced identifiers 12 13 - `at_uri` module for validating at-uris 13 14 - `handle` module for validating atproto handles 14 15
+52
src/possum/nsid.gleam
··· 1 + //// Namespaced Identifiers (NSIDs) are used to reference Lexicon schemas for 2 + //// records, XRPC endpoints, and more. For example, com.atproto.sync.getRecord. 3 + //// 4 + //// The basic structure and semantics of an NSID are a fully-qualified hostname 5 + //// in reverse domain-name order, followed by an additional name segment. 6 + //// The hostname part is the **domain authority**, and the final segment is the **name**. 7 + 8 + import gleam/regexp 9 + import gleam/result 10 + 11 + const pattern = "^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\\.[a-zA-Z]([a-zA-Z0-9]{0,62})?)$" 12 + 13 + pub type ParseError { 14 + /// Regex failed to compile 15 + InvalidRegexPattern(pattern: String) 16 + /// String does not have a valid NSID format 17 + InvalidSyntax(value: String) 18 + } 19 + 20 + /// A specification for global semantic IDs. 21 + pub opaque type Nsid { 22 + Nsid(value: String) 23 + } 24 + 25 + pub fn to_string(nsid: Nsid) -> String { 26 + nsid.value 27 + } 28 + 29 + /// Parse a string into a valid `Nsid` type 30 + /// If the value is not a valid string then an error is returned. 31 + /// 32 + /// ## Examples 33 + /// 34 + /// ```gleam 35 + /// import possum/nsid 36 + /// 37 + /// let string = "com.atproto.sync.getRecord." 38 + /// let assert Ok(nsid) = nsid.parse(string) 39 + /// ``` 40 + pub fn parse(content: String) -> Result(Nsid, ParseError) { 41 + use with <- result.try( 42 + regexp.from_string(pattern) 43 + |> result.replace_error(InvalidRegexPattern(pattern:)), 44 + ) 45 + 46 + use value <- result.map(case regexp.check(with:, content:) { 47 + True -> Ok(content) 48 + False -> Error(InvalidSyntax(content)) 49 + }) 50 + 51 + Nsid(value:) 52 + }
+42
test/possum_test/nsid_test.gleam
··· 1 + import gleam/list 2 + import possum/nsid 3 + 4 + pub fn parse_test() -> Nil { 5 + // valid syntax 6 + let assert Ok(_) = 7 + list.try_fold( 8 + [ 9 + "com.example.fooBar", 10 + "net.users.bob.ping", 11 + "a-0.b-1.c", 12 + "a.b.c", 13 + "com.example.fooBarV2", 14 + "cn.8.lex.stuff", 15 + ], 16 + Nil, 17 + fn(_, item) { 18 + case nsid.parse(item) { 19 + Ok(_) -> Ok(Nil) 20 + Error(err) -> Error(err) 21 + } 22 + }, 23 + ) 24 + 25 + // invalid syntax 26 + let assert Error(nsid.InvalidSyntax(_)) = 27 + list.try_fold( 28 + [ 29 + "com.exa💩ple.thing", 30 + "com.example", 31 + "com.example.3", 32 + ], 33 + Nil, 34 + fn(_, item) { 35 + case nsid.parse(item) { 36 + Ok(_) -> Ok(Nil) 37 + Error(err) -> Error(err) 38 + } 39 + }, 40 + ) 41 + Nil 42 + }