@muze-nl/assert 0.5.0 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Auke van Slooten
3
+ Copyright (c) 2023 - 2026 Muze.nl
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -4,158 +4,78 @@
4
4
  [![npm bundle size](https://img.shields.io/bundlephobia/min/@muze-nl/assert)](https://www.npmjs.com/package/@muze-nl/assert)
5
5
  [![Project stage: Development][project-stage-badge: Development]][project-stage-page]
6
6
 
7
- # Assert: javascript optional assertion checking
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
- // https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
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
- ## Installation
12
+ enable()
60
13
 
61
- ### Using NPM:
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
- ```shell
64
- npm install @muze-nl/assert
21
+ // continue with metadata known to match the expected shape
22
+ }
65
23
  ```
66
24
 
67
- The include it in your javascript code like this:
68
- ```javascript
69
- import * as assert from '@muze-nl/assert'
70
- ```
25
+ ## Table of Contents
71
26
 
72
- Or if you are a fan of shorter assertions:
27
+ 1. [Introduction](#introduction)
28
+ 2. [Usage](#usage)
29
+ 3. [Documentation](#documentation)
30
+ 4. [License](#license)
73
31
 
74
- ```javascript
75
- import { assert, enable, disable, Optional, Required, Recommended, oneOf, anyOf, not, validURL, instanceOf } from '@muze-nl/assert'
76
- ```
32
+ ## Introduction
77
33
 
78
- ### Using a CDN like jsdelivr
79
- ```html
80
- <script src="https://cdn.jsdelivr.net/npm/@muze-nl/assert@0.3.4/dist/browser.js" crossorigin="anonymous"></script>
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
- Using a CDN like this means that assert is loaded globally as window.assert.
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
- ```javascript
88
- import { assert, Optional, Required, not, validURL } from '@muze-nl/assert'
42
+ Install with npm:
89
43
 
90
- function myFunction(param1, param2) {
91
- assert(param1, Required(validURL))
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
- When calling myFunction above, none of the assertions are actually checked, unless you enable assertion checking first, like this:
48
+ Use the side-effect-free entry point when you want tree-shaking:
98
49
 
99
50
  ```javascript
100
- import * as assert from '@muze-nl/assert'
101
-
102
- assert.enable()
51
+ import { assert, enable, Required, validURL } from '@muze-nl/assert/core'
103
52
  ```
104
53
 
105
- ## Asserting preconditions
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
- let error
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
- The first parameter to `assert.fails` contains the data you want to check. The second (or third, fourth, etc.) contain the assertions. If the data is an object, the assertions can use the same property names to add assertions for those specific properties. Here the `url.searchParams.response_type` must be equal to `code`, or the assertion will fail. You can also use numbers and booleans like this.
60
+ In the browser, using a CDN:
137
61
 
138
- You can also add functions to the assertions. In this case the `assert.optional()` method adds a function that will only fail if the property is set and not `null`, but does not match the assertions passed to `assert.optional()`.
62
+ ```html
63
+ <script src="https://cdn.jsdelivr.net/npm/@muze-nl/assert/dist/assert.min.js" crossorigin="anonymous"></script>
64
+ ```
139
65
 
140
- An assertion may also be a regular expression. If the property value fails to match that expression, the assertion fails. Here the `url.searchParams.state` is tested to make sure that, if it is set, it must not be empty.
66
+ This loads the API as `window.assert`.
141
67
 
142
- In a mock middleware function, it is all well and good to always test your preconditions. But in production many preconditions may be assumed to be valid. These preconditions are not expected to fail in production, only in development. In that case you may use [`assert.check()`]('./docs/check.md'). This function by default does nothing. Only when you enable assertions does this function do anything. This allows you to selectively turn on assertions only in a development context. And avoid doing unnecessary work while in production. This is how it is used in the [oauth2 middleware](./middleware/oauth2.md) (not the mock server, the actual client code):
68
+ ## Documentation
143
69
 
144
- ```javascript
145
- assert.assert(oauth2, {
146
- client_id: /.+/,
147
- authRedirectURL: /.+/,
148
- scope: /.*/
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
- ```javascript
155
- assert.enable()
156
- ```
76
+ ## License
157
77
 
158
- Once the [`assert.enable()`](./docs/enable.md) function is called, now `assert.check()` will throw an error if any assertion fails. The error is also logged to the console.
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 ADDED
@@ -0,0 +1,473 @@
1
+ (() => {
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;
32
+ function enable() {
33
+ assertEnabled = true;
34
+ }
35
+ function disable() {
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;
241
+ }
242
+ function assert(source, test) {
243
+ if (assertEnabled) {
244
+ let problems = fails(source, test);
245
+ if (problems) {
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 }
252
+ });
253
+ }
254
+ }
255
+ }
256
+ function Optional(pattern) {
257
+ return function _Optional(data, root, path) {
258
+ if (typeof data != "undefined" && data != null && typeof pattern != "undefined") {
259
+ return fails(data, pattern, root, path);
260
+ }
261
+ };
262
+ }
263
+ function Required(pattern) {
264
+ return function _Required(data, root, path) {
265
+ if (data == null || typeof data == "undefined") {
266
+ return error("data is required", data, pattern || "any value", path);
267
+ } else if (typeof pattern != "undefined") {
268
+ return fails(data, pattern, root, path);
269
+ } else {
270
+ return false;
271
+ }
272
+ };
273
+ }
274
+ function Recommended(pattern) {
275
+ return function _Recommended(data, root, path) {
276
+ if (data == null || typeof data == "undefined") {
277
+ warn("data does not contain recommended value", data, pattern, path);
278
+ return false;
279
+ } else {
280
+ return fails(data, pattern, root, path);
281
+ }
282
+ };
283
+ }
284
+ function oneOf(...patterns) {
285
+ return function _oneOf(data, root, path) {
286
+ for (let pattern of patterns) {
287
+ if (!fails(data, pattern, root, path)) {
288
+ return false;
289
+ }
290
+ }
291
+ return error("data does not match oneOf patterns", data, patterns, path);
292
+ };
293
+ }
294
+ function anyOf(...patterns) {
295
+ return function _anyOf(data, root, path) {
296
+ if (!Array.isArray(data)) {
297
+ return error("data is not an array", data, "anyOf", path);
298
+ }
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);
303
+ }
304
+ }
305
+ return false;
306
+ };
307
+ }
308
+ function allOf(...patterns) {
309
+ return function _allOf(data, root, path) {
310
+ let problems = [];
311
+ for (let pattern of patterns) {
312
+ problems = problems.concat(fails(data, pattern, root, path));
313
+ }
314
+ problems = problems.filter(Boolean);
315
+ if (problems.length) {
316
+ return error("data does not match all given patterns", data, patterns, path, problems);
317
+ }
318
+ };
319
+ }
320
+ function validURL(data, root, path) {
321
+ try {
322
+ if (data instanceof URL) {
323
+ data = data.href;
324
+ }
325
+ let url = new URL(data);
326
+ if (url.href != data) {
327
+ if (!(url.href + "/" == data || url.href == data + "/")) {
328
+ return error("data is not a valid url", data, "validURL", path);
329
+ }
330
+ }
331
+ } catch (e) {
332
+ return error("data is not a valid url", data, "validURL", path);
333
+ }
334
+ }
335
+ function validEmail(data, root, path) {
336
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data)) {
337
+ return error("data is not a valid email", data, "validEmail", path);
338
+ }
339
+ }
340
+ function instanceOf(constructor) {
341
+ return function _instanceOf(data, root, path) {
342
+ if (!(data instanceof constructor)) {
343
+ return error("data is not an instanceof pattern", data, constructor, path);
344
+ }
345
+ };
346
+ }
347
+ function not(pattern) {
348
+ return function _not(data, root, path) {
349
+ if (!fails(data, pattern, root, path)) {
350
+ return error("data matches pattern, when required not to", data, pattern, path);
351
+ }
352
+ };
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
+ }
361
+ function fails(data, pattern, root, path = "") {
362
+ if (typeof root == "undefined") {
363
+ root = data;
364
+ }
365
+ let problems = [];
366
+ if (pattern === Boolean) {
367
+ if (typeof data != "boolean" && !(data instanceof Boolean)) {
368
+ problems.push(error("data is not a boolean", data, pattern, path));
369
+ }
370
+ } else if (pattern === Number) {
371
+ if (typeof data != "number" && !(data instanceof Number)) {
372
+ problems.push(error("data is not a number", data, pattern, path));
373
+ }
374
+ } else if (pattern === String) {
375
+ if (typeof data != "string" && !(data instanceof String)) {
376
+ problems.push(error("data is not a string", data, pattern, path));
377
+ }
378
+ if (data == "") {
379
+ problems.push(error("data is an empty string, which is not allowed", data, pattern, path));
380
+ }
381
+ } else if (pattern instanceof RegExp) {
382
+ if (Array.isArray(data)) {
383
+ let index = data.findIndex((element, index2) => fails(element, pattern, root, appendPath(path, index2)));
384
+ if (index > -1) {
385
+ problems.push(error("data[" + index + "] does not match pattern", data[index], pattern, appendPath(path, index)));
386
+ }
387
+ } else if (typeof data == "undefined") {
388
+ problems.push(error("data is undefined, should match pattern", data, pattern, path));
389
+ } else if (!pattern.test(data)) {
390
+ problems.push(error("data does not match pattern", data, pattern, path));
391
+ }
392
+ } else if (pattern instanceof Function) {
393
+ let problem = pattern(data, root, path);
394
+ if (problem) {
395
+ if (Array.isArray(problem)) {
396
+ problems = problems.concat(problem);
397
+ } else {
398
+ problems.push(problem);
399
+ }
400
+ }
401
+ } else if (Array.isArray(pattern)) {
402
+ if (!Array.isArray(data)) {
403
+ problems.push(error("data is not an array", data, [], path));
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
+ }
413
+ }
414
+ }
415
+ }
416
+ } else if (pattern && typeof pattern == "object") {
417
+ if (Array.isArray(data)) {
418
+ let index = data.findIndex((element, index2) => fails(element, pattern, root, appendPath(path, index2)));
419
+ if (index > -1) {
420
+ problems.push(error("data[" + index + "] does not match pattern", data[index], pattern, appendPath(path, index)));
421
+ }
422
+ } else if (!data || typeof data != "object") {
423
+ problems.push(error("data is not an object, pattern is", data, pattern, path));
424
+ } else {
425
+ if (data instanceof URLSearchParams) {
426
+ data = Object.fromEntries(data);
427
+ }
428
+ if (pattern instanceof Function) {
429
+ let result = fails(data, pattern, root, path);
430
+ if (result) {
431
+ problems = problems.concat(result);
432
+ }
433
+ } else {
434
+ for (const [patternKey, subpattern] of Object.entries(pattern)) {
435
+ let result = fails(data[patternKey], subpattern, root, appendPath(path, patternKey));
436
+ if (result) {
437
+ problems = problems.concat(result);
438
+ }
439
+ }
440
+ }
441
+ }
442
+ } else {
443
+ if (pattern != data) {
444
+ problems.push(error("data and pattern are not equal", data, pattern, path));
445
+ }
446
+ }
447
+ if (problems.length) {
448
+ return problems;
449
+ }
450
+ return false;
451
+ }
452
+ function error(message, found, expected, path = "", problems) {
453
+ let pathParts = pathToArray(path);
454
+ let result = {
455
+ path,
456
+ pathString: pathToString(pathParts),
457
+ pathParts,
458
+ message,
459
+ found,
460
+ expected
461
+ };
462
+ if (problems) {
463
+ result.problems = problems;
464
+ }
465
+ return result;
466
+ }
467
+ function warn(message, data, pattern, path) {
468
+ console.warn("\u{1F170}\uFE0F Assert: " + path, message, pattern, data);
469
+ }
470
+
471
+ // src/assert.mjs
472
+ globalThis.assert = { ...assert_core_exports };
473
+ })();