forked from
microcosm.blue/microcosm-rs
Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
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}