···5566[dependencies]
77axum = "0.8.8"
88+bytes = "1"
99+cid = { version = "0.11", default-features = false, features = ["alloc"] }
810clap = { version = "4.5.60", features = ["derive", "env"] }
1111+dashmap = "5"
912fjall = "3.0.3"
1013futures = "0.3"
1414+governor = "0.6"
1515+http = "1"
1116jacquard-api = { version = "0.9.5", default-features = false, features = ["com_atproto", "streaming"] }
1217jacquard-axum = { version = "0.9.6", default-features = false, features = ["tracing"] }
1318jacquard-common = { version = "0.9.5", features = ["websocket", "reqwest-client"] }
1919+jacquard-derive = "0.9.5"
1420jacquard-identity = "0.9.5"
2121+jacquard-lexicon = "0.9.5"
1522jacquard-repo = "0.9.6"
1616-cid = { version = "0.11", default-features = false, features = ["alloc"] }
1723metrics = "0.24.3"
1818-bytes = "1"
1924metrics-exporter-prometheus = { version = "0.18.1", features = ["http-listener"] }
2525+mini-moka = "0.10"
2026reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
2127rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] }
2828+rustversion = "1"
2229serde = { version = "1", features = ["derive"] }
3030+serde_json = "1"
2331thiserror = "2.0.18"
2432tokio = { version = "1.49.0", features = ["full"] }
2533tokio-util = { version = "0.7", features = ["rt"] }
2634tracing = "0.1.44"
2735tracing-subscriber = { version = "0.3", features = ["env-filter"] }
2828-dashmap = "5"
2929-governor = "0.6"
3030-http = "1"
3131-mini-moka = "0.10"
3636+3737+[dev-dependencies]
3838+jacquard-lexicon = { version = "0.9.5", features = ["codegen"] }
3239serde_json = "1"
4040+wiremock = "0.6"
4141+4242+[[example]]
4343+name = "lexgen"
4444+path = "src/examples/lexgen.rs"
33453446[[example]]
3547name = "list-repo-collections"
···3850[[example]]
3951name = "enqueue-resync"
4052path = "src/examples/enqueue_resync.rs"
4141-4242-[dev-dependencies]
4343-serde_json = "1"
4444-wiremock = "0.6"
+10
hacking.md
···7788in the project root to be up and running!
991010+do *not* manually modify any contents in `src/generated_lexicons/` -- to
1111+regenerate lexicons from the local defs in [`./lexicons`](./lexicons/), just do:
1212+1313+```bash
1414+$ cargo run --example lexgen
1515+```
1616+1717+and the codegen'd lexicons will be updated based on the JSON lexicon definitions
1818+1919+1020- TODO: make local dev work without needing an actual live firehose and backfill etc.
11211222before submitting a pull request, please run rustfmt, clippy, and all tests
···11+{
22+ "lexicon": 1,
33+ "id": "com.atproto.sync.listReposByCollection",
44+ "defs": {
55+ "main": {
66+ "type": "query",
77+ "description": "Enumerates all the active DIDs which have records in any of the given collection NSIDs. If omitted, active DIDs from all collections are returned.",
88+ "parameters": {
99+ "type": "params",
1010+ "required": [],
1111+ "properties": {
1212+ "collection": {
1313+ "type": "array",
1414+ "items": {
1515+ "type": "string",
1616+ "format": "nsid"
1717+ }
1818+ },
1919+ "limit": {
2020+ "type": "integer",
2121+ "description": "Maximum size of response set. Recommend setting a large maximum (1000+) when enumerating large DID lists.",
2222+ "minimum": 1,
2323+ "maximum": 2000,
2424+ "default": 500
2525+ },
2626+ "cursor": { "type": "string" }
2727+ }
2828+ },
2929+ "output": {
3030+ "encoding": "application/json",
3131+ "schema": {
3232+ "type": "object",
3333+ "required": ["repos"],
3434+ "properties": {
3535+ "cursor": { "type": "string" },
3636+ "repos": {
3737+ "type": "array",
3838+ "items": { "type": "ref", "ref": "#repo" }
3939+ }
4040+ }
4141+ }
4242+ }
4343+ },
4444+ "repo": {
4545+ "type": "object",
4646+ "required": ["did"],
4747+ "properties": {
4848+ "did": { "type": "string", "format": "did" }
4949+ }
5050+ }
5151+ }
5252+}
+33
src/examples/lexgen.rs
···11+//! lexicons/**/*.json => src/generated_lexicons/**/*.rs
22+//!
33+//! to update:
44+//! cargo run --example lexgen
55+//!
66+//! reads lexicon files, writes rust code.
77+//!
88+//! note! there is a manually-maintained top-level declaration required, see
99+//! `src/generated_lexicons.rs`.
1010+1111+use std::path::PathBuf;
1212+1313+fn main() {
1414+ let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1515+ let lexicons_dir = root.join("lexicons");
1616+ let out_dir = root.join("src").join("generated_lexicons");
1717+1818+ let ours = jacquard_lexicon::corpus::LexiconCorpus::load_from_dir(&lexicons_dir)
1919+ .expect("failed to load lexicons");
2020+2121+ jacquard_lexicon::codegen::CodeGenerator::new(&ours, "crate")
2222+ .write_to_disk(&out_dir)
2323+ .expect("lexgen failed");
2424+2525+ // hack!! lexgen expects that it's writing a whole crate.
2626+ // that's probably the *right* way to do this (make a cargo workspace, ...)
2727+ // but we're lazy so we just hackily delete the generated `lib.rs` and keep
2828+ // top-level declarations in `src/generated_lexicons.rs`.
2929+ std::fs::remove_file(out_dir.join("lib.rs")).expect("lib cleanup failed");
3030+3131+ eprintln!("done! outputs written to src/generated_lexicons/");
3232+ eprintln!("if any top-level namespaces changed, manually updating src/generated_lexicons.rs is required.");
3333+}
+9
src/generated_lexicons.rs
···11+//! manually maintained codegen declarations
22+//!
33+//! see examples/lexgen.rs
44+55+// the codegen is *almost* clean (and the one warning makes sense to ignore)
66+#![allow(non_snake_case)]
77+88+pub mod builder_types;
99+pub mod com_atproto;
+43
src/generated_lexicons/builder_types.rs
···11+// @generated by jacquard-lexicon. DO NOT EDIT.
22+//
33+// This file was automatically generated from Lexicon schemas.
44+// Any manual changes will be overwritten on the next regeneration.
55+66+/// Marker type indicating a builder field has been set
77+pub struct Set<T>(pub T);
88+impl<T> Set<T> {
99+ /// Extract the inner value
1010+ #[inline]
1111+ pub fn into_inner(self) -> T {
1212+ self.0
1313+ }
1414+}
1515+1616+/// Marker type indicating a builder field has not been set
1717+pub struct Unset;
1818+/// Trait indicating a builder field is set (has a value)
1919+#[rustversion::attr(
2020+ since(1.78.0),
2121+ diagnostic::on_unimplemented(
2222+ message = "the field `{Self}` was not set, but this method requires it to be set",
2323+ label = "the field `{Self}` was not set"
2424+ )
2525+)]
2626+pub trait IsSet: private::Sealed {}
2727+/// Trait indicating a builder field is unset (no value yet)
2828+#[rustversion::attr(
2929+ since(1.78.0),
3030+ diagnostic::on_unimplemented(
3131+ message = "the field `{Self}` was already set, but this method requires it to be unset",
3232+ label = "the field `{Self}` was already set"
3333+ )
3434+)]
3535+pub trait IsUnset: private::Sealed {}
3636+impl<T> IsSet for Set<T> {}
3737+impl IsUnset for Unset {}
3838+mod private {
3939+ /// Sealed trait to prevent external implementations
4040+ pub trait Sealed {}
4141+ impl<T> Sealed for super::Set<T> {}
4242+ impl Sealed for super::Unset {}
4343+}
+6
src/generated_lexicons/com_atproto.rs
···11+// @generated by jacquard-lexicon. DO NOT EDIT.
22+//
33+// This file was automatically generated from Lexicon schemas.
44+// Any manual changes will be overwritten on the next regeneration.
55+66+pub mod sync;
+6
src/generated_lexicons/com_atproto/sync.rs
···11+// @generated by jacquard-lexicon. DO NOT EDIT.
22+//
33+// This file was automatically generated from Lexicon schemas.
44+// Any manual changes will be overwritten on the next regeneration.
55+66+pub mod list_repos_by_collection;
···11+// @generated by jacquard-lexicon. DO NOT EDIT.
22+//
33+// Lexicon: com.atproto.sync.listReposByCollection
44+//
55+// This file was automatically generated from Lexicon schemas.
66+// Any manual changes will be overwritten on the next regeneration.
77+88+#[derive(
99+ serde::Serialize,
1010+ serde::Deserialize,
1111+ Debug,
1212+ Clone,
1313+ PartialEq,
1414+ Eq,
1515+ jacquard_derive::IntoStatic
1616+)]
1717+#[serde(rename_all = "camelCase")]
1818+pub struct ListReposByCollection<'a> {
1919+ #[serde(skip_serializing_if = "std::option::Option::is_none")]
2020+ #[serde(borrow)]
2121+ pub collection: std::option::Option<Vec<jacquard_common::types::string::Nsid<'a>>>,
2222+ #[serde(skip_serializing_if = "std::option::Option::is_none")]
2323+ #[serde(borrow)]
2424+ pub cursor: std::option::Option<jacquard_common::CowStr<'a>>,
2525+ ///(default: 500, min: 1, max: 2000)
2626+ #[serde(skip_serializing_if = "std::option::Option::is_none")]
2727+ pub limit: std::option::Option<i64>,
2828+}
2929+3030+pub mod list_repos_by_collection_state {
3131+3232+ pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
3333+ #[allow(unused)]
3434+ use ::core::marker::PhantomData;
3535+ mod sealed {
3636+ pub trait Sealed {}
3737+ }
3838+ /// State trait tracking which required fields have been set
3939+ pub trait State: sealed::Sealed {}
4040+ /// Empty state - all required fields are unset
4141+ pub struct Empty(());
4242+ impl sealed::Sealed for Empty {}
4343+ impl State for Empty {}
4444+ /// Marker types for field names
4545+ #[allow(non_camel_case_types)]
4646+ pub mod members {}
4747+}
4848+4949+/// Builder for constructing an instance of this type
5050+pub struct ListReposByCollectionBuilder<'a, S: list_repos_by_collection_state::State> {
5151+ _phantom_state: ::core::marker::PhantomData<fn() -> S>,
5252+ __unsafe_private_named: (
5353+ ::core::option::Option<Vec<jacquard_common::types::string::Nsid<'a>>>,
5454+ ::core::option::Option<jacquard_common::CowStr<'a>>,
5555+ ::core::option::Option<i64>,
5656+ ),
5757+ _phantom: ::core::marker::PhantomData<&'a ()>,
5858+}
5959+6060+impl<'a> ListReposByCollection<'a> {
6161+ /// Create a new builder for this type
6262+ pub fn new() -> ListReposByCollectionBuilder<
6363+ 'a,
6464+ list_repos_by_collection_state::Empty,
6565+ > {
6666+ ListReposByCollectionBuilder::new()
6767+ }
6868+}
6969+7070+impl<'a> ListReposByCollectionBuilder<'a, list_repos_by_collection_state::Empty> {
7171+ /// Create a new builder with all fields unset
7272+ pub fn new() -> Self {
7373+ ListReposByCollectionBuilder {
7474+ _phantom_state: ::core::marker::PhantomData,
7575+ __unsafe_private_named: (None, None, None),
7676+ _phantom: ::core::marker::PhantomData,
7777+ }
7878+ }
7979+}
8080+8181+impl<'a, S: list_repos_by_collection_state::State> ListReposByCollectionBuilder<'a, S> {
8282+ /// Set the `collection` field (optional)
8383+ pub fn collection(
8484+ mut self,
8585+ value: impl Into<Option<Vec<jacquard_common::types::string::Nsid<'a>>>>,
8686+ ) -> Self {
8787+ self.__unsafe_private_named.0 = value.into();
8888+ self
8989+ }
9090+ /// Set the `collection` field to an Option value (optional)
9191+ pub fn maybe_collection(
9292+ mut self,
9393+ value: Option<Vec<jacquard_common::types::string::Nsid<'a>>>,
9494+ ) -> Self {
9595+ self.__unsafe_private_named.0 = value;
9696+ self
9797+ }
9898+}
9999+100100+impl<'a, S: list_repos_by_collection_state::State> ListReposByCollectionBuilder<'a, S> {
101101+ /// Set the `cursor` field (optional)
102102+ pub fn cursor(
103103+ mut self,
104104+ value: impl Into<Option<jacquard_common::CowStr<'a>>>,
105105+ ) -> Self {
106106+ self.__unsafe_private_named.1 = value.into();
107107+ self
108108+ }
109109+ /// Set the `cursor` field to an Option value (optional)
110110+ pub fn maybe_cursor(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
111111+ self.__unsafe_private_named.1 = value;
112112+ self
113113+ }
114114+}
115115+116116+impl<'a, S: list_repos_by_collection_state::State> ListReposByCollectionBuilder<'a, S> {
117117+ /// Set the `limit` field (optional)
118118+ pub fn limit(mut self, value: impl Into<Option<i64>>) -> Self {
119119+ self.__unsafe_private_named.2 = value.into();
120120+ self
121121+ }
122122+ /// Set the `limit` field to an Option value (optional)
123123+ pub fn maybe_limit(mut self, value: Option<i64>) -> Self {
124124+ self.__unsafe_private_named.2 = value;
125125+ self
126126+ }
127127+}
128128+129129+impl<'a, S> ListReposByCollectionBuilder<'a, S>
130130+where
131131+ S: list_repos_by_collection_state::State,
132132+{
133133+ /// Build the final struct
134134+ pub fn build(self) -> ListReposByCollection<'a> {
135135+ ListReposByCollection {
136136+ collection: self.__unsafe_private_named.0,
137137+ cursor: self.__unsafe_private_named.1,
138138+ limit: self.__unsafe_private_named.2,
139139+ }
140140+ }
141141+}
142142+143143+#[jacquard_derive::lexicon]
144144+#[derive(
145145+ serde::Serialize,
146146+ serde::Deserialize,
147147+ Debug,
148148+ Clone,
149149+ PartialEq,
150150+ Eq,
151151+ jacquard_derive::IntoStatic
152152+)]
153153+#[serde(rename_all = "camelCase")]
154154+pub struct ListReposByCollectionOutput<'a> {
155155+ #[serde(skip_serializing_if = "std::option::Option::is_none")]
156156+ #[serde(borrow)]
157157+ pub cursor: std::option::Option<jacquard_common::CowStr<'a>>,
158158+ #[serde(borrow)]
159159+ pub repos: Vec<crate::com_atproto::sync::list_repos_by_collection::Repo<'a>>,
160160+}
161161+162162+/// Response type for
163163+///com.atproto.sync.listReposByCollection
164164+pub struct ListReposByCollectionResponse;
165165+impl jacquard_common::xrpc::XrpcResp for ListReposByCollectionResponse {
166166+ const NSID: &'static str = "com.atproto.sync.listReposByCollection";
167167+ const ENCODING: &'static str = "application/json";
168168+ type Output<'de> = ListReposByCollectionOutput<'de>;
169169+ type Err<'de> = jacquard_common::xrpc::GenericError<'de>;
170170+}
171171+172172+impl<'a> jacquard_common::xrpc::XrpcRequest for ListReposByCollection<'a> {
173173+ const NSID: &'static str = "com.atproto.sync.listReposByCollection";
174174+ const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query;
175175+ type Response = ListReposByCollectionResponse;
176176+}
177177+178178+/// Endpoint type for
179179+///com.atproto.sync.listReposByCollection
180180+pub struct ListReposByCollectionRequest;
181181+impl jacquard_common::xrpc::XrpcEndpoint for ListReposByCollectionRequest {
182182+ const PATH: &'static str = "/xrpc/com.atproto.sync.listReposByCollection";
183183+ const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query;
184184+ type Request<'de> = ListReposByCollection<'de>;
185185+ type Response = ListReposByCollectionResponse;
186186+}
187187+188188+#[jacquard_derive::lexicon]
189189+#[derive(
190190+ serde::Serialize,
191191+ serde::Deserialize,
192192+ Debug,
193193+ Clone,
194194+ PartialEq,
195195+ Eq,
196196+ jacquard_derive::IntoStatic
197197+)]
198198+#[serde(rename_all = "camelCase")]
199199+pub struct Repo<'a> {
200200+ #[serde(borrow)]
201201+ pub did: jacquard_common::types::string::Did<'a>,
202202+}
203203+204204+pub mod repo_state {
205205+206206+ pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
207207+ #[allow(unused)]
208208+ use ::core::marker::PhantomData;
209209+ mod sealed {
210210+ pub trait Sealed {}
211211+ }
212212+ /// State trait tracking which required fields have been set
213213+ pub trait State: sealed::Sealed {
214214+ type Did;
215215+ }
216216+ /// Empty state - all required fields are unset
217217+ pub struct Empty(());
218218+ impl sealed::Sealed for Empty {}
219219+ impl State for Empty {
220220+ type Did = Unset;
221221+ }
222222+ ///State transition - sets the `did` field to Set
223223+ pub struct SetDid<S: State = Empty>(PhantomData<fn() -> S>);
224224+ impl<S: State> sealed::Sealed for SetDid<S> {}
225225+ impl<S: State> State for SetDid<S> {
226226+ type Did = Set<members::did>;
227227+ }
228228+ /// Marker types for field names
229229+ #[allow(non_camel_case_types)]
230230+ pub mod members {
231231+ ///Marker type for the `did` field
232232+ pub struct did(());
233233+ }
234234+}
235235+236236+/// Builder for constructing an instance of this type
237237+pub struct RepoBuilder<'a, S: repo_state::State> {
238238+ _phantom_state: ::core::marker::PhantomData<fn() -> S>,
239239+ __unsafe_private_named: (
240240+ ::core::option::Option<jacquard_common::types::string::Did<'a>>,
241241+ ),
242242+ _phantom: ::core::marker::PhantomData<&'a ()>,
243243+}
244244+245245+impl<'a> Repo<'a> {
246246+ /// Create a new builder for this type
247247+ pub fn new() -> RepoBuilder<'a, repo_state::Empty> {
248248+ RepoBuilder::new()
249249+ }
250250+}
251251+252252+impl<'a> RepoBuilder<'a, repo_state::Empty> {
253253+ /// Create a new builder with all fields unset
254254+ pub fn new() -> Self {
255255+ RepoBuilder {
256256+ _phantom_state: ::core::marker::PhantomData,
257257+ __unsafe_private_named: (None,),
258258+ _phantom: ::core::marker::PhantomData,
259259+ }
260260+ }
261261+}
262262+263263+impl<'a, S> RepoBuilder<'a, S>
264264+where
265265+ S: repo_state::State,
266266+ S::Did: repo_state::IsUnset,
267267+{
268268+ /// Set the `did` field (required)
269269+ pub fn did(
270270+ mut self,
271271+ value: impl Into<jacquard_common::types::string::Did<'a>>,
272272+ ) -> RepoBuilder<'a, repo_state::SetDid<S>> {
273273+ self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
274274+ RepoBuilder {
275275+ _phantom_state: ::core::marker::PhantomData,
276276+ __unsafe_private_named: self.__unsafe_private_named,
277277+ _phantom: ::core::marker::PhantomData,
278278+ }
279279+ }
280280+}
281281+282282+impl<'a, S> RepoBuilder<'a, S>
283283+where
284284+ S: repo_state::State,
285285+ S::Did: repo_state::IsSet,
286286+{
287287+ /// Build the final struct
288288+ pub fn build(self) -> Repo<'a> {
289289+ Repo {
290290+ did: self.__unsafe_private_named.0.unwrap(),
291291+ extra_data: Default::default(),
292292+ }
293293+ }
294294+ /// Build the final struct with custom extra_data
295295+ pub fn build_with_data(
296296+ self,
297297+ extra_data: std::collections::BTreeMap<
298298+ jacquard_common::smol_str::SmolStr,
299299+ jacquard_common::types::value::Data<'a>,
300300+ >,
301301+ ) -> Repo<'a> {
302302+ Repo {
303303+ did: self.__unsafe_private_named.0.unwrap(),
304304+ extra_data: Some(extra_data),
305305+ }
306306+ }
307307+}
308308+309309+fn lexicon_doc_com_atproto_sync_listReposByCollection() -> ::jacquard_lexicon::lexicon::LexiconDoc<
310310+ 'static,
311311+> {
312312+ ::jacquard_lexicon::lexicon::LexiconDoc {
313313+ lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
314314+ id: ::jacquard_common::CowStr::new_static(
315315+ "com.atproto.sync.listReposByCollection",
316316+ ),
317317+ revision: None,
318318+ description: None,
319319+ defs: {
320320+ let mut map = ::std::collections::BTreeMap::new();
321321+ map.insert(
322322+ ::jacquard_common::smol_str::SmolStr::new_static("main"),
323323+ ::jacquard_lexicon::lexicon::LexUserType::XrpcQuery(::jacquard_lexicon::lexicon::LexXrpcQuery {
324324+ description: None,
325325+ parameters: Some(
326326+ ::jacquard_lexicon::lexicon::LexXrpcQueryParameter::Params(::jacquard_lexicon::lexicon::LexXrpcParameters {
327327+ description: None,
328328+ required: Some(vec![]),
329329+ properties: {
330330+ #[allow(unused_mut)]
331331+ let mut map = ::std::collections::BTreeMap::new();
332332+ map.insert(
333333+ ::jacquard_common::smol_str::SmolStr::new_static(
334334+ "collection",
335335+ ),
336336+ ::jacquard_lexicon::lexicon::LexXrpcParametersProperty::Array(::jacquard_lexicon::lexicon::LexPrimitiveArray {
337337+ description: None,
338338+ items: ::jacquard_lexicon::lexicon::LexPrimitiveArrayItem::String(::jacquard_lexicon::lexicon::LexString {
339339+ description: None,
340340+ format: Some(
341341+ ::jacquard_lexicon::lexicon::LexStringFormat::Nsid,
342342+ ),
343343+ default: None,
344344+ min_length: None,
345345+ max_length: None,
346346+ min_graphemes: None,
347347+ max_graphemes: None,
348348+ r#enum: None,
349349+ r#const: None,
350350+ known_values: None,
351351+ }),
352352+ min_length: None,
353353+ max_length: None,
354354+ }),
355355+ );
356356+ map.insert(
357357+ ::jacquard_common::smol_str::SmolStr::new_static("cursor"),
358358+ ::jacquard_lexicon::lexicon::LexXrpcParametersProperty::String(::jacquard_lexicon::lexicon::LexString {
359359+ description: None,
360360+ format: None,
361361+ default: None,
362362+ min_length: None,
363363+ max_length: None,
364364+ min_graphemes: None,
365365+ max_graphemes: None,
366366+ r#enum: None,
367367+ r#const: None,
368368+ known_values: None,
369369+ }),
370370+ );
371371+ map.insert(
372372+ ::jacquard_common::smol_str::SmolStr::new_static("limit"),
373373+ ::jacquard_lexicon::lexicon::LexXrpcParametersProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
374374+ description: None,
375375+ default: None,
376376+ minimum: None,
377377+ maximum: None,
378378+ r#enum: None,
379379+ r#const: None,
380380+ }),
381381+ );
382382+ map
383383+ },
384384+ }),
385385+ ),
386386+ output: None,
387387+ errors: None,
388388+ }),
389389+ );
390390+ map.insert(
391391+ ::jacquard_common::smol_str::SmolStr::new_static("repo"),
392392+ ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
393393+ description: None,
394394+ required: Some(
395395+ vec![::jacquard_common::smol_str::SmolStr::new_static("did")],
396396+ ),
397397+ nullable: None,
398398+ properties: {
399399+ #[allow(unused_mut)]
400400+ let mut map = ::std::collections::BTreeMap::new();
401401+ map.insert(
402402+ ::jacquard_common::smol_str::SmolStr::new_static("did"),
403403+ ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
404404+ description: None,
405405+ format: Some(
406406+ ::jacquard_lexicon::lexicon::LexStringFormat::Did,
407407+ ),
408408+ default: None,
409409+ min_length: None,
410410+ max_length: None,
411411+ min_graphemes: None,
412412+ max_graphemes: None,
413413+ r#enum: None,
414414+ r#const: None,
415415+ known_values: None,
416416+ }),
417417+ );
418418+ map
419419+ },
420420+ }),
421421+ );
422422+ map
423423+ },
424424+ }
425425+}
426426+427427+impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Repo<'a> {
428428+ fn nsid() -> &'static str {
429429+ "com.atproto.sync.listReposByCollection"
430430+ }
431431+ fn def_name() -> &'static str {
432432+ "repo"
433433+ }
434434+ fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
435435+ lexicon_doc_com_atproto_sync_listReposByCollection()
436436+ }
437437+ fn validate(
438438+ &self,
439439+ ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
440440+ Ok(())
441441+ }
442442+}
+6
src/lib.rs
···66pub mod storage;
77pub mod sync;
88pub mod util;
99+1010+// hack for generated lexicons: they expect to be able to import some stuff from
1111+// the crate root
1212+mod generated_lexicons;
1313+#[doc(hidden)]
1414+pub use generated_lexicons::{builder_types, com_atproto};
+4-7
src/server/list_repos_by_collection.rs
···99 http::StatusCode,
1010 response::{IntoResponse, Response},
1111};
1212-use jacquard_api::com_atproto::sync::list_repos_by_collection::{
1313- ListReposByCollectionOutput, Repo,
1212+use crate::com_atproto::sync::list_repos_by_collection::{
1313+ ListReposByCollectionOutput, ListReposByCollectionRequest, Repo,
1414};
1515use jacquard_axum::ExtractXrpc;
1616use jacquard_common::IntoStatic;
1717use jacquard_common::types::string::Did;
1818-1919-use super::list_repos_by_collection_params::ListReposByCollectionEndpoint;
2018use serde_json::json;
21192220use crate::storage::{DbRef, error::StorageError};
···6866/// the lexicon, because bluesky's own collectiondir only clamps at 10k.
6967pub async fn list_repos_by_collection(
7068 State(db): State<DbRef>,
7171- ExtractXrpc(req): ExtractXrpc<ListReposByCollectionEndpoint>,
6969+ ExtractXrpc(req): ExtractXrpc<ListReposByCollectionRequest>,
7270) -> Result<Json<ListReposByCollectionOutput<'static>>, ListReposByCollectionError> {
7371 let limit = req.limit.unwrap_or(500).clamp(1, 10_000) as usize;
7472···8280 // TODO: multi-collection support. For now require exactly one collection.
8381 let collection = req
8482 .collection
8585- .into_iter()
8686- .next()
8383+ .and_then(|v| v.into_iter().next())
8784 .ok_or(ListReposByCollectionError::BadCursor)?
8885 .into_static();
8986 let (dids, next) = tokio::task::spawn_blocking({
-34
src/server/list_repos_by_collection_params.rs
···11-//! Temporary override of `com.atproto.sync.listReposByCollection` request types.
22-//!
33-//! The updated lexicon accepts `collection` as an optional array of NSIDs rather
44-//! than a single required NSID. Remove this file and revert the changes in
55-//! `mod.rs` and `list_repos_by_collection.rs` once the upstream `jacquard-api`
66-//! crate is updated to match.
77-88-use jacquard_api::com_atproto::sync::list_repos_by_collection::ListReposByCollectionOutput;
99-use jacquard_common::CowStr;
1010-use jacquard_common::types::string::Nsid;
1111-1212-#[derive(
1313- serde::Serialize,
1414- serde::Deserialize,
1515- Debug,
1616- Clone,
1717- jacquard_derive::IntoStatic,
1818- jacquard_derive::XrpcRequest,
1919-)]
2020-#[serde(rename_all = "camelCase")]
2121-#[xrpc(
2222- nsid = "com.atproto.sync.listReposByCollection",
2323- method = Query,
2424- output = ListReposByCollectionOutput,
2525- server,
2626-)]
2727-pub struct ListReposByCollection<'a> {
2828- #[serde(default, borrow)]
2929- pub collection: Vec<Nsid<'a>>,
3030- #[serde(skip_serializing_if = "Option::is_none", borrow)]
3131- pub cursor: Option<CowStr<'a>>,
3232- #[serde(skip_serializing_if = "Option::is_none")]
3333- pub limit: Option<i64>,
3434-}