Repository: ViacomInc/data-point (js)

Issue:
Use outputType for hash and collection validation
**Problem description**:

We added `inputType` and `outputType` for type checking, but the `hash` and `collection` entities still have their own validation functions:

https://github.com/ViacomInc/data-point/blob/master/packages/data-point/lib/entity-types/entity-collection/resolve.js#L21

https://github.com/ViacomInc/data-point/blob/master/packages/data-point/lib/entity-types/entity-hash/resolve.js#L20

**Suggested solution**:

- `hash` and `collection` should type check with a default `outputType` property ([isObject](https://github.com/ViacomInc/data-point/blob/master/packages/data-point/lib/helpers/type-check-function-reducers.js#L48) for `hash` and [isArray](https://github.com/ViacomInc/data-point/blob/master/packages/data-point/lib/helpers/type-check-function-reducers.js#L43) for `collection`). 
- ~Users would not be allowed to override this, which would prevent conflicting type checks on a `hash` or `collection`. Right now for example, somebody could add `outputType: 'string'` to a `hash` entity.~
- Allow users to override the defaults. 
- Add Documentation to README for this new behavior.
- Mark as a breaking change




Pull request:
feat(data-point): Normalize collection and hash type checking functionality
closes #189 

**What**: always use `outputType` for type checking `hash` and `collection` entities (before, they had custom validate functions)

**Why**: type checking is always done in the same place now, whether or not the user has provided a custom type check reducer

**How**:

1. Refactored `getTypeModifier` to now process arrays (and renamed it to `normalizeTypeCheckSource`)
2. `hash#outputType` always uses the `isObject` reducer, following by an optional custom reducer
3. `collection#outputType` always uses the `isArray` reducer, following by an optional custom reducer
4. Throw error when trying to use an invalid `outputType` for hash or collection (like `string`, `number`, etc)
5. Reword the error message when type checking fails

**Checklist**:

- [ ] Has Breaking changes
- [x] Documentation
- [x] Tests
- [x] Ready to be merged 

Patch:
diff --git a/packages/data-point/README.md b/packages/data-point/README.md
index a69359c..11494dd 100644
--- a/packages/data-point/README.md
+++ b/packages/data-point/README.md
@@ -50,6 +50,7 @@ npm install --save data-point
     - [Collection](#collection-entity)
     - [Control](#control-entity)
     - [Schema](#schema-entity)
+  - [Entity type checking](#entity-type-check)
   - [Entity ComposeReducer](#entity-compose-reducer)
   - [Inspecting Entities](#inspecting-entities)
   - [Extending Entities](#extending-entities)
@@ -1370,15 +1371,15 @@ All entities share a common API (except for [Reducer](#reducer-entity)).
 
 | Key | Type | Description |
 |:---|:---|:---|
-| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *inputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's input value, but does not mutate it |
 | *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *after*   | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
-| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *outputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's output value, but does not mutate it |
 | *error*   | [Reducer](#reducers) | reducer to be resolved in case of an error |
 | *params*  | `Object` | User defined Hash that will be passed to every transform within the context of the transform's execution |
 
 
-##### <a name="entity-type-check">Entity Type checking</a>
+##### <a name="entity-type-check">Entity type checking</a>
 
 You may use **inputType** and **outputType** to type check against the values being passed and resolved from an entity. Type checking does not mutate the result. 
 
@@ -1561,10 +1562,10 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
-| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *inputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's input value, but does not mutate it |
 | *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *after*   | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
-| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *outputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's output value, but does not mutate it |
 | *error*   | [Reducer](#reducers) | reducer to be resolved in case of an error |
 | *params*  | `Object` | User defined Hash that will be passed to every transform within the context of the transform's execution |
 
@@ -1832,10 +1833,10 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
-| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *inputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's input value, but does not mutate it |
 | *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *after*   | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
-| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *outputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's output value, but does not mutate it |
 | *error*   | [Reducer](#reducers) | reducer to be resolved in case of an error |
 | *params*  | `Object` | User defined Hash that will be passed to every transform within the context of the transform's execution |
 
@@ -1865,13 +1866,13 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
-| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check) |
+| *inputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's input value, but does not mutate it |
 | *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *url*   | [StringTemplate](#string-template) | String value to resolve the request's url |
 | *options* | [Reducer](#reducers) | reducer that returns an object to use as [request.js](https://github.com/request/request) options
 | *after*   | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
 | *error*   | [Reducer](#reducers) | reducer to be resolved in case of an error |
-| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check) |
+| *outputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's output value, but does not mutate it |
 | *params*    | `Object` | User defined Hash that will be passed to every reducer within the context of the transform function's execution |
 
 ##### <a name="request-url">Request.url</a>
@@ -2051,7 +2052,7 @@ If `params.inspect` is a `function`, you may execute custom debugging code to be
 
 A Hash entity transforms a _Hash_ like data structure. It enables you to manipulate the keys within a Hash. 
 
-To prevent unexpected results, **Hash** can only process **Plain Objects**, which are objects created by the Object constructor. If [Hash.value](#hash-value) does not resolve to a Plain Object it will **throw** an error. 
+To prevent unexpected results, a **Hash** can only return **Plain Objects**, which are objects created by the Object constructor. If a hash resolves to a different type, it will throw an error. This type check occurs *before* the value is passed to the (optional) `outputType` reducer.
 
 Hash entities expose a set of reducers: [mapKeys](#hash-mapKeys), [omitKeys](#hash-omitKeys), [pickKeys](#hash-pickKeys), [addKeys](#hash-addKeys), [addValues](#hash-addValues). You may apply one or more of these reducers to a Hash entity. Keep in mind that those reducers will always be executed in a specific order:
 
@@ -2061,8 +2062,6 @@ omitKeys -> pickKeys -> mapKeys -> addValues -> addKeys
 
 If you want to have more control over the order of execution, you may use the [compose](#entity-compose-reducer) reducer.
 
-**NOTE**: The Compose reducer is meant to operate only on Hash-type objects. If its context resolves to a non-Hash type, it will **throw an error**.
-
 **SYNOPSIS**
 
 ```js
@@ -2089,7 +2088,7 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
-| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *inputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's input value, but does not mutate it |
 | *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *value* | [Reducer](#reducers) | The value to which the Entity resolves |
 | *mapKeys* | [ObjectReducer](#object-reducer) | Map to a new set of key/values. Each value accepts a reducer |
@@ -2099,7 +2098,7 @@ dataPoint.addEntities({
 | *addValues* | `Object` | Add/Override hard-coded key/values. Internally, this uses the [assign](#reducer-assign) reducer helper |
 | *compose* | [ComposeReducer](#compose-reducer)`[]` | Modify the value of accumulator through an Array of `ComposeReducer` objects. Think of it as a [Compose/Flow Operation](https://en.wikipedia.org/wiki/Function_composition_(computer_science)), where the result of one operation gets passed to the next one|
 | *after*   | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
-| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *outputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's output value, but does not mutate it. Collection only supports custom outputType reducers, and not the built-in types like **string**, **number**, etc. |
 | *error*   | [Reducer](#reducers) | reducer to be resolved in case of an error |
 | *params*    | `Object` | User-defined Hash that will be passed to every reducer within the context of the transform function's execution |
 
@@ -2389,7 +2388,7 @@ A Collection entity enables you to operate over an array. Its API provides basic
 
 Collection entities expose a set of reducers that you may apply to them: [map](#collection-map), [find](#collection-find), [filter](#collection-filter). These reducers are executed in a [specific order](#collection-reducers-order). If you want to have more control over the order of execution, use the [compose](#compose-reducer) reducer.
 
-To prevent unexpected results, a **Collection Entity** can only process **Arrays**, if Collection.value does not resolve to an Array it will **throw** an error. 
+To prevent unexpected results, a **Collection** can only return arrays. If a collection resolves to a different type, it will throw an eror. This type check occurs *before* the value is passed to the (optional) `outputType` reducer.
 
 **IMPORTANT:** Keep in mind that in DataPoint, **all** operations are asynchronous. If your operations do NOT need to be asynchronous, iterating over a large array might result in slower execution. In such cases, consider using a reducer function where you can implement a synchronous solution.
 
@@ -2417,7 +2416,7 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
-| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *inputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's input value, but does not mutate it |
 | *before*  | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *value* | [Reducer](#reducers) | The value to which the Entity resolves |
 | *map* | [Reducer](#reducers) | Maps the items of an array. Internally, this uses the [map](#reducer-map) reducer helper |
@@ -2425,7 +2424,7 @@ dataPoint.addEntities({
 | *filter* | [Reducer](#reducers) | Filters the items of an array. Internally, this uses the [filter](#reducer-filter) reducer helper |
 | *compose* | [ComposeReducer](#compose-reducer)`[]` | Modify the value of accumulator through an Array of `ComposeReducer` objects. Think of it as a [Compose/Flow Operation](https://en.wikipedia.org/wiki/Function_composition_(computer_science)), where the result of one object gets passed to the next one |
 | *after* | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
-| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *outputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's output value, but does not mutate it. Hash only supports custom outputType reducers, and not the built-in types like **string**, **number**, etc. |
 | *error* | [Reducer](#reducers) | reducer to be resolved in case of an error |
 | *params* | `Object` | User-defined Hash that will be passed to every reducer within the context of the transform function's execution |
 
@@ -2860,12 +2859,12 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
-| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *inputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's input value, but does not mutate it |
 | *before* | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *select* | [Case Statements](#case-statements)`[]` | Array of case statements, and a default fallback |
 | *params* | `Object` | User-defined Hash that will be passed to every transform within the context of the transform's execution |
 | *after* | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
-| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *outputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's output value, but does not mutate it |
 | *error* | [Reducer](#reducers) | reducer to be resolved in case of an error |
 
 ##### <a name="case-statements">Case Statements</a>
@@ -2948,13 +2947,13 @@ dataPoint.addEntities({
 
 | Key | Type | Description |
 |:---|:---|:---|
-| *inputType*  | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *inputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's input value, but does not mutate it |
 | *before* | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
 | *value* | [Reducer](#reducers) | The value that this entity will pass to the schema validation |
 | *schema* | `Object` | Valid [JSON Schema](http://json-schema.org/documentation.html) object. If the schema is not valid, an error is thrown when creating the entity. |
 | *options* | `Object` | Avj's [options](https://github.com/epoberezkin/ajv#options) object
 | *after* | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution |
-| *outputType*  | String, [Reducer](#reducers) | type checks the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check). |
+| *outputType*  | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's output value, but does not mutate it |
 | *error* | [Reducer](#reducers) | reducer to be resolved in case of an error |
 | *params* | `Object` | User-defined Hash that will be passed to every transform within the context of the transform's execution |
 
@@ -3162,7 +3161,7 @@ dataPoint.addEntityType(id:String, spec:Object)
 
 #### <a name="data-point-add-entity-types">dataPoint.addEntityType</a>
 
-Adds a single Entity Type to the DataPoint instance.
+Adds a single Entity type to the DataPoint instance.
 
 **SYNOPSIS**
 
@@ -3174,7 +3173,7 @@ dataPoint.addEntityType(name:String, spec:Object)
 
 | Argument | Type | Description |
 |:---|:---|:---|
-| *name* | `Object` | Name of the new Entity Type |
+| *name* | `Object` | Name of the new Entity type |
 | *spec* | `Object` | New [Entity spec](#custom-entity-spec) API |
 
 #### <a name="data-point-add-entity-types">dataPoint.addEntityTypes</a>
@@ -3191,7 +3190,7 @@ dataPoint.addEntityTypes(specs:Object)
 
 | Argument | Type | Description |
 |:---|:---|:---|
-| *specs* | `Object` | Key/value hash where each key is the name of the new Entity Type and value is the [Entity spec](#custom-entity-spec) API. |
+| *specs* | `Object` | Key/value hash where each key is the name of the new Entity type and value is the [Entity spec](#custom-entity-spec) API. |
 
 #### <a name="custom-entity-spec">Custom Entity Spec</a>
 
diff --git a/packages/data-point/lib/entity-types/base-entity/factory.js b/packages/data-point/lib/entity-types/base-entity/factory.js
index f6dfd37..cdaeb19 100644
--- a/packages/data-point/lib/entity-types/base-entity/factory.js
+++ b/packages/data-point/lib/entity-types/base-entity/factory.js
@@ -1,30 +1,9 @@
 const deepFreeze = require('deep-freeze')
 const defaultTo = require('lodash/defaultTo')
-const typeCheckFunctionReducers = require('../../helpers/type-check-function-reducers')
+const { normalizeTypeCheckSource } = require('../../helpers/type-check-helpers')
 
 const createReducer = require('../../reducer-types').create
 
-const typeCheckModifiers = {
-  string: typeCheckFunctionReducers.isString,
-  number: typeCheckFunctionReducers.isNumber,
-  boolean: typeCheckFunctionReducers.isBoolean,
-  function: typeCheckFunctionReducers.isFunction,
-  error: typeCheckFunctionReducers.isError,
-  array: typeCheckFunctionReducers.isArray,
-  object: typeCheckFunctionReducers.isObject
-}
-
-function getTypeModifier (reducer) {
-  const typeCheckModifier = typeCheckModifiers[reducer]
-  if (typeCheckModifier) {
-    return typeCheckModifier
-  }
-
-  return reducer
-}
-
-module.exports.getTypeModifier = getTypeModifier
-
 /**
  * @param {Function} Factory - factory function to create the entity
  * @param {Object} spec - spec for the Entity
@@ -52,12 +31,12 @@ function create (Factory, spec, id) {
   }
 
   if (spec.inputType) {
-    const inputType = getTypeModifier(spec.inputType)
+    const inputType = normalizeTypeCheckSource(spec.inputType)
     entity.inputType = createReducer(inputType)
   }
 
   if (spec.outputType) {
-    const outputType = getTypeModifier(spec.outputType)
+    const outputType = normalizeTypeCheckSource(spec.outputType)
     entity.outputType = createReducer(outputType)
   }
 
diff --git a/packages/data-point/lib/entity-types/entity-collection/factory.js b/packages/data-point/lib/entity-types/entity-collection/factory.js
index 0d37eea..eca575b 100644
--- a/packages/data-point/lib/entity-types/entity-collection/factory.js
+++ b/packages/data-point/lib/entity-types/entity-collection/factory.js
@@ -3,6 +3,9 @@ const createReducer = require('../../reducer-types').create
 const createBaseEntity = require('../base-entity').create
 const reducerHelpers = require('../../reducer-types/reducer-helpers')
 const { validateModifiers } = require('../validate-modifiers')
+const {
+  getTypeCheckSourceWithDefault
+} = require('../../helpers/type-check-helpers')
 
 /**
  * @class
@@ -21,7 +24,7 @@ const modifiers = {
 
 /**
  * @param {Array<Object>} composeSpec
- * @return {Array<Object>}
+ * @return {Reducer}
  */
 function createCompose (composeSpec) {
   const stubs = composeSpec.map(modifier => {
@@ -42,6 +45,13 @@ function create (spec, id) {
   validateModifiers(id, spec, modifierKeys.concat('compose'))
   parseCompose.validateComposeModifiers(id, spec, modifierKeys)
 
+  const outputType = getTypeCheckSourceWithDefault(
+    'collection',
+    'array',
+    spec.outputType
+  )
+  spec = Object.assign({}, spec, { outputType })
+
   const entity = createBaseEntity(EntityCollection, spec, id)
 
   const composeSpec = parseCompose.parse(spec, modifierKeys)
diff --git a/packages/data-point/lib/entity-types/entity-collection/resolve.js b/packages/data-point/lib/entity-types/entity-collection/resolve.js
index 8749efb..a8d50c3 100644
--- a/packages/data-point/lib/entity-types/entity-collection/resolve.js
+++ b/packages/data-point/lib/entity-types/entity-collection/resolve.js
@@ -1,43 +1,13 @@
-const Util = require('util')
-const Promise = require('bluebird')
-const _ = require('lodash')
-
-const utils = require('../../utils')
-
-/**
- * NOTE: as expensive as this might be, this is to avoid 'surprises'
- * @param {Accumulator} acc
- * @return {Promise<Accumulator>}
- */
-function validateAsArray (acc) {
-  const entity = acc.reducer.spec
-  return acc.value instanceof Array
-    ? acc
-    : Promise.reject(
-        new Error(
-          Util.format(
-            '%s received value = %s of type %s,',
-            entity.id,
-            _.truncate(Util.inspect(acc.value, { breakLength: Infinity }), {
-              length: 30
-            }),
-            utils.typeOf(acc.value),
-            'this entity only resolves Array values. More info https://github.com/ViacomInc/data-point/tree/master/packages/data-point#collection-entity'
-          )
-        )
-      )
-}
-
 /**
  * @param {Accumulator} accumulator
  * @param {Function} resolveReducer
+ * @return {Promise}
  */
 function resolve (accumulator, resolveReducer) {
   const entity = accumulator.reducer.spec
-
-  return resolveReducer(accumulator, entity.value)
-    .then(acc => validateAsArray(acc))
-    .then(acc => resolveReducer(acc, entity.compose))
+  return resolveReducer(accumulator, entity.value).then(acc =>
+    resolveReducer(acc, entity.compose)
+  )
 }
 
 module.exports.resolve = resolve
diff --git a/packages/data-point/lib/entity-types/entity-hash/factory.js b/packages/data-point/lib/entity-types/entity-hash/factory.js
index bbb3be1..91374db 100644
--- a/packages/data-point/lib/entity-types/entity-hash/factory.js
+++ b/packages/data-point/lib/entity-types/entity-hash/factory.js
@@ -6,6 +6,9 @@ const reducerHelpers = require('../../reducer-types/reducer-helpers')
 const parseCompose = require('../parse-compose')
 const createBaseEntity = require('../base-entity').create
 const { validateModifiers } = require('../validate-modifiers')
+const {
+  getTypeCheckSourceWithDefault
+} = require('../../helpers/type-check-helpers')
 
 /**
  * @class
@@ -23,8 +26,12 @@ const modifiers = {
   assign: reducerHelpers.stubFactories.assign
 }
 
-function createCompose (composeParse) {
-  const specList = composeParse.map(modifier => {
+/**
+ * @param {Array<Object>} composeSpec
+ * @return {Reducer}
+ */
+function createCompose (composeSpec) {
+  const specList = composeSpec.map(modifier => {
     let spec
     switch (modifier.type) {
       case 'omitKeys':
@@ -61,6 +68,13 @@ function create (spec, id) {
   validateModifiers(id, spec, modifierKeys.concat('compose'))
   parseCompose.validateComposeModifiers(id, spec, modifierKeys)
 
+  const outputType = getTypeCheckSourceWithDefault(
+    'hash',
+    'object',
+    spec.outputType
+  )
+  spec = Object.assign({}, spec, { outputType })
+
   const entity = createBaseEntity(EntityHash, spec, id)
 
   const compose = parseCompose.parse(spec, modifierKeys)
diff --git a/packages/data-point/lib/entity-types/entity-hash/resolve.js b/packages/data-point/lib/entity-types/entity-hash/resolve.js
index 5ed3d6d..a8d50c3 100644
--- a/packages/data-point/lib/entity-types/entity-hash/resolve.js
+++ b/packages/data-point/lib/entity-types/entity-hash/resolve.js
@@ -1,43 +1,13 @@
-const _ = require('lodash')
-const Promise = require('bluebird')
-const utils = require('../../utils')
-const Util = require('util')
-
-/**
- * NOTE: as expensive as this might be, this is to avoid 'surprises'
- * @param {Accumulator} acc
- * @return {Promise<Accumulator>}
- */
-function validateAsObject (acc) {
-  const entity = acc.reducer.spec
-  if (_.isPlainObject(acc.value)) {
-    return acc
-  }
-  return Promise.reject(
-    new Error(
-      Util.format(
-        '%s received value = %s of type %s,',
-        entity.id,
-        _.truncate(Util.inspect(acc.value, { breakLength: Infinity }), {
-          length: 30
-        }),
-        utils.typeOf(acc.value),
-        'this entity only resolves plain Objects. More info https://github.com/ViacomInc/data-point/tree/master/packages/data-point#hash-entity'
-      )
-    )
-  )
-}
-
 /**
  * @param {Accumulator} accumulator
  * @param {Function} resolveReducer
+ * @return {Promise}
  */
 function resolve (accumulator, resolveReducer) {
   const entity = accumulator.reducer.spec
-
-  return resolveReducer(accumulator, entity.value)
-    .then(validateAsObject)
-    .then(acc => resolveReducer(acc, entity.compose))
+  return resolveReducer(accumulator, entity.value).then(acc =>
+    resolveReducer(acc, entity.compose)
+  )
 }
 
 module.exports.resolve = resolve
diff --git a/packages/data-point/lib/helpers/helpers.js b/packages/data-point/lib/helpers/helpers.js
index 153736e..8d39ac2 100644
--- a/packages/data-point/lib/helpers/helpers.js
+++ b/packages/data-point/lib/helpers/helpers.js
@@ -4,7 +4,7 @@ const resolveReducer = require('../reducer-types').resolve
 const AccumulatorFactory = require('../accumulator/factory')
 
 const { stubFactories } = require('../reducer-types/reducer-helpers')
-const typeCheckFunctionReducers = require('./type-check-function-reducers')
+const typeCheckFunctionReducers = require('./type-check-functions')
 
 module.exports.helpers = {
   assign: stubFactories.assign,
diff --git a/packages/data-point/lib/helpers/type-check-function-reducers.js b/packages/data-point/lib/helpers/type-check-functions.js
similarity index 60%
rename from packages/data-point/lib/helpers/type-check-function-reducers.js
rename to packages/data-point/lib/helpers/type-check-functions.js
index e3ff73c..e7032bb 100644
--- a/packages/data-point/lib/helpers/type-check-function-reducers.js
+++ b/packages/data-point/lib/helpers/type-check-functions.js
@@ -2,11 +2,14 @@ const Util = require('util')
 const _ = require('lodash')
 const utils = require('../utils')
 
-function errorMessage (value, expectedType) {
-  const entityId = _.get(value, 'reducer.spec.id', 'value')
+/**
+ * @param {*} value
+ * @param {string} expectedType
+ * @return {string}
+ */
+function getErrorMessage (value, expectedType) {
   return Util.format(
-    '%s did not pass type check, type expected: %s but recevied: %s. The value received is: %s',
-    entityId,
+    'Entity type check failed!\nExpected type: %s\nActual type: %s\nInput value: %s',
     expectedType,
     utils.typeOf(value),
     _.truncate(Util.inspect(value, { breakLength: Infinity }), {
@@ -17,37 +20,37 @@ function errorMessage (value, expectedType) {
 
 function isString (value) {
   if (typeof value === 'string') return value
-  throw new Error(errorMessage(value, 'string'))
+  throw new Error(getErrorMessage(value, 'string'))
 }
 
 function isNumber (value) {
   if (typeof value === 'number') return value
-  throw new Error(errorMessage(value, 'number'))
+  throw new Error(getErrorMessage(value, 'number'))
 }
 
 function isBoolean (value) {
   if (typeof value === 'boolean') return value
-  throw new Error(errorMessage(value, 'boolean'))
+  throw new Error(getErrorMessage(value, 'boolean'))
 }
 
 function isFunction (value) {
   if (typeof value === 'function') return value
-  throw new Error(errorMessage(value, 'function'))
+  throw new Error(getErrorMessage(value, 'function'))
 }
 
 function isError (value) {
   if (value instanceof Error) return value
-  throw new Error(errorMessage(value, 'function'))
+  throw new Error(getErrorMessage(value, 'function'))
 }
 
 function isArray (value) {
   if (Array.isArray(value)) return value
-  throw new Error(errorMessage(value, 'Array'))
+  throw new Error(getErrorMessage(value, 'array'))
 }
 
 function isObject (value) {
   if (_.isPlainObject(value)) return value
-  throw new Error(errorMessage(value, 'Object'))
+  throw new Error(getErrorMessage(value, 'object'))
 }
 
 module.exports = {
diff --git a/packages/data-point/lib/helpers/type-check-helpers.js b/packages/data-point/lib/helpers/type-check-helpers.js
new file mode 100644
index 0000000..d4efcfb
--- /dev/null
+++ b/packages/data-point/lib/helpers/type-check-helpers.js
@@ -0,0 +1,47 @@
+const typeCheckFunctions = require('./type-check-functions')
+
+const modifiers = {
+  string: typeCheckFunctions.isString,
+  number: typeCheckFunctions.isNumber,
+  boolean: typeCheckFunctions.isBoolean,
+  function: typeCheckFunctions.isFunction,
+  error: typeCheckFunctions.isError,
+  array: typeCheckFunctions.isArray,
+  object: typeCheckFunctions.isObject
+}
+
+/**
+ * @param {*} source - for creating a reducer that does type checking
+ * @return {*}
+ */
+function normalizeTypeCheckSource (source) {
+  if (Array.isArray(source)) {
+    return source.map(r => normalizeTypeCheckSource(r))
+  }
+
+  return modifiers[source] || source
+}
+
+module.exports.normalizeTypeCheckSource = normalizeTypeCheckSource
+
+/**
+ * @param {string} defaultType
+ * @param {string} specType
+ * @throws if specType !== defaultType but it's a key in the modifiers object
+ * @return {*}
+ */
+function getTypeCheckSourceWithDefault (entityType, defaultType, specType) {
+  if (!specType || specType === defaultType) {
+    return defaultType
+  }
+
+  if (modifiers[specType]) {
+    throw new Error(
+      `${entityType} entities do not support the "${specType}" outputType!`
+    )
+  }
+
+  return [defaultType, specType]
+}
+
+module.exports.getTypeCheckSourceWithDefault = getTypeCheckSourceWithDefault


Test patch:
diff --git a/packages/data-point/lib/entity-types/base-entity/__snapshots__/resolve.test.js.snap b/packages/data-point/lib/entity-types/base-entity/__snapshots__/resolve.test.js.snap
index 593d71a..e4ed3f3 100644
--- a/packages/data-point/lib/entity-types/base-entity/__snapshots__/resolve.test.js.snap
+++ b/packages/data-point/lib/entity-types/base-entity/__snapshots__/resolve.test.js.snap
@@ -1,25 +1,75 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`ResolveEntity.resolveEntity inputType - throws error if inputType does not pass 1`] = `[Error: value did not pass type check, type expected: number but recevied: string. The value received is: 'foo']`;
+exports[`ResolveEntity.resolveEntity inputType - throws error if inputType does not pass 1`] = `
+[Error: Entity type check failed!
+Expected type: number
+Actual type: string
+Input value: 'foo']
+`;
 
-exports[`ResolveEntity.resolveEntity outputType fails if error method catches typeCheck errors and returns bad value 1`] = `[Error: value did not pass type check, type expected: string but recevied: number. The value received is: 2]`;
+exports[`ResolveEntity.resolveEntity outputType fails if error method catches typeCheck errors and returns bad value 1`] = `
+[Error: Entity type check failed!
+Expected type: string
+Actual type: number
+Input value: 2]
+`;
 
 exports[`ResolveEntity.resolveEntity outputType is bypassed if error throws error 1`] = `[Error: error from before method]`;
 
-exports[`ResolveEntity.resolveEntity outputType throws error if after method returns value that does not pass typeCheck 1`] = `[Error: value did not pass type check, type expected: string but recevied: number. The value received is: 1]`;
+exports[`ResolveEntity.resolveEntity outputType throws error if after method returns value that does not pass typeCheck 1`] = `
+[Error: Entity type check failed!
+Expected type: string
+Actual type: number
+Input value: 1]
+`;
 
-exports[`ResolveEntity.resolveEntity outputType throws error if after middleware returns value that does not pass typeCheck 1`] = `[Error: value did not pass type check, type expected: string but recevied: number. The value received is: 1]`;
+exports[`ResolveEntity.resolveEntity outputType throws error if after middleware returns value that does not pass typeCheck 1`] = `
+[Error: Entity type check failed!
+Expected type: string
+Actual type: number
+Input value: 1]
+`;
 
-exports[`ResolveEntity.resolveEntity outputType throws error if before method returns value that does not pass typeCheck 1`] = `[Error: value did not pass type check, type expected: string but recevied: number. The value received is: 1]`;
+exports[`ResolveEntity.resolveEntity outputType throws error if before method returns value that does not pass typeCheck 1`] = `
+[Error: Entity type check failed!
+Expected type: string
+Actual type: number
+Input value: 1]
+`;
 
 exports[`ResolveEntity.resolveEntity outputType throws error if custom typeCheck fails 1`] = `[Error: custom type error]`;
 
-exports[`ResolveEntity.resolveEntity outputType throws error if global after middleware returns value that does not pass typeCheck 1`] = `[Error: value did not pass type check, type expected: string but recevied: number. The value received is: 1]`;
+exports[`ResolveEntity.resolveEntity outputType throws error if global after middleware returns value that does not pass typeCheck 1`] = `
+[Error: Entity type check failed!
+Expected type: string
+Actual type: number
+Input value: 1]
+`;
 
-exports[`ResolveEntity.resolveEntity outputType throws error if global before middleware returns value that does not pass typeCheck 1`] = `[Error: value did not pass type check, type expected: string but recevied: number. The value received is: 1]`;
+exports[`ResolveEntity.resolveEntity outputType throws error if global before middleware returns value that does not pass typeCheck 1`] = `
+[Error: Entity type check failed!
+Expected type: string
+Actual type: number
+Input value: 1]
+`;
 
-exports[`ResolveEntity.resolveEntity outputType throws error if middleware before returns value that does not pass typeCheck 1`] = `[Error: value did not pass type check, type expected: string but recevied: number. The value received is: 1]`;
+exports[`ResolveEntity.resolveEntity outputType throws error if middleware before returns value that does not pass typeCheck 1`] = `
+[Error: Entity type check failed!
+Expected type: string
+Actual type: number
+Input value: 1]
+`;
 
-exports[`ResolveEntity.resolveEntity outputType throws error if value does not pass typeCheck 1`] = `[Error: value did not pass type check, type expected: string but recevied: number. The value received is: 1]`;
+exports[`ResolveEntity.resolveEntity outputType throws error if value does not pass typeCheck 1`] = `
+[Error: Entity type check failed!
+Expected type: string
+Actual type: number
+Input value: 1]
+`;
 
-exports[`ResolveEntity.resolveEntity outputType throws if error method does not return value with correct type 1`] = `[Error: value did not pass type check, type expected: string but recevied: number. The value received is: 1]`;
+exports[`ResolveEntity.resolveEntity outputType throws if error method does not return value with correct type 1`] = `
+[Error: Entity type check failed!
+Expected type: string
+Actual type: number
+Input value: 1]
+`;
diff --git a/packages/data-point/lib/entity-types/base-entity/factory.test.js b/packages/data-point/lib/entity-types/base-entity/factory.test.js
index 0869930..58c2bce 100644
--- a/packages/data-point/lib/entity-types/base-entity/factory.test.js
+++ b/packages/data-point/lib/entity-types/base-entity/factory.test.js
@@ -2,23 +2,6 @@
 
 const Factory = require('./factory')
 
-const typeCheckFunctionReducers = require('../../helpers/type-check-function-reducers')
-
-describe('Factory.getTypeModifier', () => {
-  test('It should return function reducer it matches any of the predefined types', () => {
-    expect(Factory.getTypeModifier('string')).toEqual(
-      typeCheckFunctionReducers.isString
-    )
-    expect(Factory.getTypeModifier('number')).toEqual(
-      typeCheckFunctionReducers.isNumber
-    )
-  })
-
-  test('It should return the reducer -as-is- if no match', () => {
-    expect(Factory.getTypeModifier('foo:bar')).toEqual('foo:bar')
-  })
-})
-
 describe('Factory.create', () => {
   test('It should create entity defaults', () => {
     function FooEntity () {}
diff --git a/packages/data-point/lib/entity-types/base-entity/resolve.test.js b/packages/data-point/lib/entity-types/base-entity/resolve.test.js
index 48cfc72..dce44c9 100644
--- a/packages/data-point/lib/entity-types/base-entity/resolve.test.js
+++ b/packages/data-point/lib/entity-types/base-entity/resolve.test.js
@@ -146,7 +146,7 @@ describe('ResolveEntity.resolveEntity', () => {
   }
 
   test('It should resolve entity', () => {
-    return resolveEntity('hash:asIs', 'foo').then(acc => {
+    return resolveEntity('model:asIs', 'foo').then(acc => {
       expect(acc).toHaveProperty('value', 'foo')
     })
   })
@@ -165,7 +165,7 @@ describe('ResolveEntity.resolveEntity', () => {
     const consoleTimeEnd = console.timeEnd
     console.time = jest.fn()
     console.timeEnd = jest.fn()
-    return resolveEntity('hash:asIs', 'foo', {
+    return resolveEntity('model:asIs', 'foo', {
       trace: true
     }).then(acc => {
       expect(console.time).toBeCalled()
@@ -178,18 +178,17 @@ describe('ResolveEntity.resolveEntity', () => {
 
   test('It should resolve through bypass', () => {
     dataPoint.middleware.use('hash:before', (acc, next) => {
-      acc.resolve('bar')
+      acc.resolve({ data: 'bar' })
       next(null)
     })
     return resolveEntity('hash:asIs', 'foo').then(acc => {
-      expect(acc).toHaveProperty('value', 'bar')
+      expect(acc.value).toEqual({ data: 'bar' })
     })
   })
 
   test('it should catch errors from middleware', () => {
     dataPoint.middleware.use('hash:before', (acc, next) => {
-      const err = new Error('test')
-      throw err
+      throw new Error('test')
     })
     return resolveEntity('hash:asIs', 'foo')
       .catch(err => err)
@@ -380,7 +379,7 @@ describe('ResolveEntity.resolve', () => {
       const result = utils.set(acc, 'value', 'bar')
       return Promise.resolve(result)
     }
-    return resolve(resolver)('hash:asIs', 'foo').then(acc => {
+    return resolve(resolver)('model:asIs', 'foo').then(acc => {
       expect(acc).toHaveProperty('value', 'bar')
     })
   })
@@ -390,7 +389,7 @@ describe('ResolveEntity.resolve', () => {
       const result = utils.set(acc, 'value', 'bar')
       return Promise.resolve(result)
     }
-    return resolve(resolver)('hash:asIs[]', ['foo']).then(acc => {
+    return resolve(resolver)('model:asIs[]', ['foo']).then(acc => {
       expect(acc).toHaveProperty('value', ['bar'])
     })
   })
@@ -398,13 +397,13 @@ describe('ResolveEntity.resolve', () => {
     const resolver = (acc, resolveReducer) => {
       return Promise.resolve(acc)
     }
-    return resolve(resolver)('hash:asIs[]', {}).then(acc => {
+    return resolve(resolver)('model:asIs[]', {}).then(acc => {
       expect(acc.value).toBeUndefined()
     })
   })
   test('It should not execute resolver if flag hasEmptyConditional is true and value is empty', () => {
     const resolver = jest.fn()
-    return resolve(resolver)('?hash:asIs', undefined).then(acc => {
+    return resolve(resolver)('?model:asIs', undefined).then(acc => {
       expect(resolver).not.toHaveBeenCalled()
     })
   })
@@ -414,7 +413,7 @@ describe('ResolveEntity.resolve', () => {
       const result = utils.set(acc, 'value', 'bar')
       return Promise.resolve(result)
     }
-    return resolve(resolver)('?hash:asIs', 'foo').then(acc => {
+    return resolve(resolver)('?model:asIs', 'foo').then(acc => {
       expect(acc).toHaveProperty('value', 'bar')
     })
   })
@@ -425,7 +424,7 @@ describe('ResolveEntity.resolve', () => {
       const result = utils.set(acc, 'value', count++)
       return Promise.resolve(result)
     }
-    return resolve(resolver)('?hash:asIs[]', [
+    return resolve(resolver)('?model:asIs[]', [
       'a',
       undefined,
       'b',
diff --git a/packages/data-point/lib/entity-types/entity-collection/__snapshots__/resolve.test.js.snap b/packages/data-point/lib/entity-types/entity-collection/__snapshots__/resolve.test.js.snap
index d1d04eb..d6a9b0d 100644
--- a/packages/data-point/lib/entity-types/entity-collection/__snapshots__/resolve.test.js.snap
+++ b/packages/data-point/lib/entity-types/entity-collection/__snapshots__/resolve.test.js.snap
@@ -1,3 +1,17 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`CollectionReducer.resolve entity.collection - throw error if value not array 1`] = `[Error: collection:ObjectsNotAllowed received value = { c: [ 1, 2, 3 ] } of type object, this entity only resolves Array values. More info https://github.com/ViacomInc/data-point/tree/master/packages/data-point#collection-entity]`;
+exports[`Collection entity type checking should execute the default outputType reducer before a custom outputType reducer  1`] = `
+[Error: Entity type check failed!
+Expected type: array
+Actual type: object
+Input value: {}]
+`;
+
+exports[`Collection entity type checking should throw error from a custom outputType reducer 1`] = `[Error: custom type check failed]`;
+
+exports[`Collection entity type checking should throw error from default outputType reducer when output is not valid 1`] = `
+[Error: Entity type check failed!
+Expected type: array
+Actual type: object
+Input value: { c: [ 1, 2, 3 ] }]
+`;
diff --git a/packages/data-point/lib/entity-types/entity-collection/resolve.test.js b/packages/data-point/lib/entity-types/entity-collection/resolve.test.js
index 3e6873c..6894edf 100644
--- a/packages/data-point/lib/entity-types/entity-collection/resolve.test.js
+++ b/packages/data-point/lib/entity-types/entity-collection/resolve.test.js
@@ -33,21 +33,24 @@ beforeEach(() => {
   dataPoint.middleware.clear()
 })
 
-describe('CollectionReducer.resolve', () => {
-  test('entity.collection - only process Plain Objects', () => {
-    return transform('collection:ObjectsNotAllowed', testData)
+describe('Collection entity type checking', () => {
+  function resolveInvalid (entity, data) {
+    return dataPoint
+      .resolve(entity, data)
       .catch(err => err)
       .then(result => {
         expect(result).toBeInstanceOf(Error)
-      })
-  })
-
-  test('entity.collection - throw error if value not array', () => {
-    return transform('collection:ObjectsNotAllowed', testData)
-      .catch(err => err)
-      .then(result => {
         expect(result).toMatchSnapshot()
       })
+  }
+  test('should throw error from default outputType reducer when output is not valid', () => {
+    return resolveInvalid('collection:ObjectsNotAllowed', testData)
+  })
+  test('should throw error from a custom outputType reducer', () => {
+    return resolveInvalid('collection:CustomOutputType', [])
+  })
+  test('should execute the default outputType reducer before a custom outputType reducer ', () => {
+    return resolveInvalid('collection:CustomOutputType', {})
   })
 })
 
diff --git a/packages/data-point/lib/entity-types/entity-hash/__snapshots__/resolve.test.js.snap b/packages/data-point/lib/entity-types/entity-hash/__snapshots__/resolve.test.js.snap
index 3fddbad..5af186a 100644
--- a/packages/data-point/lib/entity-types/entity-hash/__snapshots__/resolve.test.js.snap
+++ b/packages/data-point/lib/entity-types/entity-hash/__snapshots__/resolve.test.js.snap
@@ -1,5 +1,17 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`entity.hash.resolve entity.hash - only process Plain Objects 1`] = `"hash:asIs received value = [ { foo: 1, a: { b: [Object... of type array, this entity only resolves plain Objects. More info https://github.com/ViacomInc/data-point/tree/master/packages/data-point#hash-entity"`;
+exports[`Hash entity type checking should execute the default outputType reducer before a custom outputType reducer  1`] = `
+[Error: Entity type check failed!
+Expected type: object
+Actual type: array
+Input value: []]
+`;
 
-exports[`entity.hash.resolve entity.hash - throw error if value is not object 1`] = `[Error: hash:noValue received value = 'invalid' of type string, this entity only resolves plain Objects. More info https://github.com/ViacomInc/data-point/tree/master/packages/data-point#hash-entity]`;
+exports[`Hash entity type checking should throw error from a custom outputType reducer 1`] = `[Error: custom type check failed]`;
+
+exports[`Hash entity type checking should throw error from default outputType reducer when output is not valid 1`] = `
+[Error: Entity type check failed!
+Expected type: object
+Actual type: array
+Input value: [ { foo: 1, a: { b: [Object...]
+`;
diff --git a/packages/data-point/lib/entity-types/entity-hash/resolve.test.js b/packages/data-point/lib/entity-types/entity-hash/resolve.test.js
index bb9ac1a..30e7337 100644
--- a/packages/data-point/lib/entity-types/entity-hash/resolve.test.js
+++ b/packages/data-point/lib/entity-types/entity-hash/resolve.test.js
@@ -29,24 +29,24 @@ beforeAll(() => {
   resolveReducerBound = helpers.createReducerResolver(dataPoint)
 })
 
-describe('entity.hash.resolve', () => {
-  test('entity.hash - only process Plain Objects', () => {
-    return transform('hash:asIs', [testData])
-      .catch(result => {
-        return result
-      })
+describe('Hash entity type checking', () => {
+  function resolveInvalid (entity, data) {
+    return dataPoint
+      .resolve(entity, data)
+      .catch(err => err)
       .then(result => {
         expect(result).toBeInstanceOf(Error)
-        expect(result.message).toMatchSnapshot()
-      })
-  })
-
-  test('entity.hash - throw error if value is not object', () => {
-    return transform('hash:noValue', null)
-      .catch(e => e)
-      .then(result => {
         expect(result).toMatchSnapshot()
       })
+  }
+  test('should throw error from default outputType reducer when output is not valid', () => {
+    return resolveInvalid('hash:asIs', [testData])
+  })
+  test('should throw error from a custom outputType reducer', () => {
+    return resolveInvalid('hash:CustomOutputType', {})
+  })
+  test('should execute the default outputType reducer before a custom outputType reducer ', () => {
+    return resolveInvalid('hash:CustomOutputType', [])
   })
 })
 
diff --git a/packages/data-point/lib/helpers/__snapshots__/type-check-function-reducers.test.js.snap b/packages/data-point/lib/helpers/__snapshots__/type-check-function-reducers.test.js.snap
deleted file mode 100644
index 30c16da..0000000
--- a/packages/data-point/lib/helpers/__snapshots__/type-check-function-reducers.test.js.snap
+++ /dev/null
@@ -1,17 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`isArray It should throw error when not matched 1`] = `"value did not pass type check, type expected: Array but recevied: string. The value received is: 'foo'"`;
-
-exports[`isBoolean It should throw error when not matched 1`] = `"value did not pass type check, type expected: boolean but recevied: string. The value received is: 'foo'"`;
-
-exports[`isBoolean It should throw error when not matched 2`] = `"value did not pass type check, type expected: boolean but recevied: string. The value received is: 'foo'"`;
-
-exports[`isError It should throw error when not matched 1`] = `"value did not pass type check, type expected: function but recevied: string. The value received is: 'foo'"`;
-
-exports[`isFunction It should throw error when not matched 1`] = `"value did not pass type check, type expected: function but recevied: string. The value received is: 'foo'"`;
-
-exports[`isNumber It should throw error when not matched 1`] = `"value did not pass type check, type expected: number but recevied: string. The value received is: 'foo'"`;
-
-exports[`isObject It should throw error when not matched 1`] = `"value did not pass type check, type expected: Object but recevied: array. The value received is: []"`;
-
-exports[`isString It should throw error when not matched 1`] = `"value did not pass type check, type expected: string but recevied: number. The value received is: 1"`;
diff --git a/packages/data-point/lib/helpers/__snapshots__/type-check-functions.test.js.snap b/packages/data-point/lib/helpers/__snapshots__/type-check-functions.test.js.snap
new file mode 100644
index 0000000..f8d7bc1
--- /dev/null
+++ b/packages/data-point/lib/helpers/__snapshots__/type-check-functions.test.js.snap
@@ -0,0 +1,57 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`isArray It should throw error when not matched 1`] = `
+"Entity type check failed!
+Expected type: array
+Actual type: string
+Input value: 'foo'"
+`;
+
+exports[`isBoolean It should throw error when not matched 1`] = `
+"Entity type check failed!
+Expected type: boolean
+Actual type: string
+Input value: 'foo'"
+`;
+
+exports[`isBoolean It should throw error when not matched 2`] = `
+"Entity type check failed!
+Expected type: boolean
+Actual type: string
+Input value: 'foo'"
+`;
+
+exports[`isError It should throw error when not matched 1`] = `
+"Entity type check failed!
+Expected type: function
+Actual type: string
+Input value: 'foo'"
+`;
+
+exports[`isFunction It should throw error when not matched 1`] = `
+"Entity type check failed!
+Expected type: function
+Actual type: string
+Input value: 'foo'"
+`;
+
+exports[`isNumber It should throw error when not matched 1`] = `
+"Entity type check failed!
+Expected type: number
+Actual type: string
+Input value: 'foo'"
+`;
+
+exports[`isObject It should throw error when not matched 1`] = `
+"Entity type check failed!
+Expected type: object
+Actual type: array
+Input value: []"
+`;
+
+exports[`isString It should throw error when not matched 1`] = `
+"Entity type check failed!
+Expected type: string
+Actual type: number
+Input value: 1"
+`;
diff --git a/packages/data-point/lib/helpers/__snapshots__/type-check-helpers.test.js.snap b/packages/data-point/lib/helpers/__snapshots__/type-check-helpers.test.js.snap
new file mode 100644
index 0000000..3d1fae7
--- /dev/null
+++ b/packages/data-point/lib/helpers/__snapshots__/type-check-helpers.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`type-check-helpers#getTypeCheckSourceWithDefault should throw error for invalid specType 1`] = `"hash entities do not support the \\"number\\" outputType!"`;
diff --git a/packages/data-point/lib/helpers/type-check-function-reducers.test.js b/packages/data-point/lib/helpers/type-check-functions.test.js
similarity index 95%
rename from packages/data-point/lib/helpers/type-check-function-reducers.test.js
rename to packages/data-point/lib/helpers/type-check-functions.test.js
index d6816ff..d354df2 100644
--- a/packages/data-point/lib/helpers/type-check-function-reducers.test.js
+++ b/packages/data-point/lib/helpers/type-check-functions.test.js
@@ -8,7 +8,7 @@ const {
   isError,
   isArray,
   isObject
-} = require('./type-check-function-reducers')
+} = require('./type-check-functions')
 
 function testTypeChecker (typeCheck, goodValue, badValue, type) {
   test('It should return true if type match', () => {
diff --git a/packages/data-point/lib/helpers/type-check-helpers.test.js b/packages/data-point/lib/helpers/type-check-helpers.test.js
new file mode 100644
index 0000000..fc14138
--- /dev/null
+++ b/packages/data-point/lib/helpers/type-check-helpers.test.js
@@ -0,0 +1,53 @@
+/* eslint-env jest */
+
+const helpers = require('./type-check-helpers')
+const typeCheckFunctions = require('./type-check-functions')
+
+describe('type-check-helpers#normalizeTypeCheckSource', () => {
+  test('It should return function reducer it matches any of the predefined types', () => {
+    expect(helpers.normalizeTypeCheckSource('string')).toEqual(
+      typeCheckFunctions.isString
+    )
+    expect(helpers.normalizeTypeCheckSource('number')).toEqual(
+      typeCheckFunctions.isNumber
+    )
+  })
+
+  test('It should return the reducer -as-is- if no match', () => {
+    expect(helpers.normalizeTypeCheckSource('foo:bar')).toEqual('foo:bar')
+  })
+
+  test('It should process arrays that include some predefined types', () => {
+    expect(
+      helpers.normalizeTypeCheckSource(['string', 'foo:bar', 'number'])
+    ).toEqual([
+      typeCheckFunctions.isString,
+      'foo:bar',
+      typeCheckFunctions.isNumber
+    ])
+  })
+})
+
+describe('type-check-helpers#getTypeCheckSourceWithDefault', () => {
+  test('should return defaultType when no specType is provided', () => {
+    expect(helpers.getTypeCheckSourceWithDefault('hash', 'string')).toBe(
+      'string'
+    )
+  })
+  test('should return defaultType when specType === defaultType', () => {
+    expect(
+      helpers.getTypeCheckSourceWithDefault('hash', 'string', 'string')
+    ).toBe('string')
+  })
+  test('should throw error for invalid specType', () => {
+    expect(() =>
+      helpers.getTypeCheckSourceWithDefault('hash', 'string', 'number')
+    ).toThrowErrorMatchingSnapshot()
+  })
+  test('should return array when specType is valid and !== defaultType', () => {
+    const customTypeCheck = () => true
+    expect(
+      helpers.getTypeCheckSourceWithDefault('hash', 'string', customTypeCheck)
+    ).toEqual(['string', customTypeCheck])
+  })
+})
diff --git a/packages/data-point/test/definitions/collection.js b/packages/data-point/test/definitions/collection.js
index e2687e3..ad14e9d 100644
--- a/packages/data-point/test/definitions/collection.js
+++ b/packages/data-point/test/definitions/collection.js
@@ -9,6 +9,12 @@ module.exports = {
   'collection:ObjectsNotAllowed': {
     value: '$a.b'
   },
+  'collection:CustomOutputType': {
+    value: input => input,
+    outputType: input => {
+      throw new Error('custom type check failed')
+    }
+  },
   'collection:a.1': {
     value: '$a.d'
   },
diff --git a/packages/data-point/test/definitions/hash.js b/packages/data-point/test/definitions/hash.js
index 383bb9a..213a01a 100644
--- a/packages/data-point/test/definitions/hash.js
+++ b/packages/data-point/test/definitions/hash.js
@@ -12,6 +12,12 @@ module.exports = {
   'hash:asIs': {
     value: '$'
   },
+  'hash:CustomOutputType': {
+    value: input => input,
+    outputType: input => {
+      throw new Error('custom type check failed')
+    }
+  },
   'hash:a.1': {
     value: '$a.h'
   },
diff --git a/packages/data-point/test/definitions/model.js b/packages/data-point/test/definitions/model.js
index df32d48..8fc602c 100644
--- a/packages/data-point/test/definitions/model.js
+++ b/packages/data-point/test/definitions/model.js
@@ -1,4 +1,7 @@
 module.exports = {
+  'model:asIs': {
+    value: '$'
+  },
   'model:lifecycles': {
     inputType: 'array',
     before: (input, context) => context.locals.before(input),