Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm

filter dids by `did=&did=` repeated param

atproto queries do arrays like this

Changed files
+28 -8
constellation
src
server
+3
Cargo.lock
··· 603 603 "axum-core", 604 604 "bytes", 605 605 "cookie", 606 + "form_urlencoded", 606 607 "futures-util", 607 608 "headers", 608 609 "http", ··· 612 613 "pin-project-lite", 613 614 "rustversion", 614 615 "serde", 616 + "serde_html_form", 617 + "serde_path_to_error", 615 618 "tower", 616 619 "tower-layer", 617 620 "tower-service",
+1 -1
constellation/Cargo.toml
··· 8 8 anyhow = "1.0.95" 9 9 askama = { version = "0.12.1", features = ["serde-json"] } 10 10 axum = "0.8.1" 11 - axum-extra = { version = "0.10.0", features = ["typed-header"] } 11 + axum-extra = { version = "0.10.0", features = ["query", "typed-header"] } 12 12 axum-metrics = "0.2" 13 13 bincode = "1.3.3" 14 14 clap = { version = "4.5.26", features = ["derive"] }
+24 -7
constellation/src/server/mod.rs
··· 238 238 collection: String, 239 239 path: String, 240 240 cursor: Option<OpaqueApiCursor>, 241 + /// Filter links only from these DIDs 242 + /// 243 + /// include multiple times to filter by multiple source DIDs 244 + #[serde(default)] 245 + did: Vec<String>, 246 + /// [deprecated] Filter links only from these DIDs 247 + /// 248 + /// format: comma-separated sequence of DIDs 249 + /// 250 + /// errors: if `did` parameter is also present 251 + /// 252 + /// deprecated: use `did`, which can be repeated multiple times 241 253 from_dids: Option<String>, // comma separated: gross 242 254 limit: Option<u64>, 243 255 // TODO: allow reverse (er, forward) order as well ··· 256 268 } 257 269 fn get_links( 258 270 accept: ExtractAccept, 259 - query: Query<GetLinkItemsQuery>, 271 + query: axum_extra::extract::Query<GetLinkItemsQuery>, // supports multiple param occurrences 260 272 store: impl LinkReader, 261 273 ) -> Result<impl IntoResponse, http::StatusCode> { 262 274 let until = query ··· 271 283 return Err(http::StatusCode::BAD_REQUEST); 272 284 } 273 285 274 - let filter_dids = &query 275 - .from_dids 276 - .clone() 277 - .map(|comma_joined| HashSet::from_iter(comma_joined.split(',').map(|d| Did(d.to_string())))) 278 - .unwrap_or_default(); 286 + let mut filter_dids: HashSet<Did> = HashSet::from_iter(query.did.iter().map(|d| Did(d.to_string()))); 287 + 288 + if let Some(comma_joined) = &query.from_dids { 289 + if !filter_dids.is_empty() { 290 + return Err(http::StatusCode::BAD_REQUEST); 291 + } 292 + for did in comma_joined.split(',') { 293 + filter_dids.insert(Did(did.to_string())); 294 + } 295 + } 279 296 280 297 let paged = store 281 298 .get_links( ··· 284 301 &query.path, 285 302 limit, 286 303 until, 287 - filter_dids, 304 + &filter_dids, 288 305 ) 289 306 .map_err(|_| http::StatusCode::INTERNAL_SERVER_ERROR)?; 290 307