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.
Files changed (184) hide show
  1. package/Cargo.lock +2259 -0
  2. package/Cargo.toml +138 -0
  3. package/README.md +6 -0
  4. package/build.rs +5 -0
  5. package/castrum.linux-x64-gnu.node +0 -0
  6. package/dist/index.d.ts +1348 -1295
  7. package/dist/index.js +4307 -3776
  8. package/docs/API.md +1 -1
  9. package/docs/ARCHITECTURE.md +3 -2
  10. package/docs/CASE_STUDY.md +2 -2
  11. package/docs/ENVIRONMENT.md +16 -0
  12. package/docs/FFI_BUN_GUIDE.md +89 -6
  13. package/docs/REPO_MAP.md +2 -2
  14. package/index.ts +19 -25
  15. package/package.json +9 -1
  16. package/rust/crypto/aead.rs +378 -0
  17. package/rust/crypto/argon2.rs +265 -0
  18. package/rust/crypto/base64.rs +420 -0
  19. package/rust/crypto/bcrypt.rs +89 -0
  20. package/rust/crypto/cookie_sign.rs +169 -0
  21. package/rust/crypto/csrf.rs +106 -0
  22. package/rust/crypto/ed25519.rs +215 -0
  23. package/rust/crypto/hashing.rs +140 -0
  24. package/rust/crypto/hmac_sha256.rs +88 -0
  25. package/rust/crypto/jwt/api.rs +286 -0
  26. package/rust/crypto/jwt/mod.rs +22 -0
  27. package/rust/crypto/jwt/tests.rs +312 -0
  28. package/rust/crypto/jwt/token.rs +360 -0
  29. package/rust/crypto/mod.rs +27 -0
  30. package/rust/crypto/pbkdf2.rs +96 -0
  31. package/rust/crypto/random_token.rs +85 -0
  32. package/rust/crypto/session.rs +222 -0
  33. package/rust/ffi/crypto.rs +708 -0
  34. package/rust/ffi/hashing.rs +285 -0
  35. package/rust/ffi/http.rs +752 -0
  36. package/rust/ffi/ingress.rs +303 -0
  37. package/rust/ffi/json.rs +517 -0
  38. package/rust/ffi/jwt.rs +419 -0
  39. package/rust/ffi/metrics.rs +373 -0
  40. package/rust/ffi/mod.rs +68 -0
  41. package/rust/ffi/payload.rs +275 -0
  42. package/rust/ffi/probe.rs +60 -0
  43. package/rust/ffi/rate_limit.rs +77 -0
  44. package/rust/ffi/route.rs +85 -0
  45. package/rust/ffi/tests.rs +2333 -0
  46. package/rust/ffi/util.rs +152 -0
  47. package/rust/ffi/validators.rs +123 -0
  48. package/rust/http/accept.rs +606 -0
  49. package/rust/http/cookie_parser.rs +140 -0
  50. package/rust/http/etag.rs +289 -0
  51. package/rust/http/form.rs +132 -0
  52. package/rust/http/headers.rs +267 -0
  53. package/rust/http/http_date.rs +286 -0
  54. package/rust/http/http_parser.rs +252 -0
  55. package/rust/http/media_type.rs +355 -0
  56. package/rust/http/method.rs +200 -0
  57. package/rust/http/mime_lookup.rs +130 -0
  58. package/rust/http/mod.rs +32 -0
  59. package/rust/http/multipart.rs +685 -0
  60. package/rust/http/query_parser.rs +180 -0
  61. package/rust/http/url_codec.rs +296 -0
  62. package/rust/http/url_join.rs +324 -0
  63. package/rust/ingress/api.rs +355 -0
  64. package/rust/ingress/cors.rs +403 -0
  65. package/rust/ingress/ingress_constants.rs +209 -0
  66. package/rust/ingress/ip_trust.rs +312 -0
  67. package/rust/ingress/mod.rs +48 -0
  68. package/rust/ingress/native_route.rs +779 -0
  69. package/rust/ingress/options.rs +176 -0
  70. package/rust/ingress/output.rs +406 -0
  71. package/rust/ingress/packed.rs +191 -0
  72. package/rust/ingress/pipeline.rs +534 -0
  73. package/rust/ingress/proxy.rs +138 -0
  74. package/rust/ingress/rate_limit.rs +538 -0
  75. package/rust/ingress/terminal.rs +114 -0
  76. package/rust/ingress/tests.rs +566 -0
  77. package/rust/ingress/time.rs +70 -0
  78. package/rust/json/fast_schema/capture.rs +336 -0
  79. package/rust/json/fast_schema/compile.rs +556 -0
  80. package/rust/json/fast_schema/cursor.rs +368 -0
  81. package/rust/json/fast_schema/email.rs +282 -0
  82. package/rust/json/fast_schema/errors.rs +247 -0
  83. package/rust/json/fast_schema/mod.rs +57 -0
  84. package/rust/json/fast_schema/tests.rs +1143 -0
  85. package/rust/json/fast_schema/types.rs +140 -0
  86. package/rust/json/fast_schema/validate.rs +931 -0
  87. package/rust/json/json_ops.rs +121 -0
  88. package/rust/json/json_schema.rs +837 -0
  89. package/rust/json/json_ser.rs +881 -0
  90. package/rust/json/mod.rs +19 -0
  91. package/rust/json/napi_marshal.rs +318 -0
  92. package/rust/json/patch/api.rs +60 -0
  93. package/rust/json/patch/engine.rs +153 -0
  94. package/rust/json/patch/mod.rs +47 -0
  95. package/rust/json/patch/ops.rs +270 -0
  96. package/rust/json/patch/pointer.rs +49 -0
  97. package/rust/json/patch/tests.rs +499 -0
  98. package/rust/lib.rs +72 -0
  99. package/rust/metrics/api.rs +148 -0
  100. package/rust/metrics/mod.rs +21 -0
  101. package/rust/metrics/registry.rs +961 -0
  102. package/rust/panic_safety.rs +40 -0
  103. package/rust/payload/compress.rs +512 -0
  104. package/rust/payload/mod.rs +13 -0
  105. package/rust/payload/sse.rs +214 -0
  106. package/rust/payload/template.rs +253 -0
  107. package/rust/payload/websocket.rs +69 -0
  108. package/rust/payload/ws_frames.rs +354 -0
  109. package/rust/proptest_suite.rs +139 -0
  110. package/rust/selection.rs +95 -0
  111. package/rust/src/bin/native-bench.rs +495 -0
  112. package/rust/test_support.rs +71 -0
  113. package/rust/util/batch/api.rs +266 -0
  114. package/rust/util/batch/core.rs +473 -0
  115. package/rust/util/batch/mod.rs +23 -0
  116. package/rust/util/batch/tests.rs +445 -0
  117. package/rust/util/batch_core.rs +184 -0
  118. package/rust/util/bytes.rs +407 -0
  119. package/rust/util/mod.rs +34 -0
  120. package/rust/util/packed.rs +473 -0
  121. package/rust/util/text.rs +130 -0
  122. package/rust/util/threadpool.rs +128 -0
  123. package/rust/util/validation.rs +419 -0
  124. package/rust-toolchain.toml +17 -0
  125. package/scripts/postinstall.mjs +199 -0
  126. package/src/ingress/constants.ts +1 -1
  127. package/src/ingress/context.ts +2 -2
  128. package/src/ingress/decode/baked-result.ts +13 -13
  129. package/src/ingress/decode/fast-result.ts +17 -17
  130. package/src/ingress/errors.ts +7 -7
  131. package/src/ingress/headers/baked-templates.ts +1 -1
  132. package/src/ingress/headers/fast-templates.ts +3 -3
  133. package/src/ingress/index.ts +1 -1
  134. package/src/ingress/native-route.ts +9 -5
  135. package/src/ingress/options.ts +1 -1
  136. package/src/ingress/packing/gather-raw-headers.ts +1 -1
  137. package/src/ingress/packing/route-wire.ts +1 -5
  138. package/src/ingress/response/error-bodies.ts +5 -5
  139. package/src/ingress/response/terminal.ts +1 -1
  140. package/src/ingress/routes/echo.ts +4 -4
  141. package/src/ingress/routes/fallback.ts +2 -2
  142. package/src/ingress/routes/head.ts +2 -2
  143. package/src/ingress/routes/native.ts +2 -1
  144. package/src/ingress/routes/options.ts +1 -1
  145. package/src/ingress/routes/read.ts +2 -2
  146. package/src/ingress/server.ts +10 -6
  147. package/src/ingress/status.ts +7 -7
  148. package/src/integration/batch.ts +1 -1
  149. package/src/integration/index.ts +9 -9
  150. package/src/integration/pipeline.ts +6 -6
  151. package/src/integration/websocket.ts +2 -2
  152. package/src/loader/index.ts +1 -5
  153. package/src/loader/ops.ts +1 -1
  154. package/src/native/ffi/build/codecs.ts +104 -8
  155. package/src/native/ffi/build/compress.ts +14 -10
  156. package/src/native/ffi/build/instances.ts +40 -3
  157. package/src/native/ffi/build/metrics.ts +224 -0
  158. package/src/native/ffi/build/parse.ts +59 -23
  159. package/src/native/ffi/build/util.ts +14 -1
  160. package/src/native/ffi/build.ts +2 -0
  161. package/src/native/ffi/selftest.ts +10 -3
  162. package/src/native/ffi/types.ts +111 -9
  163. package/src/native/ffi.ts +87 -1
  164. package/src/native/index.ts +1 -1
  165. package/src/native/loader.ts +2 -2
  166. package/src/native/types/instances.ts +34 -0
  167. package/src/native/types.ts +11 -0
  168. package/src/runtime/codec.ts +53 -9
  169. package/src/runtime/types.ts +7 -0
  170. package/src/rust-ffi/batch/build.ts +16 -9
  171. package/src/rust-ffi/index.ts +7 -6
  172. package/src/rust-ffi/options.ts +1 -3
  173. package/src/rust-ffi/proven.ts +3 -3
  174. package/src/rust-ffi/scalar/crypto.ts +1 -1
  175. package/src/rust-ffi/scalar/factories.ts +73 -0
  176. package/src/rust-ffi/scalar/hashing.ts +1 -1
  177. package/src/rust-ffi/scalar/http.ts +17 -11
  178. package/src/rust-ffi/scalar/index.ts +2 -0
  179. package/src/rust-ffi/scalar/interface.ts +20 -0
  180. package/src/rust-ffi/scalar/payload.ts +11 -3
  181. package/src/rust-ffi/scalar/validation.ts +121 -0
  182. package/src/selection.ts +1 -1
  183. package/src/shared/bytes.ts +5 -4
  184. package/src/shared/codec.ts +18 -3
@@ -0,0 +1,247 @@
1
+ // rust/json/fast_schema/errors.rs — Validation context + error collection.
2
+ //
3
+ // The fast path's single `validate` walk supports two modes via `Ctx`:
4
+ // - bool mode (`errors: None`) — zero-allocation, matches the original
5
+ // `is_valid_bytes` hot path used by the ingress.
6
+ // - detailed (`errors: Some(..)`) — collects `SchemaError`s carrying an
7
+ // instance JSON pointer, a schema JSON pointer, the failing keyword, and a
8
+ // human-readable message.
9
+ //
10
+ // `suppress` silences recording during combinator branch checks (anyOf/oneOf/
11
+ // not/if-guard) whose internal outcome is not itself the cause of failure — a
12
+ // failing branch is only a cause if the whole combinator fails, at which point
13
+ // a summary error is recorded instead.
14
+
15
+ /// Maximum JSON nesting depth accepted by the fast path. Matches the default
16
+ /// recursion limits of sonic-rs (the ingress `json_valid_bytes` gate) and
17
+ /// serde_json (the DOM fallback), so rejecting deeper documents keeps the fast
18
+ /// path byte-parity with the reference AND prevents hostile deeply-nested JSON
19
+ /// from exhausting the native stack (a stack overflow is an UNCATCHABLE process
20
+ /// abort — `panic = "unwind"` + napi `catch_unwind` do NOT catch it).
21
+ pub(crate) const MAX_DEPTH: u32 = 128;
22
+
23
+ /// A single validation error, mirroring the field structure of
24
+ /// `jsonschema::ValidationError` (instance path, schema path, keyword, message)
25
+ /// but with our own message wording.
26
+ #[derive(Clone, Debug, PartialEq, Eq)]
27
+ pub struct SchemaError {
28
+ /// RFC 6901 JSON pointer to the failing instance value ("" = root).
29
+ pub(crate) instance_path: String,
30
+ /// JSON pointer into the schema at the failing keyword.
31
+ pub(crate) schema_path: String,
32
+ /// The failing keyword (e.g. "type", "pattern", "required").
33
+ pub(crate) keyword: String,
34
+ /// Human-readable failure message.
35
+ pub(crate) message: String,
36
+ }
37
+
38
+ /// One segment of the instance path being validated.
39
+ #[derive(Clone)]
40
+ pub(crate) enum PathStep {
41
+ Key(Box<[u8]>),
42
+ Index(usize),
43
+ }
44
+
45
+ pub(crate) struct Ctx {
46
+ /// `None` = bool mode (zero-alloc). `Some` = detailed mode.
47
+ pub(crate) errors: Option<Vec<SchemaError>>,
48
+ /// Maximum number of errors to record (1 for validate-first-error).
49
+ pub(crate) max_errors: usize,
50
+ /// When true, `record` is a no-op (combinator branch checks).
51
+ pub(crate) suppress: bool,
52
+ /// Instance path stack (maintained in detailed mode AND capture mode).
53
+ pub(crate) path: Vec<PathStep>,
54
+ /// Current recursion depth of the validate walk (guarded by `MAX_DEPTH`).
55
+ pub(crate) depth: u32,
56
+ /// One-pass derive capture (None = disabled; the normal hot paths).
57
+ pub(crate) capture: Option<super::capture::Capture>,
58
+ /// Root input pointer for the current walk. Capture only fires on the ROOT
59
+ /// cursor (sub-scan cursors point into sub-slices and are ignored), so
60
+ /// captured byte ranges are always absolute into the original input.
61
+ pub(crate) input_ptr: *const u8,
62
+ }
63
+
64
+ impl Ctx {
65
+ pub(crate) fn bool_mode() -> Self {
66
+ Self {
67
+ errors: None,
68
+ max_errors: usize::MAX,
69
+ suppress: false,
70
+ path: Vec::new(),
71
+ depth: 0,
72
+ capture: None,
73
+ input_ptr: std::ptr::null(),
74
+ }
75
+ }
76
+
77
+ pub(crate) fn detailed(max_errors: usize) -> Self {
78
+ Self {
79
+ errors: Some(Vec::new()),
80
+ max_errors,
81
+ suppress: false,
82
+ path: Vec::new(),
83
+ depth: 0,
84
+ capture: None,
85
+ input_ptr: std::ptr::null(),
86
+ }
87
+ }
88
+
89
+ #[inline]
90
+ pub(crate) fn is_detailed(&self) -> bool {
91
+ self.errors.is_some()
92
+ }
93
+
94
+ #[inline]
95
+ pub(crate) fn record(
96
+ &mut self,
97
+ schema_base: &str,
98
+ keyword: &str,
99
+ message: impl FnOnce() -> String,
100
+ ) {
101
+ if self.suppress {
102
+ return;
103
+ }
104
+ let Some(errors) = &mut self.errors else {
105
+ return;
106
+ };
107
+ if errors.len() >= self.max_errors {
108
+ return;
109
+ }
110
+ let schema_path = if schema_base.is_empty() {
111
+ format!("/{keyword}")
112
+ } else {
113
+ format!("{schema_base}/{keyword}")
114
+ };
115
+ errors.push(SchemaError {
116
+ instance_path: build_pointer(&self.path),
117
+ schema_path,
118
+ keyword: keyword.to_string(),
119
+ message: message(),
120
+ });
121
+ }
122
+
123
+ /// Run `f` with error recording suppressed (combinator branch checks).
124
+ pub(crate) fn with_suppressed<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
125
+ let saved = self.suppress;
126
+ self.suppress = true;
127
+ let r = f(self);
128
+ self.suppress = saved;
129
+ r
130
+ }
131
+
132
+ /// Enter one nesting level of the validate walk. Returns false once the
133
+ /// walk exceeds `MAX_DEPTH`; the caller must then fail validation WITHOUT
134
+ /// recursing further (see `FastNode::validate`).
135
+ #[inline]
136
+ pub(crate) fn enter_depth(&mut self) -> bool {
137
+ self.depth += 1;
138
+ self.depth <= MAX_DEPTH
139
+ }
140
+
141
+ /// Leave one nesting level of the validate walk (mirrors `enter_depth`).
142
+ #[inline]
143
+ pub(crate) fn leave_depth(&mut self) {
144
+ self.depth -= 1;
145
+ }
146
+
147
+ #[inline]
148
+ pub(crate) fn push_key(&mut self, key: &[u8]) {
149
+ if self.errors.is_some() {
150
+ self.path
151
+ .push(PathStep::Key(key.to_vec().into_boxed_slice()));
152
+ }
153
+ }
154
+
155
+ #[inline]
156
+ pub(crate) fn push_idx(&mut self, idx: usize) {
157
+ if self.errors.is_some() {
158
+ self.path.push(PathStep::Index(idx));
159
+ }
160
+ }
161
+
162
+ #[inline]
163
+ pub(crate) fn pop(&mut self) {
164
+ if self.errors.is_some() {
165
+ self.path.pop();
166
+ }
167
+ }
168
+
169
+ /// Descend the capture trie for an object member key (capture mode only).
170
+ #[inline]
171
+ pub(crate) fn capture_enter_key(&mut self, data_ptr: *const u8, key: &[u8]) {
172
+ let Some(cap) = &mut self.capture else {
173
+ return;
174
+ };
175
+ if self.suppress || data_ptr != self.input_ptr {
176
+ return;
177
+ }
178
+ cap.enter_key(key);
179
+ }
180
+
181
+ /// Leave the current capture-trie nesting level.
182
+ #[inline]
183
+ pub(crate) fn capture_exit_key(&mut self, data_ptr: *const u8) {
184
+ let Some(cap) = &mut self.capture else {
185
+ return;
186
+ };
187
+ if self.suppress || data_ptr != self.input_ptr {
188
+ return;
189
+ }
190
+ cap.exit_key();
191
+ }
192
+
193
+ /// Record a scalar member value after it validated (capture mode only).
194
+ #[inline]
195
+ pub(crate) fn capture_record_value(&mut self, data_ptr: *const u8, start: usize, end: usize) {
196
+ let Some(cap) = &mut self.capture else {
197
+ return;
198
+ };
199
+ if self.suppress || data_ptr != self.input_ptr {
200
+ return;
201
+ }
202
+ cap.record_value(start, end);
203
+ }
204
+
205
+ /// Record an array element count after it validated (capture mode only).
206
+ #[inline]
207
+ pub(crate) fn capture_record_length(&mut self, data_ptr: *const u8, count: usize) {
208
+ let Some(cap) = &mut self.capture else {
209
+ return;
210
+ };
211
+ if self.suppress || data_ptr != self.input_ptr {
212
+ return;
213
+ }
214
+ cap.record_length(count);
215
+ }
216
+
217
+ pub(crate) fn into_errors(self) -> Vec<SchemaError> {
218
+ self.errors.unwrap_or_default()
219
+ }
220
+ }
221
+
222
+ /// Build an RFC 6901 JSON pointer from the path stack (`~` -> `~0`, `/` -> `~1`).
223
+ fn build_pointer(path: &[PathStep]) -> String {
224
+ if path.is_empty() {
225
+ return String::new();
226
+ }
227
+ let mut out = String::new();
228
+ for step in path {
229
+ match step {
230
+ PathStep::Key(k) => {
231
+ out.push('/');
232
+ for &b in k.iter() {
233
+ match b {
234
+ b'~' => out.push_str("~0"),
235
+ b'/' => out.push_str("~1"),
236
+ _ => out.push(b as char),
237
+ }
238
+ }
239
+ }
240
+ PathStep::Index(i) => {
241
+ out.push('/');
242
+ out.push_str(&i.to_string());
243
+ }
244
+ }
245
+ }
246
+ out
247
+ }
@@ -0,0 +1,57 @@
1
+ // rust/json/fast_schema/mod.rs — Zero-DOM JSON Schema validation.
2
+ //
3
+ // Why this exists: `jsonschema::Validator::is_valid` requires a
4
+ // `serde_json::Value`, so the default path parses every document into a heap
5
+ // DOM. We measured that DOM build at ~95% of the validation cost for the bench
6
+ // fixture. This module instead compiles the same schema into a tiny structural
7
+ // AST (`types.rs`) and validates documents with a single pass over the raw
8
+ // bytes (`cursor.rs` + `validate.rs`) — no `serde_json::Value`, no per-key
9
+ // `String`s, no `BTreeMap`/`IndexMap`.
10
+ //
11
+ // Coverage (draft-07): type, enum, const, multipleOf, minimum/maximum,
12
+ // exclusiveMinimum/exclusiveMaximum (numeric), minLength/maxLength, pattern
13
+ // (fancy-regex, same engine as jsonschema), items (single + tuple),
14
+ // additionalItems, minItems/maxItems, uniqueItems, contains, minProperties/
15
+ // maxProperties, required, properties, patternProperties, additionalProperties,
16
+ // dependencies (array form), if/then/else, allOf, anyOf, oneOf, not, in-document
17
+ // $ref, format: "email".
18
+ //
19
+ // Versatility is preserved: `compile` (`compile.rs`) returns `Err(())` for any
20
+ // schema using a keyword outside the supported subset (propertyNames,
21
+ // dependencies-with-schema, boolean exclusive bounds, other formats,
22
+ // remote/dynamic/anchor refs, non-draft-07 `$schema`). The caller then falls
23
+ // back to the full `jsonschema` crate path, so the fast path only ever runs on
24
+ // schemas it can prove equivalent to the reference semantics.
25
+ //
26
+ // Validation runs in two modes (validate.rs): bool mode (`Ctx::bool_mode`) is
27
+ // allocation-free for the core subset; detailed mode (`validate_errors`)
28
+ // collects `SchemaError`s carrying an instance JSON pointer, schema pointer,
29
+ // keyword, and message.
30
+ //
31
+ // Limitations (accepted): documents must be well-formed UTF-8 JSON; malformed
32
+ // escapes/UTF-8 that the reference parser rejects may slip through. The
33
+ // fallback DOM path is authoritative for all inputs.
34
+ //
35
+ // Module split:
36
+ // - types.rs the compiled AST (FastNode + per-kind constraint structs)
37
+ // - cursor.rs byte-level JSON cursor + string/unicode helpers
38
+ // - compile.rs schema (serde_json::Value) → AST compiler
39
+ // - validate.rs AST + raw bytes → validation (bool or detailed errors)
40
+ // - errors.rs error sink/context + SchemaError (instance/schema paths)
41
+ // - tests.rs fast-vs-jsonschema-crate parity tests
42
+
43
+ mod capture;
44
+ mod compile;
45
+ mod cursor;
46
+ mod email;
47
+ mod errors;
48
+ mod types;
49
+ mod validate;
50
+
51
+ #[cfg(test)]
52
+ mod tests;
53
+
54
+ pub use self::capture::{parse_target, Capture, CaptureKind, TargetPath};
55
+ pub use self::compile::compile;
56
+ pub use self::errors::SchemaError;
57
+ pub use self::types::FastNode;