schema-shield 0.0.5 → 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.
@@ -1,9 +1,8 @@
1
- import { isCompiledSchema, isObject } from "../utils";
1
+ import { deepClone, isCompiledSchema, isObject } from "../utils";
2
2
 
3
3
  import { KeywordFunction } from "../index";
4
4
 
5
5
  export const ObjectKeywords: Record<string, KeywordFunction | false> = {
6
- // Object
7
6
  required(schema, data, defineError) {
8
7
  if (!isObject(data)) {
9
8
  return;
@@ -27,17 +26,59 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
27
26
  return;
28
27
  }
29
28
 
30
- for (const key of Object.keys(schema.properties)) {
31
- if (!data.hasOwnProperty(key)) {
32
- const schemaProp = schema.properties[key];
33
- if (isObject(schemaProp) && "default" in schemaProp) {
34
- data[key] = schemaProp.default;
29
+ let propKeys = (schema as any)._propKeys as string[] | undefined;
30
+ if (!propKeys) {
31
+ propKeys = Object.keys(schema.properties || {});
32
+ Object.defineProperty(schema, "_propKeys", {
33
+ value: propKeys,
34
+ enumerable: false,
35
+ configurable: false,
36
+ writable: false
37
+ });
38
+ }
39
+
40
+ let requiredKeys = (schema as any)._requiredKeys as
41
+ | string[]
42
+ | null
43
+ | undefined;
44
+ if (requiredKeys === undefined) {
45
+ requiredKeys = Array.isArray(schema.required) ? schema.required : null;
46
+ Object.defineProperty(schema, "_requiredKeys", {
47
+ value: requiredKeys,
48
+ enumerable: false,
49
+ configurable: false,
50
+ writable: false
51
+ });
52
+ }
53
+
54
+ const required = requiredKeys || [];
55
+
56
+ for (let i = 0; i < propKeys.length; i++) {
57
+ const key = propKeys[i];
58
+ const schemaProp = schema.properties[key];
59
+
60
+ if (!Object.prototype.hasOwnProperty.call(data, key)) {
61
+ if (
62
+ required.length &&
63
+ required.indexOf(key) !== -1 &&
64
+ isObject(schemaProp) &&
65
+ "default" in schemaProp
66
+ ) {
67
+ const error = schemaProp.$validate(schemaProp.default);
68
+ if (error) {
69
+ return defineError("Default property is invalid", {
70
+ item: key,
71
+ cause: error,
72
+ data: schemaProp.default
73
+ });
74
+ }
75
+ data[key] = deepClone(schemaProp.default);
35
76
  }
36
77
  continue;
37
78
  }
38
79
 
39
- if (typeof schema.properties[key] === "boolean") {
40
- if (schema.properties[key] === false) {
80
+ if (typeof schemaProp === "boolean") {
81
+ if (schemaProp === false) {
41
82
  return defineError("Property is not allowed", {
42
83
  item: key,
43
84
  data: data[key]
@@ -46,8 +87,8 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
46
87
  continue;
47
88
  }
48
89
 
49
- if ("$validate" in schema.properties[key]) {
50
- const error = schema.properties[key].$validate(data[key]);
90
+ if (schemaProp && "$validate" in schemaProp) {
91
+ const error = schemaProp.$validate(data[key]);
51
92
  if (error) {
52
93
  return defineError("Property is invalid", {
53
94
  item: key,
@@ -60,15 +101,21 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
60
101
 
61
102
  return;
62
103
  },
63
-
64
104
  values(schema, data, defineError) {
65
- if (!isObject(data) || !isCompiledSchema(schema.values)) {
105
+ if (!isObject(data)) {
106
+ return;
107
+ }
108
+
109
+ const valueSchema = schema.values;
110
+ const validate = valueSchema && valueSchema.$validate;
111
+ if (typeof validate !== "function") {
66
112
  return;
67
113
  }
68
114
 
69
115
  const keys = Object.keys(data);
70
- for (const key of keys) {
71
- const error = schema.values.$validate(data[key]);
116
+ for (let i = 0; i < keys.length; i++) {
117
+ const key = keys[i];
118
+ const error = validate(data[key]);
72
119
  if (error) {
73
120
  return defineError("Property is invalid", {
74
121
  item: key,
@@ -77,8 +124,6 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
77
124
  });
78
125
  }
79
126
  }
80
-
81
- return;
82
127
  },
83
128
 
84
129
  maxProperties(schema, data, defineError) {
@@ -103,16 +148,45 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
103
148
  }
104
149
 
105
150
  const keys = Object.keys(data);
106
- const isCompiled = isCompiledSchema(schema.additionalProperties);
107
- for (const key of keys) {
151
+
152
+ let apIsCompiled = (schema as any)._apIsCompiled as boolean | undefined;
153
+ if (apIsCompiled === undefined) {
154
+ apIsCompiled = isCompiledSchema(schema.additionalProperties);
155
+ Object.defineProperty(schema, "_apIsCompiled", {
156
+ value: apIsCompiled,
157
+ enumerable: false
158
+ });
159
+ }
160
+
161
+ let patternList = (schema as any)._patternPropertiesList as
162
+ | { regex: RegExp; key: string }[]
163
+ | undefined;
164
+
165
+ if (schema.patternProperties && !patternList) {
166
+ patternList = [];
167
+ for (const pattern in schema.patternProperties) {
168
+ patternList.push({
169
+ regex: new RegExp(pattern, "u"),
170
+ key: pattern
171
+ });
172
+ }
173
+ Object.defineProperty(schema, "_patternPropertiesList", {
174
+ value: patternList,
175
+ enumerable: false
176
+ });
177
+ }
178
+
179
+ for (let i = 0; i < keys.length; i++) {
180
+ const key = keys[i];
181
+
108
182
  if (schema.properties && schema.properties.hasOwnProperty(key)) {
109
183
  continue;
110
184
  }
111
185
 
112
- if (schema.patternProperties) {
186
+ if (patternList && patternList.length) {
113
187
  let match = false;
114
- for (const pattern in schema.patternProperties) {
115
- if (new RegExp(pattern, "u").test(key)) {
188
+ for (let j = 0; j < patternList.length; j++) {
189
+ if (patternList[j].regex.test(key)) {
116
190
  match = true;
117
191
  break;
118
192
  }
@@ -129,7 +203,7 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
129
203
  });
130
204
  }
131
205
 
132
- if (isCompiled) {
206
+ if (apIsCompiled && isCompiledSchema(schema.additionalProperties)) {
133
207
  const error = schema.additionalProperties.$validate(data[key]);
134
208
  if (error) {
135
209
  return defineError("Additional properties are invalid", {
@@ -143,18 +217,41 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
143
217
 
144
218
  return;
145
219
  },
146
-
147
220
  patternProperties(schema, data, defineError) {
148
221
  if (!isObject(data)) {
149
222
  return;
150
223
  }
151
224
 
152
- const patterns = Object.keys(schema.patternProperties);
153
- for (const pattern of patterns) {
154
- const regex = new RegExp(pattern, "u");
155
- if (typeof schema.patternProperties[pattern] === "boolean") {
156
- if (schema.patternProperties[pattern] === false) {
157
- for (const key in data) {
225
+ let patternList = (schema as any)._patternPropertiesList as
226
+ | { regex: RegExp; key: string }[]
227
+ | undefined;
228
+
229
+ if (!patternList) {
230
+ patternList = [];
231
+ const patterns = Object.keys(schema.patternProperties || {});
232
+ for (let i = 0; i < patterns.length; i++) {
233
+ const pattern = patterns[i];
234
+ patternList.push({
235
+ regex: new RegExp(pattern, "u"),
236
+ key: pattern
237
+ });
238
+ }
239
+ Object.defineProperty(schema, "_patternPropertiesList", {
240
+ value: patternList,
241
+ enumerable: false
242
+ });
243
+ }
244
+
245
+ const dataKeys = Object.keys(data);
246
+
247
+ for (let p = 0; p < patternList.length; p++) {
248
+ const { regex, key: patternKey } = patternList[p];
249
+ const schemaProp = schema.patternProperties[patternKey];
250
+
251
+ if (typeof schemaProp === "boolean") {
252
+ if (schemaProp === false) {
253
+ for (let i = 0; i < dataKeys.length; i++) {
254
+ const key = dataKeys[i];
158
255
  if (regex.test(key)) {
159
256
  return defineError("Property is not allowed", {
160
257
  item: key,
@@ -166,13 +263,11 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
166
263
  continue;
167
264
  }
168
265
 
169
- const keys = Object.keys(data);
170
- for (const key of keys) {
171
- if (regex.test(key)) {
172
- if ("$validate" in schema.patternProperties[pattern]) {
173
- const error = schema.patternProperties[pattern].$validate(
174
- data[key]
175
- );
266
+ if ("$validate" in schemaProp) {
267
+ for (let i = 0; i < dataKeys.length; i++) {
268
+ const key = dataKeys[i];
269
+ if (regex.test(key)) {
270
+ const error = schemaProp.$validate(data[key]);
176
271
  if (error) {
177
272
  return defineError("Property is invalid", {
178
273
  item: key,
@@ -187,30 +282,38 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
187
282
 
188
283
  return;
189
284
  },
190
-
191
285
  propertyNames(schema, data, defineError) {
192
286
  if (!isObject(data)) {
193
287
  return;
194
288
  }
195
- if (typeof schema.propertyNames === "boolean") {
196
- if (schema.propertyNames === false && Object.keys(data).length > 0) {
289
+
290
+ const pn = schema.propertyNames;
291
+
292
+ if (typeof pn === "boolean") {
293
+ if (pn === false && Object.keys(data).length > 0) {
197
294
  return defineError("Properties are not allowed", { data });
198
295
  }
296
+ return;
199
297
  }
200
- if (isCompiledSchema(schema.propertyNames)) {
201
- for (let key in data) {
202
- const error = schema.propertyNames.$validate(key);
203
- if (error) {
204
- return defineError("Property name is invalid", {
205
- item: key,
206
- cause: error,
207
- data: data[key]
208
- });
209
- }
210
- }
298
+
299
+ const validate = pn && pn.$validate;
300
+ if (typeof validate !== "function") {
301
+ return;
211
302
  }
212
303
 
213
- return;
304
+ for (const key in data) {
305
+ if (!Object.prototype.hasOwnProperty.call(data, key)) {
306
+ continue;
307
+ }
308
+ const error = validate(key);
309
+ if (error) {
310
+ return defineError("Property name is invalid", {
311
+ item: key,
312
+ cause: error,
313
+ data: data[key]
314
+ });
315
+ }
316
+ }
214
317
  },
215
318
 
216
319
  dependencies(schema, data, defineError) {
@@ -266,7 +369,6 @@ export const ObjectKeywords: Record<string, KeywordFunction | false> = {
266
369
  default: false,
267
370
 
268
371
  // Not implemented yet
269
- $ref: false,
270
372
  definitions: false,
271
373
  $id: false,
272
374
  $schema: false,
@@ -1,29 +1,26 @@
1
- import { deepEqual, isCompiledSchema, isObject } from "../utils";
1
+ import { hasChanged, isCompiledSchema, isObject } from "../utils";
2
2
 
3
3
  import { KeywordFunction } from "../index";
4
4
 
5
5
  export const OtherKeywords: Record<string, KeywordFunction> = {
6
6
  enum(schema, data, defineError) {
7
- // Check if data is an array or an object
8
- const isArray = Array.isArray(data);
9
- const isObject = typeof data === "object" && data !== null;
7
+ const list = schema.enum;
10
8
 
11
- for (let i = 0; i < schema.enum.length; i++) {
12
- const enumItem = schema.enum[i];
9
+ for (let i = 0; i < list.length; i++) {
10
+ const enumItem = list[i];
13
11
 
14
- // Simple equality check
15
12
  if (enumItem === data) {
16
13
  return;
17
14
  }
18
15
 
19
- // If data is an array or an object, check for deep equality
20
16
  if (
21
- (isArray && Array.isArray(enumItem)) ||
22
- (isObject && typeof enumItem === "object" && enumItem !== null)
17
+ enumItem !== null &&
18
+ data !== null &&
19
+ typeof enumItem === "object" &&
20
+ typeof data === "object" &&
21
+ !hasChanged(enumItem, data)
23
22
  ) {
24
- if (deepEqual(enumItem, data)) {
25
- return;
26
- }
23
+ return;
27
24
  }
28
25
  }
29
26
 
@@ -85,28 +82,44 @@ export const OtherKeywords: Record<string, KeywordFunction> = {
85
82
  },
86
83
 
87
84
  oneOf(schema, data, defineError) {
85
+ const list = schema.oneOf;
88
86
  let validCount = 0;
89
- for (let i = 0; i < schema.oneOf.length; i++) {
90
- if (isObject(schema.oneOf[i])) {
91
- if ("$validate" in schema.oneOf[i]) {
92
- const error = schema.oneOf[i].$validate(data);
87
+
88
+ for (let i = 0; i < list.length; i++) {
89
+ const sub = list[i];
90
+
91
+ if (isObject(sub)) {
92
+ if ("$validate" in sub) {
93
+ const error = sub.$validate(data);
93
94
  if (!error) {
94
95
  validCount++;
96
+ if (validCount > 1) {
97
+ return defineError("Value is not valid", { data });
98
+ }
95
99
  }
96
100
  continue;
97
101
  }
98
102
  validCount++;
103
+ if (validCount > 1) {
104
+ return defineError("Value is not valid", { data });
105
+ }
99
106
  continue;
100
- } else {
101
- if (typeof schema.oneOf[i] === "boolean") {
102
- if (Boolean(data) === schema.oneOf[i]) {
103
- validCount++;
107
+ }
108
+
109
+ if (typeof sub === "boolean") {
110
+ if (Boolean(data) === sub) {
111
+ validCount++;
112
+ if (validCount > 1) {
113
+ return defineError("Value is not valid", { data });
104
114
  }
105
- continue;
106
115
  }
116
+ continue;
117
+ }
107
118
 
108
- if (data === schema.oneOf[i]) {
109
- validCount++;
119
+ if (data === sub) {
120
+ validCount++;
121
+ if (validCount > 1) {
122
+ return defineError("Value is not valid", { data });
110
123
  }
111
124
  }
112
125
  }
@@ -119,21 +132,24 @@ export const OtherKeywords: Record<string, KeywordFunction> = {
119
132
  },
120
133
 
121
134
  const(schema, data, defineError) {
135
+ if (data === schema.const) {
136
+ return;
137
+ }
138
+
122
139
  if (
123
- data === schema.const ||
124
140
  (isObject(data) &&
125
141
  isObject(schema.const) &&
126
- deepEqual(data, schema.const)) ||
142
+ !hasChanged(data, schema.const)) ||
127
143
  (Array.isArray(data) &&
128
144
  Array.isArray(schema.const) &&
129
- deepEqual(data, schema.const))
145
+ !hasChanged(data, schema.const))
130
146
  ) {
131
147
  return;
132
148
  }
133
149
  return defineError("Value is not valid", { data });
134
150
  },
135
151
 
136
- if(schema, data, defineError) {
152
+ if(schema, data) {
137
153
  if ("then" in schema === false && "else" in schema === false) {
138
154
  return;
139
155
  }
@@ -186,5 +202,29 @@ export const OtherKeywords: Record<string, KeywordFunction> = {
186
202
  }
187
203
 
188
204
  return defineError("Value is not valid", { data });
205
+ },
206
+
207
+ $ref(schema, data, defineError, instance) {
208
+ if (schema._resolvedRef) {
209
+ return schema._resolvedRef(data);
210
+ }
211
+
212
+ const refPath = schema.$ref;
213
+ let targetSchema = instance.getSchemaRef(refPath);
214
+
215
+ if (!targetSchema) {
216
+ targetSchema = instance.getSchemaById(refPath);
217
+ }
218
+
219
+ if (!targetSchema) {
220
+ return defineError(`Missing reference: ${refPath}`);
221
+ }
222
+
223
+ if (!targetSchema.$validate) {
224
+ return;
225
+ }
226
+
227
+ schema._resolvedRef = targetSchema.$validate;
228
+ return schema._resolvedRef(data);
189
229
  }
190
230
  };
@@ -1,4 +1,4 @@
1
- import { KeywordFunction } from "../index";
1
+ import { FormatFunction, KeywordFunction } from "../index";
2
2
 
3
3
  export const StringKeywords: Record<string, KeywordFunction> = {
4
4
  minLength(schema, data, defineError) {
@@ -22,10 +22,22 @@ export const StringKeywords: Record<string, KeywordFunction> = {
22
22
  return;
23
23
  }
24
24
 
25
- const patternRegexp = new RegExp(schema.pattern, "u");
26
-
27
- if (patternRegexp instanceof RegExp === false) {
28
- return defineError("Invalid regular expression", { data });
25
+ let patternRegexp = (schema as any)._patternRegexp as RegExp | undefined;
26
+ if (!patternRegexp) {
27
+ try {
28
+ patternRegexp = new RegExp(schema.pattern, "u");
29
+ Object.defineProperty(schema, "_patternRegexp", {
30
+ value: patternRegexp,
31
+ enumerable: false,
32
+ configurable: false,
33
+ writable: false
34
+ });
35
+ } catch (error) {
36
+ return defineError("Invalid regular expression", {
37
+ data,
38
+ cause: error
39
+ });
40
+ }
29
41
  }
30
42
 
31
43
  if (patternRegexp.test(data)) {
@@ -42,7 +54,21 @@ export const StringKeywords: Record<string, KeywordFunction> = {
42
54
  return;
43
55
  }
44
56
 
45
- const formatValidate = instance.getFormat(schema.format);
57
+ let formatValidate = (schema as any)._formatValidate as
58
+ | FormatFunction
59
+ | false
60
+ | undefined;
61
+
62
+ if (formatValidate === undefined) {
63
+ formatValidate = instance.getFormat(schema.format);
64
+ Object.defineProperty(schema, "_formatValidate", {
65
+ value: formatValidate,
66
+ enumerable: false,
67
+ configurable: false,
68
+ writable: false
69
+ });
70
+ }
71
+
46
72
  if (!formatValidate || formatValidate(data)) {
47
73
  return;
48
74
  }
package/lib/types.ts CHANGED
@@ -6,17 +6,7 @@ export const Types: Record<string, TypeFunction | false> = {
6
6
  return isObject(data);
7
7
  },
8
8
  array(data) {
9
- if (Array.isArray(data)) {
10
- return true;
11
- }
12
-
13
- return (
14
- typeof data === "object" &&
15
- data !== null &&
16
- "length" in data &&
17
- "0" in data &&
18
- Object.keys(data).length - 1 === data.length
19
- );
9
+ return Array.isArray(data);
20
10
  },
21
11
  string(data) {
22
12
  return typeof data === "string";
@@ -44,6 +34,4 @@ export const Types: Record<string, TypeFunction | false> = {
44
34
  unit32: false,
45
35
  float32: false,
46
36
  float64: false
47
-
48
-
49
37
  };