Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
at main 2.7 kB view raw
1use crate::Nsid; 2use async_trait::async_trait; 3use dropshot::{ 4 ApiEndpointBodyContentType, ExtractorMetadata, HttpError, Query, RequestContext, ServerContext, 5 SharedExtractor, 6}; 7use schemars::JsonSchema; 8use serde::Deserialize; 9use std::collections::HashSet; 10 11/// The real type that gets deserialized 12#[derive(Debug, Deserialize, JsonSchema)] 13pub struct MultiCollectionQuery { 14 pub collection: Vec<String>, 15} 16 17/// The fake corresponding type for docs that dropshot won't freak out about a 18/// vec for 19#[derive(Deserialize, JsonSchema)] 20#[allow(dead_code)] 21struct MultiCollectionQueryForDocs { 22 /// One or more collection [NSID](https://atproto.com/specs/nsid)s 23 /// 24 /// Pass this parameter multiple times to specify multiple collections, like 25 /// `collection=app.bsky.feed.like&collection=app.bsky.feed.post` 26 collection: String, 27} 28 29impl TryFrom<MultiCollectionQuery> for HashSet<Nsid> { 30 type Error = HttpError; 31 fn try_from(mcq: MultiCollectionQuery) -> Result<Self, Self::Error> { 32 let mut out = HashSet::with_capacity(mcq.collection.len()); 33 for c in mcq.collection { 34 let nsid = Nsid::new(c).map_err(|e| { 35 HttpError::for_bad_request( 36 None, 37 format!("failed to convert collection to an NSID: {e:?}"), 38 ) 39 })?; 40 out.insert(nsid); 41 } 42 Ok(out) 43 } 44} 45 46// The `SharedExtractor` implementation for Query<QueryType> describes how to 47// construct an instance of `Query<QueryType>` from an HTTP request: namely, by 48// parsing the query string to an instance of `QueryType`. 49#[async_trait] 50impl SharedExtractor for MultiCollectionQuery { 51 async fn from_request<Context: ServerContext>( 52 ctx: &RequestContext<Context>, 53 ) -> Result<MultiCollectionQuery, HttpError> { 54 let raw_query = ctx.request.uri().query().unwrap_or(""); 55 let q = serde_qs::from_str(raw_query).map_err(|e| { 56 HttpError::for_bad_request(None, format!("unable to parse query string: {e}")) 57 })?; 58 Ok(q) 59 } 60 61 fn metadata(body_content_type: ApiEndpointBodyContentType) -> ExtractorMetadata { 62 // HACK: query type switcheroo: passing MultiCollectionQuery to 63 // `metadata` would "helpfully" panic because dropshot believes we can 64 // only have scalar types in a query. 65 // 66 // so instead we have a fake second type whose only job is to look the 67 // same as MultiCollectionQuery exept that it has `String` instead of 68 // `Vec<String>`, which dropshot will accept, and generate ~close-enough 69 // docs for. 70 <Query<MultiCollectionQueryForDocs> as SharedExtractor>::metadata(body_content_type) 71 } 72}