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