@takeshape/json-schema 9.80.3 → 9.81.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/dist/index.js CHANGED
@@ -3,9 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
-
7
6
  var _schemaValidator = require("./schema-validator");
8
-
9
7
  Object.keys(_schemaValidator).forEach(function (key) {
10
8
  if (key === "default" || key === "__esModule") return;
11
9
  if (key in exports && exports[key] === _schemaValidator[key]) return;
@@ -17,155 +17,115 @@ exports.isInvalidPropertyRequired = isInvalidPropertyRequired;
17
17
  exports.parseDataPath = parseDataPath;
18
18
  exports.refToPath = refToPath;
19
19
  exports.validate = validate;
20
-
21
20
  var _ajv = _interopRequireDefault(require("ajv"));
22
-
23
21
  var _ajvFormats = _interopRequireDefault(require("ajv-formats"));
24
-
25
22
  var _get = _interopRequireDefault(require("lodash/get"));
26
-
27
23
  var _unset = _interopRequireDefault(require("lodash/unset"));
28
-
29
24
  var _isString = _interopRequireDefault(require("lodash/isString"));
30
-
31
25
  var _util = require("@takeshape/util");
32
-
33
26
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
34
-
35
27
  function parseDataPath(instancePath) {
36
28
  return instancePath.substr(1).split('/');
37
29
  }
38
-
39
30
  function getData(error, data) {
40
31
  return (0, _get.default)(data, parseDataPath(error.instancePath));
41
32
  }
42
-
43
33
  function ignoreMissing(error) {
44
34
  // ignore top level missing errors
45
35
  return !(error.instancePath === '' && error.keyword === 'required');
46
36
  }
47
-
48
37
  function ignoreNull(error, data, topLevelSchema) {
49
38
  return !((error.keyword === 'type' || error.keyword === 'oneOf') && getData(error, data) === null && !isInvalidPropertyRequired(topLevelSchema, error));
50
39
  }
51
-
52
40
  function getErrorFilter(params, data, schema) {
53
41
  return error => (!params.ignoreMissing || ignoreMissing(error)) && (!params.ignoreNulls || ignoreNull(error, data, schema));
54
42
  }
55
-
56
43
  function isInvalidPropertyRequired(topLevelSchema, error) {
57
44
  const instancePath = parseDataPath(error.instancePath);
58
45
  const parentDataPath = instancePath.slice(0, instancePath.length - 1);
59
46
  const parentSchema = followSchemaPath(topLevelSchema, parentDataPath);
60
47
  const name = getName(error.instancePath);
61
-
62
48
  if (!parentSchema) {
63
49
  throw new Error('Unexpected error cannot find parent schema');
64
50
  }
65
-
66
51
  return isRequired(topLevelSchema, parentSchema, name);
67
52
  }
68
-
69
53
  function isRequired(topLevelSchema, schema, name) {
70
54
  const schemas = schema.allOf || schema.anyOf || schema.oneOf || [schema];
71
55
  return schemas.some(childSchema => {
72
56
  var _followRef, _followRef$required;
73
-
74
57
  return (_followRef = followRef(topLevelSchema, childSchema)) === null || _followRef === void 0 ? void 0 : (_followRef$required = _followRef.required) === null || _followRef$required === void 0 ? void 0 : _followRef$required.includes(name);
75
58
  });
76
59
  }
77
-
78
60
  function getName(path) {
79
61
  const parts = parseDataPath(path);
80
62
  return parts[parts.length - 1];
81
63
  }
82
-
83
64
  function refToPath(ref) {
84
65
  return ref.substring(2).split('/');
85
66
  }
86
-
87
67
  function followRef(topLevelSchema, schema) {
88
68
  if (schema.$ref) {
89
69
  const referencedSchema = (0, _get.default)(topLevelSchema, refToPath(schema.$ref));
90
-
91
70
  if (!referencedSchema) {
92
71
  throw new Error(`Could not resolve ${schema.$ref}`);
93
72
  }
94
-
95
73
  return referencedSchema;
96
74
  }
97
-
98
75
  return schema;
99
76
  }
77
+
100
78
  /**
101
79
  * Given a schema object traverse it using a "instancePath" and return the schema at that path
102
80
  */
103
-
104
-
105
81
  function followSchemaPath(topLevelSchema, instancePath) {
106
82
  const followPath = (schema, path) => {
107
83
  var _schema, _schema$properties;
108
-
109
84
  schema = followRef(topLevelSchema, schema);
110
-
111
85
  if (path.length === 0) {
112
86
  return schema;
113
87
  }
114
-
115
88
  const combinedSchemas = schema.allOf || schema.anyOf || schema.oneOf;
116
-
117
89
  if (combinedSchemas) {
118
90
  for (const childSchema of combinedSchemas) {
119
91
  const result = followPath(childSchema, path);
120
-
121
92
  if (result) {
122
93
  return result;
123
94
  }
124
95
  }
125
96
  }
126
-
127
97
  const [first, ...rest] = path;
128
-
129
98
  if (schema.items && /^\d+$/.exec(first)) {
130
99
  return followPath(schema.items, rest);
131
100
  }
132
-
133
101
  const prop = (_schema = schema) === null || _schema === void 0 ? void 0 : (_schema$properties = _schema.properties) === null || _schema$properties === void 0 ? void 0 : _schema$properties[first];
134
-
135
102
  if (prop) {
136
103
  return rest.length ? followPath(prop, rest) : prop;
137
104
  }
138
105
  };
139
-
140
106
  return followPath(topLevelSchema, instancePath);
141
107
  }
142
-
143
108
  function isSchemaObject(schema) {
144
109
  return typeof schema === 'object';
145
110
  }
146
-
147
111
  function getSchemaWithDefinitions(ajv, id) {
148
112
  var _ajv$getSchema2;
149
-
150
113
  if (id.startsWith('#')) {
151
114
  var _ajv$getSchema;
152
-
153
115
  const rootSchema = (_ajv$getSchema = ajv.getSchema('#')) === null || _ajv$getSchema === void 0 ? void 0 : _ajv$getSchema.schema;
154
-
155
116
  if (isSchemaObject(rootSchema)) {
156
117
  const path = refToPath(id);
157
118
  const {
158
119
  definitions
159
120
  } = rootSchema;
160
- return { ...(0, _get.default)(rootSchema, path),
121
+ return {
122
+ ...(0, _get.default)(rootSchema, path),
161
123
  definitions
162
124
  };
163
125
  }
164
126
  }
165
-
166
127
  return (_ajv$getSchema2 = ajv.getSchema(id)) === null || _ajv$getSchema2 === void 0 ? void 0 : _ajv$getSchema2.schema;
167
128
  }
168
-
169
129
  function validate(ajv, id, data, options) {
170
130
  const params = {
171
131
  ignoreMissing: false,
@@ -175,22 +135,18 @@ function validate(ajv, id, data, options) {
175
135
  };
176
136
  let valid = ajv.validate(id, data);
177
137
  let errors;
178
-
179
138
  if (valid) {
180
139
  errors = [];
181
140
  } else {
182
141
  errors = ajv.errors;
183
-
184
142
  if (params.ignoreNulls || params.ignoreMissing) {
185
143
  const schema = getSchemaWithDefinitions(ajv, id);
186
-
187
144
  if (isSchemaObject(schema)) {
188
145
  errors = errors.filter(getErrorFilter(params, data, schema));
189
146
  valid = errors.length === 0;
190
147
  }
191
148
  }
192
149
  }
193
-
194
150
  const errorsText = params.errorsText && !valid ? ajv.errorsText(errors) : '';
195
151
  return {
196
152
  valid,
@@ -198,7 +154,6 @@ function validate(ajv, id, data, options) {
198
154
  errorsText
199
155
  };
200
156
  }
201
-
202
157
  function createAjv(options) {
203
158
  const ajv = new _ajv.default({
204
159
  discriminator: true,
@@ -208,20 +163,19 @@ function createAjv(options) {
208
163
  ...options
209
164
  });
210
165
  (0, _ajvFormats.default)(ajv);
166
+
211
167
  /**
212
168
  * Formats ingested by Stripe OpenAPI and possibly other — trigger warnings
213
169
  * in the client when Stripe types are loaded.
214
170
  */
215
-
216
171
  ajv.addFormat('unix-time', /^[0-9]{10}$/);
217
172
  ajv.addFormat('decimal', /^[0-9]+\.[0-9]{1,12}$/);
218
173
  return ajv;
219
174
  }
175
+
220
176
  /**
221
177
  * Determine whether a string is a valid regular expression
222
178
  */
223
-
224
-
225
179
  function isValidRegex(str) {
226
180
  try {
227
181
  // eslint-disable-next-line no-new,security-node/non-literal-reg-expr
@@ -231,23 +185,22 @@ function isValidRegex(str) {
231
185
  return false;
232
186
  }
233
187
  }
188
+
234
189
  /**
235
190
  * Apply various fixes to the schema to work around AJV issues and bugs.
236
191
  * See inline comments for more.
237
192
  */
238
-
239
-
240
193
  function fixSchema(schema) {
241
194
  (0, _util.visit)(schema, ['pattern', 'oneOf'], (value, path) => {
242
195
  const key = path.slice(-1)[0];
243
196
  const parent = (0, _get.default)(schema, path.slice(0, -1));
197
+
244
198
  /**
245
199
  * Fix schema by removing any broken regexes that will cause ajv to throw a compile error.
246
200
  * It is better to remove the invalid regex rather than forgo validation altogether
247
201
  * This should be fixed in ajv see the comment here
248
202
  * https://github.com/ajv-validator/ajv/blob/9f1c3eaa4b91ca17b72b122cdac9b108d1ac30cb/lib/vocabularies/validation/pattern.ts#L21
249
203
  */
250
-
251
204
  if (key === 'pattern') {
252
205
  if (parent.type === 'string' && (!(0, _isString.default)(value) || !isValidRegex(value))) {
253
206
  (0, _unset.default)(schema, path);
@@ -256,25 +209,20 @@ function fixSchema(schema) {
256
209
  });
257
210
  return schema;
258
211
  }
259
-
260
212
  function createSchemaValidator(schema, metaSchemas = [], options = {}) {
261
213
  const schemas = Array.isArray(schema) ? schema : [schema];
262
214
  const ajv = createAjv({
263
215
  removeAdditional: true,
264
216
  ...options
265
217
  });
266
-
267
218
  if (metaSchemas) {
268
219
  metaSchemas.forEach(metaSchema => ajv.addMetaSchema(metaSchema));
269
220
  }
270
-
271
221
  schemas.forEach(schema => ajv.addSchema(fixSchema(schema)));
272
222
  const defaultRef = schemas[0].$id;
273
-
274
223
  if (!defaultRef) {
275
224
  throw Error('Failed to create schema validator: schema is missing $id');
276
225
  }
277
-
278
226
  return (data, options) => {
279
227
  return validate(ajv, defaultRef, data, options);
280
228
  };
@@ -7,137 +7,105 @@ import { visit } from '@takeshape/util';
7
7
  export function parseDataPath(instancePath) {
8
8
  return instancePath.substr(1).split('/');
9
9
  }
10
-
11
10
  function getData(error, data) {
12
11
  return get(data, parseDataPath(error.instancePath));
13
12
  }
14
-
15
13
  function ignoreMissing(error) {
16
14
  // ignore top level missing errors
17
15
  return !(error.instancePath === '' && error.keyword === 'required');
18
16
  }
19
-
20
17
  function ignoreNull(error, data, topLevelSchema) {
21
18
  return !((error.keyword === 'type' || error.keyword === 'oneOf') && getData(error, data) === null && !isInvalidPropertyRequired(topLevelSchema, error));
22
19
  }
23
-
24
20
  function getErrorFilter(params, data, schema) {
25
21
  return error => (!params.ignoreMissing || ignoreMissing(error)) && (!params.ignoreNulls || ignoreNull(error, data, schema));
26
22
  }
27
-
28
23
  export function isInvalidPropertyRequired(topLevelSchema, error) {
29
24
  const instancePath = parseDataPath(error.instancePath);
30
25
  const parentDataPath = instancePath.slice(0, instancePath.length - 1);
31
26
  const parentSchema = followSchemaPath(topLevelSchema, parentDataPath);
32
27
  const name = getName(error.instancePath);
33
-
34
28
  if (!parentSchema) {
35
29
  throw new Error('Unexpected error cannot find parent schema');
36
30
  }
37
-
38
31
  return isRequired(topLevelSchema, parentSchema, name);
39
32
  }
40
-
41
33
  function isRequired(topLevelSchema, schema, name) {
42
34
  const schemas = schema.allOf || schema.anyOf || schema.oneOf || [schema];
43
35
  return schemas.some(childSchema => {
44
36
  var _followRef, _followRef$required;
45
-
46
37
  return (_followRef = followRef(topLevelSchema, childSchema)) === null || _followRef === void 0 ? void 0 : (_followRef$required = _followRef.required) === null || _followRef$required === void 0 ? void 0 : _followRef$required.includes(name);
47
38
  });
48
39
  }
49
-
50
40
  function getName(path) {
51
41
  const parts = parseDataPath(path);
52
42
  return parts[parts.length - 1];
53
43
  }
54
-
55
44
  export function refToPath(ref) {
56
45
  return ref.substring(2).split('/');
57
46
  }
58
-
59
47
  function followRef(topLevelSchema, schema) {
60
48
  if (schema.$ref) {
61
49
  const referencedSchema = get(topLevelSchema, refToPath(schema.$ref));
62
-
63
50
  if (!referencedSchema) {
64
51
  throw new Error(`Could not resolve ${schema.$ref}`);
65
52
  }
66
-
67
53
  return referencedSchema;
68
54
  }
69
-
70
55
  return schema;
71
56
  }
57
+
72
58
  /**
73
59
  * Given a schema object traverse it using a "instancePath" and return the schema at that path
74
60
  */
75
-
76
-
77
61
  export function followSchemaPath(topLevelSchema, instancePath) {
78
62
  const followPath = (schema, path) => {
79
63
  var _schema, _schema$properties;
80
-
81
64
  schema = followRef(topLevelSchema, schema);
82
-
83
65
  if (path.length === 0) {
84
66
  return schema;
85
67
  }
86
-
87
68
  const combinedSchemas = schema.allOf || schema.anyOf || schema.oneOf;
88
-
89
69
  if (combinedSchemas) {
90
70
  for (const childSchema of combinedSchemas) {
91
71
  const result = followPath(childSchema, path);
92
-
93
72
  if (result) {
94
73
  return result;
95
74
  }
96
75
  }
97
76
  }
98
-
99
77
  const [first, ...rest] = path;
100
-
101
78
  if (schema.items && /^\d+$/.exec(first)) {
102
79
  return followPath(schema.items, rest);
103
80
  }
104
-
105
81
  const prop = (_schema = schema) === null || _schema === void 0 ? void 0 : (_schema$properties = _schema.properties) === null || _schema$properties === void 0 ? void 0 : _schema$properties[first];
106
-
107
82
  if (prop) {
108
83
  return rest.length ? followPath(prop, rest) : prop;
109
84
  }
110
85
  };
111
-
112
86
  return followPath(topLevelSchema, instancePath);
113
87
  }
114
-
115
88
  function isSchemaObject(schema) {
116
89
  return typeof schema === 'object';
117
90
  }
118
-
119
91
  function getSchemaWithDefinitions(ajv, id) {
120
92
  var _ajv$getSchema2;
121
-
122
93
  if (id.startsWith('#')) {
123
94
  var _ajv$getSchema;
124
-
125
95
  const rootSchema = (_ajv$getSchema = ajv.getSchema('#')) === null || _ajv$getSchema === void 0 ? void 0 : _ajv$getSchema.schema;
126
-
127
96
  if (isSchemaObject(rootSchema)) {
128
97
  const path = refToPath(id);
129
98
  const {
130
99
  definitions
131
100
  } = rootSchema;
132
- return { ...get(rootSchema, path),
101
+ return {
102
+ ...get(rootSchema, path),
133
103
  definitions
134
104
  };
135
105
  }
136
106
  }
137
-
138
107
  return (_ajv$getSchema2 = ajv.getSchema(id)) === null || _ajv$getSchema2 === void 0 ? void 0 : _ajv$getSchema2.schema;
139
108
  }
140
-
141
109
  export function validate(ajv, id, data, options) {
142
110
  const params = {
143
111
  ignoreMissing: false,
@@ -147,22 +115,18 @@ export function validate(ajv, id, data, options) {
147
115
  };
148
116
  let valid = ajv.validate(id, data);
149
117
  let errors;
150
-
151
118
  if (valid) {
152
119
  errors = [];
153
120
  } else {
154
121
  errors = ajv.errors;
155
-
156
122
  if (params.ignoreNulls || params.ignoreMissing) {
157
123
  const schema = getSchemaWithDefinitions(ajv, id);
158
-
159
124
  if (isSchemaObject(schema)) {
160
125
  errors = errors.filter(getErrorFilter(params, data, schema));
161
126
  valid = errors.length === 0;
162
127
  }
163
128
  }
164
129
  }
165
-
166
130
  const errorsText = params.errorsText && !valid ? ajv.errorsText(errors) : '';
167
131
  return {
168
132
  valid,
@@ -180,19 +144,19 @@ export function createAjv(options) {
180
144
  ...options
181
145
  });
182
146
  addFormats(ajv);
147
+
183
148
  /**
184
149
  * Formats ingested by Stripe OpenAPI and possibly other — trigger warnings
185
150
  * in the client when Stripe types are loaded.
186
151
  */
187
-
188
152
  ajv.addFormat('unix-time', /^[0-9]{10}$/);
189
153
  ajv.addFormat('decimal', /^[0-9]+\.[0-9]{1,12}$/);
190
154
  return ajv;
191
155
  }
156
+
192
157
  /**
193
158
  * Determine whether a string is a valid regular expression
194
159
  */
195
-
196
160
  function isValidRegex(str) {
197
161
  try {
198
162
  // eslint-disable-next-line no-new,security-node/non-literal-reg-expr
@@ -202,23 +166,22 @@ function isValidRegex(str) {
202
166
  return false;
203
167
  }
204
168
  }
169
+
205
170
  /**
206
171
  * Apply various fixes to the schema to work around AJV issues and bugs.
207
172
  * See inline comments for more.
208
173
  */
209
-
210
-
211
174
  export function fixSchema(schema) {
212
175
  visit(schema, ['pattern', 'oneOf'], (value, path) => {
213
176
  const key = path.slice(-1)[0];
214
177
  const parent = get(schema, path.slice(0, -1));
178
+
215
179
  /**
216
180
  * Fix schema by removing any broken regexes that will cause ajv to throw a compile error.
217
181
  * It is better to remove the invalid regex rather than forgo validation altogether
218
182
  * This should be fixed in ajv see the comment here
219
183
  * https://github.com/ajv-validator/ajv/blob/9f1c3eaa4b91ca17b72b122cdac9b108d1ac30cb/lib/vocabularies/validation/pattern.ts#L21
220
184
  */
221
-
222
185
  if (key === 'pattern') {
223
186
  if (parent.type === 'string' && (!isString(value) || !isValidRegex(value))) {
224
187
  unset(schema, path);
@@ -233,18 +196,14 @@ export function createSchemaValidator(schema, metaSchemas = [], options = {}) {
233
196
  removeAdditional: true,
234
197
  ...options
235
198
  });
236
-
237
199
  if (metaSchemas) {
238
200
  metaSchemas.forEach(metaSchema => ajv.addMetaSchema(metaSchema));
239
201
  }
240
-
241
202
  schemas.forEach(schema => ajv.addSchema(fixSchema(schema)));
242
203
  const defaultRef = schemas[0].$id;
243
-
244
204
  if (!defaultRef) {
245
205
  throw Error('Failed to create schema validator: schema is missing $id');
246
206
  }
247
-
248
207
  return (data, options) => {
249
208
  return validate(ajv, defaultRef, data, options);
250
209
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takeshape/json-schema",
3
- "version": "9.80.3",
3
+ "version": "9.81.0",
4
4
  "description": "JSON Schema validator",
5
5
  "homepage": "https://www.takeshape.io",
6
6
  "repository": {
@@ -20,11 +20,11 @@
20
20
  "ajv": "^8.11.0",
21
21
  "ajv-formats": "^2.1.1",
22
22
  "lodash": "^4.17.21",
23
- "@takeshape/util": "9.80.3"
23
+ "@takeshape/util": "9.81.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/lodash": "^4.14.165",
27
- "@takeshape/typescript-jest-junit-reporter": "9.80.3"
27
+ "@takeshape/typescript-jest-junit-reporter": "9.81.0"
28
28
  },
29
29
  "engines": {
30
30
  "node": ">=16"