๐Ÿ€ ATproto XRPC client for gleam possum.hexdocs.pm
gleam atproto xrpc-client library
41

Configure Feed

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

possum/at_uri: `collection` field is now a valid NSID

kacaii.dev (Jul 19, 2026, 2:24 PM -0300) 2d501b9e fa6c929e

+50 -16
+6
CHANGELOG.md
··· 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 7 8 + ## [v4.0.0] - Unreleased 9 + 10 + - [possum/at_uri] `collection` field now stores a parsed NSID 11 + - [possum/at_uri] `collection` and `rkey` now return a `Result(value, Nil)` since 12 + fields might not be present. 13 + 8 14 ## [v3.0.0] - 2026-07-13 9 15 10 16 ### Added
+44 -16
src/possum/at_uri.gleam
··· 1 + import gleam/option 1 2 import gleam/result 2 3 import gleam/string 3 4 import possum/did 4 5 import possum/handle 6 + import possum/nsid 5 7 6 8 pub type ParseError { 7 9 /// Only "at://" is supported ··· 14 16 InvalidDidAuthority(reason: did.ParseError) 15 17 /// Authority is an empty string 16 18 MissingAuthority 19 + /// Collection is not a valid NSID 20 + InvalidCollection(reason: nsid.ParseError) 17 21 } 18 22 19 23 pub type Authority { ··· 49 53 /// Identifier, can be a DID or a Handle 50 54 authority: Authority, 51 55 /// Path 52 - collection: String, 56 + collection: option.Option(nsid.Nsid), 53 57 /// Query string 54 - rkey: String, 58 + rkey: option.Option(String), 55 59 ) 56 60 } 57 61 ··· 83 87 /// let string = "at://did:plc:vwzwgnygau7ed7b7wt5ux7y2/app.bsky.feed.post/3k5nobkf2w72g" 84 88 /// let assert Ok(at_uri) = at_uri.parse(string) 85 89 /// 86 - /// assert at_uri.collection(at_uri) == "app.bsky.feed.post" 90 + /// assert at_uri.collection(at_uri) == Ok("app.bsky.feed.post") 87 91 /// ``` 88 92 /// 89 93 /// See also: [Aturi](#AtUri) 90 - pub fn collection(self: AtUri) -> String { 91 - self.collection 94 + pub fn collection(self: AtUri) -> Result(nsid.Nsid, Nil) { 95 + case self.collection { 96 + option.Some(nsid) -> Ok(nsid) 97 + option.None -> Error(Nil) 98 + } 92 99 } 93 100 94 101 /// Return the rkey part of the Uri ··· 101 108 /// let string = "at://did:plc:vwzwgnygau7ed7b7wt5ux7y2/app.bsky.feed.post/3k5nobkf2w72g" 102 109 /// let assert Ok(at_uri) = at_uri.parse(string) 103 110 /// 104 - /// assert at_uri.rkey(at_uri) == "3k5nobkf2w72g" 111 + /// assert at_uri.rkey(at_uri) == Ok("3k5nobkf2w72g") 105 112 /// ``` 106 113 /// 107 114 /// See also: [Aturi](#AtUri) 108 - pub fn rkey(self: AtUri) -> String { 109 - self.rkey 115 + pub fn rkey(self: AtUri) -> Result(String, Nil) { 116 + case self.rkey { 117 + option.Some(rkey) -> Ok(rkey) 118 + option.None -> Error(Nil) 119 + } 110 120 } 111 121 112 122 /// Parse a String into a valid At-Uri type ··· 145 155 // ^^ authority 146 156 [authority] -> { 147 157 use authority <- result.map(parse_authority(authority)) 148 - AtUri(authority:, collection: "", rkey: "") 158 + AtUri(authority:, collection: option.None, rkey: option.None) 149 159 } 150 160 151 161 // Contains at least authority and collection 152 162 // ex: at://did:plc:vwzwgnygau7ed7b7wt5ux7y2/app.bsky.feed.post 153 163 // ^^ authority ^^ collection 154 164 [authority, collection] -> { 155 - use authority <- result.map(parse_authority(authority)) 156 - AtUri(authority:, collection:, rkey: "") 165 + use authority <- result.try(parse_authority(authority)) 166 + use collection <- result.map(parse_collection(collection)) 167 + AtUri(authority:, collection: option.Some(collection), rkey: option.None) 157 168 } 158 169 159 170 // Contains authority, collection and rkey 160 171 // ex: at://did:plc:vwzwgnygau7ed7b7wt5ux7y2/app.bsky.feed.post/3k5nobkf2w72g 161 172 // ^^ authority ^^ collection ^^ rkey 162 173 [authority, collection, rkey, ..] -> { 163 - use authority <- result.map(parse_authority(authority)) 164 - AtUri(authority:, collection:, rkey:) 174 + use authority <- result.try(parse_authority(authority)) 175 + use collection <- result.map(parse_collection(collection)) 176 + 177 + AtUri( 178 + authority:, 179 + collection: option.Some(collection), 180 + rkey: option.Some(rkey), 181 + ) 165 182 } 166 183 } 184 + } 185 + 186 + fn parse_collection(collection: String) -> Result(nsid.Nsid, ParseError) { 187 + nsid.parse(collection) 188 + |> result.map_error(InvalidCollection) 167 189 } 168 190 169 191 fn parse_authority(string: String) -> Result(Authority, ParseError) { ··· 204 226 } 205 227 206 228 let path = case self.collection, self.rkey { 207 - "", _ -> "" 208 - collection, "" -> "/" <> collection 209 - collection, rkey -> "/" <> collection <> "/" <> rkey 229 + // Authority only 230 + option.None, _ -> "" 231 + 232 + // Authority and collection 233 + option.Some(nsid), option.None -> "/" <> nsid.to_string(nsid) 234 + 235 + // Authority, collection and rkey 236 + option.Some(nsid), option.Some(rkey) -> 237 + "/" <> nsid.to_string(nsid) <> "/" <> rkey 210 238 } 211 239 212 240 "at://" <> host <> path