A better Rust ATProto crate

working through the core types

Orual 8cebd32b 40c44afc

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