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

fmt

+233 -123
+24 -14
slingshot/src/identity.rs
··· 61 61 #[derive(Debug, Serialize, Deserialize)] 62 62 enum IdentityData { 63 63 NotFound, 64 - Did(Did), // from handle 65 - Doc(PartialMiniDoc), // from did 64 + Did(Did), // from handle 65 + Doc(PartialMiniDoc), // from did 66 66 ServiceDoc(MiniServiceDoc), // from service did 67 67 } 68 68 ··· 86 86 s += std::mem::size_of_val(&sv.endpoint); 87 87 } 88 88 s 89 - }, 89 + } 90 90 }; 91 91 wrapping + inner 92 92 } ··· 189 189 190 190 impl MiniServiceDoc { 191 191 pub fn get(&self, id_fragment: &str, service_type: Option<&str>) -> Option<&MiniService> { 192 - self.services 193 - .iter() 194 - .find(|MiniService { full_id, r#type, .. }| 195 - full_id.ends_with(id_fragment) 196 - && service_type 197 - .map(|t| t == *r#type) 198 - .unwrap_or(true)) 192 + self.services.iter().find(|ms| { 193 + ms.full_id.ends_with(id_fragment) 194 + && service_type.map(|t| t == ms.r#type).unwrap_or(true) 195 + }) 199 196 } 200 197 } 201 198 ··· 217 214 let mut services = Vec::new(); 218 215 let mut seen = HashSet::new(); 219 216 220 - for DidDocServic { id, r#type, service_endpoint } in did_doc.service.unwrap_or(vec![]) { 217 + for DidDocServic { 218 + id, 219 + r#type, 220 + service_endpoint, 221 + } in did_doc.service.unwrap_or(vec![]) 222 + { 221 223 let Some((_, id_fragment)) = id.rsplit_once('#') else { 222 224 continue; 223 225 }; ··· 474 476 ), 475 477 Ok(did_doc) => match did_doc.try_into() { 476 478 Ok(mini_service_doc) => ( 477 - Ok(IdentityVal(UtcDateTime::now(), IdentityData::ServiceDoc(mini_service_doc))), 479 + Ok(IdentityVal( 480 + UtcDateTime::now(), 481 + IdentityData::ServiceDoc(mini_service_doc), 482 + )), 478 483 "true", 479 484 ), 480 485 Err(e) => (Err(IdentityError::BadDidDoc(e)), "false"), ··· 510 515 Ok(Some(mini_service_doc.clone())) 511 516 } 512 517 _ => { 513 - log::error!("identity value mixup: got a doc from a different key type (should be a service did)"); 518 + log::error!( 519 + "identity value mixup: got a doc from a different key type (should be a service did)" 520 + ); 514 521 Err(IdentityError::IdentityValTypeMixup(did.to_string())) 515 522 } 516 523 } ··· 715 722 .increment(1); 716 723 self.cache.insert( 717 724 task_key.clone(), 718 - IdentityVal(UtcDateTime::now(), IdentityData::ServiceDoc(mini_service_doc)), 725 + IdentityVal( 726 + UtcDateTime::now(), 727 + IdentityData::ServiceDoc(mini_service_doc), 728 + ), 719 729 ); 720 730 } 721 731 Err(atrium_identity::Error::NotFound) => {
+119 -56
slingshot/src/proxy.rs
··· 1 + use crate::{Identity, Repo, error::ProxyError, server::HydrationSource}; 1 2 use atrium_api::types::string::{Did, Nsid}; 2 - use url::Url; 3 - use std::{collections::HashMap, time::Duration}; 4 - use crate::{Repo, Identity, server::HydrationSource, error::ProxyError}; 5 3 use reqwest::Client; 6 4 use serde_json::{Map, Value}; 5 + use std::{collections::HashMap, time::Duration}; 6 + use url::Url; 7 7 8 8 pub enum ParamValue { 9 9 String(Vec<String>), ··· 13 13 pub struct Params(HashMap<String, ParamValue>); 14 14 15 15 impl TryFrom<Map<String, Value>> for Params { 16 - type Error = (); // TODO 16 + type Error = (); // TODO 17 17 fn try_from(val: Map<String, Value>) -> Result<Self, Self::Error> { 18 18 let mut out = HashMap::new(); 19 19 for (k, v) in val { ··· 86 86 .timeout(Duration::from_secs(6)) 87 87 .build() 88 88 .unwrap(); 89 - Self { repo, client, identity } 89 + Self { 90 + repo, 91 + client, 92 + identity, 93 + } 90 94 } 91 95 92 96 pub async fn proxy( ··· 96 100 xrpc: &Nsid, 97 101 params: Option<Map<String, Value>>, 98 102 ) -> Result<Value, ProxyError> { 99 - 100 103 let mut upstream: Url = self 101 104 .identity 102 105 .did_to_mini_service_doc(service_did) ··· 134 137 } 135 138 136 139 // TODO: other headers to proxy 137 - Ok(self.client 140 + Ok(self 141 + .client 138 142 .get(upstream) 139 143 .send() 140 144 .await? ··· 160 164 while let Some((i, c)) = chars.next() { 161 165 match c { 162 166 '[' if in_bracket => return Err(format!("nested opening bracket not allowed, at {i}")), 163 - '[' if key_acc.is_empty() => return Err(format!("missing key before opening bracket, at {i}")), 167 + '[' if key_acc.is_empty() => { 168 + return Err(format!("missing key before opening bracket, at {i}")); 169 + } 164 170 '[' => in_bracket = true, 165 171 ']' if in_bracket => { 166 172 in_bracket = false; 167 173 let key = std::mem::take(&mut key_acc); 168 174 let r#type = std::mem::take(&mut type_acc); 169 - let t = if r#type.is_empty() { None } else { Some(r#type) }; 175 + let t = if r#type.is_empty() { 176 + None 177 + } else { 178 + Some(r#type) 179 + }; 170 180 out.push(PathPart::Vector(key, t)); 171 181 // peek ahead because we need a dot after array if there's more and i don't want to add more loop state 172 182 let Some((i, c)) = chars.next() else { 173 183 break; 174 184 }; 175 185 if c != '.' { 176 - return Err(format!("expected dot after close bracket, found {c:?} at {i}")); 186 + return Err(format!( 187 + "expected dot after close bracket, found {c:?} at {i}" 188 + )); 177 189 } 178 190 } 179 191 ']' => return Err(format!("unexpected close bracket at {i}")), 180 192 '.' if in_bracket => type_acc.push(c), 181 - '.' if key_acc.is_empty() => return Err(format!("missing key before next segment, at {i}")), 193 + '.' if key_acc.is_empty() => { 194 + return Err(format!("missing key before next segment, at {i}")); 195 + } 182 196 '.' => { 183 197 let key = std::mem::take(&mut key_acc); 184 198 assert!(type_acc.is_empty()); ··· 224 238 225 239 #[derive(Debug, PartialEq)] 226 240 pub enum MatchedRef { 227 - AtUri { 228 - uri: String, 229 - cid: Option<String>, 230 - }, 241 + AtUri { uri: String, cid: Option<String> }, 231 242 Identifier(String), 232 243 } 233 244 ··· 241 252 let o = val.as_object()?; 242 253 let uri = o.get("uri")?.as_str()?.to_string(); 243 254 let cid = o.get("cid")?.as_str()?.to_string(); 244 - Some(MatchedRef::AtUri { uri, cid: Some(cid) }) 255 + Some(MatchedRef::AtUri { 256 + uri, 257 + cid: Some(cid), 258 + }) 245 259 } 246 260 RefShape::AtUri => { 247 261 let uri = val.as_str()?.to_string(); ··· 270 284 } 271 285 Some(MatchedRef::Identifier(id.to_string())) 272 286 } 273 - RefShape::AtIdentifier => { 274 - Some(MatchedRef::Identifier(val.as_str()?.to_string())) 275 - } 287 + RefShape::AtIdentifier => Some(MatchedRef::Identifier(val.as_str()?.to_string())), 276 288 } 277 289 } 278 290 ··· 311 323 } 312 324 impl<'a> PathWalker<'a> { 313 325 fn new(path_parts: &'a [PathPart], skeleton: &'a Value) -> Self { 314 - Self { todo: vec![(path_parts, skeleton)] } 326 + Self { 327 + todo: vec![(path_parts, skeleton)], 328 + } 315 329 } 316 330 } 317 331 impl<'a> Iterator for PathWalker<'a> { ··· 336 350 let Some(a) = o.get(k).and_then(|v| v.as_array()) else { 337 351 continue; 338 352 }; 339 - for v in a 340 - .iter() 341 - .rev() 342 - .filter(|c| { 343 - let Some(t) = t else { return true }; 344 - c 345 - .as_object() 346 - .and_then(|o| o.get("$type")) 347 - .and_then(|v| v.as_str()) 348 - .map(|s| s == t) 349 - .unwrap_or(false) 350 - }) 351 - { 353 + for v in a.iter().rev().filter(|c| { 354 + let Some(t) = t else { return true }; 355 + c.as_object() 356 + .and_then(|o| o.get("$type")) 357 + .and_then(|v| v.as_str()) 358 + .map(|s| s == t) 359 + .unwrap_or(false) 360 + }) { 352 361 self.todo.push((rest, v)) 353 362 } 354 363 } ··· 356 365 } 357 366 } 358 367 } 359 - 360 368 361 369 #[cfg(test)] 362 370 mod tests { ··· 369 377 ("", vec![]), 370 378 ("subject", vec![PathPart::Scalar("subject".into())]), 371 379 ("authorDid", vec![PathPart::Scalar("authorDid".into())]), 372 - ("subject.uri", vec![PathPart::Scalar("subject".into()), PathPart::Scalar("uri".into())]), 380 + ( 381 + "subject.uri", 382 + vec![ 383 + PathPart::Scalar("subject".into()), 384 + PathPart::Scalar("uri".into()), 385 + ], 386 + ), 373 387 ("members[]", vec![PathPart::Vector("members".into(), None)]), 374 - ("add[].key", vec![ 375 - PathPart::Vector("add".into(), None), 376 - PathPart::Scalar("key".into()), 377 - ]), 388 + ( 389 + "add[].key", 390 + vec![ 391 + PathPart::Vector("add".into(), None), 392 + PathPart::Scalar("key".into()), 393 + ], 394 + ), 378 395 ("a[b]", vec![PathPart::Vector("a".into(), Some("b".into()))]), 379 - ("a[b.c]", vec![PathPart::Vector("a".into(), Some("b.c".into()))]), 380 - ("facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet#mention].did", vec![ 381 - PathPart::Vector("facets".into(), Some("app.bsky.richtext.facet".into())), 382 - PathPart::Vector("features".into(), Some("app.bsky.richtext.facet#mention".into())), 383 - PathPart::Scalar("did".into()), 384 - ]), 396 + ( 397 + "a[b.c]", 398 + vec![PathPart::Vector("a".into(), Some("b.c".into()))], 399 + ), 400 + ( 401 + "facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet#mention].did", 402 + vec![ 403 + PathPart::Vector("facets".into(), Some("app.bsky.richtext.facet".into())), 404 + PathPart::Vector( 405 + "features".into(), 406 + Some("app.bsky.richtext.facet#mention".into()), 407 + ), 408 + PathPart::Scalar("did".into()), 409 + ], 410 + ), 385 411 ]; 386 412 387 413 for (path, expected) in cases { ··· 402 428 ( 403 429 "strong-ref", 404 430 json!({ "uri": "abc", "cid": "def" }), 405 - Some(MatchedRef::AtUri { uri: "abc".to_string(), cid: Some("def".to_string()) }), 431 + Some(MatchedRef::AtUri { 432 + uri: "abc".to_string(), 433 + cid: Some("def".to_string()), 434 + }), 406 435 ), 407 436 ("at-uri", json!({ "uri": "abc" }), None), 408 437 ("at-uri", json!({ "uri": "abc", "cid": "def" }), None), 409 438 ( 410 439 "at-uri", 411 440 json!("abc"), 412 - Some(MatchedRef::AtUri { uri: "abc".to_string(), cid: None }), 441 + Some(MatchedRef::AtUri { 442 + uri: "abc".to_string(), 443 + cid: None, 444 + }), 413 445 ), 414 446 ("at-uri-parts", json!("abc"), None), 415 447 ("at-uri-parts", json!({}), None), 416 448 ( 417 449 "at-uri-parts", 418 450 json!({"repo": "a", "collection": "b", "rkey": "c"}), 419 - Some(MatchedRef::AtUri { uri: "at://a/b/c".to_string(), cid: None }), 451 + Some(MatchedRef::AtUri { 452 + uri: "at://a/b/c".to_string(), 453 + cid: None, 454 + }), 420 455 ), 421 456 ( 422 457 "at-uri-parts", 423 458 json!({"did": "a", "collection": "b", "rkey": "c"}), 424 - Some(MatchedRef::AtUri { uri: "at://a/b/c".to_string(), cid: None }), 459 + Some(MatchedRef::AtUri { 460 + uri: "at://a/b/c".to_string(), 461 + cid: None, 462 + }), 425 463 ), 426 464 ( 427 465 "at-uri-parts", 428 466 // 'repo' takes precedence over 'did' 429 467 json!({"did": "a", "repo": "z", "collection": "b", "rkey": "c"}), 430 - Some(MatchedRef::AtUri { uri: "at://z/b/c".to_string(), cid: None }), 468 + Some(MatchedRef::AtUri { 469 + uri: "at://z/b/c".to_string(), 470 + cid: None, 471 + }), 431 472 ), 432 473 ( 433 474 "at-uri-parts", 434 475 json!({"repo": "a", "collection": "b", "rkey": "c", "cid": "def"}), 435 - Some(MatchedRef::AtUri { uri: "at://a/b/c".to_string(), cid: Some("def".to_string()) }), 476 + Some(MatchedRef::AtUri { 477 + uri: "at://a/b/c".to_string(), 478 + cid: Some("def".to_string()), 479 + }), 436 480 ), 437 481 ( 438 482 "at-uri-parts", 439 483 json!({"repo": "a", "collection": "b", "rkey": "c", "cid": {}}), 440 - Some(MatchedRef::AtUri { uri: "at://a/b/c".to_string(), cid: None }), 484 + Some(MatchedRef::AtUri { 485 + uri: "at://a/b/c".to_string(), 486 + cid: None, 487 + }), 441 488 ), 442 489 ("did", json!({}), None), 443 490 ("did", json!(""), None), 444 491 ("did", json!("bad-example.com"), None), 445 - ("did", json!("did:plc:xyz"), Some(MatchedRef::Identifier("did:plc:xyz".to_string()))), 492 + ( 493 + "did", 494 + json!("did:plc:xyz"), 495 + Some(MatchedRef::Identifier("did:plc:xyz".to_string())), 496 + ), 446 497 ("handle", json!({}), None), 447 - ("handle", json!("bad-example.com"), Some(MatchedRef::Identifier("bad-example.com".to_string()))), 498 + ( 499 + "handle", 500 + json!("bad-example.com"), 501 + Some(MatchedRef::Identifier("bad-example.com".to_string())), 502 + ), 448 503 ("handle", json!("did:plc:xyz"), None), 449 504 ("at-identifier", json!({}), None), 450 - ("at-identifier", json!("bad-example.com"), Some(MatchedRef::Identifier("bad-example.com".to_string()))), 451 - ("at-identifier", json!("did:plc:xyz"), Some(MatchedRef::Identifier("did:plc:xyz".to_string()))), 505 + ( 506 + "at-identifier", 507 + json!("bad-example.com"), 508 + Some(MatchedRef::Identifier("bad-example.com".to_string())), 509 + ), 510 + ( 511 + "at-identifier", 512 + json!("did:plc:xyz"), 513 + Some(MatchedRef::Identifier("did:plc:xyz".to_string())), 514 + ), 452 515 ]; 453 516 for (shape, val, expected) in cases { 454 517 let s = shape.try_into().unwrap();
+90 -53
slingshot/src/server.rs
··· 1 1 use crate::{ 2 2 CachedRecord, ErrorResponseObject, Identity, Proxy, Repo, 3 3 error::{RecordError, ServerError}, 4 - proxy::{extract_links, MatchedRef}, 4 + proxy::{MatchedRef, extract_links}, 5 5 record::RawRecord, 6 6 }; 7 7 use atrium_api::types::string::{Cid, Did, Handle, Nsid, RecordKey}; 8 8 use foyer::HybridCache; 9 9 use links::at_uri::parse_at_uri as normalize_at_uri; 10 10 use serde::Serialize; 11 - use std::{path::PathBuf, str::FromStr, sync::Arc, time::Instant, collections::HashMap}; 11 + use std::{collections::HashMap, path::PathBuf, str::FromStr, sync::Arc, time::Instant}; 12 12 use tokio::sync::mpsc; 13 13 use tokio_util::sync::CancellationToken; 14 14 ··· 24 24 }; 25 25 use poem_openapi::{ 26 26 ApiResponse, ContactObject, ExternalDocumentObject, Object, OpenApi, OpenApiService, Tags, 27 - Union, 28 - param::Query, payload::Json, types::Example, 27 + Union, param::Query, payload::Json, types::Example, 29 28 }; 30 29 31 30 fn example_handle() -> String { ··· 233 232 Ok(Json<ServiceResponseObject>), 234 233 /// Bad request or service not resolved 235 234 #[oai(status = 400)] 236 - BadRequest(XrpcError) 235 + BadRequest(XrpcError), 237 236 } 238 237 239 238 #[derive(Object)] ··· 257 256 } 258 257 259 258 // todo: there's gotta be a supertrait that collects these? 260 - use poem_openapi::types::{Type, ToJSON, ParseFromJSON, IsObjectType}; 259 + use poem_openapi::types::{IsObjectType, ParseFromJSON, ToJSON, Type}; 261 260 262 261 #[derive(Union)] 263 262 #[oai(discriminator_name = "status", rename_all = "camelCase")] ··· 281 280 fn example() -> Self { 282 281 Self { 283 282 output: serde_json::json!({}), 284 - records: HashMap::from([ 285 - ("asdf".into(), Hydration::Pending(ProxyHydrationPending { url: "todo".into() })), 286 - ]), 283 + records: HashMap::from([( 284 + "asdf".into(), 285 + Hydration::Pending(ProxyHydrationPending { url: "todo".into() }), 286 + )]), 287 287 identifiers: HashMap::new(), 288 288 } 289 289 } ··· 295 295 #[oai(status = 200)] 296 296 Ok(Json<ProxyHydrateResponseObject>), 297 297 #[oai(status = 400)] 298 - BadRequest(XrpcError) 298 + BadRequest(XrpcError), 299 299 } 300 300 301 301 #[derive(Object)] ··· 329 329 /// Paths within the response to look for at-uris that can be hydrated 330 330 hydration_sources: Vec<HydrationSource>, 331 331 // todo: deadline thing 332 - 333 332 } 334 333 impl Example for ProxyQueryPayload { 335 334 fn example() -> Self { ··· 339 338 params: Some(serde_json::json!({ 340 339 "feed": "at://did:plc:oio4hkxaop4ao4wz2pp3f4cr/app.bsky.feed.generator/atproto", 341 340 })), 342 - hydration_sources: vec![ 343 - HydrationSource { 344 - path: "feed[].post".to_string(), 345 - shape: "at-uri".to_string(), 346 - } 347 - ], 341 + hydration_sources: vec![HydrationSource { 342 + path: "feed[].post".to_string(), 343 + shape: "at-uri".to_string(), 344 + }], 348 345 } 349 346 } 350 347 } ··· 741 738 Query(r#type): Query<Option<String>>, 742 739 ) -> ResolveServiceResponse { 743 740 let Ok(did) = Did::new(did) else { 744 - return ResolveServiceResponse::BadRequest(xrpc_error("InvalidRequest", "could not parse 'did' into a DID")); 741 + return ResolveServiceResponse::BadRequest(xrpc_error( 742 + "InvalidRequest", 743 + "could not parse 'did' into a DID", 744 + )); 745 745 }; 746 746 let identity = self.identity.clone(); 747 747 Self::resolve_service_impl(&did, &id, r#type.as_deref(), identity).await ··· 751 751 did: &Did, 752 752 id_fragment: &str, 753 753 service_type: Option<&str>, 754 - identity: Identity 754 + identity: Identity, 755 755 ) -> ResolveServiceResponse { 756 756 let invalid = |reason: &'static str| { 757 757 ResolveServiceResponse::BadRequest(xrpc_error("InvalidRequest", reason)) 758 758 }; 759 - let Ok(service_mini_doc) = identity.did_to_mini_service_doc(&did).await else { 759 + let Ok(service_mini_doc) = identity.did_to_mini_service_doc(did).await else { 760 760 return invalid("Failed to get DID doc"); 761 761 }; 762 762 let Some(service_mini_doc) = service_mini_doc else { ··· 795 795 panic!("params have to be an object"); 796 796 }; 797 797 Some(map) 798 - } else { None }; 798 + } else { 799 + None 800 + }; 799 801 800 802 let Some((service_did, id_fragment)) = payload.atproto_proxy.rsplit_once("#") else { 801 - return ProxyHydrateResponse::BadRequest(xrpc_error("BadParameter", "atproto_proxy could not be understood")); 803 + return ProxyHydrateResponse::BadRequest(xrpc_error( 804 + "BadParameter", 805 + "atproto_proxy could not be understood", 806 + )); 802 807 }; 803 808 804 809 let Ok(service_did) = service_did.parse() else { 805 - return ProxyHydrateResponse::BadRequest(xrpc_error("BadParameter", "atproto_proxy service did could not be parsed")); 810 + return ProxyHydrateResponse::BadRequest(xrpc_error( 811 + "BadParameter", 812 + "atproto_proxy service did could not be parsed", 813 + )); 806 814 }; 807 815 808 816 let Ok(xrpc) = payload.xrpc.parse() else { 809 - return ProxyHydrateResponse::BadRequest(xrpc_error("BadParameter", "invalid NSID for xrpc param")); 817 + return ProxyHydrateResponse::BadRequest(xrpc_error( 818 + "BadParameter", 819 + "invalid NSID for xrpc param", 820 + )); 810 821 }; 811 822 812 - match self.proxy.proxy( 813 - &service_did, 814 - &format!("#{id_fragment}"), 815 - &xrpc, 816 - params, 817 - ).await { 823 + match self 824 + .proxy 825 + .proxy(&service_did, &format!("#{id_fragment}"), &xrpc, params) 826 + .await 827 + { 818 828 Ok(skeleton) => { 819 829 let links = match extract_links(payload.hydration_sources, &skeleton) { 820 830 Ok(l) => l, 821 831 Err(e) => { 822 832 log::warn!("problem extracting: {e:?}"); 823 - return ProxyHydrateResponse::BadRequest(xrpc_error("oop", "sorry, error extracting")) 833 + return ProxyHydrateResponse::BadRequest(xrpc_error( 834 + "oop", 835 + "sorry, error extracting", 836 + )); 824 837 } 825 838 }; 826 839 let mut records = HashMap::new(); ··· 842 855 } 843 856 let mut u = url::Url::parse("https://example.com").unwrap(); 844 857 u.query_pairs_mut().append_pair("at_uri", &uri); // BLEH todo 845 - records.insert(uri.clone(), Hydration::Pending(ProxyHydrationPending { 846 - url: format!("/xrpc/blue.microcosm.repo.getRecordByUri?{}", u.query().unwrap()), // TODO better; with cid, etc. 847 - })); 858 + records.insert( 859 + uri.clone(), 860 + Hydration::Pending(ProxyHydrationPending { 861 + url: format!( 862 + "/xrpc/blue.microcosm.repo.getRecordByUri?{}", 863 + u.query().unwrap() 864 + ), // TODO better; with cid, etc. 865 + }), 866 + ); 848 867 let tx = tx.clone(); 849 868 let identity = self.identity.clone(); 850 869 let repo = self.repo.clone(); ··· 860 879 identity.handle_to_did(handle).await.unwrap().unwrap() 861 880 }; 862 881 863 - let res = match repo.get_record( 864 - &did, 865 - &Nsid::new(collection.to_string()).unwrap(), 866 - &RecordKey::new(rkey.to_string()).unwrap(), 867 - &cid.as_ref().map(|s| Cid::from_str(s).unwrap()), 868 - ).await { 869 - Ok(CachedRecord::Deleted) => 882 + let res = match repo 883 + .get_record( 884 + &did, 885 + &Nsid::new(collection.to_string()).unwrap(), 886 + &RecordKey::new(rkey.to_string()).unwrap(), 887 + &cid.as_ref().map(|s| Cid::from_str(s).unwrap()), 888 + ) 889 + .await 890 + { 891 + Ok(CachedRecord::Deleted) => { 870 892 Hydration::Error(ProxyHydrationError { 871 893 reason: "record deleted".to_string(), 872 - }), 873 - Ok(CachedRecord::Found(RawRecord { cid: found_cid, record })) => { 874 - if let Some(c) = cid && found_cid.as_ref().to_string() != c { 894 + }) 895 + } 896 + Ok(CachedRecord::Found(RawRecord { 897 + cid: found_cid, 898 + record, 899 + })) => { 900 + if let Some(c) = cid 901 + && found_cid.as_ref().to_string() != c 902 + { 875 903 log::warn!("ignoring cid mismatch"); 876 904 } 877 905 let value = serde_json::from_str(&record).unwrap(); ··· 895 923 } 896 924 let mut u = url::Url::parse("https://example.com").unwrap(); 897 925 u.query_pairs_mut().append_pair("identifier", &id); 898 - identifiers.insert(id.clone(), Hydration::Pending(ProxyHydrationPending { 899 - url: format!("/xrpc/blue.microcosm.identity.resolveMiniDoc?{}", u.query().unwrap()), // gross 900 - })); 926 + identifiers.insert( 927 + id.clone(), 928 + Hydration::Pending(ProxyHydrationPending { 929 + url: format!( 930 + "/xrpc/blue.microcosm.identity.resolveMiniDoc?{}", 931 + u.query().unwrap() 932 + ), // gross 933 + }), 934 + ); 901 935 let tx = tx.clone(); 902 936 let identity = self.identity.clone(); 903 937 tokio::task::spawn(async move { 904 938 let res = match Self::resolve_mini_doc_impl(&id, identity).await { 905 - ResolveMiniDocResponse::Ok(Json(mini_doc)) => Hydration::Found(ProxyHydrationIdentifierFound { 906 - mini_doc 907 - }), 939 + ResolveMiniDocResponse::Ok(Json(mini_doc)) => { 940 + Hydration::Found(ProxyHydrationIdentifierFound { mini_doc }) 941 + } 908 942 ResolveMiniDocResponse::BadRequest(e) => { 909 943 log::warn!("minidoc fail: {:?}", e.0); 910 944 Hydration::Error(ProxyHydrationError { ··· 923 957 924 958 while let Some(hydration) = rx.recv().await { 925 959 match hydration { 926 - GetThing::Record(uri, h) => { records.insert(uri, h); } 927 - GetThing::Identifier(uri, md) => { identifiers.insert(uri, md); } 960 + GetThing::Record(uri, h) => { 961 + records.insert(uri, h); 962 + } 963 + GetThing::Identifier(uri, md) => { 964 + identifiers.insert(uri, md); 965 + } 928 966 }; 929 967 } 930 968 ··· 939 977 ProxyHydrateResponse::BadRequest(xrpc_error("oop", "sorry")) 940 978 } 941 979 } 942 - 943 980 } 944 981 945 982 async fn get_record_impl(