Repository: ViacomInc/data-point (js)

Issue:
Validate ajv schemas
**Problem description:**

Schema entities do not validate the ajv schemas they're given.

**Suggested solution:**

When schema entities are created, they should call ajv's [validateSchema](https://github.com/epoberezkin/ajv#validateschemaobject-schema---boolean) method and throw with any errors that are generated.


Pull request description:
feat(data-point/entity-schema): Throw errors for invalid schemas
<!-- What changes are being made? (What feature/bug is being fixed here?) -->
**What**: Closes #75

<!-- Why are these changes necessary? -->
**Why**: Validates schemas that are passed to `EntitySchema#factory`, so we do not get schema validation errors at runtime.

<!-- How were these changes implemented? -->
**How**: Using `ajv.validateSchema`.

<!-- Have you done all of these things?  -->
**Checklist**:
<!-- add "N/A" to the end of each line that's irrelevant to your changes -->
<!-- to check an item, place an "x" in the box like so: "- [x] Documentation" -->
- [x] Documentation
- [x] Tests
- [x] Ready to be merged <!-- In your opinion, is this ready to be merged as soon as it's reviewed? -->

Patch:
diff --git a/packages/data-point/README.md b/packages/data-point/README.md
index 8f6e3a0..6020728 100644
--- a/packages/data-point/README.md
+++ b/packages/data-point/README.md
@@ -2662,7 +2662,7 @@ dataPoint.addEntities({
 | Key | Type | Description |
 |:---|:---|:---|
 | *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 |
+| *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
 | *params* | `Object` | User-defined Hash that will be passed to every transform within the context of the transform's execution |
 | *before* | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution |
diff --git a/packages/data-point/lib/entity-types/entity-schema/factory.js b/packages/data-point/lib/entity-types/entity-schema/factory.js
index 1804d96..6093361 100644
--- a/packages/data-point/lib/entity-types/entity-schema/factory.js
+++ b/packages/data-point/lib/entity-types/entity-schema/factory.js
@@ -1,5 +1,7 @@
+const Ajv = require('ajv')
 const _ = require('lodash')
 const deepFreeze = require('deep-freeze')
+
 const createBaseEntity = require('../base-entity').create
 
 /**
@@ -12,16 +14,41 @@ function EntitySchema () {
 
 module.exports.EntitySchema = EntitySchema
 
+/**
+ * @param {Object} schema
+ * @param {Object} options
+ * @throws if schema is not a valid ajv schema
+ * @return {boolean}
+ */
+function validateSchema (schema, options) {
+  const ajv = new Ajv(options)
+  ajv.validateSchema(schema)
+  if (ajv.errors) {
+    const msg = `Schema validation failed with the following errors:\n${JSON.stringify(
+      ajv.errors,
+      null,
+      2
+    )}`
+    throw new Error(msg)
+  }
+
+  return true
+}
+
+module.exports.validateSchema = validateSchema
+
 /**
  * Creates new Entity Object
- * @param  {Object} spec - spec
+ * @param {Object} spec - spec
  * @param {string} id - Entity id
+ * @throws if spec.schema is not a valid ajv schema
  * @return {EntitySchema} Entity Object
  */
 function create (spec, id) {
   const entity = createBaseEntity(EntitySchema, spec, id)
   entity.schema = deepFreeze(_.defaultTo(spec.schema, {}))
   entity.options = deepFreeze(_.defaultTo(spec.options, {}))
+  validateSchema(entity.schema, entity.options)
 
   return Object.freeze(entity)
 }


Test patch:
diff --git a/packages/data-point/lib/entity-types/entity-schema/__snapshots__/factory.test.js.snap b/packages/data-point/lib/entity-types/entity-schema/__snapshots__/factory.test.js.snap
new file mode 100644
index 0000000..1e5a33d
--- /dev/null
+++ b/packages/data-point/lib/entity-types/entity-schema/__snapshots__/factory.test.js.snap
@@ -0,0 +1,94 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Factory#create 1`] = `
+"Schema validation failed with the following errors:
+[
+  {
+    \\"keyword\\": \\"enum\\",
+    \\"dataPath\\": \\".type\\",
+    \\"schemaPath\\": \\"#/definitions/simpleTypes/enum\\",
+    \\"params\\": {
+      \\"allowedValues\\": [
+        \\"array\\",
+        \\"boolean\\",
+        \\"integer\\",
+        \\"null\\",
+        \\"number\\",
+        \\"object\\",
+        \\"string\\"
+      ]
+    },
+    \\"message\\": \\"should be equal to one of the allowed values\\"
+  },
+  {
+    \\"keyword\\": \\"type\\",
+    \\"dataPath\\": \\".type\\",
+    \\"schemaPath\\": \\"#/properties/type/anyOf/1/type\\",
+    \\"params\\": {
+      \\"type\\": \\"array\\"
+    },
+    \\"message\\": \\"should be array\\"
+  },
+  {
+    \\"keyword\\": \\"anyOf\\",
+    \\"dataPath\\": \\".type\\",
+    \\"schemaPath\\": \\"#/properties/type/anyOf\\",
+    \\"params\\": {},
+    \\"message\\": \\"should match some schema in anyOf\\"
+  }
+]"
+`;
+
+exports[`Factory#validateSchema 1`] = `
+"Schema validation failed with the following errors:
+[
+  {
+    \\"keyword\\": \\"type\\",
+    \\"dataPath\\": \\"\\",
+    \\"schemaPath\\": \\"#/type\\",
+    \\"params\\": {
+      \\"type\\": \\"object,boolean\\"
+    },
+    \\"message\\": \\"should be object,boolean\\"
+  }
+]"
+`;
+
+exports[`Factory#validateSchema 2`] = `
+"Schema validation failed with the following errors:
+[
+  {
+    \\"keyword\\": \\"enum\\",
+    \\"dataPath\\": \\".type\\",
+    \\"schemaPath\\": \\"#/definitions/simpleTypes/enum\\",
+    \\"params\\": {
+      \\"allowedValues\\": [
+        \\"array\\",
+        \\"boolean\\",
+        \\"integer\\",
+        \\"null\\",
+        \\"number\\",
+        \\"object\\",
+        \\"string\\"
+      ]
+    },
+    \\"message\\": \\"should be equal to one of the allowed values\\"
+  },
+  {
+    \\"keyword\\": \\"type\\",
+    \\"dataPath\\": \\".type\\",
+    \\"schemaPath\\": \\"#/properties/type/anyOf/1/type\\",
+    \\"params\\": {
+      \\"type\\": \\"array\\"
+    },
+    \\"message\\": \\"should be array\\"
+  },
+  {
+    \\"keyword\\": \\"anyOf\\",
+    \\"dataPath\\": \\".type\\",
+    \\"schemaPath\\": \\"#/properties/type/anyOf\\",
+    \\"params\\": {},
+    \\"message\\": \\"should match some schema in anyOf\\"
+  }
+]"
+`;
diff --git a/packages/data-point/lib/entity-types/entity-schema/factory.test.js b/packages/data-point/lib/entity-types/entity-schema/factory.test.js
index 64ca17f..e16cd7e 100644
--- a/packages/data-point/lib/entity-types/entity-schema/factory.test.js
+++ b/packages/data-point/lib/entity-types/entity-schema/factory.test.js
@@ -21,4 +21,18 @@ test('Factory#create', () => {
   expect(obj).not.toHaveProperty('after')
   expect(obj).not.toHaveProperty('error')
   expect(obj).toHaveProperty('params')
+
+  expect(() =>
+    Factory.create({
+      schema: { type: null }
+    })
+  ).toThrowErrorMatchingSnapshot()
+})
+
+test('Factory#validateSchema', () => {
+  expect(Factory.validateSchema({}, {})).toBe(true)
+  expect(() => Factory.validateSchema(42, {})).toThrowErrorMatchingSnapshot()
+  expect(() =>
+    Factory.validateSchema({ type: null }, {})
+  ).toThrowErrorMatchingSnapshot()
 })