schema-shield 1.0.4 → 1.1.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/README.md +419 -919
- package/dist/formats.d.ts.map +1 -1
- package/dist/index.d.ts +33 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1250 -391
- package/dist/index.min.js +2 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +1250 -391
- package/dist/keywords/array-keywords.d.ts.map +1 -1
- package/dist/keywords/number-keywords.d.ts.map +1 -1
- package/dist/keywords/object-keywords.d.ts +7 -1
- package/dist/keywords/object-keywords.d.ts.map +1 -1
- package/dist/keywords/other-keywords.d.ts +17 -1
- package/dist/keywords/other-keywords.d.ts.map +1 -1
- package/dist/keywords/string-keywords.d.ts.map +1 -1
- package/dist/utils/deep-freeze.d.ts.map +1 -1
- package/dist/utils/main-utils.d.ts +8 -4
- package/dist/utils/main-utils.d.ts.map +1 -1
- package/lib/formats.ts +36 -10
- package/lib/index.ts +1157 -155
- package/lib/keywords/array-keywords.ts +18 -3
- package/lib/keywords/number-keywords.ts +19 -5
- package/lib/keywords/object-keywords.ts +59 -61
- package/lib/keywords/other-keywords.ts +205 -192
- package/lib/keywords/string-keywords.ts +51 -8
- package/lib/types.ts +2 -2
- package/lib/utils/deep-freeze.ts +6 -4
- package/lib/utils/main-utils.ts +97 -42
- package/package.json +9 -10
package/README.md
CHANGED
|
@@ -1,1158 +1,658 @@
|
|
|
1
1
|
# SchemaShield 🛡️
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Secure, Stack-Safe, Offline, and Domain-Aware JSON Schema Validation.**
|
|
4
4
|
|
|
5
|
-
SchemaShield is a
|
|
5
|
+
SchemaShield is a high-performance JSON Schema interpreter for JavaScript applications that need predictable validation without runtime code generation or hidden network access.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
>
|
|
9
|
-
> 📊 **#3 fastest on Node.js** — 70% ajv speed, 50x faster than others
|
|
7
|
+
In the latest benchmark run, SchemaShield reached approximately **80% of AJV's throughput** and ran approximately **15% faster than schemasafe**.
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
- No `eval()` or `new Function()`
|
|
10
|
+
- Synchronous, offline validation
|
|
11
|
+
- No implicit fetch, HTTP, DNS, or network I/O
|
|
12
|
+
- First-class validation for JavaScript objects and domain rules
|
|
13
|
+
- Structured error paths, causes, and trees
|
|
14
|
+
- Zero runtime dependencies
|
|
15
|
+
- Verified CJS, ESM, browser, and TypeScript declaration packages
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
type: "object",
|
|
18
|
-
properties: {
|
|
19
|
-
name: { type: "string" },
|
|
20
|
-
age: { type: "number" }
|
|
21
|
-
}
|
|
22
|
-
});
|
|
17
|
+
## Quick Start
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
// { valid: true, data: { name: "John", age: 30 } }
|
|
19
|
+
Install SchemaShield:
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
```bash
|
|
22
|
+
npm install schema-shield
|
|
29
23
|
```
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- [Quick Start](#quick-start)
|
|
34
|
-
- [Why SchemaShield?](#why-schemashield)
|
|
35
|
-
- [Comparison with Other Approaches](#comparison-with-other-approaches)
|
|
36
|
-
- [Usage](#usage)
|
|
37
|
-
- [Performance](#performance)
|
|
38
|
-
- [Understanding Performance Context](#understanding-performance-context)
|
|
39
|
-
- [1. Modern Runtimes (Bun)](#1-modern-runtimes-bun)
|
|
40
|
-
- [2. Standard Runtimes (Node.js)](#2-standard-runtimes-nodejs)
|
|
41
|
-
- [Edge \& Serverless Ready](#edge--serverless-ready)
|
|
42
|
-
- [Features](#features)
|
|
43
|
-
- [Security Philosophy: Hermetic Validation](#security-philosophy-hermetic-validation)
|
|
44
|
-
- [Why No Remote References?](#why-no-remote-references)
|
|
45
|
-
- [No Code Generation](#no-code-generation)
|
|
46
|
-
- [Error Handling](#error-handling)
|
|
47
|
-
- [Adding Custom Types](#adding-custom-types)
|
|
48
|
-
- [Method Signature](#method-signature)
|
|
49
|
-
- [Example: Adding a Custom Type](#example-adding-a-custom-type)
|
|
50
|
-
- [Adding Custom Formats](#adding-custom-formats)
|
|
51
|
-
- [Method Signature](#method-signature-1)
|
|
52
|
-
- [Example: Adding a Custom Format](#example-adding-a-custom-format)
|
|
53
|
-
- [Adding Custom Keywords](#adding-custom-keywords)
|
|
54
|
-
- [Method Signature](#method-signature-2)
|
|
55
|
-
- [Example: Adding a Custom Keyword](#example-adding-a-custom-keyword)
|
|
56
|
-
- [Complex example: Adding a Custom Keyword that uses the instance](#complex-example-adding-a-custom-keyword-that-uses-the-instance)
|
|
57
|
-
- [Supported Formats](#supported-formats)
|
|
58
|
-
- [Validating Runtime Objects](#validating-runtime-objects)
|
|
59
|
-
- [More on Error Handling](#more-on-error-handling)
|
|
60
|
-
- [ValidationError Properties](#validationerror-properties)
|
|
61
|
-
- [Get the path to the error location](#get-the-path-to-the-error-location)
|
|
62
|
-
- [Get the full error chain as a tree](#get-the-full-error-chain-as-a-tree)
|
|
63
|
-
- [Get the cause of the error](#get-the-cause-of-the-error)
|
|
64
|
-
- [Immutable Mode](#immutable-mode)
|
|
65
|
-
- [TypeScript Support](#typescript-support)
|
|
66
|
-
- [Known Limitations](#known-limitations)
|
|
67
|
-
- [1. Dynamic ID Scope Resolution (Scope Alteration)](#1-dynamic-id-scope-resolution-scope-alteration)
|
|
68
|
-
- [2. Unicode Length Validation](#2-unicode-length-validation)
|
|
69
|
-
- [3. Conservative Equality Path (Performance Trade-off)](#3-conservative-equality-path-performance-trade-off)
|
|
70
|
-
- [Testing](#testing)
|
|
71
|
-
- [Contribute](#contribute)
|
|
72
|
-
- [Legal](#legal)
|
|
73
|
-
|
|
74
|
-
## Why SchemaShield?
|
|
75
|
-
|
|
76
|
-
Most validators optimize for "operations per second" in synthetic benchmarks, often sacrificing security, stability, or maintainability. SchemaShield optimizes for the **Total Cost of Ownership** of your software.
|
|
77
|
-
|
|
78
|
-
| Feature | JIT Compilers | SchemaShield | Why it matters |
|
|
79
|
-
| :------------------- | :------------------------------------------------------------------- | :--------------------------------------------------------- | :-------------------------------------------------------------------------------------- |
|
|
80
|
-
| **Security Model** | **Reactive**<br>(Requires config to prevent Prototype Pollution/DoS) | **Preventive**<br>(Hermetic & Immutable by design) | "Secure by default" prevents human error and vulnerabilities in production. |
|
|
81
|
-
| **Debug Experience** | **Black Box**<br>(Debugs opaque generated strings/code) | **White Box**<br>(Standard JS stack traces) | Drastically reduces time-to-fix when validation fails in complex logic. |
|
|
82
|
-
| **Stability** | **Recursive**<br>(Risk of Stack Overflow on deep data) | **Flat Loop**<br>(Constant memory usage) | Protects your server availability against malicious deep-payload attacks. |
|
|
83
|
-
| **Domain Logic** | **Disconnected**<br>(Hard to validate Class Instances/State) | **Integrated**<br>(Native `instanceof` & state validation) | Unifies DTO validation and Business Rules, eliminating "spaghetti code" in controllers. |
|
|
84
|
-
| **Ecosystem** | **Fragmented**<br>(Requires plugins for errors/formats) | **Cohesive**<br>(Advanced error trees & formats built-in) | Reduces dependency fatigue and maintenance burden. |
|
|
85
|
-
|
|
86
|
-
> **The Trade-off:** While JIT compilers can be faster in raw throughput on V8 (Node.js), SchemaShield offers a balanced architecture where validation is never the bottleneck in real-world I/O bound applications (Database/Network APIs).
|
|
87
|
-
|
|
88
|
-
### Comparison with Other Approaches
|
|
89
|
-
|
|
90
|
-
| Feature | SchemaShield | JIT Compilers | Classic Interpreters |
|
|
91
|
-
| :---------------------------- | :-------------------------------------------------- | :------------------------------- | :-------------------- |
|
|
92
|
-
| **Architecture** | **Secure Flat Interpreter** | JIT Compiler (eval/new Function) | Recursive Interpreter |
|
|
93
|
-
| **Relative Speed** | **High (~70%)** | Reference (100%) | Low (1% - 20%) |
|
|
94
|
-
| **CSP Compliance** | **Native (100% Safe)** | Requires Build Config | Variable |
|
|
95
|
-
| **Edge Ready** | **Native** | Complex Setup | Variable |
|
|
96
|
-
| **Stack Safety** | **Minimized stack usage (non-recursive core loop)** | Risk of Overflow | Risk of Overflow |
|
|
97
|
-
| **Class Instance Validation** | **Native** | No | No |
|
|
98
|
-
| **Debug Experience** | **Clean Stack Trace** | Opaque Generated Code | Variable |
|
|
99
|
-
|
|
100
|
-
> **Note:** Stack Safety refers to the risk of stack overflow errors when validating deeply nested data structures. SchemaShield's flat interpreter design minimizes this risk in its core validation loop. Keep in mind that custom keywords can still introduce recursion if not implemented carefully.
|
|
101
|
-
|
|
102
|
-
## Usage
|
|
103
|
-
|
|
104
|
-
**1. Install the package**
|
|
25
|
+
Or with Bun:
|
|
105
26
|
|
|
106
27
|
```bash
|
|
107
|
-
npm install schema-shield
|
|
108
|
-
# or
|
|
109
28
|
bun add schema-shield
|
|
110
29
|
```
|
|
111
30
|
|
|
112
|
-
|
|
31
|
+
Compile a schema and reuse the validator:
|
|
113
32
|
|
|
114
33
|
```javascript
|
|
115
34
|
import { SchemaShield } from "schema-shield";
|
|
116
|
-
// or
|
|
117
|
-
const { SchemaShield } = require("schema-shield");
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
**3. Instantiate the SchemaShield class**
|
|
121
|
-
|
|
122
|
-
```javascript
|
|
123
|
-
const schemaShield = new SchemaShield();
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
- **`immutable`** (optional): Set to `true` to ensure that input data remains unmodified during validation. Default is `false` for better performance.
|
|
127
|
-
- **`failFast`** (optional): Set to `false` to receive detailed error objects on validation failure. Default is `true` for lightweight failure indication.
|
|
128
|
-
|
|
129
|
-
**3.5. Add custom types, keywords, and formats (optional)**
|
|
130
|
-
|
|
131
|
-
```javascript
|
|
132
|
-
schemaShield.addType("customType", (data) => {
|
|
133
|
-
// Custom type validation logic
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
schemaShield.addFormat("customFormat", (data) => {
|
|
137
|
-
// Custom format validation logic
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
schemaShield.addKeyword(
|
|
141
|
-
"customKeyword",
|
|
142
|
-
(schema, data, defineError, instance) => {
|
|
143
|
-
// Custom keyword validation logic
|
|
144
|
-
}
|
|
145
|
-
);
|
|
146
|
-
```
|
|
147
35
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
```javascript
|
|
151
|
-
const schema = {
|
|
36
|
+
const validateUser = new SchemaShield({ failFast: false }).compile({
|
|
152
37
|
type: "object",
|
|
153
38
|
properties: {
|
|
154
|
-
name: { type: "string" },
|
|
155
|
-
age: { type: "number" }
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
**5. Validate some data**
|
|
163
|
-
|
|
164
|
-
```javascript
|
|
165
|
-
const data = {
|
|
166
|
-
name: "John Doe",
|
|
167
|
-
age: 30
|
|
168
|
-
};
|
|
39
|
+
name: { type: "string", minLength: 1 },
|
|
40
|
+
age: { type: "number", minimum: 18 }
|
|
41
|
+
},
|
|
42
|
+
required: ["name", "age"],
|
|
43
|
+
additionalProperties: false
|
|
44
|
+
});
|
|
169
45
|
|
|
170
|
-
const
|
|
46
|
+
const validResult = validateUser({ name: "Ada", age: 37 });
|
|
47
|
+
// { valid: true, data: { name: "Ada", age: 37 }, error: null }
|
|
171
48
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
} else {
|
|
175
|
-
console.error("Validation error:", validationResult.error);
|
|
176
|
-
}
|
|
49
|
+
const invalidResult = validateUser({ name: "Ada", age: 16 });
|
|
50
|
+
// { valid: false, data: { name: "Ada", age: 16 }, error: ValidationError }
|
|
177
51
|
```
|
|
178
52
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
- `data`: The validated (and potentially modified) data.
|
|
182
|
-
- `error`: A `ValidationError` instance if validation failed (when `failFast: false`), `true` if validation failed in fail-fast mode, otherwise `null`.
|
|
183
|
-
- `valid`: true if validation was successful, otherwise false.
|
|
53
|
+
> The default `failFast: true` mode returns `error: true` when validation fails. With `failFast: false`, built-in failures and custom keyword failures created with `defineError()` return a detailed `ValidationError` with paths, causes, and an error tree. A custom keyword that returns `true` directly still produces `error: true`. Use `defineError()` for detailed errors.
|
|
184
54
|
|
|
185
|
-
|
|
55
|
+
## Contents
|
|
186
56
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
57
|
+
- [Why SchemaShield?](#why-schemashield)
|
|
58
|
+
- [Architecture Comparison](#architecture-comparison)
|
|
59
|
+
- [Performance](#performance)
|
|
60
|
+
- [Offline by Design](#offline-by-design)
|
|
61
|
+
- [Domain-Aware Validation](#domain-aware-validation)
|
|
62
|
+
- [Edge, Serverless, and Package Support](#edge-serverless-and-package-support)
|
|
63
|
+
- [Core API](#core-api)
|
|
64
|
+
- [Errors and Debugging](#errors-and-debugging)
|
|
65
|
+
- [Extensibility](#extensibility)
|
|
66
|
+
- [Supported Formats and Compatibility](#supported-formats-and-compatibility)
|
|
67
|
+
- [Immutable Mode and Known Limitations](#immutable-mode-and-known-limitations)
|
|
68
|
+
- [Testing](#testing)
|
|
69
|
+
- [Contribute](#contribute)
|
|
70
|
+
- [Legal](#legal)
|
|
197
71
|
|
|
198
|
-
|
|
72
|
+
## Why SchemaShield?
|
|
199
73
|
|
|
200
|
-
|
|
74
|
+
SchemaShield gives developers competitive performance without surrendering control of the validation runtime.
|
|
201
75
|
|
|
202
|
-
|
|
76
|
+
JIT validators generate executable code to maximize throughput. SchemaShield uses an interpreter architecture that stays inside ordinary JavaScript execution, works in restrictive CSP environments, and keeps validation visible through standard stack traces and structured errors.
|
|
203
77
|
|
|
204
|
-
|
|
205
|
-
- **Predictable Throughput:** Consistent performance regardless of schema complexity.
|
|
78
|
+
Choose SchemaShield when your application needs:
|
|
206
79
|
|
|
207
|
-
|
|
80
|
+
- **Secure-by-design execution:** No `eval()`, `new Function()`, or validator-initiated network access.
|
|
81
|
+
- **Competitive throughput:** In the latest benchmark run, SchemaShield reached approximately 80% of AJV's throughput and ran approximately 15% faster than schemasafe.
|
|
82
|
+
- **Stack-safe validation:** Schemas beyond the fixed compile-depth limit are rejected, while recursive validation paths that exceed `maxDepth` fail with a controlled error instead of a stack overflow.
|
|
83
|
+
- **Domain-aware rules:** Validate class instances, Dates, application state, and custom business logic through the same API.
|
|
84
|
+
- **Inspectable failures:** Follow schema paths, instance paths, root causes, and complete error trees.
|
|
85
|
+
- **A cohesive runtime:** Built-in formats, custom types, custom keywords, immutable mode, and zero runtime dependencies.
|
|
208
86
|
|
|
209
|
-
|
|
87
|
+
## Architecture Comparison
|
|
210
88
|
|
|
211
|
-
|
|
89
|
+
SchemaShield was designed for developers who want strong performance, predictable execution, and direct integration with real application domains.
|
|
212
90
|
|
|
213
|
-
|
|
|
214
|
-
|
|
|
215
|
-
| **
|
|
216
|
-
|
|
|
217
|
-
|
|
|
218
|
-
|
|
|
91
|
+
| Capability | SchemaShield | JIT / Codegen Validators | Conventional Interpreters |
|
|
92
|
+
| :------------------------------ | :-------------------------------------------------------------------- | :----------------------------------------- | :-------------------------------------- |
|
|
93
|
+
| **Runtime execution** | Interpreter without runtime code generation | Generated executable code | Interpreted validation |
|
|
94
|
+
| **`eval()` / `new Function()`** | Not used | Common to the codegen model | Usually not required |
|
|
95
|
+
| **Network behavior** | Offline with no implicit network access | Depends on library and configuration | Varies |
|
|
96
|
+
| **Stack behavior** | Controlled compile and runtime depth errors instead of stack overflow | Depends on generated execution | Recursive traversal is common |
|
|
97
|
+
| **Debugging** | Standard JavaScript stacks and structured error trees | Generated code can obscure validation flow | Varies |
|
|
98
|
+
| **Domain integration** | Class instances, runtime objects, and custom business rules | Extension model varies | Usually requires additional integration |
|
|
99
|
+
| **Extensibility** | Types, formats, keywords, and error tooling in one API | Frequently configuration or plugin driven | Varies |
|
|
219
100
|
|
|
220
|
-
|
|
101
|
+
## Performance
|
|
221
102
|
|
|
222
|
-
|
|
103
|
+
SchemaShield delivers high-performance interpreted validation, offline execution, stack safety, standard JavaScript debugging, and domain-aware extensibility without runtime code generation.
|
|
223
104
|
|
|
224
|
-
|
|
225
|
-
| :----------------- | :------------- | :------------------ |
|
|
226
|
-
| ajv | 100% | Reference (JIT) |
|
|
227
|
-
| @exodus/schemasafe | ~74% | Interpreter |
|
|
228
|
-
| **SchemaShield** | **~70%** | **Secure Standard** |
|
|
229
|
-
| jsonschema | ~1% | Legacy |
|
|
105
|
+
In the latest benchmark run:
|
|
230
106
|
|
|
231
|
-
|
|
107
|
+
- SchemaShield reached approximately **80% of AJV's throughput**.
|
|
108
|
+
- SchemaShield ran approximately **15% faster than schemasafe**.
|
|
232
109
|
|
|
233
|
-
|
|
110
|
+
These results position SchemaShield as a the second fastest interpreter.
|
|
234
111
|
|
|
235
|
-
|
|
112
|
+
> Benchmark results may vary by runtime, hardware, schema mix, and validator configuration. The benchmark results shown remain consistent across multiple runs on different environments.
|
|
236
113
|
|
|
237
|
-
|
|
238
|
-
- **CSP Compliant:** Works in environments where `eval()` and `new Function()` are banned for security.
|
|
239
|
-
- **Instant Startup:** No compilation overhead, minimizing "cold start" latency in Serverless functions.
|
|
114
|
+
## Offline by Design
|
|
240
115
|
|
|
241
|
-
|
|
116
|
+
SchemaShield keeps validation inside your application's trust boundary.
|
|
242
117
|
|
|
243
|
-
|
|
244
|
-
- Full support for internal references ($ref) and anchors ($id).
|
|
245
|
-
- No Code Generation for Enhanced Safety and Validation Flexibility.
|
|
246
|
-
- Custom type, keyword, and format validators.
|
|
247
|
-
- Runtime object validation: first-class support for business logic checks (type checking and schema validation).
|
|
248
|
-
- Immutable mode for data protection.
|
|
249
|
-
- Lightweight and fast.
|
|
250
|
-
- Easy to use and extend.
|
|
251
|
-
- No dependencies.
|
|
252
|
-
- TypeScript support.
|
|
118
|
+
SchemaShield itself performs no fetches, HTTP requests, DNS lookups, or other network I/O during schema compilation or validation. Validation behavior ships with your application and does not depend on a remote schema host remaining available or unchanged.
|
|
253
119
|
|
|
254
|
-
|
|
120
|
+
### No Remote References by Design
|
|
255
121
|
|
|
256
|
-
|
|
122
|
+
Remote `$ref` fetching is intentionally excluded from SchemaShield.
|
|
257
123
|
|
|
258
|
-
|
|
124
|
+
A validator that retrieves schemas at runtime introduces a network-capable component into the validation path. That expands the attack surface, creates an SSRF vector, adds DNS and availability dependencies, and allows a remote schema change to alter validation behavior without an application deployment.
|
|
259
125
|
|
|
260
|
-
|
|
126
|
+
SchemaShield removes that runtime dependency:
|
|
261
127
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
128
|
+
- No validator-initiated network requests
|
|
129
|
+
- No hidden schema downloads
|
|
130
|
+
- No DNS or remote-host dependency
|
|
131
|
+
- No remote schema substitution during validation
|
|
132
|
+
- No network latency inside the validation path
|
|
265
133
|
|
|
266
|
-
|
|
134
|
+
> Treat external schemas as code dependencies. Review them, version them, and incorporate every required target into the single root schema passed to `compile()`. Deploy that controlled schema with the application.
|
|
267
135
|
|
|
268
|
-
|
|
136
|
+
### No Runtime Code Generation
|
|
269
137
|
|
|
270
|
-
|
|
138
|
+
SchemaShield does not use `eval()` or `new Function()` to generate executable validators. This keeps validation compatible with strict Content Security Policy environments and lets custom validation functions work directly with JavaScript values, classes, and references.
|
|
271
139
|
|
|
272
|
-
|
|
140
|
+
```javascript
|
|
141
|
+
import { SchemaShield } from "schema-shield";
|
|
273
142
|
|
|
274
|
-
|
|
143
|
+
const schemaShield = new SchemaShield();
|
|
275
144
|
|
|
276
|
-
```javascript
|
|
277
145
|
schemaShield.addType("date-class", (data) => data instanceof Date);
|
|
278
|
-
|
|
146
|
+
|
|
279
147
|
class CustomDate extends Date {}
|
|
280
148
|
schemaShield.addType("custom-date-class", (data) => data instanceof CustomDate);
|
|
281
|
-
```
|
|
282
149
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
150
|
+
const validateDate = schemaShield.compile({
|
|
151
|
+
type: "object",
|
|
152
|
+
properties: {
|
|
153
|
+
createdAt: { type: "date-class" },
|
|
154
|
+
updatedAt: { type: "custom-date-class" }
|
|
155
|
+
},
|
|
156
|
+
required: ["createdAt", "updatedAt"]
|
|
157
|
+
});
|
|
286
158
|
|
|
287
|
-
|
|
288
|
-
|
|
159
|
+
validateDate({
|
|
160
|
+
createdAt: new Date(),
|
|
161
|
+
updatedAt: new CustomDate()
|
|
162
|
+
});
|
|
163
|
+
// { valid: true, data: { createdAt: Date, updatedAt: CustomDate }, error: null }
|
|
164
|
+
```
|
|
289
165
|
|
|
290
|
-
|
|
166
|
+
## Domain-Aware Validation
|
|
291
167
|
|
|
292
|
-
|
|
168
|
+
JSON Schema is commonly used for serialized JSON data. SchemaShield also validates live JavaScript objects, including class instances, Dates, and internal application state.
|
|
293
169
|
|
|
294
170
|
```javascript
|
|
295
171
|
import { SchemaShield } from "schema-shield";
|
|
296
172
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
properties: {
|
|
302
|
-
name: { type: "string" },
|
|
303
|
-
age: {
|
|
304
|
-
type: "number",
|
|
305
|
-
minimum: 18
|
|
306
|
-
}
|
|
173
|
+
class Project {
|
|
174
|
+
constructor(name, requiredSkills) {
|
|
175
|
+
this.name = name;
|
|
176
|
+
this.requiredSkills = requiredSkills;
|
|
307
177
|
}
|
|
308
|
-
}
|
|
178
|
+
}
|
|
309
179
|
|
|
310
|
-
|
|
180
|
+
class Employee {
|
|
181
|
+
constructor(name, skills) {
|
|
182
|
+
this.name = name;
|
|
183
|
+
this.skills = skills;
|
|
184
|
+
}
|
|
311
185
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
186
|
+
canJoin(project) {
|
|
187
|
+
return project.requiredSkills.every((skill) => this.skills.includes(skill));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
316
190
|
|
|
317
|
-
const
|
|
191
|
+
const shield = new SchemaShield({ failFast: false });
|
|
318
192
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
} else {
|
|
322
|
-
console.error("Validation error:", validationResult.error.message); // "Property is invalid"
|
|
193
|
+
shield.addType("project", (data) => data instanceof Project);
|
|
194
|
+
shield.addType("employee", (data) => data instanceof Employee);
|
|
323
195
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
```
|
|
196
|
+
shield.addKeyword("qualifiedAssignment", (schema, data, defineError) => {
|
|
197
|
+
if (!schema.qualifiedAssignment) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
330
200
|
|
|
331
|
-
|
|
201
|
+
if (!(data?.project instanceof Project)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
332
204
|
|
|
333
|
-
|
|
205
|
+
if (!(data?.employee instanceof Employee)) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
334
208
|
|
|
335
|
-
|
|
209
|
+
if (!data.employee.canJoin(data.project)) {
|
|
210
|
+
return defineError("Employee does not meet the project's requirements", {
|
|
211
|
+
data
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
336
215
|
|
|
337
|
-
|
|
216
|
+
const validateAssignment = shield.compile({
|
|
217
|
+
type: "object",
|
|
218
|
+
properties: {
|
|
219
|
+
project: { type: "project" },
|
|
220
|
+
employee: { type: "employee" }
|
|
221
|
+
},
|
|
222
|
+
required: ["project", "employee"],
|
|
223
|
+
qualifiedAssignment: true
|
|
224
|
+
});
|
|
338
225
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
(data: any): boolean;
|
|
342
|
-
}
|
|
226
|
+
const project = new Project("Website Redesign", ["HTML", "CSS", "JavaScript"]);
|
|
227
|
+
const employee = new Employee("Ada", ["HTML", "CSS"]);
|
|
343
228
|
|
|
344
|
-
|
|
229
|
+
const result = validateAssignment({ project, employee });
|
|
230
|
+
// { valid: false, data: { project, employee }, error: ValidationError }
|
|
345
231
|
```
|
|
346
232
|
|
|
347
|
-
|
|
348
|
-
- `validator`: A `TypeFunction` that takes a single argument `data` and returns a boolean value. The function should return `true` if the provided data is valid for the custom type, and `false` otherwise.
|
|
349
|
-
- `overwrite` (optional): Set to `true` to overwrite an existing type with the same name. Default is `false`. If set to `false` and a type with the same name already exists, an error will be thrown.
|
|
233
|
+
This keeps transport validation and domain rules in one validation system without converting live application objects into generated source code.
|
|
350
234
|
|
|
351
|
-
|
|
235
|
+
## Edge, Serverless, and Package Support
|
|
352
236
|
|
|
353
|
-
|
|
237
|
+
SchemaShield is designed for restrictive JavaScript environments and reusable validators.
|
|
354
238
|
|
|
355
|
-
|
|
356
|
-
|
|
239
|
+
- **Zero runtime dependencies**
|
|
240
|
+
- **Node.js 16.1 or later** for the CJS and ESM package entry points
|
|
241
|
+
- **No runtime code generation**, for environments that prohibit `eval()` and `new Function()`
|
|
242
|
+
- **Synchronous execution**, with no implicit network access
|
|
243
|
+
- **Verified package paths** for CJS, ESM, browser, and TypeScript declarations
|
|
244
|
+
- **Reusable validators** that can be compiled once and shared across requests
|
|
357
245
|
|
|
358
|
-
|
|
246
|
+
### Serverless Example
|
|
359
247
|
|
|
360
|
-
|
|
361
|
-
const ageValidator = (data) => {
|
|
362
|
-
return typeof data === "number" && data >= 18 && data <= 120;
|
|
363
|
-
};
|
|
248
|
+
Compile validators at module scope and reuse them across invocations:
|
|
364
249
|
|
|
365
|
-
|
|
366
|
-
|
|
250
|
+
```javascript
|
|
251
|
+
import { SchemaShield } from "schema-shield";
|
|
367
252
|
|
|
368
|
-
const
|
|
253
|
+
const validateRequest = new SchemaShield({ failFast: false }).compile({
|
|
369
254
|
type: "object",
|
|
370
255
|
properties: {
|
|
371
256
|
name: { type: "string" },
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const validator = schemaShield.compile(schema);
|
|
257
|
+
email: { type: "string", format: "email" }
|
|
258
|
+
},
|
|
259
|
+
required: ["name", "email"]
|
|
260
|
+
});
|
|
377
261
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
};
|
|
262
|
+
export default async function handler(request) {
|
|
263
|
+
const body = await request.json();
|
|
264
|
+
const result = validateRequest(body);
|
|
382
265
|
|
|
383
|
-
|
|
266
|
+
if (!result.valid) {
|
|
267
|
+
return new Response(JSON.stringify({ error: result.error.message }), {
|
|
268
|
+
status: 400,
|
|
269
|
+
headers: { "Content-Type": "application/json" }
|
|
270
|
+
});
|
|
271
|
+
}
|
|
384
272
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
273
|
+
return new Response(JSON.stringify(result.data), {
|
|
274
|
+
status: 200,
|
|
275
|
+
headers: { "Content-Type": "application/json" }
|
|
276
|
+
});
|
|
389
277
|
}
|
|
390
278
|
```
|
|
391
279
|
|
|
392
|
-
##
|
|
393
|
-
|
|
394
|
-
SchemaShield allows you to add custom formats for validation using the `addFormat` method.
|
|
280
|
+
## Core API
|
|
395
281
|
|
|
396
|
-
###
|
|
282
|
+
### Import
|
|
397
283
|
|
|
398
284
|
```javascript
|
|
399
|
-
|
|
400
|
-
(data: any): boolean;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
addFormat(name: string, validator: FormatFunction, overwrite?: boolean): void;
|
|
285
|
+
import { SchemaShield } from "schema-shield";
|
|
404
286
|
```
|
|
405
287
|
|
|
406
|
-
|
|
407
|
-
- `validator`: A FormatFunction that takes a single argument data and returns a boolean value. The function should return true if the provided data is valid for the custom format, and false otherwise.
|
|
408
|
-
- `overwrite` (optional): Set to true to overwrite an existing format with the same name. Default is false. If set to false and a format with the same name already exists, an error will be thrown.
|
|
288
|
+
CommonJS is also supported:
|
|
409
289
|
|
|
410
|
-
|
|
290
|
+
```javascript
|
|
291
|
+
const { SchemaShield } = require("schema-shield");
|
|
292
|
+
```
|
|
411
293
|
|
|
412
|
-
|
|
294
|
+
### Create an Instance
|
|
413
295
|
|
|
414
296
|
```javascript
|
|
415
|
-
|
|
297
|
+
const schemaShield = new SchemaShield({
|
|
298
|
+
immutable: false,
|
|
299
|
+
failFast: true,
|
|
300
|
+
maxDepth: 128,
|
|
301
|
+
useDefaults: false
|
|
302
|
+
});
|
|
303
|
+
```
|
|
416
304
|
|
|
417
|
-
|
|
305
|
+
| Option | Default | Behavior |
|
|
306
|
+
| :------------ | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
307
|
+
| `immutable` | `false` | Set to `true` to preserve the original input when defaults or custom keywords could modify data. |
|
|
308
|
+
| `failFast` | `true` | Returns `error: true` on failure. With `false`, built-ins and custom failures created with `defineError()` return a detailed `ValidationError`. A custom keyword that returns `true` directly still returns `error: true`. |
|
|
309
|
+
| `maxDepth` | `128` | Sets the maximum recursive validation depth. Accepts an integer from `1` to `256`; validation paths that exceed it fail with a controlled error. |
|
|
310
|
+
| `useDefaults` | `false` | Leaves `default` as a JSON Schema annotation. Use `true` to replace absent or `undefined` properties, or `"empty"` to also replace `null` and empty strings. |
|
|
418
311
|
|
|
419
|
-
|
|
420
|
-
const ssnValidator = (data) => {
|
|
421
|
-
const ssnPattern = /^(?!000|.+0{4})(?:\d{9}|\d{3}-\d{2}-\d{4})$/;
|
|
422
|
-
return typeof data === "string" && ssnPattern.test(data);
|
|
423
|
-
};
|
|
312
|
+
Schema compilation applies its own depth limit before a validator is created. This compile-time protection is separate from the runtime `maxDepth` option.
|
|
424
313
|
|
|
425
|
-
|
|
426
|
-
schemaShield.addFormat("ssn", ssnValidator);
|
|
314
|
+
### Compile and Validate
|
|
427
315
|
|
|
316
|
+
```javascript
|
|
428
317
|
const schema = {
|
|
429
318
|
type: "object",
|
|
430
319
|
properties: {
|
|
431
320
|
name: { type: "string" },
|
|
432
|
-
|
|
433
|
-
}
|
|
321
|
+
age: { type: "number" }
|
|
322
|
+
},
|
|
323
|
+
required: ["name"]
|
|
434
324
|
};
|
|
435
325
|
|
|
436
326
|
const validator = schemaShield.compile(schema);
|
|
437
|
-
|
|
438
|
-
const validData = {
|
|
439
|
-
name: "John Doe",
|
|
440
|
-
ssn: "123-45-6789"
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
const validationResult = validator(validData);
|
|
444
|
-
|
|
445
|
-
if (validationResult.valid) {
|
|
446
|
-
console.log("Data is valid:", validationResult.data);
|
|
447
|
-
} else {
|
|
448
|
-
console.error("Validation error:", validationResult.error.getCause().message);
|
|
449
|
-
}
|
|
327
|
+
const result = validator({ name: "Ada", age: 37 });
|
|
450
328
|
```
|
|
451
329
|
|
|
452
|
-
|
|
330
|
+
Every result contains:
|
|
453
331
|
|
|
454
|
-
|
|
332
|
+
- `valid`: `true` when validation succeeds, otherwise `false`.
|
|
333
|
+
- `data`: The validated data, including any applied defaults.
|
|
334
|
+
- `error`: `null` on success, `true` on a fail-fast failure or when a custom keyword returns `true` directly, or a `ValidationError` for built-in failures and custom failures created with `defineError()` when `failFast: false`.
|
|
455
335
|
|
|
456
|
-
###
|
|
336
|
+
### Defaults
|
|
457
337
|
|
|
458
|
-
|
|
459
|
-
type Result = void | ValidationError | true;
|
|
338
|
+
SchemaShield keeps data shaping explicit. By default, `default` remains a standard JSON Schema annotation and your input stays unchanged.
|
|
460
339
|
|
|
461
|
-
|
|
462
|
-
item?: any; // Final item in the path
|
|
463
|
-
cause?: ValidationError | true; // Cause of the error (or true in failFast mode)
|
|
464
|
-
data?: any; // Data that caused the error
|
|
465
|
-
}
|
|
340
|
+
Enable `useDefaults` when you want validated data to apply defaults and leave the validator ready to use:
|
|
466
341
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
(data: any): Result;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
interface CompiledSchema {
|
|
476
|
-
$validate?: ValidateFunction;
|
|
477
|
-
[key: string]: any;
|
|
478
|
-
}
|
|
342
|
+
| `useDefaults` | Behavior |
|
|
343
|
+
| :----------------- | :------------------------------------------------------------------------------------ |
|
|
344
|
+
| Omitted or `false` | Keeps defaults as annotations and does not modify data. |
|
|
345
|
+
| `true` | Completes missing required and optional properties. |
|
|
346
|
+
| `"empty"` | Also replaces `null` and `""`, while preserving valid values such as `0` and `false`. |
|
|
479
347
|
|
|
480
|
-
|
|
481
|
-
(data: any): boolean;
|
|
482
|
-
}
|
|
348
|
+
Combine `useDefaults` with `immutable: true` to receive completed data without modifying the original object:
|
|
483
349
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
addKeyword(name: string, validator: KeywordFunction, overwrite?: boolean): void;
|
|
497
|
-
getType(type: string): TypeFunction | false;
|
|
498
|
-
getFormat(format: string): FormatFunction | false;
|
|
499
|
-
getKeyword(keyword: string): KeywordFunction | false;
|
|
500
|
-
isSchemaLike(subSchema: any): boolean;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
interface KeywordFunction {
|
|
504
|
-
(
|
|
505
|
-
schema: CompiledSchema,
|
|
506
|
-
data: any,
|
|
507
|
-
defineError: DefineErrorFunction,
|
|
508
|
-
instance: SchemaShield
|
|
509
|
-
): Result;
|
|
510
|
-
}
|
|
350
|
+
```javascript
|
|
351
|
+
const validateUser = new SchemaShield({
|
|
352
|
+
useDefaults: true,
|
|
353
|
+
immutable: true
|
|
354
|
+
}).compile({
|
|
355
|
+
type: "object",
|
|
356
|
+
properties: {
|
|
357
|
+
role: { type: "string", default: "member" },
|
|
358
|
+
theme: { type: "string", default: "system" }
|
|
359
|
+
},
|
|
360
|
+
required: ["role"]
|
|
361
|
+
});
|
|
511
362
|
|
|
363
|
+
const input = {};
|
|
364
|
+
const result = validateUser(input);
|
|
512
365
|
|
|
366
|
+
input;
|
|
367
|
+
// {}
|
|
513
368
|
|
|
514
|
-
|
|
369
|
+
result.data;
|
|
370
|
+
// { role: "member", theme: "system" }
|
|
515
371
|
```
|
|
516
372
|
|
|
517
|
-
|
|
518
|
-
- `validator`: A `KeywordFunction` that takes four arguments: `schema`, `data`, `defineError`, and `instance` (The SchemaShield instance that is currently running the validation). The function should not return anything if the data is valid for the custom keyword, and should return a `ValidationError` instance if the data is invalid when `failFast` is `false`, or `true` when `failFast` is `true`.
|
|
519
|
-
- `overwrite` (optional): Set to true to overwrite an existing keyword with the same name. Default is false. If set to false and a keyword with the same name already exists, an error will be thrown.
|
|
520
|
-
|
|
521
|
-
#### About the `defineError` Function
|
|
522
|
-
|
|
523
|
-
Take into account that the error must be generated using the `defineError` function because the error returned by this method has the required data relevant for the current keyword (`schema`, `keyword`, `getCause` method).
|
|
524
|
-
|
|
525
|
-
- `message`: A string that describes the validation error.
|
|
526
|
-
- `options`: An optional object with properties that provide more context for the error:
|
|
527
|
-
- `item`?: An optional value representing the final item in the path where the validation error occurred. (e.g. index of an array item)
|
|
528
|
-
- `cause`?: An optional `ValidationError` (or `true` in fail-fast mode) that represents the cause of the current error.
|
|
529
|
-
- `data`?: An optional value representing the data that caused the validation error.
|
|
530
|
-
|
|
531
|
-
When the SchemaShield instance is created with `failFast: true`, `defineError` returns `true` instead of a `ValidationError`, and keyword implementations should simply `return` whatever `defineError` gives them.
|
|
532
|
-
|
|
533
|
-
#### About the `instance` Argument
|
|
534
|
-
|
|
535
|
-
The `instance` argument is the SchemaShield instance that is currently running the validation. This can be used to access to other `types`, `keywords` or `formats` that have been added to the instance.
|
|
373
|
+
## Errors and Debugging
|
|
536
374
|
|
|
537
|
-
|
|
375
|
+
SchemaShield provides two error modes:
|
|
538
376
|
|
|
539
|
-
|
|
377
|
+
- `failFast: true` returns `error: true` with minimal allocation.
|
|
378
|
+
- `failFast: false` returns a `ValidationError` with rich debugging information for built-in failures and custom keyword failures created with `defineError()`. A custom keyword that returns `true` directly still produces `error: true`. Use `defineError()` for detailed errors.
|
|
540
379
|
|
|
541
380
|
```javascript
|
|
542
|
-
import { SchemaShield
|
|
543
|
-
|
|
544
|
-
const schemaShield = new SchemaShield({ failFast: false });
|
|
545
|
-
|
|
546
|
-
// Custom keyword 'divisibleBy' validator function
|
|
547
|
-
const divisibleByValidator = (schema, data, defineError, instance) => {
|
|
548
|
-
if (typeof data !== "number") {
|
|
549
|
-
return defineError("Value must be a number", {
|
|
550
|
-
data
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
if (data % schema.divisibleBy !== 0) {
|
|
555
|
-
return defineError(`Value must be divisible by ${schema.divisibleBy}`, {
|
|
556
|
-
data
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
// Adding the custom keyword 'divisibleBy'
|
|
562
|
-
schemaShield.addKeyword("divisibleBy", divisibleByValidator);
|
|
381
|
+
import { SchemaShield } from "schema-shield";
|
|
563
382
|
|
|
564
|
-
const
|
|
383
|
+
const validatePerson = new SchemaShield({ failFast: false }).compile({
|
|
565
384
|
type: "object",
|
|
566
385
|
properties: {
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const validator = schemaShield.compile(schema);
|
|
386
|
+
age: { type: "number", minimum: 18 }
|
|
387
|
+
},
|
|
388
|
+
required: ["age"]
|
|
389
|
+
});
|
|
572
390
|
|
|
573
|
-
const
|
|
574
|
-
value: 15
|
|
575
|
-
};
|
|
391
|
+
const result = validatePerson({ age: 15 });
|
|
576
392
|
|
|
577
|
-
|
|
393
|
+
if (!result.valid) {
|
|
394
|
+
const paths = result.error.getPath();
|
|
578
395
|
|
|
579
|
-
|
|
580
|
-
console.
|
|
581
|
-
|
|
582
|
-
console.error("Validation error:", validationResult.error.getCause().message);
|
|
396
|
+
console.error(result.error.message);
|
|
397
|
+
console.error(paths.schemaPath);
|
|
398
|
+
console.error(paths.instancePath);
|
|
583
399
|
}
|
|
584
400
|
```
|
|
585
401
|
|
|
586
|
-
###
|
|
587
|
-
|
|
588
|
-
In this example we'll add a custom keyword called `prefixedUsername` that will validate if a given string is a valid username and has a specific prefix. This will only work if the additional validation methods and types have been added to the instance.
|
|
402
|
+
### ValidationError Properties
|
|
589
403
|
|
|
590
|
-
|
|
591
|
-
|
|
404
|
+
- `message`: Description of the validation error.
|
|
405
|
+
- `item`: Final item in the failing path, when available.
|
|
406
|
+
- `keyword`: Keyword that triggered the error.
|
|
407
|
+
- `cause`: Nested `ValidationError` that caused the current error.
|
|
408
|
+
- `code`: Stable error code, when the error provides one.
|
|
409
|
+
- `schemaPath`: JSON Pointer to the failing schema location.
|
|
410
|
+
- `instancePath`: JSON Pointer to the failing data location.
|
|
411
|
+
- `data`: Data that caused the error, when available.
|
|
412
|
+
- `schema`: Schema value that caused the error, when available.
|
|
592
413
|
|
|
593
|
-
|
|
414
|
+
`schemaPath` and `instancePath` become available after calling `getCause()`, `getTree()`, or `getPath()`.
|
|
594
415
|
|
|
595
|
-
|
|
596
|
-
const nonEmptyStringValidator = (data) =>
|
|
597
|
-
typeof data === "string" && data.length > 0;
|
|
598
|
-
schemaShield.addType("nonEmptyString", nonEmptyStringValidator);
|
|
416
|
+
### Error Methods
|
|
599
417
|
|
|
600
|
-
|
|
601
|
-
const hasPrefixValidator = (schema, data, defineError, instance) => {
|
|
602
|
-
const { prefix } = schema.hasPrefix;
|
|
603
|
-
if (typeof data === "string" && !data.startsWith(prefix)) {
|
|
604
|
-
return defineError(`String must have the prefix "${prefix}"`, {
|
|
605
|
-
data
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
};
|
|
609
|
-
schemaShield.addKeyword("hasPrefix", hasPrefixValidator);
|
|
610
|
-
|
|
611
|
-
// Custom format validator: username
|
|
612
|
-
const usernameValidator = (data) => /^[a-zA-Z0-9._-]{3,}$/i.test(data);
|
|
613
|
-
schemaShield.addFormat("username", usernameValidator);
|
|
614
|
-
|
|
615
|
-
// Custom keyword 'prefixedUsername' validator function
|
|
616
|
-
const prefixedUsername = (schema, data, defineError, instance) => {
|
|
617
|
-
const { validType, prefixValidator, validFormat } = schema.prefixedUsername;
|
|
618
|
-
|
|
619
|
-
// Get the validators for the specified types and formats from the instance
|
|
620
|
-
// (if they exist)
|
|
621
|
-
const typeValidator = instance.getType(validType);
|
|
622
|
-
const prefixKeyword = instance.getKeyword(prefixValidator);
|
|
623
|
-
const formatValidator = instance.getFormat(validFormat);
|
|
624
|
-
|
|
625
|
-
for (let i = 0; i < data.length; i++) {
|
|
626
|
-
const item = data[i];
|
|
627
|
-
|
|
628
|
-
// Validate that the data is of the correct type if specified
|
|
629
|
-
if (validType && typeValidator) {
|
|
630
|
-
if (!typeValidator(item)) {
|
|
631
|
-
return defineError(`Invalid type: ${validType}`, {
|
|
632
|
-
item: i,
|
|
633
|
-
data: data[i]
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// Validate that the data has the correct format if specified
|
|
639
|
-
if (validFormat && formatValidator) {
|
|
640
|
-
if (!formatValidator(item)) {
|
|
641
|
-
return defineError(`Invalid format: ${validFormat}`, {
|
|
642
|
-
item: i,
|
|
643
|
-
data: data[i]
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// Validate that the data has the correct prefix if specified
|
|
649
|
-
if (prefixKeyword) {
|
|
650
|
-
const error = prefixKeyword(schema, item, defineError, instance);
|
|
651
|
-
if (error) {
|
|
652
|
-
return defineError(`Invalid prefix: ${prefixValidator}`, {
|
|
653
|
-
cause: error,
|
|
654
|
-
item: i,
|
|
655
|
-
data: data[i]
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
};
|
|
418
|
+
#### `getPath()`
|
|
661
419
|
|
|
662
|
-
|
|
420
|
+
Returns the schema and instance paths for the root validation failure.
|
|
663
421
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
validType: "nonEmptyString",
|
|
668
|
-
prefixValidator: "hasPrefix",
|
|
669
|
-
validFormat: "username"
|
|
670
|
-
},
|
|
671
|
-
items: {
|
|
672
|
-
type: "string"
|
|
673
|
-
}
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
const validator = schemaShield.compile(schema);
|
|
422
|
+
```javascript
|
|
423
|
+
const { schemaPath, instancePath } = result.error.getPath();
|
|
424
|
+
```
|
|
677
425
|
|
|
678
|
-
|
|
426
|
+
#### `getCause()`
|
|
679
427
|
|
|
680
|
-
|
|
428
|
+
Returns the root `ValidationError` that caused the validation chain.
|
|
681
429
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
console.error("Validation error:", validationResult.error.getCause().message);
|
|
686
|
-
}
|
|
430
|
+
```javascript
|
|
431
|
+
const cause = result.error.getCause();
|
|
432
|
+
console.error(cause.message, cause.keyword);
|
|
687
433
|
```
|
|
688
434
|
|
|
689
|
-
|
|
435
|
+
#### `getTree()`
|
|
690
436
|
|
|
691
|
-
|
|
437
|
+
Returns the complete nested error chain as an error tree.
|
|
692
438
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
439
|
+
```typescript
|
|
440
|
+
interface ErrorTree {
|
|
441
|
+
message: string;
|
|
442
|
+
keyword: string;
|
|
443
|
+
item?: string | number;
|
|
444
|
+
schemaPath: string;
|
|
445
|
+
instancePath: string;
|
|
446
|
+
data?: any;
|
|
447
|
+
cause?: ErrorTree;
|
|
448
|
+
}
|
|
449
|
+
```
|
|
702
450
|
|
|
703
|
-
##
|
|
451
|
+
## Extensibility
|
|
704
452
|
|
|
705
|
-
|
|
453
|
+
SchemaShield exposes custom types, formats, and keywords through the same instance used to compile validators.
|
|
706
454
|
|
|
707
|
-
|
|
455
|
+
The examples in this section use the following instance:
|
|
708
456
|
|
|
709
457
|
```javascript
|
|
710
|
-
import { SchemaShield
|
|
458
|
+
import { SchemaShield } from "schema-shield";
|
|
711
459
|
|
|
712
|
-
const schemaShield = new SchemaShield(
|
|
460
|
+
const schemaShield = new SchemaShield();
|
|
461
|
+
```
|
|
713
462
|
|
|
714
|
-
|
|
715
|
-
class Project {
|
|
716
|
-
constructor(name: string, requiredSkills: string[]) {
|
|
717
|
-
this.name = name;
|
|
718
|
-
this.requiredSkills = requiredSkills;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
463
|
+
### Custom Types
|
|
721
464
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
}
|
|
465
|
+
```typescript
|
|
466
|
+
interface TypeFunction {
|
|
467
|
+
(data: any): boolean;
|
|
468
|
+
}
|
|
727
469
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
}
|
|
470
|
+
interface SchemaShieldTypeAPI {
|
|
471
|
+
addType(name: string, validator: TypeFunction, overwrite?: boolean): void;
|
|
731
472
|
}
|
|
473
|
+
```
|
|
732
474
|
|
|
733
|
-
|
|
734
|
-
schemaShield.addType(
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
schemaShield.addKeyword(
|
|
738
|
-
"requiresQualifiedEmployee",
|
|
739
|
-
(schema, data, defineError, instance) => {
|
|
740
|
-
const { assignment, project, employee } = data;
|
|
741
|
-
|
|
742
|
-
const stringTypeValidator = instance.getType("string");
|
|
743
|
-
const projectTypeValidator = instance.getType("project");
|
|
744
|
-
const employeeTypeValidator = instance.getType("employee");
|
|
745
|
-
|
|
746
|
-
if (!stringTypeValidator(assignment)) {
|
|
747
|
-
return defineError("Assignment must be a string", {
|
|
748
|
-
item: "assignment",
|
|
749
|
-
data: assignment
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
if (!projectTypeValidator(project)) {
|
|
754
|
-
return defineError("Project must be a Project instance", {
|
|
755
|
-
item: "project",
|
|
756
|
-
data: project
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
if (!employeeTypeValidator(employee)) {
|
|
761
|
-
return defineError("Employee must be an Employee instance", {
|
|
762
|
-
item: "employee",
|
|
763
|
-
data: employee
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
if (schema.requiresQualifiedEmployee) {
|
|
768
|
-
if (!employee.hasSkillsForProject(project)) {
|
|
769
|
-
return defineError(
|
|
770
|
-
"Employee does not meet the project's requirements",
|
|
771
|
-
{
|
|
772
|
-
data: {
|
|
773
|
-
assignment,
|
|
774
|
-
project,
|
|
775
|
-
employee
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
475
|
+
```javascript
|
|
476
|
+
schemaShield.addType(
|
|
477
|
+
"adult-age",
|
|
478
|
+
(data) => typeof data === "number" && data >= 18
|
|
782
479
|
);
|
|
783
480
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
type: "object",
|
|
787
|
-
properties: {
|
|
788
|
-
assignment: {}, // Empty schema because we will validate it with the custom keyword
|
|
789
|
-
project: {}, // Empty schema because we will validate it with the custom keyword
|
|
790
|
-
employee: {} // Empty schema because we will validate it with the custom keyword
|
|
791
|
-
},
|
|
792
|
-
required: ["assignment", "project", "employee"],
|
|
793
|
-
requiresQualifiedEmployee: true
|
|
794
|
-
};
|
|
795
|
-
|
|
796
|
-
const validator = schemaShield.compile(schema);
|
|
797
|
-
|
|
798
|
-
// Create some data to validate
|
|
799
|
-
const employee1 = new Employee("Employee 1", ["A", "B", "C"]);
|
|
481
|
+
const validator = schemaShield.compile({ type: "adult-age" });
|
|
482
|
+
```
|
|
800
483
|
|
|
801
|
-
|
|
484
|
+
Set `overwrite` to `true` to replace an existing type with the same name. The default is `false`.
|
|
802
485
|
|
|
803
|
-
|
|
804
|
-
assignment: "Assignment 1 for Project 1",
|
|
805
|
-
project: project1,
|
|
806
|
-
employee: employee1
|
|
807
|
-
};
|
|
486
|
+
### Custom Formats
|
|
808
487
|
|
|
809
|
-
|
|
810
|
-
|
|
488
|
+
```typescript
|
|
489
|
+
interface FormatFunction {
|
|
490
|
+
(data: any): boolean;
|
|
491
|
+
}
|
|
811
492
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
} else {
|
|
815
|
-
console.error("Validation error:", validationResult.error.message);
|
|
493
|
+
interface SchemaShieldFormatAPI {
|
|
494
|
+
addFormat(name: string, validator: FormatFunction, overwrite?: boolean): void;
|
|
816
495
|
}
|
|
817
496
|
```
|
|
818
497
|
|
|
819
|
-
In this example, SchemaShield safely accesses instances of custom classes and utilizes them in the validation process. This level of complexity and flexibility would not be possible or would require a lot of boilerplate code with other libraries that rely on code generation.
|
|
820
|
-
|
|
821
|
-
## More on Error Handling
|
|
822
|
-
|
|
823
|
-
SchemaShield provides a `ValidationError` class to handle errors that occur during schema validation. When a validation error is encountered, a `ValidationError` instance is returned in the error property of the validation result when `failFast: false` is used; otherwise `error` is `true`.
|
|
824
|
-
|
|
825
|
-
This error instance uses the [`Error.cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) property. This allows you to analyze the whole error chain or retrieve the root cause of the error using the `getCause()`, `getTree()`, and `getPath()` methods.
|
|
826
|
-
|
|
827
|
-
### ValidationError Properties
|
|
828
|
-
|
|
829
|
-
- `message`: A string containing a description of the error.
|
|
830
|
-
- `item`: The final item in the path that caused the error (either a string or a number) (optional).
|
|
831
|
-
- `keyword`: The keyword that triggered the error.
|
|
832
|
-
- `cause`: A nested ValidationError or a normal Error that caused the current error.
|
|
833
|
-
- `schemaPath`: The JSON Pointer path to the error location in the schema.
|
|
834
|
-
- `instancePath`: The JSON Pointer path to the error location in the data.
|
|
835
|
-
- `data`: The data that caused the error (optional).
|
|
836
|
-
- `schema`: The schema that caused the error (optional).
|
|
837
|
-
|
|
838
|
-
_Note:_ The `schemaPath` and `instancePath` will be only available after using the `getCause()` `getTree()` or `getPath()` methods.
|
|
839
|
-
|
|
840
|
-
### Get the path to the error location
|
|
841
|
-
|
|
842
|
-
You can use the `getPath` method to get the JSON Pointer path to the error location in the schema and in the data. This method returns an object containing the `schemaPath` and `instancePath`.
|
|
843
|
-
|
|
844
|
-
**Example:**
|
|
845
|
-
|
|
846
498
|
```javascript
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
499
|
+
schemaShield.addFormat(
|
|
500
|
+
"username",
|
|
501
|
+
(data) => typeof data === "string" && /^[a-z0-9._-]{3,}$/i.test(data)
|
|
502
|
+
);
|
|
850
503
|
|
|
851
|
-
const
|
|
852
|
-
type: "
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
enableNetConnectFor: { type: "array", items: { type: "string" } },
|
|
857
|
-
params: {
|
|
858
|
-
type: "object",
|
|
859
|
-
additionalProperties: {
|
|
860
|
-
type: "object",
|
|
861
|
-
properties: {
|
|
862
|
-
description: { type: "string" },
|
|
863
|
-
default: { type: "string" }
|
|
864
|
-
},
|
|
865
|
-
required: ["description"]
|
|
866
|
-
}
|
|
867
|
-
},
|
|
868
|
-
run: { type: "string" }
|
|
869
|
-
}
|
|
870
|
-
};
|
|
504
|
+
const validator = schemaShield.compile({
|
|
505
|
+
type: "string",
|
|
506
|
+
format: "username"
|
|
507
|
+
});
|
|
508
|
+
```
|
|
871
509
|
|
|
872
|
-
|
|
510
|
+
Set `overwrite` to `true` to replace an existing format with the same name. The default is `false`.
|
|
873
511
|
|
|
874
|
-
|
|
875
|
-
description: "Say hello to the bot.",
|
|
876
|
-
shouldLoadDb: false,
|
|
877
|
-
enableNetConnectFor: [],
|
|
878
|
-
params: {
|
|
879
|
-
color: {
|
|
880
|
-
type: "string",
|
|
881
|
-
// description: "The color of the text", // Missing description on purpose
|
|
882
|
-
default: "red"
|
|
883
|
-
}
|
|
884
|
-
},
|
|
885
|
-
run: "run"
|
|
886
|
-
};
|
|
512
|
+
### Custom Keywords
|
|
887
513
|
|
|
888
|
-
|
|
514
|
+
```typescript
|
|
515
|
+
import type {
|
|
516
|
+
CompiledSchema,
|
|
517
|
+
SchemaShield,
|
|
518
|
+
ValidationError
|
|
519
|
+
} from "schema-shield";
|
|
889
520
|
|
|
890
|
-
|
|
891
|
-
console.log("Data is valid:", validationResult.data);
|
|
892
|
-
} else {
|
|
893
|
-
console.error("Validation error:", validationResult.error.message); // "Property is invalid"
|
|
521
|
+
type Result = void | ValidationError | true;
|
|
894
522
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
523
|
+
interface DefineErrorOptions {
|
|
524
|
+
code?: string;
|
|
525
|
+
item?: any;
|
|
526
|
+
cause?: ValidationError | true;
|
|
527
|
+
data?: any;
|
|
899
528
|
}
|
|
900
|
-
```
|
|
901
529
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
530
|
+
interface DefineErrorFunction {
|
|
531
|
+
(
|
|
532
|
+
message: string,
|
|
533
|
+
options?: DefineErrorOptions
|
|
534
|
+
): ValidationError | void | true;
|
|
535
|
+
}
|
|
905
536
|
|
|
906
|
-
|
|
537
|
+
interface KeywordFunction {
|
|
538
|
+
(
|
|
539
|
+
schema: CompiledSchema,
|
|
540
|
+
data: any,
|
|
541
|
+
defineError: DefineErrorFunction,
|
|
542
|
+
instance: SchemaShield
|
|
543
|
+
): Result;
|
|
544
|
+
}
|
|
907
545
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
instancePath: string; // The JSON Pointer path to the error location in the data
|
|
915
|
-
data?: any; // The data that caused the error (optional)
|
|
916
|
-
cause?: ErrorTree; // A nested ErrorTree representation of the nested error that caused the current error
|
|
546
|
+
interface SchemaShieldKeywordAPI {
|
|
547
|
+
addKeyword(
|
|
548
|
+
name: string,
|
|
549
|
+
validator: KeywordFunction,
|
|
550
|
+
overwrite?: boolean
|
|
551
|
+
): void;
|
|
917
552
|
}
|
|
918
553
|
```
|
|
919
554
|
|
|
920
|
-
|
|
555
|
+
Use `defineError()` so SchemaShield can attach the current keyword, schema, data, and error-chain context:
|
|
921
556
|
|
|
922
557
|
```javascript
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
const schema = {
|
|
928
|
-
type: "object",
|
|
929
|
-
properties: {
|
|
930
|
-
description: { type: "string" },
|
|
931
|
-
shouldLoadDb: { type: "boolean" },
|
|
932
|
-
enableNetConnectFor: { type: "array", items: { type: "string" } },
|
|
933
|
-
params: {
|
|
934
|
-
type: "object",
|
|
935
|
-
additionalProperties: {
|
|
936
|
-
type: "object",
|
|
937
|
-
properties: {
|
|
938
|
-
description: { type: "string" },
|
|
939
|
-
default: { type: "string" }
|
|
940
|
-
},
|
|
941
|
-
required: ["description"]
|
|
942
|
-
}
|
|
943
|
-
},
|
|
944
|
-
run: { type: "string" }
|
|
558
|
+
schemaShield.addKeyword("divisibleBy", (schema, data, defineError) => {
|
|
559
|
+
if (typeof data !== "number") {
|
|
560
|
+
return defineError("Value must be a number", { data });
|
|
945
561
|
}
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
const validator = schemaShield.compile(schema);
|
|
949
562
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
type: "string",
|
|
957
|
-
// description: "The color of the text", // Missing description on purpose
|
|
958
|
-
default: "red"
|
|
959
|
-
}
|
|
960
|
-
},
|
|
961
|
-
run: "run"
|
|
962
|
-
};
|
|
563
|
+
if (data % schema.divisibleBy !== 0) {
|
|
564
|
+
return defineError(`Value must be divisible by ${schema.divisibleBy}`, {
|
|
565
|
+
data
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
});
|
|
963
569
|
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
} else {
|
|
969
|
-
console.error("Validation error:", validationResult.error.message); // "Property is invalid"
|
|
970
|
-
|
|
971
|
-
// Get the full error chain as a tree
|
|
972
|
-
const errorTree = validationResult.error.getTree();
|
|
973
|
-
console.error(errorTree);
|
|
974
|
-
|
|
975
|
-
/*
|
|
976
|
-
{
|
|
977
|
-
message: "Property is invalid",
|
|
978
|
-
keyword: "properties",
|
|
979
|
-
item: "params",
|
|
980
|
-
schemaPath: "#/properties/params",
|
|
981
|
-
instancePath: "#/params",
|
|
982
|
-
data: { color: { type: "string", default: "red" } },
|
|
983
|
-
cause: {
|
|
984
|
-
message: "Additional properties are invalid",
|
|
985
|
-
keyword: "additionalProperties",
|
|
986
|
-
item: "color",
|
|
987
|
-
schemaPath: "#/properties/params/additionalProperties",
|
|
988
|
-
instancePath: "#/params/color",
|
|
989
|
-
data: { type: "string", default: "red" },
|
|
990
|
-
cause: {
|
|
991
|
-
message: "Required property is missing",
|
|
992
|
-
keyword: "required",
|
|
993
|
-
item: "description",
|
|
994
|
-
schemaPath: "#/properties/params/additionalProperties/required",
|
|
995
|
-
instancePath: "#/params/color/description",
|
|
996
|
-
data: undefined
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
*/
|
|
1001
|
-
}
|
|
570
|
+
const validator = schemaShield.compile({
|
|
571
|
+
type: "number",
|
|
572
|
+
divisibleBy: 5
|
|
573
|
+
});
|
|
1002
574
|
```
|
|
1003
575
|
|
|
1004
|
-
The `
|
|
576
|
+
The `instance` argument provides access to types, formats, and keywords registered on the active `SchemaShield` instance through `getType()`, `getFormat()`, and `getKeyword()`.
|
|
1005
577
|
|
|
1006
|
-
|
|
578
|
+
When `failFast: true`, `defineError()` returns `true`. When `failFast: false`, it returns a `ValidationError`. A custom keyword that returns `true` directly produces `error: true` in either mode, so use `defineError()` when you need detailed errors.
|
|
1007
579
|
|
|
1008
|
-
|
|
580
|
+
## Supported Formats and Compatibility
|
|
1009
581
|
|
|
1010
|
-
|
|
1011
|
-
import { SchemaShield } from "schema-shield";
|
|
582
|
+
SchemaShield supports draft-06 and draft-07 JSON Schema validation and includes built-in validators for:
|
|
1012
583
|
|
|
1013
|
-
|
|
584
|
+
- **Date and time:** `date`, `time`, `date-time`, `duration`
|
|
585
|
+
- **Email:** `email`, `idn-email`
|
|
586
|
+
- **Hostnames:** `hostname`, `idn-hostname`
|
|
587
|
+
- **IP addresses:** `ipv4`, `ipv6`
|
|
588
|
+
- **Resource identifiers:** `uuid`, `uri`, `uri-reference`, `uri-template`, `iri`, `iri-reference`
|
|
589
|
+
- **JSON Pointers:** `json-pointer`, `relative-json-pointer`
|
|
590
|
+
- **Regular expressions:** `regex`
|
|
1014
591
|
|
|
1015
|
-
|
|
1016
|
-
type: "object",
|
|
1017
|
-
properties: {
|
|
1018
|
-
description: { type: "string" },
|
|
1019
|
-
shouldLoadDb: { type: "boolean" },
|
|
1020
|
-
enableNetConnectFor: { type: "array", items: { type: "string" } },
|
|
1021
|
-
params: {
|
|
1022
|
-
type: "object",
|
|
1023
|
-
additionalProperties: {
|
|
1024
|
-
type: "object",
|
|
1025
|
-
properties: {
|
|
1026
|
-
description: { type: "string" },
|
|
1027
|
-
default: { type: "string" }
|
|
1028
|
-
},
|
|
1029
|
-
required: ["description"]
|
|
1030
|
-
}
|
|
1031
|
-
},
|
|
1032
|
-
run: { type: "string" }
|
|
1033
|
-
}
|
|
1034
|
-
};
|
|
592
|
+
Remote references are intentionally outside the offline execution model described above.
|
|
1035
593
|
|
|
1036
|
-
|
|
594
|
+
Any built-in format can be replaced, and new formats can be added with `schemaShield.addFormat()`.
|
|
1037
595
|
|
|
1038
|
-
|
|
1039
|
-
description: "Say hello to the bot.",
|
|
1040
|
-
shouldLoadDb: false,
|
|
1041
|
-
enableNetConnectFor: [],
|
|
1042
|
-
params: {
|
|
1043
|
-
color: {
|
|
1044
|
-
type: "string",
|
|
1045
|
-
// description: "The color of the text", // Missing description on purpose
|
|
1046
|
-
default: "red"
|
|
1047
|
-
}
|
|
1048
|
-
},
|
|
1049
|
-
run: "run"
|
|
1050
|
-
};
|
|
596
|
+
### TypeScript
|
|
1051
597
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
if (validationResult.valid) {
|
|
1055
|
-
console.log("Data is valid:", validationResult.data);
|
|
1056
|
-
} else {
|
|
1057
|
-
console.error("Validation error:", validationResult.error.message); // "Property is invalid"
|
|
1058
|
-
|
|
1059
|
-
// Get the root cause of the error
|
|
1060
|
-
const errorCause = validationResult.error.getCause();
|
|
1061
|
-
console.error("Root cause:", errorCause.message); // "Required property is missing"
|
|
1062
|
-
console.error("Schema path:", errorCause.schemaPath); // "#/properties/params/additionalProperties/required"
|
|
1063
|
-
console.error("Instance path:", errorCause.instancePath); // "#/params/color/description"
|
|
1064
|
-
console.error("Error data:", errorCause.data); // undefined
|
|
1065
|
-
console.error("Error schema:", errorCause.schema); // ["description"]
|
|
1066
|
-
console.error("Error keyword:", errorCause.keyword); // "required"
|
|
1067
|
-
}
|
|
1068
|
-
```
|
|
1069
|
-
|
|
1070
|
-
## Immutable Mode
|
|
598
|
+
Type declarations are included in the package. CJS, ESM, browser, and declaration package smokes have been verified.
|
|
1071
599
|
|
|
1072
|
-
|
|
600
|
+
## Immutable Mode and Known Limitations
|
|
1073
601
|
|
|
1074
|
-
|
|
602
|
+
### Immutable Mode
|
|
1075
603
|
|
|
1076
|
-
|
|
604
|
+
SchemaShield can apply defaults, and application-provided custom keywords can modify data. Enable immutable mode when the original input must remain unchanged:
|
|
1077
605
|
|
|
1078
606
|
```javascript
|
|
1079
607
|
const schemaShield = new SchemaShield({ immutable: true });
|
|
1080
608
|
```
|
|
1081
609
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
However, there are some caveats to consider when using immutable mode. The deep copy may not accurately reproduce complex objects such as instantiated classes. In such cases, you can handle the cloning process yourself using a custom keyword to ensure the proper preservation of your data's structure.
|
|
1085
|
-
|
|
1086
|
-
## TypeScript Support
|
|
1087
|
-
|
|
1088
|
-
SchemaShield offers comprehensive TypeScript support, enhancing the library's usability for TypeScript projects. Type definitions are included in the package, so you can import the library and use it in your TypeScript projects without any additional configuration.
|
|
1089
|
-
|
|
1090
|
-
With the built in TypeScript support, you can take advantage of features like strong typing, autocompletion, and compile-time error checking, which can help you catch potential issues early and improve the overall quality of your code.
|
|
1091
|
-
|
|
1092
|
-
## Known Limitations
|
|
1093
|
-
|
|
1094
|
-
SchemaShield is optimized for local execution and strict security.
|
|
1095
|
-
|
|
1096
|
-
### 1. Dynamic ID Scope Resolution (Scope Alteration)
|
|
1097
|
-
|
|
1098
|
-
SchemaShield resolves references based on static JSON Pointers and unique IDs. It does not support changing the resolution base URI dynamically based on nested `$id` properties within sub-schemas.
|
|
1099
|
-
|
|
1100
|
-
- **Impact:** Rare edge-cases in draft-07 involving complex relative URI resolution inside nested scopes are not supported.
|
|
1101
|
-
|
|
1102
|
-
### 2. Unicode Length Validation
|
|
610
|
+
Immutable mode creates a deep copy before validation. This preserves ordinary JSON-compatible data, but cloning may not reproduce instantiated classes or other complex objects accurately. Applications that validate such values can manage cloning explicitly.
|
|
1103
611
|
|
|
1104
|
-
|
|
612
|
+
Leave immutable mode disabled when input preservation is unnecessary and validation performance is the priority.
|
|
1105
613
|
|
|
1106
|
-
|
|
614
|
+
### Remote References
|
|
1107
615
|
|
|
1108
|
-
|
|
616
|
+
SchemaShield does not retrieve remote references and does not provide an external registry or multi-document bundle API. Every referenced target must form part of the single root schema supplied to `compile()`.
|
|
1109
617
|
|
|
1110
|
-
|
|
618
|
+
### Structural Equality
|
|
1111
619
|
|
|
1112
|
-
|
|
1113
|
-
- **Future Option:** An opt-in aggressive mode based on structural hashing/bucketing could improve throughput in those extreme cases, but it is intentionally not enabled by default to avoid edge-case semantic divergence.
|
|
620
|
+
`enum`, `const`, and `uniqueItems` use exact structural comparison for predictable semantics. Very large arrays or enums containing complex objects can take longer to compare than strategies based on aggressive structural hashing.
|
|
1114
621
|
|
|
1115
622
|
## Testing
|
|
1116
623
|
|
|
1117
|
-
SchemaShield
|
|
624
|
+
SchemaShield is tested against the [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) and the package's own functional and distribution checks.
|
|
1118
625
|
|
|
1119
|
-
|
|
626
|
+
The latest observed functional suite completed with **1,635 passing** and **14 pending**. Package smokes for CJS, ESM, browser, and TypeScript declarations also passed.
|
|
1120
627
|
|
|
1121
628
|
```bash
|
|
1122
629
|
npm test
|
|
1123
|
-
# or
|
|
1124
|
-
npm run test:dev # for development
|
|
1125
630
|
```
|
|
1126
631
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
SchemaShield is an open-source project, and we welcome contributions from the community. By contributing to SchemaShield, you can help improve the library and expand its feature set.
|
|
1130
|
-
|
|
1131
|
-
If you are interested in contributing, please follow these steps:
|
|
1132
|
-
|
|
1133
|
-
- **Fork the repository:** Fork the SchemaShield repository on GitHub and clone it to your local machine.
|
|
1134
|
-
|
|
1135
|
-
- **Create a feature branch:** Create a new branch for your feature or bugfix. Make sure to give it a descriptive name.
|
|
1136
|
-
|
|
1137
|
-
- **Implement your changes:** Make the necessary changes to the codebase. Be sure to add or update the relevant tests and documentation.
|
|
632
|
+
For development:
|
|
1138
633
|
|
|
1139
|
-
|
|
634
|
+
```bash
|
|
635
|
+
npm run dev:test
|
|
636
|
+
```
|
|
1140
637
|
|
|
1141
|
-
|
|
638
|
+
## Contribute
|
|
1142
639
|
|
|
1143
|
-
|
|
640
|
+
SchemaShield is open source. Contributions that improve validation behavior, developer experience, documentation, compatibility, or performance are welcome.
|
|
1144
641
|
|
|
1145
|
-
|
|
642
|
+
Before opening a pull request:
|
|
1146
643
|
|
|
1147
|
-
|
|
644
|
+
1. Add or update the relevant tests and documentation.
|
|
645
|
+
2. Run the functional suite.
|
|
646
|
+
3. Check that the change does not introduce a performance regression.
|
|
647
|
+
4. Explain the behavior change and its motivation in the pull request.
|
|
1148
648
|
|
|
1149
649
|
## Acknowledgments
|
|
1150
650
|
|
|
1151
|
-
- **
|
|
1152
|
-
|
|
1153
|
-
- **@exodus/schemasafe**: Your interpreter-first approach validates that security-first architectures can still deliver strong performance. Competing with you makes us better.
|
|
651
|
+
- **AJV:** The performance reference for JSON Schema validation. Its JIT architecture sets the raw-throughput bar for the ecosystem.
|
|
652
|
+
- **@exodus/schemasafe:** A strong interpreter-first validator that demonstrates the value of security-focused validation architectures. Competing with it makes SchemaShield better.
|
|
1154
653
|
|
|
1155
654
|
## Legal
|
|
1156
655
|
|
|
1157
656
|
Author: [Masquerade Circus](http://masquerade-circus.net).
|
|
1158
|
-
|
|
657
|
+
|
|
658
|
+
License: [Apache-2.0](https://opensource.org/licenses/Apache-2.0).
|