Comparing version 9.6.0 to 9.6.1
{ | ||
"name": "rttc", | ||
"version": "9.6.0", | ||
"version": "9.6.1", | ||
"description": "Runtime type-checking for JavaScript.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
825
README.md
# rttc | ||
Runtime (recursive) type-checking for JavaScript. | ||
## Installation | ||
This package is the official SDK for working with the RTTC type system. It includes a lot of methods that suitable for everyday use, as well as some lower-level methods that are intended for developers building tools which leverage the [machine specification](http://node-machine.org). | ||
```sh | ||
$ npm install rttc --save | ||
``` | ||
## What is RTTC? | ||
Throwing errors in an asynchronous callback can be [dangerous](http://stackoverflow.com/questions/5999373/how-do-i-prevent-node-js-from-crashing-try-catch-doesnt-work), particularly in Node.js. Too often, these types of errors and crashes occur because of a trivial mistake or miscommunication about the data type of a variable; particularly when you're working on a team with other developers. | ||
## Quick Start | ||
RTTC is a lightweight type system that provides a safety net for JavaScript code. It provides flexible, performant type guarantees on an as-needed basis; without messing with your development stack or build tools. Instead, RTTC builds on top of the existing data structures and programming concepts from JavaScript and Node.js to validate and coerce data _at runtime_. This allows you to add as much or as little type-checking as you like, in any new _or_ existing Node.js/Sails.js application. | ||
RTTC semantics are used by: | ||
+ the Node-Machine project's core utility packages, including the [`machine` runner](https://github.com/node-machine/machine) | ||
+ throughout the [Sails.js framework](http://sailsjs.org) | ||
+ every [Waterline driver](https://github.com/node-machine/driver-interface) | ||
+ every [machinepack published on NPM](http://node-machine.org/machinepacks), and | ||
+ the [Treeline](https://treeline.io) standard library | ||
Want to coerce a value to match a particular type? | ||
```javascript | ||
var rttc = require('rttc'); | ||
## Installation | ||
rttc.coerce({ firstName: 'string'}, {firstName: 13375055}); | ||
// => { firstName: "13375055" } | ||
rttc.coerce({ firstName: 'string'}, {something: 'totally incorrect'}); | ||
// => { firstName: "" } | ||
// (when confronted with something totally weird, `.coerce()` returns the "base value" for the type) | ||
```sh | ||
$ npm install rttc --save | ||
``` | ||
Want to throw an Error if a value doesn't match a particular type? | ||
## Basic Usage | ||
```javascript | ||
rttc.validateStrict({ firstName: 'string'}, {firstName: 13375055}); | ||
// throws error | ||
// (`.validateStrict()` demands a value that is precisely the correct type) | ||
rttc.validateStrict({ firstName: 'string'}, {firstName: '13375055'}); | ||
// does not throw | ||
var rttc = require('rttc'); | ||
``` | ||
Or if you want to be a little more forgiving: | ||
The `rttc` package has many different methods for working with fixtures, examples, type schemas, and runtime data in JavaScript. But the most commonly-used RTTC methods are related to validation and coercion of runtime data: | ||
```javascript | ||
rttc.validate({ firstName: 'string'}, {something: 'totally incorrect'}); | ||
// throws error | ||
rttc.validateStrict('number', 999); | ||
// (If the value is valid vs. the specified type schema, then `.validateStrict()` simply returns undefined) | ||
rttc.validate({ firstName: 'string'}, {firstName: 45}); | ||
// => "45" | ||
// (when confronted with minor differences, `.validate()` coerces as needed to make stuff fit) | ||
``` | ||
rttc.validateStrict('number', '999'); | ||
// throws Error | ||
// (but if the provided value is **even slightly off**, then `.validateStrict()` throws). | ||
rttc.validate('number', '999'); | ||
// => 999 | ||
// (if the provided value is close-ish, `.validate()` coerces as needed to make it fit.) | ||
Not sure how to build a type schema for use with `.coerce()` or `.validate()`? Use `.infer()` to build one from an example value you've got laying around: | ||
rttc.validate('number', { x: 32, y: 79 }); | ||
// throws Error | ||
// (but when confronted with **major** differences, `.validate()` throws too.) | ||
```javascript | ||
rttc.infer({ firstName: 'Rosella', lastName: 'Graham', friends: ['Valencia', 'Edgar', 'Attis'] }); | ||
// => { firstName: 'string', lastName: 'string', friends: ['string'] } | ||
rttc.coerce('number', '999'); | ||
// => 999 | ||
// (as long as the provided type schema is valid, `.coerce()` **never** throws) | ||
rttc.coerce('number', { x: 32, y: 79 }); | ||
// => 0 | ||
// (when confronted with **major** differences, `.coerce()` returns the _base value_ for the given type) | ||
``` | ||
This special example value is called an **exemplar**. | ||
Unless otherwise stated, all RTTC methods support recursive (or "deep") traversal of values. | ||
In other words, they iterate over the keys of **dictionaries** (aka plain old JavaScript | ||
objects) and the indices of **arrays**-- and if those dictionary properties and array items | ||
are _themselves_ dictionaries or arrays, then `rttc` recursively dives into them too (and | ||
so on and so forth). | ||
> Note that when inferring the schema for an array in an exemplar, the array item is considered the _pattern_. It is used to indicate the expected type of each item in the array. Consequently, if an array in an exemplar has an item, that means it is homogeneous (i.e. all its items have the same schema). | ||
You can use exemplars to specify just about any type-- here's a more advanced schema to show what I mean: | ||
For example: | ||
```javascript | ||
rttc.infer([{ upstream: '===', fieldName: 'photos', files: [{getFile: '->', fileName: 'whatever', numBytes: 34353, meta: '*' }] }]); | ||
// => | ||
// [ | ||
// { | ||
// upstream: 'ref', | ||
// fieldName: 'string', | ||
// files: [ | ||
// { | ||
// getFile: 'lamda', | ||
// fileName: 'string', | ||
// numBytes: 'number', | ||
// meta: 'json' | ||
// } | ||
// ] | ||
// } | ||
rttc.coerce([ { name: 'string', age: 'number', friends: [ 'string' ] } ], [ | ||
{ name: 'Karl', age: 258 }, | ||
{ name: 'Samantha', age: '937' }, | ||
{ name: 'Lupé', age: 82, friends: ['Henry', 'Mario', undefined] }, | ||
{ name: 'Andres', age: '22' }, | ||
{ age: ['nonsense!'] } | ||
]); | ||
// => [ | ||
// { name: 'Karl', age: 258, friends: [] }, | ||
// { name: 'Samantha', age: 937, friends: [] }, | ||
// { name: 'Lupé', age: 82, friends: ['Henry', 'Mario'] }, | ||
// { name: 'Andres', age: 22, friends: [] }, | ||
// { name: '', age: 0, friends: [] }, | ||
// ] | ||
// | ||
``` | ||
> Note that all of the validation and coercion strategies used in this modules are recursive through the keys of plain old JavaScript objects and the indices of arrays. | ||
#### Next Steps | ||
For a quick rundown of common use cases, as well as some additional examples, check out [the RTTC quick start guide](https://gist.github.com/mikermcneil/8d20ba78b248ac9f5644fcdd0bb96b74). Keep reading for a brief overview of how RTTC works and a tour of each of its data types. Or, if this isn't your first rodeo, feel free to skip ahead to the complete [reference documentation](https://github.com/node-machine/rttc#methods) below. | ||
## Types & terminology | ||
| ||
| ||
> Each type can be validated or coerced against. If coercion fails, the "base value" for the type will be used. | ||
## Types & Exemplars | ||
> Also note that all types below may be expressed recursively within faceted dictionaries and patterned arrays. If those words don't make sense, keep reading, you'll see what I mean. | ||
There are 10 different types recognized by `rttc`, each of which is uniquely expressed by special notation called **RTTC exemplar syntax**. For example, if we were to interpret `'hello world'` as an **exemplar**, we would be able to infer that it represents a string data type. Exemplars make it easier to write out intricate data structures and validation rules, because they allow us to reason about our data types using representative _examples_. Plus, when working with exemplars programatically, you have access to a much richer set of information about any given API. But most importantly: writing data types as examples makes it easier for other _humans_ to read and understand our intentions. | ||
Exemplars can be mixed and matched with varying levels of specificity, up to any imaginable depth, by using the recursive | ||
array and faceted dictionary types. For example, we can infer from the exemplar `['Rover']` that its **type schema** is `['string']`, | ||
indicating that it accepts any array of strings. Similarly, given the exemplar `[{ name: 'Rover' }]`, we can infer the type schema | ||
`[{name: 'string'}]`. This indicates that it accepts any array of dictionaries, so long as each of those dictionaries has a | ||
key called `name` with any string value. | ||
There are 10 different types recognized by `rttc`: | ||
The table below gives each of the RTTC types, the exemplar notation used to describe it, as well as its _base value_: | ||
| type | rttc example notation | base value | | ||
| type | rttc exemplar syntax | base value | | ||
|-------------------------|--------------------------|-------------------------------------| | ||
@@ -102,40 +110,84 @@ | string | `'any string like this'` | `''` | ||
| boolean | `false` _(or `true`)_ | `false` | ||
| lamda | `'->'` | `function () { throw new Error('Not implemented! (this function was automatically created by `rttc`'); };` | ||
| generic dictionary | `{}` | `{}` _(empty dictionary)_ | ||
| generic array | `[]` | `[]` _(empty array)_ | ||
| lamda (aka function) | `'->'` | `function () { throw new Error('Not implemented! (this function was automatically created by `rttc`'); };` | ||
| generic dictionary | `{}` | `{}` _(empty dictionary)_ | ||
| json | `'*'` | `null` | ||
| ref | `'==='` | `null` | ||
| faceted dictionary (recursive) | `{...}` _(i.e. w/ keys)_ | `{...}` (w/ all expected keys and _their_ base values) | ||
| pattern array (recursive) | `[...]` _(i.e. w/ 1 item)_ | `[]` _(empty array)_ | ||
| faceted dictionary _(recursive)_ | `{...}` _(i.e. w/ keys)_ | `{...}` (w/ all expected keys and _their_ base values) | ||
| array _(recursive)_ | `[...]` _(i.e. w/ 1 item)_ | `[]` _(empty array)_ | ||
A type's "base value" is its minimum empty state. When coercing some data vs. an exemplar, if coercion fails | ||
at a particular path within that exemplar, then the "base value" for the type will be used at that path instead. | ||
For example, if you are coercing the value `{name:'Lynda'}` vs. the exemplar `{name: 'Angela', age: 47}`, then | ||
the result would be `{name: 'Lynda', age: 0}` (because the base value for the number type is zero). | ||
### Strings | ||
`example: 'stuff'` | ||
> ##### Compatibility Note | ||
> RTTC also supports an 11th type, sometimes called the "generic array" and represented by the exemplar (`[]`). | ||
> However, if an exemplar is specified as `[]`, it is really just an alias for `['*']`, an array | ||
> exemplar with a `*` (json) pattern. That means it accepts any array of JSON-compatible items. | ||
> | ||
> The `[]` alias is for consistency with the generic dictionary type (`{}`), as well as for backwards compatibility. | ||
> While future versions of RTTC will likely continue to maintain support for the `[]` exemplar, for clarity, you should | ||
> switch to using `['*']` in new code and documentation and migrate `[]` to `['*']` in existing code at your earliest convenience. | ||
## Strings | ||
| Exemplar | RTTC Display Type | Display Type Label | Base Value | | ||
|:-----------------------|:-----------------------|:------------------------------|:-------------| | ||
| `'foo'` _(any string)_ | `'string'` | `'String'` | `''` | | ||
The **string** type accepts any string. | ||
### Numbers | ||
`example: 323` | ||
## Numbers | ||
| Exemplar | RTTC Display Type | Display Type Label | Base Value | | ||
|:-----------------------|:-----------------------|:------------------------------|:-------------| | ||
| `32` _(any number)_ | `'number'` | `'Number'` | `0` | | ||
The **number** type accepts numbers like `0`, `-4`, or `235.3`. Anathemas like `Infinity`, `-Infinity`, `NaN`, and `-0` are all coerced to zero. | ||
### Booleans | ||
## Booleans | ||
`example: false` | ||
| Exemplar | RTTC Display Type | Display Type Label | Base Value | | ||
|:-----------------------|:-----------------------|:------------------------------|:-------------| | ||
| `true` _(or `false`)_ | `'boolean'` | `'Boolean'` | `false` | | ||
The **boolean** type accepts `true` or `false`. | ||
### Lamdas | ||
`example: ->` | ||
## Functions (aka "lamdas") | ||
| Exemplar | RTTC Display Type | Display Type Label | Base Value | | ||
|:-----------------------|:-----------------------|:------------------------------|:---------------| | ||
| `'->'` | `'lamda'` | `'Function'` | _(see below)_ | | ||
The **lamda** type accepts any function. | ||
### Generic dictionaries | ||
`example: {}` | ||
#### Base Value | ||
The base value for the lamda type is the following automatically-generated function: | ||
```js | ||
function () { | ||
throw new Error('Not implemented! (this function was automatically created by `rttc`'); | ||
}; | ||
``` | ||
## Generic dictionaries | ||
| Exemplar | RTTC Display Type | Display Type Label | Base Value | | ||
|:-----------------------|:-----------------------|:------------------------------|:----------------------------| | ||
| `{}` | `'dictionary'` | `'Dictionary'` | `{}` _(empty dictionary)_ | | ||
The **generic dictionary** type accepts any JSON-serializable dictionary. | ||
@@ -154,6 +206,38 @@ | ||
### Faceted dictionaries | ||
`example: {...}` | ||
#### What about keys with `undefined` values? | ||
When validating or coercing a value vs. a generic dictionary exemplar or type schema, keys with `undefined` values _will always be stripped out_. For example, coercing `{ name: 'Rob', age: undefined, weight: undefined }` vs. the type schema `{}` would result in `{ name: 'Rob' }`. This ensures consistency with the behavior of the native JSON.stringify() and JSON.parse() methods in browser-side JavaScript and Node.js. | ||
> Note that `undefined` array items are stripped out _even if you are using `['===']`_. | ||
## JSON-Compatible Values | ||
| Exemplar | Rttc Display Type | Display Type Label | Base Value | | ||
|:------------------|:----------------------|:------------------------------|:--------------| | ||
| `'*'` | `'json'` | `'JSON-Compatible Value'` | `null` | | ||
This works pretty much like the generic dictionary type, with one major difference: the top-level value can be a string, boolean, number, dictionary, array, or `null` value. When faced with a dictionary or array that contains nested values, the generic JSON type follows the same JSON-serializability as the generic dictionary type (see above). | ||
## Mutable references (aka "ref") | ||
| Exemplar | RTTC Display Type | Display Type Label | Base Value | | ||
|:-----------------------|:-----------------------|:------------------------------|:--------------| | ||
| `'==='` | `'ref'` | `'Anything'` | `null` | | ||
This special type allows anything except `undefined` at the top level (undefined is permitted at any other level). It also _does not rebuild objects_, which means it maintains the original reference (i.e. is `===`). It does not guarantee JSON-serializability. | ||
## Faceted dictionaries | ||
| Exemplar | RTTC Display Type | Display Type Label | Base Value | | ||
|:-----------------------|:-----------------------|:------------------------------|:----------------------| | ||
| `{...}` _(recursive)_ | `'dictionary'` | `'Dictionary'` | `{...}` _(see below)_ | | ||
The **faceted dictionary** type is any dictionary type schema with at least one key. Extra keys in the actual value that are not in the type schema will be stripped out. Missing keys will cause `.validate()` to throw. | ||
@@ -170,6 +254,14 @@ | ||
id: 'number', | ||
name: 'string', | ||
spouse: 'json', | ||
occupation: { | ||
title: 'string', | ||
workplace: 'string' | ||
workplace: 'json', | ||
hobbies: {}, | ||
incomingUploads: [ | ||
{ | ||
fd: 'string', | ||
startBuffering: 'lamda', | ||
rawStream: 'ref' | ||
} | ||
] | ||
} | ||
@@ -180,38 +272,102 @@ } | ||
#### Base Value | ||
The base value for the faceted dictionary type is a dictionary which consists of a key for every expected facet, each with _its own_ base value (recursively deep). | ||
### Generic arrays | ||
For example, for the type schema described above, the base value is: | ||
`example: []` | ||
```javascript | ||
{ | ||
id: 0, | ||
name: '', | ||
isAdmin: false, | ||
mom: { | ||
id: 0, | ||
spouse: null, | ||
hobbies: {}, | ||
occupation: { | ||
title: '', | ||
workplace: null, | ||
incomingUploads: [] | ||
} | ||
} | ||
} | ||
``` | ||
Arrays that have been validated/coerced against the generic array type: | ||
+ _may_ be heterogeneous (have items with different types) - but it is generally best practice to avoid heterogeneous arrays in general. | ||
+ are guaranteed to be JSON-serializable, with a few additional affordances: | ||
+ normally, `Error` instances get stringified into empty objects. Instead, rttc turns them into human-readable strings by reducing them to their `.stack` property (this includes the error message and the stack trace w/ line numbers) | ||
+ normally, `RegExp` instances get stringified into empty objects. Instead, rttc turns them into human-readable strings like `'/some regexp/gi'` | ||
+ normally, `function()` instances get stringified into empty objects. Instead, rttc turns them into human-readable strings like `'function doStuff (a,b) { console.log(\'wow I can actually read this!\'); }'` | ||
+ keys of nested dictionaries with undefined values will be stripped out | ||
+ undefined array items at any level will be stripped out | ||
+ keys of nested dictionaries with null values may be present | ||
+ null items in arrays at any level may be present | ||
> Note: Generic array exemplar syntax is really just another way to write `['*']`. The special empty array syntax will continue to be supported for backwards compatibility, but it may eventually be removed from documentation and tests to avoid potential confusion. | ||
#### What about keys with `undefined` values? | ||
### Patterned arrays | ||
When validating or coercing a value vs. a faceted dictionary exemplar or type schema, if a required key exists, but has an `undefined` value, it is _considered the same thing as if the key did not exist at all_. This is a deliberate decision designed to normalize the use of `null` vs. `undefined` in your application, and to avoid the pitfalls of `==` vs. `===` equality comparisons and `hasOwnProperty` checks. This approach prevents countless bugs and makes it much easier to hunt down the sources of problems when they occur. | ||
`example: ['Margaret']` | ||
`example: [123]` | ||
`example: [true]` | ||
`example: [[...]]` | ||
`example: [{...}]` | ||
Array type schemas may be infinitely nested and combined with dictionaries or any other types. | ||
##### Can I have optional facets? | ||
The best way to allow dictionaries which _may or may not_ include certain keys is to always provide those keys using the appropriate base values. For example, let's say a user in your app or script _may or may not_ have `msOutlookEmail`, `contactInfo`, or `misc` keys. Regardless, you could still use the same faceted dictionary exemplar: | ||
Runtime arrays being validated/coerced against array type schemas will be homogeneous (meaning every item in the array will have the same type). | ||
```js | ||
var USER_SCHEMA = { | ||
id: 38, | ||
name: 'Margaret Thatcher', | ||
email: 'margaret@gmail.com', | ||
msOutlookEmail: 'marge@outlook.com', | ||
contactInfo: {}, | ||
misc: '*' | ||
} | ||
``` | ||
Undefined items will always be stripped out of arrays. | ||
Then whenever you build a dictionary that you want to validate at runtime, just coerce it first: | ||
```js | ||
var alfred = rttc.coerce(rttc.infer(USER_SCHEMA), { | ||
id: 100, | ||
name: 'Alfred Roberts', | ||
email: 'alfred@gmail.com', | ||
contactInfo: { | ||
phone: '+3 9284829424' | ||
} | ||
}); | ||
``` | ||
> Also note that, because of this, when providing a type schema or type-inference-able example for an array, you only need to provide one item in the array, e.g.: | ||
This will ensure that all of the facets exist, even if it's just as base values; e.g.: | ||
```js | ||
console.log(alfred); | ||
// - - - - - - - - - - - - - | ||
{ | ||
id: 100, | ||
name: 'Alfred Roberts', | ||
email: 'alfred@gmail.com', | ||
msOutlookEmail: '', | ||
contactInfo: { | ||
phone: '+3 9284829424' | ||
}, | ||
misc: null | ||
} | ||
``` | ||
#### Can I have facets that could be vastly different types? | ||
The best way to implement [union facets](https://en.wikipedia.org/wiki/Union_type), or facets that could be more than one type, is to use a more generic type (such as `{}`, `'*'`, or `'==='`), and then add additional specificity through custom code. | ||
#### What if I don't need to validate every key recursively deep? | ||
Even if you don't need to validate every key recursively deep, to use the faceted dictionary exemplar, you still need to declare every facet you plan to use. Alternately, to indicate _any_ dictionary of JSON-compatible values, just use the generic dictionary (`{}`) type / exemplar instead. Then, implement any additional validation or coercion logic you need on top of that by writing it into your code. | ||
> This is another way you can go about validating dictionaries with keys that may or may not exist, and keys that could be multiple different types. Just be careful: this approach has the problem of introducing human error into the equation. If possible, the best, safest, and most foolproof approach is to use a faceted dictionary. This ensures your runtime dictionaries always include the key/value pairs you expect them to, and it reduces the number of times you have to squint at the computer screen and read `Cannot read property "foo" of undefined`. | ||
## Arrays | ||
| Exemplar | RTTC Display Type | Display Type Label | Base Value | | ||
|:-----------------------|:-----------------------|:------------------------------|:-----------------------| | ||
| `[...]` _(recursive)_ | `'array'` | `'Array'` | `[]` _(empty array)_ | | ||
The **array** type accepts any array, so long as all of that array's items are also valid (recursively deep). Every array exemplar and type schema must declare a **pattern**: a nested exemplar or type schema which indicates the expected type of array items. This pattern is how the array type is able to validate nested values. When validating vs. an array type schema, RTTC first checks that the corresponding value is an array (a la [`_.isArray()`](http://lodash.org)), then also recursively checks each of its items vs. the expected pattern. For example, given the exemplar `['Margaret']`, we can infer that the type schema is `['string']`, and therefore that it would accept any array of strings. So when designing an array exemplar or type schema, make sure the array has _exactly one item_ to serve as the pattern, which is itself another exemplar or type schema. This pattern will be used for validating/coercing array items. | ||
An array type schema or exemplar may be _infinitely nested_ simply by using another array or a faceted dictionary as its pattern. For example: | ||
```javascript | ||
@@ -236,17 +392,22 @@ [ | ||
### Generic JSON | ||
#### What if I don't need to validate array items recursively deep? | ||
`example: '*'` | ||
Even if you don't need to validate array items, you still need a pattern. But luckily, there's another RTTC type (`'==='`) that you can easily use to accept _any value_. To indicate an array of _anything_, just use the mutable reference type as your pattern: | ||
This works pretty much like the generic array or generic dictionary type, with one major difference: the top-level value can be a string, boolean, number, dictionary, array, or null value. | ||
```js | ||
// Type schema that indicates an array of anything, where array items are passed by reference: | ||
['ref'] | ||
Other than the aforementioned exception for `null`, the generic JSON type follows the JSON-serializability rules from generic arrays and generic dictionaries. | ||
// Exemplar that indicates an array of anything, where array items are passed by reference: | ||
['==='] | ||
``` | ||
#### What about `undefined`? | ||
When validating or coercing a value vs. an array exemplar or type schema, `undefined` items in the array _will always be stripped out_. For example, coercing `['Jerry', undefined, undefined, 'Robin']` vs. the type schema `['string']` would result in `['Jerry', 'Robbin']`. This ensures consistency with the behavior of the native JSON.stringify() and JSON.parse() methods in browser-side JavaScript and Node.js. | ||
### Mutable reference ("ref") | ||
> Note that `undefined` array items are stripped out _even if you are using `['===']`_. | ||
`example: '==='` | ||
| ||
This special type allows anything except `undefined` at the top level (undefined is permitted at any other level). It also _does not rebuild objects_, which means it maintains the original reference (i.e. is `===`). It does not guarantee JSON-serializability. | ||
@@ -275,3 +436,3 @@ | ||
When coerced against the generic dictionary, generic array, or the generic json types, the following is true: | ||
When coerced against the generic dictionary or generic json types, the following is true: | ||
+ `Error` instances are coerced to the string value of their `.stack` property (i.e. the message + stack trace you're used to seeing in the terminal) | ||
@@ -285,3 +446,3 @@ + `Date` instances are coerced to the string value of running their `.toJSON()` method (a ISO-8601 timestamp, e.g. `'2015-05-24T15:16:48.999Z'`. This reflects the Date in GMT/UTC time, so is therefore timezone-agnostic). | ||
+ `Stream` and `Buffer` instances (from Node.js) are only valid against the mutable reference type. | ||
+ Streams and Buffers are coerced to `null` against the generic dictionary, generic array, or the generic json types. | ||
+ Streams and Buffers are coerced to `null` against the generic dictionary or the generic json types. | ||
@@ -299,3 +460,3 @@ | ||
+ For the generic dictionary type (`{}`) or a faceted dictionary type (e.g. `{foo:'bar'}`), the base value is `{}`. | ||
+ For the generic array type (`[]`), or a faceted/homogenous array type (e.g. `[3]` or `[{age:48,name: 'Nico'}]`), the base value is `[]` | ||
+ For the array type (e.g. `[3]` or `[{age:48,name: 'Nico'}]`), the base value is `[]` (an empty array) | ||
+ For the "json" type (`'*'`), base value is `null`. | ||
@@ -312,5 +473,4 @@ + For the "ref" type (`'==='`), base value is `null`. | ||
This package exposes a number of different utility methods. If you're interested in using any of these directly, we highly recommend you consider looking at [machinepack-rttc](http://node-machine.org/machinepack-rttc), which provides a higher-level abstraction with better documentation. | ||
This package exposes a number of different methods, some of which are much more likely to be relevant than others for your everyday development needs. The methods in this reference documentation are listed roughly in descending order of familiarity, starting with the most commonly-used and ending with the more bizarre. | ||
> The low-level reference below assumes you are willing/able to dig into the source code of this module for more information. So continue at your own risk! | ||
@@ -329,3 +489,11 @@ ### Validation | ||
##### .isEqual(firstValue, secondValue, [_expectedTypeSchema_=`undefined`]) | ||
Determine whether two values are equivalent using `_.isEqual()`. | ||
This is the method used by `rttc`'s own tests to validate that expected values and actual values match. | ||
If the third argument was provided, also look for expected `lamda` values in the optional type schema and call `toString()` on functions before comparing them. | ||
### Munging | ||
@@ -336,10 +504,38 @@ | ||
ALWAYS returns an acceptable version of the value, even if it has to mangle it to get there (i.e. by using the "base value" for the expected type. More on that below.) | ||
ALWAYS returns an acceptable version of the value, even if it has to mangle it to get there (i.e. by using the "base value" for the expected type schema). | ||
##### .hydrate(value, [_typeSchema_=`undefined`]) | ||
This function will use the provided `typeSchema` to figure out where "lamda" values (functions) are expected, then will use `eval()` to bring them back to life. Use with care. | ||
##### .rebuild(value, handlePrimitive, [handleComposite]) | ||
Recursively rebuild (non-destructively) the specified `value` using the provided transformer function (`handlePrimitive`) | ||
to potentially modify each primitive (`null`, string, number, boolean, or function) therein. Values like JavaScript Dates, | ||
Errors, streams, etc. are coerced to strings before being passed in to `handlePrimitive`. | ||
The `handlePrimitive` transformer function is not run for dictionaries or arrays, since they're recursed into automatically | ||
by default-- unless the `handleComposite` transformer is provided. If provided, the `handleComposite` transformer function | ||
is called once for each array and once for each dictionary in `value`. It is expected to return a modified dictionary or array | ||
that will then continue to be recursively iterated into by `rebuild()`. | ||
In any case, arrays and dictionaries end up as normal array and dictionary literals in the rebuilt value, meaning that any | ||
JavaScript-language-specific metadata such as getters/setters/non-enumerable properties like prototypal methods and constructor | ||
information are all stripped out. `.rebuild()` also protects against endless recursion due to circular references, whether or | ||
not the `handleComposite` transformer function is being used (since even if it is provided, JSON-serializability is ensured _before_ | ||
it is called). | ||
Both transformer functions should be written expecting the particular primitive, dictionary or array value as their first argument | ||
and an RTTC display type string as the second argument. For `handlePrimitive`, that second argument is either 'string', 'number', | ||
'boolean', 'lamda', or 'null'. For `handleComposite`, it is either 'dictionary' or 'array'. | ||
> If you need further technical specifics, see the implementation of `rebuild()` in `lib/rebuild.js` in this repo. | ||
Example usage: | ||
```javascript | ||
return res.json(rttc.rebuild(someData, function handlePrimitive(val, type){ | ||
if (type === 'string') { return val + ' (a grass-type Pokemon)'; } | ||
else { return val; } | ||
})); | ||
``` | ||
##### .dehydrate(value, [_allowNull_=`false`], [_dontStringifyFunctions_=`false`]) | ||
@@ -356,83 +552,164 @@ | ||
##### .parse(stringifiedValue, [_typeSchema_=`undefined`], [_unsafeMode_=`false`]) | ||
##### .hydrate(value, [_typeSchema_=`undefined`]) | ||
Parse a stringified value back into a usable value. | ||
This function will use the provided `typeSchema` to figure out where "lamda" values (functions) are expected, then will use `eval()` to bring them back to life. Use with care. | ||
This is basically just a variation on JSON.parse that calls `rttc.hydrate()` first if `unsafeMode` is enabled. | ||
##### .stringify(value, [_allowNull_=`false`]) | ||
##### .parseHuman(stringFromHuman, [_typeSchema_=`undefined`], [_unsafeMode_=`false`]) | ||
Encode a value into a string. | ||
Parse a human-readable string (typically entered by a human into some kind of UI or CLI application) and return a best guess at the JavaScript value it represents. | ||
This is basically just a variation on JSON.stringify that calls `rttc.dehydrate()` first. | ||
+ If provided, `typeSchema` will be used to make a more educated guess. If you are calling `parseHuman()` in order to parse a string that was generated using `stringifyHuman()`, then be sure to use the same type schema. | ||
+ If the `unsafeMode` flag is enabled, lamda functions will be hydrated. | ||
##### .parseHuman(stringFromHuman, [_typeSchema_=`undefined`], [_unsafeMode_=`false`]) | ||
For example: | ||
Parse a string from a human into something usable. If provided, `typeSchema` will be used to make a better guess. If `unsafeMode` is enabled, lamda functions will be hydrated. | ||
```js | ||
var result; | ||
##### .stringifyHuman(value, typeSchema) | ||
result = rttc.parseHuman('hi'); | ||
// result === 'hi' | ||
The inverse of `.parseHuman()`, this function encodes a string that, if run through `.parseHuman()` would result in the given value. | ||
result = rttc.parseHuman('"hi"', 'string'); | ||
// result === '"hi"' | ||
result = rttc.parseHuman('"hi"', 'json'); | ||
// result === 'hi' | ||
// typeof result === 'string' | ||
### Assertions | ||
##### .isEqual(firstValue, secondValue, [_expectedTypeSchema_=`undefined`]) | ||
Determine whether two values are equivalent using `_.isEqual()`. | ||
result = rttc.parseHuman('3'); | ||
// result === '3' | ||
// typeof result === 'string' | ||
This is the method used by `rttc`'s own tests to validate that expected values and actual values match. | ||
result = rttc.parseHuman('3', 'number'); | ||
// result === 3 | ||
// typeof result === 'number' | ||
result = rttc.parseHuman('3', 'json'); | ||
// result === 3 | ||
// typeof result === 'number' | ||
> Experimental: | ||
> If the third argument was provided, also look for expected `lamda` values in the optional type schema and call `toString()` on functions before comparing them. The third argument SHOULD NOT BE RELIED UPON. | ||
// JSON is not parsed by default: | ||
result = rttc.parseHuman('{"foo":"100"}') | ||
// result === '{"foo":"100"}' | ||
// typeof result === 'string' | ||
// But it will be, if given the proper type schema: | ||
// e.g. either one of these: | ||
result = rttc.parseHuman('{"foo":"100"}', 'json') | ||
result = rttc.parseHuman('{"foo":"100"}', {}) | ||
// assert.deepEqual(result, { foo: '100' }) | ||
// typeof result === 'object' | ||
// typeof result.foo === 'string' | ||
##### .infer(exampleValue) | ||
// But: | ||
result = rttc.parseHuman('{"foo":"100"}', { foo: 'number' }) | ||
// assert.deepEqual(result, { foo: 100 }) | ||
// typeof result === 'object' | ||
// typeof result.foo === 'number' | ||
``` | ||
Guess the type schema from an example value. | ||
##### .isSpecific(typeSchemaOrExemplar, [recursive=false], [isExemplar=false]) | ||
Determine whether the given type schema is "specific". String, number, boolean, lamda, faceted dictionary, or patterned array types are "specific". Everything else is "generic". | ||
Another example, this time using a more complex type schema: | ||
If the second argument (`recursive`) is set to `true`, then also recursively check the subkeys of faceted dictionaries and patterns of arrays in the type schema. | ||
```js | ||
var result = rttc.parseHuman('{"name":"Mr. Tumnus","friends":[{"name":"Broderick","age":13},{"name":"Ashley","age":8000}]}',{ | ||
name: 'string', | ||
friends: [ | ||
{ | ||
name: 'string', | ||
age: 'number' | ||
} | ||
] | ||
}); | ||
If the third argument (`isExemplar`) is set to `true`, then treat the provided schema as an rttc example rather than a type schema. | ||
// Results in the following dictionary: | ||
// | ||
// { name: 'Mr. Tumnus', | ||
// friends: | ||
// [ { name: 'Broderick', age: 13 }, | ||
// { name: 'Ashley', age: 8000 } ] } | ||
``` | ||
For reference | ||
| type | is specific? | | ||
|-------------------------|---------------------| | ||
| string | yes _(always)_ | | ||
| number | yes _(always)_ | | ||
| boolean | yes _(always)_ | | ||
| lamda | yes _(always)_ | | ||
| `{}` (generic) | no | | ||
| `[]` (generic) | no | | ||
| `{...}` (faceted) | yes _(maybe recursively)_ | | ||
| `[...]` (patterned) | yes _(maybe recursively)_ | | ||
| json | no | | ||
| ref | no | | ||
### Utilities | ||
##### .stringifyHuman(value, typeSchema) | ||
##### .sample(typeSchema, [n=2]) | ||
Convert a JavaScript value into a string that can be parsed by `stringifyHuman()`. Specifically, this method is an inverse operation of `.parseHuman()`; that is, if you take the stringified result from this function and pass that in to `.parseHuman()` using the same type schema, you'll end up back where you started: with the original JavaScript value you passed in to `rttc.stringifyHuman()`. | ||
Given a type schema, return an array of up to `n` unique sample values that would validate against it (in random order). `n` defaults to 2 if left undefined. | ||
> This losslessness is guaranteed by two factors: that `stringifyHuman()` (1) enforces _strict_ RTTC validation | ||
> rules (i.e. `rttc.validateStrict(typeSchema, value)`) and (2) the fact that it rejects values which cannot be safely | ||
> stringified in a reversible way (e.g. JavaScript Dates, Errors, streams, prototypal objects, dictionaries and arrays | ||
> with circular references, etc.). If either of these checks fails, `stringifyHuman()` throws an error. | ||
> | ||
> So even though `parseHuman()` is quite forgiving (it uses `rttc.coerce()`), you can rest assured that _any_ string you | ||
> generate using `stringifyHuman()` will be properly deserialized by `parseHuman()`, provided it is passed in with the same type schema. | ||
##### .getDisplayType(value) | ||
For example: | ||
```js | ||
var result; | ||
Given a value, return its type as a human-readable string (this is not limited to rttc types-- it can return strings like `"Error"` and `"Date"`). | ||
If special rttc exemplar syntax is used, it is respected. | ||
// Basic usage: | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
result = rttc.stringifyHuman(100, 'number'); | ||
// result === 100 | ||
// typeof result === 'number' | ||
// The method performs strict validation, so the value must be compatible with the provided type schema: | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
result = rttc.stringifyHuman('100', 'number'); | ||
// Error: rttc.stringifyHuman() failed: the provided value does not match the expected type schema. | ||
// Details: | ||
// Error: 1 error validating value: | ||
// • Specified value (a string: '100') doesn't match the expected type: 'number' | ||
// at consolidateErrors (/Users/mikermcneil/code/rttc/lib/helpers/consolidate-errors.js:45:13) | ||
// ... | ||
// And even if you specify the `ref` type, non-serializable things are not allowed: | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
result = rttc.stringifyHuman(new Date(), 'ref'); | ||
// Error: rttc.stringifyHuman() failed: the provided value cannot be safely stringified in a reversible way. | ||
// at Object.stringifyHuman (/Users/mikermcneil/code/rttc/lib/stringify-human.js:49:11) | ||
// at repl:1:15 | ||
// ... | ||
// One more normal-case usage scenario, this time using the same more complex value and type schema | ||
// from the `parseHuman()` example above: | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
var result = rttc.stringifyHuman({ | ||
name: 'Mr. Tumnus', | ||
friends: [ | ||
{ name: 'Broderick', age: 13 }, | ||
{ name: 'Ashley', age: 8000 } | ||
] | ||
}, | ||
{ | ||
name: 'string', | ||
friends: [ | ||
{ | ||
name: 'string', | ||
age: 'number' | ||
} | ||
] | ||
}); | ||
// result === '{"name":"Mr. Tumnus","friends":[{"name":"Broderick","age":13},{"name":"Ashley","age":8000}]}' | ||
// typeof result === 'string' | ||
``` | ||
##### .compile(value) | ||
@@ -476,47 +753,120 @@ | ||
##### .union(schema0, schema1, [isExemplar=false], [isStrict=false]) | ||
Given two rttc schemas (e.g. `A` and `B`), return the most specific schema that would accept the superset of what both schemas accept normally (`A ∪ B`). | ||
##### .parse(stringifiedValue, [_typeSchema_=`undefined`], [_unsafeMode_=`false`]) | ||
+ _schema0_ - the first schema | ||
+ _schema1_ - the second schema (order doesn't matter) | ||
+ _isExemplar_ - if set, the schemas will be treated as exemplars (rather than type schemas) | ||
+ _isStrict_ - if set, the schemas will be unioned using strict validation rules (i.e. like `validateStrict()`) | ||
Parse a stringified value back into a usable value. | ||
This is basically just a variation on JSON.parse that calls `rttc.hydrate()` first if `unsafeMode` is enabled. | ||
##### .intersection(schema0, schema1, [isExemplar=false], [isStrict=false]) | ||
Given two rttc schemas, return the most specific schema that accepts the shared subset of values accepted by both. Formally, this subset is the intersection of A and B (A ∩ B), where A is the set of values accepted by `schema0` and B is the set of values accepted by `schema1`. If `A ∩ B` is the empty set, then this function will return `null`. Otherwise it will return the schema that precisely accepts `A ∩ B`. | ||
##### .stringify(value, [_allowNull_=`false`]) | ||
+ _schema0_ - the first schema | ||
+ _schema1_ - the second schema (order doesn't matter) | ||
+ _isExemplar_ - if set, the schemas will be treated as exemplars (rather than type schemas) | ||
+ _isStrict_ - if set, the schemas will be intersected using strict validation rules (i.e. like `validateStrict()`) | ||
Encode a value into a string. | ||
This is basically just a variation on JSON.stringify that calls `rttc.dehydrate()` first. | ||
### Experimental | ||
The following functions are newly implemented, experimental, and tend to be a bit more advanced. They may undergo frequent changes over the coming months, so use with care. You have been warned! | ||
##### .rebuild(val, transformLeaf) | ||
### Meta | ||
Recursively rebuild (non-destructively) the specified value using the provided transformer function to change each primitive or function therein. Transformer function is provided the value as the first argument, and an rttc display type as the second (either 'string', 'number', 'boolean', 'lamda', or 'null'). The transformer function is not run for dictionaries or arrays, since they're recursed into automatically (in the rebuilt value, they will be normal dictionary and array literals, so any stuff about getters/setters/non-enumerable properties like prototypal methods and constructor information is all stripped out. `.rebuild()` also protects against endless recursion due to circular references. | ||
> Methods for working w/ exemplars, type schemas, and display types | ||
Example usage: | ||
##### .infer(exemplar) | ||
Infer the type schema from the given exemplar. | ||
```javascript | ||
return res.json(rttc.rebuild(someData, function transformLeaf(val, type){ | ||
if (type === 'string') { return val + ' (a grass-type Pokemon)'; } | ||
else { return val; } | ||
})); | ||
rttc.infer([ | ||
{ | ||
name: 'Rachael', | ||
age: 27, | ||
filesBeingUploaded: ['==='], | ||
friends: [ | ||
{ | ||
name: 'Mr. Bailey', | ||
species: 'cat', | ||
getClawSharpness: '->' | ||
} | ||
] | ||
} | ||
]); | ||
/* => | ||
[ | ||
{ | ||
name: 'string' | ||
age: 'number', | ||
filesBeingUploaded: [ | ||
'ref' | ||
], | ||
friends: [ | ||
{ | ||
name: 'string', | ||
species: 'string', | ||
getClawSharpness: 'lamda' | ||
} | ||
] | ||
} | ||
] | ||
*/ | ||
``` | ||
##### .getDefaultExemplar(typeSchema) | ||
Given a type schema, return a random exemplar which accepts precisely the same set of values. | ||
##### .inferDisplayType(exemplar) | ||
Compute the **display type** (aka "typeclass") for an RTTC exemplar. | ||
Always returns one of the standard RTTC display types: | ||
+ string | ||
+ number | ||
+ boolean | ||
+ lamda | ||
+ dictionary | ||
+ array | ||
+ json | ||
+ ref | ||
Or `''` (empty string) if the exemplar is unrecognized or invalid; e.g. `null`. | ||
```javascript | ||
rttc.inferDisplayType({foo: 'bar'}); | ||
// => 'dictionary' | ||
rttc.inferDisplayType('->'); | ||
// => 'lamda' | ||
``` | ||
##### .getDisplayTypeLabel(displayType) | ||
Get the appropriate human-readable label for a given RTTC "display type" (aka "typeclass") string. | ||
Useful for error messages, user interfaces, etc. | ||
```js | ||
rttc.getDisplayTypeLabel('ref'); | ||
// => 'Anything' | ||
rttc.getDisplayTypeLabel('string'); | ||
// => 'String' | ||
rttc.getDisplayTypeLabel('dictionary'); | ||
// => 'Dictionary' | ||
``` | ||
##### .coerceExemplar(value, [allowSpecialSyntax=false]) | ||
Convert a normal value into an exemplar representative of the _most specific_ type schema which would accept it. In most cases, this leaves the value untouched-- however it does take care of a few special cases: | ||
Build a reasonable-looking exemplar from a normal value-- specifically, the _most specific_ exemplar which would accept that value. | ||
> Note: This is particularly useful for inferring RTTC exemplar schemas from fixture data. | ||
In most cases, this leaves the value untouched-- however it does take care of a few special cases: | ||
+ Empty dictionaries become generic dictionaries (`{}`). The most specific exemplar which can accept an empty dictionary is the generic dictionary. | ||
@@ -551,18 +901,14 @@ + Empty arrays become generic arrays (`[]`). Since we don't know the contents, we have to assume this array could be heterogeneous (i.e. have items with different types). | ||
##### .isInvalidExample(exemplar) | ||
Return truthy if the provided value is NOT a valid rttc exemplar (e.g. `null`). | ||
<!-- | ||
##### .getDisplayType(value) | ||
##### .getDisplayTypeLabel(type) | ||
Given a value, return its type as a human-readable string (this is not limited to rttc types-- it can return strings like `"Error"` and `"Date"`). | ||
If special rttc exemplar syntax is used, it is respected. | ||
Given an RTTC "display type" aka "typeclass" string, return the appropriate human-readable label for that type. Useful for error messages, user interfaces, etc. | ||
> May be deprecated in an upcoming release. | ||
```js | ||
rttc.getDisplayTypeLabel('ref'); | ||
// => 'Anything' | ||
--> | ||
rttc.getDisplayTypeLabel('string'); | ||
// => 'String' | ||
``` | ||
@@ -602,2 +948,79 @@ ##### .getPathInfo(exemplar, path) | ||
##### .union(schema0, schema1, [isExemplar=false], [isStrict=false]) | ||
Given two rttc schemas (e.g. `A` and `B`), return the most specific schema that would accept the superset of what both schemas accept normally (`A ∪ B`). | ||
+ _schema0_ - the first schema | ||
+ _schema1_ - the second schema (order doesn't matter) | ||
+ _isExemplar_ - if set, the schemas will be treated as exemplars (rather than type schemas) | ||
+ _isStrict_ - if set, the schemas will be unioned using strict validation rules (i.e. like `validateStrict()`) | ||
##### .intersection(schema0, schema1, [isExemplar=false], [isStrict=false]) | ||
Given two rttc schemas, return the most specific schema that accepts the shared subset of values accepted by both. Formally, this subset is the intersection of A and B (A ∩ B), where A is the set of values accepted by `schema0` and B is the set of values accepted by `schema1`. If `A ∩ B` is the empty set, then this function will return `null`. Otherwise it will return the schema that precisely accepts `A ∩ B`. | ||
+ _schema0_ - the first schema | ||
+ _schema1_ - the second schema (order doesn't matter) | ||
+ _isExemplar_ - if set, the schemas will be treated as exemplars (rather than type schemas) | ||
+ _isStrict_ - if set, the schemas will be intersected using strict validation rules (i.e. like `validateStrict()`) | ||
##### .sample(typeSchema, [n=2]) | ||
Given a type schema, return an array of up to `n` unique sample values that would validate against it (in random order). `n` defaults to 2 if left undefined. | ||
### Experimental | ||
The following functions are newly implemented, experimental, and tend to be a bit more advanced. They may undergo frequent changes over the coming months, so use with care. You have been warned! | ||
##### .isSpecific(typeSchemaOrExemplar, [recursive=false], [isExemplar=false]) | ||
Determine whether the given type schema is "specific". String, number, boolean, lamda, faceted dictionary, or patterned array types are "specific". Everything else is "generic". | ||
If the second argument (`recursive`) is set to `true`, then also recursively check the subkeys of faceted dictionaries and patterns of arrays in the type schema. | ||
If the third argument (`isExemplar`) is set to `true`, then treat the provided schema as an rttc example rather than a type schema. | ||
For reference | ||
| type | is specific? | | ||
|-------------------------|---------------------| | ||
| string | yes _(always)_ | | ||
| number | yes _(always)_ | | ||
| boolean | yes _(always)_ | | ||
| lamda | yes _(always)_ | | ||
| `{}` (generic) | no | | ||
| `[]` (generic) | no | | ||
| `{...}` (faceted) | yes _(maybe recursively)_ | | ||
| `[...]` (patterned) | yes _(maybe recursively)_ | | ||
| json | no | | ||
| ref | no | | ||
##### .getDefaultExemplar(typeSchema) | ||
Given a type schema, return a random exemplar which accepts precisely the same set of values. | ||
<!-- | ||
##### .isInvalidExample(exemplar) | ||
Return truthy if the provided value is NOT a valid rttc exemplar (e.g. `null`). | ||
> May be deprecated in an upcoming release | ||
--> | ||
<!-- | ||
##### .reify(typeSchema) | ||
@@ -608,2 +1031,3 @@ | ||
Given a type schema, strip out generics ("ref", "json", {}, and []) to convert it into a "specific" type. In other words, the result of this function always passes `rttc.isSpecific()`. | ||
--> | ||
@@ -613,4 +1037,11 @@ | ||
A convenience method to return the base value for the given exemplar. This is effectively the same thing as calling `rttc.infer()` to get its type schema, then coercing `undefined` to match (i.e. passing the type schema to `rttc.coerce()` without a second argument). | ||
A convenience method to return the base value for the given exemplar. This is effectively the same thing as calling `rttc.infer()` to get the exemplar's type schema, then coercing `undefined` to match it (i.e. passing the type schema to `rttc.coerce()` without a second argument). | ||
```js | ||
rttc.getBaseVal(exemplar); | ||
// ... is just a shorcut for doing: | ||
rttc.coerce(rttc.infer(exemplar), undefined); | ||
``` | ||
##### .cast(exemplar, actualValue) | ||
@@ -620,3 +1051,11 @@ | ||
```js | ||
rttc.cast(exemplar, actualValue); | ||
// ... is just a shorcut for doing: | ||
rttc.coerce(rttc.infer(exemplar), actualValue); | ||
``` | ||
## License | ||
@@ -626,2 +1065,2 @@ | ||
© 2014 Mike McNeil, Cody Stoltman; © 2015 The Treeline Company | ||
© 2014 Mike McNeil, Cody Stoltman; © 2015-2016 The Treeline Company |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
262067
1048