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.
@@ -1,4 +1,24 @@
1
- import { CompiledSchema } from "../index";
1
+ import type { CompiledSchema } from "../index";
2
+
3
+ const hasOwnPropertyIntrinsic = Object.prototype.hasOwnProperty;
4
+ const hasOwnPropertyCall = Function.prototype.call.bind(
5
+ hasOwnPropertyIntrinsic
6
+ ) as (target: any, key: PropertyKey) => boolean;
7
+
8
+ export function definePropertyOrThrow<T extends object>(
9
+ target: T,
10
+ key: PropertyKey,
11
+ descriptor: PropertyDescriptor
12
+ ): T {
13
+ if (!Reflect.defineProperty(target, key, descriptor)) {
14
+ throw new TypeError(`Cannot define property "${String(key)}"`);
15
+ }
16
+ return target;
17
+ }
18
+
19
+ export function hasOwn(target: any, key: PropertyKey): boolean {
20
+ return hasOwnPropertyCall(target, key);
21
+ }
2
22
 
3
23
  interface ErrorTree {
4
24
  message: string;
@@ -11,6 +31,7 @@ interface ErrorTree {
11
31
  }
12
32
 
13
33
  export class ValidationError extends Error {
34
+ code?: string;
14
35
  message: string;
15
36
  item?: string | number;
16
37
  keyword: string;
@@ -20,51 +41,76 @@ export class ValidationError extends Error {
20
41
  data?: any;
21
42
  schema?: CompiledSchema;
22
43
 
23
- private _getCause(pointer = "#", instancePointer = "#"): ValidationError {
24
- let schemaPath = `${pointer}/${this.keyword}`;
25
- let instancePath = `${instancePointer}`;
26
- if (typeof this.item !== "undefined") {
27
- if (typeof this.item === "string" && this.item in this.schema) {
28
- schemaPath += `/${this.item}`;
29
- }
30
- instancePath += `/${this.item}`;
31
- }
32
-
33
- this.instancePath = instancePath;
34
- this.schemaPath = schemaPath;
35
-
36
- // If there is no cause or the cause is not a ValidationError, return this
37
- if (!this.cause || !(this.cause instanceof ValidationError)) {
38
- return this;
39
- }
40
-
41
- return this.cause._getCause(schemaPath, instancePath);
44
+ constructor(message: string) {
45
+ super(message);
46
+ this.message = message;
42
47
  }
43
48
 
44
49
  getCause(): ValidationError {
45
- return this._getCause();
46
- }
47
-
48
- private _getTree(): ErrorTree {
49
- const tree: ErrorTree = {
50
- message: this.message,
51
- keyword: this.keyword,
52
- item: this.item,
53
- schemaPath: this.schemaPath,
54
- instancePath: this.instancePath,
55
- data: this.data
56
- };
57
-
58
- if (this.cause) {
59
- tree.cause = this.cause._getTree();
50
+ let current: ValidationError = this;
51
+ let schemaPointer = "#";
52
+ let instancePointer = "#";
53
+ const seen = new Set<ValidationError>();
54
+
55
+ while (!seen.has(current)) {
56
+ seen.add(current);
57
+ let schemaPath = `${schemaPointer}/${current.keyword}`;
58
+ let instancePath = instancePointer;
59
+ if (typeof current.item !== "undefined") {
60
+ if (
61
+ typeof current.item === "string" &&
62
+ current.schema &&
63
+ typeof current.schema === "object" &&
64
+ current.item in current.schema
65
+ ) {
66
+ schemaPath += `/${escapeJsonPointerToken(current.item)}`;
67
+ }
68
+ instancePath += `/${escapeJsonPointerToken(current.item)}`;
69
+ }
70
+ current.schemaPath = schemaPath;
71
+ current.instancePath = instancePath;
72
+
73
+ if (
74
+ !(current.cause instanceof ValidationError) ||
75
+ seen.has(current.cause)
76
+ ) {
77
+ return current;
78
+ }
79
+ schemaPointer = schemaPath;
80
+ instancePointer = instancePath;
81
+ current = current.cause;
60
82
  }
61
-
62
- return tree;
83
+ return current;
63
84
  }
64
85
 
65
86
  getTree(): ErrorTree {
66
87
  this.getCause();
67
- return this._getTree();
88
+ let current: ValidationError | undefined = this;
89
+ let root: ErrorTree | undefined;
90
+ let target: ErrorTree | undefined;
91
+ const seen = new Set<ValidationError>();
92
+
93
+ while (current && !seen.has(current)) {
94
+ seen.add(current);
95
+ const node: ErrorTree = {
96
+ message: current.message,
97
+ keyword: current.keyword,
98
+ item: current.item,
99
+ schemaPath: current.schemaPath,
100
+ instancePath: current.instancePath,
101
+ data: current.data
102
+ };
103
+ if (!root) {
104
+ root = node;
105
+ } else if (target) {
106
+ target.cause = node;
107
+ }
108
+ target = node;
109
+ current =
110
+ current.cause instanceof ValidationError ? current.cause : undefined;
111
+ }
112
+
113
+ return root!;
68
114
  }
69
115
 
70
116
  getPath() {
@@ -77,6 +123,7 @@ export class ValidationError extends Error {
77
123
  }
78
124
 
79
125
  export interface DefineErrorOptions {
126
+ code?: string;
80
127
  item?: any; // Final item in the schemaPath
81
128
  cause?: ValidationError | true; // Cause of the error
82
129
  data?: any; // Data that caused the error
@@ -88,6 +135,7 @@ export interface DefineErrorFunction {
88
135
  options?: DefineErrorOptions
89
136
  ): ValidationError | void | true;
90
137
  }
138
+
91
139
  const FAIL_FAST_DEFINE_ERROR: DefineErrorFunction = () => true;
92
140
 
93
141
  export function getDefinedErrorFunctionForKey(
@@ -105,9 +153,12 @@ export function getDefinedErrorFunctionForKey(
105
153
 
106
154
  const defineError: DefineErrorFunction = (message, options = {}) => {
107
155
  KeywordError.message = message;
156
+ KeywordError.code = options.code;
108
157
  KeywordError.item = options.item;
109
- KeywordError.cause =
110
- options.cause && options.cause !== true ? options.cause : undefined;
158
+ if (options.cause !== KeywordError) {
159
+ KeywordError.cause =
160
+ options.cause && options.cause !== true ? options.cause : undefined;
161
+ }
111
162
  KeywordError.data = options.data;
112
163
  return KeywordError;
113
164
  };
@@ -118,6 +169,10 @@ export function getDefinedErrorFunctionForKey(
118
169
  );
119
170
  }
120
171
 
172
+ export function escapeJsonPointerToken(value: string | number): string {
173
+ return String(value).replace(/~/g, "~0").replace(/\//g, "~1");
174
+ }
175
+
121
176
  export function getUTF16Length(str) {
122
177
  let length = 0;
123
178
  for (let i = 0; i < str.length; i++) {
@@ -139,8 +194,8 @@ export function isCompiledSchema(subSchema: any): subSchema is CompiledSchema {
139
194
  );
140
195
  }
141
196
 
142
- export function getNamedFunction<T>(name: string, fn: T): T {
143
- return Object.defineProperty(fn, "name", { value: name });
197
+ export function getNamedFunction<T extends object>(name: string, fn: T): T {
198
+ return definePropertyOrThrow(fn, "name", { value: name });
144
199
  }
145
200
 
146
201
  export function resolvePath(root: any, path: string): any {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schema-shield",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "A fast library that protects your JSON schema from invalid data.",
5
5
  "repository": "git@github.com:Masquerade-Circus/schema-shield.git",
6
6
  "author": "Masquerade <christian@masquerade-circus.net>",
@@ -13,7 +13,7 @@
13
13
  "module": "dist/index.mjs",
14
14
  "unpkg": "dist/index.min.js",
15
15
  "browser": "dist/index.min.js",
16
- "types": "dist/**/*.d.ts",
16
+ "types": "dist/index.d.ts",
17
17
  "exports": {
18
18
  ".": {
19
19
  "types": "./dist/index.d.ts",
@@ -66,7 +66,7 @@
66
66
  "User friendly"
67
67
  ],
68
68
  "engines": {
69
- "node": ">=12.0.0"
69
+ "node": ">=16.1.0"
70
70
  },
71
71
  "scripts": {
72
72
  "test": "mocha --bail --recursive --no-timeouts --forbid-only --exit --require ts-node/register --enable-source-maps tests/**/*.test.ts",
@@ -78,15 +78,10 @@
78
78
  "release": "release-it --verbose",
79
79
  "release-test": "release-it --dry-run --verbose"
80
80
  },
81
- "dependencies": {
82
- "ts-node": "^10.9.1",
83
- "tsc-prog": "^2.2.1",
84
- "tslib": "^2.5.0",
85
- "typescript": "^5.0.2"
86
- },
87
81
  "devDependencies": {
88
82
  "@exodus/schemasafe": "^1.0.0",
89
83
  "@release-it/conventional-changelog": "^5.1.1",
84
+ "@types/mocha": "^10.0.10",
90
85
  "@typescript-eslint/eslint-plugin": "^5.56.0",
91
86
  "@typescript-eslint/parser": "^5.56.0",
92
87
  "ajv": "^8.12.0",
@@ -100,7 +95,11 @@
100
95
  "nodemon": "^2.0.22",
101
96
  "nyc": "^15.1.0",
102
97
  "release-it": "^15.9.3",
103
- "terser": "^5.16.6"
98
+ "terser": "^5.16.6",
99
+ "ts-node": "^10.9.1",
100
+ "tsc-prog": "^2.2.1",
101
+ "tslib": "^2.5.0",
102
+ "typescript": "^5.0.2"
104
103
  },
105
104
  "nyc": {
106
105
  "exclude": [