Skip to content

Commit

Permalink
Merge pull request #14 from tomekwi/throw-by-default-#10
Browse files Browse the repository at this point in the history
Throw by default
  • Loading branch information
ericelliott committed Nov 19, 2015
2 parents 17db5ac + 164a4d3 commit 58ed96c
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 4 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ rfx({
description?: String,
doc?: String,
example?: String,
onError?: (error: TypeError): Void,
...metadata?: Object,
fn: Function
}): Function
```
Expand All @@ -165,3 +167,15 @@ It can also take a `predicate` function:
```js
predicate(...args?: Any[]): Boolean
```

### Error handling

Whenever the type check fails the `onError` callback will be called. This behavior is opt-out – if the environment variable `NODE_ENV` is set to `production`, no type checking will be performed.

If the type check fails and you don’t specify an `onError` callback, we’ll throw a descriptive `TypeError`. This behavior is opt-in – if you want us to throw by default, you need to set the environment variable `NODE_ENV` to `development`. You can use *[envify](https://github.com/hughsk/envify)* to make this work in your browser.

### Additional `metadata`

All properties attached to the `fn` function and to the interface description will be copied over to the interface.

You can use this to attach additional metadata to your function. We recommend namespacing your metadata to one property for better protection against name conflicts.
36 changes: 32 additions & 4 deletions source/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,40 @@ import 'core-js';
const runCheck = ({ rtype, onError, options }) => {
return (...args) => {
if (!rtype(...args) && typeof onError === 'function') {
onError({ args, options });
const errorMessage = (
`Type check failed!${ rtype.signature ?
(
` Expected signature: \`${ rtype.signature }\`. More info: ` +
'https://git.io/rtype .'
) :
''
}`
);

const error = Object.assign(
new TypeError(errorMessage),
{ args, options }
);

onError(error);
}
};
};

const buildCheck = ({ shouldCheck, rtype, onError, options }) => {
const buildCheck = (params) => {
const { shouldCheck, rtype, options } = params;

const shouldThrowByDefault = typeof process !== 'undefined' &&
process.env.NODE_ENV === 'development';

const onError = (typeof options.onError === 'function' ?
options.onError :
(shouldThrowByDefault ?
(error) => { throw error; } :
null
)
);

return shouldCheck ?
typeof rtype === 'function' ?
runCheck({ rtype, onError, options }) :
Expand All @@ -19,7 +47,7 @@ const buildCheck = ({ shouldCheck, rtype, onError, options }) => {
const rfx = (options = {}) => {
const { type, onError } = options;

const shouldCheck = process &&
const shouldCheck = typeof process !== 'undefined' &&
process.env.NODE_ENV !== 'production';

const check = buildCheck({ shouldCheck, rtype: type, onError, options });
Expand All @@ -28,7 +56,7 @@ const rfx = (options = {}) => {
const { fn } = options;
if (typeof check === 'function') check(...args);
return fn(...args);
}, options.fn);
}, options.fn, options);
};

export default rfx;
149 changes: 149 additions & 0 deletions test/flow-logic/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,57 @@ test('rfx', nest => {
})('foo', 'bar', 'baz');
});

nest.test('...predicate rtype, dev env & default error handling', assert => {
assert.plan(4);

process.env.NODE_ENV = 'development';

const signature = '(a: String): Void';

const type = Object.assign(
(a) => {
assert.pass('should type check');
return typeof a === 'string';
},
{ signature }
);

const fx = rfx({
type,
fn () {
assert.fail('should not call fn');
}
});

try {
fx();
} catch (error) {
{
const actual = error.constructor;
const expected = TypeError;

assert.equal(actual, expected,
'should throw a TypeError');
}

{
const expectedContent = /type check failed/i;
const actualContent = error.message;

assert.ok(expectedContent.test(actualContent),
'should throw a descriptive error');
}

{
const expectedString = signature;
const actualString = error.message;

assert.ok(actualString.indexOf(expectedString) !== -1,
'should include signature in error message');
}
}
});

nest.test('...predicate rtype, prod env, onError & invalid args', assert => {
assert.plan(1);

Expand All @@ -58,13 +109,79 @@ test('rfx', nest => {
})('foo', 'bar', 'baz');
});

nest.test('...predicate rtype, prod env, default error handling', assert => {
assert.plan(2);

process.env.NODE_ENV = 'production';

const fx = rfx({
type () {
assert.fail('should not type check');
return false;
},

fn () {
assert.pass('should call fn');
}
});

assert.doesNotThrow(
() => fx(),
'should not throw an error'
);
});

nest.test('...predicate rtype, unknown env & onError', assert => {
assert.plan(3);

process.env.NODE_ENV = null;

rfx({
type () {
assert.pass('should type check');
return false;
},

fn () {
assert.pass('should call fn');
},

onError () {
assert.pass('should call onError');
}
})();
});

nest.test('...predicate rtype, unknown env & default error handling', assert => {
assert.plan(3);

process.env.NODE_ENV = null;

const fx = rfx({
type () {
assert.pass('should type check');
return false;
},

fn () {
assert.pass('should call fn');
}
});

assert.doesNotThrow(
() => fx(),
'should not throw an error'
);
});

nest.test('...predicate rtype, dev env, & no args', assert => {
process.env.NODE_ENV = 'development';

rfx({
type () {
assert.pass('should call typecheck function');
assert.end();
return true;
},
fn () {}
})();
Expand All @@ -85,4 +202,36 @@ test('rfx', nest => {

assert.end();
});

nest.test('...with metadata', assert => {
const myNamespace = {};

const fn = () => null;
const myFunctionProperty = {};
fn.myFunctionProperty = 'should be overridden!';

const fx = rfx({
myNamespace,
myFunctionProperty,
fn
});

{
const actual = fx.myNamespace;
const expected = myNamespace;

assert.equal(actual, expected,
'should attach extra properties to the interface');
}

{
const actual = fx.myFunctionProperty;
const expected = myFunctionProperty;

assert.equal(actual, expected,
'should overwrite function properties with interface properties');
}

assert.end();
});
});

0 comments on commit 58ed96c

Please sign in to comment.