castrum 0.7.0 → 0.9.1

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.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -368
  3. package/castrum.linux-x64-gnu.node +0 -0
  4. package/dist/index.d.ts +2763 -0
  5. package/dist/index.js +8707 -0
  6. package/docs/API.md +337 -0
  7. package/docs/ARCHITECTURE.md +507 -0
  8. package/docs/BENCHMARKS.md +343 -0
  9. package/docs/CASE_STUDY.md +240 -0
  10. package/docs/ENVIRONMENT.md +81 -0
  11. package/docs/FFI_BUN_GUIDE.md +327 -0
  12. package/docs/GETTING_STARTED.md +192 -0
  13. package/docs/INGRESS-ROUTER.md +81 -0
  14. package/docs/INGRESS.md +746 -0
  15. package/docs/NATIVE-ROUTE.md +53 -0
  16. package/docs/REPO_MAP.md +279 -0
  17. package/docs/adr/0001-two-wire-formats.md +39 -0
  18. package/docs/adr/0002-zero-dom-fast-schema.md +38 -0
  19. package/docs/adr/0003-bun-builtins-delegation.md +43 -0
  20. package/docs/bun-builtins-decision-matrix.md +115 -0
  21. package/index.ts +44 -8
  22. package/package.json +98 -33
  23. package/src/ingress/body.ts +289 -0
  24. package/src/ingress/constants.ts +220 -48
  25. package/src/ingress/context.ts +136 -0
  26. package/src/ingress/decode/baked-result.ts +121 -0
  27. package/src/ingress/decode/fast-result.ts +160 -0
  28. package/src/ingress/decode/packed-sections.ts +56 -0
  29. package/src/ingress/decode/result-base.ts +172 -0
  30. package/src/ingress/errors.ts +72 -0
  31. package/src/ingress/fast.ts +152 -1021
  32. package/src/ingress/handlers.ts +639 -1001
  33. package/src/ingress/headers/baked-templates.ts +102 -0
  34. package/src/ingress/headers/cors.ts +44 -0
  35. package/src/ingress/headers/fast-templates.ts +238 -0
  36. package/src/ingress/headers/hsts.ts +83 -0
  37. package/src/ingress/health.ts +68 -0
  38. package/src/ingress/index.ts +142 -405
  39. package/src/ingress/metrics.ts +132 -0
  40. package/src/ingress/options.ts +197 -0
  41. package/src/ingress/packing/gather-raw-headers.ts +139 -0
  42. package/src/ingress/packing/header-packing.ts +75 -0
  43. package/src/ingress/packing/input-packer.ts +164 -0
  44. package/src/ingress/packing/scratch.ts +50 -0
  45. package/src/ingress/packing/select-headers.ts +102 -0
  46. package/src/ingress/response/error-bodies.ts +75 -0
  47. package/src/ingress/response/terminal.ts +59 -0
  48. package/src/ingress/router.ts +237 -0
  49. package/src/ingress/routes/common.ts +79 -0
  50. package/src/ingress/routes/delete.ts +14 -0
  51. package/src/ingress/routes/echo.ts +107 -0
  52. package/src/ingress/routes/fallback.ts +23 -0
  53. package/src/ingress/routes/head.ts +27 -0
  54. package/src/ingress/routes/index.ts +11 -0
  55. package/src/ingress/routes/json-write.ts +129 -0
  56. package/src/ingress/routes/options.ts +26 -0
  57. package/src/ingress/routes/read.ts +35 -0
  58. package/src/ingress/routes/responder.ts +159 -0
  59. package/src/ingress/server-node.ts +376 -0
  60. package/src/ingress/server.ts +475 -0
  61. package/src/ingress/shared.ts +132 -0
  62. package/src/ingress/status.ts +97 -0
  63. package/src/ingress/types.ts +191 -0
  64. package/src/integration/batch.ts +58 -0
  65. package/src/integration/index.ts +29 -0
  66. package/src/integration/pipeline.ts +220 -0
  67. package/src/integration/streaming.ts +78 -0
  68. package/src/integration/websocket.ts +93 -0
  69. package/src/loader/batch.ts +137 -0
  70. package/src/loader/cost.ts +105 -0
  71. package/src/loader/index.ts +745 -0
  72. package/src/loader/ops.ts +657 -0
  73. package/src/loader/state.ts +84 -0
  74. package/src/loader/types.ts +145 -0
  75. package/src/native/ffi/constants.ts +58 -0
  76. package/src/native/ffi/selftest.ts +729 -0
  77. package/src/native/ffi/types.ts +474 -0
  78. package/src/native/ffi.ts +1775 -0
  79. package/src/native/index.ts +8 -207
  80. package/src/native/loader.ts +240 -0
  81. package/src/native/types/instances.ts +274 -0
  82. package/src/native/types.ts +401 -0
  83. package/src/runtime/builtins.ts +131 -0
  84. package/src/runtime/codec.ts +147 -0
  85. package/src/runtime/detect.ts +36 -0
  86. package/src/runtime/index.ts +28 -0
  87. package/src/runtime/native.ts +35 -0
  88. package/src/runtime/server.ts +31 -0
  89. package/src/runtime/transport.ts +64 -0
  90. package/src/runtime/types.ts +173 -0
  91. package/src/runtime/uuid.ts +45 -0
  92. package/src/runtime/websocket.ts +30 -0
  93. package/src/rust-ffi/addon.ts +10 -0
  94. package/src/rust-ffi/batch/build.ts +155 -0
  95. package/src/rust-ffi/batch/index.ts +8 -0
  96. package/src/rust-ffi/batch/types.ts +92 -0
  97. package/src/rust-ffi/client.ts +87 -0
  98. package/src/rust-ffi/context.ts +193 -0
  99. package/src/rust-ffi/index.ts +51 -541
  100. package/src/rust-ffi/into.ts +21 -0
  101. package/src/rust-ffi/options.ts +113 -0
  102. package/src/rust-ffi/packed.ts +253 -0
  103. package/src/rust-ffi/proven.ts +28 -0
  104. package/src/rust-ffi/scalar/crypto.ts +346 -0
  105. package/src/rust-ffi/scalar/factories.ts +459 -0
  106. package/src/rust-ffi/scalar/hashing.ts +74 -0
  107. package/src/rust-ffi/scalar/http.ts +337 -0
  108. package/src/rust-ffi/scalar/index.ts +31 -0
  109. package/src/rust-ffi/scalar/interface.ts +325 -0
  110. package/src/rust-ffi/scalar/json-packed.ts +175 -0
  111. package/src/rust-ffi/scalar/json.ts +61 -0
  112. package/src/rust-ffi/scalar/payload.ts +241 -0
  113. package/src/rust-ffi/text.ts +121 -0
  114. package/src/selection.json +194 -0
  115. package/src/selection.ts +73 -0
  116. package/src/shared/adaptive.ts +67 -0
  117. package/src/shared/buffer-pool.ts +264 -0
  118. package/src/shared/bytes.ts +108 -9
  119. package/src/shared/codec.ts +78 -0
  120. package/src/shared/env.ts +20 -0
  121. package/src/shared/log.ts +75 -0
  122. package/src/shared/metrics.ts +247 -0
  123. package/src/shared/packed/index.ts +10 -0
  124. package/src/shared/packed/parsers.ts +31 -0
  125. package/src/shared/packed/schema.ts +23 -0
  126. package/src/shared/packed/wire.ts +504 -0
  127. package/src/shared/proven.ts +289 -0
  128. package/src/shared/request-id.ts +67 -0
  129. package/src/shared/response.ts +104 -0
  130. package/src/shared/runtime.ts +29 -0
  131. package/src/shared/trace.ts +81 -0
  132. package/src/shared/uuid.ts +15 -0
  133. package/Cargo.toml +0 -73
  134. package/bench.ts +0 -3
  135. package/rust/batch.rs +0 -195
  136. package/rust/cookie_parser.rs +0 -70
  137. package/rust/cors.rs +0 -263
  138. package/rust/hashing.rs +0 -164
  139. package/rust/headers.rs +0 -234
  140. package/rust/hmac_sha256.rs +0 -113
  141. package/rust/http_parser.rs +0 -132
  142. package/rust/ingress.rs +0 -616
  143. package/rust/ingress_constants.rs +0 -89
  144. package/rust/ip_trust.rs +0 -185
  145. package/rust/json_ops.rs +0 -66
  146. package/rust/json_patch_ops.rs +0 -25
  147. package/rust/json_schema.rs +0 -151
  148. package/rust/json_ser.rs +0 -429
  149. package/rust/lib.rs +0 -48
  150. package/rust/method.rs +0 -141
  151. package/rust/mime_lookup.rs +0 -76
  152. package/rust/output.rs +0 -238
  153. package/rust/proxy.rs +0 -90
  154. package/rust/query_parser.rs +0 -141
  155. package/rust/random_token.rs +0 -52
  156. package/rust/rate_limit.rs +0 -203
  157. package/rust/terminal.rs +0 -95
  158. package/rust/unit_tests.rs +0 -843
  159. package/rust/url_codec.rs +0 -133
  160. package/rust/util.rs +0 -537
  161. package/rust/validation.rs +0 -107
  162. package/rust/websocket.rs +0 -23
  163. package/src/baseline/index.ts +0 -12
  164. package/src/baseline/tasks/cookie.ts +0 -22
  165. package/src/baseline/tasks/hashing.ts +0 -18
  166. package/src/baseline/tasks/hmac.ts +0 -38
  167. package/src/baseline/tasks/http.ts +0 -62
  168. package/src/baseline/tasks/json-patch.ts +0 -12
  169. package/src/baseline/tasks/json.ts +0 -50
  170. package/src/baseline/tasks/mime.ts +0 -5
  171. package/src/baseline/tasks/query.ts +0 -14
  172. package/src/baseline/tasks/token.ts +0 -15
  173. package/src/baseline/tasks/url.ts +0 -11
  174. package/src/baseline/tasks/validation.ts +0 -19
  175. package/src/baseline/tasks/websocket.ts +0 -14
  176. package/src/bench/assert.ts +0 -70
  177. package/src/bench/checks.ts +0 -227
  178. package/src/bench/checksum.ts +0 -27
  179. package/src/bench/comparisons.ts +0 -70
  180. package/src/bench/concurrent-worker.ts +0 -135
  181. package/src/bench/fixtures.ts +0 -233
  182. package/src/bench/index.ts +0 -2
  183. package/src/bench/measure.ts +0 -177
  184. package/src/bench/now.ts +0 -3
  185. package/src/bench/report.ts +0 -79
  186. package/src/bench/run.ts +0 -57
  187. package/src/bench/tasks/complex.ts +0 -232
  188. package/src/bench/tasks/concurrent.ts +0 -213
  189. package/src/bench/tasks/cookie.ts +0 -44
  190. package/src/bench/tasks/hashing.ts +0 -33
  191. package/src/bench/tasks/hmac.ts +0 -36
  192. package/src/bench/tasks/http.ts +0 -50
  193. package/src/bench/tasks/index.ts +0 -52
  194. package/src/bench/tasks/json-patch.ts +0 -21
  195. package/src/bench/tasks/json.ts +0 -33
  196. package/src/bench/tasks/mime.ts +0 -21
  197. package/src/bench/tasks/query.ts +0 -44
  198. package/src/bench/tasks/stress.ts +0 -142
  199. package/src/bench/tasks/token.ts +0 -20
  200. package/src/bench/tasks/url.ts +0 -34
  201. package/src/bench/tasks/validation.ts +0 -57
  202. package/src/bench/tasks/websocket.ts +0 -21
  203. package/src/bench/types.ts +0 -60
  204. package/src/data/json-rows.ts +0 -33
  205. package/src/ingress/packed-input.ts +0 -59
  206. package/src/shared/json.ts +0 -18
  207. package/src/shared/packed.ts +0 -316
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
- }
@@ -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
- }
@@ -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
- }