@muze-nl/assert 0.3.2 → 0.3.4

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/README.md CHANGED
@@ -1,9 +1,59 @@
1
- # Asserts
1
+ [![GitHub License](https://img.shields.io/github/license/muze-nl/assert)](https://github.com/muze-nl/assert/blob/main/LICENSE)
2
+ [![GitHub package.json version](https://img.shields.io/github/package-json/v/muze-nl/assert)]()
3
+ [![NPM Version](https://img.shields.io/npm/v/assert)](https://www.npmjs.com/package/assert)
4
+ [![npm bundle size](https://img.shields.io/bundlephobia/min/assert)](https://www.npmjs.com/package/assert)
5
+ [![Project stage: Development][project-stage-badge: Development]][project-stage-page]
2
6
 
3
- [![Project stage: Experimental][project-stage-badge: Experimental]][project-stage-page]
7
+ # Assert: javascript optional assertion checking
4
8
 
5
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.
6
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:
14
+
15
+ ```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
+
7
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.
8
58
 
9
59
  ## Installation
@@ -27,16 +77,16 @@ import { assert, enable, disable, Optional, Required, Recommended, oneOf, anyOf,
27
77
 
28
78
  ### Using a CDN like jsdelivr
29
79
  ```html
30
- <script src="https://cdn.jsdelivr.net/npm/@muze-nl/assert@0.1.1/dist/browser.js" integrity="sha384-fqO47gvA1/4UGo0iokMu6ZXdBCkRUbNfXejhrmZrWpJaP+7FPaJqJ03Irhzl1ifk" crossorigin="anonymous"></script>
80
+ <script src="https://cdn.jsdelivr.net/npm/@muze-nl/assert@0.3.4/dist/browser.js" crossorigin="anonymous"></script>
31
81
  ```
32
82
 
33
- _Note_: jsdelivr.com doesn't calculate the integrity hash for you, I've used https://www.srihash.org here.
34
-
35
83
  Using a CDN like this means that assert is loaded globally as window.assert.
36
84
 
37
85
  ## Usage
38
86
 
39
87
  ```javascript
88
+ import { assert, Optional, Required, not, validURL } from '@muze-nl/assert'
89
+
40
90
  function myFunction(param1, param2) {
41
91
  assert(param1, Required(validURL))
42
92
  assert(param2, Optional(not(/foo.*/)))
@@ -47,7 +97,9 @@ function myFunction(param1, param2) {
47
97
  When calling myFunction above, none of the assertions are actually checked, unless you enable assertion checking first, like this:
48
98
 
49
99
  ```javascript
50
- enable()
100
+ import * as assert from '@muze-nl/assert'
101
+
102
+ assert.enable()
51
103
  ```
52
104
 
53
105
  ## Asserting preconditions
@@ -105,6 +157,5 @@ assert.enable()
105
157
 
106
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.
107
159
 
108
-
109
- [project-stage-badge: Experimental]: https://img.shields.io/badge/Project%20Stage-Experimental-yellow.svg
160
+ [project-stage-badge: Development]: https://img.shields.io/badge/Project%20Stage-Development-yellowgreen.svg
110
161
  [project-stage-page]: https://blog.pother.ca/project-stages/
package/dist/browser.js CHANGED
@@ -1 +1 @@
1
- (()=>{var m=Object.defineProperty;var b=(e,o)=>{for(var n in o)m(e,n,{get:o[n],enumerable:!0})};var c={};b(c,{Optional:()=>A,Recommended:()=>g,Required:()=>O,allOf:()=>R,anyOf:()=>v,assert:()=>h,disable:()=>d,enable:()=>x,error:()=>s,fails:()=>l,instanceOf:()=>p,not:()=>_,oneOf:()=>y,validEmail:()=>E,validURL:()=>w});globalThis.assertEnabled=!1;function x(){globalThis.assertEnabled=!0}function d(){globalThis.assertEnabled=!1}function h(e,o){if(globalThis.assertEnabled){let n=l(e,o);if(n)throw new Error("Assertions failed",{cause:{problems:n,source:e}})}}function A(e){return function(n,r,i){if(typeof n<"u"&&n!=null&&typeof e<"u")return l(n,e,r,i)}}function O(e){return function(n,r,i){return n==null||typeof n>"u"?s("data is required",n,e||"any value",i):typeof e<"u"?l(n,e,r,i):!1}}function g(e){return function(n,r,i){return n==null||typeof n>"u"?(console.warn("data does not contain recommended value",n,e,i),!1):l(n,e,r,i)}}function y(...e){return function(n,r,i){for(let f of e)if(!l(n,f,r,i))return!1;return s("data does not match oneOf patterns",n,e,i)}}function v(...e){return function(n,r,i){if(!Array.isArray(n))return s("data is not an array",n,"anyOf",i);for(let f of n)if(y(...e)(f))return s("data does not match anyOf patterns",f,e,i);return!1}}function R(...e){return function(n,r,i){let f=[];for(let t of e)f=f.concat(l(n,t,r,i));if(f=f.filter(Boolean),f.length)return s("data does not match all given patterns",n,e,i,f)}}function w(e,o,n){try{e instanceof URL&&(e=e.href);let r=new URL(e);if(r.href!=e&&!(r.href+"/"==e||r.href==e+"/"))return s("data is not a valid url",e,"validURL",n)}catch{return s("data is not a valid url",e,"validURL",n)}}function E(e,o,n){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))return s("data is not a valid email",e,"validEmail",n)}function p(e){return function(n,r,i){if(!(n instanceof e))return s("data is not an instanceof pattern",n,e,i)}}function _(e){return function(n,r,i){if(!l(n,e,r,i))return s("data matches pattern, when required not to",n,e,i)}}function l(e,o,n,r=""){n||(n=e);let i=[];if(o===Boolean)typeof e!="boolean"&&!(e instanceof Boolean)&&i.push(s("data is not a boolean",e,o,r));else if(o===Number)typeof e!="number"&&!(e instanceof Number)&&i.push(s("data is not a number",e,o,r));else if(o===String)typeof e!="string"&&!(e instanceof String)&&i.push(s("data is not a string",e,o,r)),e==""&&i.push(s("data is an empty string, which is not allowed",e,o,r));else if(o instanceof RegExp)if(Array.isArray(e)){let f=e.findIndex((t,u)=>l(t,o,n,r+"["+u+"]"));f>-1&&i.push(s("data["+f+"] does not match pattern",e[f],o,r+"["+f+"]"))}else typeof e>"u"?i.push(s("data is undefined, should match pattern",e,o,r)):o.test(e)||i.push(s("data does not match pattern",e,o,r));else if(o instanceof Function){let f=o(e,n,r);f&&(Array.isArray(f)?i=i.concat(f):i.push(f))}else if(Array.isArray(o)){Array.isArray(e)||i.push(s("data is not an array",e,[],r));for(let f of o)for(let t of e.keys()){let u=l(e[t],f,n,r+"["+t+"]");Array.isArray(u)?i=i.concat(u):u&&i.push(u)}}else if(o&&typeof o=="object")if(Array.isArray(e)){let f=e.findIndex((t,u)=>l(t,o,n,r+"["+u+"]"));f>-1&&i.push(s("data["+f+"] does not match pattern",e[f],o,r+"["+f+"]"))}else if(!e||typeof e!="object")i.push(s("data is not an object, pattern is",e,o,r));else if(e instanceof URLSearchParams&&(e=Object.fromEntries(e)),o instanceof Function){let f=l(e,o,n,r);f&&(i=i.concat(f))}else for(let[f,t]of Object.entries(o)){let u=l(e[f],t,n,r+"."+f);u&&(i=i.concat(u))}else o!=e&&i.push(s("data and pattern are not equal",e,o,r));return i.length?i:!1}function s(e,o,n,r,i){let f={message:e,found:o,expected:n,path:r};return i&&(f.problems=i),f}globalThis.assert=c;})();
1
+ (()=>{var m=Object.defineProperty;var b=(e,o)=>{for(var n in o)m(e,n,{get:o[n],enumerable:!0})};var c={};b(c,{Optional:()=>A,Recommended:()=>g,Required:()=>O,allOf:()=>R,anyOf:()=>v,assert:()=>h,disable:()=>d,enable:()=>x,error:()=>s,fails:()=>l,instanceOf:()=>_,not:()=>a,oneOf:()=>y,validEmail:()=>E,validURL:()=>w});globalThis.assertEnabled=!1;function x(){globalThis.assertEnabled=!0}function d(){globalThis.assertEnabled=!1}function h(e,o){if(globalThis.assertEnabled){let n=l(e,o);if(n)throw console.error("\u{1F170}\uFE0F Assertions failed because of:",n,"in this source:",e),new Error("Assertions failed",{cause:{problems:n,source:e}})}}function A(e){return function(n,r,i){if(typeof n<"u"&&n!=null&&typeof e<"u")return l(n,e,r,i)}}function O(e){return function(n,r,i){return n==null||typeof n>"u"?s("data is required",n,e||"any value",i):typeof e<"u"?l(n,e,r,i):!1}}function g(e){return function(n,r,i){return n==null||typeof n>"u"?(console.warn("data does not contain recommended value",n,e,i),!1):l(n,e,r,i)}}function y(...e){return function(n,r,i){for(let f of e)if(!l(n,f,r,i))return!1;return s("data does not match oneOf patterns",n,e,i)}}function v(...e){return function(n,r,i){if(!Array.isArray(n))return s("data is not an array",n,"anyOf",i);for(let f of n)if(y(...e)(f))return s("data does not match anyOf patterns",f,e,i);return!1}}function R(...e){return function(n,r,i){let f=[];for(let t of e)f=f.concat(l(n,t,r,i));if(f=f.filter(Boolean),f.length)return s("data does not match all given patterns",n,e,i,f)}}function w(e,o,n){try{e instanceof URL&&(e=e.href);let r=new URL(e);if(r.href!=e&&!(r.href+"/"==e||r.href==e+"/"))return s("data is not a valid url",e,"validURL",n)}catch{return s("data is not a valid url",e,"validURL",n)}}function E(e,o,n){if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))return s("data is not a valid email",e,"validEmail",n)}function _(e){return function(n,r,i){if(!(n instanceof e))return s("data is not an instanceof pattern",n,e,i)}}function a(e){return function(n,r,i){if(!l(n,e,r,i))return s("data matches pattern, when required not to",n,e,i)}}function l(e,o,n,r=""){n||(n=e);let i=[];if(o===Boolean)typeof e!="boolean"&&!(e instanceof Boolean)&&i.push(s("data is not a boolean",e,o,r));else if(o===Number)typeof e!="number"&&!(e instanceof Number)&&i.push(s("data is not a number",e,o,r));else if(o===String)typeof e!="string"&&!(e instanceof String)&&i.push(s("data is not a string",e,o,r)),e==""&&i.push(s("data is an empty string, which is not allowed",e,o,r));else if(o instanceof RegExp)if(Array.isArray(e)){let f=e.findIndex((t,u)=>l(t,o,n,r+"["+u+"]"));f>-1&&i.push(s("data["+f+"] does not match pattern",e[f],o,r+"["+f+"]"))}else typeof e>"u"?i.push(s("data is undefined, should match pattern",e,o,r)):o.test(e)||i.push(s("data does not match pattern",e,o,r));else if(o instanceof Function){let f=o(e,n,r);f&&(Array.isArray(f)?i=i.concat(f):i.push(f))}else if(Array.isArray(o)){Array.isArray(e)||i.push(s("data is not an array",e,[],r));for(let f of o)for(let t of e.keys()){let u=l(e[t],f,n,r+"["+t+"]");Array.isArray(u)?i=i.concat(u):u&&i.push(u)}}else if(o&&typeof o=="object")if(Array.isArray(e)){let f=e.findIndex((t,u)=>l(t,o,n,r+"["+u+"]"));f>-1&&i.push(s("data["+f+"] does not match pattern",e[f],o,r+"["+f+"]"))}else if(!e||typeof e!="object")i.push(s("data is not an object, pattern is",e,o,r));else if(e instanceof URLSearchParams&&(e=Object.fromEntries(e)),o instanceof Function){let f=l(e,o,n,r);f&&(i=i.concat(f))}else for(let[f,t]of Object.entries(o)){let u=l(e[f],t,n,r+"."+f);u&&(i=i.concat(u))}else o!=e&&i.push(s("data and pattern are not equal",e,o,r));return i.length?i:!1}function s(e,o,n,r,i){let f={message:e,found:o,expected:n,path:r};return i&&(f.problems=i),f}globalThis.assert=c;})();
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@muze-nl/assert",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "light optional assert library",
5
5
  "type": "module",
6
6
  "main": "src/assert.mjs",
7
7
  "scripts": {
8
8
  "test": "tap test/*.mjs",
9
- "tap": "tap"
9
+ "tap": "tap",
10
+ "build": "npx esbuild --bundle src/browser.mjs --outdir=dist --minify",
11
+ "build-dev": "npx esbuild --bundle src/browser.mjs --outdir=dist"
10
12
  },
11
13
  "repository": {
12
14
  "type": "git",
package/src/assert.mjs CHANGED
@@ -36,6 +36,7 @@ export function assert(source, test) {
36
36
  if (globalThis.assertEnabled) {
37
37
  let problems = fails(source,test)
38
38
  if (problems) {
39
+ console.error('🅰️ Assertions failed because of:', problems, 'in this source:', source)
39
40
  throw new Error('Assertions failed', {
40
41
  cause: { problems, source }
41
42
  })
@@ -76,7 +77,7 @@ export function Required(pattern) {
76
77
  export function Recommended(pattern) {
77
78
  return function _Recommended(data, root, path) {
78
79
  if (data==null || typeof data == 'undefined') {
79
- console.warn('data does not contain recommended value', data, pattern, path)
80
+ warn('data does not contain recommended value', data, pattern, path)
80
81
  return false
81
82
  } else {
82
83
  return fails(data, pattern, root, path)
@@ -292,13 +293,17 @@ export function fails(data, pattern, root, path='') {
292
293
  */
293
294
  export function error(message, found, expected, path, problems) {
294
295
  let result = {
296
+ path,
295
297
  message,
296
298
  found,
297
- expected,
298
- path
299
+ expected
299
300
  }
300
301
  if (problems) {
301
302
  result.problems = problems
302
303
  }
303
304
  return result
304
305
  }
306
+
307
+ export function warn(message, data, pattern, path) {
308
+ console.warn('🅰️ Assert: '+path, message, pattern, data)
309
+ }