castrum 0.7.0 → 0.9.0
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/LICENSE +21 -0
- package/README.md +322 -59
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +3161 -0
- package/dist/index.js +7306 -0
- package/docs/ARCHITECTURE.md +415 -0
- package/docs/BENCHMARKS.md +307 -0
- package/docs/ENVIRONMENT.md +79 -0
- package/docs/GETTING_STARTED.md +209 -0
- package/docs/INGRESS.md +745 -0
- package/docs/REPO_MAP.md +259 -0
- package/docs/adr/0001-two-wire-formats.md +39 -0
- package/docs/adr/0002-zero-dom-fast-schema.md +38 -0
- package/docs/adr/0003-bun-builtins-delegation.md +42 -0
- package/docs/bun-builtins-decision-matrix.md +108 -0
- package/index.ts +51 -8
- package/package.json +73 -20
- package/src/data/json-rows.ts +13 -13
- package/src/ingress/body.ts +149 -0
- package/src/ingress/constants.ts +220 -48
- package/src/ingress/context.ts +136 -0
- package/src/ingress/decode/baked-result.ts +121 -0
- package/src/ingress/decode/fast-result.ts +160 -0
- package/src/ingress/decode/packed-sections.ts +56 -0
- package/src/ingress/decode/result-base.ts +172 -0
- package/src/ingress/errors.ts +72 -0
- package/src/ingress/fast.ts +152 -1021
- package/src/ingress/handlers.ts +563 -1001
- package/src/ingress/headers/baked-templates.ts +102 -0
- package/src/ingress/headers/cors.ts +44 -0
- package/src/ingress/headers/fast-templates.ts +238 -0
- package/src/ingress/headers/hsts.ts +83 -0
- package/src/ingress/health.ts +67 -0
- package/src/ingress/index.ts +130 -405
- package/src/ingress/metrics.ts +132 -0
- package/src/ingress/options.ts +197 -0
- package/src/ingress/packing/gather-raw-headers.ts +75 -0
- package/src/ingress/packing/header-packing.ts +75 -0
- package/src/ingress/packing/input-packer.ts +150 -0
- package/src/ingress/packing/scratch.ts +50 -0
- package/src/ingress/packing/select-headers.ts +96 -0
- package/src/ingress/response/error-bodies.ts +75 -0
- package/src/ingress/response/terminal.ts +59 -0
- package/src/ingress/routes/common.ts +38 -0
- package/src/ingress/routes/delete.ts +14 -0
- package/src/ingress/routes/echo.ts +107 -0
- package/src/ingress/routes/fallback.ts +23 -0
- package/src/ingress/routes/head.ts +37 -0
- package/src/ingress/routes/index.ts +10 -0
- package/src/ingress/routes/json-write.ts +153 -0
- package/src/ingress/routes/options.ts +36 -0
- package/src/ingress/routes/read.ts +45 -0
- package/src/ingress/server-node.ts +376 -0
- package/src/ingress/server.ts +443 -0
- package/src/ingress/shared.ts +132 -0
- package/src/ingress/status.ts +97 -0
- package/src/ingress/types.ts +157 -0
- package/src/integration/batch.ts +58 -0
- package/src/integration/index.ts +29 -0
- package/src/integration/pipeline.ts +217 -0
- package/src/integration/streaming.ts +79 -0
- package/src/integration/websocket.ts +92 -0
- package/src/loader/batch.ts +133 -0
- package/src/loader/cost.ts +104 -0
- package/src/loader/index.ts +930 -0
- package/src/loader/ops.ts +657 -0
- package/src/native/ffi.ts +1541 -0
- package/src/native/index.ts +8 -207
- package/src/native/loader.ts +194 -0
- package/src/native/types.ts +598 -0
- package/src/rust-ffi/addon.ts +10 -0
- package/src/rust-ffi/batch.ts +431 -0
- package/src/rust-ffi/client.ts +88 -0
- package/src/rust-ffi/context.ts +164 -0
- package/src/rust-ffi/index.ts +40 -541
- package/src/rust-ffi/options.ts +113 -0
- package/src/rust-ffi/packed.ts +253 -0
- package/src/rust-ffi/proven.ts +42 -0
- package/src/rust-ffi/scalar/crypto.ts +265 -0
- package/src/rust-ffi/scalar/factories.ts +233 -0
- package/src/rust-ffi/scalar/hashing.ts +72 -0
- package/src/rust-ffi/scalar/http.ts +206 -0
- package/src/rust-ffi/scalar/index.ts +31 -0
- package/src/rust-ffi/scalar/interface.ts +450 -0
- package/src/rust-ffi/scalar/json.ts +45 -0
- package/src/rust-ffi/scalar/payload.ts +124 -0
- package/src/rust-ffi/text.ts +95 -0
- package/src/selection.json +194 -0
- package/src/selection.ts +77 -0
- package/src/shared/adaptive.ts +68 -0
- package/src/shared/bench-classify.ts +209 -0
- package/src/shared/buffer-pool.ts +209 -0
- package/src/shared/bytes.ts +35 -5
- package/src/shared/env.ts +20 -0
- package/src/shared/log.ts +74 -0
- package/src/shared/metrics.ts +247 -0
- package/src/shared/packed.ts +350 -154
- package/src/shared/proven.ts +596 -0
- package/src/shared/request-id.ts +67 -0
- package/src/shared/response.ts +104 -0
- package/src/shared/runtime.ts +34 -0
- package/src/shared/trace.ts +81 -0
- package/src/shared/uuid.ts +31 -0
- package/Cargo.toml +0 -73
- package/bench.ts +0 -3
- package/rust/batch.rs +0 -195
- package/rust/cookie_parser.rs +0 -70
- package/rust/cors.rs +0 -263
- package/rust/hashing.rs +0 -164
- package/rust/headers.rs +0 -234
- package/rust/hmac_sha256.rs +0 -113
- package/rust/http_parser.rs +0 -132
- package/rust/ingress.rs +0 -616
- package/rust/ingress_constants.rs +0 -89
- package/rust/ip_trust.rs +0 -185
- package/rust/json_ops.rs +0 -66
- package/rust/json_patch_ops.rs +0 -25
- package/rust/json_schema.rs +0 -151
- package/rust/json_ser.rs +0 -429
- package/rust/lib.rs +0 -48
- package/rust/method.rs +0 -141
- package/rust/mime_lookup.rs +0 -76
- package/rust/output.rs +0 -238
- package/rust/proxy.rs +0 -90
- package/rust/query_parser.rs +0 -141
- package/rust/random_token.rs +0 -52
- package/rust/rate_limit.rs +0 -203
- package/rust/terminal.rs +0 -95
- package/rust/unit_tests.rs +0 -843
- package/rust/url_codec.rs +0 -133
- package/rust/util.rs +0 -537
- package/rust/validation.rs +0 -107
- package/rust/websocket.rs +0 -23
- package/src/baseline/index.ts +0 -12
- package/src/baseline/tasks/cookie.ts +0 -22
- package/src/baseline/tasks/hashing.ts +0 -18
- package/src/baseline/tasks/hmac.ts +0 -38
- package/src/baseline/tasks/http.ts +0 -62
- package/src/baseline/tasks/json-patch.ts +0 -12
- package/src/baseline/tasks/json.ts +0 -50
- package/src/baseline/tasks/mime.ts +0 -5
- package/src/baseline/tasks/query.ts +0 -14
- package/src/baseline/tasks/token.ts +0 -15
- package/src/baseline/tasks/url.ts +0 -11
- package/src/baseline/tasks/validation.ts +0 -19
- package/src/baseline/tasks/websocket.ts +0 -14
- package/src/bench/assert.ts +0 -70
- package/src/bench/checks.ts +0 -227
- package/src/bench/checksum.ts +0 -27
- package/src/bench/comparisons.ts +0 -70
- package/src/bench/concurrent-worker.ts +0 -135
- package/src/bench/fixtures.ts +0 -233
- package/src/bench/index.ts +0 -2
- package/src/bench/measure.ts +0 -177
- package/src/bench/now.ts +0 -3
- package/src/bench/report.ts +0 -79
- package/src/bench/run.ts +0 -57
- package/src/bench/tasks/complex.ts +0 -232
- package/src/bench/tasks/concurrent.ts +0 -213
- package/src/bench/tasks/cookie.ts +0 -44
- package/src/bench/tasks/hashing.ts +0 -33
- package/src/bench/tasks/hmac.ts +0 -36
- package/src/bench/tasks/http.ts +0 -50
- package/src/bench/tasks/index.ts +0 -52
- package/src/bench/tasks/json-patch.ts +0 -21
- package/src/bench/tasks/json.ts +0 -33
- package/src/bench/tasks/mime.ts +0 -21
- package/src/bench/tasks/query.ts +0 -44
- package/src/bench/tasks/stress.ts +0 -142
- package/src/bench/tasks/token.ts +0 -20
- package/src/bench/tasks/url.ts +0 -34
- package/src/bench/tasks/validation.ts +0 -57
- package/src/bench/tasks/websocket.ts +0 -21
- package/src/bench/types.ts +0 -60
- package/src/ingress/packed-input.ts +0 -59
- package/src/shared/json.ts +0 -18
package/rust/ip_trust.rs
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
use crate::util::trim_ascii_whitespace;
|
|
2
|
-
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
|
|
3
|
-
use napi::{Error, Result, Status};
|
|
4
|
-
use std::net::IpAddr;
|
|
5
|
-
use xxhash_rust::xxh3::Xxh3;
|
|
6
|
-
|
|
7
|
-
#[derive(Clone)]
|
|
8
|
-
pub enum ProxyTrustMode {
|
|
9
|
-
None,
|
|
10
|
-
All,
|
|
11
|
-
Networks(Vec<IpNet>),
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
impl ProxyTrustMode {
|
|
15
|
-
pub fn from_config(enabled: bool, networks: Option<Vec<String>>) -> Result<Self> {
|
|
16
|
-
if !enabled {
|
|
17
|
-
return Ok(Self::None);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Safety: an enabled trust-proxy mode with NO explicit networks must
|
|
21
|
-
// never fall back to ProxyTrustMode::All (which trusts every hop and
|
|
22
|
-
// lets clients spoof X-Forwarded-For). Treat it as "trust nothing"
|
|
23
|
-
// unless the operator explicitly lists trusted networks.
|
|
24
|
-
let Some(networks) = networks else {
|
|
25
|
-
return Ok(Self::Networks(Vec::new()));
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
if networks.is_empty() {
|
|
29
|
-
return Ok(Self::Networks(Vec::new()));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
let mut out = Vec::with_capacity(networks.len());
|
|
33
|
-
|
|
34
|
-
for raw in networks {
|
|
35
|
-
let raw = raw.trim();
|
|
36
|
-
if raw.is_empty() {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if let Ok(ip) = raw.parse::<IpAddr>() {
|
|
41
|
-
out.push(ip_to_net(ip));
|
|
42
|
-
} else {
|
|
43
|
-
let net: IpNet = raw.parse().map_err(|_| {
|
|
44
|
-
Error::new(
|
|
45
|
-
Status::InvalidArg,
|
|
46
|
-
format!("invalid trusted proxy network: {raw}"),
|
|
47
|
-
)
|
|
48
|
-
})?;
|
|
49
|
-
out.push(net);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Networks that filtered down to nothing -> trust nothing (see above).
|
|
54
|
-
Ok(Self::Networks(out))
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
#[inline(always)]
|
|
58
|
-
pub fn is_none(&self) -> bool {
|
|
59
|
-
matches!(self, Self::None)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
pub fn is_trusted(&self, ip: IpAddr) -> bool {
|
|
63
|
-
match self {
|
|
64
|
-
Self::None => false,
|
|
65
|
-
Self::All => true,
|
|
66
|
-
Self::Networks(nets) => nets.iter().any(|n| n.contains(&ip)),
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
pub enum ResolvedIp<'a> {
|
|
72
|
-
V4([u8; 4]),
|
|
73
|
-
V6([u8; 16]),
|
|
74
|
-
Raw(&'a [u8]),
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
impl<'a> From<IpAddr> for ResolvedIp<'a> {
|
|
78
|
-
fn from(ip: IpAddr) -> Self {
|
|
79
|
-
match ip {
|
|
80
|
-
IpAddr::V4(v4) => ResolvedIp::V4(v4.octets()),
|
|
81
|
-
IpAddr::V6(v6) => ResolvedIp::V6(v6.octets()),
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
impl ResolvedIp<'_> {
|
|
87
|
-
pub fn rate_key(&self, seed: u64) -> u64 {
|
|
88
|
-
let mut h = Xxh3::with_seed(seed);
|
|
89
|
-
|
|
90
|
-
match self {
|
|
91
|
-
ResolvedIp::V4(octets) => {
|
|
92
|
-
h.update(&[1]);
|
|
93
|
-
h.update(octets);
|
|
94
|
-
}
|
|
95
|
-
ResolvedIp::V6(octets) => {
|
|
96
|
-
h.update(&[2]);
|
|
97
|
-
h.update(octets);
|
|
98
|
-
}
|
|
99
|
-
ResolvedIp::Raw(bytes) => {
|
|
100
|
-
h.update(&[0]);
|
|
101
|
-
h.update(bytes);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
h.digest()
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
fn ip_to_net(ip: IpAddr) -> IpNet {
|
|
110
|
-
match ip {
|
|
111
|
-
IpAddr::V4(v4) => IpNet::V4(Ipv4Net::new(v4, 32).expect("valid /32")),
|
|
112
|
-
IpAddr::V6(v6) => IpNet::V6(Ipv6Net::new(v6, 128).expect("valid /128")),
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
fn parse_ip_bytes(bytes: &[u8]) -> Option<IpAddr> {
|
|
117
|
-
let s = std::str::from_utf8(trim_ascii_whitespace(bytes)).ok()?;
|
|
118
|
-
s.parse::<IpAddr>().ok()
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
pub fn resolve_client_ip<'a>(
|
|
122
|
-
mode: &ProxyTrustMode,
|
|
123
|
-
socket_ip: &'a [u8],
|
|
124
|
-
xff: Option<&'a [u8]>,
|
|
125
|
-
x_real_ip: Option<&'a [u8]>,
|
|
126
|
-
) -> (ResolvedIp<'a>, bool) {
|
|
127
|
-
let socket_trim = trim_ascii_whitespace(socket_ip);
|
|
128
|
-
let socket_parsed = parse_ip_bytes(socket_trim);
|
|
129
|
-
|
|
130
|
-
let peer_trusted = socket_parsed
|
|
131
|
-
.map(|ip| mode.is_trusted(ip))
|
|
132
|
-
.unwrap_or(false);
|
|
133
|
-
|
|
134
|
-
if mode.is_none() || !peer_trusted {
|
|
135
|
-
return (
|
|
136
|
-
socket_parsed
|
|
137
|
-
.map(Into::into)
|
|
138
|
-
.unwrap_or(ResolvedIp::Raw(socket_trim)),
|
|
139
|
-
peer_trusted,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if let Some(xff) = xff {
|
|
144
|
-
let mut last_valid: Option<ResolvedIp> = None;
|
|
145
|
-
|
|
146
|
-
for part in xff.split(|&b| b == b',').rev() {
|
|
147
|
-
let part = trim_ascii_whitespace(part);
|
|
148
|
-
if part.is_empty() {
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
match parse_ip_bytes(part) {
|
|
153
|
-
Some(ip) => {
|
|
154
|
-
let trusted = mode.is_trusted(ip);
|
|
155
|
-
last_valid = Some(ip.into());
|
|
156
|
-
|
|
157
|
-
if !trusted {
|
|
158
|
-
return (last_valid.take().unwrap(), peer_trusted);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
None => break,
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if let Some(ip) = last_valid {
|
|
166
|
-
return (ip, peer_trusted);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if let Some(xri) = x_real_ip {
|
|
171
|
-
let xri_trim = trim_ascii_whitespace(xri);
|
|
172
|
-
let first = xri_trim.split(|&b| b == b',').next().unwrap_or(xri_trim);
|
|
173
|
-
|
|
174
|
-
if let Some(ip) = parse_ip_bytes(first) {
|
|
175
|
-
return (ip.into(), peer_trusted);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
(
|
|
180
|
-
socket_parsed
|
|
181
|
-
.map(Into::into)
|
|
182
|
-
.unwrap_or(ResolvedIp::Raw(socket_trim)),
|
|
183
|
-
peer_trusted,
|
|
184
|
-
)
|
|
185
|
-
}
|
package/rust/json_ops.rs
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// rust/json_ops.rs — v2: tighter inlining
|
|
2
|
-
use napi::bindgen_prelude::*;
|
|
3
|
-
use napi_derive::napi;
|
|
4
|
-
use serde::de::{Deserializer, IgnoredAny, SeqAccess, Visitor};
|
|
5
|
-
use serde::Deserialize;
|
|
6
|
-
use std::fmt;
|
|
7
|
-
|
|
8
|
-
#[derive(Deserialize)]
|
|
9
|
-
struct IdRow {
|
|
10
|
-
#[serde(default)]
|
|
11
|
-
id: Option<i64>,
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
struct IdSumVisitor;
|
|
15
|
-
|
|
16
|
-
impl<'de> Visitor<'de> for IdSumVisitor {
|
|
17
|
-
type Value = i64;
|
|
18
|
-
|
|
19
|
-
#[inline]
|
|
20
|
-
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
21
|
-
f.write_str("an array of objects with numeric ids")
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
#[inline]
|
|
25
|
-
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error> {
|
|
26
|
-
let mut sum = 0i64;
|
|
27
|
-
while let Some(row) = seq.next_element::<Option<IdRow>>()? {
|
|
28
|
-
if let Some(IdRow { id: Some(id) }) = row {
|
|
29
|
-
sum = sum.saturating_add(id);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
Ok(sum)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
struct IdSum(i64);
|
|
37
|
-
|
|
38
|
-
impl<'de> Deserialize<'de> for IdSum {
|
|
39
|
-
#[inline]
|
|
40
|
-
fn deserialize<D: Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
|
|
41
|
-
d.deserialize_seq(IdSumVisitor).map(IdSum)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
#[inline(always)]
|
|
46
|
-
pub fn json_valid_bytes(input: &[u8]) -> bool {
|
|
47
|
-
// sonic-rs uses SIMD validation internally — already optimal.
|
|
48
|
-
sonic_rs::from_slice::<IgnoredAny>(input).is_ok()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
#[inline]
|
|
52
|
-
pub fn json_sum_ids_bytes(input: &[u8]) -> Result<i64> {
|
|
53
|
-
sonic_rs::from_slice::<IdSum>(input)
|
|
54
|
-
.map(|s| s.0)
|
|
55
|
-
.map_err(|e| Error::from_reason(e.to_string()))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
#[napi]
|
|
59
|
-
pub fn json_valid(input: Uint8Array) -> bool {
|
|
60
|
-
json_valid_bytes(input.as_ref())
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
#[napi]
|
|
64
|
-
pub fn json_sum_ids(input: Uint8Array) -> Result<i64> {
|
|
65
|
-
json_sum_ids_bytes(input.as_ref())
|
|
66
|
-
}
|
package/rust/json_patch_ops.rs
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
use napi::bindgen_prelude::*;
|
|
2
|
-
use napi_derive::napi;
|
|
3
|
-
use serde_json::Value;
|
|
4
|
-
|
|
5
|
-
#[napi]
|
|
6
|
-
pub fn json_patch(doc: Uint8Array, patch: Uint8Array) -> Result<Buffer> {
|
|
7
|
-
let mut doc_val: Value =
|
|
8
|
-
sonic_rs::from_slice(doc.as_ref()).map_err(|e| Error::from_reason(e.to_string()))?;
|
|
9
|
-
|
|
10
|
-
let patch_val: json_patch::Patch =
|
|
11
|
-
sonic_rs::from_slice(patch.as_ref()).map_err(|e| Error::from_reason(e.to_string()))?;
|
|
12
|
-
|
|
13
|
-
json_patch::patch(&mut doc_val, &patch_val).map_err(|e| Error::from_reason(e.to_string()))?;
|
|
14
|
-
|
|
15
|
-
let mut out = Vec::with_capacity(
|
|
16
|
-
doc.as_ref()
|
|
17
|
-
.len()
|
|
18
|
-
.saturating_add(patch.as_ref().len())
|
|
19
|
-
.saturating_add(32),
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
sonic_rs::to_writer(&mut out, &doc_val).map_err(|e| Error::from_reason(e.to_string()))?;
|
|
23
|
-
|
|
24
|
-
Ok(Buffer::from(out))
|
|
25
|
-
}
|
package/rust/json_schema.rs
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
use napi::bindgen_prelude::*;
|
|
2
|
-
use napi_derive::napi;
|
|
3
|
-
use serde::de::{DeserializeSeed, Deserializer, SeqAccess, Visitor};
|
|
4
|
-
use serde_json::Value;
|
|
5
|
-
use std::fmt;
|
|
6
|
-
use std::sync::Arc;
|
|
7
|
-
|
|
8
|
-
use crate::util::{should_parallelize, total_bytes, unpack};
|
|
9
|
-
#[napi]
|
|
10
|
-
pub struct SchemaValidator {
|
|
11
|
-
schema: Arc<jsonschema::Validator>,
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
#[napi]
|
|
15
|
-
impl SchemaValidator {
|
|
16
|
-
#[napi(constructor)]
|
|
17
|
-
pub fn new(schema_bytes: Uint8Array) -> Result<Self> {
|
|
18
|
-
let schema_str = std::str::from_utf8(&schema_bytes)
|
|
19
|
-
.map_err(|_| Error::new(Status::InvalidArg, "Invalid UTF-8 in schema"))?;
|
|
20
|
-
|
|
21
|
-
let schema_value: Value = sonic_rs::from_str(schema_str)
|
|
22
|
-
.map_err(|e| Error::new(Status::InvalidArg, format!("Schema JSON error: {}", e)))?;
|
|
23
|
-
|
|
24
|
-
let compiled = jsonschema::validator_for(&schema_value).map_err(|e| {
|
|
25
|
-
Error::new(
|
|
26
|
-
Status::InvalidArg,
|
|
27
|
-
format!("Schema compilation error: {}", e),
|
|
28
|
-
)
|
|
29
|
-
})?;
|
|
30
|
-
|
|
31
|
-
Ok(Self {
|
|
32
|
-
schema: Arc::new(compiled),
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
#[inline]
|
|
37
|
-
fn validate_doc(&self, bytes: &[u8]) -> bool {
|
|
38
|
-
match sonic_rs::from_slice::<Value>(bytes) {
|
|
39
|
-
Ok(value) => self.schema.is_valid(&value),
|
|
40
|
-
Err(_) => false,
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/// Validate a packed batch of individual JSON documents.
|
|
45
|
-
///
|
|
46
|
-
/// Input format:
|
|
47
|
-
///
|
|
48
|
-
/// [u32 count]
|
|
49
|
-
/// repeated:
|
|
50
|
-
/// [u32 doc_len]
|
|
51
|
-
/// [doc bytes]
|
|
52
|
-
///
|
|
53
|
-
/// This avoids materializing the entire batch into one DOM array.
|
|
54
|
-
#[napi]
|
|
55
|
-
pub fn validate_batch_packed_count(&self, packed: Uint8Array) -> Result<u32> {
|
|
56
|
-
let items = unpack(packed.as_ref())?;
|
|
57
|
-
|
|
58
|
-
let count: u32 = if should_parallelize(items.len(), total_bytes(&items)) {
|
|
59
|
-
use rayon::prelude::*;
|
|
60
|
-
|
|
61
|
-
items
|
|
62
|
-
.par_chunks(256)
|
|
63
|
-
.map(|chunk| chunk.iter().filter(|item| self.validate_doc(item)).count() as u32)
|
|
64
|
-
.sum()
|
|
65
|
-
} else {
|
|
66
|
-
items.iter().filter(|item| self.validate_doc(item)).count() as u32
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
Ok(count)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/// Validate a packed batch and return a packed bitset.
|
|
73
|
-
///
|
|
74
|
-
/// Output format:
|
|
75
|
-
///
|
|
76
|
-
/// [u32 count]
|
|
77
|
-
/// [ceil(count / 8) bytes]
|
|
78
|
-
#[napi]
|
|
79
|
-
pub fn validate_batch_packed_bitset(&self, packed: Uint8Array) -> Result<Buffer> {
|
|
80
|
-
let items = unpack(packed.as_ref())?;
|
|
81
|
-
|
|
82
|
-
Ok(Buffer::from(crate::util::validation_bitset_chunked(
|
|
83
|
-
&items,
|
|
84
|
-
|item| self.validate_doc(item),
|
|
85
|
-
256,
|
|
86
|
-
)))
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/// Stream-validate a JSON array payload.
|
|
90
|
-
///
|
|
91
|
-
/// This still materializes one array element at a time, because
|
|
92
|
-
/// `jsonschema` currently expects a DOM-like value, but it avoids
|
|
93
|
-
/// materializing the entire array at once.
|
|
94
|
-
#[napi]
|
|
95
|
-
pub fn validate_batch_streaming(&self, batch_bytes: Uint8Array) -> Result<u32> {
|
|
96
|
-
let mut de = sonic_rs::Deserializer::from_slice(batch_bytes.as_ref());
|
|
97
|
-
let seed = BatchSeed {
|
|
98
|
-
validator: &self.schema,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
let count = seed
|
|
102
|
-
.deserialize(&mut de)
|
|
103
|
-
.map_err(|e| Error::from_reason(format!("Streaming batch JSON error: {}", e)))?;
|
|
104
|
-
|
|
105
|
-
Ok(count)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
struct BatchSeed<'a> {
|
|
110
|
-
validator: &'a jsonschema::Validator,
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
impl<'de> DeserializeSeed<'de> for BatchSeed<'_> {
|
|
114
|
-
type Value = u32;
|
|
115
|
-
|
|
116
|
-
fn deserialize<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
|
|
117
|
-
where
|
|
118
|
-
D: Deserializer<'de>,
|
|
119
|
-
{
|
|
120
|
-
deserializer.deserialize_seq(BatchVisitor {
|
|
121
|
-
validator: self.validator,
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
struct BatchVisitor<'a> {
|
|
127
|
-
validator: &'a jsonschema::Validator,
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
impl<'de> Visitor<'de> for BatchVisitor<'_> {
|
|
131
|
-
type Value = u32;
|
|
132
|
-
|
|
133
|
-
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
134
|
-
formatter.write_str("a JSON array of documents")
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
|
|
138
|
-
where
|
|
139
|
-
A: SeqAccess<'de>,
|
|
140
|
-
{
|
|
141
|
-
let mut count = 0u32;
|
|
142
|
-
|
|
143
|
-
while let Some(value) = seq.next_element::<Value>()? {
|
|
144
|
-
if self.validator.is_valid(&value) {
|
|
145
|
-
count += 1;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
Ok(count)
|
|
150
|
-
}
|
|
151
|
-
}
|