A better Rust ATProto crate
109
fork

Configure Feed

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

ouroboros more compact owned aturi implementation, plus likely faster less space-efficient clone method to work around limitations of the type. also plan for additional optimization option.

+575 -99
+63 -1
Cargo.lock
··· 31 31 ] 32 32 33 33 [[package]] 34 + name = "aliasable" 35 + version = "0.1.3" 36 + source = "registry+https://github.com/rust-lang/crates.io-index" 37 + checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" 38 + 39 + [[package]] 34 40 name = "android_system_properties" 35 41 version = "0.1.5" 36 42 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 275 281 source = "registry+https://github.com/rust-lang/crates.io-index" 276 282 checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" 277 283 dependencies = [ 278 - "heck", 284 + "heck 0.5.0", 279 285 "proc-macro2", 280 286 "quote", 281 287 "syn 2.0.106", ··· 504 510 505 511 [[package]] 506 512 name = "heck" 513 + version = "0.4.1" 514 + source = "registry+https://github.com/rust-lang/crates.io-index" 515 + checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 516 + 517 + [[package]] 518 + name = "heck" 507 519 version = "0.5.0" 508 520 source = "registry+https://github.com/rust-lang/crates.io-index" 509 521 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" ··· 718 730 "miette", 719 731 "multibase", 720 732 "multihash", 733 + "ouroboros", 721 734 "regex", 722 735 "serde", 723 736 "serde_html_form", ··· 865 878 checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 866 879 867 880 [[package]] 881 + name = "ouroboros" 882 + version = "0.18.5" 883 + source = "registry+https://github.com/rust-lang/crates.io-index" 884 + checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" 885 + dependencies = [ 886 + "aliasable", 887 + "ouroboros_macro", 888 + "static_assertions", 889 + ] 890 + 891 + [[package]] 892 + name = "ouroboros_macro" 893 + version = "0.18.5" 894 + source = "registry+https://github.com/rust-lang/crates.io-index" 895 + checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" 896 + dependencies = [ 897 + "heck 0.4.1", 898 + "proc-macro2", 899 + "proc-macro2-diagnostics", 900 + "quote", 901 + "syn 2.0.106", 902 + ] 903 + 904 + [[package]] 868 905 name = "percent-encoding" 869 906 version = "2.3.2" 870 907 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 916 953 checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 917 954 dependencies = [ 918 955 "unicode-ident", 956 + ] 957 + 958 + [[package]] 959 + name = "proc-macro2-diagnostics" 960 + version = "0.10.1" 961 + source = "registry+https://github.com/rust-lang/crates.io-index" 962 + checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" 963 + dependencies = [ 964 + "proc-macro2", 965 + "quote", 966 + "syn 2.0.106", 967 + "version_check", 968 + "yansi", 919 969 ] 920 970 921 971 [[package]] ··· 1179 1229 "syn 2.0.106", 1180 1230 "thiserror 1.0.69", 1181 1231 ] 1232 + 1233 + [[package]] 1234 + name = "static_assertions" 1235 + version = "1.1.0" 1236 + source = "registry+https://github.com/rust-lang/crates.io-index" 1237 + checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1182 1238 1183 1239 [[package]] 1184 1240 name = "strsim" ··· 1550 1606 version = "0.6.1" 1551 1607 source = "registry+https://github.com/rust-lang/crates.io-index" 1552 1608 checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 1609 + 1610 + [[package]] 1611 + name = "yansi" 1612 + version = "1.0.1" 1613 + source = "registry+https://github.com/rust-lang/crates.io-index" 1614 + checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 1553 1615 1554 1616 [[package]] 1555 1617 name = "yoke"
+1
crates/jacquard-common/Cargo.toml
··· 20 20 miette = "7.6.0" 21 21 multibase = "0.9.1" 22 22 multihash = "0.19.3" 23 + ouroboros = "0.18.5" 23 24 regex = "1.11.3" 24 25 serde = { version = "1.0.227", features = ["derive"] } 25 26 serde_html_form = "0.2.8"
+465 -88
crates/jacquard-common/src/types/aturi.rs
··· 1 - use crate::CowStr; 2 1 use crate::types::ident::AtIdentifier; 3 2 use crate::types::nsid::Nsid; 4 3 use crate::types::recordkey::{RecordKey, Rkey}; 5 4 use crate::types::string::AtStrError; 5 + use crate::{CowStr, IntoStatic}; 6 6 use regex::Regex; 7 7 use serde::Serializer; 8 8 use serde::{Deserialize, Deserializer, Serialize, de::Error}; 9 9 use smol_str::{SmolStr, ToSmolStr}; 10 10 use std::fmt; 11 + use std::hash::{Hash, Hasher}; 11 12 use std::sync::LazyLock; 12 13 use std::{ops::Deref, str::FromStr}; 13 14 ··· 15 16 /// 16 17 /// based on the regex here: https://github.com/bluesky-social/atproto/blob/main/packages/syntax/src/aturi_validation.ts 17 18 /// 18 - /// Doesn't support the query segment, but then neither does the Typescript SDK 19 - /// 20 - /// TODO: support IntoStatic on string types. For composites like this where all borrow from (present) input, 21 - /// perhaps use some careful unsafe to launder the lifetimes. 22 - #[derive(Clone, PartialEq, Eq, Hash, Debug)] 19 + /// Doesn't support the query segment, but then neither does the Typescript SDK. 20 + #[derive(PartialEq, Eq, Debug)] 23 21 pub struct AtUri<'u> { 22 + inner: Inner<'u>, 23 + } 24 + 25 + #[ouroboros::self_referencing] 26 + #[derive(PartialEq, Eq, Debug)] 27 + struct Inner<'u> { 24 28 uri: CowStr<'u>, 25 - pub authority: AtIdentifier<'u>, 26 - pub path: Option<UriPath<'u>>, 27 - pub fragment: Option<CowStr<'u>>, 29 + #[borrows(uri)] 30 + #[covariant] 31 + pub authority: AtIdentifier<'this>, 32 + #[borrows(uri)] 33 + #[covariant] 34 + pub path: Option<UriPath<'this>>, 35 + #[borrows(uri)] 36 + #[covariant] 37 + pub fragment: Option<CowStr<'this>>, 38 + } 39 + 40 + impl Clone for AtUri<'_> { 41 + fn clone(&self) -> Self { 42 + let uri = self.inner.borrow_uri(); 43 + 44 + Self { 45 + inner: Inner::new( 46 + CowStr::Owned(uri.as_ref().to_smolstr()), 47 + |uri| { 48 + let parts = ATURI_REGEX.captures(uri).unwrap(); 49 + unsafe { AtIdentifier::unchecked(parts.name("authority").unwrap().as_str()) } 50 + }, 51 + |uri| { 52 + let parts = ATURI_REGEX.captures(uri).unwrap(); 53 + if let Some(collection) = parts.name("collection") { 54 + let collection = unsafe { Nsid::unchecked(collection.as_str()) }; 55 + let rkey = if let Some(rkey) = parts.name("rkey") { 56 + let rkey = unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; 57 + Some(rkey) 58 + } else { 59 + None 60 + }; 61 + Some(UriPath { collection, rkey }) 62 + } else { 63 + None 64 + } 65 + }, 66 + |uri| { 67 + let parts = ATURI_REGEX.captures(uri).unwrap(); 68 + parts.name("fragment").map(|fragment| { 69 + let fragment = CowStr::Borrowed(fragment.as_str()); 70 + fragment 71 + }) 72 + }, 73 + ), 74 + } 75 + } 76 + } 77 + 78 + impl Hash for AtUri<'_> { 79 + fn hash<H: Hasher>(&self, state: &mut H) { 80 + self.inner.borrow_uri().hash(state); 81 + } 28 82 } 29 83 30 84 /// at:// URI path component (current subset) ··· 32 86 pub struct UriPath<'u> { 33 87 pub collection: Nsid<'u>, 34 88 pub rkey: Option<RecordKey<Rkey<'u>>>, 89 + } 90 + 91 + impl IntoStatic for UriPath<'_> { 92 + type Output = UriPath<'static>; 93 + 94 + fn into_static(self) -> Self::Output { 95 + UriPath { 96 + collection: self.collection.into_static(), 97 + rkey: self.rkey.map(|rkey| rkey.into_static()), 98 + } 99 + } 35 100 } 36 101 37 102 pub type UriPathBuf = UriPath<'static>; ··· 68 133 fragment 69 134 }); 70 135 Ok(AtUri { 71 - uri: CowStr::Borrowed(uri), 72 - authority, 73 - path, 74 - fragment, 136 + inner: InnerBuilder { 137 + uri: CowStr::Borrowed(uri), 138 + authority_builder: |_| authority, 139 + path_builder: |_| path, 140 + fragment_builder: |_| fragment, 141 + } 142 + .build(), 75 143 }) 76 144 } else { 77 145 Err(AtStrError::missing("at-uri-scheme", uri, "authority")) ··· 106 174 fragment 107 175 }); 108 176 AtUri { 109 - uri: CowStr::Borrowed(uri), 110 - authority, 111 - path, 112 - fragment, 177 + inner: InnerBuilder { 178 + uri: CowStr::Borrowed(uri), 179 + authority_builder: |_| authority, 180 + path_builder: |_| path, 181 + fragment_builder: |_| fragment, 182 + } 183 + .build(), 113 184 } 114 185 } else { 115 186 panic!("at:// URI missing authority") ··· 119 190 } 120 191 } 121 192 122 - pub fn new_owned(uri: impl AsRef<str>) -> Result<Self, AtStrError> { 123 - let uri = uri.as_ref(); 193 + /// Unchecked borrowing constructor. This one does do some validation but if that fails will just 194 + /// dump everything in the authority field. 195 + /// 196 + /// TODO: do some fallback splitting, but really, if you use this on something invalid, you deserve it. 197 + pub unsafe fn unchecked(uri: &'u str) -> Self { 124 198 if let Some(parts) = ATURI_REGEX.captures(uri) { 125 199 if let Some(authority) = parts.name("authority") { 126 - let authority = AtIdentifier::new_owned(authority.as_str()) 127 - .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; 200 + let authority = unsafe { AtIdentifier::unchecked(authority.as_str()) }; 128 201 let path = if let Some(collection) = parts.name("collection") { 129 - let collection = Nsid::new_owned(collection.as_str()) 130 - .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; 202 + let collection = unsafe { Nsid::unchecked(collection.as_str()) }; 131 203 let rkey = if let Some(rkey) = parts.name("rkey") { 132 - let rkey = 133 - RecordKey::from(Rkey::new_owned(rkey.as_str()).map_err(|e| { 134 - AtStrError::wrap("at-uri-scheme", uri.to_string(), e) 135 - })?); 204 + let rkey = RecordKey::from(unsafe { Rkey::unchecked(rkey.as_str()) }); 136 205 Some(rkey) 137 206 } else { 138 207 None ··· 142 211 None 143 212 }; 144 213 let fragment = parts.name("fragment").map(|fragment| { 145 - let fragment = CowStr::Owned(fragment.as_str().to_smolstr()); 214 + let fragment = CowStr::Borrowed(fragment.as_str()); 146 215 fragment 147 216 }); 217 + AtUri { 218 + inner: InnerBuilder { 219 + uri: CowStr::Borrowed(uri), 220 + authority_builder: |_| authority, 221 + path_builder: |_| path, 222 + fragment_builder: |_| fragment, 223 + } 224 + .build(), 225 + } 226 + } else { 227 + // let mut uriParts = uri.split('#'); 228 + // let mut parts = uriParts.next().unwrap_or(uri).split('/'); 229 + // let auth = parts.next().unwrap_or(uri); 230 + Self { 231 + inner: InnerBuilder { 232 + uri: CowStr::Borrowed(uri), 233 + authority_builder: |_| unsafe { AtIdentifier::unchecked(uri) }, 234 + path_builder: |_| None, 235 + fragment_builder: |_| None, 236 + } 237 + .build(), 238 + } 239 + } 240 + } else { 241 + Self { 242 + inner: InnerBuilder { 243 + uri: CowStr::Borrowed(uri), 244 + authority_builder: |_| unsafe { AtIdentifier::unchecked(uri) }, 245 + path_builder: |_| None, 246 + fragment_builder: |_| None, 247 + } 248 + .build(), 249 + } 250 + } 251 + } 252 + 253 + /// Clone method that should be O(1) in terms of time 254 + /// 255 + /// Calling on a borrowed variant will turn it into an owned variant, taking a little 256 + /// more time and allocating memory for each part. Calling it on an owned variant will 257 + /// increment all the internal reference counters (or, if constructed from a `&'static str`, 258 + /// essentially do nothing). 259 + pub fn fast_clone(&self) -> AtUri<'static> { 260 + self.inner.with(move |u| { 261 + let uri = u.uri.clone().into_static(); 262 + let authority = u.authority.clone().into_static(); 263 + let path = u.path.clone().into_static(); 264 + let fragment = u.fragment.clone().into_static(); 265 + AtUri { 266 + inner: InnerBuilder { 267 + uri, 268 + authority_builder: |_| authority, 269 + path_builder: |_| path, 270 + fragment_builder: |_| fragment, 271 + } 272 + .build(), 273 + } 274 + }) 275 + } 276 + 277 + pub fn as_str(&self) -> &str { 278 + { 279 + let this = &self.inner.borrow_uri(); 280 + this 281 + } 282 + } 283 + 284 + pub fn authority(&self) -> &AtIdentifier<'_> { 285 + self.inner.borrow_authority() 286 + } 287 + 288 + pub fn path(&self) -> &Option<UriPath<'_>> { 289 + self.inner.borrow_path() 290 + } 291 + 292 + pub fn fragment(&self) -> &Option<CowStr<'_>> { 293 + self.inner.borrow_fragment() 294 + } 295 + 296 + pub fn collection(&self) -> Option<&Nsid<'_>> { 297 + self.inner.borrow_path().as_ref().map(|p| &p.collection) 298 + } 299 + 300 + pub fn rkey(&self) -> Option<&RecordKey<Rkey<'_>>> { 301 + self.inner 302 + .borrow_path() 303 + .as_ref() 304 + .and_then(|p| p.rkey.as_ref()) 305 + } 306 + } 307 + 308 + impl AtUri<'static> { 309 + /// Owned constructor 310 + /// 311 + /// Uses ouroboros self-referential tricks internally to make sure everything 312 + /// borrows efficiently from the uri `CowStr<'static>`. 313 + /// 314 + /// Performs validation up-front, but is slower than the borrowing constructor 315 + /// due to currently having to re-run the main regex, in addition to allocating. 316 + /// 317 + /// `.into_static()` and Clone implementations have similar limitations. 318 + /// 319 + /// O(1) clone mathod is AtUri::fast_clone(). 320 + /// 321 + /// Future optimization involves working out the indices borrowed and either using those 322 + /// to avoid re-computing in some places, or, for a likely fully optimal version, only storing 323 + /// the indices and constructing the borrowed components unsafely when asked. 324 + pub fn new_owned(uri: impl AsRef<str>) -> Result<Self, AtStrError> { 325 + if let Some(parts) = ATURI_REGEX.captures(uri.as_ref()) { 326 + if let Some(authority) = parts.name("authority") { 327 + let _authority = AtIdentifier::new(authority.as_str()) 328 + .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.as_ref().to_string(), e))?; 329 + let path = if let Some(collection) = parts.name("collection") { 330 + let collection = Nsid::new(collection.as_str()).map_err(|e| { 331 + AtStrError::wrap("at-uri-scheme", uri.as_ref().to_string(), e) 332 + })?; 333 + let rkey = if let Some(rkey) = parts.name("rkey") { 334 + let rkey = RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { 335 + AtStrError::wrap("at-uri-scheme", uri.as_ref().to_string(), e) 336 + })?); 337 + Some(rkey) 338 + } else { 339 + None 340 + }; 341 + Some(UriPath { collection, rkey }) 342 + } else { 343 + None 344 + }; 345 + 148 346 Ok(AtUri { 149 - uri: CowStr::Owned(uri.to_smolstr()), 150 - authority, 151 - path, 152 - fragment, 347 + inner: Inner::new( 348 + CowStr::Owned(uri.as_ref().to_smolstr()), 349 + |uri| { 350 + let parts = ATURI_REGEX.captures(uri).unwrap(); 351 + unsafe { 352 + AtIdentifier::unchecked(parts.name("authority").unwrap().as_str()) 353 + } 354 + }, 355 + |uri| { 356 + if path.is_some() { 357 + let parts = ATURI_REGEX.captures(uri).unwrap(); 358 + if let Some(collection) = parts.name("collection") { 359 + let collection = 360 + unsafe { Nsid::unchecked(collection.as_str()) }; 361 + let rkey = if let Some(rkey) = parts.name("rkey") { 362 + let rkey = unsafe { 363 + RecordKey::from(Rkey::unchecked(rkey.as_str())) 364 + }; 365 + Some(rkey) 366 + } else { 367 + None 368 + }; 369 + Some(UriPath { collection, rkey }) 370 + } else { 371 + None 372 + } 373 + } else { 374 + None 375 + } 376 + }, 377 + |uri| { 378 + let parts = ATURI_REGEX.captures(uri).unwrap(); 379 + parts.name("fragment").map(|fragment| { 380 + let fragment = CowStr::Borrowed(fragment.as_str()); 381 + fragment 382 + }) 383 + }, 384 + ), 153 385 }) 154 386 } else { 155 - Err(AtStrError::missing("at-uri-scheme", uri, "authority")) 387 + Err(AtStrError::missing( 388 + "at-uri-scheme", 389 + &uri.as_ref(), 390 + "authority", 391 + )) 156 392 } 157 393 } else { 158 394 Err(AtStrError::regex( 159 395 "at-uri-scheme", 160 - uri, 396 + &uri.as_ref(), 161 397 SmolStr::new_static("doesn't match schema"), 162 398 )) 163 399 } 164 400 } 165 401 166 - pub fn new_static(uri: &'static str) -> Result<AtUri<'static>, AtStrError> { 402 + pub fn new_static(uri: &'static str) -> Result<Self, AtStrError> { 167 403 let uri = uri.as_ref(); 168 404 if let Some(parts) = ATURI_REGEX.captures(uri) { 169 405 if let Some(authority) = parts.name("authority") { ··· 190 426 fragment 191 427 }); 192 428 Ok(AtUri { 193 - uri: CowStr::new_static(uri), 194 - authority, 195 - path, 196 - fragment, 429 + inner: InnerBuilder { 430 + uri: CowStr::new_static(uri), 431 + authority_builder: |_| authority, 432 + path_builder: |_| path, 433 + fragment_builder: |_| fragment, 434 + } 435 + .build(), 197 436 }) 198 437 } else { 199 438 Err(AtStrError::missing("at-uri-scheme", uri, "authority")) ··· 206 445 )) 207 446 } 208 447 } 448 + } 209 449 210 - pub unsafe fn unchecked(uri: &'u str) -> Self { 211 - if let Some(parts) = ATURI_REGEX.captures(uri) { 450 + impl FromStr for AtUri<'_> { 451 + type Err = AtStrError; 452 + 453 + /// Has to take ownership due to the lifetime constraints of the FromStr trait. 454 + /// Prefer `AtUri::new()` or `AtUri::raw()` if you want to borrow. 455 + fn from_str(uri: &str) -> Result<Self, Self::Err> { 456 + if let Some(parts) = ATURI_REGEX.captures(uri.as_ref()) { 212 457 if let Some(authority) = parts.name("authority") { 213 - let authority = unsafe { AtIdentifier::unchecked(authority.as_str()) }; 458 + let _authority = AtIdentifier::new(authority.as_str()) 459 + .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; 214 460 let path = if let Some(collection) = parts.name("collection") { 215 - let collection = unsafe { Nsid::unchecked(collection.as_str()) }; 461 + let collection = Nsid::new(collection.as_str()) 462 + .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; 216 463 let rkey = if let Some(rkey) = parts.name("rkey") { 217 - let rkey = RecordKey::from(unsafe { Rkey::unchecked(rkey.as_str()) }); 464 + let rkey = 465 + RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { 466 + AtStrError::wrap("at-uri-scheme", uri.to_string(), e) 467 + })?); 218 468 Some(rkey) 219 469 } else { 220 470 None ··· 223 473 } else { 224 474 None 225 475 }; 226 - let fragment = parts.name("fragment").map(|fragment| { 227 - let fragment = CowStr::Borrowed(fragment.as_str()); 228 - fragment 229 - }); 230 - AtUri { 231 - uri: CowStr::Borrowed(uri), 232 - authority, 233 - path, 234 - fragment, 235 - } 476 + 477 + Ok(AtUri { 478 + inner: Inner::new( 479 + CowStr::Owned(uri.to_smolstr()), 480 + |uri| { 481 + let parts = ATURI_REGEX.captures(uri).unwrap(); 482 + unsafe { 483 + AtIdentifier::unchecked(parts.name("authority").unwrap().as_str()) 484 + } 485 + }, 486 + |uri| { 487 + if path.is_some() { 488 + let parts = ATURI_REGEX.captures(uri).unwrap(); 489 + if let Some(collection) = parts.name("collection") { 490 + let collection = 491 + unsafe { Nsid::unchecked(collection.as_str()) }; 492 + let rkey = if let Some(rkey) = parts.name("rkey") { 493 + let rkey = unsafe { 494 + RecordKey::from(Rkey::unchecked(rkey.as_str())) 495 + }; 496 + Some(rkey) 497 + } else { 498 + None 499 + }; 500 + Some(UriPath { collection, rkey }) 501 + } else { 502 + None 503 + } 504 + } else { 505 + None 506 + } 507 + }, 508 + |uri| { 509 + let parts = ATURI_REGEX.captures(uri).unwrap(); 510 + parts.name("fragment").map(|fragment| { 511 + let fragment = CowStr::Borrowed(fragment.as_str()); 512 + fragment 513 + }) 514 + }, 515 + ), 516 + }) 236 517 } else { 237 - Self { 238 - uri: CowStr::Borrowed(uri), 239 - authority: unsafe { AtIdentifier::unchecked(uri) }, 240 - path: None, 241 - fragment: None, 242 - } 518 + Err(AtStrError::missing( 519 + "at-uri-scheme", 520 + &uri.as_ref(), 521 + "authority", 522 + )) 243 523 } 244 524 } else { 245 - Self { 246 - uri: CowStr::Borrowed(uri), 247 - authority: unsafe { AtIdentifier::unchecked(uri) }, 248 - path: None, 249 - fragment: None, 250 - } 251 - } 252 - } 253 - 254 - pub fn as_str(&self) -> &str { 255 - { 256 - let this = &self.uri; 257 - this 525 + Err(AtStrError::regex( 526 + "at-uri-scheme", 527 + &uri.as_ref(), 528 + SmolStr::new_static("doesn't match schema"), 529 + )) 258 530 } 259 531 } 260 532 } 261 533 262 - impl FromStr for AtUri<'_> { 263 - type Err = AtStrError; 534 + impl IntoStatic for AtUri<'_> { 535 + type Output = AtUri<'static>; 264 536 265 - /// Has to take ownership due to the lifetime constraints of the FromStr trait. 266 - /// Prefer `AtUri::new()` or `AtUri::raw()` if you want to borrow. 267 - fn from_str(s: &str) -> Result<Self, Self::Err> { 268 - Self::new_owned(s) 537 + fn into_static(self) -> AtUri<'static> { 538 + AtUri { 539 + inner: Inner::new( 540 + self.inner.borrow_uri().clone().into_static(), 541 + |uri| { 542 + let parts = ATURI_REGEX.captures(uri).unwrap(); 543 + unsafe { AtIdentifier::unchecked(parts.name("authority").unwrap().as_str()) } 544 + }, 545 + |uri| { 546 + if self.inner.borrow_path().is_some() { 547 + let parts = ATURI_REGEX.captures(uri).unwrap(); 548 + if let Some(collection) = parts.name("collection") { 549 + let collection = unsafe { Nsid::unchecked(collection.as_str()) }; 550 + let rkey = if let Some(rkey) = parts.name("rkey") { 551 + let rkey = 552 + unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; 553 + Some(rkey) 554 + } else { 555 + None 556 + }; 557 + Some(UriPath { collection, rkey }) 558 + } else { 559 + None 560 + } 561 + } else { 562 + None 563 + } 564 + }, 565 + |uri| { 566 + if self.inner.borrow_fragment().is_some() { 567 + let parts = ATURI_REGEX.captures(uri).unwrap(); 568 + parts.name("fragment").map(|fragment| { 569 + let fragment = CowStr::Borrowed(fragment.as_str()); 570 + fragment 571 + }) 572 + } else { 573 + None 574 + } 575 + }, 576 + ), 577 + } 269 578 } 270 579 } 271 580 ··· 284 593 where 285 594 S: Serializer, 286 595 { 287 - serializer.serialize_str(&self.uri) 596 + serializer.serialize_str(&self.inner.borrow_uri()) 288 597 } 289 598 } 290 599 291 600 impl fmt::Display for AtUri<'_> { 292 601 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 293 - f.write_str(&self.uri) 602 + f.write_str(&self.inner.borrow_uri()) 294 603 } 295 604 } 296 605 297 606 impl<'d> From<AtUri<'d>> for String { 298 607 fn from(value: AtUri<'d>) -> Self { 299 - value.uri.to_string() 608 + value.inner.borrow_uri().to_string() 300 609 } 301 610 } 302 611 303 612 impl<'d> From<AtUri<'d>> for CowStr<'d> { 304 613 fn from(value: AtUri<'d>) -> Self { 305 - value.uri 614 + value.inner.borrow_uri().clone() 306 615 } 307 616 } 308 617 ··· 316 625 317 626 impl<'d> TryFrom<CowStr<'d>> for AtUri<'d> { 318 627 type Error = AtStrError; 319 - /// TODO: rewrite to avoid taking ownership/cloning 320 - fn try_from(value: CowStr<'d>) -> Result<Self, Self::Error> { 321 - Self::new_owned(value) 628 + fn try_from(uri: CowStr<'d>) -> Result<Self, Self::Error> { 629 + if let Some(parts) = ATURI_REGEX.captures(uri.as_ref()) { 630 + if let Some(authority) = parts.name("authority") { 631 + let _authority = AtIdentifier::new(authority.as_str()) 632 + .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; 633 + let _path = if let Some(collection) = parts.name("collection") { 634 + let collection = Nsid::new(collection.as_str()) 635 + .map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?; 636 + let rkey = if let Some(rkey) = parts.name("rkey") { 637 + let rkey = 638 + RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| { 639 + AtStrError::wrap("at-uri-scheme", uri.to_string(), e) 640 + })?); 641 + Some(rkey) 642 + } else { 643 + None 644 + }; 645 + Some(UriPath { collection, rkey }) 646 + } else { 647 + None 648 + }; 649 + drop(parts); 650 + 651 + Ok(AtUri { 652 + inner: Inner::new( 653 + uri, 654 + |uri| { 655 + let parts = ATURI_REGEX.captures(uri).unwrap(); 656 + unsafe { 657 + AtIdentifier::unchecked(parts.name("authority").unwrap().as_str()) 658 + } 659 + }, 660 + |uri| { 661 + let parts = ATURI_REGEX.captures(uri).unwrap(); 662 + if let Some(collection) = parts.name("collection") { 663 + let collection = unsafe { Nsid::unchecked(collection.as_str()) }; 664 + let rkey = if let Some(rkey) = parts.name("rkey") { 665 + let rkey = 666 + unsafe { RecordKey::from(Rkey::unchecked(rkey.as_str())) }; 667 + Some(rkey) 668 + } else { 669 + None 670 + }; 671 + Some(UriPath { collection, rkey }) 672 + } else { 673 + None 674 + } 675 + }, 676 + |uri| { 677 + let parts = ATURI_REGEX.captures(uri).unwrap(); 678 + parts.name("fragment").map(|fragment| { 679 + let fragment = CowStr::Borrowed(fragment.as_str()); 680 + fragment 681 + }) 682 + }, 683 + ), 684 + }) 685 + } else { 686 + Err(AtStrError::missing( 687 + "at-uri-scheme", 688 + &uri.as_ref(), 689 + "authority", 690 + )) 691 + } 692 + } else { 693 + Err(AtStrError::regex( 694 + "at-uri-scheme", 695 + &uri.as_ref(), 696 + SmolStr::new_static("doesn't match schema"), 697 + )) 698 + } 322 699 } 323 700 } 324 701 325 702 impl AsRef<str> for AtUri<'_> { 326 703 fn as_ref(&self) -> &str { 327 - &self.uri.as_ref() 704 + &self.inner.borrow_uri().as_ref() 328 705 } 329 706 } 330 707 ··· 332 709 type Target = str; 333 710 334 711 fn deref(&self) -> &Self::Target { 335 - self.uri.as_ref() 712 + self.inner.borrow_uri().as_ref() 336 713 } 337 714 }
+43 -4
crates/jacquard-common/src/types/blob.rs
··· 21 21 pub size: usize, 22 22 } 23 23 24 - impl<'r> BlobRef<'r> { 25 - pub fn blob(&self) -> &Blob<'r> { 26 - match self { 27 - BlobRef::Blob(blob) => blob, 24 + impl IntoStatic for Blob<'_> { 25 + type Output = Blob<'static>; 26 + 27 + fn into_static(self) -> Self::Output { 28 + Blob { 29 + r#ref: self.r#ref.into_static(), 30 + mime_type: self.mime_type.into_static(), 31 + size: self.size, 28 32 } 29 33 } 30 34 } 31 35 32 36 /// Current, typed blob reference. 37 + /// Quite dislike this nesting, but it serves the same purpose as it did in Atrium 38 + /// Couple of helper methods and conversions to make it less annoying. 39 + /// TODO: revisit nesting and maybe hand-roll a serde impl that supports this sans nesting 33 40 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 34 41 #[serde(tag = "$type", rename_all = "lowercase")] 35 42 pub enum BlobRef<'r> { 36 43 #[serde(borrow)] 37 44 Blob(Blob<'r>), 45 + } 46 + 47 + impl<'r> BlobRef<'r> { 48 + pub fn blob(&self) -> &Blob<'r> { 49 + match self { 50 + BlobRef::Blob(blob) => blob, 51 + } 52 + } 53 + } 54 + 55 + impl<'b> From<BlobRef<'b>> for Blob<'b> { 56 + fn from(blob_ref: BlobRef<'b>) -> Self { 57 + match blob_ref { 58 + BlobRef::Blob(blob) => blob, 59 + } 60 + } 61 + } 62 + 63 + impl<'b> From<Blob<'b>> for BlobRef<'b> { 64 + fn from(blob: Blob<'b>) -> Self { 65 + BlobRef::Blob(blob) 66 + } 67 + } 68 + 69 + impl IntoStatic for BlobRef<'_> { 70 + type Output = BlobRef<'static>; 71 + 72 + fn into_static(self) -> Self::Output { 73 + match self { 74 + BlobRef::Blob(blob) => BlobRef::Blob(blob.into_static()), 75 + } 76 + } 38 77 } 39 78 40 79 /// Wrapper for file type
+3 -6
crates/jacquard-common/src/types/string.rs
··· 1 - use bytes::Bytes; 2 1 use miette::SourceSpan; 3 2 use serde::{Deserialize, Deserializer, Serialize, Serializer}; 4 3 use smol_str::{SmolStr, ToSmolStr}; 5 - use std::{collections::BTreeMap, str::FromStr, sync::Arc}; 4 + use std::{str::FromStr, sync::Arc}; 6 5 7 6 use crate::IntoStatic; 8 7 pub use crate::{ ··· 187 186 AtprotoStr::Did(did) => AtprotoStr::Did(did.into_static()), 188 187 AtprotoStr::Handle(handle) => AtprotoStr::Handle(handle.into_static()), 189 188 AtprotoStr::AtIdentifier(ident) => AtprotoStr::AtIdentifier(ident.into_static()), 190 - AtprotoStr::AtUri(at_uri) => { 191 - AtprotoStr::AtUri(AtUri::new_owned(at_uri.as_str()).unwrap()) 192 - } 189 + AtprotoStr::AtUri(at_uri) => AtprotoStr::AtUri(at_uri.into_static()), 193 190 AtprotoStr::Uri(uri) => AtprotoStr::Uri(uri.into_static()), 194 191 AtprotoStr::Cid(cid) => AtprotoStr::Cid(cid.into_static()), 195 192 AtprotoStr::RecordKey(record_key) => AtprotoStr::RecordKey(record_key.into_static()), ··· 214 211 pub struct AtStrError { 215 212 pub spec: SmolStr, 216 213 #[source_code] 217 - source: String, 214 + pub source: String, 218 215 #[source] 219 216 #[diagnostic_source] 220 217 pub kind: StrParseKind,