A better Rust ATProto crate

working through the core types

Orual 8cebd32b 40c44afc

+51
Cargo.lock
··· 97 97 ] 98 98 99 99 [[package]] 100 + name = "allocator-api2" 101 + version = "0.2.21" 102 + source = "registry+https://github.com/rust-lang/crates.io-index" 103 + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 104 + 105 + [[package]] 100 106 name = "android_system_properties" 101 107 version = "0.1.5" 102 108 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1510 1516 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 1511 1517 1512 1518 [[package]] 1519 + name = "foldhash" 1520 + version = "0.1.5" 1521 + source = "registry+https://github.com/rust-lang/crates.io-index" 1522 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 1523 + 1524 + [[package]] 1513 1525 name = "form_urlencoded" 1514 1526 version = "1.2.2" 1515 1527 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1815 1827 version = "0.14.5" 1816 1828 source = "registry+https://github.com/rust-lang/crates.io-index" 1817 1829 checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1830 + 1831 + [[package]] 1832 + name = "hashbrown" 1833 + version = "0.15.5" 1834 + source = "registry+https://github.com/rust-lang/crates.io-index" 1835 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 1836 + dependencies = [ 1837 + "allocator-api2", 1838 + "equivalent", 1839 + "foldhash", 1840 + ] 1818 1841 1819 1842 [[package]] 1820 1843 name = "hashbrown" ··· 2449 2472 "futures-lite", 2450 2473 "getrandom 0.2.16", 2451 2474 "getrandom 0.3.4", 2475 + "hashbrown 0.15.5", 2452 2476 "http", 2453 2477 "ipld-core", 2454 2478 "k256", 2455 2479 "langtag", 2480 + "maitake-sync", 2456 2481 "miette", 2457 2482 "multibase", 2458 2483 "multihash", ··· 2471 2496 "serde_json", 2472 2497 "signature", 2473 2498 "smol_str", 2499 + "spin 0.10.0", 2474 2500 "thiserror 2.0.17", 2475 2501 "tokio", 2476 2502 "tokio-tungstenite-wasm", ··· 2893 2919 checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 2894 2920 2895 2921 [[package]] 2922 + name = "maitake-sync" 2923 + version = "0.1.2" 2924 + source = "registry+https://github.com/rust-lang/crates.io-index" 2925 + checksum = "6816ab14147f80234c675b80ed6dc4f440d8a1cefc158e766067aedb84c0bcd5" 2926 + dependencies = [ 2927 + "cordyceps", 2928 + "loom", 2929 + "mycelium-bitfield", 2930 + "pin-project", 2931 + "portable-atomic", 2932 + ] 2933 + 2934 + [[package]] 2896 2935 name = "markup5ever" 2897 2936 version = "0.12.1" 2898 2937 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3121 3160 "tempfile", 3122 3161 "twoway", 3123 3162 ] 3163 + 3164 + [[package]] 3165 + name = "mycelium-bitfield" 3166 + version = "0.1.5" 3167 + source = "registry+https://github.com/rust-lang/crates.io-index" 3168 + checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" 3124 3169 3125 3170 [[package]] 3126 3171 name = "n0-future" ··· 3590 3635 "flate2", 3591 3636 "miniz_oxide 0.8.9", 3592 3637 ] 3638 + 3639 + [[package]] 3640 + name = "portable-atomic" 3641 + version = "1.13.0" 3642 + source = "registry+https://github.com/rust-lang/crates.io-index" 3643 + checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" 3593 3644 3594 3645 [[package]] 3595 3646 name = "postcard"
+15 -7
crates/jacquard-common/Cargo.toml
··· 12 12 license.workspace = true 13 13 14 14 [features] 15 - default = ["service-auth", "reqwest-client", "crypto"] 15 + default = ["std", "service-auth", "reqwest-client", "crypto"] 16 + std = [ 17 + "dep:tokio", 18 + "miette/fancy", 19 + "serde_json/std", 20 + "serde/std", 21 + ] 16 22 crypto = [] 17 23 crypto-ed25519 = ["crypto", "dep:ed25519-dalek"] 18 24 crypto-k256 = ["crypto", "dep:k256", "k256/ecdsa"] 19 25 crypto-p256 = ["crypto", "dep:p256", "p256/ecdsa"] 20 26 service-auth = ["crypto-k256", "crypto-p256", "dep:signature"] 21 - reqwest-client = ["dep:reqwest"] 27 + reqwest-client = ["std", "dep:reqwest"] 22 28 tracing = ["dep:tracing"] 23 29 streaming = ["n0-future", "futures", "reqwest/stream"] 24 30 websocket = ["streaming", "tokio-tungstenite-wasm", "dep:ciborium"] ··· 33 39 cid.workspace = true 34 40 ipld-core.workspace = true 35 41 langtag = { version = "0.4.0", features = ["serde"] } 36 - miette.workspace = true 42 + miette = { workspace = true } # also need to gate this to std only 37 43 multibase = "0.9.1" 38 44 multihash = "0.19.3" 39 45 ouroboros = "0.18.5" 40 - rand = "0.9.2" 46 + rand = "0.9.2" # also need to check this for no-std 41 47 serde.workspace = true 42 - serde_html_form.workspace = true 48 + serde_html_form.workspace = true # need to check these at workspace level 43 49 serde_json.workspace = true 44 50 smol_str.workspace = true 45 51 thiserror.workspace = true 46 52 url.workspace = true 47 53 http.workspace = true 48 54 serde_bytes = "0.11" 49 - 55 + spin = { version = "0.10", default-features = false, features = ["lazy"] } 56 + maitake-sync = { version = "0.1", default-features = false } 57 + hashbrown = "0.15" 50 58 51 59 reqwest = { workspace = true, optional = true, features = ["json", "gzip", "charset"] } 52 60 serde_ipld_dagcbor.workspace = true 53 61 signature = { version = "2", optional = true } 54 62 tracing = { workspace = true, optional = true } 55 - tokio = { workspace = true, default-features = false, features = ["sync"] } 63 + tokio = { workspace = true, default-features = false, features = ["sync"], optional = true } 56 64 57 65 # Streaming support (optional) 58 66 n0-future = { workspace = true, optional = true }
+14 -13
crates/jacquard-common/src/cowstr.rs
··· 1 + use alloc::borrow::Cow; 2 + use alloc::boxed::Box; 3 + use alloc::string::String; 4 + use core::fmt; 5 + use core::hash::{Hash, Hasher}; 6 + use core::ops::Deref; 7 + 1 8 use serde::{Deserialize, Serialize}; 2 9 use smol_str::SmolStr; 3 - use std::{ 4 - borrow::Cow, 5 - fmt, 6 - hash::{Hash, Hasher}, 7 - ops::Deref, 8 - }; 9 10 10 11 use crate::IntoStatic; 11 12 ··· 40 41 impl<'s> CowStr<'s> { 41 42 #[inline] 42 43 /// Borrow and decode a byte slice as utf8 into a CowStr 43 - pub fn from_utf8(s: &'s [u8]) -> Result<Self, std::str::Utf8Error> { 44 - Ok(Self::Borrowed(std::str::from_utf8(s)?)) 44 + pub fn from_utf8(s: &'s [u8]) -> Result<Self, core::str::Utf8Error> { 45 + Ok(Self::Borrowed(core::str::from_utf8(s)?)) 45 46 } 46 47 47 48 #[inline] 48 49 /// Take bytes and decode them as utf8 into an owned CowStr. Might allocate. 49 - pub fn from_utf8_owned(s: impl AsRef<[u8]>) -> Result<Self, std::str::Utf8Error> { 50 - Ok(Self::Owned(SmolStr::new(std::str::from_utf8(&s.as_ref())?))) 50 + pub fn from_utf8_owned(s: impl AsRef<[u8]>) -> Result<Self, core::str::Utf8Error> { 51 + Ok(Self::Owned(SmolStr::new(core::str::from_utf8(s.as_ref())?))) 51 52 } 52 53 53 54 #[inline] ··· 62 63 /// This function is unsafe because it does not check that the bytes are valid UTF-8. 63 64 #[inline] 64 65 pub unsafe fn from_utf8_unchecked(s: &'s [u8]) -> Self { 65 - unsafe { Self::Owned(SmolStr::new(std::str::from_utf8_unchecked(s))) } 66 + unsafe { Self::Owned(SmolStr::new(core::str::from_utf8_unchecked(s))) } 66 67 } 67 68 68 69 /// Returns a reference to the underlying string slice. ··· 218 219 } 219 220 220 221 impl PartialOrd for CowStr<'_> { 221 - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { 222 + fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { 222 223 Some(match (self, other) { 223 224 (CowStr::Borrowed(s1), CowStr::Borrowed(s2)) => s1.cmp(s2), 224 225 (CowStr::Borrowed(s1), CowStr::Owned(s2)) => s1.cmp(&s2.as_ref()), ··· 229 230 } 230 231 231 232 impl Ord for CowStr<'_> { 232 - fn cmp(&self, other: &Self) -> std::cmp::Ordering { 233 + fn cmp(&self, other: &Self) -> core::cmp::Ordering { 233 234 match (self, other) { 234 235 (CowStr::Borrowed(s1), CowStr::Borrowed(s2)) => s1.cmp(s2), 235 236 (CowStr::Borrowed(s1), CowStr::Owned(s2)) => s1.cmp(&s2.as_ref()),
+3 -1
crates/jacquard-common/src/error.rs
··· 1 1 //! Error types for XRPC client operations 2 2 3 3 use crate::xrpc::EncodeError; 4 + use alloc::boxed::Box; 5 + use alloc::string::ToString; 4 6 use bytes::Bytes; 5 7 use smol_str::SmolStr; 6 8 7 9 /// Boxed error type for wrapping arbitrary errors 8 - pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>; 10 + pub type BoxError = Box<dyn core::error::Error + Send + Sync + 'static>; 9 11 10 12 /// Client error type for all XRPC client operations 11 13 #[derive(Debug, thiserror::Error, miette::Diagnostic)]
+7 -4
crates/jacquard-common/src/http_client.rs
··· 1 1 //! Minimal HTTP client abstraction shared across crates. 2 2 3 - use std::fmt::Display; 4 - use std::future::Future; 5 - use std::sync::Arc; 3 + use alloc::string::ToString; 4 + use alloc::sync::Arc; 5 + use alloc::vec::Vec; 6 + use core::fmt::Display; 7 + use core::future::Future; 6 8 7 9 /// HTTP client trait for sending raw HTTP requests. 8 10 #[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))] 9 11 pub trait HttpClient { 10 12 /// Error type returned by the HTTP client 11 - type Error: std::error::Error + Display + Send + Sync + 'static; 13 + type Error: core::error::Error + Display + Send + Sync + 'static; 12 14 13 15 /// Send an HTTP request and return the response. 14 16 fn send_http( ··· 60 62 request: http::Request<Vec<u8>>, 61 63 ) -> core::result::Result<http::Response<Vec<u8>>, Self::Error> { 62 64 // Convert http::Request to reqwest::Request 65 + 63 66 let (parts, body) = request.into_parts(); 64 67 65 68 let mut req = self.request(parts.method, parts.uri.to_string()).body(body);
+13 -8
crates/jacquard-common/src/into_static.rs
··· 1 - use std::borrow::Cow; 2 - use std::collections::BTreeMap; 3 - use std::collections::HashMap; 4 - use std::collections::HashSet; 5 - use std::collections::VecDeque; 6 - use std::hash::BuildHasher; 7 - use std::hash::Hash; 8 - use std::sync::Arc; 1 + use alloc::borrow::Cow; 2 + use alloc::borrow::ToOwned; 3 + use alloc::boxed::Box; 4 + use alloc::collections::BTreeMap; 5 + use alloc::collections::VecDeque; 6 + use alloc::string::String; 7 + use alloc::sync::Arc; 8 + use alloc::vec::Vec; 9 + use core::hash::BuildHasher; 10 + use core::hash::Hash; 11 + 12 + use hashbrown::HashMap; 13 + use hashbrown::HashSet; 9 14 10 15 /// Allow turning a value into an "owned" variant, which can then be 11 16 /// returned, moved, etc.
+1
crates/jacquard-common/src/jetstream.rs
··· 8 8 use crate::types::string::{Datetime, Did, Handle, Rkey}; 9 9 use crate::xrpc::{MessageEncoding, SubscriptionResp, XrpcSubscription}; 10 10 use crate::{CowStr, Data, IntoStatic, RawData}; 11 + use alloc::vec::Vec; 11 12 use serde::{Deserialize, Serialize}; 12 13 13 14 /// Parameters for subscribing to Jetstream
+19
crates/jacquard-common/src/lib.rs
··· 192 192 //! be happy to debug, and if it's using a method from one of the jacquard crates and seems like 193 193 //! it *should* just work, that is a bug in jacquard, and you should [file an issue](https://tangled.org/@nonbinary.computer/jacquard/). 194 194 195 + #![no_std] 195 196 #![warn(missing_docs)] 197 + 198 + #[macro_use] 199 + extern crate alloc; 200 + 201 + #[cfg(feature = "std")] 202 + extern crate std; 203 + 204 + /// Lazy initialization type for static values. 205 + /// 206 + /// Uses `std::sync::LazyLock` when the `std` feature is enabled, or `spin::Lazy` 207 + /// for no_std environments. Exported so downstream crates can use it without 208 + /// their own conditional compilation. 209 + #[cfg(feature = "std")] 210 + pub type Lazy<T> = std::sync::LazyLock<T>; 211 + 212 + #[cfg(not(feature = "std"))] 213 + pub use spin::Lazy; 214 + 196 215 pub use bytes; 197 216 pub use chrono; 198 217 pub use cowstr::CowStr;
+2
crates/jacquard-common/src/opt_serde_bytes_helper.rs
··· 1 1 //! Custom serde helpers for bytes::Bytes using serde_bytes 2 2 3 + use alloc::string::String; 4 + use alloc::vec::Vec; 3 5 use base64::{ 4 6 Engine, 5 7 prelude::{BASE64_STANDARD, BASE64_STANDARD_NO_PAD, BASE64_URL_SAFE, BASE64_URL_SAFE_NO_PAD},
+2
crates/jacquard-common/src/serde_bytes_helper.rs
··· 1 1 //! Custom serde helpers for bytes::Bytes using serde_bytes 2 2 3 + use alloc::string::String; 4 + use alloc::vec::Vec; 3 5 use core::fmt; 4 6 5 7 use base64::{
+3
crates/jacquard-common/src/service_auth.rs
··· 19 19 use crate::CowStr; 20 20 use crate::IntoStatic; 21 21 use crate::types::string::{Did, Nsid}; 22 + use alloc::string::String; 23 + use alloc::string::ToString; 24 + use alloc::vec::Vec; 22 25 use base64::Engine; 23 26 use base64::engine::general_purpose::URL_SAFE_NO_PAD; 24 27 use ouroboros::self_referencing;
+25 -10
crates/jacquard-common/src/session.rs
··· 1 1 //! Generic session storage traits and utilities. 2 2 3 + use alloc::boxed::Box; 4 + use alloc::string::ToString; 5 + use alloc::sync::Arc; 6 + use core::error::Error as StdError; 7 + use core::fmt::Display; 8 + use core::future::Future; 9 + use core::hash::Hash; 10 + use hashbrown::HashMap; 11 + #[cfg(feature = "std")] 3 12 use miette::Diagnostic; 4 13 use serde::Serialize; 5 14 use serde::de::DeserializeOwned; 6 15 use serde_json::Value; 7 - use std::collections::HashMap; 8 - use std::error::Error as StdError; 9 - use std::fmt::Display; 10 - use std::future::Future; 11 - use std::hash::Hash; 16 + 17 + #[cfg(feature = "std")] 12 18 use std::path::{Path, PathBuf}; 13 - use std::sync::Arc; 19 + 20 + // Use tokio's RwLock with std, maitake-sync's async RwLock for no_std 21 + #[cfg(not(feature = "std"))] 22 + use maitake_sync::RwLock; 23 + #[cfg(feature = "std")] 14 24 use tokio::sync::RwLock; 15 25 16 26 /// Errors emitted by session stores. 17 - #[derive(Debug, thiserror::Error, Diagnostic)] 27 + #[derive(Debug, thiserror::Error)] 28 + #[cfg_attr(feature = "std", derive(Diagnostic))] 18 29 pub enum SessionStoreError { 19 30 /// Filesystem or I/O error 31 + #[cfg(feature = "std")] 20 32 #[error("I/O error: {0}")] 21 - #[diagnostic(code(jacquard::session_store::io))] 33 + #[cfg_attr(feature = "std", diagnostic(code(jacquard::session_store::io)))] 22 34 Io(#[from] std::io::Error), 23 35 /// Serialization error (e.g., JSON) 24 36 #[error("serialization error: {0}")] 25 - #[diagnostic(code(jacquard::session_store::serde))] 37 + #[cfg_attr(feature = "std", diagnostic(code(jacquard::session_store::serde)))] 26 38 Serde(#[from] serde_json::Error), 27 39 /// Any other error from a backend implementation 28 40 #[error(transparent)] 29 - #[diagnostic(code(jacquard::session_store::other))] 41 + #[cfg_attr(feature = "std", diagnostic(code(jacquard::session_store::other)))] 30 42 Other(#[from] Box<dyn StdError + Send + Sync>), 31 43 } 32 44 ··· 84 96 /// let store = FileTokenStore::new("/tmp/jacquard-session.json"); 85 97 /// let client = AtClient::new(reqwest::Client::new(), base, store); 86 98 /// ``` 99 + #[cfg(feature = "std")] 87 100 #[derive(Clone, Debug)] 88 101 pub struct FileTokenStore { 89 102 /// Path to the JSON file. 90 103 pub path: PathBuf, 91 104 } 92 105 106 + #[cfg(feature = "std")] 93 107 impl FileTokenStore { 94 108 /// Create a new file token store at the given path. 95 109 pub fn new(path: impl AsRef<Path>) -> Self { ··· 104 118 } 105 119 } 106 120 121 + #[cfg(feature = "std")] 107 122 impl<K: Eq + Hash + Display + Send + Sync, T: Clone + Serialize + DeserializeOwned + Send + Sync> 108 123 SessionStore<K, T> for FileTokenStore 109 124 {
+5 -3
crates/jacquard-common/src/stream.rs
··· 42 42 //! # } 43 43 //! ``` 44 44 45 - use std::error::Error; 46 - use std::fmt; 47 - use std::pin::Pin; 45 + use alloc::boxed::Box; 46 + use alloc::string::String; 47 + use core::error::Error; 48 + use core::fmt; 49 + use core::pin::Pin; 48 50 49 51 /// Boxed error type for streaming operations 50 52 pub type BoxError = Box<dyn Error + Send + Sync + 'static>;
+3
crates/jacquard-common/src/types.rs
··· 1 1 use serde::{Deserialize, Serialize}; 2 2 3 + // Re-export Lazy from crate root for submodule use via `super::Lazy` 4 + pub use crate::Lazy; 5 + 3 6 /// AT Protocol URI (at://) types and validation 4 7 pub mod aturi; 5 8 /// Blob references for binary data
+9 -5
crates/jacquard-common/src/types/aturi.rs
··· 3 3 use crate::types::recordkey::{RecordKey, Rkey}; 4 4 use crate::types::string::AtStrError; 5 5 use crate::{CowStr, IntoStatic}; 6 + use alloc::string::String; 7 + use alloc::string::ToString; 8 + use core::fmt; 9 + use core::hash::{Hash, Hasher}; 10 + use core::ops::Deref; 11 + use core::str::FromStr; 6 12 #[cfg(not(target_arch = "wasm32"))] 7 13 use regex::Regex; 8 14 #[cfg(target_arch = "wasm32")] ··· 10 16 use serde::Serializer; 11 17 use serde::{Deserialize, Deserializer, Serialize, de::Error}; 12 18 use smol_str::{SmolStr, ToSmolStr}; 13 - use std::fmt; 14 - use std::hash::{Hash, Hasher}; 15 - use std::sync::LazyLock; 16 - use std::{ops::Deref, str::FromStr}; 19 + 20 + use super::Lazy; 17 21 18 22 /// AT Protocol URI (`at://`) for referencing records in repositories 19 23 /// ··· 131 135 pub type UriPathBuf = RepoPath<'static>; 132 136 133 137 /// Regex for AT URI validation per AT Protocol spec 134 - pub static ATURI_REGEX: LazyLock<Regex> = LazyLock::new(|| { 138 + pub static ATURI_REGEX: Lazy<Regex> = Lazy::new(|| { 135 139 // Fragment allows: / and \ and other special chars. In raw string, backslashes are literal. 136 140 Regex::new(r##"^at://(?<authority>[a-zA-Z0-9._:%-]+)(/(?<collection>[a-zA-Z0-9-.]+)(/(?<rkey>[a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(#(?<fragment>/[a-zA-Z0-9._~:@!$&%')(*+,;=\-\[\]/\\]*))?$"##).unwrap() 137 141 });
+3 -2
crates/jacquard-common/src/types/blob.rs
··· 2 2 use crate::{CowStr, IntoStatic, types::cid::CidLink}; 3 3 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; 4 4 use smol_str::ToSmolStr; 5 - use std::convert::Infallible; 6 - use std::{fmt, hash::Hash, ops::Deref, str::FromStr}; 5 + use alloc::string::{String, ToString}; 6 + use core::convert::Infallible; 7 + use core::{fmt, hash::Hash, ops::Deref, str::FromStr}; 7 8 8 9 /// Blob reference for binary data in AT Protocol 9 10 ///
+2 -1
crates/jacquard-common/src/types/cid.rs
··· 2 2 pub use cid::Cid as IpldCid; 3 3 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor}; 4 4 use smol_str::ToSmolStr; 5 - use std::{convert::Infallible, fmt, ops::Deref, str::FromStr}; 5 + use alloc::string::{String, ToString}; 6 + use core::{convert::Infallible, fmt, ops::Deref, str::FromStr}; 6 7 7 8 /// CID codec for AT Protocol (raw) 8 9 pub const ATP_CID_CODEC: u64 = 0x55;
+1
crates/jacquard-common/src/types/collection.rs
··· 1 + use alloc::string::String; 1 2 use core::fmt; 2 3 3 4 use serde::{Deserialize, Serialize};
+4 -1
crates/jacquard-common/src/types/crypto.rs
··· 17 17 //! assert!(matches!(pk.codec, KeyCodec::Ed25519)); 18 18 //! assert_eq!(pk.bytes.as_ref(), &key); 19 19 20 + use alloc::borrow::Cow; 21 + use alloc::string::{String, ToString}; 22 + use alloc::vec::Vec; 23 + 20 24 use crate::IntoStatic; 21 - use std::borrow::Cow; 22 25 23 26 /// Multicodec code for SHA2-256 hash 24 27 pub const SHA2_256: u64 = 0x12;
+8 -4
crates/jacquard-common/src/types/datetime.rs
··· 1 + use alloc::string::{String, ToString}; 2 + use core::cmp; 3 + use core::fmt; 4 + use core::str::FromStr; 5 + 1 6 use chrono::DurationRound; 2 7 use serde::Serializer; 3 8 use serde::{Deserialize, Deserializer, Serialize, de::Error}; 4 9 use smol_str::{SmolStr, ToSmolStr}; 5 - use std::fmt; 6 - use std::sync::LazyLock; 7 - use std::{cmp, str::FromStr}; 10 + 11 + use super::Lazy; 8 12 9 13 use crate::{CowStr, IntoStatic}; 10 14 #[cfg(not(target_arch = "wasm32"))] ··· 13 17 use regex_lite::Regex; 14 18 15 19 /// Regex for ISO 8601 datetime validation per AT Protocol spec 16 - pub static ISO8601_REGEX: LazyLock<Regex> = LazyLock::new(|| { 20 + pub static ISO8601_REGEX: Lazy<Regex> = Lazy::new(|| { 17 21 Regex::new(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+[0-9]{2}|\-[0-9][1-9]):[0-9]{2})$").unwrap() 18 22 }); 19 23
+8 -5
crates/jacquard-common/src/types/did.rs
··· 6 6 use regex_lite::Regex; 7 7 use serde::{Deserialize, Deserializer, Serialize, de::Error}; 8 8 use smol_str::{SmolStr, ToSmolStr}; 9 - use std::fmt; 10 - use std::sync::LazyLock; 11 - use std::{ops::Deref, str::FromStr}; 9 + use alloc::string::{String, ToString}; 10 + use core::fmt; 11 + use core::ops::Deref; 12 + use core::str::FromStr; 13 + 14 + use super::Lazy; 12 15 13 16 /// Decentralized Identifier (DID) for AT Protocol accounts 14 17 /// ··· 37 40 /// enforce percent-encoding validity at validation time. While the spec states "percent sign 38 41 /// must be followed by two hex characters," this is treated as a best practice rather than 39 42 /// a hard validation requirement. 40 - pub static DID_REGEX: LazyLock<Regex> = 41 - LazyLock::new(|| Regex::new(r"^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$").unwrap()); 43 + pub static DID_REGEX: Lazy<Regex> = 44 + Lazy::new(|| Regex::new(r"^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$").unwrap()); 42 45 43 46 impl<'d> Did<'d> { 44 47 /// Fallible constructor, validates, borrows from input
+9 -7
crates/jacquard-common/src/types/did_doc.rs
··· 2 2 use crate::types::string::{Did, Handle}; 3 3 use crate::types::value::Data; 4 4 use crate::{CowStr, IntoStatic}; 5 + use alloc::collections::BTreeMap; 6 + use alloc::vec::Vec; 5 7 use bon::Builder; 6 8 use serde::{Deserialize, Serialize}; 7 9 use smol_str::SmolStr; 8 - use std::collections::BTreeMap; 9 10 use url::Url; 10 11 11 12 /// DID Document representation with borrowed data where possible. ··· 54 55 55 56 /// Alternate identifiers for the subject, such as at://\<handle\> 56 57 #[serde(borrow)] 57 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 58 + #[serde(skip_serializing_if = "Option::is_none")] 58 59 pub also_known_as: Option<Vec<CowStr<'a>>>, 59 60 60 61 /// Verification methods (keys) for this DID 61 62 #[serde(borrow)] 62 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 63 + #[serde(skip_serializing_if = "Option::is_none")] 63 64 pub verification_method: Option<Vec<VerificationMethod<'a>>>, 64 65 65 66 /// Services associated with this DID (e.g., AtprotoPersonalDataServer) 66 67 #[serde(borrow)] 67 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 68 + #[serde(skip_serializing_if = "Option::is_none")] 68 69 pub service: Option<Vec<Service<'a>>>, 69 70 70 71 /// Forward‑compatible capture of unmodeled fields ··· 174 175 pub r#type: CowStr<'a>, 175 176 /// Optional controller DID 176 177 #[serde(borrow)] 177 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 178 + #[serde(skip_serializing_if = "Option::is_none")] 178 179 pub controller: Option<CowStr<'a>>, 179 180 /// Multikey `publicKeyMultibase` (base58btc) 180 181 #[serde(borrow)] 181 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 182 + #[serde(skip_serializing_if = "Option::is_none")] 182 183 pub public_key_multibase: Option<CowStr<'a>>, 183 184 184 185 /// Forward‑compatible capture of unmodeled fields ··· 212 213 pub r#type: CowStr<'a>, 213 214 /// String or object; we preserve as Data 214 215 #[serde(borrow)] 215 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 216 + #[serde(skip_serializing_if = "Option::is_none")] 216 217 pub service_endpoint: Option<Data<'a>>, 217 218 218 219 /// Forward‑compatible capture of unmodeled fields ··· 235 236 #[cfg(test)] 236 237 mod tests { 237 238 use super::*; 239 + use alloc::string::String; 238 240 use serde_json::json; 239 241 240 242 fn encode_uvarint(mut x: u64) -> Vec<u8> {
+7 -4
crates/jacquard-common/src/types/handle.rs
··· 7 7 use regex_lite::Regex; 8 8 use serde::{Deserialize, Deserializer, Serialize, de::Error}; 9 9 use smol_str::{SmolStr, ToSmolStr}; 10 - use std::fmt; 11 - use std::sync::LazyLock; 12 - use std::{ops::Deref, str::FromStr}; 10 + use alloc::string::{String, ToString}; 11 + use core::fmt; 12 + use core::ops::Deref; 13 + use core::str::FromStr; 14 + 15 + use super::Lazy; 13 16 14 17 /// AT Protocol handle (human-readable account identifier) 15 18 /// ··· 34 37 pub struct Handle<'h>(pub(crate) CowStr<'h>); 35 38 36 39 /// Regex for handle validation per AT Protocol spec 37 - pub static HANDLE_REGEX: LazyLock<Regex> = LazyLock::new(|| { 40 + pub static HANDLE_REGEX: Lazy<Regex> = Lazy::new(|| { 38 41 Regex::new(r"^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$").unwrap() 39 42 }); 40 43 impl<'h> Handle<'h> {
+3 -2
crates/jacquard-common/src/types/ident.rs
··· 1 1 use crate::types::handle::Handle; 2 2 use crate::types::string::AtStrError; 3 3 use crate::{IntoStatic, types::did::Did}; 4 - use std::fmt; 5 - use std::str::FromStr; 4 + use alloc::string::String; 5 + use core::fmt; 6 + use core::str::FromStr; 6 7 7 8 use serde::{Deserialize, Serialize}; 8 9
+3 -2
crates/jacquard-common/src/types/integer.rs
··· 1 1 //! Lexicon integer types with minimum or maximum acceptable values. 2 2 //! Copied from [atrium](https://github.com/atrium-rs/atrium/blob/main/atrium-api/src/types/integer.rs), because this they got right 3 3 4 - use std::num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64}; 5 - use std::str::FromStr; 4 + use alloc::string::{String, ToString}; 5 + use core::num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64}; 6 + use core::str::FromStr; 6 7 7 8 use serde::{Deserialize, de::Error}; 8 9
+4 -2
crates/jacquard-common/src/types/language.rs
··· 2 2 use langtag::InvalidLangTag; 3 3 use serde::{Deserialize, Deserializer, Serialize, de::Error}; 4 4 use smol_str::{SmolStr, ToSmolStr}; 5 - use std::fmt; 6 - use std::{ops::Deref, str::FromStr}; 5 + use alloc::string::{String, ToString}; 6 + use core::fmt; 7 + use core::ops::Deref; 8 + use core::str::FromStr; 7 9 8 10 use crate::CowStr; 9 11
+7 -4
crates/jacquard-common/src/types/nsid.rs
··· 7 7 use regex_lite::Regex; 8 8 use serde::{Deserialize, Deserializer, Serialize, de::Error}; 9 9 use smol_str::{SmolStr, ToSmolStr}; 10 - use std::fmt; 11 - use std::sync::LazyLock; 12 - use std::{ops::Deref, str::FromStr}; 10 + use alloc::string::{String, ToString}; 11 + use core::fmt; 12 + use core::ops::Deref; 13 + use core::str::FromStr; 14 + 15 + use super::Lazy; 13 16 14 17 /// Namespaced Identifier (NSID) for Lexicon schemas and XRPC endpoints 15 18 /// ··· 33 36 pub struct Nsid<'n>(pub(crate) CowStr<'n>); 34 37 35 38 /// Regex for NSID validation per AT Protocol spec 36 - pub static NSID_REGEX: LazyLock<Regex> = LazyLock::new(|| { 39 + pub static NSID_REGEX: Lazy<Regex> = Lazy::new(|| { 37 40 Regex::new(r"^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z][a-zA-Z0-9]{0,62})$").unwrap() 38 41 }); 39 42
+9 -6
crates/jacquard-common/src/types/recordkey.rs
··· 7 7 use regex_lite::Regex; 8 8 use serde::{Deserialize, Deserializer, Serialize, de::Error}; 9 9 use smol_str::{SmolStr, ToSmolStr}; 10 - use std::fmt; 11 - use std::marker::PhantomData; 12 - use std::sync::LazyLock; 13 - use std::{ops::Deref, str::FromStr}; 10 + use alloc::string::{String, ToString}; 11 + use core::fmt; 12 + use core::marker::PhantomData; 13 + use core::ops::Deref; 14 + use core::str::FromStr; 15 + 16 + use super::Lazy; 14 17 15 18 /// Trait for typed record key implementations 16 19 /// ··· 115 118 } 116 119 117 120 /// Regex for record key validation per AT Protocol spec 118 - pub static RKEY_REGEX: LazyLock<Regex> = 119 - LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9.\-_:~]{1,512}$").unwrap()); 121 + pub static RKEY_REGEX: Lazy<Regex> = 122 + Lazy::new(|| Regex::new(r"^[a-zA-Z0-9.\-_:~]{1,512}$").unwrap()); 120 123 121 124 impl<'r> Rkey<'r> { 122 125 /// Fallible constructor, validates, borrows from input
+3 -1
crates/jacquard-common/src/types/string.rs
··· 1 1 use miette::SourceSpan; 2 2 use serde::{Deserialize, Deserializer, Serialize, Serializer}; 3 3 use smol_str::{SmolStr, ToSmolStr}; 4 - use std::{str::FromStr, sync::Arc}; 4 + use alloc::string::{String, ToString}; 5 + use alloc::sync::Arc; 6 + use core::str::FromStr; 5 7 6 8 pub use crate::{ 7 9 CowStr,
+9 -4
crates/jacquard-common/src/types/tid.rs
··· 1 + use alloc::borrow::ToOwned; 2 + use alloc::string::{String, ToString}; 3 + use core::fmt; 4 + use core::ops::Deref; 5 + use core::str::FromStr; 6 + 1 7 use serde::{Deserialize, Deserializer, Serialize, de::Error}; 2 8 use smol_str::{SmolStr, SmolStrBuilder}; 3 - use std::fmt; 4 - use std::sync::LazyLock; 5 - use std::{ops::Deref, str::FromStr}; 9 + 10 + use super::Lazy; 6 11 7 12 use crate::CowStr; 8 13 use crate::types::integer::LimitedU32; ··· 31 36 } 32 37 33 38 /// Regex for TID validation per AT Protocol spec 34 - static TID_REGEX: LazyLock<Regex> = LazyLock::new(|| { 39 + static TID_REGEX: Lazy<Regex> = Lazy::new(|| { 35 40 Regex::new(r"^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$").unwrap() 36 41 }); 37 42
+1 -1
crates/jacquard-common/src/types/uri.rs
··· 6 6 }; 7 7 use serde::{Deserialize, Deserializer, Serialize, Serializer}; 8 8 use smol_str::ToSmolStr; 9 - use std::{fmt::Display, marker::PhantomData, ops::Deref, str::FromStr}; 9 + use core::{fmt::Display, marker::PhantomData, ops::Deref, str::FromStr}; 10 10 use url::Url; 11 11 12 12 /// Generic URI with type-specific parsing
+5 -1
crates/jacquard-common/src/types/value.rs
··· 2 2 IntoStatic, 3 3 types::{DataModelType, LexiconStringType, UriType, blob::Blob, string::*}, 4 4 }; 5 + use alloc::boxed::Box; 6 + use alloc::collections::BTreeMap; 7 + use alloc::string::{String, ToString}; 8 + use alloc::vec::Vec; 5 9 use bytes::Bytes; 10 + use core::convert::Infallible; 6 11 use ipld_core::ipld::Ipld; 7 12 use smol_str::{SmolStr, ToSmolStr}; 8 - use std::{collections::BTreeMap, convert::Infallible}; 9 13 10 14 /// Conversion utilities for Data types 11 15 pub mod convert;
+6 -1
crates/jacquard-common/src/types/value/convert.rs
··· 6 6 string::AtprotoStr, 7 7 value::{Array, Data, Object, RawData, parsing}, 8 8 }; 9 + use alloc::borrow::ToOwned; 10 + use alloc::boxed::Box; 11 + use alloc::collections::BTreeMap; 12 + use alloc::string::String; 13 + use alloc::string::ToString; 14 + use alloc::vec::Vec; 9 15 use bytes::Bytes; 10 16 use core::any::TypeId; 11 17 use smol_str::SmolStr; 12 - use std::{borrow::ToOwned, boxed::Box, collections::BTreeMap, vec::Vec}; 13 18 14 19 /// Error used for converting from and into [`crate::types::value::Data`]. 15 20 #[derive(Clone, Debug, thiserror::Error, miette::Diagnostic)]
+3 -1
crates/jacquard-common/src/types/value/parsing.rs
··· 14 14 use bytes::Bytes; 15 15 use ipld_core::ipld::Ipld; 16 16 use smol_str::{SmolStr, ToSmolStr}; 17 - use std::{collections::BTreeMap, str::FromStr}; 17 + use alloc::collections::BTreeMap; 18 + use alloc::string::String; 19 + use core::str::FromStr; 18 20 use url::Url; 19 21 20 22 /// Insert a string into an at:// `Data<'_>` map, inferring its type.
+7 -1
crates/jacquard-common/src/types/value/serde_impl.rs
··· 1 1 use crate::types::cid::IpldCid; 2 + use alloc::borrow::ToOwned; 3 + use alloc::boxed::Box; 4 + use alloc::collections::BTreeMap; 5 + use alloc::string::String; 6 + use alloc::string::ToString; 7 + use alloc::vec::Vec; 2 8 use base64::{Engine, prelude::BASE64_STANDARD}; 3 9 use bytes::Bytes; 4 10 use core::fmt; 11 + use core::str::FromStr; 5 12 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::VariantAccess}; 6 13 use smol_str::{SmolStr, ToSmolStr}; 7 - use std::{collections::BTreeMap, str::FromStr}; 8 14 9 15 use crate::{ 10 16 IntoStatic,
+102 -26
crates/jacquard-common/src/types/value/tests.rs
··· 1 1 use super::*; 2 - use std::str::FromStr; 2 + use core::str::FromStr; 3 3 4 4 /// Canonicalize JSON by sorting object keys recursively 5 5 fn canonicalize_json(value: &serde_json::Value) -> serde_json::Value { ··· 896 896 // Test complex types 897 897 let mut map = BTreeMap::new(); 898 898 map.insert(SmolStr::new_static("num"), Data::Integer(42)); 899 - map.insert(SmolStr::new_static("text"), Data::String(AtprotoStr::String("test".into()))); 899 + map.insert( 900 + SmolStr::new_static("text"), 901 + Data::String(AtprotoStr::String("test".into())), 902 + ); 900 903 let obj_data = Data::Object(Object(map)); 901 904 let cbor_result = obj_data.to_dag_cbor(); 902 905 assert!(cbor_result.is_ok()); 903 906 assert!(!cbor_result.unwrap().is_empty()); 904 907 905 908 // Test array 906 - let arr_data = Data::Array(Array(vec![Data::Integer(1), Data::Integer(2), Data::Integer(3)])); 909 + let arr_data = Data::Array(Array(vec![ 910 + Data::Integer(1), 911 + Data::Integer(2), 912 + Data::Integer(3), 913 + ])); 907 914 let arr_cbor = arr_data.to_dag_cbor(); 908 915 assert!(arr_cbor.is_ok()); 909 916 assert!(!arr_cbor.unwrap().is_empty()); ··· 935 942 fn test_object_methods() { 936 943 let mut map = BTreeMap::new(); 937 944 map.insert(SmolStr::new_static("num"), Data::Integer(42)); 938 - map.insert(SmolStr::new_static("text"), Data::String(AtprotoStr::String("hello".into()))); 945 + map.insert( 946 + SmolStr::new_static("text"), 947 + Data::String(AtprotoStr::String("hello".into())), 948 + ); 939 949 let obj = Object(map); 940 950 941 951 // Test get ··· 992 1002 fn test_get_at_path_simple() { 993 1003 // Build nested structure: {"embed": {"alt": "test"}} 994 1004 let mut inner = BTreeMap::new(); 995 - inner.insert(SmolStr::new_static("alt"), Data::String(AtprotoStr::String("test".into()))); 1005 + inner.insert( 1006 + SmolStr::new_static("alt"), 1007 + Data::String(AtprotoStr::String("test".into())), 1008 + ); 996 1009 997 1010 let mut outer = BTreeMap::new(); 998 1011 outer.insert(SmolStr::new_static("embed"), Data::Object(Object(inner))); ··· 1018 1031 fn test_get_at_path_arrays() { 1019 1032 // Build: {"items": [{"name": "first"}, {"name": "second"}]} 1020 1033 let mut item1 = BTreeMap::new(); 1021 - item1.insert(SmolStr::new_static("name"), Data::String(AtprotoStr::String("first".into()))); 1034 + item1.insert( 1035 + SmolStr::new_static("name"), 1036 + Data::String(AtprotoStr::String("first".into())), 1037 + ); 1022 1038 1023 1039 let mut item2 = BTreeMap::new(); 1024 - item2.insert(SmolStr::new_static("name"), Data::String(AtprotoStr::String("second".into()))); 1040 + item2.insert( 1041 + SmolStr::new_static("name"), 1042 + Data::String(AtprotoStr::String("second".into())), 1043 + ); 1025 1044 1026 1045 let items = Data::Array(Array(vec![ 1027 1046 Data::Object(Object(item1)), ··· 1049 1068 fn test_get_at_path_complex() { 1050 1069 // Build: {"post": {"embed": {"images": [{"alt": "img1"}, {"alt": "img2"}]}}} 1051 1070 let mut img1 = BTreeMap::new(); 1052 - img1.insert(SmolStr::new_static("alt"), Data::String(AtprotoStr::String("img1".into()))); 1071 + img1.insert( 1072 + SmolStr::new_static("alt"), 1073 + Data::String(AtprotoStr::String("img1".into())), 1074 + ); 1053 1075 1054 1076 let mut img2 = BTreeMap::new(); 1055 - img2.insert(SmolStr::new_static("alt"), Data::String(AtprotoStr::String("img2".into()))); 1077 + img2.insert( 1078 + SmolStr::new_static("alt"), 1079 + Data::String(AtprotoStr::String("img2".into())), 1080 + ); 1056 1081 1057 1082 let images = Data::Array(Array(vec![ 1058 1083 Data::Object(Object(img1)), ··· 1063 1088 embed_map.insert(SmolStr::new_static("images"), images); 1064 1089 1065 1090 let mut post_map = BTreeMap::new(); 1066 - post_map.insert(SmolStr::new_static("embed"), Data::Object(Object(embed_map))); 1091 + post_map.insert( 1092 + SmolStr::new_static("embed"), 1093 + Data::Object(Object(embed_map)), 1094 + ); 1067 1095 1068 1096 let mut root = BTreeMap::new(); 1069 1097 root.insert(SmolStr::new_static("post"), Data::Object(Object(post_map))); ··· 1100 1128 #[test] 1101 1129 fn test_query_exact_path() { 1102 1130 let mut inner = BTreeMap::new(); 1103 - inner.insert(SmolStr::new_static("handle"), Data::String(AtprotoStr::String("alice.bsky.social".into()))); 1131 + inner.insert( 1132 + SmolStr::new_static("handle"), 1133 + Data::String(AtprotoStr::String("alice.bsky.social".into())), 1134 + ); 1104 1135 1105 1136 let mut outer = BTreeMap::new(); 1106 1137 outer.insert(SmolStr::new_static("author"), Data::Object(Object(inner))); ··· 1117 1148 fn test_query_wildcard_array() { 1118 1149 // Build: {"actors": [{"handle": "alice"}, {"handle": "bob"}, {"name": "carol"}]} 1119 1150 let mut actor1 = BTreeMap::new(); 1120 - actor1.insert(SmolStr::new_static("handle"), Data::String(AtprotoStr::String("alice".into()))); 1151 + actor1.insert( 1152 + SmolStr::new_static("handle"), 1153 + Data::String(AtprotoStr::String("alice".into())), 1154 + ); 1121 1155 1122 1156 let mut actor2 = BTreeMap::new(); 1123 - actor2.insert(SmolStr::new_static("handle"), Data::String(AtprotoStr::String("bob".into()))); 1157 + actor2.insert( 1158 + SmolStr::new_static("handle"), 1159 + Data::String(AtprotoStr::String("bob".into())), 1160 + ); 1124 1161 1125 1162 let mut actor3 = BTreeMap::new(); 1126 - actor3.insert(SmolStr::new_static("name"), Data::String(AtprotoStr::String("carol".into()))); 1163 + actor3.insert( 1164 + SmolStr::new_static("name"), 1165 + Data::String(AtprotoStr::String("carol".into())), 1166 + ); 1127 1167 1128 1168 let actors = Data::Array(Array(vec![ 1129 1169 Data::Object(Object(actor1)), ··· 1149 1189 fn test_query_wildcard_object() { 1150 1190 // Build: {"embed": {"images": {...}, "video": {...}}} 1151 1191 let mut images = BTreeMap::new(); 1152 - images.insert(SmolStr::new_static("alt"), Data::String(AtprotoStr::String("img".into()))); 1192 + images.insert( 1193 + SmolStr::new_static("alt"), 1194 + Data::String(AtprotoStr::String("img".into())), 1195 + ); 1153 1196 1154 1197 let mut video = BTreeMap::new(); 1155 - video.insert(SmolStr::new_static("alt"), Data::String(AtprotoStr::String("vid".into()))); 1198 + video.insert( 1199 + SmolStr::new_static("alt"), 1200 + Data::String(AtprotoStr::String("vid".into())), 1201 + ); 1156 1202 1157 1203 let mut embed = BTreeMap::new(); 1158 1204 embed.insert(SmolStr::new_static("images"), Data::Object(Object(images))); ··· 1173 1219 fn test_query_scoped_recursion() { 1174 1220 // Build: {"post": {"author": {"profile": {"handle": "alice"}}}} 1175 1221 let mut handle_map = BTreeMap::new(); 1176 - handle_map.insert(SmolStr::new_static("handle"), Data::String(AtprotoStr::String("alice".into()))); 1222 + handle_map.insert( 1223 + SmolStr::new_static("handle"), 1224 + Data::String(AtprotoStr::String("alice".into())), 1225 + ); 1177 1226 1178 1227 let mut profile_map = BTreeMap::new(); 1179 - profile_map.insert(SmolStr::new_static("profile"), Data::Object(Object(handle_map))); 1228 + profile_map.insert( 1229 + SmolStr::new_static("profile"), 1230 + Data::Object(Object(handle_map)), 1231 + ); 1180 1232 1181 1233 let mut author_map = BTreeMap::new(); 1182 - author_map.insert(SmolStr::new_static("author"), Data::Object(Object(profile_map))); 1234 + author_map.insert( 1235 + SmolStr::new_static("author"), 1236 + Data::Object(Object(profile_map)), 1237 + ); 1183 1238 1184 1239 let mut post_map = BTreeMap::new(); 1185 - post_map.insert(SmolStr::new_static("post"), Data::Object(Object(author_map))); 1240 + post_map.insert( 1241 + SmolStr::new_static("post"), 1242 + Data::Object(Object(author_map)), 1243 + ); 1186 1244 1187 1245 let data = Data::Object(Object(post_map)); 1188 1246 ··· 1197 1255 fn test_query_global_recursion() { 1198 1256 // Build structure with multiple 'cid' fields at different depths 1199 1257 let mut inner1 = BTreeMap::new(); 1200 - inner1.insert(SmolStr::new_static("cid"), Data::String(AtprotoStr::String("cid1".into()))); 1258 + inner1.insert( 1259 + SmolStr::new_static("cid"), 1260 + Data::String(AtprotoStr::String("cid1".into())), 1261 + ); 1201 1262 1202 1263 let mut inner2 = BTreeMap::new(); 1203 - inner2.insert(SmolStr::new_static("cid"), Data::String(AtprotoStr::String("cid2".into()))); 1264 + inner2.insert( 1265 + SmolStr::new_static("cid"), 1266 + Data::String(AtprotoStr::String("cid2".into())), 1267 + ); 1204 1268 1205 1269 let mut middle = BTreeMap::new(); 1206 1270 middle.insert(SmolStr::new_static("post"), Data::Object(Object(inner1))); ··· 1208 1272 1209 1273 let mut root = BTreeMap::new(); 1210 1274 root.insert(SmolStr::new_static("thread"), Data::Object(Object(middle))); 1211 - root.insert(SmolStr::new_static("cid"), Data::String(AtprotoStr::String("cid3".into()))); 1275 + root.insert( 1276 + SmolStr::new_static("cid"), 1277 + Data::String(AtprotoStr::String("cid3".into())), 1278 + ); 1212 1279 1213 1280 let data = Data::Object(Object(root)); 1214 1281 ··· 1229 1296 fn test_query_combined_wildcard_field() { 1230 1297 // Build: {"actors": [{"handle": "alice"}, {"handle": "bob"}]} 1231 1298 let mut actor1 = BTreeMap::new(); 1232 - actor1.insert(SmolStr::new_static("handle"), Data::String(AtprotoStr::String("alice".into()))); 1299 + actor1.insert( 1300 + SmolStr::new_static("handle"), 1301 + Data::String(AtprotoStr::String("alice".into())), 1302 + ); 1233 1303 1234 1304 let mut actor2 = BTreeMap::new(); 1235 - actor2.insert(SmolStr::new_static("handle"), Data::String(AtprotoStr::String("bob".into()))); 1305 + actor2.insert( 1306 + SmolStr::new_static("handle"), 1307 + Data::String(AtprotoStr::String("bob".into())), 1308 + ); 1236 1309 1237 1310 let actors = Data::Array(Array(vec![ 1238 1311 Data::Object(Object(actor1)), ··· 1291 1364 SmolStr::new_static("$type"), 1292 1365 Data::String(AtprotoStr::String(CowStr::new_static("app.bsky.feed.post"))), 1293 1366 ); 1294 - map.insert(SmolStr::new_static("text"), Data::String(AtprotoStr::String(CowStr::new_static("hello")))); 1367 + map.insert( 1368 + SmolStr::new_static("text"), 1369 + Data::String(AtprotoStr::String(CowStr::new_static("hello"))), 1370 + ); 1295 1371 let obj = Object(map); 1296 1372 1297 1373 assert_eq!(obj.type_discriminator(), Some("app.bsky.feed.post"));
+10 -6
crates/jacquard-common/src/websocket.rs
··· 2 2 3 3 use crate::CowStr; 4 4 use crate::stream::StreamError; 5 + use alloc::boxed::Box; 6 + use alloc::string::String; 7 + use alloc::string::ToString; 8 + use alloc::vec::Vec; 5 9 use bytes::Bytes; 10 + use core::borrow::Borrow; 11 + use core::fmt::{self, Display}; 12 + use core::future::Future; 13 + use core::ops::Deref; 14 + use core::pin::Pin; 6 15 use n0_future::Stream; 7 - use std::borrow::Borrow; 8 - use std::fmt::{self, Display}; 9 - use std::future::Future; 10 - use std::ops::Deref; 11 - use std::pin::Pin; 12 16 use url::Url; 13 17 14 18 /// UTF-8 validated bytes for WebSocket text messages ··· 24 28 25 29 /// Get as string slice 26 30 pub fn as_str(&self) -> &str { 27 - unsafe { std::str::from_utf8_unchecked(&self.0) } 31 + unsafe { core::str::from_utf8_unchecked(&self.0) } 28 32 } 29 33 30 34 /// Create from bytes without validation (caller must ensure UTF-8)
+9 -2
crates/jacquard-common/src/xrpc.rs
··· 13 13 #[cfg(feature = "streaming")] 14 14 pub mod streaming; 15 15 16 + use alloc::borrow::ToOwned; 17 + use alloc::boxed::Box; 18 + use alloc::string::String; 19 + #[cfg(feature = "streaming")] 20 + use alloc::string::ToString; 21 + use alloc::vec::Vec; 16 22 use ipld_core::ipld::Ipld; 17 23 #[cfg(feature = "streaming")] 18 24 pub use streaming::{ ··· 33 39 use crate::{CowStr, error::XrpcResult}; 34 40 use crate::{IntoStatic, types::value::RawData}; 35 41 use bytes::Bytes; 42 + use core::error::Error; 43 + use core::fmt::{self, Debug}; 44 + use core::marker::PhantomData; 36 45 use http::{ 37 46 HeaderName, HeaderValue, Request, StatusCode, 38 47 header::{AUTHORIZATION, CONTENT_TYPE}, 39 48 }; 40 49 use serde::{Deserialize, Serialize}; 41 50 use smol_str::SmolStr; 42 - use std::fmt::{self, Debug}; 43 - use std::{error::Error, marker::PhantomData}; 44 51 #[cfg(feature = "websocket")] 45 52 pub use subscription::{ 46 53 BasicSubscriptionClient, MessageEncoding, SubscriptionCall, SubscriptionClient,
+14 -15
crates/jacquard-common/src/xrpc/streaming.rs
··· 1 1 //! Streaming support for XRPC requests and responses 2 2 3 3 use crate::{IntoStatic, StreamError, stream::ByteStream, xrpc::XrpcRequest}; 4 + use alloc::boxed::Box; 4 5 use bytes::Bytes; 6 + use core::{marker::PhantomData, pin::Pin}; 5 7 use http::StatusCode; 6 8 use n0_future::{StreamExt, TryStreamExt}; 7 9 use serde::{Deserialize, Serialize}; 8 10 #[cfg(not(target_arch = "wasm32"))] 9 11 use std::path::Path; 10 - use std::{marker::PhantomData, pin::Pin}; 11 12 12 13 /// Boxed stream type with proper Send bounds for native, no Send for WASM 13 14 #[cfg(not(target_arch = "wasm32"))] ··· 152 153 where 153 154 <P as XrpcProcedureStream>::Frame<'static>: Serialize, 154 155 { 155 - let stream = s 156 - .map(|f| P::encode_frame(f).map(|b| XrpcStreamFrame::new_typed::<P::Frame<'_>>(b))); 156 + let stream = 157 + s.map(|f| P::encode_frame(f).map(|b| XrpcStreamFrame::new_typed::<P::Frame<'_>>(b))); 157 158 158 159 XrpcProcedureSend(Box::pin(stream)) 159 160 } ··· 179 180 pub fn from_bytestream(StreamingResponse { parts, body }: StreamingResponse) -> Self { 180 181 Self { 181 182 parts, 182 - body: Box::pin(body 183 - .into_inner() 184 - .map_ok(|b| XrpcStreamFrame::new(b))), 183 + body: Box::pin(body.into_inner().map_ok(|b| XrpcStreamFrame::new(b))), 185 184 } 186 185 } 187 186 ··· 189 188 pub fn from_parts(parts: http::response::Parts, body: ByteStream) -> Self { 190 189 Self { 191 190 parts, 192 - body: Box::pin(body 193 - .into_inner() 194 - .map_ok(|b| XrpcStreamFrame::new(b))), 191 + body: Box::pin(body.into_inner().map_ok(|b| XrpcStreamFrame::new(b))), 195 192 } 196 193 } 197 194 ··· 214 211 pub fn from_stream(StreamingResponse { parts, body }: StreamingResponse) -> Self { 215 212 Self { 216 213 parts, 217 - body: Box::pin(body 218 - .into_inner() 219 - .map_ok(|b| XrpcStreamFrame::new_typed::<F::Frame<'_>>(b))), 214 + body: Box::pin( 215 + body.into_inner() 216 + .map_ok(|b| XrpcStreamFrame::new_typed::<F::Frame<'_>>(b)), 217 + ), 220 218 } 221 219 } 222 220 ··· 224 222 pub fn from_typed_parts(parts: http::response::Parts, body: ByteStream) -> Self { 225 223 Self { 226 224 parts, 227 - body: Box::pin(body 228 - .into_inner() 229 - .map_ok(|b| XrpcStreamFrame::new_typed::<F::Frame<'_>>(b))), 225 + body: Box::pin( 226 + body.into_inner() 227 + .map_ok(|b| XrpcStreamFrame::new_typed::<F::Frame<'_>>(b)), 228 + ), 230 229 } 231 230 } 232 231 }
+7 -3
crates/jacquard-common/src/xrpc/subscription.rs
··· 3 3 //! This module defines traits and types for typed WebSocket subscriptions, 4 4 //! mirroring the request/response pattern used for HTTP XRPC endpoints. 5 5 6 + use alloc::borrow::ToOwned; 7 + use alloc::string::String; 8 + use alloc::string::ToString; 9 + use alloc::vec::Vec; 10 + use core::error::Error; 11 + use core::future::Future; 12 + use core::marker::PhantomData; 6 13 #[cfg(not(target_arch = "wasm32"))] 7 14 use n0_future::stream::Boxed; 8 15 #[cfg(target_arch = "wasm32")] 9 16 use n0_future::stream::BoxedLocal as Boxed; 10 17 use serde::{Deserialize, Serialize}; 11 - use std::error::Error; 12 - use std::future::Future; 13 - use std::marker::PhantomData; 14 18 use url::Url; 15 19 16 20 use crate::cowstr::ToCowStr;
+1
crates/jacquard-derive/tests/lexicon.rs
··· 1 1 use jacquard_derive::lexicon; 2 2 use serde::{Deserialize, Serialize}; 3 + extern crate alloc; 3 4 4 5 #[lexicon] 5 6 #[derive(Serialize, Deserialize, Debug, PartialEq)]
+1
crates/jacquard-derive/tests/lexicon_schema_derive.rs
··· 3 3 use jacquard_derive::{LexiconSchema, open_union}; 4 4 use jacquard_lexicon::schema::LexiconSchema as LexiconSchemaTrait; 5 5 use serde::{Deserialize, Serialize}; 6 + extern crate alloc; 6 7 7 8 #[test] 8 9 fn test_simple_struct() {
+1
crates/jacquard-derive/tests/open_union.rs
··· 1 1 use jacquard_derive::open_union; 2 2 use serde::{Deserialize, Serialize}; 3 + extern crate alloc; 3 4 4 5 #[open_union] 5 6 #[derive(Serialize, Deserialize, Debug, PartialEq)]
+1 -1
crates/jacquard-lexicon/tests/roundtrip_post.rs
··· 2 2 // 3 3 // This test verifies that the derive macro generates lexicon documents that match 4 4 // the original JSON schemas. 5 - 5 + extern crate alloc; 6 6 use jacquard_common::CowStr; 7 7 use jacquard_common::types::string::{Datetime, Language}; 8 8 use jacquard_lexicon::lexicon::LexiconDoc;
+1 -1
crates/jacquard-lexicon/tests/union_test.rs
··· 1 1 // Minimal test for union attribute 2 - 2 + extern crate alloc; 3 3 use jacquard_common::CowStr; 4 4 use serde::{Deserialize, Serialize}; 5 5