castrum 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +2259 -0
- package/Cargo.toml +138 -0
- package/README.md +6 -0
- package/build.rs +5 -0
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +1348 -1295
- package/dist/index.js +4307 -3776
- package/docs/API.md +1 -1
- package/docs/ARCHITECTURE.md +3 -2
- package/docs/CASE_STUDY.md +2 -2
- package/docs/ENVIRONMENT.md +16 -0
- package/docs/FFI_BUN_GUIDE.md +89 -6
- package/docs/REPO_MAP.md +2 -2
- package/index.ts +19 -25
- package/package.json +9 -1
- package/rust/crypto/aead.rs +378 -0
- package/rust/crypto/argon2.rs +265 -0
- package/rust/crypto/base64.rs +420 -0
- package/rust/crypto/bcrypt.rs +89 -0
- package/rust/crypto/cookie_sign.rs +169 -0
- package/rust/crypto/csrf.rs +106 -0
- package/rust/crypto/ed25519.rs +215 -0
- package/rust/crypto/hashing.rs +140 -0
- package/rust/crypto/hmac_sha256.rs +88 -0
- package/rust/crypto/jwt/api.rs +286 -0
- package/rust/crypto/jwt/mod.rs +22 -0
- package/rust/crypto/jwt/tests.rs +312 -0
- package/rust/crypto/jwt/token.rs +360 -0
- package/rust/crypto/mod.rs +27 -0
- package/rust/crypto/pbkdf2.rs +96 -0
- package/rust/crypto/random_token.rs +85 -0
- package/rust/crypto/session.rs +222 -0
- package/rust/ffi/crypto.rs +708 -0
- package/rust/ffi/hashing.rs +285 -0
- package/rust/ffi/http.rs +752 -0
- package/rust/ffi/ingress.rs +303 -0
- package/rust/ffi/json.rs +517 -0
- package/rust/ffi/jwt.rs +419 -0
- package/rust/ffi/metrics.rs +373 -0
- package/rust/ffi/mod.rs +68 -0
- package/rust/ffi/payload.rs +275 -0
- package/rust/ffi/probe.rs +60 -0
- package/rust/ffi/rate_limit.rs +77 -0
- package/rust/ffi/route.rs +85 -0
- package/rust/ffi/tests.rs +2333 -0
- package/rust/ffi/util.rs +152 -0
- package/rust/ffi/validators.rs +123 -0
- package/rust/http/accept.rs +606 -0
- package/rust/http/cookie_parser.rs +140 -0
- package/rust/http/etag.rs +289 -0
- package/rust/http/form.rs +132 -0
- package/rust/http/headers.rs +267 -0
- package/rust/http/http_date.rs +286 -0
- package/rust/http/http_parser.rs +252 -0
- package/rust/http/media_type.rs +355 -0
- package/rust/http/method.rs +200 -0
- package/rust/http/mime_lookup.rs +130 -0
- package/rust/http/mod.rs +32 -0
- package/rust/http/multipart.rs +685 -0
- package/rust/http/query_parser.rs +180 -0
- package/rust/http/url_codec.rs +296 -0
- package/rust/http/url_join.rs +324 -0
- package/rust/ingress/api.rs +355 -0
- package/rust/ingress/cors.rs +403 -0
- package/rust/ingress/ingress_constants.rs +209 -0
- package/rust/ingress/ip_trust.rs +312 -0
- package/rust/ingress/mod.rs +48 -0
- package/rust/ingress/native_route.rs +779 -0
- package/rust/ingress/options.rs +176 -0
- package/rust/ingress/output.rs +406 -0
- package/rust/ingress/packed.rs +191 -0
- package/rust/ingress/pipeline.rs +534 -0
- package/rust/ingress/proxy.rs +138 -0
- package/rust/ingress/rate_limit.rs +538 -0
- package/rust/ingress/terminal.rs +114 -0
- package/rust/ingress/tests.rs +566 -0
- package/rust/ingress/time.rs +70 -0
- package/rust/json/fast_schema/capture.rs +336 -0
- package/rust/json/fast_schema/compile.rs +556 -0
- package/rust/json/fast_schema/cursor.rs +368 -0
- package/rust/json/fast_schema/email.rs +282 -0
- package/rust/json/fast_schema/errors.rs +247 -0
- package/rust/json/fast_schema/mod.rs +57 -0
- package/rust/json/fast_schema/tests.rs +1143 -0
- package/rust/json/fast_schema/types.rs +140 -0
- package/rust/json/fast_schema/validate.rs +931 -0
- package/rust/json/json_ops.rs +121 -0
- package/rust/json/json_schema.rs +837 -0
- package/rust/json/json_ser.rs +881 -0
- package/rust/json/mod.rs +19 -0
- package/rust/json/napi_marshal.rs +318 -0
- package/rust/json/patch/api.rs +60 -0
- package/rust/json/patch/engine.rs +153 -0
- package/rust/json/patch/mod.rs +47 -0
- package/rust/json/patch/ops.rs +270 -0
- package/rust/json/patch/pointer.rs +49 -0
- package/rust/json/patch/tests.rs +499 -0
- package/rust/lib.rs +72 -0
- package/rust/metrics/api.rs +148 -0
- package/rust/metrics/mod.rs +21 -0
- package/rust/metrics/registry.rs +961 -0
- package/rust/panic_safety.rs +40 -0
- package/rust/payload/compress.rs +512 -0
- package/rust/payload/mod.rs +13 -0
- package/rust/payload/sse.rs +214 -0
- package/rust/payload/template.rs +253 -0
- package/rust/payload/websocket.rs +69 -0
- package/rust/payload/ws_frames.rs +354 -0
- package/rust/proptest_suite.rs +139 -0
- package/rust/selection.rs +95 -0
- package/rust/src/bin/native-bench.rs +495 -0
- package/rust/test_support.rs +71 -0
- package/rust/util/batch/api.rs +266 -0
- package/rust/util/batch/core.rs +473 -0
- package/rust/util/batch/mod.rs +23 -0
- package/rust/util/batch/tests.rs +445 -0
- package/rust/util/batch_core.rs +184 -0
- package/rust/util/bytes.rs +407 -0
- package/rust/util/mod.rs +34 -0
- package/rust/util/packed.rs +473 -0
- package/rust/util/text.rs +130 -0
- package/rust/util/threadpool.rs +128 -0
- package/rust/util/validation.rs +419 -0
- package/rust-toolchain.toml +17 -0
- package/scripts/postinstall.mjs +199 -0
- package/src/ingress/constants.ts +1 -1
- package/src/ingress/context.ts +2 -2
- package/src/ingress/decode/baked-result.ts +13 -13
- package/src/ingress/decode/fast-result.ts +17 -17
- package/src/ingress/errors.ts +7 -7
- package/src/ingress/headers/baked-templates.ts +1 -1
- package/src/ingress/headers/fast-templates.ts +3 -3
- package/src/ingress/index.ts +1 -1
- package/src/ingress/native-route.ts +9 -5
- package/src/ingress/options.ts +1 -1
- package/src/ingress/packing/gather-raw-headers.ts +1 -1
- package/src/ingress/packing/route-wire.ts +1 -5
- package/src/ingress/response/error-bodies.ts +5 -5
- package/src/ingress/response/terminal.ts +1 -1
- package/src/ingress/routes/echo.ts +4 -4
- package/src/ingress/routes/fallback.ts +2 -2
- package/src/ingress/routes/head.ts +2 -2
- package/src/ingress/routes/native.ts +2 -1
- package/src/ingress/routes/options.ts +1 -1
- package/src/ingress/routes/read.ts +2 -2
- package/src/ingress/server.ts +10 -6
- package/src/ingress/status.ts +7 -7
- package/src/integration/batch.ts +1 -1
- package/src/integration/index.ts +9 -9
- package/src/integration/pipeline.ts +6 -6
- package/src/integration/websocket.ts +2 -2
- package/src/loader/index.ts +1 -5
- package/src/loader/ops.ts +1 -1
- package/src/native/ffi/build/codecs.ts +104 -8
- package/src/native/ffi/build/compress.ts +14 -10
- package/src/native/ffi/build/instances.ts +40 -3
- package/src/native/ffi/build/metrics.ts +224 -0
- package/src/native/ffi/build/parse.ts +59 -23
- package/src/native/ffi/build/util.ts +14 -1
- package/src/native/ffi/build.ts +2 -0
- package/src/native/ffi/selftest.ts +10 -3
- package/src/native/ffi/types.ts +111 -9
- package/src/native/ffi.ts +87 -1
- package/src/native/index.ts +1 -1
- package/src/native/loader.ts +2 -2
- package/src/native/types/instances.ts +34 -0
- package/src/native/types.ts +11 -0
- package/src/runtime/codec.ts +53 -9
- package/src/runtime/types.ts +7 -0
- package/src/rust-ffi/batch/build.ts +16 -9
- package/src/rust-ffi/index.ts +7 -6
- package/src/rust-ffi/options.ts +1 -3
- package/src/rust-ffi/proven.ts +3 -3
- package/src/rust-ffi/scalar/crypto.ts +1 -1
- package/src/rust-ffi/scalar/factories.ts +73 -0
- package/src/rust-ffi/scalar/hashing.ts +1 -1
- package/src/rust-ffi/scalar/http.ts +17 -11
- package/src/rust-ffi/scalar/index.ts +2 -0
- package/src/rust-ffi/scalar/interface.ts +20 -0
- package/src/rust-ffi/scalar/payload.ts +11 -3
- package/src/rust-ffi/scalar/validation.ts +121 -0
- package/src/selection.ts +1 -1
- package/src/shared/bytes.ts +5 -4
- package/src/shared/codec.ts +18 -3
package/rust/ffi/json.rs
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
// rust/ffi/json.rs — JSON C-ABI exports.
|
|
2
|
+
//
|
|
3
|
+
// RFC 6902 JSON patch, the packed JSON token stream (`castrum_json_parse_packed`),
|
|
4
|
+
// schema validation, and template rendering (opaque-handle paths).
|
|
5
|
+
|
|
6
|
+
use rustc_hash::FxHashMap;
|
|
7
|
+
use std::slice;
|
|
8
|
+
|
|
9
|
+
use super::util::panic_guard;
|
|
10
|
+
|
|
11
|
+
/// RFC 6902 JSON patch → patched doc into `out` (0 on invalid/inapplicable).
|
|
12
|
+
///
|
|
13
|
+
/// # Safety
|
|
14
|
+
/// `doc`/`patch` must be valid for reads of their lengths; `out` for `out_cap`.
|
|
15
|
+
#[no_mangle]
|
|
16
|
+
pub unsafe extern "C" fn castrum_json_patch(
|
|
17
|
+
doc: *const u8,
|
|
18
|
+
dlen: usize,
|
|
19
|
+
patch: *const u8,
|
|
20
|
+
plen: usize,
|
|
21
|
+
out: *mut u8,
|
|
22
|
+
out_cap: usize,
|
|
23
|
+
) -> usize {
|
|
24
|
+
if doc.is_null() || patch.is_null() || out.is_null() {
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
// Wrap in panic_guard: a panic in the patch engine must not unwind through
|
|
28
|
+
// the C ABI (process crash) — it becomes 0 (invalid input) instead.
|
|
29
|
+
let Some(patched) = panic_guard(
|
|
30
|
+
|| {
|
|
31
|
+
crate::json::patch::apply_json_patch_bytes(
|
|
32
|
+
slice::from_raw_parts(doc, dlen),
|
|
33
|
+
slice::from_raw_parts(patch, plen),
|
|
34
|
+
)
|
|
35
|
+
.ok()
|
|
36
|
+
},
|
|
37
|
+
None,
|
|
38
|
+
) else {
|
|
39
|
+
return 0;
|
|
40
|
+
};
|
|
41
|
+
if patched.len() > out_cap {
|
|
42
|
+
// Needed-size convention (see compress_to_out!): exact retry, no re-run.
|
|
43
|
+
return patched.len();
|
|
44
|
+
}
|
|
45
|
+
slice::from_raw_parts_mut(out, patched.len()).copy_from_slice(&patched);
|
|
46
|
+
patched.len()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Packed JSON token stream (structural parse, no re-parse) ────────
|
|
50
|
+
// `castrum_json_parse_packed` parses ONCE with sonic-rs and emits a typed
|
|
51
|
+
// token stream with a DEDUPLICATED string table. The JS side assembles the
|
|
52
|
+
// value directly from the tokens — it never re-parses JSON text, and repeated
|
|
53
|
+
// keys/values are decoded exactly once (the old cstring path re-serialized the
|
|
54
|
+
// whole doc to text and `JSON.parse`d it again, measured 3.92x slower than
|
|
55
|
+
// Bun's JSON.parse on the 5k-row fixture).
|
|
56
|
+
//
|
|
57
|
+
// Layout: `[u32 strCount]{[u32 len][utf8 bytes]}... [u32 treeLen][tree]`
|
|
58
|
+
// The tree is a single value encoded as a start/end-marker token stream
|
|
59
|
+
// (little-endian; no counts — the sonic-rs SeqAccess/MapAccess expose no
|
|
60
|
+
// size_hint, and markers let JS decode iteratively with `push`/`pop`):
|
|
61
|
+
// 0 = null | 1 = false | 2 = true | 3 = number (f64 LE, 8 bytes)
|
|
62
|
+
// 4 = string (u32 index into the string table)
|
|
63
|
+
// 5 = array start | 6 = object start
|
|
64
|
+
// 7 = array end | 8 = object end
|
|
65
|
+
// 9 = object key (u32 index into the string table)
|
|
66
|
+
// Object body: `6, (9, keyIdx, value)*, 8`.
|
|
67
|
+
|
|
68
|
+
const JSON_PACKED_NULL: u8 = 0;
|
|
69
|
+
const JSON_PACKED_FALSE: u8 = 1;
|
|
70
|
+
const JSON_PACKED_TRUE: u8 = 2;
|
|
71
|
+
const JSON_PACKED_NUMBER: u8 = 3;
|
|
72
|
+
const JSON_PACKED_STRING: u8 = 4;
|
|
73
|
+
const JSON_PACKED_ARRAY_START: u8 = 5;
|
|
74
|
+
const JSON_PACKED_OBJECT_START: u8 = 6;
|
|
75
|
+
const JSON_PACKED_ARRAY_END: u8 = 7;
|
|
76
|
+
const JSON_PACKED_OBJECT_END: u8 = 8;
|
|
77
|
+
const JSON_PACKED_KEY: u8 = 9;
|
|
78
|
+
|
|
79
|
+
/// Single-pass emitter. sonic-rs's fast parser drives this via serde, writing
|
|
80
|
+
/// the packed token stream DIRECTLY — there is NO intermediate `sonic_rs::Value`
|
|
81
|
+
/// DOM (building it measured ~1.0ms for the 5k-row fixture). Strings (keys +
|
|
82
|
+
/// values) are interned into a deduplicated table keyed by OWNED bytes
|
|
83
|
+
/// (`Vec<u8>`) so escaped strings work uniformly; lookups borrow (`get(&[u8])`)
|
|
84
|
+
/// without allocating. `FxHashMap` (rustc-hash) is used because default SipHash
|
|
85
|
+
/// is slow on short keys (same choice fast_schema makes); a hash collision only
|
|
86
|
+
/// costs an extra byte compare, never a wrong index. Dedup keeps the JS-side
|
|
87
|
+
/// decode to ~1 decode per unique string (a per-occurrence blob was ~10x worse
|
|
88
|
+
/// on the JS side due to rope-slicing).
|
|
89
|
+
pub(crate) struct JsonPackedEmitter {
|
|
90
|
+
strings: FxHashMap<Vec<u8>, u32>,
|
|
91
|
+
table: Vec<u8>,
|
|
92
|
+
tree: Vec<u8>,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
impl JsonPackedEmitter {
|
|
96
|
+
#[inline]
|
|
97
|
+
fn intern(&mut self, bytes: &[u8]) -> u32 {
|
|
98
|
+
if let Some(&idx) = self.strings.get(bytes) {
|
|
99
|
+
return idx;
|
|
100
|
+
}
|
|
101
|
+
let idx = self.strings.len() as u32;
|
|
102
|
+
self.strings.insert(bytes.to_vec(), idx);
|
|
103
|
+
self.table
|
|
104
|
+
.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
|
|
105
|
+
self.table.extend_from_slice(bytes);
|
|
106
|
+
idx
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#[inline]
|
|
110
|
+
fn emit_u32(&mut self, v: u32) {
|
|
111
|
+
self.tree.extend_from_slice(&v.to_le_bytes());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#[inline]
|
|
115
|
+
fn emit_string(&mut self, s: &str) {
|
|
116
|
+
self.tree.push(JSON_PACKED_STRING);
|
|
117
|
+
let idx = self.intern(s.as_bytes());
|
|
118
|
+
self.emit_u32(idx);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[inline]
|
|
122
|
+
fn emit_key(&mut self, s: &str) {
|
|
123
|
+
self.tree.push(JSON_PACKED_KEY);
|
|
124
|
+
let idx = self.intern(s.as_bytes());
|
|
125
|
+
self.emit_u32(idx);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[inline]
|
|
129
|
+
fn emit_number(&mut self, f: f64) {
|
|
130
|
+
self.tree.push(JSON_PACKED_NUMBER);
|
|
131
|
+
self.tree.extend_from_slice(&f.to_le_bytes());
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Seed that deserializes any nested value through the shared emitter.
|
|
136
|
+
struct JsonPackedSeed<'a> {
|
|
137
|
+
out: &'a mut JsonPackedEmitter,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
impl<'de> serde::de::DeserializeSeed<'de> for JsonPackedSeed<'_> {
|
|
141
|
+
type Value = ();
|
|
142
|
+
|
|
143
|
+
#[inline]
|
|
144
|
+
fn deserialize<D>(self, d: D) -> Result<Self::Value, D::Error>
|
|
145
|
+
where
|
|
146
|
+
D: serde::de::Deserializer<'de>,
|
|
147
|
+
{
|
|
148
|
+
d.deserialize_any(JsonPackedVisitor { out: self.out })
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Seed for object KEYS (always JSON strings).
|
|
153
|
+
struct JsonPackedKeySeed<'a> {
|
|
154
|
+
out: &'a mut JsonPackedEmitter,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
impl<'de> serde::de::DeserializeSeed<'de> for JsonPackedKeySeed<'_> {
|
|
158
|
+
type Value = ();
|
|
159
|
+
|
|
160
|
+
#[inline]
|
|
161
|
+
fn deserialize<D>(self, d: D) -> Result<Self::Value, D::Error>
|
|
162
|
+
where
|
|
163
|
+
D: serde::de::Deserializer<'de>,
|
|
164
|
+
{
|
|
165
|
+
d.deserialize_str(JsonPackedKeyVisitor { out: self.out })
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
struct JsonPackedKeyVisitor<'a> {
|
|
170
|
+
out: &'a mut JsonPackedEmitter,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
impl<'de> serde::de::Visitor<'de> for JsonPackedKeyVisitor<'_> {
|
|
174
|
+
type Value = ();
|
|
175
|
+
|
|
176
|
+
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
177
|
+
f.write_str("an object key")
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[inline]
|
|
181
|
+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
182
|
+
where
|
|
183
|
+
E: serde::de::Error,
|
|
184
|
+
{
|
|
185
|
+
self.out.emit_key(v);
|
|
186
|
+
Ok(())
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#[inline]
|
|
190
|
+
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
|
|
191
|
+
where
|
|
192
|
+
E: serde::de::Error,
|
|
193
|
+
{
|
|
194
|
+
self.visit_str(v)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#[inline]
|
|
198
|
+
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
|
199
|
+
where
|
|
200
|
+
E: serde::de::Error,
|
|
201
|
+
{
|
|
202
|
+
self.visit_str(v.as_str())
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
struct JsonPackedVisitor<'a> {
|
|
207
|
+
out: &'a mut JsonPackedEmitter,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
impl<'de> serde::de::Visitor<'de> for JsonPackedVisitor<'_> {
|
|
211
|
+
type Value = ();
|
|
212
|
+
|
|
213
|
+
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
214
|
+
f.write_str("any JSON value")
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#[inline]
|
|
218
|
+
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
|
|
219
|
+
where
|
|
220
|
+
E: serde::de::Error,
|
|
221
|
+
{
|
|
222
|
+
self.out.tree.push(if v {
|
|
223
|
+
JSON_PACKED_TRUE
|
|
224
|
+
} else {
|
|
225
|
+
JSON_PACKED_FALSE
|
|
226
|
+
});
|
|
227
|
+
Ok(())
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#[inline]
|
|
231
|
+
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
|
232
|
+
where
|
|
233
|
+
E: serde::de::Error,
|
|
234
|
+
{
|
|
235
|
+
// f64 = JS number semantics (the napi serde_json::Value path rounds the
|
|
236
|
+
// same way JSON.parse does).
|
|
237
|
+
self.out.emit_number(v as f64);
|
|
238
|
+
Ok(())
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#[inline]
|
|
242
|
+
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
|
243
|
+
where
|
|
244
|
+
E: serde::de::Error,
|
|
245
|
+
{
|
|
246
|
+
self.out.emit_number(v as f64);
|
|
247
|
+
Ok(())
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#[inline]
|
|
251
|
+
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
|
|
252
|
+
where
|
|
253
|
+
E: serde::de::Error,
|
|
254
|
+
{
|
|
255
|
+
self.out.emit_number(v);
|
|
256
|
+
Ok(())
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[inline]
|
|
260
|
+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
261
|
+
where
|
|
262
|
+
E: serde::de::Error,
|
|
263
|
+
{
|
|
264
|
+
self.out.emit_string(v);
|
|
265
|
+
Ok(())
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#[inline]
|
|
269
|
+
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
|
|
270
|
+
where
|
|
271
|
+
E: serde::de::Error,
|
|
272
|
+
{
|
|
273
|
+
self.out.emit_string(v);
|
|
274
|
+
Ok(())
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#[inline]
|
|
278
|
+
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
|
279
|
+
where
|
|
280
|
+
E: serde::de::Error,
|
|
281
|
+
{
|
|
282
|
+
self.out.emit_string(v.as_str());
|
|
283
|
+
Ok(())
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
#[inline]
|
|
287
|
+
fn visit_none<E>(self) -> Result<Self::Value, E>
|
|
288
|
+
where
|
|
289
|
+
E: serde::de::Error,
|
|
290
|
+
{
|
|
291
|
+
self.out.tree.push(JSON_PACKED_NULL);
|
|
292
|
+
Ok(())
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
#[inline]
|
|
296
|
+
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
|
297
|
+
where
|
|
298
|
+
E: serde::de::Error,
|
|
299
|
+
{
|
|
300
|
+
self.out.tree.push(JSON_PACKED_NULL);
|
|
301
|
+
Ok(())
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#[inline]
|
|
305
|
+
fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
|
|
306
|
+
where
|
|
307
|
+
D: serde::de::Deserializer<'de>,
|
|
308
|
+
{
|
|
309
|
+
d.deserialize_any(self)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
#[inline]
|
|
313
|
+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
|
314
|
+
where
|
|
315
|
+
A: serde::de::SeqAccess<'de>,
|
|
316
|
+
{
|
|
317
|
+
self.out.tree.push(JSON_PACKED_ARRAY_START);
|
|
318
|
+
while let Some(()) = seq.next_element_seed(JsonPackedSeed {
|
|
319
|
+
out: &mut *self.out,
|
|
320
|
+
})? {}
|
|
321
|
+
self.out.tree.push(JSON_PACKED_ARRAY_END);
|
|
322
|
+
Ok(())
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
#[inline]
|
|
326
|
+
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
|
327
|
+
where
|
|
328
|
+
A: serde::de::MapAccess<'de>,
|
|
329
|
+
{
|
|
330
|
+
self.out.tree.push(JSON_PACKED_OBJECT_START);
|
|
331
|
+
while let Some(()) = map.next_key_seed(JsonPackedKeySeed {
|
|
332
|
+
out: &mut *self.out,
|
|
333
|
+
})? {
|
|
334
|
+
map.next_value_seed(JsonPackedSeed {
|
|
335
|
+
out: &mut *self.out,
|
|
336
|
+
})?;
|
|
337
|
+
}
|
|
338
|
+
self.out.tree.push(JSON_PACKED_OBJECT_END);
|
|
339
|
+
Ok(())
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
impl<'de> serde::Deserialize<'de> for JsonPackedEmitter {
|
|
344
|
+
#[inline]
|
|
345
|
+
fn deserialize<D>(d: D) -> Result<Self, D::Error>
|
|
346
|
+
where
|
|
347
|
+
D: serde::de::Deserializer<'de>,
|
|
348
|
+
{
|
|
349
|
+
let mut out = JsonPackedEmitter {
|
|
350
|
+
strings: FxHashMap::default(),
|
|
351
|
+
table: Vec::new(),
|
|
352
|
+
tree: Vec::new(),
|
|
353
|
+
};
|
|
354
|
+
d.deserialize_any(JsonPackedVisitor { out: &mut out })?;
|
|
355
|
+
Ok(out)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/// Parse JSON → packed token stream (see the layout above). Needed-size
|
|
360
|
+
/// convention: `0` = invalid JSON (real error → JS throws); `w > out_cap` =
|
|
361
|
+
/// exact required size (JS allocates once and retries); else bytes written.
|
|
362
|
+
///
|
|
363
|
+
/// # Safety
|
|
364
|
+
/// `data` must be valid for reads of `len` bytes; `out` for `out_cap` writes.
|
|
365
|
+
#[no_mangle]
|
|
366
|
+
pub unsafe extern "C" fn castrum_json_parse_packed(
|
|
367
|
+
data: *const u8,
|
|
368
|
+
len: usize,
|
|
369
|
+
out: *mut u8,
|
|
370
|
+
out_cap: usize,
|
|
371
|
+
) -> usize {
|
|
372
|
+
if data.is_null() || out.is_null() {
|
|
373
|
+
return 0;
|
|
374
|
+
}
|
|
375
|
+
let input = slice::from_raw_parts(data, len);
|
|
376
|
+
let Some(emitter) = panic_guard(
|
|
377
|
+
|| sonic_rs::from_slice::<JsonPackedEmitter>(input).ok(),
|
|
378
|
+
None,
|
|
379
|
+
) else {
|
|
380
|
+
return 0;
|
|
381
|
+
};
|
|
382
|
+
// Write the packed [u32 stringsLen][table][u32 treeLen][tree] directly
|
|
383
|
+
// into the caller's buffer — no intermediate Vec + second copy.
|
|
384
|
+
let needed = 4 + emitter.table.len() + 4 + emitter.tree.len();
|
|
385
|
+
if needed > out_cap {
|
|
386
|
+
return needed;
|
|
387
|
+
}
|
|
388
|
+
let out = slice::from_raw_parts_mut(out, needed);
|
|
389
|
+
let mut wp = 0usize;
|
|
390
|
+
out[wp..wp + 4].copy_from_slice(&(emitter.strings.len() as u32).to_le_bytes());
|
|
391
|
+
wp += 4;
|
|
392
|
+
out[wp..wp + emitter.table.len()].copy_from_slice(&emitter.table);
|
|
393
|
+
wp += emitter.table.len();
|
|
394
|
+
out[wp..wp + 4].copy_from_slice(&(emitter.tree.len() as u32).to_le_bytes());
|
|
395
|
+
wp += 4;
|
|
396
|
+
out[wp..wp + emitter.tree.len()].copy_from_slice(&emitter.tree);
|
|
397
|
+
wp += emitter.tree.len();
|
|
398
|
+
wp
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/// SchemaValidator: validate a document against the COMPILED schema via its
|
|
402
|
+
/// opaque inner handle. Returns 1 = valid. A null handle (0) → 0.
|
|
403
|
+
///
|
|
404
|
+
/// # Safety
|
|
405
|
+
/// `inner` must be a valid `SchemaValidator` pointer from `inner_ptr()`, alive
|
|
406
|
+
/// for the call.
|
|
407
|
+
#[no_mangle]
|
|
408
|
+
pub unsafe extern "C" fn castrum_schema_validator_validate(
|
|
409
|
+
inner: usize,
|
|
410
|
+
doc: *const u8,
|
|
411
|
+
doc_len: usize,
|
|
412
|
+
) -> u8 {
|
|
413
|
+
if inner == 0 || doc.is_null() {
|
|
414
|
+
return 0;
|
|
415
|
+
}
|
|
416
|
+
let d = slice::from_raw_parts(doc, doc_len);
|
|
417
|
+
panic_guard(
|
|
418
|
+
|| {
|
|
419
|
+
u8::from(unsafe {
|
|
420
|
+
crate::json::json_schema::schema_validator_validate_core(
|
|
421
|
+
inner as *const crate::json::json_schema::SchemaValidator,
|
|
422
|
+
d,
|
|
423
|
+
)
|
|
424
|
+
})
|
|
425
|
+
},
|
|
426
|
+
0,
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/// TemplateRenderer: render the compiled template with a pre-serialized JSON
|
|
431
|
+
/// context via its opaque inner handle. Needed-size convention; 0 = invalid
|
|
432
|
+
/// context / render error / null handle (real error).
|
|
433
|
+
///
|
|
434
|
+
/// # Safety
|
|
435
|
+
/// `inner` must be a valid `TemplateRenderer` pointer from `inner_ptr()`, alive
|
|
436
|
+
/// for the call; `out` for writes up to `out_cap`.
|
|
437
|
+
#[no_mangle]
|
|
438
|
+
pub unsafe extern "C" fn castrum_template_render(
|
|
439
|
+
inner: usize,
|
|
440
|
+
context: *const u8,
|
|
441
|
+
context_len: usize,
|
|
442
|
+
out: *mut u8,
|
|
443
|
+
out_cap: usize,
|
|
444
|
+
) -> usize {
|
|
445
|
+
if inner == 0 || context.is_null() || out.is_null() {
|
|
446
|
+
return 0;
|
|
447
|
+
}
|
|
448
|
+
let c = slice::from_raw_parts(context, context_len);
|
|
449
|
+
let Some(rendered) = panic_guard(
|
|
450
|
+
|| unsafe {
|
|
451
|
+
crate::payload::template::template_render_core(
|
|
452
|
+
inner as *const crate::payload::template::TemplateRenderer,
|
|
453
|
+
c,
|
|
454
|
+
)
|
|
455
|
+
.ok()
|
|
456
|
+
},
|
|
457
|
+
None,
|
|
458
|
+
) else {
|
|
459
|
+
return 0;
|
|
460
|
+
};
|
|
461
|
+
if rendered.len() > out_cap {
|
|
462
|
+
return rendered.len();
|
|
463
|
+
}
|
|
464
|
+
slice::from_raw_parts_mut(out, rendered.len()).copy_from_slice(&rendered);
|
|
465
|
+
rendered.len()
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/// Fused wire-level validation: RAW query string → JSON → draft-07 validate
|
|
469
|
+
/// against the compiled schema, ONE call. Returns 1 = valid.
|
|
470
|
+
///
|
|
471
|
+
/// # Safety
|
|
472
|
+
/// `inner` must be a live `SchemaValidator` pointer from `inner_ptr()`;
|
|
473
|
+
/// `qs` must be a valid NUL-terminated C string.
|
|
474
|
+
#[no_mangle]
|
|
475
|
+
pub unsafe extern "C" fn castrum_query_validate(
|
|
476
|
+
inner: usize,
|
|
477
|
+
qs: *const std::os::raw::c_char,
|
|
478
|
+
) -> u8 {
|
|
479
|
+
if inner == 0 || qs.is_null() {
|
|
480
|
+
return 0;
|
|
481
|
+
}
|
|
482
|
+
let q = std::ffi::CStr::from_ptr(qs).to_bytes();
|
|
483
|
+
panic_guard(
|
|
484
|
+
|| {
|
|
485
|
+
crate::json::json_schema::schema_validate_query_core(
|
|
486
|
+
inner as *const crate::json::json_schema::SchemaValidator,
|
|
487
|
+
q,
|
|
488
|
+
)
|
|
489
|
+
},
|
|
490
|
+
false,
|
|
491
|
+
) as u8
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/// Fused cookie-header variant of [`castrum_query_validate`] (trim +
|
|
495
|
+
/// DQUOTE-unwrap values, NO percent-decode). Returns 1 = valid.
|
|
496
|
+
///
|
|
497
|
+
/// # Safety
|
|
498
|
+
/// See [`castrum_query_validate`].
|
|
499
|
+
#[no_mangle]
|
|
500
|
+
pub unsafe extern "C" fn castrum_cookie_validate(
|
|
501
|
+
inner: usize,
|
|
502
|
+
header: *const std::os::raw::c_char,
|
|
503
|
+
) -> u8 {
|
|
504
|
+
if inner == 0 || header.is_null() {
|
|
505
|
+
return 0;
|
|
506
|
+
}
|
|
507
|
+
let h = std::ffi::CStr::from_ptr(header).to_bytes();
|
|
508
|
+
panic_guard(
|
|
509
|
+
|| {
|
|
510
|
+
crate::json::json_schema::schema_validate_cookie_core(
|
|
511
|
+
inner as *const crate::json::json_schema::SchemaValidator,
|
|
512
|
+
h,
|
|
513
|
+
)
|
|
514
|
+
},
|
|
515
|
+
false,
|
|
516
|
+
) as u8
|
|
517
|
+
}
|