lightweight com.atproto.sync.listReposByCollection
45
fork

Configure Feed

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

codegen the updated lexicon

phil 45c414be c27e243f

+634 -54
+3
Cargo.lock
··· 2320 2320 "jacquard-api", 2321 2321 "jacquard-axum", 2322 2322 "jacquard-common", 2323 + "jacquard-derive", 2323 2324 "jacquard-identity", 2325 + "jacquard-lexicon", 2324 2326 "jacquard-repo", 2325 2327 "metrics", 2326 2328 "metrics-exporter-prometheus", 2327 2329 "mini-moka", 2328 2330 "reqwest", 2329 2331 "rustls", 2332 + "rustversion", 2330 2333 "serde", 2331 2334 "serde_json", 2332 2335 "thiserror 2.0.18",
+18 -10
Cargo.toml
··· 5 5 6 6 [dependencies] 7 7 axum = "0.8.8" 8 + bytes = "1" 9 + cid = { version = "0.11", default-features = false, features = ["alloc"] } 8 10 clap = { version = "4.5.60", features = ["derive", "env"] } 11 + dashmap = "5" 9 12 fjall = "3.0.3" 10 13 futures = "0.3" 14 + governor = "0.6" 15 + http = "1" 11 16 jacquard-api = { version = "0.9.5", default-features = false, features = ["com_atproto", "streaming"] } 12 17 jacquard-axum = { version = "0.9.6", default-features = false, features = ["tracing"] } 13 18 jacquard-common = { version = "0.9.5", features = ["websocket", "reqwest-client"] } 19 + jacquard-derive = "0.9.5" 14 20 jacquard-identity = "0.9.5" 21 + jacquard-lexicon = "0.9.5" 15 22 jacquard-repo = "0.9.6" 16 - cid = { version = "0.11", default-features = false, features = ["alloc"] } 17 23 metrics = "0.24.3" 18 - bytes = "1" 19 24 metrics-exporter-prometheus = { version = "0.18.1", features = ["http-listener"] } 25 + mini-moka = "0.10" 20 26 reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } 21 27 rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] } 28 + rustversion = "1" 22 29 serde = { version = "1", features = ["derive"] } 30 + serde_json = "1" 23 31 thiserror = "2.0.18" 24 32 tokio = { version = "1.49.0", features = ["full"] } 25 33 tokio-util = { version = "0.7", features = ["rt"] } 26 34 tracing = "0.1.44" 27 35 tracing-subscriber = { version = "0.3", features = ["env-filter"] } 28 - dashmap = "5" 29 - governor = "0.6" 30 - http = "1" 31 - mini-moka = "0.10" 36 + 37 + [dev-dependencies] 38 + jacquard-lexicon = { version = "0.9.5", features = ["codegen"] } 32 39 serde_json = "1" 40 + wiremock = "0.6" 41 + 42 + [[example]] 43 + name = "lexgen" 44 + path = "src/examples/lexgen.rs" 33 45 34 46 [[example]] 35 47 name = "list-repo-collections" ··· 38 50 [[example]] 39 51 name = "enqueue-resync" 40 52 path = "src/examples/enqueue_resync.rs" 41 - 42 - [dev-dependencies] 43 - serde_json = "1" 44 - wiremock = "0.6"
+10
hacking.md
··· 7 7 8 8 in the project root to be up and running! 9 9 10 + do *not* manually modify any contents in `src/generated_lexicons/` -- to 11 + regenerate lexicons from the local defs in [`./lexicons`](./lexicons/), just do: 12 + 13 + ```bash 14 + $ cargo run --example lexgen 15 + ``` 16 + 17 + and the codegen'd lexicons will be updated based on the JSON lexicon definitions 18 + 19 + 10 20 - TODO: make local dev work without needing an actual live firehose and backfill etc. 11 21 12 22 before submitting a pull request, please run rustfmt, clippy, and all tests
+52
lexicons/com_atproto/sync/listReposByCollection.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.listReposByCollection", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "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.", 8 + "parameters": { 9 + "type": "params", 10 + "required": [], 11 + "properties": { 12 + "collection": { 13 + "type": "array", 14 + "items": { 15 + "type": "string", 16 + "format": "nsid" 17 + } 18 + }, 19 + "limit": { 20 + "type": "integer", 21 + "description": "Maximum size of response set. Recommend setting a large maximum (1000+) when enumerating large DID lists.", 22 + "minimum": 1, 23 + "maximum": 2000, 24 + "default": 500 25 + }, 26 + "cursor": { "type": "string" } 27 + } 28 + }, 29 + "output": { 30 + "encoding": "application/json", 31 + "schema": { 32 + "type": "object", 33 + "required": ["repos"], 34 + "properties": { 35 + "cursor": { "type": "string" }, 36 + "repos": { 37 + "type": "array", 38 + "items": { "type": "ref", "ref": "#repo" } 39 + } 40 + } 41 + } 42 + } 43 + }, 44 + "repo": { 45 + "type": "object", 46 + "required": ["did"], 47 + "properties": { 48 + "did": { "type": "string", "format": "did" } 49 + } 50 + } 51 + } 52 + }
+33
src/examples/lexgen.rs
··· 1 + //! lexicons/**/*.json => src/generated_lexicons/**/*.rs 2 + //! 3 + //! to update: 4 + //! cargo run --example lexgen 5 + //! 6 + //! reads lexicon files, writes rust code. 7 + //! 8 + //! note! there is a manually-maintained top-level declaration required, see 9 + //! `src/generated_lexicons.rs`. 10 + 11 + use std::path::PathBuf; 12 + 13 + fn main() { 14 + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 15 + let lexicons_dir = root.join("lexicons"); 16 + let out_dir = root.join("src").join("generated_lexicons"); 17 + 18 + let ours = jacquard_lexicon::corpus::LexiconCorpus::load_from_dir(&lexicons_dir) 19 + .expect("failed to load lexicons"); 20 + 21 + jacquard_lexicon::codegen::CodeGenerator::new(&ours, "crate") 22 + .write_to_disk(&out_dir) 23 + .expect("lexgen failed"); 24 + 25 + // hack!! lexgen expects that it's writing a whole crate. 26 + // that's probably the *right* way to do this (make a cargo workspace, ...) 27 + // but we're lazy so we just hackily delete the generated `lib.rs` and keep 28 + // top-level declarations in `src/generated_lexicons.rs`. 29 + std::fs::remove_file(out_dir.join("lib.rs")).expect("lib cleanup failed"); 30 + 31 + eprintln!("done! outputs written to src/generated_lexicons/"); 32 + eprintln!("if any top-level namespaces changed, manually updating src/generated_lexicons.rs is required."); 33 + }
+9
src/generated_lexicons.rs
··· 1 + //! manually maintained codegen declarations 2 + //! 3 + //! see examples/lexgen.rs 4 + 5 + // the codegen is *almost* clean (and the one warning makes sense to ignore) 6 + #![allow(non_snake_case)] 7 + 8 + pub mod builder_types; 9 + pub mod com_atproto;
+43
src/generated_lexicons/builder_types.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 6 + /// Marker type indicating a builder field has been set 7 + pub struct Set<T>(pub T); 8 + impl<T> Set<T> { 9 + /// Extract the inner value 10 + #[inline] 11 + pub fn into_inner(self) -> T { 12 + self.0 13 + } 14 + } 15 + 16 + /// Marker type indicating a builder field has not been set 17 + pub struct Unset; 18 + /// Trait indicating a builder field is set (has a value) 19 + #[rustversion::attr( 20 + since(1.78.0), 21 + diagnostic::on_unimplemented( 22 + message = "the field `{Self}` was not set, but this method requires it to be set", 23 + label = "the field `{Self}` was not set" 24 + ) 25 + )] 26 + pub trait IsSet: private::Sealed {} 27 + /// Trait indicating a builder field is unset (no value yet) 28 + #[rustversion::attr( 29 + since(1.78.0), 30 + diagnostic::on_unimplemented( 31 + message = "the field `{Self}` was already set, but this method requires it to be unset", 32 + label = "the field `{Self}` was already set" 33 + ) 34 + )] 35 + pub trait IsUnset: private::Sealed {} 36 + impl<T> IsSet for Set<T> {} 37 + impl IsUnset for Unset {} 38 + mod private { 39 + /// Sealed trait to prevent external implementations 40 + pub trait Sealed {} 41 + impl<T> Sealed for super::Set<T> {} 42 + impl Sealed for super::Unset {} 43 + }
+6
src/generated_lexicons/com_atproto.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 6 + pub mod sync;
+6
src/generated_lexicons/com_atproto/sync.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // This file was automatically generated from Lexicon schemas. 4 + // Any manual changes will be overwritten on the next regeneration. 5 + 6 + pub mod list_repos_by_collection;
+442
src/generated_lexicons/com_atproto/sync/list_repos_by_collection.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: com.atproto.sync.listReposByCollection 4 + // 5 + // This file was automatically generated from Lexicon schemas. 6 + // Any manual changes will be overwritten on the next regeneration. 7 + 8 + #[derive( 9 + serde::Serialize, 10 + serde::Deserialize, 11 + Debug, 12 + Clone, 13 + PartialEq, 14 + Eq, 15 + jacquard_derive::IntoStatic 16 + )] 17 + #[serde(rename_all = "camelCase")] 18 + pub struct ListReposByCollection<'a> { 19 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 20 + #[serde(borrow)] 21 + pub collection: std::option::Option<Vec<jacquard_common::types::string::Nsid<'a>>>, 22 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 + #[serde(borrow)] 24 + pub cursor: std::option::Option<jacquard_common::CowStr<'a>>, 25 + ///(default: 500, min: 1, max: 2000) 26 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 27 + pub limit: std::option::Option<i64>, 28 + } 29 + 30 + pub mod list_repos_by_collection_state { 31 + 32 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 33 + #[allow(unused)] 34 + use ::core::marker::PhantomData; 35 + mod sealed { 36 + pub trait Sealed {} 37 + } 38 + /// State trait tracking which required fields have been set 39 + pub trait State: sealed::Sealed {} 40 + /// Empty state - all required fields are unset 41 + pub struct Empty(()); 42 + impl sealed::Sealed for Empty {} 43 + impl State for Empty {} 44 + /// Marker types for field names 45 + #[allow(non_camel_case_types)] 46 + pub mod members {} 47 + } 48 + 49 + /// Builder for constructing an instance of this type 50 + pub struct ListReposByCollectionBuilder<'a, S: list_repos_by_collection_state::State> { 51 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 52 + __unsafe_private_named: ( 53 + ::core::option::Option<Vec<jacquard_common::types::string::Nsid<'a>>>, 54 + ::core::option::Option<jacquard_common::CowStr<'a>>, 55 + ::core::option::Option<i64>, 56 + ), 57 + _phantom: ::core::marker::PhantomData<&'a ()>, 58 + } 59 + 60 + impl<'a> ListReposByCollection<'a> { 61 + /// Create a new builder for this type 62 + pub fn new() -> ListReposByCollectionBuilder< 63 + 'a, 64 + list_repos_by_collection_state::Empty, 65 + > { 66 + ListReposByCollectionBuilder::new() 67 + } 68 + } 69 + 70 + impl<'a> ListReposByCollectionBuilder<'a, list_repos_by_collection_state::Empty> { 71 + /// Create a new builder with all fields unset 72 + pub fn new() -> Self { 73 + ListReposByCollectionBuilder { 74 + _phantom_state: ::core::marker::PhantomData, 75 + __unsafe_private_named: (None, None, None), 76 + _phantom: ::core::marker::PhantomData, 77 + } 78 + } 79 + } 80 + 81 + impl<'a, S: list_repos_by_collection_state::State> ListReposByCollectionBuilder<'a, S> { 82 + /// Set the `collection` field (optional) 83 + pub fn collection( 84 + mut self, 85 + value: impl Into<Option<Vec<jacquard_common::types::string::Nsid<'a>>>>, 86 + ) -> Self { 87 + self.__unsafe_private_named.0 = value.into(); 88 + self 89 + } 90 + /// Set the `collection` field to an Option value (optional) 91 + pub fn maybe_collection( 92 + mut self, 93 + value: Option<Vec<jacquard_common::types::string::Nsid<'a>>>, 94 + ) -> Self { 95 + self.__unsafe_private_named.0 = value; 96 + self 97 + } 98 + } 99 + 100 + impl<'a, S: list_repos_by_collection_state::State> ListReposByCollectionBuilder<'a, S> { 101 + /// Set the `cursor` field (optional) 102 + pub fn cursor( 103 + mut self, 104 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 105 + ) -> Self { 106 + self.__unsafe_private_named.1 = value.into(); 107 + self 108 + } 109 + /// Set the `cursor` field to an Option value (optional) 110 + pub fn maybe_cursor(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 111 + self.__unsafe_private_named.1 = value; 112 + self 113 + } 114 + } 115 + 116 + impl<'a, S: list_repos_by_collection_state::State> ListReposByCollectionBuilder<'a, S> { 117 + /// Set the `limit` field (optional) 118 + pub fn limit(mut self, value: impl Into<Option<i64>>) -> Self { 119 + self.__unsafe_private_named.2 = value.into(); 120 + self 121 + } 122 + /// Set the `limit` field to an Option value (optional) 123 + pub fn maybe_limit(mut self, value: Option<i64>) -> Self { 124 + self.__unsafe_private_named.2 = value; 125 + self 126 + } 127 + } 128 + 129 + impl<'a, S> ListReposByCollectionBuilder<'a, S> 130 + where 131 + S: list_repos_by_collection_state::State, 132 + { 133 + /// Build the final struct 134 + pub fn build(self) -> ListReposByCollection<'a> { 135 + ListReposByCollection { 136 + collection: self.__unsafe_private_named.0, 137 + cursor: self.__unsafe_private_named.1, 138 + limit: self.__unsafe_private_named.2, 139 + } 140 + } 141 + } 142 + 143 + #[jacquard_derive::lexicon] 144 + #[derive( 145 + serde::Serialize, 146 + serde::Deserialize, 147 + Debug, 148 + Clone, 149 + PartialEq, 150 + Eq, 151 + jacquard_derive::IntoStatic 152 + )] 153 + #[serde(rename_all = "camelCase")] 154 + pub struct ListReposByCollectionOutput<'a> { 155 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 156 + #[serde(borrow)] 157 + pub cursor: std::option::Option<jacquard_common::CowStr<'a>>, 158 + #[serde(borrow)] 159 + pub repos: Vec<crate::com_atproto::sync::list_repos_by_collection::Repo<'a>>, 160 + } 161 + 162 + /// Response type for 163 + ///com.atproto.sync.listReposByCollection 164 + pub struct ListReposByCollectionResponse; 165 + impl jacquard_common::xrpc::XrpcResp for ListReposByCollectionResponse { 166 + const NSID: &'static str = "com.atproto.sync.listReposByCollection"; 167 + const ENCODING: &'static str = "application/json"; 168 + type Output<'de> = ListReposByCollectionOutput<'de>; 169 + type Err<'de> = jacquard_common::xrpc::GenericError<'de>; 170 + } 171 + 172 + impl<'a> jacquard_common::xrpc::XrpcRequest for ListReposByCollection<'a> { 173 + const NSID: &'static str = "com.atproto.sync.listReposByCollection"; 174 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 175 + type Response = ListReposByCollectionResponse; 176 + } 177 + 178 + /// Endpoint type for 179 + ///com.atproto.sync.listReposByCollection 180 + pub struct ListReposByCollectionRequest; 181 + impl jacquard_common::xrpc::XrpcEndpoint for ListReposByCollectionRequest { 182 + const PATH: &'static str = "/xrpc/com.atproto.sync.listReposByCollection"; 183 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 184 + type Request<'de> = ListReposByCollection<'de>; 185 + type Response = ListReposByCollectionResponse; 186 + } 187 + 188 + #[jacquard_derive::lexicon] 189 + #[derive( 190 + serde::Serialize, 191 + serde::Deserialize, 192 + Debug, 193 + Clone, 194 + PartialEq, 195 + Eq, 196 + jacquard_derive::IntoStatic 197 + )] 198 + #[serde(rename_all = "camelCase")] 199 + pub struct Repo<'a> { 200 + #[serde(borrow)] 201 + pub did: jacquard_common::types::string::Did<'a>, 202 + } 203 + 204 + pub mod repo_state { 205 + 206 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 207 + #[allow(unused)] 208 + use ::core::marker::PhantomData; 209 + mod sealed { 210 + pub trait Sealed {} 211 + } 212 + /// State trait tracking which required fields have been set 213 + pub trait State: sealed::Sealed { 214 + type Did; 215 + } 216 + /// Empty state - all required fields are unset 217 + pub struct Empty(()); 218 + impl sealed::Sealed for Empty {} 219 + impl State for Empty { 220 + type Did = Unset; 221 + } 222 + ///State transition - sets the `did` field to Set 223 + pub struct SetDid<S: State = Empty>(PhantomData<fn() -> S>); 224 + impl<S: State> sealed::Sealed for SetDid<S> {} 225 + impl<S: State> State for SetDid<S> { 226 + type Did = Set<members::did>; 227 + } 228 + /// Marker types for field names 229 + #[allow(non_camel_case_types)] 230 + pub mod members { 231 + ///Marker type for the `did` field 232 + pub struct did(()); 233 + } 234 + } 235 + 236 + /// Builder for constructing an instance of this type 237 + pub struct RepoBuilder<'a, S: repo_state::State> { 238 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 239 + __unsafe_private_named: ( 240 + ::core::option::Option<jacquard_common::types::string::Did<'a>>, 241 + ), 242 + _phantom: ::core::marker::PhantomData<&'a ()>, 243 + } 244 + 245 + impl<'a> Repo<'a> { 246 + /// Create a new builder for this type 247 + pub fn new() -> RepoBuilder<'a, repo_state::Empty> { 248 + RepoBuilder::new() 249 + } 250 + } 251 + 252 + impl<'a> RepoBuilder<'a, repo_state::Empty> { 253 + /// Create a new builder with all fields unset 254 + pub fn new() -> Self { 255 + RepoBuilder { 256 + _phantom_state: ::core::marker::PhantomData, 257 + __unsafe_private_named: (None,), 258 + _phantom: ::core::marker::PhantomData, 259 + } 260 + } 261 + } 262 + 263 + impl<'a, S> RepoBuilder<'a, S> 264 + where 265 + S: repo_state::State, 266 + S::Did: repo_state::IsUnset, 267 + { 268 + /// Set the `did` field (required) 269 + pub fn did( 270 + mut self, 271 + value: impl Into<jacquard_common::types::string::Did<'a>>, 272 + ) -> RepoBuilder<'a, repo_state::SetDid<S>> { 273 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 274 + RepoBuilder { 275 + _phantom_state: ::core::marker::PhantomData, 276 + __unsafe_private_named: self.__unsafe_private_named, 277 + _phantom: ::core::marker::PhantomData, 278 + } 279 + } 280 + } 281 + 282 + impl<'a, S> RepoBuilder<'a, S> 283 + where 284 + S: repo_state::State, 285 + S::Did: repo_state::IsSet, 286 + { 287 + /// Build the final struct 288 + pub fn build(self) -> Repo<'a> { 289 + Repo { 290 + did: self.__unsafe_private_named.0.unwrap(), 291 + extra_data: Default::default(), 292 + } 293 + } 294 + /// Build the final struct with custom extra_data 295 + pub fn build_with_data( 296 + self, 297 + extra_data: std::collections::BTreeMap< 298 + jacquard_common::smol_str::SmolStr, 299 + jacquard_common::types::value::Data<'a>, 300 + >, 301 + ) -> Repo<'a> { 302 + Repo { 303 + did: self.__unsafe_private_named.0.unwrap(), 304 + extra_data: Some(extra_data), 305 + } 306 + } 307 + } 308 + 309 + fn lexicon_doc_com_atproto_sync_listReposByCollection() -> ::jacquard_lexicon::lexicon::LexiconDoc< 310 + 'static, 311 + > { 312 + ::jacquard_lexicon::lexicon::LexiconDoc { 313 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 314 + id: ::jacquard_common::CowStr::new_static( 315 + "com.atproto.sync.listReposByCollection", 316 + ), 317 + revision: None, 318 + description: None, 319 + defs: { 320 + let mut map = ::std::collections::BTreeMap::new(); 321 + map.insert( 322 + ::jacquard_common::smol_str::SmolStr::new_static("main"), 323 + ::jacquard_lexicon::lexicon::LexUserType::XrpcQuery(::jacquard_lexicon::lexicon::LexXrpcQuery { 324 + description: None, 325 + parameters: Some( 326 + ::jacquard_lexicon::lexicon::LexXrpcQueryParameter::Params(::jacquard_lexicon::lexicon::LexXrpcParameters { 327 + description: None, 328 + required: Some(vec![]), 329 + properties: { 330 + #[allow(unused_mut)] 331 + let mut map = ::std::collections::BTreeMap::new(); 332 + map.insert( 333 + ::jacquard_common::smol_str::SmolStr::new_static( 334 + "collection", 335 + ), 336 + ::jacquard_lexicon::lexicon::LexXrpcParametersProperty::Array(::jacquard_lexicon::lexicon::LexPrimitiveArray { 337 + description: None, 338 + items: ::jacquard_lexicon::lexicon::LexPrimitiveArrayItem::String(::jacquard_lexicon::lexicon::LexString { 339 + description: None, 340 + format: Some( 341 + ::jacquard_lexicon::lexicon::LexStringFormat::Nsid, 342 + ), 343 + default: None, 344 + min_length: None, 345 + max_length: None, 346 + min_graphemes: None, 347 + max_graphemes: None, 348 + r#enum: None, 349 + r#const: None, 350 + known_values: None, 351 + }), 352 + min_length: None, 353 + max_length: None, 354 + }), 355 + ); 356 + map.insert( 357 + ::jacquard_common::smol_str::SmolStr::new_static("cursor"), 358 + ::jacquard_lexicon::lexicon::LexXrpcParametersProperty::String(::jacquard_lexicon::lexicon::LexString { 359 + description: None, 360 + format: None, 361 + default: None, 362 + min_length: None, 363 + max_length: None, 364 + min_graphemes: None, 365 + max_graphemes: None, 366 + r#enum: None, 367 + r#const: None, 368 + known_values: None, 369 + }), 370 + ); 371 + map.insert( 372 + ::jacquard_common::smol_str::SmolStr::new_static("limit"), 373 + ::jacquard_lexicon::lexicon::LexXrpcParametersProperty::Integer(::jacquard_lexicon::lexicon::LexInteger { 374 + description: None, 375 + default: None, 376 + minimum: None, 377 + maximum: None, 378 + r#enum: None, 379 + r#const: None, 380 + }), 381 + ); 382 + map 383 + }, 384 + }), 385 + ), 386 + output: None, 387 + errors: None, 388 + }), 389 + ); 390 + map.insert( 391 + ::jacquard_common::smol_str::SmolStr::new_static("repo"), 392 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 393 + description: None, 394 + required: Some( 395 + vec![::jacquard_common::smol_str::SmolStr::new_static("did")], 396 + ), 397 + nullable: None, 398 + properties: { 399 + #[allow(unused_mut)] 400 + let mut map = ::std::collections::BTreeMap::new(); 401 + map.insert( 402 + ::jacquard_common::smol_str::SmolStr::new_static("did"), 403 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 404 + description: None, 405 + format: Some( 406 + ::jacquard_lexicon::lexicon::LexStringFormat::Did, 407 + ), 408 + default: None, 409 + min_length: None, 410 + max_length: None, 411 + min_graphemes: None, 412 + max_graphemes: None, 413 + r#enum: None, 414 + r#const: None, 415 + known_values: None, 416 + }), 417 + ); 418 + map 419 + }, 420 + }), 421 + ); 422 + map 423 + }, 424 + } 425 + } 426 + 427 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Repo<'a> { 428 + fn nsid() -> &'static str { 429 + "com.atproto.sync.listReposByCollection" 430 + } 431 + fn def_name() -> &'static str { 432 + "repo" 433 + } 434 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 435 + lexicon_doc_com_atproto_sync_listReposByCollection() 436 + } 437 + fn validate( 438 + &self, 439 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 440 + Ok(()) 441 + } 442 + }
+6
src/lib.rs
··· 6 6 pub mod storage; 7 7 pub mod sync; 8 8 pub mod util; 9 + 10 + // hack for generated lexicons: they expect to be able to import some stuff from 11 + // the crate root 12 + mod generated_lexicons; 13 + #[doc(hidden)] 14 + pub use generated_lexicons::{builder_types, com_atproto};
+4 -7
src/server/list_repos_by_collection.rs
··· 9 9 http::StatusCode, 10 10 response::{IntoResponse, Response}, 11 11 }; 12 - use jacquard_api::com_atproto::sync::list_repos_by_collection::{ 13 - ListReposByCollectionOutput, Repo, 12 + use crate::com_atproto::sync::list_repos_by_collection::{ 13 + ListReposByCollectionOutput, ListReposByCollectionRequest, Repo, 14 14 }; 15 15 use jacquard_axum::ExtractXrpc; 16 16 use jacquard_common::IntoStatic; 17 17 use jacquard_common::types::string::Did; 18 - 19 - use super::list_repos_by_collection_params::ListReposByCollectionEndpoint; 20 18 use serde_json::json; 21 19 22 20 use crate::storage::{DbRef, error::StorageError}; ··· 68 66 /// the lexicon, because bluesky's own collectiondir only clamps at 10k. 69 67 pub async fn list_repos_by_collection( 70 68 State(db): State<DbRef>, 71 - ExtractXrpc(req): ExtractXrpc<ListReposByCollectionEndpoint>, 69 + ExtractXrpc(req): ExtractXrpc<ListReposByCollectionRequest>, 72 70 ) -> Result<Json<ListReposByCollectionOutput<'static>>, ListReposByCollectionError> { 73 71 let limit = req.limit.unwrap_or(500).clamp(1, 10_000) as usize; 74 72 ··· 82 80 // TODO: multi-collection support. For now require exactly one collection. 83 81 let collection = req 84 82 .collection 85 - .into_iter() 86 - .next() 83 + .and_then(|v| v.into_iter().next()) 87 84 .ok_or(ListReposByCollectionError::BadCursor)? 88 85 .into_static(); 89 86 let (dids, next) = tokio::task::spawn_blocking({
-34
src/server/list_repos_by_collection_params.rs
··· 1 - //! Temporary override of `com.atproto.sync.listReposByCollection` request types. 2 - //! 3 - //! The updated lexicon accepts `collection` as an optional array of NSIDs rather 4 - //! than a single required NSID. Remove this file and revert the changes in 5 - //! `mod.rs` and `list_repos_by_collection.rs` once the upstream `jacquard-api` 6 - //! crate is updated to match. 7 - 8 - use jacquard_api::com_atproto::sync::list_repos_by_collection::ListReposByCollectionOutput; 9 - use jacquard_common::CowStr; 10 - use jacquard_common::types::string::Nsid; 11 - 12 - #[derive( 13 - serde::Serialize, 14 - serde::Deserialize, 15 - Debug, 16 - Clone, 17 - jacquard_derive::IntoStatic, 18 - jacquard_derive::XrpcRequest, 19 - )] 20 - #[serde(rename_all = "camelCase")] 21 - #[xrpc( 22 - nsid = "com.atproto.sync.listReposByCollection", 23 - method = Query, 24 - output = ListReposByCollectionOutput, 25 - server, 26 - )] 27 - pub struct ListReposByCollection<'a> { 28 - #[serde(default, borrow)] 29 - pub collection: Vec<Nsid<'a>>, 30 - #[serde(skip_serializing_if = "Option::is_none", borrow)] 31 - pub cursor: Option<CowStr<'a>>, 32 - #[serde(skip_serializing_if = "Option::is_none")] 33 - pub limit: Option<i64>, 34 - }
+2 -3
src/server/mod.rs
··· 7 7 mod hello; 8 8 mod list_repos; 9 9 mod list_repos_by_collection; 10 - mod list_repos_by_collection_params; 11 10 12 11 use get_repo_status::get_repo_status; 13 12 use list_repos::list_repos; ··· 15 14 16 15 use std::net::SocketAddr; 17 16 17 + use crate::com_atproto::sync::list_repos_by_collection::ListReposByCollectionRequest; 18 18 use jacquard_api::com_atproto::sync::{ 19 19 get_repo_status::GetRepoStatusRequest, list_repos::ListReposRequest, 20 20 }; 21 21 use jacquard_axum::IntoRouter; 22 - use list_repos_by_collection_params::ListReposByCollectionEndpoint; 23 22 24 23 use crate::error::Result; 25 24 use crate::storage::DbRef; ··· 37 36 ) -> Result<()> { 38 37 let app = GetRepoStatusRequest::into_router(get_repo_status) 39 38 .merge(ListReposRequest::into_router(list_repos)) 40 - .merge(ListReposByCollectionEndpoint::into_router( 39 + .merge(ListReposByCollectionRequest::into_router( 41 40 list_repos_by_collection, 42 41 )) 43 42 .with_state(db)