@muze-nl/assert 0.5.1 → 0.6.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 +1 -1
- package/README.md +41 -121
- package/dist/assert.js +276 -44
- package/dist/assert.min.js +3 -1
- package/dist/assert.min.js.map +4 -4
- package/package.json +18 -5
- package/src/{assert.mjs~ → assert-core.mjs} +273 -32
- package/src/assert.mjs +3 -326
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -4,158 +4,78 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@muze-nl/assert)
|
|
5
5
|
[![Project stage: Development][project-stage-badge: Development]][project-stage-page]
|
|
6
6
|
|
|
7
|
-
# Assert:
|
|
8
|
-
|
|
9
|
-
This is a light-weight library to do optional assertion checking. By default any assertions made are not tested. Assertion code is not run. Unless you toggle assertion checking, usually in developer mode, by calling `enable`. Now your assertions are run, and if any assertions fail, an error is thrown with information about the specific failure.
|
|
10
|
-
|
|
11
|
-
This style of assertion testing is often used with [Design by Contract](https://en.wikipedia.org/wiki/Design_by_contract) software development. When implementing a fixed specification, like a W3C recommendation or RFC, using design by contract allows you to write code very similar to the specification.
|
|
12
|
-
|
|
13
|
-
Here is an example from the [@muze-nl/metro-oidc](https://github.com/muze-nl/,etro-oidc) library, which shows how you can use Assert to check specification requirements, in a dense but readable way:
|
|
7
|
+
# Assert: optional assertion checking
|
|
14
8
|
|
|
15
9
|
```javascript
|
|
16
|
-
|
|
17
|
-
const openid_client_metadata = {
|
|
18
|
-
redirect_uris: Required([validURL]),
|
|
19
|
-
response_types: Optional([]),
|
|
20
|
-
grant_types: Optional(anyOf('authorization_code','refresh_token')), //TODO: match response_types with grant_types
|
|
21
|
-
application_type: Optional(oneOf('native','web')),
|
|
22
|
-
contacts: Optional([validEmail]),
|
|
23
|
-
client_name: Optional(String),
|
|
24
|
-
logo_uri: Optional(validURL),
|
|
25
|
-
client_uri: Optional(validURL),
|
|
26
|
-
policy_uri: Optional(validURL),
|
|
27
|
-
tos_uri: Optional(validURL),
|
|
28
|
-
jwks_uri: Optional(validURL, not(MustHave('jwks'))),
|
|
29
|
-
jwks: Optional(validURL, not(MustHave('jwks_uri'))),
|
|
30
|
-
sector_identifier_uri: Optional(validURL),
|
|
31
|
-
subject_type: Optional(String),
|
|
32
|
-
id_token_signed_response_alg: Optional(oneOf(...validJWA)),
|
|
33
|
-
id_token_encrypted_response_alg: Optional(oneOf(...validJWA)),
|
|
34
|
-
id_token_encrypted_response_enc: Optional(oneOf(...validJWA), MustHave('id_token_encrypted_response_alg')),
|
|
35
|
-
userinfo_signed_response_alg: Optional(oneOf(...validJWA)),
|
|
36
|
-
userinfo_encrypted_response_alg: Optional(oneOf(...validJWA)),
|
|
37
|
-
userinfo_encrypted_response_enc: Optional(oneOf(...validJWA), MustHave('userinfo_encrypted_response_alg')),
|
|
38
|
-
request_object_signing_alg: Optional(oneOf(...validJWA)),
|
|
39
|
-
request_object_encryption_alg: Optional(oneOf(...validJWA)),
|
|
40
|
-
request_object_encryption_enc: Optional(oneOf(...validJWA)),
|
|
41
|
-
token_endpoint_auth_method: Optional(oneOf(...validAuthMethods)),
|
|
42
|
-
token_endpoint_auth_signing_alg: Optional(oneOf(...validJWA)),
|
|
43
|
-
default_max_age: Optional(Number),
|
|
44
|
-
require_auth_time: Optional(Boolean),
|
|
45
|
-
default_acr_values: Optional([String]),
|
|
46
|
-
initiate_login_uri: Optional([validURL]),
|
|
47
|
-
request_uris: Optional([validURL])
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
assert(options, {
|
|
51
|
-
client: Optional(instanceOf(metro.client().constructor)),
|
|
52
|
-
registration_endpoint: validURL,
|
|
53
|
-
client_info: openid_client_metadata
|
|
54
|
-
})
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
_Note:_ This library was created as part of the [@muze-nl/metro](https://github.com/muze-nl/metro/) package initially, but has escaped its confines. In the rest of the documentation, when referring to 'middleware', we mean middleware modules for the metro http client in the browser.
|
|
10
|
+
import { assert, enable, Optional, Required, oneOf, validURL } from '@muze-nl/assert/core'
|
|
58
11
|
|
|
59
|
-
|
|
12
|
+
enable()
|
|
60
13
|
|
|
61
|
-
|
|
14
|
+
function registerClient(metadata) {
|
|
15
|
+
assert(metadata, {
|
|
16
|
+
redirect_uris: Required([validURL]),
|
|
17
|
+
application_type: Optional(oneOf('web', 'native')),
|
|
18
|
+
client_name: Optional(String)
|
|
19
|
+
})
|
|
62
20
|
|
|
63
|
-
|
|
64
|
-
|
|
21
|
+
// continue with metadata known to match the expected shape
|
|
22
|
+
}
|
|
65
23
|
```
|
|
66
24
|
|
|
67
|
-
|
|
68
|
-
```javascript
|
|
69
|
-
import * as assert from '@muze-nl/assert'
|
|
70
|
-
```
|
|
25
|
+
## Table of Contents
|
|
71
26
|
|
|
72
|
-
|
|
27
|
+
1. [Introduction](#introduction)
|
|
28
|
+
2. [Usage](#usage)
|
|
29
|
+
3. [Documentation](#documentation)
|
|
30
|
+
4. [License](#license)
|
|
73
31
|
|
|
74
|
-
|
|
75
|
-
import { assert, enable, disable, Optional, Required, Recommended, oneOf, anyOf, not, validURL, instanceOf } from '@muze-nl/assert'
|
|
76
|
-
```
|
|
32
|
+
## Introduction
|
|
77
33
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
```
|
|
34
|
+
Assert is a lightweight library for optional runtime checks. It is meant for code that benefits from explicit developer feedback during development, but should not spend time validating assumptions in production unless you ask it to.
|
|
35
|
+
|
|
36
|
+
Assertions are disabled by default. `assert()` returns immediately until you call `enable()`. When enabled, failed assertions throw an error with path-aware details. If you always want to validate and handle failures yourself, use `fails()` or `issues()` directly.
|
|
82
37
|
|
|
83
|
-
|
|
38
|
+
This style is useful for design-by-contract checks, protocol implementations, middleware preconditions, mock servers, and other places where executable requirements make code easier to understand.
|
|
84
39
|
|
|
85
40
|
## Usage
|
|
86
41
|
|
|
87
|
-
|
|
88
|
-
import { assert, Optional, Required, not, validURL } from '@muze-nl/assert'
|
|
42
|
+
Install with npm:
|
|
89
43
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
assert(param2, Optional(not(/foo.*/)))
|
|
93
|
-
// do your own stuff here
|
|
94
|
-
}
|
|
44
|
+
```shell
|
|
45
|
+
npm install @muze-nl/assert
|
|
95
46
|
```
|
|
96
47
|
|
|
97
|
-
|
|
48
|
+
Use the side-effect-free entry point when you want tree-shaking:
|
|
98
49
|
|
|
99
50
|
```javascript
|
|
100
|
-
import
|
|
101
|
-
|
|
102
|
-
assert.enable()
|
|
51
|
+
import { assert, enable, Required, validURL } from '@muze-nl/assert/core'
|
|
103
52
|
```
|
|
104
53
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
When writing middleware there is usually quite a lot of preconditions to check. When a developer wants to use your middleware, it is nice to have explicit feedback about what he or she is doing wrong. However this is only useful during development. Once in production you should assume that there are no developer mistakes anymore... or at least that the end user has no use for detailed error reports about your middleware.
|
|
108
|
-
|
|
109
|
-
This is especially true about mock middleware. Mock middleware is middleware that blocks the actual transmission of a request, and returns a mock response instead. Your browser doesn't actually fetch the requests URL.
|
|
110
|
-
|
|
111
|
-
The [oauth2 middleware](https://github.com/muze-nl/metro-oauth2) for example, has unit tests that use the oauth2 mock-server middleware to mimick a server. This way you can be sure that the oauth2 client implementation works, without having to setup a real oauth2 server anywhere.
|
|
112
|
-
|
|
113
|
-
Since these mock middleware servers are especially meant for the initial development of new middleware, they should assert as much as they can. And send comprehensive error messages to the console. Here the [`assert.fails()`](./docs/fails.md) method comes in handy.
|
|
114
|
-
|
|
115
|
-
`assert.fails()` returns `false` if there are no problems. If one or more assertions do fail, it will return an array with messages about each failed assertion. So one way of using it is like this:
|
|
54
|
+
The package root exports the same API and also assigns it to `globalThis.assert` for compatibility:
|
|
116
55
|
|
|
117
56
|
```javascript
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (error = assert.fails(url, {
|
|
121
|
-
searchParams: {
|
|
122
|
-
response_type: 'code',
|
|
123
|
-
client_id: 'mockClientId',
|
|
124
|
-
state: assert.Optional(/.+/)
|
|
125
|
-
}
|
|
126
|
-
})) {
|
|
127
|
-
return metro.response({
|
|
128
|
-
url: req.url,
|
|
129
|
-
status: 400,
|
|
130
|
-
statusText: 'Bad Request',
|
|
131
|
-
body: '400 Bad Request'
|
|
132
|
-
})
|
|
133
|
-
}
|
|
57
|
+
import * as assert from '@muze-nl/assert'
|
|
134
58
|
```
|
|
135
59
|
|
|
136
|
-
|
|
60
|
+
In the browser, using a CDN:
|
|
137
61
|
|
|
138
|
-
|
|
62
|
+
```html
|
|
63
|
+
<script src="https://cdn.jsdelivr.net/npm/@muze-nl/assert/dist/assert.min.js" crossorigin="anonymous"></script>
|
|
64
|
+
```
|
|
139
65
|
|
|
140
|
-
|
|
66
|
+
This loads the API as `window.assert`.
|
|
141
67
|
|
|
142
|
-
|
|
68
|
+
## Documentation
|
|
143
69
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
})
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
This makes sure that the `client_id` and `authRedirectURL` configuration options have been set and are not empty. But when the code is used in production, this should never happen. There is no need to constantly test for this. And in production it won't actually get checked. Only when you enable assertions will this code actually perform the tests:
|
|
70
|
+
- [Documentation index](docs/)
|
|
71
|
+
- [Tutorial](docs/tutorial.md)
|
|
72
|
+
- [Reference](docs/reference/)
|
|
73
|
+
- [Creating custom assertion checks](docs/reference/#creating-custom-assertion-checks)
|
|
74
|
+
- [fails()](docs/reference/fails.md), [issues()](docs/reference/issues.md), and [formatIssues()](docs/reference/formatIssues.md)
|
|
153
75
|
|
|
154
|
-
|
|
155
|
-
assert.enable()
|
|
156
|
-
```
|
|
76
|
+
## License
|
|
157
77
|
|
|
158
|
-
|
|
78
|
+
This software is licensed under the MIT open source license. See the [License](./LICENSE) file.
|
|
159
79
|
|
|
160
80
|
[project-stage-badge: Development]: https://img.shields.io/badge/Project%20Stage-Development-yellowgreen.svg
|
|
161
81
|
[project-stage-page]: https://blog.pother.ca/project-stages/
|
package/dist/assert.js
CHANGED
|
@@ -1,19 +1,254 @@
|
|
|
1
1
|
(() => {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/assert-core.mjs
|
|
9
|
+
var assert_core_exports = {};
|
|
10
|
+
__export(assert_core_exports, {
|
|
11
|
+
Optional: () => Optional,
|
|
12
|
+
Recommended: () => Recommended,
|
|
13
|
+
Required: () => Required,
|
|
14
|
+
allOf: () => allOf,
|
|
15
|
+
anyOf: () => anyOf,
|
|
16
|
+
assert: () => assert,
|
|
17
|
+
disable: () => disable,
|
|
18
|
+
enable: () => enable,
|
|
19
|
+
error: () => error,
|
|
20
|
+
fails: () => fails,
|
|
21
|
+
formatIssue: () => formatIssue,
|
|
22
|
+
formatIssues: () => formatIssues,
|
|
23
|
+
instanceOf: () => instanceOf,
|
|
24
|
+
issues: () => issues,
|
|
25
|
+
not: () => not,
|
|
26
|
+
oneOf: () => oneOf,
|
|
27
|
+
validEmail: () => validEmail,
|
|
28
|
+
validURL: () => validURL,
|
|
29
|
+
warn: () => warn
|
|
30
|
+
});
|
|
31
|
+
var assertEnabled = false;
|
|
4
32
|
function enable() {
|
|
5
|
-
|
|
33
|
+
assertEnabled = true;
|
|
6
34
|
}
|
|
7
35
|
function disable() {
|
|
8
|
-
|
|
36
|
+
assertEnabled = false;
|
|
37
|
+
}
|
|
38
|
+
function appendPath(path = "", key) {
|
|
39
|
+
if (typeof path == "undefined" || path == null) {
|
|
40
|
+
path = "";
|
|
41
|
+
}
|
|
42
|
+
if (typeof key == "number") {
|
|
43
|
+
return `${path}[${key}]`;
|
|
44
|
+
}
|
|
45
|
+
return `${path}.${key}`;
|
|
46
|
+
}
|
|
47
|
+
function pathToArray(path = "") {
|
|
48
|
+
if (Array.isArray(path)) {
|
|
49
|
+
return path;
|
|
50
|
+
}
|
|
51
|
+
if (!path) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
let result = [];
|
|
55
|
+
let matcher = /(?:^|\.)([^.\[\]]+)|\[(\d+)\]/g;
|
|
56
|
+
let match;
|
|
57
|
+
while (match = matcher.exec(path)) {
|
|
58
|
+
if (typeof match[1] != "undefined") {
|
|
59
|
+
result.push(match[1]);
|
|
60
|
+
} else if (typeof match[2] != "undefined") {
|
|
61
|
+
result.push(Number(match[2]));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
function pathToString(path = []) {
|
|
67
|
+
if (typeof path == "string") {
|
|
68
|
+
return path.startsWith(".") ? path.slice(1) : path;
|
|
69
|
+
}
|
|
70
|
+
return path.map((part, index) => {
|
|
71
|
+
if (typeof part == "number") {
|
|
72
|
+
return `[${part}]`;
|
|
73
|
+
}
|
|
74
|
+
return `${index ? "." : ""}${part}`;
|
|
75
|
+
}).join("");
|
|
76
|
+
}
|
|
77
|
+
function describeFunction(value) {
|
|
78
|
+
if (value === String) {
|
|
79
|
+
return "string";
|
|
80
|
+
}
|
|
81
|
+
if (value === Number) {
|
|
82
|
+
return "number";
|
|
83
|
+
}
|
|
84
|
+
if (value === Boolean) {
|
|
85
|
+
return "boolean";
|
|
86
|
+
}
|
|
87
|
+
return value.name || "function";
|
|
88
|
+
}
|
|
89
|
+
function clip(text, maxLength = 60) {
|
|
90
|
+
if (text.length <= maxLength) {
|
|
91
|
+
return text;
|
|
92
|
+
}
|
|
93
|
+
return text.slice(0, maxLength - 1) + "\u2026";
|
|
94
|
+
}
|
|
95
|
+
function quoteString(value) {
|
|
96
|
+
return `'${clip(String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n"))}'`;
|
|
97
|
+
}
|
|
98
|
+
function jsonSummary(value) {
|
|
99
|
+
try {
|
|
100
|
+
let json = JSON.stringify(value);
|
|
101
|
+
if (typeof json == "string") {
|
|
102
|
+
return clip(json);
|
|
103
|
+
}
|
|
104
|
+
} catch (e) {
|
|
105
|
+
}
|
|
106
|
+
let name = value?.constructor?.name;
|
|
107
|
+
if (name && name != "Object") {
|
|
108
|
+
return name;
|
|
109
|
+
}
|
|
110
|
+
return Object.prototype.toString.call(value);
|
|
111
|
+
}
|
|
112
|
+
function formatValue(value) {
|
|
113
|
+
if (typeof value == "string") {
|
|
114
|
+
return quoteString(value);
|
|
115
|
+
}
|
|
116
|
+
if (typeof value == "undefined") {
|
|
117
|
+
return "undefined";
|
|
118
|
+
}
|
|
119
|
+
if (value === null) {
|
|
120
|
+
return "null";
|
|
121
|
+
}
|
|
122
|
+
if (typeof value == "function") {
|
|
123
|
+
return describeFunction(value);
|
|
124
|
+
}
|
|
125
|
+
if (value instanceof RegExp) {
|
|
126
|
+
return value.toString();
|
|
127
|
+
}
|
|
128
|
+
if (typeof value == "number" || typeof value == "boolean" || typeof value == "bigint") {
|
|
129
|
+
return String(value);
|
|
130
|
+
}
|
|
131
|
+
if (typeof value == "symbol") {
|
|
132
|
+
return value.toString();
|
|
133
|
+
}
|
|
134
|
+
return jsonSummary(value);
|
|
135
|
+
}
|
|
136
|
+
function describeExpected(value) {
|
|
137
|
+
if (value === String || value === Number || value === Boolean) {
|
|
138
|
+
return describeFunction(value);
|
|
139
|
+
}
|
|
140
|
+
if (typeof value == "function") {
|
|
141
|
+
return describeFunction(value);
|
|
142
|
+
}
|
|
143
|
+
if (value instanceof RegExp) {
|
|
144
|
+
return value.toString();
|
|
145
|
+
}
|
|
146
|
+
if (Array.isArray(value)) {
|
|
147
|
+
return "[" + value.map(describeExpected).join(", ") + "]";
|
|
148
|
+
}
|
|
149
|
+
return formatValue(value);
|
|
150
|
+
}
|
|
151
|
+
function describeOneOf(patterns) {
|
|
152
|
+
return patterns.map(describeExpected).join(", ");
|
|
153
|
+
}
|
|
154
|
+
function conciseMessage(message, actual, expected) {
|
|
155
|
+
if (message == "data and pattern are not equal") {
|
|
156
|
+
return `expected ${formatValue(expected)}, found ${formatValue(actual)}`;
|
|
157
|
+
}
|
|
158
|
+
if (message == "data does not match pattern" || /^data\[\d+\] does not match pattern$/.test(message)) {
|
|
159
|
+
return `expected ${describeExpected(expected)}, found ${formatValue(actual)}`;
|
|
160
|
+
}
|
|
161
|
+
if (message == "data is undefined, should match pattern") {
|
|
162
|
+
return `missing; expected ${describeExpected(expected)}`;
|
|
163
|
+
}
|
|
164
|
+
if (message == "data is required") {
|
|
165
|
+
return "required";
|
|
166
|
+
}
|
|
167
|
+
if (message == "data is an empty string, which is not allowed") {
|
|
168
|
+
return "empty string is not allowed";
|
|
169
|
+
}
|
|
170
|
+
if (message == "data is not an object, pattern is") {
|
|
171
|
+
return "data is not an object";
|
|
172
|
+
}
|
|
173
|
+
if (message == "data is not an instanceof pattern") {
|
|
174
|
+
return `expected instance of ${describeExpected(expected)}, found ${formatValue(actual)}`;
|
|
175
|
+
}
|
|
176
|
+
if (message == "data does not match oneOf patterns" || message == "data does not match anyOf patterns") {
|
|
177
|
+
return `expected one of ${describeOneOf(expected)}, found ${formatValue(actual)}`;
|
|
178
|
+
}
|
|
179
|
+
if (message == "data matches pattern, when required not to") {
|
|
180
|
+
return `must not match ${describeExpected(expected)}`;
|
|
181
|
+
}
|
|
182
|
+
return message;
|
|
183
|
+
}
|
|
184
|
+
function formatIssue(issue, options = {}) {
|
|
185
|
+
if (!issue || typeof issue != "object") {
|
|
186
|
+
return String(issue);
|
|
187
|
+
}
|
|
188
|
+
let path = issue.pathString || pathToString(issue.path || []) || "value";
|
|
189
|
+
let indent = options.indent ?? "";
|
|
190
|
+
return `${indent}${path}: ${issue.message}`;
|
|
191
|
+
}
|
|
192
|
+
function formatIssues(issues2, options = {}) {
|
|
193
|
+
if (!issues2) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
let indent = options.indent ?? " - ";
|
|
197
|
+
return (Array.isArray(issues2) ? issues2 : [issues2]).map((issue) => formatIssue(issue, { ...options, indent }));
|
|
198
|
+
}
|
|
199
|
+
function issueFromProblem(problem) {
|
|
200
|
+
if (!problem || typeof problem != "object") {
|
|
201
|
+
return {
|
|
202
|
+
path: [],
|
|
203
|
+
pathString: "",
|
|
204
|
+
message: String(problem),
|
|
205
|
+
expected: void 0,
|
|
206
|
+
actual: void 0
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
let path = pathToArray(problem.path);
|
|
210
|
+
let pathString = pathToString(path);
|
|
211
|
+
let actual = problem.actual ?? problem.found;
|
|
212
|
+
let expected = describeExpected(problem.expected);
|
|
213
|
+
let message = conciseMessage(problem.message, actual, problem.expected);
|
|
214
|
+
return {
|
|
215
|
+
path,
|
|
216
|
+
pathString,
|
|
217
|
+
message,
|
|
218
|
+
expected,
|
|
219
|
+
actual
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function problemsToIssues(problems) {
|
|
223
|
+
if (!problems) {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
let result = [];
|
|
227
|
+
for (let problem of Array.isArray(problems) ? problems : [problems]) {
|
|
228
|
+
if (!problem) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (problem && typeof problem == "object" && problem.problems) {
|
|
232
|
+
let nested = problemsToIssues(problem.problems);
|
|
233
|
+
if (nested.length) {
|
|
234
|
+
result = result.concat(nested);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
result.push(issueFromProblem(problem));
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
9
241
|
}
|
|
10
242
|
function assert(source, test) {
|
|
11
|
-
if (
|
|
243
|
+
if (assertEnabled) {
|
|
12
244
|
let problems = fails(source, test);
|
|
13
245
|
if (problems) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
246
|
+
let assertionIssues = problemsToIssues(problems);
|
|
247
|
+
let formattedIssues = formatIssues(assertionIssues);
|
|
248
|
+
let message = "Assertions failed:\n" + formattedIssues.join("\n");
|
|
249
|
+
console.error("\u{1F170}\uFE0F " + message);
|
|
250
|
+
throw new Error(message, {
|
|
251
|
+
cause: { problems, issues: assertionIssues, source }
|
|
17
252
|
});
|
|
18
253
|
}
|
|
19
254
|
}
|
|
@@ -61,9 +296,10 @@
|
|
|
61
296
|
if (!Array.isArray(data)) {
|
|
62
297
|
return error("data is not an array", data, "anyOf", path);
|
|
63
298
|
}
|
|
64
|
-
for (let value of data) {
|
|
65
|
-
|
|
66
|
-
|
|
299
|
+
for (let [index, value] of data.entries()) {
|
|
300
|
+
let itemPath = appendPath(path, index);
|
|
301
|
+
if (oneOf(...patterns)(value, root, itemPath)) {
|
|
302
|
+
return error("data does not match anyOf patterns", value, patterns, itemPath);
|
|
67
303
|
}
|
|
68
304
|
}
|
|
69
305
|
return false;
|
|
@@ -115,8 +351,15 @@
|
|
|
115
351
|
}
|
|
116
352
|
};
|
|
117
353
|
}
|
|
354
|
+
function issues(data, pattern, root) {
|
|
355
|
+
let problems = fails(data, pattern, root);
|
|
356
|
+
if (!problems) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
return problemsToIssues(problems);
|
|
360
|
+
}
|
|
118
361
|
function fails(data, pattern, root, path = "") {
|
|
119
|
-
if (
|
|
362
|
+
if (typeof root == "undefined") {
|
|
120
363
|
root = data;
|
|
121
364
|
}
|
|
122
365
|
let problems = [];
|
|
@@ -137,9 +380,9 @@
|
|
|
137
380
|
}
|
|
138
381
|
} else if (pattern instanceof RegExp) {
|
|
139
382
|
if (Array.isArray(data)) {
|
|
140
|
-
let index = data.findIndex((element, index2) => fails(element, pattern, root, path
|
|
383
|
+
let index = data.findIndex((element, index2) => fails(element, pattern, root, appendPath(path, index2)));
|
|
141
384
|
if (index > -1) {
|
|
142
|
-
problems.push(error("data[" + index + "] does not match pattern", data[index], pattern, path
|
|
385
|
+
problems.push(error("data[" + index + "] does not match pattern", data[index], pattern, appendPath(path, index)));
|
|
143
386
|
}
|
|
144
387
|
} else if (typeof data == "undefined") {
|
|
145
388
|
problems.push(error("data is undefined, should match pattern", data, pattern, path));
|
|
@@ -158,22 +401,23 @@
|
|
|
158
401
|
} else if (Array.isArray(pattern)) {
|
|
159
402
|
if (!Array.isArray(data)) {
|
|
160
403
|
problems.push(error("data is not an array", data, [], path));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
404
|
+
} else {
|
|
405
|
+
for (let p of pattern) {
|
|
406
|
+
for (let index of data.keys()) {
|
|
407
|
+
let problem = fails(data[index], p, root, appendPath(path, index));
|
|
408
|
+
if (Array.isArray(problem)) {
|
|
409
|
+
problems = problems.concat(problem);
|
|
410
|
+
} else if (problem) {
|
|
411
|
+
problems.push(problem);
|
|
412
|
+
}
|
|
169
413
|
}
|
|
170
414
|
}
|
|
171
415
|
}
|
|
172
416
|
} else if (pattern && typeof pattern == "object") {
|
|
173
417
|
if (Array.isArray(data)) {
|
|
174
|
-
let index = data.findIndex((element, index2) => fails(element, pattern, root, path
|
|
418
|
+
let index = data.findIndex((element, index2) => fails(element, pattern, root, appendPath(path, index2)));
|
|
175
419
|
if (index > -1) {
|
|
176
|
-
problems.push(error("data[" + index + "] does not match pattern", data[index], pattern, path
|
|
420
|
+
problems.push(error("data[" + index + "] does not match pattern", data[index], pattern, appendPath(path, index)));
|
|
177
421
|
}
|
|
178
422
|
} else if (!data || typeof data != "object") {
|
|
179
423
|
problems.push(error("data is not an object, pattern is", data, pattern, path));
|
|
@@ -188,7 +432,7 @@
|
|
|
188
432
|
}
|
|
189
433
|
} else {
|
|
190
434
|
for (const [patternKey, subpattern] of Object.entries(pattern)) {
|
|
191
|
-
let result = fails(data[patternKey], subpattern, root, path
|
|
435
|
+
let result = fails(data[patternKey], subpattern, root, appendPath(path, patternKey));
|
|
192
436
|
if (result) {
|
|
193
437
|
problems = problems.concat(result);
|
|
194
438
|
}
|
|
@@ -205,9 +449,12 @@
|
|
|
205
449
|
}
|
|
206
450
|
return false;
|
|
207
451
|
}
|
|
208
|
-
function error(message, found, expected, path, problems) {
|
|
452
|
+
function error(message, found, expected, path = "", problems) {
|
|
453
|
+
let pathParts = pathToArray(path);
|
|
209
454
|
let result = {
|
|
210
455
|
path,
|
|
456
|
+
pathString: pathToString(pathParts),
|
|
457
|
+
pathParts,
|
|
211
458
|
message,
|
|
212
459
|
found,
|
|
213
460
|
expected
|
|
@@ -220,22 +467,7 @@
|
|
|
220
467
|
function warn(message, data, pattern, path) {
|
|
221
468
|
console.warn("\u{1F170}\uFE0F Assert: " + path, message, pattern, data);
|
|
222
469
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
assert,
|
|
227
|
-
enable,
|
|
228
|
-
disable,
|
|
229
|
-
Required,
|
|
230
|
-
Recommended,
|
|
231
|
-
Optional,
|
|
232
|
-
oneOf,
|
|
233
|
-
anyOf,
|
|
234
|
-
allOf,
|
|
235
|
-
validURL,
|
|
236
|
-
validEmail,
|
|
237
|
-
instanceOf,
|
|
238
|
-
not,
|
|
239
|
-
fails
|
|
240
|
-
};
|
|
470
|
+
|
|
471
|
+
// src/assert.mjs
|
|
472
|
+
globalThis.assert = { ...assert_core_exports };
|
|
241
473
|
})();
|
package/dist/assert.min.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
(()=>{
|
|
1
|
+
(()=>{var j=Object.defineProperty;var w=(e,n)=>{for(var r in n)j(e,r,{get:n[r],enumerable:!0})};var g={};w(g,{Optional:()=>L,Recommended:()=>F,Required:()=>P,allOf:()=>J,anyOf:()=>T,assert:()=>B,disable:()=>q,enable:()=>R,error:()=>f,fails:()=>c,formatIssue:()=>A,formatIssues:()=>$,instanceOf:()=>V,issues:()=>z,not:()=>W,oneOf:()=>O,validEmail:()=>M,validURL:()=>K,warn:()=>S});var m=!1;function R(){m=!0}function q(){m=!1}function l(e="",n){return(typeof e>"u"||e==null)&&(e=""),typeof n=="number"?`${e}[${n}]`:`${e}.${n}`}function h(e=""){if(Array.isArray(e))return e;if(!e)return[];let n=[],r=/(?:^|\.)([^.\[\]]+)|\[(\d+)\]/g,t;for(;t=r.exec(e);)typeof t[1]<"u"?n.push(t[1]):typeof t[2]<"u"&&n.push(Number(t[2]));return n}function p(e=[]){return typeof e=="string"?e.startsWith(".")?e.slice(1):e:e.map((n,r)=>typeof n=="number"?`[${n}]`:`${r?".":""}${n}`).join("")}function a(e){return e===String?"string":e===Number?"number":e===Boolean?"boolean":e.name||"function"}function x(e,n=60){return e.length<=n?e:e.slice(0,n-1)+"\u2026"}function E(e){return`'${x(String(e).replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/\n/g,"\\n"))}'`}function I(e){try{let r=JSON.stringify(e);if(typeof r=="string")return x(r)}catch{}let n=e?.constructor?.name;return n&&n!="Object"?n:Object.prototype.toString.call(e)}function y(e){return typeof e=="string"?E(e):typeof e>"u"?"undefined":e===null?"null":typeof e=="function"?a(e):e instanceof RegExp?e.toString():typeof e=="number"||typeof e=="boolean"||typeof e=="bigint"?String(e):typeof e=="symbol"?e.toString():I(e)}function d(e){return e===String||e===Number||e===Boolean||typeof e=="function"?a(e):e instanceof RegExp?e.toString():Array.isArray(e)?"["+e.map(d).join(", ")+"]":y(e)}function _(e){return e.map(d).join(", ")}function N(e,n,r){return e=="data and pattern are not equal"?`expected ${y(r)}, found ${y(n)}`:e=="data does not match pattern"||/^data\[\d+\] does not match pattern$/.test(e)?`expected ${d(r)}, found ${y(n)}`:e=="data is undefined, should match pattern"?`missing; expected ${d(r)}`:e=="data is required"?"required":e=="data is an empty string, which is not allowed"?"empty string is not allowed":e=="data is not an object, pattern is"?"data is not an object":e=="data is not an instanceof pattern"?`expected instance of ${d(r)}, found ${y(n)}`:e=="data does not match oneOf patterns"||e=="data does not match anyOf patterns"?`expected one of ${_(r)}, found ${y(n)}`:e=="data matches pattern, when required not to"?`must not match ${d(r)}`:e}function A(e,n={}){if(!e||typeof e!="object")return String(e);let r=e.pathString||p(e.path||[])||"value";return`${n.indent??""}${r}: ${e.message}`}function $(e,n={}){if(!e)return!1;let r=n.indent??" - ";return(Array.isArray(e)?e:[e]).map(t=>A(t,{...n,indent:r}))}function U(e){if(!e||typeof e!="object")return{path:[],pathString:"",message:String(e),expected:void 0,actual:void 0};let n=h(e.path),r=p(n),t=e.actual??e.found,i=d(e.expected),o=N(e.message,t,e.expected);return{path:n,pathString:r,message:o,expected:i,actual:t}}function b(e){if(!e)return[];let n=[];for(let r of Array.isArray(e)?e:[e])if(r){if(r&&typeof r=="object"&&r.problems){let t=b(r.problems);if(t.length){n=n.concat(t);continue}}n.push(U(r))}return n}function B(e,n){if(m){let r=c(e,n);if(r){let t=b(r),o=`Assertions failed:
|
|
2
|
+
`+$(t).join(`
|
|
3
|
+
`);throw console.error("\u{1F170}\uFE0F "+o),new Error(o,{cause:{problems:r,issues:t,source:e}})}}}function L(e){return function(r,t,i){if(typeof r<"u"&&r!=null&&typeof e<"u")return c(r,e,t,i)}}function P(e){return function(r,t,i){return r==null||typeof r>"u"?f("data is required",r,e||"any value",i):typeof e<"u"?c(r,e,t,i):!1}}function F(e){return function(r,t,i){return r==null||typeof r>"u"?(S("data does not contain recommended value",r,e,i),!1):c(r,e,t,i)}}function O(...e){return function(r,t,i){for(let o of e)if(!c(r,o,t,i))return!1;return f("data does not match oneOf patterns",r,e,i)}}function T(...e){return function(r,t,i){if(!Array.isArray(r))return f("data is not an array",r,"anyOf",i);for(let[o,u]of r.entries()){let s=l(i,o);if(O(...e)(u,t,s))return f("data does not match anyOf patterns",u,e,s)}return!1}}function J(...e){return function(r,t,i){let o=[];for(let u of e)o=o.concat(c(r,u,t,i));if(o=o.filter(Boolean),o.length)return f("data does not match all given patterns",r,e,i,o)}}function K(e,n,r){try{e instanceof URL&&(e=e.href);let t=new URL(e);if(t.href!=e&&!(t.href+"/"==e||t.href==e+"/"))return f("data is not a valid url",e,"validURL",r)}catch{return f("data is not a valid url",e,"validURL",r)}}function M(e,n,r){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))return f("data is not a valid email",e,"validEmail",r)}function V(e){return function(r,t,i){if(!(r instanceof e))return f("data is not an instanceof pattern",r,e,i)}}function W(e){return function(r,t,i){if(!c(r,e,t,i))return f("data matches pattern, when required not to",r,e,i)}}function z(e,n,r){let t=c(e,n,r);return t?b(t):!1}function c(e,n,r,t=""){typeof r>"u"&&(r=e);let i=[];if(n===Boolean)typeof e!="boolean"&&!(e instanceof Boolean)&&i.push(f("data is not a boolean",e,n,t));else if(n===Number)typeof e!="number"&&!(e instanceof Number)&&i.push(f("data is not a number",e,n,t));else if(n===String)typeof e!="string"&&!(e instanceof String)&&i.push(f("data is not a string",e,n,t)),e==""&&i.push(f("data is an empty string, which is not allowed",e,n,t));else if(n instanceof RegExp)if(Array.isArray(e)){let o=e.findIndex((u,s)=>c(u,n,r,l(t,s)));o>-1&&i.push(f("data["+o+"] does not match pattern",e[o],n,l(t,o)))}else typeof e>"u"?i.push(f("data is undefined, should match pattern",e,n,t)):n.test(e)||i.push(f("data does not match pattern",e,n,t));else if(n instanceof Function){let o=n(e,r,t);o&&(Array.isArray(o)?i=i.concat(o):i.push(o))}else if(Array.isArray(n))if(!Array.isArray(e))i.push(f("data is not an array",e,[],t));else for(let o of n)for(let u of e.keys()){let s=c(e[u],o,r,l(t,u));Array.isArray(s)?i=i.concat(s):s&&i.push(s)}else if(n&&typeof n=="object")if(Array.isArray(e)){let o=e.findIndex((u,s)=>c(u,n,r,l(t,s)));o>-1&&i.push(f("data["+o+"] does not match pattern",e[o],n,l(t,o)))}else if(!e||typeof e!="object")i.push(f("data is not an object, pattern is",e,n,t));else if(e instanceof URLSearchParams&&(e=Object.fromEntries(e)),n instanceof Function){let o=c(e,n,r,t);o&&(i=i.concat(o))}else for(let[o,u]of Object.entries(n)){let s=c(e[o],u,r,l(t,o));s&&(i=i.concat(s))}else n!=e&&i.push(f("data and pattern are not equal",e,n,t));return i.length?i:!1}function f(e,n,r,t="",i){let o=h(t),u={path:t,pathString:p(o),pathParts:o,message:e,found:n,expected:r};return i&&(u.problems=i),u}function S(e,n,r,t){console.warn("\u{1F170}\uFE0F Assert: "+t,e,r,n)}globalThis.assert={...g};})();
|
|
2
4
|
//# sourceMappingURL=assert.min.js.map
|