+51
Cargo.lock
+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
+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
+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
+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
+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
+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
+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 alloc::vec::Vec;
12
use serde::{Deserialize, Serialize};
13
14
/// Parameters for subscribing to Jetstream
+19
crates/jacquard-common/src/lib.rs
+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
+2
crates/jacquard-common/src/opt_serde_bytes_helper.rs
+2
crates/jacquard-common/src/serde_bytes_helper.rs
+2
crates/jacquard-common/src/serde_bytes_helper.rs
+3
crates/jacquard-common/src/service_auth.rs
+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 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
+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
+5
-3
crates/jacquard-common/src/stream.rs
+3
crates/jacquard-common/src/types.rs
+3
crates/jacquard-common/src/types.rs
+9
-5
crates/jacquard-common/src/types/aturi.rs
+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
+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
-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 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
crates/jacquard-common/src/types/collection.rs
+4
-1
crates/jacquard-common/src/types/crypto.rs
+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 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
+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
+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
+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
-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
+3
-2
crates/jacquard-common/src/types/ident.rs
+3
-2
crates/jacquard-common/src/types/integer.rs
+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
+4
-2
crates/jacquard-common/src/types/language.rs
+7
-4
crates/jacquard-common/src/types/nsid.rs
+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
+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
+3
-1
crates/jacquard-common/src/types/string.rs
+9
-4
crates/jacquard-common/src/types/tid.rs
+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
+1
-1
crates/jacquard-common/src/types/uri.rs
+5
-1
crates/jacquard-common/src/types/value.rs
+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
-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
+3
-1
crates/jacquard-common/src/types/value/parsing.rs
+7
-1
crates/jacquard-common/src/types/value/serde_impl.rs
+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
+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
+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
+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
+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
+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
crates/jacquard-derive/tests/lexicon.rs
+1
crates/jacquard-derive/tests/lexicon_schema_derive.rs
+1
crates/jacquard-derive/tests/lexicon_schema_derive.rs
+1
crates/jacquard-derive/tests/open_union.rs
+1
crates/jacquard-derive/tests/open_union.rs
+1
-1
crates/jacquard-lexicon/tests/roundtrip_post.rs
+1
-1
crates/jacquard-lexicon/tests/roundtrip_post.rs
+1
-1
crates/jacquard-lexicon/tests/union_test.rs
+1
-1
crates/jacquard-lexicon/tests/union_test.rs