+8
Cargo.lock
+8
Cargo.lock
···
2279
2279
"miette",
2280
2280
"n0-future",
2281
2281
"regex",
2282
+
"regex-lite",
2282
2283
"reqwest",
2283
2284
"serde",
2284
2285
"serde_html_form",
···
2368
2369
"p256",
2369
2370
"rand 0.9.2",
2370
2371
"regex",
2372
+
"regex-lite",
2371
2373
"reqwest",
2372
2374
"serde",
2373
2375
"serde_html_form",
···
3880
3882
"memchr",
3881
3883
"regex-syntax",
3882
3884
]
3885
+
3886
+
[[package]]
3887
+
name = "regex-lite"
3888
+
version = "0.1.8"
3889
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3890
+
checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da"
3883
3891
3884
3892
[[package]]
3885
3893
name = "regex-syntax"
+1
-1
Cargo.toml
+1
-1
Cargo.toml
+2
-1
crates/jacquard-common/Cargo.toml
+2
-1
crates/jacquard-common/Cargo.toml
···
38
38
multihash = "0.19.3"
39
39
ouroboros = "0.18.5"
40
40
rand = "0.9.2"
41
-
regex = "1.11.3"
42
41
serde.workspace = true
43
42
serde_html_form.workspace = true
44
43
serde_json.workspace = true
···
64
63
getrandom = { version = "0.3.4", features = ["wasm_js"] }
65
64
chrono = { workspace = true, features = ["wasmbind"] }
66
65
getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] }
66
+
regex-lite = "0.1"
67
67
68
68
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
69
69
reqwest = { workspace = true, optional = true, features = [ "http2", "system-proxy", "rustls-tls"] }
70
70
tokio-util = { version = "0.7.16", features = ["io"] }
71
+
regex = { version = "1.11.3", default-features = false, features = ["std", "perf-literal"] }
71
72
72
73
73
74
+3
crates/jacquard-common/src/types/aturi.rs
+3
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
+
#[cfg(not(target_arch = "wasm32"))]
6
7
use regex::Regex;
8
+
#[cfg(target_arch = "wasm32")]
9
+
use regex_lite::Regex;
7
10
use serde::Serializer;
8
11
use serde::{Deserialize, Deserializer, Serialize, de::Error};
9
12
use smol_str::{SmolStr, ToSmolStr};
+3
crates/jacquard-common/src/types/datetime.rs
+3
crates/jacquard-common/src/types/datetime.rs
···
7
7
use std::{cmp, str::FromStr};
8
8
9
9
use crate::{CowStr, IntoStatic};
10
+
#[cfg(not(target_arch = "wasm32"))]
10
11
use regex::Regex;
12
+
#[cfg(target_arch = "wasm32")]
13
+
use regex_lite::Regex;
11
14
12
15
/// Regex for ISO 8601 datetime validation per AT Protocol spec
13
16
pub static ISO8601_REGEX: LazyLock<Regex> = LazyLock::new(|| {
+3
crates/jacquard-common/src/types/did.rs
+3
crates/jacquard-common/src/types/did.rs
···
1
1
use crate::types::string::AtStrError;
2
2
use crate::{CowStr, IntoStatic};
3
+
#[cfg(not(target_arch = "wasm32"))]
3
4
use regex::Regex;
5
+
#[cfg(target_arch = "wasm32")]
6
+
use regex_lite::Regex;
4
7
use serde::{Deserialize, Deserializer, Serialize, de::Error};
5
8
use smol_str::{SmolStr, ToSmolStr};
6
9
use std::fmt;
+3
crates/jacquard-common/src/types/handle.rs
+3
crates/jacquard-common/src/types/handle.rs
···
1
1
use crate::types::string::AtStrError;
2
2
use crate::types::{DISALLOWED_TLDS, ends_with};
3
3
use crate::{CowStr, IntoStatic};
4
+
#[cfg(not(target_arch = "wasm32"))]
4
5
use regex::Regex;
6
+
#[cfg(target_arch = "wasm32")]
7
+
use regex_lite::Regex;
5
8
use serde::{Deserialize, Deserializer, Serialize, de::Error};
6
9
use smol_str::{SmolStr, ToSmolStr};
7
10
use std::fmt;
+3
crates/jacquard-common/src/types/nsid.rs
+3
crates/jacquard-common/src/types/nsid.rs
···
1
1
use crate::types::recordkey::RecordKeyType;
2
2
use crate::types::string::AtStrError;
3
3
use crate::{CowStr, IntoStatic};
4
+
#[cfg(not(target_arch = "wasm32"))]
4
5
use regex::Regex;
6
+
#[cfg(target_arch = "wasm32")]
7
+
use regex_lite::Regex;
5
8
use serde::{Deserialize, Deserializer, Serialize, de::Error};
6
9
use smol_str::{SmolStr, ToSmolStr};
7
10
use std::fmt;
+3
crates/jacquard-common/src/types/recordkey.rs
+3
crates/jacquard-common/src/types/recordkey.rs
···
1
1
use crate::types::Literal;
2
2
use crate::types::string::AtStrError;
3
3
use crate::{CowStr, IntoStatic};
4
+
#[cfg(not(target_arch = "wasm32"))]
4
5
use regex::Regex;
6
+
#[cfg(target_arch = "wasm32")]
7
+
use regex_lite::Regex;
5
8
use serde::{Deserialize, Deserializer, Serialize, de::Error};
6
9
use smol_str::{SmolStr, ToSmolStr};
7
10
use std::fmt;
+3
crates/jacquard-common/src/types/tid.rs
+3
crates/jacquard-common/src/types/tid.rs
···
7
7
use crate::CowStr;
8
8
use crate::types::integer::LimitedU32;
9
9
use crate::types::string::{AtStrError, StrParseKind};
10
+
#[cfg(not(target_arch = "wasm32"))]
10
11
use regex::Regex;
12
+
#[cfg(target_arch = "wasm32")]
13
+
use regex_lite::Regex;
11
14
12
15
const S32_CHAR: &str = "234567abcdefghijklmnopqrstuvwxyz";
13
16
+73
crates/jacquard-common/src/xrpc/dyn_req.rs
+73
crates/jacquard-common/src/xrpc/dyn_req.rs
···
54
54
}
55
55
}
56
56
}
57
+
58
+
pub struct DynResponse {
59
+
buffer: Bytes,
60
+
status: StatusCode,
61
+
}
62
+
63
+
impl DynResponse {
64
+
pub fn new(buffer: Bytes, status: StatusCode) -> Self {
65
+
Self { buffer, status }
66
+
}
67
+
68
+
/// Parse the response into an owned output
69
+
pub fn into_output<R>(self) -> Result<RespOutput<'static, R>, XrpcError<RespErr<'static, R>>>
70
+
where
71
+
R: XrpcResp,
72
+
for<'a> RespOutput<'a, R>: IntoStatic<Output = RespOutput<'static, R>>,
73
+
for<'a> RespErr<'a, R>: IntoStatic<Output = RespErr<'static, R>>,
74
+
{
75
+
fn parse_error<'b, R: XrpcResp>(buffer: &'b [u8]) -> Result<R::Err<'b>, serde_json::Error> {
76
+
serde_json::from_slice(buffer)
77
+
}
78
+
79
+
// 200: parse as output
80
+
if self.status.is_success() {
81
+
match R::decode_output(&self.buffer) {
82
+
Ok(output) => Ok(output.into_static()),
83
+
Err(e) => Err(XrpcError::Decode(e)),
84
+
}
85
+
// 400: try typed XRPC error, fallback to generic error
86
+
} else if self.status.as_u16() == 400 {
87
+
let error = match parse_error::<R>(&self.buffer) {
88
+
Ok(error) => XrpcError::Xrpc(error),
89
+
Err(_) => {
90
+
// Fallback to generic error (InvalidRequest, ExpiredToken, etc.)
91
+
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
92
+
Ok(mut generic) => {
93
+
generic.nsid = R::NSID;
94
+
generic.method = ""; // method info only available on request
95
+
generic.http_status = self.status;
96
+
// Map auth-related errors to AuthError
97
+
match generic.error.as_ref() {
98
+
"ExpiredToken" => XrpcError::Auth(AuthError::TokenExpired),
99
+
"InvalidToken" => XrpcError::Auth(AuthError::InvalidToken),
100
+
_ => XrpcError::Generic(generic),
101
+
}
102
+
}
103
+
Err(e) => XrpcError::Decode(DecodeError::Json(e)),
104
+
}
105
+
}
106
+
};
107
+
Err(error.into_static())
108
+
// 401: always auth error
109
+
} else {
110
+
let error: XrpcError<<R as XrpcResp>::Err<'_>> =
111
+
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
112
+
Ok(mut generic) => {
113
+
let status = self.status;
114
+
generic.nsid = R::NSID;
115
+
generic.method = ""; // method info only available on request
116
+
generic.http_status = status;
117
+
match generic.error.as_ref() {
118
+
"ExpiredToken" => XrpcError::Auth(AuthError::TokenExpired),
119
+
"InvalidToken" => XrpcError::Auth(AuthError::InvalidToken),
120
+
_ => XrpcError::Auth(AuthError::NotAuthenticated),
121
+
}
122
+
}
123
+
Err(e) => XrpcError::Decode(DecodeError::Json(e)),
124
+
};
125
+
126
+
Err(error.into_static())
127
+
}
128
+
}
129
+
}
+2
-1
crates/jacquard/Cargo.toml
+2
-1
crates/jacquard/Cargo.toml
···
152
152
tokio = { workspace = true, default-features = false, features = ["sync"] }
153
153
url.workspace = true
154
154
smol_str.workspace = true
155
-
regex.workspace = true
156
155
webpage.workspace = true
157
156
jose-jwk = { workspace = true, features = ["p256"] }
158
157
tracing = { workspace = true, optional = true }
···
167
166
"rustls-tls",
168
167
] }
169
168
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "fs"] }
169
+
regex = { workspace = true, default-features = false, features = ["std", "perf-literal", "unicode"] }
170
170
171
171
[target.'cfg(target_family = "wasm")'.dependencies]
172
172
getrandom = { version = "0.2", features = ["js"] }
173
+
regex-lite = "0.1"
173
174
174
175
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
175
176
gloo-storage = "0.3"
+5
-2
crates/jacquard/src/richtext.rs
+5
-2
crates/jacquard/src/richtext.rs
···
20
20
use jacquard_identity::resolver::IdentityError;
21
21
#[cfg(feature = "api_bluesky")]
22
22
use jacquard_identity::resolver::IdentityResolver;
23
-
use regex::Regex;
23
+
#[cfg(not(target_family = "wasm"))]
24
+
use regex::{Regex, Captures};
25
+
#[cfg(target_family = "wasm")]
26
+
use regex_lite::{Regex, Captures};
24
27
use std::marker::PhantomData;
25
28
use std::ops::Range;
26
29
use std::sync::LazyLock;
···
197
200
/// runs of newlines and invisible chars to at most two newlines.
198
201
fn sanitize_text(text: &str) -> String {
199
202
SANITIZE_NEWLINES_REGEX
200
-
.replace_all(text, |caps: ®ex::Captures| {
203
+
.replace_all(text, |caps: &Captures| {
201
204
let matched = caps.get(0).unwrap().as_str();
202
205
203
206
// Count newline sequences, treating \r\n as one unit