@sleekcms/json-zen 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +292 -0
- package/lib/index.d.ts +25 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +326 -0
- package/lib/index.js.map +1 -0
- package/lib/lib.d.ts +9 -0
- package/lib/lib.d.ts.map +1 -0
- package/lib/lib.js +35 -0
- package/lib/lib.js.map +1 -0
- package/lib/types.d.ts +24 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +111 -0
- package/lib/types.js.map +1 -0
- package/lib/zen-error.d.ts +7 -0
- package/lib/zen-error.d.ts.map +1 -0
- package/lib/zen-error.js +17 -0
- package/lib/zen-error.js.map +1 -0
- package/package.json +55 -0
- package/src/index.ts +334 -0
- package/src/lib.ts +31 -0
- package/src/types.ts +115 -0
- package/src/zen-error.ts +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022, SleekSky LLC, California, USA
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# JSON Zen
|
|
2
|
+
|
|
3
|
+
A compact, string-based schema syntax for validating and shaping JSON objects — with far less boilerplate than JSON Schema.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @sleekcms/json-zen
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
// CommonJS
|
|
13
|
+
const { verify, check, shape, toSchema, extendTypes } = require('@sleekcms/json-zen');
|
|
14
|
+
|
|
15
|
+
// ESM / TypeScript
|
|
16
|
+
import { verify, check, shape, toSchema, extendTypes } from '@sleekcms/json-zen';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { verify, check, shape, toSchema } from '@sleekcms/json-zen';
|
|
23
|
+
|
|
24
|
+
const data = { name: 'Alice', age: 30, tags: ['admin', 'user'] };
|
|
25
|
+
|
|
26
|
+
// Validate (throws on failure)
|
|
27
|
+
verify(data, '{name:s, age:n, tags:[s]}'); // true
|
|
28
|
+
// using full type names is equivalent:
|
|
29
|
+
verify(data, '{name:string, age:number, tags:[string]}'); // true
|
|
30
|
+
|
|
31
|
+
// Safe boolean check
|
|
32
|
+
check(data, '{name:s, age:s}'); // false
|
|
33
|
+
|
|
34
|
+
// Generate a schema from an existing object
|
|
35
|
+
toSchema(data); // "{name:string,age:number,tags:[string]}"
|
|
36
|
+
|
|
37
|
+
// Fill in missing fields with typed defaults
|
|
38
|
+
shape({ name: 'Bob' }, '{name:s, age:n, active:b}');
|
|
39
|
+
// { name: 'Bob', age: 0, active: true }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Schema Syntax
|
|
45
|
+
|
|
46
|
+
Alt schemas are plain strings. Whitespace is ignored.
|
|
47
|
+
|
|
48
|
+
### Built-in Types
|
|
49
|
+
|
|
50
|
+
| Shorthand | Full name | Validates | Default (shape) |
|
|
51
|
+
|-----------|-----------|----------------------|-----------------|
|
|
52
|
+
| `s` | `string` | string | `""` |
|
|
53
|
+
| `n` | `number` | number (incl. floats)| `0` |
|
|
54
|
+
| `b` | `boolean` | boolean | `true` |
|
|
55
|
+
| — | `null` | strictly `null` | `null` |
|
|
56
|
+
| `?` | — | any value / nullable | `null` |
|
|
57
|
+
|
|
58
|
+
Shorthands and full names are interchangeable: `{a:s}` and `{a:string}` behave identically.
|
|
59
|
+
|
|
60
|
+
### Objects
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
{key:type, key:type, ...}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
verify({ a: 'hello', b: 42 }, '{a:s, b:n}'); // true — shorthands
|
|
68
|
+
verify({ a: 'hello', b: 42 }, '{a:string, b:number}'); // true — full names
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Arrays
|
|
72
|
+
|
|
73
|
+
A single type applies to every element. Multiple comma-separated types cycle by index.
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
verify([1, 2, 3], '[n]'); // true — all numbers
|
|
77
|
+
verify([1, 'x', 2, 'y'], '[n,s]'); // true — alternating number/string
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Optional fields (`?`)
|
|
81
|
+
|
|
82
|
+
Prefix a type with `?` to make it optional. A missing or `null` value passes validation.
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
verify({ a: 1 }, '{a:n, b:?s}'); // true — b is missing but optional
|
|
86
|
+
verify({ a: 1, b: null }, '{a:n, b:?s}'); // true
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Presence-only validation
|
|
90
|
+
|
|
91
|
+
Omit the type to require only that a key exists and is not `null`/`undefined`.
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
verify({ a: 1, b: false }, '{a, b}'); // true
|
|
95
|
+
verify({ a: 1, b: null }, '{a, b}'); // throws — b is null
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Default values (`type:default`)
|
|
99
|
+
|
|
100
|
+
Attach a default after the type, used by `shape` to fill missing values.
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
shape({}, '{name:s:anonymous, count:n:0}');
|
|
104
|
+
// { name: 'anonymous', count: 0 }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Use quotes for defaults containing spaces:
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
shape({}, '{title:s:"Hello World"}');
|
|
111
|
+
// { title: 'Hello World' }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Wildcard key (`*`)
|
|
115
|
+
|
|
116
|
+
`*:type` matches any key not already listed in the schema.
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
verify({ a: 1, b: 2, c: 3 }, '{*:n}'); // true
|
|
120
|
+
verify({ a: 1, b: '2' }, '{a:n, *:s}'); // true — b matched by wildcard
|
|
121
|
+
shape({ x: 1, y: 2, z: 3 }, '{*:n}'); // { x: 1, y: 2, z: 3 }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Nested objects and arrays
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
const schema = '{user:{name:s, scores:[n]}, active:b}';
|
|
128
|
+
|
|
129
|
+
verify({ user: { name: 'Alice', scores: [10, 20] }, active: true }, schema); // true
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## API
|
|
135
|
+
|
|
136
|
+
### `verify(json, schema, options?)`
|
|
137
|
+
|
|
138
|
+
Validates `json` against `schema`. Returns `true` on success, throws an `ZenError` on failure.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
verify({ a: 1 }, '{a:s}');
|
|
142
|
+
// throws: "json.a: validation failed"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Access all failures at once via `error.errors`:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
try {
|
|
149
|
+
verify({}, '{a:n, b:n}');
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.log(e.message); // "Validation failed: 2 errors found"
|
|
152
|
+
console.log(e.errors); // [['json.a', 'is required'], ['json.b', 'is required']]
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Options:**
|
|
157
|
+
|
|
158
|
+
| Option | Type | Description |
|
|
159
|
+
|---------|----------|---------------------------------------------------|
|
|
160
|
+
| `_path` | `string` | Override the root label in error messages |
|
|
161
|
+
| _(key)_ | validator | Inline custom type — see [Custom Validators](#custom-validators) |
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
verify({ a: 'x' }, '{a:s}', { _path: 'payload' });
|
|
165
|
+
// throws: "payload.a: validation failed"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### `check(json, schema, options?)`
|
|
171
|
+
|
|
172
|
+
Same as `verify` but returns `false` instead of throwing. Schema errors still throw.
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
check({ a: 1, b: 'x' }, '{a:n, b:n}'); // false
|
|
176
|
+
check({ a: 1, b: 2 }, '{a:n, b:n}'); // true
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
### `shape(json, schema, options?)`
|
|
182
|
+
|
|
183
|
+
Returns a new object shaped to the schema. Values from `json` are used when valid; otherwise, typed defaults fill in the gaps. Extra keys in `json` not in the schema are omitted.
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
shape({ a: 1, extra: 99 }, '{a:n, b:s, c:b}');
|
|
187
|
+
// { a: 1, b: '', c: true }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Nested structures are shaped recursively:
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
shape({ a: {} }, '{a:{x:n, y:s}}');
|
|
194
|
+
// { a: { x: 0, y: '' } }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Options:**
|
|
198
|
+
|
|
199
|
+
| Option | Type | Description |
|
|
200
|
+
|-------------|-----------|--------------------------------------------------------------|
|
|
201
|
+
| `_optional` | `boolean` | When `true`, fill optional (`?`) fields with defaults instead of `null` |
|
|
202
|
+
| _(key)_ | validator | Inline custom type |
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
shape({}, '{a:?n}'); // { a: null }
|
|
206
|
+
shape({}, '{a:?n}', { _optional: true }); // { a: 0 }
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### `toSchema(json)`
|
|
212
|
+
|
|
213
|
+
Generates a schema string from a JSON object. Useful for bootstrapping a schema from a known-good example.
|
|
214
|
+
|
|
215
|
+
```js
|
|
216
|
+
toSchema({ a: 'foo', b: 1, c: [1, 2], d: { e: null } });
|
|
217
|
+
// "{a:string,b:number,c:[number],d:{e:?}}"
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
### `extendTypes(validators)`
|
|
223
|
+
|
|
224
|
+
Registers one or more custom types globally. Once registered, the type name can be used in any schema.
|
|
225
|
+
|
|
226
|
+
The validator function receives the value when checking, and `undefined` when `shape` needs a default — return the default value in that case.
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
import { extendTypes, shape, verify } from '@sleekcms/json-zen';
|
|
230
|
+
|
|
231
|
+
extendTypes({
|
|
232
|
+
url: (value) => {
|
|
233
|
+
if (value === undefined) return 'https://example.com'; // default for shape
|
|
234
|
+
return typeof value === 'string' && value.startsWith('http');
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
verify({ img: 'https://example.com/pic.jpg' }, '{img:url}'); // true
|
|
239
|
+
shape({}, '{img:url}'); // { img: 'https://example.com' }
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Custom Validators
|
|
245
|
+
|
|
246
|
+
Custom validators can be passed inline via `options`, registered globally via `extendTypes`, or provided as enum arrays or RegExp patterns.
|
|
247
|
+
|
|
248
|
+
### Inline function
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
verify({ status: 'active' }, '{status:myType}', {
|
|
252
|
+
myType: (value) => value === 'active' || value === 'inactive'
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Enum (array)
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
verify({ role: 'admin' }, '{role:r}', { r: ['admin', 'user', 'guest'] }); // true
|
|
260
|
+
verify({ role: 'root' }, '{role:r}', { r: ['admin', 'user', 'guest'] }); // throws
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Regex
|
|
264
|
+
|
|
265
|
+
```js
|
|
266
|
+
verify({ slug: 'hello-world' }, '{slug:sl}', { sl: /^[a-z-]+$/ }); // true
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Context-aware validator
|
|
270
|
+
|
|
271
|
+
Validators receive `{ path, json, parent }` as a second argument, enabling cross-field validation:
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
extendTypes({
|
|
275
|
+
matchesType: (value, { parent }) => {
|
|
276
|
+
if (parent.type === 'number') return typeof value === 'number';
|
|
277
|
+
if (parent.type === 'string') return typeof value === 'string';
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
verify(
|
|
283
|
+
[{ type: 'number', value: 42 }, { type: 'string', value: 'hi' }],
|
|
284
|
+
'[{type:s, value:matchesType}]'
|
|
285
|
+
); // true
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT © Yusuf Bhabhrawala, SleekSky LLC
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author: Yusuf Bhabhrawala
|
|
3
|
+
*/
|
|
4
|
+
export declare const setEnv: (e: string | null) => void;
|
|
5
|
+
type FlattenResult = [string, string[], string[]];
|
|
6
|
+
export declare const _flatten: (schema: string) => FlattenResult;
|
|
7
|
+
export type TypeShape = string | TypeShape[] | {
|
|
8
|
+
[key: string]: TypeShape;
|
|
9
|
+
};
|
|
10
|
+
export declare const typeShape: (schema: string) => TypeShape;
|
|
11
|
+
export declare const toSchema: (json: unknown) => string;
|
|
12
|
+
export interface ShapeOptions {
|
|
13
|
+
_optional?: boolean;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
export declare const shape: <T = unknown>(json: unknown, schema: string, options?: ShapeOptions) => T;
|
|
17
|
+
export interface VerifyOptions {
|
|
18
|
+
_path?: string;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
export declare const verify: (json: unknown, schema: string, options?: VerifyOptions) => boolean | undefined;
|
|
22
|
+
export declare const check: (json: unknown, schema: string, options?: VerifyOptions) => boolean;
|
|
23
|
+
export declare const extendTypes: (types: Record<string, unknown>) => void;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAiBH,eAAO,MAAM,MAAM,GAAI,GAAG,MAAM,GAAG,IAAI,KAAG,IAEzC,CAAC;AAEF,KAAK,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AAElD,eAAO,MAAM,QAAQ,GAAI,QAAQ,MAAM,KAAG,aAqCzC,CAAC;AAQF,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAG5E,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,SAqC1C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,MAAM,OAAO,KAAG,MAiBxC,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,KAAK,GAAI,CAAC,GAAG,OAAO,EAAE,MAAM,OAAO,EAAE,QAAQ,MAAM,EAAE,UAAS,YAAiB,KAAG,CAsF9F,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,MAAM,GAAI,MAAM,OAAO,EAAE,QAAQ,MAAM,EAAE,UAAS,aAAkB,KAAG,OAAO,GAAG,SA6F7F,CAAC;AAGF,eAAO,MAAM,KAAK,GAAI,MAAM,OAAO,EAAE,QAAQ,MAAM,EAAE,UAAU,aAAa,KAAG,OAQ9E,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,IAE5D,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Author: Yusuf Bhabhrawala
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.extendTypes = exports.check = exports.verify = exports.shape = exports.toSchema = exports.typeShape = exports._flatten = exports.setEnv = void 0;
|
|
10
|
+
const types_1 = __importDefault(require("./types"));
|
|
11
|
+
const zen_error_1 = __importDefault(require("./zen-error"));
|
|
12
|
+
const RX = {
|
|
13
|
+
FLAT_ARRAY: /(\[([^\[\]\{\}]*)\])/,
|
|
14
|
+
FLAT_OBJECT: /(\{([^\{\}\[\]]*)\})/,
|
|
15
|
+
MALFORMED: /[\[\]\{\}]/,
|
|
16
|
+
FLAT_SCALAR: /^[^\[\]\{\}]*$/,
|
|
17
|
+
OPTIONAL: /^[\?]/,
|
|
18
|
+
LOOKUP: /^[0-9]+$/,
|
|
19
|
+
DEFAULTS: /^\$([0-9]+)$/
|
|
20
|
+
};
|
|
21
|
+
let env = null;
|
|
22
|
+
const setEnv = (e) => {
|
|
23
|
+
env = e;
|
|
24
|
+
};
|
|
25
|
+
exports.setEnv = setEnv;
|
|
26
|
+
const _flatten = (schema) => {
|
|
27
|
+
const lookups = [];
|
|
28
|
+
const default_strings = [];
|
|
29
|
+
let m;
|
|
30
|
+
// convert "hello world" to $0. remote " (quotes)
|
|
31
|
+
while ((m = schema.match(/"([^"]+)"/))) {
|
|
32
|
+
schema = schema.substr(0, m.index) + `$${default_strings.length}` + schema.substr(m.index + m[0].length);
|
|
33
|
+
default_strings.push(m[1]);
|
|
34
|
+
}
|
|
35
|
+
while ((m = schema.match(/'([^']+)'/))) {
|
|
36
|
+
schema = schema.substr(0, m.index) + `$${default_strings.length}` + schema.substr(m.index + m[0].length);
|
|
37
|
+
default_strings.push(m[1]);
|
|
38
|
+
}
|
|
39
|
+
if (schema.match(/"/))
|
|
40
|
+
throw new Error("Missing closing quote");
|
|
41
|
+
// strip all white spaces
|
|
42
|
+
schema = schema.replace(/\s/g, "");
|
|
43
|
+
function reduce(schema) {
|
|
44
|
+
let m;
|
|
45
|
+
let found = false;
|
|
46
|
+
while ((m = schema.match(RX.FLAT_ARRAY)) || (m = schema.match(RX.FLAT_OBJECT))) {
|
|
47
|
+
schema = schema.substr(0, m.index) + lookups.length + schema.substr(m.index + m[0].length);
|
|
48
|
+
lookups.push(m[0]);
|
|
49
|
+
found = true;
|
|
50
|
+
}
|
|
51
|
+
if (found)
|
|
52
|
+
return reduce(schema);
|
|
53
|
+
if (!found && schema.match(RX.MALFORMED)) {
|
|
54
|
+
throw new Error("Schema error: probably invalid braces or brackets");
|
|
55
|
+
}
|
|
56
|
+
return schema;
|
|
57
|
+
}
|
|
58
|
+
schema = reduce(schema);
|
|
59
|
+
return [schema, lookups, default_strings];
|
|
60
|
+
};
|
|
61
|
+
exports._flatten = _flatten;
|
|
62
|
+
function splitOnce(str, delim) {
|
|
63
|
+
const parts = str.split(delim);
|
|
64
|
+
const first = parts.shift();
|
|
65
|
+
return [first, parts.join(delim)];
|
|
66
|
+
}
|
|
67
|
+
// Set leaf types to format "optional:type:default_value" tuple. Eg. "?:boolean:false" or ":integer:"
|
|
68
|
+
const typeShape = (schema) => {
|
|
69
|
+
let [sch, lookups, default_strings] = (0, exports._flatten)(schema);
|
|
70
|
+
function traverse(sch) {
|
|
71
|
+
if (!sch)
|
|
72
|
+
return "";
|
|
73
|
+
let m;
|
|
74
|
+
if (sch.match(RX.LOOKUP))
|
|
75
|
+
return traverse(lookups[Number(sch)]);
|
|
76
|
+
if ((m = sch.match(RX.FLAT_ARRAY))) {
|
|
77
|
+
sch = m[2];
|
|
78
|
+
const schema_parts = sch.split(",");
|
|
79
|
+
return schema_parts.length > 0 ? [traverse(schema_parts[0])] : [traverse("")];
|
|
80
|
+
}
|
|
81
|
+
if ((m = sch.match(RX.FLAT_OBJECT))) {
|
|
82
|
+
sch = m[2];
|
|
83
|
+
return sch.split(",").reduce((acc, name) => {
|
|
84
|
+
let [k, v] = splitOnce(name, ":");
|
|
85
|
+
if (k.match(RX.DEFAULTS))
|
|
86
|
+
k = default_strings[Number(k.substr(1))];
|
|
87
|
+
acc[k] = traverse(v);
|
|
88
|
+
return acc;
|
|
89
|
+
}, {});
|
|
90
|
+
}
|
|
91
|
+
if (sch.match(RX.FLAT_SCALAR)) {
|
|
92
|
+
let [type, def] = sch.split(":");
|
|
93
|
+
if (!def)
|
|
94
|
+
def = "";
|
|
95
|
+
if (def && def.match(RX.DEFAULTS))
|
|
96
|
+
def = default_strings[Number(def.substr(1))];
|
|
97
|
+
let optional = '';
|
|
98
|
+
if (type.match(RX.OPTIONAL)) {
|
|
99
|
+
optional = '?';
|
|
100
|
+
type = type.substr(1);
|
|
101
|
+
}
|
|
102
|
+
const type_lookup = { i: "integer", n: "number", b: "boolean", s: "string" };
|
|
103
|
+
if (type_lookup[type])
|
|
104
|
+
type = type_lookup[type];
|
|
105
|
+
return [optional, type, def].join(':');
|
|
106
|
+
}
|
|
107
|
+
return "";
|
|
108
|
+
}
|
|
109
|
+
return traverse(sch);
|
|
110
|
+
};
|
|
111
|
+
exports.typeShape = typeShape;
|
|
112
|
+
const toSchema = (json) => {
|
|
113
|
+
function traverse(obj) {
|
|
114
|
+
const type = types_1.default.toSchema(obj);
|
|
115
|
+
if (type === "array") {
|
|
116
|
+
const arr = obj;
|
|
117
|
+
const sch = arr.length > 0 ? traverse(arr[0]) : "";
|
|
118
|
+
return `[${sch}]`;
|
|
119
|
+
}
|
|
120
|
+
if (type === "object") {
|
|
121
|
+
const objRecord = obj;
|
|
122
|
+
return '{' + Object.keys(objRecord).map((k) => {
|
|
123
|
+
return `${k}:${traverse(objRecord[k])}`;
|
|
124
|
+
}).join(",") + '}';
|
|
125
|
+
}
|
|
126
|
+
return type;
|
|
127
|
+
}
|
|
128
|
+
return traverse(json);
|
|
129
|
+
};
|
|
130
|
+
exports.toSchema = toSchema;
|
|
131
|
+
const shape = (json, schema, options = {}) => {
|
|
132
|
+
const types = new types_1.default(options);
|
|
133
|
+
options._optional = options._optional || false;
|
|
134
|
+
let lookups;
|
|
135
|
+
let default_strings;
|
|
136
|
+
[schema, lookups, default_strings] = (0, exports._flatten)(schema);
|
|
137
|
+
const getValue = (type, value, def) => {
|
|
138
|
+
let m;
|
|
139
|
+
if (def && (m = def.match(RX.DEFAULTS)))
|
|
140
|
+
def = default_strings[Number(m[1])];
|
|
141
|
+
if (types.has(type)) {
|
|
142
|
+
return (value && types.check(type, value)) ? value : (def !== undefined) ? types.cast(type, def) : types.sample(type);
|
|
143
|
+
}
|
|
144
|
+
return value || def || "Not defined!";
|
|
145
|
+
};
|
|
146
|
+
// flat validator value = {k:t:d,..} [t:d,..] t:d
|
|
147
|
+
function traverse({ value, schema }) {
|
|
148
|
+
if (!schema)
|
|
149
|
+
schema = "";
|
|
150
|
+
let m;
|
|
151
|
+
let optional = false;
|
|
152
|
+
if (schema.match(RX.OPTIONAL)) {
|
|
153
|
+
schema = schema.substr(1);
|
|
154
|
+
optional = true;
|
|
155
|
+
}
|
|
156
|
+
// if lookup, validate further
|
|
157
|
+
if (schema.match(RX.LOOKUP))
|
|
158
|
+
return traverse({ value, schema: `${optional ? '?' : ''}${lookups[Number(schema)]}` });
|
|
159
|
+
if ((value === null || value === undefined) && optional && !options._optional)
|
|
160
|
+
return null;
|
|
161
|
+
if (schema.match(RX.FLAT_SCALAR)) {
|
|
162
|
+
// if scalar
|
|
163
|
+
let type;
|
|
164
|
+
let def;
|
|
165
|
+
[type, def] = schema.split(":");
|
|
166
|
+
return getValue(type, value, def);
|
|
167
|
+
}
|
|
168
|
+
else if ((m = schema.match(RX.FLAT_ARRAY))) {
|
|
169
|
+
// if array
|
|
170
|
+
schema = m[2];
|
|
171
|
+
const schema_parts = schema.split(",");
|
|
172
|
+
let arr = value;
|
|
173
|
+
if (!Array.isArray(value))
|
|
174
|
+
arr = schema_parts.map(() => null);
|
|
175
|
+
if (arr.length < schema_parts.length) {
|
|
176
|
+
arr = arr.concat(schema_parts.slice(arr.length).map(() => null));
|
|
177
|
+
}
|
|
178
|
+
return arr.map((v, i) => traverse({ value: v, schema: schema_parts[i % schema_parts.length] }));
|
|
179
|
+
}
|
|
180
|
+
else if ((m = schema.match(RX.FLAT_OBJECT))) {
|
|
181
|
+
// if object
|
|
182
|
+
let obj = value;
|
|
183
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
184
|
+
obj = {};
|
|
185
|
+
}
|
|
186
|
+
schema = m[2];
|
|
187
|
+
if (schema === "")
|
|
188
|
+
return obj;
|
|
189
|
+
let wildcard = false;
|
|
190
|
+
const shp = schema.split(",").reduce((acc, name) => {
|
|
191
|
+
let [k, t, d] = name.split(":");
|
|
192
|
+
if (!t)
|
|
193
|
+
t = "";
|
|
194
|
+
if (d)
|
|
195
|
+
t += ":" + d;
|
|
196
|
+
if (k === "*")
|
|
197
|
+
wildcard = t;
|
|
198
|
+
else
|
|
199
|
+
acc[k] = traverse({ value: obj[k], schema: t });
|
|
200
|
+
return acc;
|
|
201
|
+
}, {});
|
|
202
|
+
if (wildcard !== false) {
|
|
203
|
+
for (const k in obj)
|
|
204
|
+
if (shp[k] === undefined)
|
|
205
|
+
shp[k] = traverse({ value: obj[k], schema: wildcard });
|
|
206
|
+
}
|
|
207
|
+
return shp;
|
|
208
|
+
}
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
211
|
+
const result = traverse({ value: json, schema });
|
|
212
|
+
return result;
|
|
213
|
+
};
|
|
214
|
+
exports.shape = shape;
|
|
215
|
+
const verify = (json, schema, options = {}) => {
|
|
216
|
+
if (env && env !== "development")
|
|
217
|
+
return undefined;
|
|
218
|
+
const types = new types_1.default(options);
|
|
219
|
+
const errors = [];
|
|
220
|
+
const jsonPath = options._path || 'json';
|
|
221
|
+
let lookups;
|
|
222
|
+
[schema, lookups] = (0, exports._flatten)(schema);
|
|
223
|
+
// flat validator value = {k:t:d,..} [t:d,..] t:d
|
|
224
|
+
function validate({ path, value, schema, parent = null }) {
|
|
225
|
+
let m;
|
|
226
|
+
let optional = false;
|
|
227
|
+
if (schema.match(RX.OPTIONAL)) {
|
|
228
|
+
schema = schema.substr(1);
|
|
229
|
+
if (value === undefined || value === null)
|
|
230
|
+
return true;
|
|
231
|
+
optional = true;
|
|
232
|
+
}
|
|
233
|
+
// if lookup, validate further
|
|
234
|
+
if (schema.match(RX.LOOKUP))
|
|
235
|
+
return validate({ path: `${path}`, value, schema: `${optional ? '?' : ''}${lookups[Number(schema)]}`, parent: parent });
|
|
236
|
+
if (schema.match(RX.FLAT_SCALAR)) {
|
|
237
|
+
// if scalar
|
|
238
|
+
let def;
|
|
239
|
+
[schema, def] = schema.split(":");
|
|
240
|
+
if (value === undefined || value === null) {
|
|
241
|
+
errors.push([path, 'is required']);
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
if (schema === "")
|
|
245
|
+
return true; // no validation needed
|
|
246
|
+
if (!types.has(schema))
|
|
247
|
+
throw new Error(`Schema error: Validator - ${schema} - not found`);
|
|
248
|
+
else if (types.check(schema, value, { path, json, parent }))
|
|
249
|
+
return true;
|
|
250
|
+
else {
|
|
251
|
+
errors.push([path, 'validation failed']);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else if ((m = schema.match(RX.FLAT_ARRAY))) {
|
|
256
|
+
// if array
|
|
257
|
+
if (!Array.isArray(value)) {
|
|
258
|
+
errors.push([path, 'should be array']);
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
schema = m[2];
|
|
262
|
+
const schema_parts = schema.split(",");
|
|
263
|
+
for (const i in value) {
|
|
264
|
+
validate({ path: `${path}.${i}`, value: value[i], schema: schema_parts[Number(i) % schema_parts.length], parent: value });
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
else if ((m = schema.match(RX.FLAT_OBJECT))) {
|
|
269
|
+
// if object
|
|
270
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
271
|
+
errors.push([path, 'should be object']);
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
schema = m[2];
|
|
275
|
+
let wildcard = false;
|
|
276
|
+
if (schema !== "") {
|
|
277
|
+
const obj = value;
|
|
278
|
+
const keys = schema.split(",").reduce((acc, name) => {
|
|
279
|
+
let [k, t] = name.split(":");
|
|
280
|
+
if (!t)
|
|
281
|
+
t = "";
|
|
282
|
+
if (k === '*')
|
|
283
|
+
wildcard = t;
|
|
284
|
+
else
|
|
285
|
+
acc[k] = t;
|
|
286
|
+
return acc;
|
|
287
|
+
}, {});
|
|
288
|
+
if (wildcard !== false) {
|
|
289
|
+
for (const k in obj)
|
|
290
|
+
if (keys[k] === undefined)
|
|
291
|
+
keys[k] = wildcard;
|
|
292
|
+
}
|
|
293
|
+
// if object, validate for all k-v
|
|
294
|
+
for (const k in keys)
|
|
295
|
+
validate({ path: `${path}.${k}`, value: obj[k], schema: keys[k], parent: value });
|
|
296
|
+
}
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
validate({ path: jsonPath, value: json, schema });
|
|
302
|
+
if (errors.length > 0)
|
|
303
|
+
throw new zen_error_1.default(errors);
|
|
304
|
+
return true;
|
|
305
|
+
};
|
|
306
|
+
exports.verify = verify;
|
|
307
|
+
// same as verify. returns boolean. won't throw.
|
|
308
|
+
const check = (json, schema, options) => {
|
|
309
|
+
try {
|
|
310
|
+
(0, exports.verify)(json, schema, options);
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (error instanceof Error && error.message.match(/^Schema error/))
|
|
315
|
+
throw error;
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
exports.check = check;
|
|
320
|
+
const extendTypes = (types) => {
|
|
321
|
+
types_1.default.extend(types);
|
|
322
|
+
};
|
|
323
|
+
exports.extendTypes = extendTypes;
|
|
324
|
+
// CommonJS compatibility
|
|
325
|
+
module.exports = { verify: exports.verify, check: exports.check, shape: exports.shape, toSchema: exports.toSchema, _flatten: exports._flatten, typeShape: exports.typeShape, extendTypes: exports.extendTypes, setEnv: exports.setEnv };
|
|
326
|
+
//# sourceMappingURL=index.js.map
|