@takeshape/json-schema 7.194.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 +5 -0
- package/es/index.js +1 -0
- package/es/schema-validator.js +223 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +18 -0
- package/lib/schema-validator.d.ts +33 -0
- package/lib/schema-validator.d.ts.map +1 -0
- package/lib/schema-validator.js +260 -0
- package/package.json +45 -0
package/README.md
ADDED
package/es/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './schema-validator';
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import Ajv, { ErrorObject } from 'ajv';
|
|
2
|
+
import addFormats from 'ajv-formats';
|
|
3
|
+
import get from 'lodash/get';
|
|
4
|
+
export function parseDataPath(dataPath) {
|
|
5
|
+
return dataPath.substr(1).split('/');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function getData(error, data) {
|
|
9
|
+
return get(data, parseDataPath(error.dataPath));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function ignoreMissing(error) {
|
|
13
|
+
// ignore top level missing errors
|
|
14
|
+
return !(error.dataPath === '' && error.keyword === 'required');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function ignoreNull(error, data, schema) {
|
|
18
|
+
return !((error.keyword === 'type' || error.keyword === 'oneOf') && getData(error, data) === null && !isInvalidPropertyRequired(schema, error));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getErrorFilter(params, data, schema) {
|
|
22
|
+
return error => (!params.ignoreMissing || ignoreMissing(error)) && (!params.ignoreNulls || ignoreNull(error, data, schema));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isInvalidPropertyRequired(schema, error) {
|
|
26
|
+
const dataPath = parseDataPath(error.dataPath);
|
|
27
|
+
const parentDataPath = dataPath.slice(0, dataPath.length - 1);
|
|
28
|
+
const parentSchema = followSchemaPath(schema, parentDataPath);
|
|
29
|
+
const name = getName(error.dataPath);
|
|
30
|
+
|
|
31
|
+
if (!parentSchema) {
|
|
32
|
+
throw new Error('Unexpected error cannot find parent schema');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return isRequired(schema, parentSchema, name);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isRequired(topLevelSchema, schema, name) {
|
|
39
|
+
const schemas = schema.allOf || schema.anyOf || schema.oneOf || [schema];
|
|
40
|
+
return schemas.some(childSchema => {
|
|
41
|
+
var _followRef, _followRef$required;
|
|
42
|
+
|
|
43
|
+
return (_followRef = followRef(topLevelSchema, childSchema)) === null || _followRef === void 0 ? void 0 : (_followRef$required = _followRef.required) === null || _followRef$required === void 0 ? void 0 : _followRef$required.find(prop => prop === name);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getName(path) {
|
|
48
|
+
const parts = parseDataPath(path);
|
|
49
|
+
return parts[parts.length - 1];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function refToPath(ref) {
|
|
53
|
+
return ref.substr(2).split('/');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function followRef(topLevelSchema, schema) {
|
|
57
|
+
return schema.$ref ? get(topLevelSchema, refToPath(schema.$ref)) : schema;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Given a schema object traverse it using a "dataPath" and return the schema at that path
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
export function followSchemaPath(topLevelSchema, dataPath) {
|
|
65
|
+
const followPath = (schema, path) => {
|
|
66
|
+
var _schema, _schema$properties;
|
|
67
|
+
|
|
68
|
+
schema = followRef(topLevelSchema, schema);
|
|
69
|
+
|
|
70
|
+
if (path.length === 0) {
|
|
71
|
+
return schema;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const combinedSchemas = schema.allOf || schema.anyOf || schema.oneOf;
|
|
75
|
+
|
|
76
|
+
if (combinedSchemas) {
|
|
77
|
+
for (const childSchema of combinedSchemas) {
|
|
78
|
+
const result = followPath(childSchema, path);
|
|
79
|
+
|
|
80
|
+
if (result) {
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const [first, ...rest] = path;
|
|
87
|
+
|
|
88
|
+
if (schema.items && /^\d+$/.exec(first)) {
|
|
89
|
+
return followPath(schema.items, rest);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const prop = (_schema = schema) === null || _schema === void 0 ? void 0 : (_schema$properties = _schema.properties) === null || _schema$properties === void 0 ? void 0 : _schema$properties[first];
|
|
93
|
+
|
|
94
|
+
if (prop) {
|
|
95
|
+
return rest.length ? followPath(prop, rest) : prop;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return followPath(topLevelSchema, dataPath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isSchemaObject(schema) {
|
|
103
|
+
return typeof schema === 'object';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function validate(ajv, id, data, options) {
|
|
107
|
+
const params = {
|
|
108
|
+
ignoreMissing: false,
|
|
109
|
+
ignoreNulls: true,
|
|
110
|
+
errorsText: false,
|
|
111
|
+
...options
|
|
112
|
+
};
|
|
113
|
+
let valid = ajv.validate(id, data);
|
|
114
|
+
let errors;
|
|
115
|
+
|
|
116
|
+
if (valid) {
|
|
117
|
+
errors = [];
|
|
118
|
+
} else {
|
|
119
|
+
errors = ajv.errors;
|
|
120
|
+
|
|
121
|
+
if (params.ignoreNulls || params.ignoreMissing) {
|
|
122
|
+
var _ajv$getSchema;
|
|
123
|
+
|
|
124
|
+
const schema = (_ajv$getSchema = ajv.getSchema(id)) === null || _ajv$getSchema === void 0 ? void 0 : _ajv$getSchema.schema;
|
|
125
|
+
|
|
126
|
+
if (isSchemaObject(schema)) {
|
|
127
|
+
errors = errors.filter(getErrorFilter(params, data, schema));
|
|
128
|
+
valid = errors.length === 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const errorsText = params.errorsText && !valid ? ajv.errorsText(errors) : '';
|
|
134
|
+
return {
|
|
135
|
+
valid,
|
|
136
|
+
errors,
|
|
137
|
+
errorsText
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export { Ajv, ErrorObject };
|
|
141
|
+
export function createAjv(options) {
|
|
142
|
+
const ajv = new Ajv({
|
|
143
|
+
allErrors: true,
|
|
144
|
+
unevaluated: true,
|
|
145
|
+
strict: false,
|
|
146
|
+
...options
|
|
147
|
+
});
|
|
148
|
+
addFormats(ajv);
|
|
149
|
+
/**
|
|
150
|
+
* Formats ingested by Stripe OpenAPI and possibly other — trigger warnings
|
|
151
|
+
* in the client when Stripe types are loaded.
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
ajv.addFormat('unix-time', /^[0-9]{10}$/);
|
|
155
|
+
ajv.addFormat('decimal', /^[0-9]+\.[0-9]{1,12}$/);
|
|
156
|
+
return ajv;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Determine whether a string is a valid regular expression
|
|
160
|
+
*/
|
|
161
|
+
|
|
162
|
+
function isValidRegex(str) {
|
|
163
|
+
try {
|
|
164
|
+
// eslint-disable-next-line no-new,security-node/non-literal-reg-expr
|
|
165
|
+
new RegExp(str, 'u');
|
|
166
|
+
return true;
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Fix schema by removing any broken regexes that will cause ajv to throw a compile error.
|
|
173
|
+
* It is better to remove the invalid regex rather than forgo validation altogether
|
|
174
|
+
* This should be fixed in ajv see the comment here
|
|
175
|
+
* https://github.com/ajv-validator/ajv/blob/9f1c3eaa4b91ca17b72b122cdac9b108d1ac30cb/lib/vocabularies/validation/pattern.ts#L21
|
|
176
|
+
*/
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
export function fixSchema(schema) {
|
|
180
|
+
const normalize = obj => {
|
|
181
|
+
if (Array.isArray(obj)) {
|
|
182
|
+
return obj.map(normalize);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (obj && typeof obj === 'object') {
|
|
186
|
+
const result = {};
|
|
187
|
+
|
|
188
|
+
for (const key of Object.keys(obj)) {
|
|
189
|
+
if (key !== 'pattern' || obj.type !== 'string' || isValidRegex(obj[key])) {
|
|
190
|
+
result[key] = normalize(obj[key]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return obj;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
return normalize(schema);
|
|
201
|
+
}
|
|
202
|
+
export function createSchemaValidator(schema, metaSchemas = [], options = {}) {
|
|
203
|
+
const schemas = Array.isArray(schema) ? schema : [schema];
|
|
204
|
+
const ajv = createAjv({
|
|
205
|
+
removeAdditional: true,
|
|
206
|
+
...options
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
if (metaSchemas) {
|
|
210
|
+
metaSchemas.forEach(metaSchema => ajv.addMetaSchema(metaSchema));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
schemas.forEach(schema => ajv.addSchema(fixSchema(schema)));
|
|
214
|
+
const defaultRef = schemas[0].$id;
|
|
215
|
+
|
|
216
|
+
if (!defaultRef) {
|
|
217
|
+
throw Error('Failed to create schema validator: schema is missing $id');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return (data, options) => {
|
|
221
|
+
return validate(ajv, defaultRef, data, options);
|
|
222
|
+
};
|
|
223
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
var _schemaValidator = require("./schema-validator");
|
|
8
|
+
|
|
9
|
+
Object.keys(_schemaValidator).forEach(function (key) {
|
|
10
|
+
if (key === "default" || key === "__esModule") return;
|
|
11
|
+
if (key in exports && exports[key] === _schemaValidator[key]) return;
|
|
12
|
+
Object.defineProperty(exports, key, {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _schemaValidator[key];
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Ajv, { ErrorObject, Options, SchemaObject } from 'ajv';
|
|
2
|
+
export declare type Data = any;
|
|
3
|
+
export interface ValidateParams {
|
|
4
|
+
ignoreMissing: boolean;
|
|
5
|
+
ignoreNulls: boolean;
|
|
6
|
+
errorsText: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ValidateResult {
|
|
9
|
+
valid: boolean;
|
|
10
|
+
errorsText: string;
|
|
11
|
+
errors: ErrorObject[];
|
|
12
|
+
}
|
|
13
|
+
export declare type Validator = (data: Data, options?: Partial<ValidateParams>) => ValidateResult;
|
|
14
|
+
export declare function parseDataPath(dataPath: string): string[];
|
|
15
|
+
export declare function isInvalidPropertyRequired(schema: SchemaObject, error: ErrorObject): boolean;
|
|
16
|
+
export declare function refToPath(ref: string): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Given a schema object traverse it using a "dataPath" and return the schema at that path
|
|
19
|
+
*/
|
|
20
|
+
export declare function followSchemaPath(topLevelSchema: SchemaObject, dataPath: string[]): SchemaObject | undefined;
|
|
21
|
+
export declare type MinimalAjv = Pick<Ajv, 'validate' | 'errors' | 'getSchema' | 'errorsText'>;
|
|
22
|
+
export declare function validate(ajv: MinimalAjv, id: string, data: Data, options?: Partial<ValidateParams>): ValidateResult;
|
|
23
|
+
export { Ajv, ErrorObject };
|
|
24
|
+
export declare function createAjv(options?: Options): Ajv;
|
|
25
|
+
/**
|
|
26
|
+
* Fix schema by removing any broken regexes that will cause ajv to throw a compile error.
|
|
27
|
+
* It is better to remove the invalid regex rather than forgo validation altogether
|
|
28
|
+
* This should be fixed in ajv see the comment here
|
|
29
|
+
* https://github.com/ajv-validator/ajv/blob/9f1c3eaa4b91ca17b72b122cdac9b108d1ac30cb/lib/vocabularies/validation/pattern.ts#L21
|
|
30
|
+
*/
|
|
31
|
+
export declare function fixSchema(schema: SchemaObject): SchemaObject;
|
|
32
|
+
export declare function createSchemaValidator(schema: SchemaObject | SchemaObject[], metaSchemas?: SchemaObject[], options?: Options): Validator;
|
|
33
|
+
//# sourceMappingURL=schema-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-validator.d.ts","sourceRoot":"","sources":["../../src/schema-validator.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,EAAE,EAAC,WAAW,EAAE,OAAO,EAAE,YAAY,EAAC,MAAM,KAAK,CAAC;AAI5D,oBAAY,IAAI,GAAG,GAAG,CAAC;AAEvB,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,oBAAY,SAAS,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,KAAK,cAAc,CAAC;AAE1F,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAExD;AAwBD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAW3F;AAcD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/C;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,YAAY,GAAG,SAAS,CA6B3G;AAED,oBAAY,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,CAAC,CAAC;AAMvF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CA2BnH;AAED,OAAO,EAAC,GAAG,EAAE,WAAW,EAAC,CAAC;AAC1B,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,GAAG,CAkBhD;AAeD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAiB5D;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,GAAG,YAAY,EAAE,EACrC,WAAW,GAAE,YAAY,EAAO,EAChC,OAAO,GAAE,OAAY,GACpB,SAAS,CAqBX"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.parseDataPath = parseDataPath;
|
|
7
|
+
exports.isInvalidPropertyRequired = isInvalidPropertyRequired;
|
|
8
|
+
exports.refToPath = refToPath;
|
|
9
|
+
exports.followSchemaPath = followSchemaPath;
|
|
10
|
+
exports.validate = validate;
|
|
11
|
+
exports.createAjv = createAjv;
|
|
12
|
+
exports.fixSchema = fixSchema;
|
|
13
|
+
exports.createSchemaValidator = createSchemaValidator;
|
|
14
|
+
Object.defineProperty(exports, "Ajv", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () {
|
|
17
|
+
return _ajv.default;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(exports, "ErrorObject", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function () {
|
|
23
|
+
return _ajv.ErrorObject;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
var _ajv = _interopRequireWildcard(require("ajv"));
|
|
28
|
+
|
|
29
|
+
var _ajvFormats = _interopRequireDefault(require("ajv-formats"));
|
|
30
|
+
|
|
31
|
+
var _get = _interopRequireDefault(require("lodash/get"));
|
|
32
|
+
|
|
33
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
34
|
+
|
|
35
|
+
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
|
36
|
+
|
|
37
|
+
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
38
|
+
|
|
39
|
+
function parseDataPath(dataPath) {
|
|
40
|
+
return dataPath.substr(1).split('/');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getData(error, data) {
|
|
44
|
+
return (0, _get.default)(data, parseDataPath(error.dataPath));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ignoreMissing(error) {
|
|
48
|
+
// ignore top level missing errors
|
|
49
|
+
return !(error.dataPath === '' && error.keyword === 'required');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ignoreNull(error, data, schema) {
|
|
53
|
+
return !((error.keyword === 'type' || error.keyword === 'oneOf') && getData(error, data) === null && !isInvalidPropertyRequired(schema, error));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getErrorFilter(params, data, schema) {
|
|
57
|
+
return error => (!params.ignoreMissing || ignoreMissing(error)) && (!params.ignoreNulls || ignoreNull(error, data, schema));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isInvalidPropertyRequired(schema, error) {
|
|
61
|
+
const dataPath = parseDataPath(error.dataPath);
|
|
62
|
+
const parentDataPath = dataPath.slice(0, dataPath.length - 1);
|
|
63
|
+
const parentSchema = followSchemaPath(schema, parentDataPath);
|
|
64
|
+
const name = getName(error.dataPath);
|
|
65
|
+
|
|
66
|
+
if (!parentSchema) {
|
|
67
|
+
throw new Error('Unexpected error cannot find parent schema');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return isRequired(schema, parentSchema, name);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isRequired(topLevelSchema, schema, name) {
|
|
74
|
+
const schemas = schema.allOf || schema.anyOf || schema.oneOf || [schema];
|
|
75
|
+
return schemas.some(childSchema => {
|
|
76
|
+
var _followRef, _followRef$required;
|
|
77
|
+
|
|
78
|
+
return (_followRef = followRef(topLevelSchema, childSchema)) === null || _followRef === void 0 ? void 0 : (_followRef$required = _followRef.required) === null || _followRef$required === void 0 ? void 0 : _followRef$required.find(prop => prop === name);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getName(path) {
|
|
83
|
+
const parts = parseDataPath(path);
|
|
84
|
+
return parts[parts.length - 1];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function refToPath(ref) {
|
|
88
|
+
return ref.substr(2).split('/');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function followRef(topLevelSchema, schema) {
|
|
92
|
+
return schema.$ref ? (0, _get.default)(topLevelSchema, refToPath(schema.$ref)) : schema;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Given a schema object traverse it using a "dataPath" and return the schema at that path
|
|
96
|
+
*/
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
function followSchemaPath(topLevelSchema, dataPath) {
|
|
100
|
+
const followPath = (schema, path) => {
|
|
101
|
+
var _schema, _schema$properties;
|
|
102
|
+
|
|
103
|
+
schema = followRef(topLevelSchema, schema);
|
|
104
|
+
|
|
105
|
+
if (path.length === 0) {
|
|
106
|
+
return schema;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const combinedSchemas = schema.allOf || schema.anyOf || schema.oneOf;
|
|
110
|
+
|
|
111
|
+
if (combinedSchemas) {
|
|
112
|
+
for (const childSchema of combinedSchemas) {
|
|
113
|
+
const result = followPath(childSchema, path);
|
|
114
|
+
|
|
115
|
+
if (result) {
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const [first, ...rest] = path;
|
|
122
|
+
|
|
123
|
+
if (schema.items && /^\d+$/.exec(first)) {
|
|
124
|
+
return followPath(schema.items, rest);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const prop = (_schema = schema) === null || _schema === void 0 ? void 0 : (_schema$properties = _schema.properties) === null || _schema$properties === void 0 ? void 0 : _schema$properties[first];
|
|
128
|
+
|
|
129
|
+
if (prop) {
|
|
130
|
+
return rest.length ? followPath(prop, rest) : prop;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return followPath(topLevelSchema, dataPath);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isSchemaObject(schema) {
|
|
138
|
+
return typeof schema === 'object';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function validate(ajv, id, data, options) {
|
|
142
|
+
const params = {
|
|
143
|
+
ignoreMissing: false,
|
|
144
|
+
ignoreNulls: true,
|
|
145
|
+
errorsText: false,
|
|
146
|
+
...options
|
|
147
|
+
};
|
|
148
|
+
let valid = ajv.validate(id, data);
|
|
149
|
+
let errors;
|
|
150
|
+
|
|
151
|
+
if (valid) {
|
|
152
|
+
errors = [];
|
|
153
|
+
} else {
|
|
154
|
+
errors = ajv.errors;
|
|
155
|
+
|
|
156
|
+
if (params.ignoreNulls || params.ignoreMissing) {
|
|
157
|
+
var _ajv$getSchema;
|
|
158
|
+
|
|
159
|
+
const schema = (_ajv$getSchema = ajv.getSchema(id)) === null || _ajv$getSchema === void 0 ? void 0 : _ajv$getSchema.schema;
|
|
160
|
+
|
|
161
|
+
if (isSchemaObject(schema)) {
|
|
162
|
+
errors = errors.filter(getErrorFilter(params, data, schema));
|
|
163
|
+
valid = errors.length === 0;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const errorsText = params.errorsText && !valid ? ajv.errorsText(errors) : '';
|
|
169
|
+
return {
|
|
170
|
+
valid,
|
|
171
|
+
errors,
|
|
172
|
+
errorsText
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function createAjv(options) {
|
|
177
|
+
const ajv = new _ajv.default({
|
|
178
|
+
allErrors: true,
|
|
179
|
+
unevaluated: true,
|
|
180
|
+
strict: false,
|
|
181
|
+
...options
|
|
182
|
+
});
|
|
183
|
+
(0, _ajvFormats.default)(ajv);
|
|
184
|
+
/**
|
|
185
|
+
* Formats ingested by Stripe OpenAPI and possibly other — trigger warnings
|
|
186
|
+
* in the client when Stripe types are loaded.
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
ajv.addFormat('unix-time', /^[0-9]{10}$/);
|
|
190
|
+
ajv.addFormat('decimal', /^[0-9]+\.[0-9]{1,12}$/);
|
|
191
|
+
return ajv;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Determine whether a string is a valid regular expression
|
|
195
|
+
*/
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
function isValidRegex(str) {
|
|
199
|
+
try {
|
|
200
|
+
// eslint-disable-next-line no-new,security-node/non-literal-reg-expr
|
|
201
|
+
new RegExp(str, 'u');
|
|
202
|
+
return true;
|
|
203
|
+
} catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Fix schema by removing any broken regexes that will cause ajv to throw a compile error.
|
|
209
|
+
* It is better to remove the invalid regex rather than forgo validation altogether
|
|
210
|
+
* This should be fixed in ajv see the comment here
|
|
211
|
+
* https://github.com/ajv-validator/ajv/blob/9f1c3eaa4b91ca17b72b122cdac9b108d1ac30cb/lib/vocabularies/validation/pattern.ts#L21
|
|
212
|
+
*/
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
function fixSchema(schema) {
|
|
216
|
+
const normalize = obj => {
|
|
217
|
+
if (Array.isArray(obj)) {
|
|
218
|
+
return obj.map(normalize);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (obj && typeof obj === 'object') {
|
|
222
|
+
const result = {};
|
|
223
|
+
|
|
224
|
+
for (const key of Object.keys(obj)) {
|
|
225
|
+
if (key !== 'pattern' || obj.type !== 'string' || isValidRegex(obj[key])) {
|
|
226
|
+
result[key] = normalize(obj[key]);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return obj;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
return normalize(schema);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function createSchemaValidator(schema, metaSchemas = [], options = {}) {
|
|
240
|
+
const schemas = Array.isArray(schema) ? schema : [schema];
|
|
241
|
+
const ajv = createAjv({
|
|
242
|
+
removeAdditional: true,
|
|
243
|
+
...options
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (metaSchemas) {
|
|
247
|
+
metaSchemas.forEach(metaSchema => ajv.addMetaSchema(metaSchema));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
schemas.forEach(schema => ajv.addSchema(fixSchema(schema)));
|
|
251
|
+
const defaultRef = schemas[0].$id;
|
|
252
|
+
|
|
253
|
+
if (!defaultRef) {
|
|
254
|
+
throw Error('Failed to create schema validator: schema is missing $id');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return (data, options) => {
|
|
258
|
+
return validate(ajv, defaultRef, data, options);
|
|
259
|
+
};
|
|
260
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@takeshape/json-schema",
|
|
3
|
+
"version": "7.194.0",
|
|
4
|
+
"description": "JSON Schema validator",
|
|
5
|
+
"homepage": "https://www.takeshape.io",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "github.com:takeshape/takeshape.git"
|
|
9
|
+
},
|
|
10
|
+
"author": "asprouse",
|
|
11
|
+
"license": "UNLICENSED",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=12"
|
|
14
|
+
},
|
|
15
|
+
"main": "lib/index.js",
|
|
16
|
+
"module": "es/index.js",
|
|
17
|
+
"types": "lib/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"lib",
|
|
20
|
+
"es"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"ajv": "^7.0.4",
|
|
24
|
+
"ajv-formats": "^1.5.1",
|
|
25
|
+
"lodash": "^4.17.21"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/lodash": "^4.14.165"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"lint": "eslint . --ext .js,.ts",
|
|
32
|
+
"lint:code:ci": "eslint . --ext .js,.ts --format junit -o \"${HOME}/test-results/${npm_package_name#*\\/}/eslint-results.xml\"",
|
|
33
|
+
"test": "jest",
|
|
34
|
+
"test-changed": "pnpm run test -- --changedSince=master",
|
|
35
|
+
"test:ci": "JEST_JUNIT_OUTPUT_DIR=\"${HOME}/test-results/${npm_package_name#*\\/}\" JEST_JUNIT_OUTPUT_NAME=jest-results.xml jest --ci --reporters=default --reporters=jest-junit",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"clean": "rimraf lib es build *.tsbuildinfo",
|
|
38
|
+
"build": "pnpm clean && pnpm build:types && pnpm build:js && pnpm build:es && pnpm build:copy",
|
|
39
|
+
"build:types": "tsc --emitDeclarationOnly --project tsconfig.build.json",
|
|
40
|
+
"build:js": "cross-env BABEL_MODULES=commonjs babel src --out-dir lib --extensions \".js,.ts\" --ignore '**/__tests__'",
|
|
41
|
+
"build:es": "cross-env BABEL_MODULES=es babel src --out-dir es --extensions \".js,.ts\" --ignore '**/__tests__'",
|
|
42
|
+
"build:copy": "cp -rf build/src/* lib/",
|
|
43
|
+
"will-it-blend": "pnpm typecheck && pnpm lint -- --quiet && pnpm test -- --silent --coverage false"
|
|
44
|
+
}
|
|
45
|
+
}
|