superstruct
Advanced tools
Comparing version 0.0.5 to 0.1.0
@@ -12,4 +12,31 @@ | ||
To install Superstruct with Yarn or Npm, simply: | ||
```bash | ||
yarn add superstruct | ||
``` | ||
```bash | ||
npm install --save superstruct | ||
``` | ||
And then you can import it into your code base: | ||
```js | ||
import { struct, superstruct } from 'superstruct' | ||
``` | ||
If you would rather import Superstruct with a `<script>` tag, you can use the bundled build: | ||
```html | ||
<script src="https://unpkg.com/superstruct/dist/superstruct.min.js"></script> | ||
``` | ||
This will expose the `Superstruct` global with the exported functions. | ||
## Creating Structs | ||
## Defining Custom Data Types | ||
@@ -16,0 +43,0 @@ |
323
lib/index.js
@@ -8,331 +8,28 @@ 'use strict'; | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
var _structError = require('./struct-error'); | ||
var _cloneDeep = require('lodash/cloneDeep'); | ||
var _structError2 = _interopRequireDefault(_structError); | ||
var _cloneDeep2 = _interopRequireDefault(_cloneDeep); | ||
var _superstruct = require('./superstruct'); | ||
var _componentType = require('component-type'); | ||
var _superstruct2 = _interopRequireDefault(_superstruct); | ||
var _componentType2 = _interopRequireDefault(_componentType); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } | ||
/** | ||
* Default types. | ||
* Create a simple `struct` method for the default types. | ||
* | ||
* @type {Object} | ||
* @type {Function} | ||
*/ | ||
var DEFAULT_TYPES = { | ||
any: function any(v) { | ||
return v !== undefined; | ||
}, | ||
array: function array(v) { | ||
return (0, _componentType2.default)(v) === 'array'; | ||
}, | ||
boolean: function boolean(v) { | ||
return (0, _componentType2.default)(v) === 'boolean'; | ||
}, | ||
buffer: function buffer(v) { | ||
return (0, _componentType2.default)(v) === 'buffer'; | ||
}, | ||
date: function date(v) { | ||
return (0, _componentType2.default)(v) === 'date'; | ||
}, | ||
error: function error(v) { | ||
return (0, _componentType2.default)(v) === 'error'; | ||
}, | ||
function: function _function(v) { | ||
return (0, _componentType2.default)(v) === 'function'; | ||
}, | ||
null: function _null(v) { | ||
return (0, _componentType2.default)(v) === 'null'; | ||
}, | ||
number: function number(v) { | ||
return (0, _componentType2.default)(v) === 'number'; | ||
}, | ||
object: function object(v) { | ||
return (0, _componentType2.default)(v) === 'object'; | ||
}, | ||
regexp: function regexp(v) { | ||
return (0, _componentType2.default)(v) === 'regexp'; | ||
}, | ||
string: function string(v) { | ||
return (0, _componentType2.default)(v) === 'string'; | ||
}, | ||
undefined: function undefined(v) { | ||
return (0, _componentType2.default)(v) === 'undefined'; | ||
} | ||
const struct = (0, _superstruct2.default)(); | ||
/** | ||
* Define a struct error. | ||
* | ||
* @type {StructError} | ||
*/ | ||
}; | ||
var StructError = function (_Error) { | ||
_inherits(StructError, _Error); | ||
function StructError(message, data) { | ||
_classCallCheck(this, StructError); | ||
data.code = message; | ||
data.path = data.path || []; | ||
var index = data.index, | ||
key = data.key, | ||
value = data.value, | ||
type = data.type; | ||
switch (message) { | ||
case 'element_invalid': | ||
message = 'Expected the element at index `' + index + '` to be of type "' + type + '", but it was `' + value + '`.'; | ||
break; | ||
case 'property_invalid': | ||
case 'property_required': | ||
message = 'Expected the `' + key + '` property to be of type "' + type + '", but it was `' + value + '`.'; | ||
break; | ||
case 'property_unknown': | ||
message = 'Unexpected `' + key + '` property that was not defined in the struct.'; | ||
break; | ||
case 'value_invalid': | ||
case 'value_required': | ||
message = 'Expected a value of type "' + type + '" but received `' + value + '`.'; | ||
break; | ||
default: | ||
throw new Error('Unknown struct error code: "' + message + '"'); | ||
} | ||
var _this = _possibleConstructorReturn(this, (StructError.__proto__ || Object.getPrototypeOf(StructError)).call(this, message)); | ||
_this.name = 'StructError'; | ||
for (var k in data) { | ||
_this[k] = data[k]; | ||
} | ||
Error.captureStackTrace(_this, _this.constructor); | ||
return _this; | ||
} | ||
return StructError; | ||
}(Error); | ||
/** | ||
* Create a struct factory from a set of `options`. | ||
* Export. | ||
* | ||
* @param {Object} options | ||
* @return {Function} | ||
*/ | ||
function superstruct() { | ||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var TYPES = _extends({}, DEFAULT_TYPES, options.types || {}); | ||
/** | ||
* Define a scalar struct with a `schema` type string. | ||
* | ||
* @param {String} schema | ||
* @param {Any} defaults | ||
* @return {Function} | ||
*/ | ||
function scalarStruct(schema, defaults) { | ||
var isOptional = schema.endsWith('?'); | ||
var type = isOptional ? schema.slice(0, -1) : schema; | ||
var types = type.split(/\s*\|\s*/g); | ||
var fns = types.map(function (t) { | ||
var fn = TYPES[t]; | ||
if (typeof fn !== 'function') { | ||
throw new Error('No struct validator function found for type "' + t + '".'); | ||
} | ||
return fn; | ||
}); | ||
return function (value) { | ||
if (!isOptional && value === undefined) { | ||
throw new StructError('value_required', { type: type }); | ||
} | ||
if (value !== undefined && !fns.some(function (fn) { | ||
return fn(value); | ||
})) { | ||
throw new StructError('value_invalid', { type: type, value: value }); | ||
} | ||
return value; | ||
}; | ||
} | ||
/** | ||
* Define a list struct with a `schema` array. | ||
* | ||
* @param {Array} schema | ||
* @param {Any} defaults | ||
* @return {Function} | ||
*/ | ||
function listStruct(schema, defaults) { | ||
if (schema.length !== 1) { | ||
throw new Error('List structs must be defined as an array with a single element, but you passed ' + schema.length + ' elements.'); | ||
} | ||
schema = schema[0]; | ||
var fn = struct(schema); | ||
var type = 'array'; | ||
return function (value) { | ||
if (value === undefined) { | ||
throw new StructError('value_required', { type: type }); | ||
} else if ((0, _componentType2.default)(value) !== 'array') { | ||
throw new StructError('value_invalid', { type: type, value: value }); | ||
} | ||
var ret = value.map(function (v, index) { | ||
try { | ||
return fn(v); | ||
} catch (e) { | ||
var path = [index].concat(e.path); | ||
switch (e.code) { | ||
case 'value_invalid': | ||
throw new StructError('element_invalid', _extends({}, e, { index: index, path: path })); | ||
default: | ||
if ('path' in e) e.path = path; | ||
throw e; | ||
} | ||
} | ||
}); | ||
return ret; | ||
}; | ||
} | ||
/** | ||
* Define an object struct with a `schema` dictionary. | ||
* | ||
* @param {Object} schema | ||
* @param {Any} defaults | ||
* @return {Function} | ||
*/ | ||
function objectStruct(schema, defaults) { | ||
var structs = {}; | ||
var type = 'object'; | ||
for (var _key in schema) { | ||
var fn = struct(schema[_key]); | ||
structs[_key] = fn; | ||
} | ||
return function (value) { | ||
var isUndefined = false; | ||
if (value === undefined) { | ||
isUndefined = true; | ||
value = {}; | ||
} else if ((0, _componentType2.default)(value) !== 'object') { | ||
throw new StructError('value_invalid', { type: type, value: value }); | ||
} | ||
var ret = {}; | ||
for (var _key2 in structs) { | ||
var s = structs[_key2]; | ||
var v = value[_key2]; | ||
var r = void 0; | ||
try { | ||
r = s(v); | ||
} catch (e) { | ||
var path = [_key2].concat(e.path); | ||
switch (e.code) { | ||
case 'value_invalid': | ||
throw new StructError('property_invalid', _extends({}, e, { key: _key2, path: path })); | ||
case 'value_required': | ||
throw isUndefined ? new StructError('value_required', { type: type }) : new StructError('property_required', _extends({}, e, { key: _key2, path: path })); | ||
default: | ||
if ('path' in e) e.path = path; | ||
throw e; | ||
} | ||
} | ||
if (_key2 in value) { | ||
ret[_key2] = r; | ||
} | ||
} | ||
for (var _key3 in value) { | ||
if (!(_key3 in structs)) { | ||
throw new StructError('property_unknown', { key: _key3, path: [_key3] }); | ||
} | ||
} | ||
return isUndefined ? undefined : ret; | ||
}; | ||
} | ||
/** | ||
* Define a struct with `schema`. | ||
* | ||
* @param {Function|String|Array|Object} schema | ||
* @param {Any} defaults | ||
* @return {Function} | ||
*/ | ||
function struct(schema, defaults) { | ||
var s = void 0; | ||
if ((0, _componentType2.default)(schema) === 'function') { | ||
s = schema; | ||
} else if ((0, _componentType2.default)(schema) === 'string') { | ||
s = scalarStruct(schema, defaults); | ||
} else if ((0, _componentType2.default)(schema) === 'array') { | ||
s = listStruct(schema, defaults); | ||
} else if ((0, _componentType2.default)(schema) === 'object') { | ||
s = objectStruct(schema, defaults); | ||
} else { | ||
throw new Error('A struct schema definition must be a string, array or object, but you passed: ' + schema); | ||
} | ||
return function (value) { | ||
if (value === undefined) { | ||
value = typeof defaults === 'function' ? defaults() : (0, _cloneDeep2.default)(defaults); | ||
} | ||
return s(value); | ||
}; | ||
} | ||
/** | ||
* Return the struct factory. | ||
*/ | ||
return struct; | ||
} | ||
/** | ||
* Export the factory and the factory creator. | ||
* | ||
* @type {Function} | ||
*/ | ||
var struct = superstruct(); | ||
exports.default = struct; | ||
exports.struct = struct; | ||
exports.superstruct = superstruct; | ||
exports.StructError = StructError; | ||
exports.superstruct = _superstruct2.default; | ||
exports.StructError = _structError2.default; |
{ | ||
"name": "superstruct", | ||
"description": "A simple, expressive way to validate data in Javascript.", | ||
"version": "0.0.5", | ||
"version": "0.1.0", | ||
"license": "MIT", | ||
@@ -11,4 +11,4 @@ "repository": "git://github.com/ianstormtaylor/superstruct.git", | ||
"build:lib": "babel ./src --out-dir ./lib", | ||
"build:max": "mkdir -p ./dist && NODE_ENV=production browserify ./src/index.js --transform babelify --transform envify --standalone Superstruct > ./dist/superstruct.js", | ||
"build:min": "mkdir -p ./dist && NODE_ENV=production browserify ./src/index.js --transform babelify --transform envify --transform uglifyify --standalone Superstruct | uglifyjs > ./dist/superstruct.min.js", | ||
"build:max": "mkdir -p ./dist && browserify ./src/index.js --transform babelify --standalone Superstruct > ./dist/superstruct.js", | ||
"build:min": "mkdir -p ./dist && browserify ./src/index.js --transform babelify --standalone Superstruct > ./dist/superstruct.min.js", | ||
"clean": "rm -rf ./lib ./node_modules", | ||
@@ -29,5 +29,7 @@ "lint": "eslint src/* test/*", | ||
"babel-eslint": "^6.1.0", | ||
"babel-plugin-transform-async-to-generator": "^6.24.1", | ||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", | ||
"babel-plugin-transform-object-rest-spread": "^6.26.0", | ||
"babel-polyfill": "^6.26.0", | ||
"babel-preset-es2015": "^6.9.0", | ||
"babel-preset-stage-0": "^6.5.0", | ||
"babel-preset-minify": "^0.2.0", | ||
"babelify": "^7.3.0", | ||
@@ -34,0 +36,0 @@ "browserify": "^13.0.1", |
@@ -33,5 +33,7 @@ | ||
Superstruct makes it easy to define interfaces and then validate Javascript data against them. Its type annotation API was inspired by [Typescript](https://www.typescriptlang.org/docs/handbook/basic-types.html), [Flow](https://flow.org/en/docs/types/) and [GraphQL](http://graphql.org/learn/schema/), which gives it a API familiar, easy to understand API. But Superstruct is designed for runtime data validations, like accepting arbitrary input in a REST or GraphQL API, so it throws detailed errors for you or your end users. | ||
Superstruct makes it easy to define interfaces and then validate Javascript data against them. Its type annotation API was inspired by [Typescript](https://www.typescriptlang.org/docs/handbook/basic-types.html), [Flow](https://flow.org/en/docs/types/), [Go](https://gobyexample.com/structs), and [GraphQL](http://graphql.org/learn/schema/), which gives it a familiar, easy to understand API. | ||
But Superstruct is designed for runtime data validations, so it throws detailed runtime errors for you or your end users. This is especially useful in situations like accepting arbitrary input in a REST or GraphQL API. But it can even be used to validate internal data structures in non-typed code bases. | ||
<br/> | ||
@@ -121,3 +123,3 @@ | ||
- **They are tightly coupled to other concerns.** Many validators are implemented as plugins for Express or other HTTP frameworks, which is completely unnecessary and confusing to reason about. And since you can only use them with a web server you end up with even more fragmentation in your codebase. | ||
- **They are tightly coupled to other concerns.** Many validators are implemented as plugins for Express or other HTTP frameworks, which is completely unnecessary and confusing to reason about. And when you need to validate data elsewhere in your code base you end up with fragmentation. | ||
@@ -124,0 +126,0 @@ Of course, not every validation library suffers from all of these issues, but most of them exhibit at least one. If you've run into this problem before, you might like Superstruct. |
274
src/index.js
import cloneDeep from 'lodash/cloneDeep' | ||
import typeOf from 'component-type' | ||
import StructError from './struct-error' | ||
import superstruct from './superstruct' | ||
/** | ||
* Default types. | ||
* Create a simple `struct` method for the default types. | ||
* | ||
* @type {Object} | ||
* @type {Function} | ||
*/ | ||
const DEFAULT_TYPES = { | ||
any: v => v !== undefined, | ||
array: v => typeOf(v) === 'array', | ||
boolean: v => typeOf(v) === 'boolean', | ||
buffer: v => typeOf(v) === 'buffer', | ||
date: v => typeOf(v) === 'date', | ||
error: v => typeOf(v) === 'error', | ||
function: v => typeOf(v) === 'function', | ||
null: v => typeOf(v) === 'null', | ||
number: v => typeOf(v) === 'number', | ||
object: v => typeOf(v) === 'object', | ||
regexp: v => typeOf(v) === 'regexp', | ||
string: v => typeOf(v) === 'string', | ||
undefined: v => typeOf(v) === 'undefined', | ||
} | ||
const struct = superstruct() | ||
/** | ||
* Define a struct error. | ||
* Export. | ||
* | ||
* @type {StructError} | ||
*/ | ||
class StructError extends Error { | ||
constructor(message, data) { | ||
data.code = message | ||
data.path = data.path || [] | ||
const { index, key, value, type } = data | ||
switch (message) { | ||
case 'element_invalid': | ||
message = `Expected the element at index \`${index}\` to be of type "${type}", but it was \`${value}\`.` | ||
break | ||
case 'property_invalid': | ||
case 'property_required': | ||
message = `Expected the \`${key}\` property to be of type "${type}", but it was \`${value}\`.` | ||
break | ||
case 'property_unknown': | ||
message = `Unexpected \`${key}\` property that was not defined in the struct.` | ||
break | ||
case 'value_invalid': | ||
case 'value_required': | ||
message = `Expected a value of type "${type}" but received \`${value}\`.` | ||
break | ||
default: | ||
throw new Error(`Unknown struct error code: "${message}"`) | ||
} | ||
super(message) | ||
this.name = 'StructError' | ||
for (const k in data) { | ||
this[k] = data[k] | ||
} | ||
Error.captureStackTrace(this, this.constructor) | ||
} | ||
} | ||
/** | ||
* Create a struct factory from a set of `options`. | ||
* | ||
* @param {Object} options | ||
* @return {Function} | ||
*/ | ||
function superstruct(options = {}) { | ||
const TYPES = { | ||
...DEFAULT_TYPES, | ||
...(options.types || {}), | ||
} | ||
/** | ||
* Define a scalar struct with a `schema` type string. | ||
* | ||
* @param {String} schema | ||
* @param {Any} defaults | ||
* @return {Function} | ||
*/ | ||
function scalarStruct(schema, defaults) { | ||
const isOptional = schema.endsWith('?') | ||
const type = isOptional ? schema.slice(0, -1) : schema | ||
const types = type.split(/\s*\|\s*/g) | ||
const fns = types.map((t) => { | ||
const fn = TYPES[t] | ||
if (typeof fn !== 'function') { | ||
throw new Error(`No struct validator function found for type "${t}".`) | ||
} | ||
return fn | ||
}) | ||
return (value) => { | ||
if (!isOptional && value === undefined) { | ||
throw new StructError('value_required', { type }) | ||
} | ||
if (value !== undefined && !fns.some(fn => fn(value))) { | ||
throw new StructError('value_invalid', { type, value }) | ||
} | ||
return value | ||
} | ||
} | ||
/** | ||
* Define a list struct with a `schema` array. | ||
* | ||
* @param {Array} schema | ||
* @param {Any} defaults | ||
* @return {Function} | ||
*/ | ||
function listStruct(schema, defaults) { | ||
if (schema.length !== 1) { | ||
throw new Error(`List structs must be defined as an array with a single element, but you passed ${schema.length} elements.`) | ||
} | ||
schema = schema[0] | ||
const fn = struct(schema) | ||
const type = 'array' | ||
return (value) => { | ||
if (value === undefined) { | ||
throw new StructError('value_required', { type }) | ||
} else if (typeOf(value) !== 'array') { | ||
throw new StructError('value_invalid', { type, value }) | ||
} | ||
const ret = value.map((v, index) => { | ||
try { | ||
return fn(v) | ||
} catch (e) { | ||
const path = [index].concat(e.path) | ||
switch (e.code) { | ||
case 'value_invalid': | ||
throw new StructError('element_invalid', { ...e, index, path }) | ||
default: | ||
if ('path' in e) e.path = path | ||
throw e | ||
} | ||
} | ||
}) | ||
return ret | ||
} | ||
} | ||
/** | ||
* Define an object struct with a `schema` dictionary. | ||
* | ||
* @param {Object} schema | ||
* @param {Any} defaults | ||
* @return {Function} | ||
*/ | ||
function objectStruct(schema, defaults) { | ||
const structs = {} | ||
const type = 'object' | ||
for (const key in schema) { | ||
const fn = struct(schema[key]) | ||
structs[key] = fn | ||
} | ||
return (value) => { | ||
let isUndefined = false | ||
if (value === undefined) { | ||
isUndefined = true | ||
value = {} | ||
} else if (typeOf(value) !== 'object') { | ||
throw new StructError('value_invalid', { type, value }) | ||
} | ||
const ret = {} | ||
for (const key in structs) { | ||
const s = structs[key] | ||
const v = value[key] | ||
let r | ||
try { | ||
r = s(v) | ||
} catch (e) { | ||
const path = [key].concat(e.path) | ||
switch (e.code) { | ||
case 'value_invalid': | ||
throw new StructError('property_invalid', { ...e, key, path }) | ||
case 'value_required': | ||
throw isUndefined | ||
? new StructError('value_required', { type }) | ||
: new StructError('property_required', { ...e, key, path }) | ||
default: | ||
if ('path' in e) e.path = path | ||
throw e | ||
} | ||
} | ||
if (key in value) { | ||
ret[key] = r | ||
} | ||
} | ||
for (const key in value) { | ||
if (!(key in structs)) { | ||
throw new StructError('property_unknown', { key, path: [key] }) | ||
} | ||
} | ||
return isUndefined ? undefined : ret | ||
} | ||
} | ||
/** | ||
* Define a struct with `schema`. | ||
* | ||
* @param {Function|String|Array|Object} schema | ||
* @param {Any} defaults | ||
* @return {Function} | ||
*/ | ||
function struct(schema, defaults) { | ||
let s | ||
if (typeOf(schema) === 'function') { | ||
s = schema | ||
} else if (typeOf(schema) === 'string') { | ||
s = scalarStruct(schema, defaults) | ||
} else if (typeOf(schema) === 'array') { | ||
s = listStruct(schema, defaults) | ||
} else if (typeOf(schema) === 'object') { | ||
s = objectStruct(schema, defaults) | ||
} else { | ||
throw new Error(`A struct schema definition must be a string, array or object, but you passed: ${schema}`) | ||
} | ||
return (value) => { | ||
if (value === undefined) { | ||
value = typeof defaults === 'function' | ||
? defaults() | ||
: cloneDeep(defaults) | ||
} | ||
return s(value) | ||
} | ||
} | ||
/** | ||
* Return the struct factory. | ||
*/ | ||
return struct | ||
} | ||
/** | ||
* Export the factory and the factory creator. | ||
* | ||
* @type {Function} | ||
*/ | ||
const struct = superstruct() | ||
export default struct | ||
export { struct, superstruct, StructError } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
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
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
652647
32
7197
2
177
20