Merge fx-team to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 12 Jul 2016 16:24:27 -0700
changeset 344737 04821a70c739a00d12e12df651c0989441e22728
parent 344713 53947432322b06f80b079fef73e43d7e6785f81b (current diff)
parent 344736 7ebc6c9a418eeafa7fbb22fce71707e99615db33 (diff)
child 344744 adc385472f0e099bf37d88bdfb9c8f87085e01ed
child 344795 0d7a788f1c40adf5038505e25e600d8551dbda06
child 344831 5430e0911c9f97e6efd52dfb75ea0c3a64228452
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.0a1
first release with
nightly linux32
04821a70c739 / 50.0a1 / 20160713030216 / files
nightly linux64
04821a70c739 / 50.0a1 / 20160713030216 / files
nightly mac
04821a70c739 / 50.0a1 / 20160713030204 / files
nightly win32
04821a70c739 / 50.0a1 / 20160713030216 / files
nightly win64
04821a70c739 / 50.0a1 / 20160713030216 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to central, a=merge
--- a/addon-sdk/source/lib/sdk/event/dom.js
+++ b/addon-sdk/source/lib/sdk/event/dom.js
@@ -7,20 +7,21 @@
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Ci } = require("chrome");
 
 var { emit } = require("./core");
 var { when: unload } = require("../system/unload");
-var listeners = new Map();
+var listeners = new WeakMap();
 
 const { Cu } = require("chrome");
 const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
+const { ThreadSafeChromeUtils } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 var getWindowFrom = x =>
                     x instanceof Ci.nsIDOMWindow ? x :
                     x instanceof Ci.nsIDOMDocument ? x.defaultView :
                     x instanceof Ci.nsIDOMNode ? x.ownerDocument.defaultView :
                     null;
 
 function removeFromListeners() {
@@ -64,13 +65,14 @@ function open(target, type, options) {
 
   cleaners.push(() => ShimWaiver.getProperty(target, "removeEventListener")(type, listener, capture));
   ShimWaiver.getProperty(target, "addEventListener")(type, listener, capture);
 
   return output;
 }
 
 unload(() => {
-  for (let window of listeners.keys())
+  let keys = ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(listeners)
+  for (let window of keys)
     removeFromListeners.call(window);
 });
 
 exports.open = open;
--- a/browser/themes/linux/preferences/in-content/dialog.css
+++ b/browser/themes/linux/preferences/in-content/dialog.css
@@ -6,8 +6,14 @@
 
 label:not(.menu-text),
 textbox,
 description,
 .tab-text,
 caption > label {
   font-size: 1.2em;
 }
+
+/* Create a separate rule to unset these styles on .tree-input instead of
+   using :not(.tree-input) so the selector specifity doesn't change. */
+textbox.tree-input {
+  font-size: unset;
+}
--- a/browser/themes/osx/preferences/in-content/dialog.css
+++ b/browser/themes/osx/preferences/in-content/dialog.css
@@ -12,16 +12,22 @@ prefwindow,
 label:not(.menu-text),
 textbox,
 description,
 .tab-text,
 caption > label {
   font-size: 1.3em;
 }
 
+/* Create a separate rule to unset these styles on .tree-input instead of
+   using :not(.tree-input) so the selector specifity doesn't change. */
+textbox.tree-input {
+  font-size: unset;
+}
+
 button {
   font-size: 1em;
 }
 
 caption {
   font: message-box;
 }
 
--- a/browser/themes/windows/preferences/in-content/dialog.css
+++ b/browser/themes/windows/preferences/in-content/dialog.css
@@ -6,8 +6,14 @@
 
 label:not(.menu-text),
 textbox,
 description,
 .tab-text,
 caption > label {
   font-size: 1.2em;
 }
+
+/* Create a separate rule to unset these styles on .tree-input instead of
+   using :not(.tree-input) so the selector specifity doesn't change. */
+textbox.tree-input {
+  font-size: unset;
+}
--- a/devtools/client/dom/content/reducers/grips.js
+++ b/devtools/client/dom/content/reducers/grips.js
@@ -43,24 +43,31 @@ function onRequestProperties(state, acti
 }
 
 /**
  * Handle receiveProperties action
  */
 function onReceiveProperties(cache, action) {
   let response = action.response;
   let from = response.from;
+  let className = action.grip.class;
 
   // Properly deal with getters.
   mergeProperties(response);
 
   // Compute list of requested children.
   let previewProps = response.preview ? response.preview.ownProperties : null;
   let ownProps = response.ownProperties || previewProps || [];
+
   let props = Object.keys(ownProps).map(key => {
+    // Array indexes as a special case. We convert any keys that are string
+    // representations of integers to integers.
+    if (className === "Array" && isInteger(key)) {
+      key = parseInt(key, 10);
+    }
     return new Property(key, ownProps[key], key);
   });
 
   props.sort(sortName);
 
   // Return new state/map.
   let newCache = new Map(cache);
   newCache.set(from, props);
@@ -95,16 +102,21 @@ function sortName(a, b) {
     return 1;
   }
   if (a.value.enumerable && !b.value.enumerable) {
     return -1;
   }
   return a.name > b.name ? 1 : -1;
 }
 
+function isInteger(n) {
+  // We use parseInt(n, 10) == n to disregard scientific notation e.g. "3e24"
+  return isFinite(n) && parseInt(n, 10) == n;
+}
+
 function Property(name, value, key) {
   this.name = name;
   this.value = value;
   this.key = key;
 }
 
 // Exports from this module
 exports.grips = grips;
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -885,17 +885,17 @@ InspectorPanel.prototype = {
       type: "separator",
     }));
 
     let isNodeWithChildren = this.selection.isNode() &&
                              markupContainer.hasChildren;
     menu.append(new MenuItem({
       id: "node-menu-expand",
       label: strings.GetStringFromName("inspectorExpandNode.label"),
-      disabled: !isNodeWithChildren || markupContainer.expanded,
+      disabled: !isNodeWithChildren,
       click: () => this.expandNode(),
     }));
     menu.append(new MenuItem({
       id: "node-menu-collapse",
       label: strings.GetStringFromName("inspectorCollapseNode.label"),
       disabled: !isNodeWithChildren || !markupContainer.expanded,
       click: () => this.collapseNode(),
     }));
--- a/devtools/client/inspector/test/browser_inspector_expand-collapse.js
+++ b/devtools/client/inspector/test/browser_inspector_expand-collapse.js
@@ -46,16 +46,19 @@ add_task(function* () {
 
   info("Testing whether collapse works properly");
   info("Simulating context menu click on the selected node container.");
   allMenuItems = openContextMenuAndGetAllItems(inspector, {
     target: getContainerForNodeFront(front, inspector).tagLine,
   });
   nodeMenuCollapseElement =
     allMenuItems.find(item => item.id === "node-menu-collapse");
+  nodeMenuExpandElement =
+    allMenuItems.find(item => item.id === "node-menu-expand");
 
   ok(!nodeMenuCollapseElement.disabled, "Collapse option is enabled");
+  ok(!nodeMenuExpandElement.disabled, "ExpandAll option is enabled");
   nodeMenuCollapseElement.click();
 
   info("Waiting for collapse to occur");
   yield waitForMultipleChildrenUpdates(inspector);
   ok(!markUpContainer.expanded, "node has been successfully collapsed");
 });
--- a/devtools/client/locales/en-US/aboutdebugging.properties
+++ b/devtools/client/locales/en-US/aboutdebugging.properties
@@ -1,35 +1,91 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+# LOCALIZATION NOTE (debug):
+# This string is displayed as a label of the button that starts
+# debugging a service worker.
 debug = Debug
+
+# LOCALIZATION NOTE (push):
+# This string is displayed as a label of the button that pushes a test payload
+# to a service worker.
 push = Push
+
+# LOCALIZATION NOTE (start):
+# This string is displayed as a label of the button that starts a service worker.
 start = Start
 
 scope = Scope
 unregister = unregister
 
 pushService = Push Service
 
+# LOCALIZATION NOTE (addons):
+# This string is displayed as a header of the about:debugging#addons page.
 addons = Add-ons
+
+# LOCALIZATION NOTE (addonDebugging.label):
+# This string is displayed next to a check box that enables the user to switch
+# addon debugging on/off.
 addonDebugging.label = Enable add-on debugging
+
+# LOCALIZATION NOTE (addonDebugging.tooltip):
+# This string is displayed in a tooltip that appears when hovering over a check
+# box that switches addon debugging on/off.
 addonDebugging.tooltip = Turning this on will allow you to debug add-ons and various other parts of the browser chrome
+
+# LOCALIZATION NOTE (moreInfo):
+# This string is displayed next to addonDebugging.label as a link to a page
+# with documentation.
 moreInfo = more info
+
+# LOCALIZATION NOTE (loadTemporaryAddon):
+# This string is displayed as a label of a button that allows the user to
+# load additional add-ons.
 loadTemporaryAddon = Load Temporary Add-on
+
+# LOCALIZATION NOTE (extensions):
+# This string is displayed as a header above the list of loaded add-ons.
 extensions = Extensions
+
+# LOCALIZATION NOTE (selectAddonFromFile2):
+# This string is displayed as the title of the file picker that appears when
+# the user clicks the 'Load Temporary Add-on' button
 selectAddonFromFile2 = Select Manifest File or Package (.xpi)
+
+# LOCALIZATION NOTE (reload):
+# This string is displayed as a label of the button that reloads a given addon.
 reload = Reload
+
+# LOCALIZATION NOTE (reloadDisabledTooltip):
+# This string is displayed in a tooltip that appears when hovering over a
+# disabled 'reload' button.
 reloadDisabledTooltip = Only temporarily installed add-ons can be reloaded
 
+# LOCALIZATION NOTE (workers):
+# This string is displayed as a header of the about:debugging#workers page.
 workers = Workers
+
 serviceWorkers = Service Workers
 sharedWorkers = Shared Workers
 otherWorkers = Other Workers
 
+# LOCALIZATION NOTE (tabs):
+# This string is displayed as a header of the about:debugging#tabs page.
 tabs = Tabs
 
+# LOCALIZATION NOTE (pageNotFound):
+# This string is displayed as the main message at any error/invalid page.
 pageNotFound = Page not found
+
+# LOCALIZATION NOTE (doesNotExist):
+# This string is displayed as an error message when navigating to an invalid page
+# %S will be replaced by the name of the page at run-time.
 doesNotExist = #%S does not exist!
 
+# LOCALIZATION NOTE (nothing):
+# This string is displayed when the list of workers is empty.
 nothing = Nothing yet.
+
 configurationIsNotCompatible = Your browser configuration is not compatible with Service Workers
--- a/devtools/client/performance/performance-view.js
+++ b/devtools/client/performance/performance-view.js
@@ -113,16 +113,24 @@ var PerformanceView = {
     yield DetailsView.destroy();
   }),
 
   /**
    * Sets the state of the profiler view. Possible options are "unavailable",
    * "empty", "recording", "console-recording", "recorded".
    */
   setState: function (state) {
+    // Make sure that the focus isn't captured on a hidden iframe. This fixes a
+    // XUL bug where shortcuts stop working.
+    const iframes = window.document.querySelectorAll('iframe');
+    for (let iframe of iframes) {
+      iframe.blur();
+    }
+    window.focus();
+
     let viewConfig = this.states[state];
     if (!viewConfig) {
       throw new Error(`Invalid state for PerformanceView: ${state}`);
     }
     for (let { sel, opt, val } of viewConfig) {
       for (let el of $$(sel)) {
         el[opt] = val();
       }
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -291,16 +291,19 @@ pref("devtools.webconsole.timestampMessa
 
 // Web Console automatic multiline mode: |true| if you want incomplete statements
 // to automatically trigger multiline editing (equivalent to shift + enter).
 pref("devtools.webconsole.autoMultiline", true);
 
 // Enable the experimental webconsole frontend (work in progress)
 pref("devtools.webconsole.new-frontend-enabled", false);
 
+// The number of lines that are displayed in the web console.
+pref("devtools.hud.loglimit", 1000);
+
 // The number of lines that are displayed in the web console for the Net,
 // CSS, JS and Web Developer categories. These defaults should be kept in sync
 // with DEFAULT_LOG_LIMIT in the webconsole frontend.
 pref("devtools.hud.loglimit.network", 1000);
 pref("devtools.hud.loglimit.cssparser", 1000);
 pref("devtools.hud.loglimit.exception", 1000);
 pref("devtools.hud.loglimit.console", 1000);
 
--- a/devtools/client/shared/components/reps/array.js
+++ b/devtools/client/shared/components/reps/array.js
@@ -57,19 +57,23 @@ define(function (require, exports, modul
             delim: delim,
             key: i
           }));
         }
       }
 
       if (array.length > max + 1) {
         items.pop();
+
+        let objectLink = this.props.objectLink || DOM.span;
         items.push(Caption({
           key: "more",
-          object: "more...",
+          object: objectLink({
+            object: this.props.object
+          }, "more...")
         }));
       }
 
       return items;
     },
 
     /**
      * Returns true if the passed object is an array with additional (custom)
@@ -124,39 +128,32 @@ define(function (require, exports, modul
 
       if (mode == "tiny") {
         items = DOM.span({className: "length"}, object.length);
       } else {
         let max = (mode == "short") ? 3 : 300;
         items = this.arrayIterator(object, max);
       }
 
+      let objectLink = this.props.objectLink || DOM.span;
+
       return (
         ObjectBox({
-          className: "array",
-          onClick: this.onToggleProperties},
-          DOM.a({
-            className: "objectLink",
-            onclick: this.onClickBracket},
-            DOM.span({
-              className: "arrayLeftBracket",
-              role: "presentation"},
-              "["
-            )
-          ),
+          className: "array"},
+          objectLink({
+            className: "arrayLeftBracket",
+            role: "presentation",
+            object: object
+          }, "["),
           items,
-          DOM.a({
-            className: "objectLink",
-            onclick: this.onClickBracket},
-            DOM.span({
-              className: "arrayRightBracket",
-              role: "presentation"},
-              "]"
-            )
-          ),
+          objectLink({
+            className: "arrayRightBracket",
+            role: "presentation",
+            object: object
+          }, "]"),
           DOM.span({
             className: "arrayProperties",
             role: "group"}
           )
         )
       );
     },
   });
--- a/devtools/client/shared/components/reps/date-time.js
+++ b/devtools/client/shared/components/reps/date-time.js
@@ -7,41 +7,47 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Used to render JS built-in Date() object.
    */
   let DateTime = React.createClass({
     displayName: "Date",
 
     propTypes: {
       object: React.PropTypes.object.isRequired
     },
 
     getTitle: function (grip) {
-      return new Date(grip.preview.timestamp).toISOString();
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          object: grip
+        }, grip.class);
+      }
+      return "";
     },
 
     render: function () {
       let grip = this.props.object;
       return (
-        ObjectLink({className: "Date"},
-          span({className: "objectTitle"},
-            this.getTitle(grip)
+        ObjectBox({},
+          this.getTitle(grip),
+          span({className: "Date"},
+            new Date(grip.preview.timestamp).toISOString()
           )
         )
       );
     },
   });
 
   // Registration
 
--- a/devtools/client/shared/components/reps/document.js
+++ b/devtools/client/shared/components/reps/document.js
@@ -28,29 +28,37 @@ define(function (require, exports, modul
       object: React.PropTypes.object.isRequired
     },
 
     getLocation: function (grip) {
       let location = grip.preview.location;
       return location ? getFileName(location) : "";
     },
 
-    getTitle: function (win, context) {
-      return "document";
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return ObjectBox({},
+          this.props.objectLink({
+            object: grip
+          }, grip.class)
+        );
+      }
+      return "";
     },
 
     getTooltip: function (doc) {
       return doc.location.href;
     },
 
     render: function () {
       let grip = this.props.object;
 
       return (
         ObjectBox({className: "object"},
+          this.getTitle(grip),
           span({className: "objectPropValue"},
             this.getLocation(grip)
           )
         )
       );
     },
   });
 
--- a/devtools/client/shared/components/reps/event.js
+++ b/devtools/client/shared/components/reps/event.js
@@ -7,30 +7,39 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
 
   /**
    * Renders DOM event objects.
    */
   let Event = React.createClass({
     displayName: "event",
 
     propTypes: {
       object: React.PropTypes.object.isRequired
     },
 
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          object: grip
+        }, grip.preview.type);
+      }
+      return grip.preview.type;
+    },
+
     summarizeEvent: function (grip) {
-      let info = [grip.preview.type, " "];
+      let info = [];
 
       let eventFamily = grip.class;
       let props = grip.preview.properties;
 
       if (eventFamily == "MouseEvent") {
         info.push("clientX=", props.clientX, ", clientY=", props.clientY);
       } else if (eventFamily == "KeyboardEvent") {
         info.push("charCode=", props.charCode, ", keyCode=", props.keyCode);
@@ -39,17 +48,18 @@ define(function (require, exports, modul
       }
 
       return info.join("");
     },
 
     render: function () {
       let grip = this.props.object;
       return (
-        ObjectLink({className: "event"},
+        ObjectBox({className: "event"},
+          this.getTitle(grip),
           this.summarizeEvent(grip)
         )
       );
     },
   });
 
   // Registration
 
--- a/devtools/client/shared/components/reps/function.js
+++ b/devtools/client/shared/components/reps/function.js
@@ -7,38 +7,48 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip, cropString } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
 
   /**
    * This component represents a template for Function objects.
    */
   let Func = React.createClass({
     displayName: "Func",
 
     propTypes: {
       object: React.PropTypes.object.isRequired
     },
 
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          object: grip
+        }, "function");
+      }
+      return "";
+    },
+
     summarizeFunction: function (grip) {
       let name = grip.displayName || grip.name || "function";
       return cropString(name + "()", 100);
     },
 
     render: function () {
       let grip = this.props.object;
 
       return (
-        ObjectLink({className: "function"},
+        ObjectBox({className: "function"},
+          this.getTitle(grip),
           this.summarizeFunction(grip)
         )
       );
     },
   });
 
   // Registration
 
--- a/devtools/client/shared/components/reps/grip-array.js
+++ b/devtools/client/shared/components/reps/grip-array.js
@@ -10,17 +10,17 @@
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
   const { createFactories, isGrip } = require("./rep-utils");
   const { ObjectBox } = createFactories(require("./object-box"));
   const { Caption } = createFactories(require("./caption"));
 
   // Shortcuts
-  const { a, span } = React.DOM;
+  const { span } = React.DOM;
 
   /**
    * Renders an array. The array is enclosed by left and right bracket
    * and the max number of rendered items depends on the current mode.
    */
   let GripArray = React.createClass({
     displayName: "GripArray",
 
@@ -30,17 +30,22 @@ define(function (require, exports, modul
       provider: React.PropTypes.object,
     },
 
     getLength: function (grip) {
       return grip.preview ? grip.preview.length : 0;
     },
 
     getTitle: function (object, context) {
-      return "[" + object.length + "]";
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          object: object
+        }, object.class);
+      }
+      return "";
     },
 
     arrayIterator: function (grip, max) {
       let items = [];
 
       if (!grip.preview || !grip.preview.length) {
         return items;
       }
@@ -79,20 +84,23 @@ define(function (require, exports, modul
             delim: delim,
             key: i}
           )));
         }
       }
 
       if (array.length > max + 1) {
         items.pop();
+        let objectLink = this.props.objectLink || span;
         items.push(Caption({
           key: "more",
-          object: "more..."}
-        ));
+          object: objectLink({
+            object: this.props.object
+          }, "more...")
+        }));
       }
 
       return items;
     },
 
     render: function () {
       let mode = this.props.mode || "short";
       let object = this.props.object;
@@ -101,33 +109,33 @@ define(function (require, exports, modul
 
       if (mode == "tiny") {
         items = span({className: "length"}, this.getLength(object));
       } else {
         let max = (mode == "short") ? 3 : 300;
         items = this.arrayIterator(object, max);
       }
 
+      let objectLink = this.props.objectLink || span;
+
       return (
-        ObjectBox({className: "array"},
-          a({className: "objectLink"},
-            span({
-              className: "arrayLeftBracket",
-              role: "presentation"},
-              "["
-            )
-          ),
+        ObjectBox({
+          className: "array"},
+          this.getTitle(object),
+          objectLink({
+            className: "arrayLeftBracket",
+            role: "presentation",
+            object: object
+          }, "["),
           items,
-          a({className: "objectLink"},
-            span({
-              className: "arrayRightBracket",
-              role: "presentation"},
-              "]"
-            )
-          ),
+          objectLink({
+            className: "arrayRightBracket",
+            role: "presentation",
+            object: object
+          }, "]"),
           span({
             className: "arrayProperties",
             role: "group"}
           )
         )
       );
     },
   });
--- a/devtools/client/shared/components/reps/grip.js
+++ b/devtools/client/shared/components/reps/grip.js
@@ -25,18 +25,23 @@ define(function (require, exports, modul
   const Grip = React.createClass({
     displayName: "Grip",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
       mode: React.PropTypes.string,
     },
 
-    getTitle: function () {
-      return this.props.object.class || "Object";
+    getTitle: function (object) {
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          object: object
+        }, object.class);
+      }
+      return object.class || "Object";
     },
 
     longPropIterator: function (object) {
       try {
         return this.propIterator(object, 100);
       } catch (err) {
         console.error(err);
       }
@@ -76,19 +81,24 @@ define(function (require, exports, modul
         }));
       }
 
       // getProps() can return max+1 properties (it can't return more)
       // to indicate that there is more props than allowed. Remove the last
       // one and append 'more...' postfix in such case.
       if (props.length > max) {
         props.pop();
+
+        let objectLink = this.props.objectLink || span;
+
         props.push(Caption({
           key: "more",
-          object: "more...",
+          object: objectLink({
+            object: object
+          }, "more...")
         }));
       } else if (props.length > 0) {
         // Remove the last comma.
         // NOTE: do not change comp._store.props directly to update a property,
         // it should be re-rendered or cloned with changed props
         let last = props.length - 1;
         props[last] = React.cloneElement(props[last], {
           delim: ""
@@ -141,30 +151,44 @@ define(function (require, exports, modul
     },
 
     render: function () {
       let object = this.props.object;
       let props = (this.props.mode == "long") ?
         this.longPropIterator(object) :
         this.shortPropIterator(object);
 
+      let objectLink = this.props.objectLink || span;
       if (this.props.mode == "tiny" || !props.length) {
         return (
           ObjectBox({className: "object"},
-            span({className: "objectTitle"}, this.getTitle(object))
+            this.getTitle(object),
+            objectLink({
+              className: "objectLeftBrace",
+              role: "presentation",
+              object: object
+            }, "")
           )
         );
       }
 
       return (
         ObjectBox({className: "object"},
-          span({className: "objectTitle"}, this.getTitle(object)),
-          span({className: "objectLeftBrace", role: "presentation"}, " {"),
+          this.getTitle(object),
+          objectLink({
+            className: "objectLeftBrace",
+            role: "presentation",
+            object: object
+          }, " {"),
           props,
-          span({className: "objectRightBrace"}, "}")
+          objectLink({
+            className: "objectRightBrace",
+            role: "presentation",
+            object: object
+          }, "}")
         )
       );
     },
   });
 
   // Registration
   function supportsObject(object, type) {
     if (!isGrip(object)) {
--- a/devtools/client/shared/components/reps/named-node-map.js
+++ b/devtools/client/shared/components/reps/named-node-map.js
@@ -7,53 +7,60 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
   const { Caption } = createFactories(require("./caption"));
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Used to render a map of values provided as a grip.
    */
   let NamedNodeMap = React.createClass({
+    displayName: "NamedNodeMap",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
       mode: React.PropTypes.string,
       provider: React.PropTypes.object,
     },
 
-    className: "NamedNodeMap",
-
     getLength: function (object) {
       return object.preview.length;
     },
 
     getTitle: function (object) {
+      if (this.props.objectLink && object.class) {
+        return this.props.objectLink({
+          object: object
+        }, object.class);
+      }
       return object.class ? object.class : "";
     },
 
     getItems: function (array, max) {
       let items = this.propIterator(array, max);
 
       items = items.map(item => PropRep(item));
 
       if (items.length > max + 1) {
         items.pop();
+        let objectLink = this.props.objectLink || span;
         items.push(Caption({
           key: "more",
-          object: "more...",
+          object: objectLink({
+            object: this.props.object
+          }, "more...")
         }));
       }
 
       return items;
     },
 
     propIterator: function (grip, max) {
       max = max || 3;
@@ -93,32 +100,32 @@ define(function (require, exports, modul
       let items;
       if (mode == "tiny") {
         items = this.getLength(grip);
       } else {
         let max = (mode == "short") ? 3 : 100;
         items = this.getItems(grip, max);
       }
 
+      let objectLink = this.props.objectLink || span;
+
       return (
-        ObjectLink({className: "NamedNodeMap"},
-          span({className: "objectTitle"},
-            this.getTitle(grip)
-          ),
-          span({
+        ObjectBox({className: "NamedNodeMap"},
+          this.getTitle(grip),
+          objectLink({
             className: "arrayLeftBracket",
-            role: "presentation"},
-            "["
-          ),
+            role: "presentation",
+            object: grip
+          }, "["),
           items,
-          span({
+          objectLink({
             className: "arrayRightBracket",
-            role: "presentation"},
-            "]"
-          )
+            role: "presentation",
+            object: grip
+          }, "]")
         )
       );
     },
   });
 
   /**
    * Property for a grip object.
    */
--- a/devtools/client/shared/components/reps/object-with-text.js
+++ b/devtools/client/shared/components/reps/object-with-text.js
@@ -7,43 +7,55 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a grip object with textual data.
    */
   let ObjectWithText = React.createClass({
     displayName: "ObjectWithText",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
     },
 
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return ObjectBox({},
+          this.props.objectLink({
+            object: grip
+          }, this.getType(grip))
+        );
+      }
+      return "";
+    },
+
     getType: function (grip) {
       return grip.class;
     },
 
     getDescription: function (grip) {
       return (grip.preview.kind == "ObjectWithText") ? grip.preview.text : "";
     },
 
     render: function () {
       let grip = this.props.object;
       return (
-        ObjectLink({className: this.getType(grip)},
+        ObjectBox({className: this.getType(grip)},
+          this.getTitle(grip),
           span({className: "objectPropValue"},
             this.getDescription(grip)
           )
         )
       );
     },
   });
 
--- a/devtools/client/shared/components/reps/object-with-url.js
+++ b/devtools/client/shared/components/reps/object-with-url.js
@@ -7,43 +7,55 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a grip object with URL data.
    */
   let ObjectWithURL = React.createClass({
     displayName: "ObjectWithURL",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
     },
 
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return ObjectBox({},
+          this.props.objectLink({
+            object: grip
+          }, this.getType(grip))
+        );
+      }
+      return "";
+    },
+
     getType: function (grip) {
       return grip.class;
     },
 
     getDescription: function (grip) {
       return grip.preview.url;
     },
 
     render: function () {
       let grip = this.props.object;
       return (
-        ObjectLink({className: this.getType(grip)},
+        ObjectBox({className: this.getType(grip)},
+          this.getTitle(grip),
           span({className: "objectPropValue"},
             this.getDescription(grip)
           )
         )
       );
     },
   });
 
--- a/devtools/client/shared/components/reps/object.js
+++ b/devtools/client/shared/components/reps/object.js
@@ -22,17 +22,22 @@ define(function (require, exports, modul
   const Obj = React.createClass({
     displayName: "Obj",
 
     propTypes: {
       object: React.PropTypes.object,
       mode: React.PropTypes.string,
     },
 
-    getTitle: function () {
+    getTitle: function (object) {
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          object: object
+        }, object.class);
+      }
       return "Object";
     },
 
     longPropIterator: function (object) {
       try {
         return this.propIterator(object, 100);
       } catch (err) {
         console.error(err);
@@ -70,19 +75,23 @@ define(function (require, exports, modul
         // Let's display also empty members and functions.
         props = props.concat(this.getProps(object, max, (t, value) => {
           return !isInterestingProp(t, value);
         }));
       }
 
       if (props.length > max) {
         props.pop();
+        let objectLink = this.props.objectLink || span;
+
         props.push(Caption({
           key: "more",
-          object: "more...",
+          object: objectLink({
+            object: object
+          }, "more...")
         }));
       } else if (props.length > 0) {
         // Remove the last comma.
         props[props.length - 1] = React.cloneElement(
           props[props.length - 1], { delim: "" });
       }
 
       return props;
@@ -128,36 +137,46 @@ define(function (require, exports, modul
       }
 
       return props;
     },
 
     render: function () {
       let object = this.props.object;
       let props = this.shortPropIterator(object);
+      let objectLink = this.props.objectLink || span;
 
       if (this.props.mode == "tiny" || !props.length) {
         return (
           ObjectBox({className: "object"},
-            span({className: "objectTitle"}, this.getTitle())
+            objectLink({className: "objectTitle"}, this.getTitle())
           )
         );
       }
 
       return (
         ObjectBox({className: "object"},
-          span({className: "objectTitle"}, this.getTitle()),
-          span({className: "objectLeftBrace", role: "presentation"}, "{"),
+          this.getTitle(object),
+          objectLink({
+            className: "objectLeftBrace",
+            role: "presentation",
+            object: object
+          }, "{"),
           props,
-          span({className: "objectRightBrace"}, "}")
+          objectLink({
+            className: "objectRightBrace",
+            role: "presentation",
+            object: object
+          }, "}")
         )
       );
     },
   });
   function supportsObject(object, type) {
     return true;
   }
+
   // Exports from this module
   exports.Obj = {
     rep: Obj,
     supportsObject: supportsObject
   };
 });
--- a/devtools/client/shared/components/reps/regexp.js
+++ b/devtools/client/shared/components/reps/regexp.js
@@ -7,50 +7,45 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a grip object with regular expression.
    */
   let RegExp = React.createClass({
     displayName: "regexp",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
     },
 
-    getTitle: function (grip) {
-      return grip.class;
-    },
-
     getSource: function (grip) {
       return grip.displayString;
     },
 
     render: function () {
       let grip = this.props.object;
+      let objectLink = this.props.objectLink || span;
+
       return (
-        ObjectLink({className: "regexp"},
-          span({className: "objectTitle"},
-            this.getTitle(grip)
-          ),
-          span(" "),
-          span({className: "regexpSource"},
-            this.getSource(grip)
-          )
+        ObjectBox({className: "regexp"},
+          objectLink({
+            object: grip,
+            className: "regexpSource"
+          }, this.getSource(grip))
         )
       );
     },
   });
 
   // Registration
 
   function supportsObject(object, type) {
--- a/devtools/client/shared/components/reps/string.js
+++ b/devtools/client/shared/components/reps/string.js
@@ -25,19 +25,21 @@ define(function (require, exports, modul
       if (member && member.open) {
         return (
           ObjectBox({className: "string"},
             "\"" + text + "\""
           )
         );
       }
 
+      let croppedString = this.props.cropLimit ?
+        cropMultipleLines(text, this.props.cropLimit) : cropMultipleLines(text);
+
       return (
-        ObjectBox({className: "string"},
-          "\"" + cropMultipleLines(text) + "\""
+        ObjectBox({className: "string"}, "\"" + croppedString + "\""
         )
       );
     },
   });
 
   function supportsObject(object, type) {
     return (type == "string");
   }
--- a/devtools/client/shared/components/reps/stylesheet.js
+++ b/devtools/client/shared/components/reps/stylesheet.js
@@ -23,28 +23,40 @@ define(function (require, exports, modul
    */
   let StyleSheet = React.createClass({
     displayName: "object",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
     },
 
+    getTitle: function (grip) {
+      let title = "StyleSheet ";
+      if (this.props.objectLink) {
+        return ObjectBox({},
+          this.props.objectLink({
+            object: grip
+          }, title)
+        );
+      }
+      return title;
+    },
+
     getLocation: function (grip) {
       // Embedded stylesheets don't have URL and so, no preview.
       let url = grip.preview ? grip.preview.url : "";
       return url ? getFileName(url) : "";
     },
 
     render: function () {
       let grip = this.props.object;
 
       return (
         ObjectBox({className: "object"},
-          "StyleSheet ",
+          this.getTitle(grip),
           DOM.span({className: "objectPropValue"},
             this.getLocation(grip)
           )
         )
       );
     },
   });
 
--- a/devtools/client/shared/components/reps/text-node.js
+++ b/devtools/client/shared/components/reps/text-node.js
@@ -6,18 +6,19 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { createFactories, isGrip, cropMultipleLines } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectBox } = createFactories(require("./object-box"));
+  const { cropMultipleLines } = require("./string");
 
   // Shortcuts
   const DOM = React.DOM;
 
   /**
    * Renders DOM #text node.
    */
   let TextNode = React.createClass({
@@ -27,42 +28,54 @@ define(function (require, exports, modul
       object: React.PropTypes.object.isRequired,
       mode: React.PropTypes.string,
     },
 
     getTextContent: function (grip) {
       return cropMultipleLines(grip.preview.textContent);
     },
 
-    getTitle: function (win, context) {
-      return "textNode";
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          object: grip
+        }, "#text");
+      }
+      return "";
     },
 
     render: function () {
       let grip = this.props.object;
       let mode = this.props.mode || "short";
 
       if (mode == "short" || mode == "tiny") {
         return (
-          ObjectLink({className: "textNode"},
+          ObjectBox({className: "textNode"},
+            this.getTitle(grip),
             "\"" + this.getTextContent(grip) + "\""
           )
         );
       }
 
+      let objectLink = this.props.objectLink || DOM.span;
       return (
-        ObjectLink({className: "textNode"},
-          "<",
+        ObjectBox({className: "textNode"},
+          this.getTitle(grip),
+          objectLink({
+            object: grip
+          }, "<"),
           DOM.span({className: "nodeTag"}, "TextNode"),
           " textContent=\"",
           DOM.span({className: "nodeValue"},
             this.getTextContent(grip)
           ),
           "\"",
-          ">;"
+          objectLink({
+            object: grip
+          }, ">;")
         )
       );
     },
   });
 
   // Registration
 
   function supportsObject(grip, type) {
--- a/devtools/client/shared/components/reps/window.js
+++ b/devtools/client/shared/components/reps/window.js
@@ -22,25 +22,37 @@ define(function (require, exports, modul
    */
   let Window = React.createClass({
     displayName: "Window",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
     },
 
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return ObjectBox({},
+          this.props.objectLink({
+            object: grip
+          }, grip.class)
+        );
+      }
+      return "";
+    },
+
     getLocation: function (grip) {
       return cropString(grip.preview.url);
     },
 
     render: function () {
       let grip = this.props.object;
 
       return (
         ObjectBox({className: "Window"},
+          this.getTitle(grip),
           DOM.span({className: "objectPropValue"},
             this.getLocation(grip)
           )
         )
       );
     },
   });
 
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -2,16 +2,17 @@
 support-files =
   head.js
 
 [test_frame_01.html]
 [test_HSplitBox_01.html]
 [test_notification_box_01.html]
 [test_notification_box_02.html]
 [test_notification_box_03.html]
+[test_reps_array.html]
 [test_reps_attribute.html]
 [test_reps_date-time.html]
 [test_reps_function.html]
 [test_reps_grip.html]
 [test_reps_grip-array.html]
 [test_reps_null.html]
 [test_reps_number.html]
 [test_reps_object-with-text.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_reps_array.html
@@ -0,0 +1,185 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+Test ArrayRep rep
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Rep test - ArrayRep</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
+window.onload = Task.async(function* () {
+  let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
+  let { ArrayRep } = browserRequire("devtools/client/shared/components/reps/array");
+
+  let componentUnderTest = ArrayRep;
+
+  try {
+    yield testBasic();
+
+    // Test property iterator
+    yield testMaxProps();
+    yield testMoreThanMaxProps();
+    yield testRecursiveArray();
+
+    // Test that properties are rendered as expected by ItemRep
+    yield testNested();
+  } catch(e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+
+  function testBasic() {
+    // Test that correct rep is chosen
+    const stub = [];
+    const renderedRep = shallowRenderComponent(Rep, { object: stub });
+    is(renderedRep.type, ArrayRep.rep, `Rep correctly selects ${ArrayRep.rep.displayName}`);
+
+    // Test rendering
+    const defaultOutput = `[]`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `[0]`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testBasic", componentUnderTest, stub);
+  }
+
+  function testMaxProps() {
+    const stub = [1, "foo", {}];
+    const defaultOutput = `[1, "foo", Object]`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `[3]`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testMaxProps", componentUnderTest, stub);
+  }
+
+  function testMoreThanMaxProps() {
+    const stub = Array(302).fill("foo");
+    const defaultOutput = `["foo", "foo", "foo", more...]`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `[302]`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: `[${Array(300).fill("\"foo\"").join(", ")}, more...]`,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testMoreThanMaxProps", componentUnderTest, stub);
+  }
+
+  function testRecursiveArray() {
+    let stub = [1];
+    stub.push(stub);
+    const defaultOutput = `[1, [...]]`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `[2]`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testRecursiveArray", componentUnderTest, stub);
+  }
+
+  function testNested() {
+    let stub = [
+      {
+        p1: "s1",
+        p2: ["a1", "a2", "a3"],
+        p3: "s3",
+        p4: "s4"
+      }
+    ];
+    const defaultOutput = `[Object{p1: "s1", p3: "s3", p4: "s4", more...}]`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `[1]`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testNested", componentUnderTest, stub);
+  }
+});
+</script>
+</pre>
+</body>
+</html>
--- a/devtools/client/shared/components/test/mochitest/test_reps_object-with-url.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_object-with-url.html
@@ -36,17 +36,17 @@ window.onload = Task.async(function* () 
     };
 
     // Test that correct rep is chosen
     const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
     is(renderedRep.type, ObjectWithURL.rep, `Rep correctly selects ${ObjectWithURL.rep.displayName}`);
 
     // Test rendering
     const renderedComponent = renderComponent(ObjectWithURL.rep, { object: gripStub });
-    ok(renderedComponent.className.includes("objectLink-Location"), "ObjectWithURL rep has expected class name");
+    ok(renderedComponent.className.includes("objectBox-Location"), "ObjectWithURL rep has expected class name");
     const innerNode = renderedComponent.querySelector(".objectPropValue");
     is(innerNode.textContent, "https://www.mozilla.org/en-US/", "ObjectWithURL rep has expected inner HTML structure and text content");
 
     // @TODO test link once Bug 1245303 has been implemented.
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
--- a/devtools/client/shared/components/test/mochitest/test_reps_string.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_string.html
@@ -21,27 +21,33 @@ window.onload = Task.async(function* () 
   try {
     // Test that correct rep is chosen
     const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testMultiline") });
     is(renderedRep.type, StringRep.rep, `Rep correctly selects ${StringRep.rep.displayName}`);
 
     // Test rendering
     yield testMultiline();
     yield testMultilineOpen();
+    yield testMultilineLimit();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testMultiline() {
     const renderedComponent = renderComponent(StringRep.rep, { object: getGripStub("testMultiline") });
     is(renderedComponent.textContent, "\"aaaaaaaaaaaaaaaaaaaaa\\nbbb…bbbbbb\\ncccccccccccccccc\\n\"", "String rep has expected text content for multiline string");
   }
 
+  function testMultilineLimit() {
+    const renderedComponent = renderComponent(StringRep.rep, { object: getGripStub("testMultiline"), cropLimit: 20 });
+    is(renderedComponent.textContent, "\"aaaaaaaaaa…cccccccc\\n\"", "String rep has expected text content for multiline string with specified number of characters");
+  }
+
   function testMultilineOpen() {
     const renderedComponent = renderComponent(StringRep.rep, { object: getGripStub("testMultiline"), member: {open: true} });
     is(renderedComponent.textContent, "\"aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n\"", "String rep has expected text content for multiline string when open");
   }
 
   function getGripStub(name) {
     switch (name) {
       case "testMultiline":
--- a/devtools/client/themes/firebug-theme.css
+++ b/devtools/client/themes/firebug-theme.css
@@ -242,8 +242,12 @@
 
 .theme-firebug .devtools-toolbarbutton {
   min-width: 24px;
 }
 
 .theme-firebug #command-button-frames {
   min-width: 32px;
 }
+
+.theme-firebug #element-picker {
+  min-height: 21px;
+}
--- a/devtools/client/webconsole/new-console-output/components/variables-view-link.js
+++ b/devtools/client/webconsole/new-console-output/components/variables-view-link.js
@@ -11,24 +11,25 @@ const {
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const {openVariablesView} = require("devtools/client/webconsole/new-console-output/utils/variables-view");
 
 VariablesViewLink.displayName = "VariablesViewLink";
 
 VariablesViewLink.propTypes = {
-  objectActor: PropTypes.object.required,
-  label: PropTypes.string.label,
+  object: PropTypes.object.required
 };
 
 function VariablesViewLink(props) {
-  const { objectActor, label } = props;
+  const { object, children } = props;
 
-  return dom.a({
-    onClick: openVariablesView.bind(null, objectActor),
-    className: "cm-variable",
-    draggable: false,
-    href: "#"
-  }, label);
+  return (
+    dom.a({
+      onClick: openVariablesView.bind(null, object),
+      className: "cm-variable",
+      draggable: false,
+      href: "#"
+    }, children)
+  );
 }
 
 module.exports.VariablesViewLink = VariablesViewLink;
--- a/devtools/client/webconsole/new-console-output/reducers/index.js
+++ b/devtools/client/webconsole/new-console-output/reducers/index.js
@@ -1,12 +1,14 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { messages } = require("./messages");
+const { prefs } = require("./prefs");
 
 exports.reducers = {
-  messages
+  messages,
+  prefs,
 };
--- a/devtools/client/webconsole/new-console-output/reducers/moz.build
+++ b/devtools/client/webconsole/new-console-output/reducers/moz.build
@@ -1,9 +1,10 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'index.js',
     'messages.js',
+    'prefs.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/prefs.js
@@ -0,0 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+function prefs(state = {}, action) {
+  return state;
+}
+
+exports.prefs = prefs;
--- a/devtools/client/webconsole/new-console-output/selectors/messages.js
+++ b/devtools/client/webconsole/new-console-output/selectors/messages.js
@@ -1,12 +1,22 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
+const { getLogLimit } = require("devtools/client/webconsole/new-console-output/selectors/prefs");
+
 function getAllMessages(state) {
-  return state.messages;
+  let messages = state.messages;
+  let messageCount = messages.count();
+  let logLimit = getLogLimit(state);
+
+  if (messageCount > logLimit) {
+    return messages.splice(0, messageCount - logLimit);
+  }
+
+  return messages;
 }
 
 exports.getAllMessages = getAllMessages;
--- a/devtools/client/webconsole/new-console-output/selectors/moz.build
+++ b/devtools/client/webconsole/new-console-output/selectors/moz.build
@@ -1,8 +1,9 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'messages.js',
+    'prefs.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/prefs.js
@@ -0,0 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+function getLogLimit(state) {
+  return state.prefs.logLimit;
+}
+
+exports.getLogLimit = getLogLimit;
--- a/devtools/client/webconsole/new-console-output/store.js
+++ b/devtools/client/webconsole/new-console-output/store.js
@@ -1,17 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { combineReducers, createStore } = require("devtools/client/shared/vendor/redux");
+const Immutable = require("devtools/client/shared/vendor/immutable");
 const { reducers } = require("./reducers/index");
+const Services = require("Services");
 
-function storeFactory(initialState = {}) {
+function storeFactory() {
+  const initialState = {
+    messages: Immutable.List(),
+    prefs: {
+      logLimit: Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1)
+    }
+  };
+
   return createStore(combineReducers(reducers), initialState);
 }
 
 // Provide the single store instance for app code.
 module.exports.store = storeFactory();
 // Provide the store factory for test code so that each test is working with
 // its own instance.
 module.exports.storeFactory = storeFactory;
--- a/devtools/client/webconsole/new-console-output/test/components/test_evaluation-result.html
+++ b/devtools/client/webconsole/new-console-output/test/components/test_evaluation-result.html
@@ -29,17 +29,17 @@ window.onload = Task.async(function* () 
     const testCommand = testCommands.get("new Date()");
     const packet = yield getPacket(testCommand.command, testCommand.commandType);
     const message = prepareMessage(packet);
     const props = {
       message
     };
     const rendered = renderComponent(EvaluationResult, props);
 
-    const queryPathBase = "div.message.cm-s-mozilla span.message-body-wrapper.message-body.devtools-monospace span .objectLink";
+    const queryPathBase = "div.message.cm-s-mozilla span.message-body-wrapper.message-body.devtools-monospace span .objectBox";
 
     const preview = rendered.querySelectorAll(queryPathBase);
     is(preview[0].textContent, testCommand.expectedText, "EvaluationResult outputs expected text");
   }
 });
 
 </script>
 </body>
--- a/devtools/client/webconsole/new-console-output/test/store/head.js
+++ b/devtools/client/webconsole/new-console-output/test/store/head.js
@@ -2,16 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* exported storeFactory */
 
 "use strict";
 
 var { utils: Cu } = Components;
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const Services = require("Services");
 
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 DevToolsUtils.testing = true;
 DevToolsUtils.dumpn.wantLogging = true;
 DevToolsUtils.dumpv.wantVerbose = false;
 
 const { storeFactory } = require("devtools/client/webconsole/new-console-output/store");
 
--- a/devtools/client/webconsole/new-console-output/test/store/test_messages.js
+++ b/devtools/client/webconsole/new-console-output/test/store/test_messages.js
@@ -77,8 +77,48 @@ add_task(function*() {
     "MESSAGE_ADD action adds a message");
 
   dispatch(actions.messageAdd(clearPacket));
 
   messages = getAllMessages(getState());
   deepEqual(messages.toArray(), [prepareMessage(clearPacket)],
     "console.clear clears existing messages and add a new one");
 });
+
+/**
+ * Test message limit on the store.
+ */
+add_task(function* () {
+  const { getState, dispatch } = storeFactory();
+  const logLimit = 1000;
+  const messageNumber = logLimit + 1;
+
+  let newPacket = Object.assign({}, packet);
+  for (let i = 1; i <= messageNumber; i++) {
+    newPacket.message.arguments = [i];
+    dispatch(actions.messageAdd(newPacket));
+  }
+
+  let messages = getAllMessages(getState());
+  equal(messages.count(), logLimit, "Messages are pruned up to the log limit");
+  deepEqual(messages.last().data.arguments, [messageNumber],
+    "The last message is the expected one");
+});
+
+/**
+ * Test message limit on the store with user set prefs.
+ */
+add_task(function* () {
+  const userSetLimit = 10;
+  Services.prefs.setIntPref("devtools.hud.loglimit", userSetLimit);
+
+  const { getState, dispatch } = storeFactory();
+
+  let newPacket = Object.assign({}, packet);
+  for (let i = 1; i <= userSetLimit + 1; i++) {
+    newPacket.message.arguments = [i];
+    dispatch(actions.messageAdd(newPacket));
+  }
+
+  let messages = getAllMessages(getState());
+  equal(messages.count(), userSetLimit,
+    "Messages are pruned up to the user set log limit");
+});
--- a/devtools/client/webconsole/new-console-output/utils/variables-view.js
+++ b/devtools/client/webconsole/new-console-output/utils/variables-view.js
@@ -7,11 +7,11 @@
 /* global window */
 "use strict";
 
 /**
  * @TODO Remove this.
  *
  * Once JSTerm is also written in React/Redux, these will be actions.
  */
-exports.openVariablesView = (objectActor) => {
-  window.jsterm.openVariablesView({objectActor});
+exports.openVariablesView = (object) => {
+  window.jsterm.openVariablesView({objectActor: object});
 };
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
@@ -229,16 +229,22 @@ public class BrowserContract {
 
         public static final int FIXED_ROOT_ID = 0;
         public static final int FAKE_DESKTOP_FOLDER_ID = -1;
         public static final int FIXED_READING_LIST_ID = -2;
         public static final int FIXED_PINNED_LIST_ID = -3;
         public static final int FIXED_SCREENSHOT_FOLDER_ID = -4;
         public static final int FAKE_READINGLIST_SMARTFOLDER_ID = -5;
 
+        /**
+         * This ID and the following negative IDs are reserved for bookmarks from Android's partner
+         * bookmark provider.
+         */
+        public static final long FAKE_PARTNER_BOOKMARKS_START = -1000;
+
         public static final String MOBILE_FOLDER_GUID = "mobile";
         public static final String PLACES_FOLDER_GUID = "places";
         public static final String MENU_FOLDER_GUID = "menu";
         public static final String TAGS_FOLDER_GUID = "tags";
         public static final String TOOLBAR_FOLDER_GUID = "toolbar";
         public static final String UNFILED_FOLDER_GUID = "unfiled";
         public static final String FAKE_DESKTOP_FOLDER_GUID = "desktop";
         public static final String PINNED_FOLDER_GUID = "pinned";
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBookmarksProviderClient.java
@@ -0,0 +1,71 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.distribution;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+
+import org.mozilla.gecko.db.BrowserContract;
+
+/**
+ * Client for reading Android's PartnerBookmarksProvider.
+ *
+ * Note: This client is only invoked for distributions. Without a distribution the content provider
+ *       will not be read and no bookmarks will be added to the UI.
+ */
+public class PartnerBookmarksProviderClient {
+    /**
+     * The contract between the partner bookmarks provider and applications. Contains the definition
+     * for the supported URIs and columns.
+     */
+    private static class PartnerContract {
+        public static final Uri CONTENT_URI = Uri.parse("content://com.android.partnerbookmarks/bookmarks");
+
+        public static final int TYPE_BOOKMARK = 1;
+        public static final int TYPE_FOLDER = 2;
+
+        public static final int PARENT_ROOT_ID = 0;
+
+        public static final String ID = "_id";
+        public static final String TYPE = "type";
+        public static final String URL = "url";
+        public static final String TITLE = "title";
+        public static final String FAVICON = "favicon";
+        public static final String TOUCHICON = "touchicon";
+        public static final String PARENT = "parent";
+    }
+
+    public static Cursor getBookmarksInFolder(ContentResolver contentResolver, int folderId) {
+        // Use root folder id or transform negative id into actual (positive) folder id.
+        final long actualFolderId = folderId == BrowserContract.Bookmarks.FIXED_ROOT_ID
+                ? PartnerContract.PARENT_ROOT_ID
+                : BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START - folderId;
+
+        return contentResolver.query(
+                PartnerContract.CONTENT_URI,
+                new String[] {
+                        // Transform ids into negative values starting with FAKE_PARTNER_BOOKMARKS_START.
+                        "(" + BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START + " - " + PartnerContract.ID + ") as " + BrowserContract.Bookmarks._ID,
+                        PartnerContract.TITLE + " as " + BrowserContract.Bookmarks.TITLE,
+                        PartnerContract.URL +  " as " + BrowserContract.Bookmarks.URL,
+                        // Transform parent ids to negative ids as well
+                        "(" + BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START + " - " + PartnerContract.PARENT + ") as " + BrowserContract.Bookmarks.PARENT,
+                        // Convert types (we use 0-1 and the partner provider 1-2)
+                        "(2 - " + PartnerContract.TYPE + ") as " + BrowserContract.Bookmarks.TYPE,
+                        // Use the ID of the entry as GUID
+                        PartnerContract.ID + " as " + BrowserContract.Bookmarks.GUID
+                },
+                PartnerContract.PARENT + " = ?"
+                        // Only select entries with valid type
+                        + " AND (" + BrowserContract.Bookmarks.TYPE + " = 1 OR " + BrowserContract.Bookmarks.TYPE + " = 2)"
+                        // Only select entries with non empty title
+                        + " AND " + BrowserContract.Bookmarks.TITLE + " <> ''",
+                new String[] { String.valueOf(actualFolderId) },
+                // Same order we use in our content provider (without position)
+                BrowserContract.Bookmarks.TYPE + " ASC, " + BrowserContract.Bookmarks._ID + " ASC");
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
@@ -5,29 +5,35 @@
 
 package org.mozilla.gecko.home;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
 import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.distribution.PartnerBookmarksProviderClient;
 import org.mozilla.gecko.home.BookmarksListAdapter.FolderInfo;
 import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
 import org.mozilla.gecko.home.BookmarksListAdapter.RefreshType;
 import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
+import org.mozilla.gecko.preferences.GeckoPreferences;
 
 import android.app.Activity;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.database.Cursor;
+import android.database.MergeCursor;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.support.annotation.NonNull;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.content.Loader;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -215,17 +221,42 @@ public class BookmarksPanel extends Home
             super(context);
             mFolderInfo = folderInfo;
             mRefreshType = refreshType;
             mDB = GeckoProfile.get(context).getDB();
         }
 
         @Override
         public Cursor loadCursor() {
-            return mDB.getBookmarksInFolder(getContext().getContentResolver(), mFolderInfo.id);
+            final boolean isRootFolder = mFolderInfo.id == BrowserContract.Bookmarks.FIXED_ROOT_ID;
+
+            final ContentResolver contentResolver = getContext().getContentResolver();
+
+            Cursor partnerCursor = null;
+            Cursor userCursor = null;
+
+            if (GeckoSharedPrefs.forProfile(getContext()).getBoolean(GeckoPreferences.PREFS_READ_PARTNER_BOOKMARKS_PROVIDER, false)
+                    && (isRootFolder || mFolderInfo.id <= Bookmarks.FAKE_PARTNER_BOOKMARKS_START)) {
+                partnerCursor = PartnerBookmarksProviderClient.getBookmarksInFolder(contentResolver, mFolderInfo.id);
+            }
+
+            if (isRootFolder || mFolderInfo.id > Bookmarks.FAKE_PARTNER_BOOKMARKS_START) {
+                userCursor = mDB.getBookmarksInFolder(contentResolver, mFolderInfo.id);
+            }
+
+
+            if (partnerCursor == null && userCursor == null) {
+                return null;
+            } else if (partnerCursor == null) {
+                return userCursor;
+            } else if (userCursor == null) {
+                return partnerCursor;
+            } else {
+                return new MergeCursor(new Cursor[] { partnerCursor, userCursor });
+            }
         }
 
         @Override
         public void onContentChanged() {
             // Invalidate the cached value that keeps track of whether or
             // not desktop bookmarks exist.
             mDB.invalidate();
             super.onContentChanged();
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
@@ -158,16 +158,17 @@ OnSharedPreferenceChangeListener
     public static final String PREFS_HISTORY_SAVED_SEARCH = NON_PREF_PREFIX + "search.search_history.enabled";
     private static final String PREFS_FAQ_LINK = NON_PREF_PREFIX + "faq.link";
     private static final String PREFS_FEEDBACK_LINK = NON_PREF_PREFIX + "feedback.link";
     public static final String PREFS_NOTIFICATIONS_CONTENT = NON_PREF_PREFIX + "notifications.content";
     public static final String PREFS_NOTIFICATIONS_CONTENT_LEARN_MORE = NON_PREF_PREFIX + "notifications.content.learn_more";
     public static final String PREFS_NOTIFICATIONS_WHATS_NEW = NON_PREF_PREFIX + "notifications.whats_new";
     public static final String PREFS_APP_UPDATE_LAST_BUILD_ID = "app.update.last_build_id";
     public static final String PREFS_READ_PARTNER_CUSTOMIZATIONS_PROVIDER = NON_PREF_PREFIX + "distribution.read_partner_customizations_provider";
+    public static final String PREFS_READ_PARTNER_BOOKMARKS_PROVIDER = NON_PREF_PREFIX + "distribution.read_partner_bookmarks_provider";
 
     private static final String ACTION_STUMBLER_UPLOAD_PREF = "STUMBLER_PREF";
 
 
     // This isn't a Gecko pref, even if it looks like one.
     private static final String PREFS_BROWSER_LOCALE = "locale";
 
     public static final String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession3";
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -263,16 +263,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'delegates/BrowserAppDelegate.java',
     'delegates/BrowserAppDelegateWithReference.java',
     'delegates/OfflineTabStatusDelegate.java',
     'delegates/ScreenshotDelegate.java',
     'delegates/TabsTrayVisibilityAwareDelegate.java',
     'DevToolsAuthHelper.java',
     'distribution/Distribution.java',
     'distribution/DistributionStoreCallback.java',
+    'distribution/PartnerBookmarksProviderClient.java',
     'distribution/PartnerBrowserCustomizationsClient.java',
     'distribution/ReferrerDescriptor.java',
     'distribution/ReferrerReceiver.java',
     'dlc/BaseAction.java',
     'dlc/catalog/DownloadContent.java',
     'dlc/catalog/DownloadContentBootstrap.java',
     'dlc/catalog/DownloadContentBuilder.java',
     'dlc/catalog/DownloadContentCatalog.java',
--- a/services/sync/modules-testing/utils.js
+++ b/services/sync/modules-testing/utils.js
@@ -140,17 +140,17 @@ this.makeIdentityConfig = function(overr
     // fxaccount specific credentials.
     fxaccount: {
       user: {
         assertion: 'assertion',
         email: 'email',
         kA: 'kA',
         kB: 'kB',
         sessionToken: 'sessionToken',
-        uid: 'user_uid',
+        uid: "a".repeat(32),
         verified: true,
       },
       token: {
         endpoint: null,
         duration: 300,
         id: "id",
         key: "key",
         // uid will be set to the username.
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = ["BrowserIDManager"];
+this.EXPORTED_SYMBOLS = ["BrowserIDManager", "AuthenticationError"];
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/async.js");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-common/tokenserverclient.js");
 Cu.import("resource://services-crypto/utils.js");
@@ -60,18 +60,19 @@ function deriveKeyBundle(kB) {
 
 /*
   General authentication error for abstracting authentication
   errors from multiple sources (e.g., from FxAccounts, TokenServer).
   details is additional details about the error - it might be a string, or
   some other error object (which should do the right thing when toString() is
   called on it)
 */
-function AuthenticationError(details) {
+function AuthenticationError(details, source) {
   this.details = details;
+  this.source = source;
 }
 
 AuthenticationError.prototype = {
   toString: function() {
     return "AuthenticationError(" + this.details + ")";
   }
 }
 
@@ -107,16 +108,24 @@ this.BrowserIDManager.prototype = {
   get needsCustomization() {
     try {
       return Services.prefs.getBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION);
     } catch (e) {
       return false;
     }
   },
 
+  // Get the FxA UID. Throws if there is no signed in user
+  userUID() {
+    if (!this._signedInUser) {
+      throw new Error("userUID(): No signed in user");
+    }
+    return this._signedInUser.uid;
+  },
+
   initialize: function() {
     for (let topic of OBSERVER_TOPICS) {
       Services.obs.addObserver(this, topic, false);
     }
     // and a background fetch of account data just so we can set this.account,
     // so we have a username available before we've actually done a login.
     // XXX - this is actually a hack just for tests and really shouldn't be
     // necessary. Also, you'd think it would be safe to allow this.account to
@@ -620,23 +629,23 @@ this.BrowserIDManager.prototype = {
         }
         return token;
       })
       .catch(err => {
         // TODO: unify these errors - we need to handle errors thrown by
         // both tokenserverclient and hawkclient.
         // A tokenserver error thrown based on a bad response.
         if (err.response && err.response.status === 401) {
-          err = new AuthenticationError(err);
+          err = new AuthenticationError(err, "tokenserver");
         // A hawkclient error.
         } else if (err.code && err.code === 401) {
-          err = new AuthenticationError(err);
+          err = new AuthenticationError(err, "hawkclient");
         // An FxAccounts.jsm error.
         } else if (err.message == fxAccountsCommon.ERROR_AUTH_ERROR) {
-          err = new AuthenticationError(err);
+          err = new AuthenticationError(err, "fxaccounts");
         }
 
         // TODO: write tests to make sure that different auth error cases are handled here
         // properly: auth error getting assertion, auth error getting token (invalid generation
         // and client-state error)
         if (err instanceof AuthenticationError) {
           this._log.error("Authentication error in _fetchTokenForUser", err);
           // set it to the "fatal" LOGIN_FAILED_LOGIN_REJECTED reason.
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -1448,31 +1448,34 @@ SyncEngine.prototype = {
   _uploadOutgoing: function () {
     this._log.trace("Uploading local changes to server.");
 
     let modifiedIDs = Object.keys(this._modified);
     if (modifiedIDs.length) {
       this._log.trace("Preparing " + modifiedIDs.length +
                       " outgoing records");
 
+      let counts = { sent: modifiedIDs.length, failed: 0 };
+
       // collection we'll upload
       let up = new Collection(this.engineURL, null, this.service);
       let handleResponse = resp => {
         if (!resp.success) {
           this._log.debug("Uploading records failed: " + resp);
           resp.failureCode = ENGINE_UPLOAD_FAIL;
           throw resp;
         }
 
         // Update server timestamp from the upload.
         let modified = resp.headers["x-weave-timestamp"];
         if (modified > this.lastSync)
           this.lastSync = modified;
 
         let failed_ids = Object.keys(resp.obj.failed);
+        counts.failed += failed_ids.length;
         if (failed_ids.length)
           this._log.debug("Records that will be uploaded again because "
                           + "the server couldn't store them: "
                           + failed_ids.join(", "));
 
         // Clear successfully uploaded objects.
         for (let key in resp.obj.success) {
           let id = resp.obj.success[key];
@@ -1499,16 +1502,17 @@ SyncEngine.prototype = {
           this._log.warn("Error creating record", ex);
         }
         if (ok) {
           postQueue.enqueue(out);
         }
         this._store._sleep(0);
       }
       postQueue.flush();
+      Observers.notify("weave:engine:sync:uploaded", counts, this.name);
     }
   },
 
   // Any cleanup necessary.
   // Save the current snapshot so as to calculate changes at next sync
   _syncFinish: function () {
     this._log.trace("Finishing up sync");
     this._tracker.resetScore();
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -27,16 +27,17 @@ Cu.import("resource://services-sync/engi
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/policies.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/rest.js");
 Cu.import("resource://services-sync/stages/enginesync.js");
 Cu.import("resource://services-sync/stages/declined.js");
 Cu.import("resource://services-sync/status.js");
+Cu.import("resource://services-sync/telemetry.js");
 Cu.import("resource://services-sync/userapi.js");
 Cu.import("resource://services-sync/util.js");
 
 const ENGINE_MODULES = {
   Addons: "addons.js",
   Bookmarks: "bookmarks.js",
   Form: "forms.js",
   History: "history.js",
@@ -543,17 +544,18 @@ Sync11Service.prototype = {
     } catch (ex) {
       this.errorHandler.checkServerError(ex);
       throw ex;
     }
 
     // Always check for errors; this is also where we look for X-Weave-Alert.
     this.errorHandler.checkServerError(info);
     if (!info.success) {
-      throw "Aborting sync: failed to get collections.";
+      this._log.error("Aborting sync: failed to get collections.")
+      throw info;
     }
     return info;
   },
 
   verifyAndFetchSymmetricKeys: function verifyAndFetchSymmetricKeys(infoResponse) {
 
     this._log.debug("Fetching and verifying -- or generating -- symmetric keys.");
 
new file mode 100644
--- /dev/null
+++ b/services/sync/modules/telemetry.js
@@ -0,0 +1,432 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = ["SyncTelemetry"];
+
+Cu.import("resource://services-sync/browserid_identity.js");
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://services-sync/status.js");
+Cu.import("resource://services-sync/util.js");
+Cu.import("resource://services-common/observers.js");
+Cu.import("resource://services-common/async.js");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/TelemetryController.jsm");
+Cu.import("resource://gre/modules/FxAccounts.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let constants = {};
+Cu.import("resource://services-sync/constants.js", constants);
+
+var fxAccountsCommon = {};
+Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
+
+XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
+                                   "@mozilla.org/base/telemetry;1",
+                                   "nsITelemetry");
+
+const log = Log.repository.getLogger("Sync.Telemetry");
+
+const TOPICS = [
+  "xpcom-shutdown",
+  "weave:service:sync:start",
+  "weave:service:sync:finish",
+  "weave:service:sync:error",
+
+  "weave:engine:sync:start",
+  "weave:engine:sync:finish",
+  "weave:engine:sync:error",
+  "weave:engine:sync:applied",
+  "weave:engine:sync:uploaded",
+];
+
+const PING_FORMAT_VERSION = 1;
+
+// The set of engines we record telemetry for - any other engines are ignored.
+const ENGINES = new Set(["addons", "bookmarks", "clients", "forms", "history",
+                         "passwords", "prefs", "tabs"]);
+
+// is it a wrapped auth error from browserid_identity?
+function isBrowerIdAuthError(error) {
+  // I can't think of what could throw on String conversion
+  // but we have absolutely no clue about the type, and
+  // there's probably some things out there that would
+  try {
+    if (String(error).startsWith("AuthenticationError")) {
+      return true;
+    }
+  } catch (e) {}
+  return false;
+}
+
+function transformError(error, engineName) {
+  if (Async.isShutdownException(error)) {
+    return { name: "shutdownerror" };
+  }
+
+  if (typeof error === "string") {
+    if (error.startsWith("error.")) {
+      // This is hacky, but I can't imagine that it's not also accurate.
+      return { name: "othererror", error };
+    }
+
+    return { name: "unexpectederror", error };
+  }
+
+  if (error.failureCode) {
+    return { name: "othererror", error: error.failureCode };
+  }
+
+  if (error instanceof AuthenticationError) {
+    return { name: "autherror", from: error.source };
+  }
+
+  let httpCode = error.status ||
+    (error.response && error.response.status) ||
+    error.code;
+
+  if (httpCode) {
+    return { name: "httperror", code: httpCode };
+  }
+
+  if (error.result) {
+    return { name: "nserror", code: error.result };
+  }
+
+  return {
+    name: "unexpectederror",
+    error: String(error),
+  }
+}
+
+function tryGetMonotonicTimestamp() {
+  try {
+    return Telemetry.msSinceProcessStart();
+  } catch (e) {
+    log.warn("Unable to get a monotonic timestamp!");
+    return -1;
+  }
+}
+
+function timeDeltaFrom(monotonicStartTime) {
+  let now = tryGetMonotonicTimestamp();
+  if (monotonicStartTime !== -1 && now !== -1) {
+    return Math.round(now - monotonicStartTime);
+  }
+  return -1;
+}
+
+class EngineRecord {
+  constructor(name) {
+    // startTime is in ms from process start, but is monotonic (unlike Date.now())
+    // so we need to keep both it and when.
+    this.startTime = tryGetMonotonicTimestamp();
+    this.name = name;
+  }
+
+  toJSON() {
+    let result = Object.assign({}, this);
+    delete result.startTime;
+    return result;
+  }
+
+  finished(error) {
+    let took = timeDeltaFrom(this.startTime);
+    if (took > 0) {
+      this.took = took;
+    }
+    if (error) {
+      this.failureReason = transformError(error, this.name);
+    }
+  }
+
+  recordApplied(counts) {
+    if (this.incoming) {
+      log.error(`Incoming records applied multiple times for engine ${this.name}!`);
+      return;
+    }
+    if (this.name === "clients" && !counts.failed) {
+      // ignore successful application of client records
+      // since otherwise they show up every time and are meaningless.
+      return;
+    }
+
+    let incomingData = {};
+    let properties = ["applied", "failed", "newFailed", "reconciled"];
+    // Only record non-zero properties and only record incoming at all if
+    // there's at least one property we care about.
+    for (let property of properties) {
+      if (counts[property]) {
+        incomingData[property] = counts[property];
+        this.incoming = incomingData;
+      }
+    }
+  }
+
+  recordUploaded(counts) {
+    if (counts.sent || counts.failed) {
+      if (!this.outgoing) {
+        this.outgoing = [];
+      }
+      this.outgoing.push({
+        sent: counts.sent || undefined,
+        failed: counts.failed || undefined,
+      });
+    }
+  }
+}
+
+class TelemetryRecord {
+  constructor(allowedEngines) {
+    this.allowedEngines = allowedEngines;
+    // Our failure reason. This property only exists in the generated ping if an
+    // error actually occurred.
+    this.failureReason = undefined;
+    this.uid = "";
+    this.when = Date.now();
+    this.startTime = tryGetMonotonicTimestamp();
+    this.took = 0; // will be set later.
+
+    // All engines that have finished (ie, does not include the "current" one)
+    // We omit this from the ping if it's empty.
+    this.engines = [];
+    // The engine that has started but not yet stopped.
+    this.currentEngine = null;
+  }
+
+  // toJSON returns the actual payload we will submit.
+  toJSON() {
+    let result = {
+      when: this.when,
+      uid: this.uid,
+      took: this.took,
+      version: PING_FORMAT_VERSION,
+      failureReason: this.failureReason,
+      status: this.status,
+    };
+    let engines = [];
+    for (let engine of this.engines) {
+      engines.push(engine.toJSON());
+    }
+    if (engines.length > 0) {
+      result.engines = engines;
+    }
+    return result;
+  }
+
+  finished(error) {
+    this.took = timeDeltaFrom(this.startTime);
+    if (this.currentEngine != null) {
+      log.error("Finished called for the sync before the current engine finished");
+      this.currentEngine.finished(null);
+      this.onEngineStop(this.currentEngine.name);
+    }
+    if (error) {
+      this.failureReason = transformError(error);
+    }
+
+    try {
+      this.uid = Weave.Service.identity.userUID();
+    } catch (e) {
+      this.uid = "0".repeat(32);
+    }
+
+    // Check for engine statuses. -- We do this now, and not in engine.finished
+    // to make sure any statuses that get set "late" are recorded
+    for (let engine of this.engines) {
+      let status = Status.engines[engine.name];
+      if (status && status !== constants.ENGINE_SUCCEEDED) {
+        engine.status = status;
+      }
+    }
+
+    let statusObject = {};
+
+    let serviceStatus = Status.service;
+    if (serviceStatus && serviceStatus !== constants.STATUS_OK) {
+      statusObject.service = serviceStatus;
+      this.status = statusObject;
+    }
+    let syncStatus = Status.sync;
+    if (syncStatus && syncStatus !== constants.SYNC_SUCCEEDED) {
+      statusObject.sync = syncStatus;
+      this.status = statusObject;
+    }
+  }
+
+  onEngineStart(engineName) {
+    if (this._shouldIgnoreEngine(engineName, false)) {
+      return;
+    }
+
+    if (this.currentEngine) {
+      log.error(`Being told that engine ${engineName} has started, but current engine ${
+        this.currentEngine.name} hasn't stopped`);
+      // Just discard the current engine rather than making up data for it.
+    }
+    this.currentEngine = new EngineRecord(engineName);
+  }
+
+  onEngineStop(engineName, error) {
+    if (error && !this.currentEngine) {
+      log.error(`Error triggered on ${engineName} when no current engine exists: ${error}`);
+      // It's possible for us to get an error before the start message of an engine
+      // (somehow), in which case we still want to record that error.
+      this.currentEngine = new EngineRecord(engineName);
+    } else if (!this.currentEngine || (engineName && this._shouldIgnoreEngine(engineName, true))) {
+      return;
+    }
+    this.currentEngine.finished(error);
+    this.engines.push(this.currentEngine);
+    this.currentEngine = null;
+  }
+
+  onEngineApplied(engineName, counts) {
+    if (this._shouldIgnoreEngine(engineName)) {
+      return;
+    }
+    this.currentEngine.recordApplied(counts);
+  }
+
+  onEngineUploaded(engineName, counts) {
+    if (this._shouldIgnoreEngine(engineName)) {
+      return;
+    }
+    this.currentEngine.recordUploaded(counts);
+  }
+
+  _shouldIgnoreEngine(engineName, shouldBeCurrent = true) {
+    if (!this.allowedEngines.has(engineName)) {
+      log.info(`Notification for engine ${engineName}, but we aren't recording telemetry for it`);
+      return true;
+    }
+    if (shouldBeCurrent) {
+      if (!this.currentEngine || engineName != this.currentEngine.name) {
+        log.error(`Notification for engine ${engineName} but it isn't current`);
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+class SyncTelemetryImpl {
+  constructor(allowedEngines) {
+    log.level = Log.Level[Svc.Prefs.get("log.logger.telemetry", "Trace")];
+    // This is accessible so we can enable custom engines during tests.
+    this.allowedEngines = allowedEngines;
+    this.current = null;
+    this.setupObservers();
+  }
+
+  setupObservers() {
+    for (let topic of TOPICS) {
+      Observers.add(topic, this, this);
+    }
+  }
+
+  shutdown() {
+    for (let topic of TOPICS) {
+      Observers.remove(topic, this, this);
+    }
+  }
+
+  submit(record) {
+    TelemetryController.submitExternalPing("sync", record);
+  }
+
+  onSyncStarted() {
+    if (this.current) {
+      log.warn("Observed weave:service:sync:start, but we're already recording a sync!");
+      // Just discard the old record, consistent with our handling of engines, above.
+    }
+    this.current = new TelemetryRecord(this.allowedEngines);
+  }
+
+  _checkCurrent(topic) {
+    if (!this.current) {
+      log.warn(`Observed notification ${topic} but no current sync is being recorded.`);
+      return false;
+    }
+    return true;
+  }
+
+  onSyncFinished(error) {
+    if (!this.current) {
+      log.warn("onSyncFinished but we aren't recording");
+      return;
+    }
+    this.current.finished(error);
+    let current = this.current;
+    this.current = null;
+    this.submit(current.toJSON());
+  }
+
+  observe(subject, topic, data) {
+    log.trace(`observed ${topic} ${data}`);
+
+    switch (topic) {
+      case "xpcom-shutdown":
+        this.shutdown();
+        break;
+
+      /* sync itself state changes */
+      case "weave:service:sync:start":
+        this.onSyncStarted();
+        break;
+
+      case "weave:service:sync:finish":
+        if (this._checkCurrent(topic)) {
+          this.onSyncFinished(null);
+        }
+        break;
+
+      case "weave:service:sync:error":
+        // argument needs to be truthy (this should always be the case)
+        this.onSyncFinished(subject || "Unknown");
+        break;
+
+      /* engine sync state changes */
+      case "weave:engine:sync:start":
+        if (this._checkCurrent(topic)) {
+          this.current.onEngineStart(data);
+        }
+        break;
+      case "weave:engine:sync:finish":
+        if (this._checkCurrent(topic)) {
+          this.current.onEngineStop(data, null);
+        }
+        break;
+
+      case "weave:engine:sync:error":
+        if (this._checkCurrent(topic)) {
+          // argument needs to be truthy (this should always be the case)
+          this.current.onEngineStop(data, subject || "Unknown");
+        }
+        break;
+
+      /* engine counts */
+      case "weave:engine:sync:applied":
+        if (this._checkCurrent(topic)) {
+          this.current.onEngineApplied(data, subject);
+        }
+        break;
+
+      case "weave:engine:sync:uploaded":
+        if (this._checkCurrent(topic)) {
+          this.current.onEngineUploaded(data, subject);
+        }
+        break;
+
+      default:
+        log.warn(`unexpected observer topic ${topic}`);
+        break;
+    }
+  }
+}
+
+this.SyncTelemetry = new SyncTelemetryImpl(ENGINES);
--- a/services/sync/moz.build
+++ b/services/sync/moz.build
@@ -30,16 +30,17 @@ EXTRA_JS_MODULES['services-sync'] += [
     'modules/main.js',
     'modules/policies.js',
     'modules/record.js',
     'modules/resource.js',
     'modules/rest.js',
     'modules/service.js',
     'modules/status.js',
     'modules/SyncedTabs.jsm',
+    'modules/telemetry.js',
     'modules/userapi.js',
     'modules/util.js',
 ]
 
 EXTRA_PP_JS_MODULES['services-sync'] += [
     'modules/constants.js',
 ]
 
--- a/services/sync/tests/unit/head_helpers.js
+++ b/services/sync/tests/unit/head_helpers.js
@@ -1,14 +1,44 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://services-common/async.js");
 Cu.import("resource://testing-common/services/common/utils.js");
 Cu.import("resource://testing-common/PlacesTestUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, 'SyncPingSchema', function() {
+  let ns = {};
+  Cu.import("resource://gre/modules/FileUtils.jsm", ns);
+  let stream = Cc["@mozilla.org/network/file-input-stream;1"]
+               .createInstance(Ci.nsIFileInputStream);
+  let jsonReader = Cc["@mozilla.org/dom/json;1"]
+                   .createInstance(Components.interfaces.nsIJSON);
+  let schema;
+  try {
+    let schemaFile = do_get_file("sync_ping_schema.json");
+    stream.init(schemaFile, ns.FileUtils.MODE_RDONLY, ns.FileUtils.PERMS_FILE, 0);
+    schema = jsonReader.decodeFromStream(stream, stream.available());
+  } finally {
+    stream.close();
+  }
+
+  // Allow tests to make whatever engines they want, this shouldn't cause
+  // validation failure.
+  schema.definitions.engine.properties.name = { type: "string" };
+  return schema;
+});
+
+XPCOMUtils.defineLazyGetter(this, 'SyncPingValidator', function() {
+  let ns = {};
+  Cu.import("resource://testing-common/ajv-4.1.1.js", ns);
+  let ajv = new ns.Ajv({ async: "co*" });
+  return ajv.compile(SyncPingSchema);
+});
 
 var provider = {
   getFile: function(prop, persistent) {
     persistent.value = true;
     switch (prop) {
       case "ExtPrefDL":
         return [Services.dirsvc.get("CurProcD", Ci.nsIFile)];
       default:
@@ -201,8 +231,162 @@ function mockGetWindowEnumerator(url, nu
 
 // Helper that allows checking array equality.
 function do_check_array_eq(a1, a2) {
   do_check_eq(a1.length, a2.length);
   for (let i = 0; i < a1.length; ++i) {
     do_check_eq(a1[i], a2[i]);
   }
 }
+
+// Helper function to get the sync telemetry and add the typically used test
+// engine names to its list of allowed engines.
+function get_sync_test_telemetry() {
+  let ns = {};
+  Cu.import("resource://services-sync/telemetry.js", ns);
+  let testEngines = ["rotary", "steam", "sterling", "catapult"];
+  for (let engineName of testEngines) {
+    ns.SyncTelemetry.allowedEngines.add(engineName);
+  }
+  return ns.SyncTelemetry;
+}
+
+function assert_valid_ping(record) {
+  if (record) {
+    if (!SyncPingValidator(record)) {
+      deepEqual([], SyncPingValidator.errors, "Sync telemetry ping validation failed");
+    }
+    equal(record.version, 1);
+    lessOrEqual(record.when, Date.now());
+  }
+}
+
+// Asserts that `ping` is a ping that doesn't contain any failure information
+function assert_success_ping(ping) {
+  ok(!!ping);
+  assert_valid_ping(ping);
+  ok(!ping.failureReason);
+  equal(undefined, ping.status);
+  greater(ping.engines.length, 0);
+  for (let e of ping.engines) {
+    ok(!e.failureReason);
+    equal(undefined, e.status);
+    if (e.outgoing) {
+      for (let o of e.outgoing) {
+        equal(undefined, o.failed);
+        notEqual(undefined, o.sent);
+      }
+    }
+    if (e.incoming) {
+      equal(undefined, e.incoming.failed);
+      equal(undefined, e.incoming.newFailed);
+      notEqual(undefined, e.incoming.applied || e.incoming.reconciled);
+    }
+  }
+}
+
+// Hooks into telemetry to validate all pings after calling.
+function validate_all_future_pings() {
+  let telem = get_sync_test_telemetry();
+  telem.submit = assert_valid_ping;
+}
+
+function wait_for_ping(callback, allowErrorPings) {
+  return new Promise(resolve => {
+    let telem = get_sync_test_telemetry();
+    let oldSubmit = telem.submit;
+    telem.submit = function(record) {
+      telem.submit = oldSubmit;
+      if (allowErrorPings) {
+        assert_valid_ping(record);
+      } else {
+        assert_success_ping(record);
+      }
+      resolve(record);
+    };
+    callback();
+  });
+}
+
+// Short helper for wait_for_ping
+function sync_and_validate_telem(allowErrorPings) {
+  return wait_for_ping(() => Service.sync(), allowErrorPings);
+}
+
+// Used for the (many) cases where we do a 'partial' sync, where only a single
+// engine is actually synced, but we still want to ensure we're generating a
+// valid ping. Returns a promise that resolves to the ping, or rejects with the
+// thrown error after calling an optional callback.
+function sync_engine_and_validate_telem(engine, allowErrorPings, onError) {
+  return new Promise((resolve, reject) => {
+    let telem = get_sync_test_telemetry();
+    let caughtError = null;
+    // Clear out status, so failures from previous syncs won't show up in the
+    // telemetry ping.
+    let ns = {};
+    Cu.import("resource://services-sync/status.js", ns);
+    ns.Status._engines = {};
+    ns.Status.partial = false;
+    // Ideally we'd clear these out like we do with engines, (probably via
+    // Status.resetSync()), but this causes *numerous* tests to fail, so we just
+    // assume that if no failureReason or engine failures are set, and the
+    // status properties are the same as they were initially, that it's just
+    // a leftover.
+    // This is only an issue since we're triggering the sync of just one engine,
+    // without doing any other parts of the sync.
+    let initialServiceStatus = ns.Status._service;
+    let initialSyncStatus = ns.Status._sync;
+
+    let oldSubmit = telem.submit;
+    telem.submit = function(record) {
+      telem.submit = oldSubmit;
+      if (record && record.status) {
+        // did we see anything to lead us to believe that something bad actually happened
+        let realProblem = record.failureReason || record.engines.some(e => {
+          if (e.failureReason || e.status) {
+            return true;
+          }
+          if (e.outgoing && e.outgoing.some(o => o.failed > 0)) {
+            return true;
+          }
+          return e.incoming && e.incoming.failed;
+        });
+        if (!realProblem) {
+          // no, so if the status is the same as it was initially, just assume
+          // that its leftover and that we can ignore it.
+          if (record.status.sync && record.status.sync == initialSyncStatus) {
+            delete record.status.sync;
+          }
+          if (record.status.service && record.status.service == initialServiceStatus) {
+            delete record.status.service;
+          }
+          if (!record.status.sync && !record.status.service) {
+            delete record.status;
+          }
+        }
+      }
+      if (allowErrorPings) {
+        assert_valid_ping(record);
+      } else {
+        assert_success_ping(record);
+      }
+      if (caughtError) {
+        if (onError) {
+          onError(record);
+        }
+        reject(caughtError);
+      } else {
+        resolve(record);
+      }
+    };
+    Svc.Obs.notify("weave:service:sync:start");
+    try {
+      engine.sync();
+    } catch (e) {
+      caughtError = e;
+    }
+    if (caughtError) {
+      Svc.Obs.notify("weave:service:sync:error", caughtError);
+    } else {
+      Svc.Obs.notify("weave:service:sync:finish");
+    }
+  });
+}
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/sync_ping_schema.json
@@ -0,0 +1,147 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "description": "schema for Sync pings, documentation avaliable in toolkit/components/telemetry/docs/sync-ping.rst",
+  "type": "object",
+  "additionalProperties": false,
+  "required": ["when", "version", "took", "uid"],
+  "properties": {
+    "version": { "type": "integer", "minimum": 0 },
+    "didLogin": { "type": "boolean" },
+    "when": { "type": "integer" },
+    "status": {
+      "type": "object",
+      "anyOf": [
+        {"required": ["sync"]},
+        {"required": ["service"]}
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "sync": { "type": "string" },
+        "service": { "type": "string" }
+      }
+    },
+    "why": { "enum": ["startup", "schedule", "score", "user", "tabs"] },
+    "took": { "type": "integer", "minimum": -1 },
+    "uid": {
+      "type": "string",
+      "oneOf": [
+        { "pattern": "^[0-9a-f]{32}$" },
+        { "maxLength": 0 }
+      ]
+    },
+    "failureReason": { "$ref": "#/definitions/error" },
+    "engines": {
+      "type": "array",
+      "minItems": 1,
+      "items": { "$ref": "#/definitions/engine" }
+    }
+  },
+  "definitions": {
+    "engine": {
+      "required": ["name"],
+      "additionalProperties": false,
+      "properties": {
+        "failureReason": { "$ref": "#/definitions/error" },
+        "name": { "enum": ["addons", "bookmarks", "clients", "forms", "history", "passwords", "prefs", "tabs"] },
+        "took": { "type": "integer", "minimum": 1 },
+        "status": { "type": "string" },
+        "incoming": {
+          "type": "object",
+          "additionalProperties": false,
+          "anyOf": [
+            {"required": ["applied"]},
+            {"required": ["failed"]},
+            {"required": ["newFailed"]},
+            {"required": ["reconciled"]}
+          ],
+          "properties": {
+            "applied": { "type": "integer", "minimum": 1 },
+            "failed": { "type": "integer", "minimum": 1 },
+            "newFailed": { "type": "integer", "minimum": 1 },
+            "reconciled": { "type": "integer", "minimum": 1 }
+          }
+        },
+        "outgoing": {
+          "type": "array",
+          "minItems": 1,
+          "items": { "$ref": "#/definitions/outgoingBatch" }
+        },
+        "validation": {
+          "type": "array",
+          "minItems": 1,
+          "$ref": "#/definitions/validationProblem"
+        }
+      }
+    },
+    "outgoingBatch": {
+      "type": "object",
+      "additionalProperties": false,
+      "anyOf": [
+        {"required": ["sent"]},
+        {"required": ["failed"]}
+      ],
+      "properties": {
+        "sent": { "type": "integer", "minimum": 1 },
+        "failed": { "type": "integer", "minimum": 1 }
+      }
+    },
+    "error": {
+      "oneOf": [
+        { "$ref": "#/definitions/httpError" },
+        { "$ref": "#/definitions/nsError" },
+        { "$ref": "#/definitions/shutdownError" },
+        { "$ref": "#/definitions/authError" },
+        { "$ref": "#/definitions/otherError" },
+        { "$ref": "#/definitions/unexpectedError" }
+      ]
+    },
+    "httpError": {
+      "required": ["name", "code"],
+      "properties": {
+        "name": { "enum": ["httperror"] },
+        "code": { "type": "integer" }
+      }
+    },
+    "nsError": {
+      "required": ["name", "code"],
+      "properties": {
+        "name": { "enum": ["nserror"] },
+        "code": { "type": "integer" }
+      }
+    },
+    "shutdownError": {
+      "required": ["name"],
+      "properties": {
+        "name": { "enum": ["shutdownerror"] }
+      }
+    },
+    "authError": {
+      "required": ["name"],
+      "properties": {
+        "name": { "enum": ["autherror"] },
+        "from": { "enum": ["tokenserver", "fxaccounts", "hawkclient"] }
+      }
+    },
+    "otherError": {
+      "required": ["name"],
+      "properties": {
+        "name": { "enum": ["othererror"] },
+        "error": { "type": "string" }
+      }
+    },
+    "unexpectedError": {
+      "required": ["name"],
+      "properties": {
+        "name": { "enum": ["unexpectederror"] },
+        "error": { "type": "string" }
+      }
+    },
+    "validationProblem": {
+      "required": ["name", "count"],
+      "properties": {
+        "name": { "type": "string" },
+        "count": { "type": "integer" }
+      }
+    }
+  }
+}
\ No newline at end of file
--- a/services/sync/tests/unit/test_bookmark_engine.js
+++ b/services/sync/tests/unit/test_bookmark_engine.js
@@ -94,17 +94,17 @@ add_test(function test_ID_caching() {
 function serverForFoo(engine) {
   return serverForUsers({"foo": "password"}, {
     meta: {global: {engines: {bookmarks: {version: engine.version,
                                           syncID: engine.syncID}}}},
     bookmarks: {}
   });
 }
 
-add_test(function test_processIncoming_error_orderChildren() {
+add_task(function* test_processIncoming_error_orderChildren() {
   _("Ensure that _orderChildren() is called even when _processIncoming() throws an error.");
 
   let engine = new BookmarksEngine(Service);
   let store  = engine._store;
   let server = serverForFoo(engine);
   new SyncTestingInfrastructure(server.server);
 
   let collection = server.user("foo").collection("bookmarks");
@@ -141,36 +141,36 @@ add_test(function test_processIncoming_e
 
     // Make the 10 minutes old so it will only be synced in the toFetch phase.
     bogus_record.modified = Date.now() / 1000 - 60 * 10;
     engine.lastSync = Date.now() / 1000 - 60;
     engine.toFetch = [BOGUS_GUID];
 
     let error;
     try {
-      engine.sync();
+      yield sync_engine_and_validate_telem(engine, true)
     } catch(ex) {
       error = ex;
     }
-    do_check_true(!!error);
+    ok(!!error);
 
     // Verify that the bookmark order has been applied.
     let new_children = store.createRecord(folder1_guid).children;
     do_check_eq(new_children.length, 2);
     do_check_eq(new_children[0], folder1_payload.children[0]);
     do_check_eq(new_children[1], folder1_payload.children[1]);
 
     do_check_eq(PlacesUtils.bookmarks.getItemIndex(bmk1_id), 1);
     do_check_eq(PlacesUtils.bookmarks.getItemIndex(bmk2_id), 0);
 
   } finally {
     store.wipe();
     Svc.Prefs.resetBranch("");
     Service.recordManager.clearCache();
-    server.stop(run_next_test);
+    yield new Promise(resolve => server.stop(resolve));
   }
 });
 
 add_task(function* test_restorePromptsReupload() {
   _("Ensure that restoring from a backup will reupload all records.");
   let engine = new BookmarksEngine(Service);
   let store  = engine._store;
   let server = serverForFoo(engine);
@@ -213,17 +213,17 @@ add_task(function* test_restorePromptsRe
       folder1_id, tburi, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Thunderbird!");
     let bmk2_guid = store.GUIDForId(bmk2_id);
     _("Get Thunderbird!: " + bmk2_id + ", " + bmk2_guid);
 
     PlacesUtils.bookmarks.removeItem(bmk1_id);
 
     let error;
     try {
-      engine.sync();
+      yield sync_engine_and_validate_telem(engine, false);
     } catch(ex) {
       error = ex;
       _("Got error: " + Log.exceptionStr(ex));
     }
     do_check_true(!error);
 
     _("Verify that there's only one bookmark on the server, and it's Thunderbird.");
     // Of course, there's also the Bookmarks Toolbar and Bookmarks Menu...
@@ -257,17 +257,17 @@ add_task(function* test_restorePromptsRe
     _("We found it: " + found);
     do_check_true(found);
 
     _("Have the correct number of IDs locally, too.");
     do_check_eq(count, ["menu", "toolbar", "mobile", "unfiled", folder1_id, bmk1_id].length);
 
     _("Sync again. This'll wipe bookmarks from the server.");
     try {
-      engine.sync();
+      yield sync_engine_and_validate_telem(engine, false);
     } catch(ex) {
       error = ex;
       _("Got error: " + Log.exceptionStr(ex));
     }
     do_check_true(!error);
 
     _("Verify that there's only one bookmark on the server, and it's Firefox.");
     // Of course, there's also the Bookmarks Toolbar and Bookmarks Menu...
--- a/services/sync/tests/unit/test_bookmark_smart_bookmarks.js
+++ b/services/sync/tests/unit/test_bookmark_smart_bookmarks.js
@@ -52,17 +52,17 @@ function serverForFoo(engine) {
     meta: {global: {engines: {bookmarks: {version: engine.version,
                                           syncID: engine.syncID}}}},
     bookmarks: {}
   });
 }
 
 // Verify that Places smart bookmarks have their annotation uploaded and
 // handled locally.
-add_test(function test_annotation_uploaded() {
+add_task(function *test_annotation_uploaded() {
   let server = serverForFoo(engine);
   new SyncTestingInfrastructure(server.server);
 
   let startCount = smartBookmarkCount();
 
   _("Start count is " + startCount);
 
   if (startCount > 0) {
@@ -105,17 +105,17 @@ add_test(function test_annotation_upload
 
   _("Our count has increased since we started.");
   do_check_eq(smartBookmarkCount(), startCount + 1);
 
   _("Sync record to the server.");
   let collection = server.user("foo").collection("bookmarks");
 
   try {
-    engine.sync();
+    yield sync_engine_and_validate_telem(engine, false);
     let wbos = collection.keys(function (id) {
                  return ["menu", "toolbar", "mobile", "unfiled"].indexOf(id) == -1;
                });
     do_check_eq(wbos.length, 1);
 
     _("Verify that the server WBO has the annotation.");
     let serverGUID = wbos[0];
     do_check_eq(serverGUID, guid);
@@ -136,17 +136,17 @@ add_test(function test_annotation_upload
       mostVisitedID, Utils.makeURI("http://something/else"));
     PlacesUtils.annotations.removeItemAnnotation(mostVisitedID,
                                                  SMART_BOOKMARKS_ANNO);
     store.wipe();
     engine.resetClient();
     do_check_eq(smartBookmarkCount(), startCount);
 
     _("Sync. Verify that the downloaded record carries the annotation.");
-    engine.sync();
+    yield sync_engine_and_validate_telem(engine, false);
 
     _("Verify that the Places DB now has an annotated bookmark.");
     _("Our count has increased again.");
     do_check_eq(smartBookmarkCount(), startCount + 1);
 
     _("Find by GUID and verify that it's annotated.");
     let newID = store.idForGUID(serverGUID);
     let newAnnoValue = PlacesUtils.annotations.getItemAnnotation(
--- a/services/sync/tests/unit/test_collections_recovery.js
+++ b/services/sync/tests/unit/test_collections_recovery.js
@@ -28,16 +28,19 @@ add_identity_test(this, function* test_m
 
   let handlers = {
     "/1.1/johndoe/info/collections": maybe_empty(johnHelper.handler),
     "/1.1/johndoe/storage/crypto/keys": johnU("crypto", new ServerWBO("keys").handler()),
     "/1.1/johndoe/storage/meta/global": johnU("meta",   new ServerWBO("global").handler())
   };
   let collections = ["clients", "bookmarks", "forms", "history",
                      "passwords", "prefs", "tabs"];
+  // Disable addon sync because AddonManager won't be initialized here.
+  Service.engineManager.unregister("addons");
+
   for (let coll of collections) {
     handlers["/1.1/johndoe/storage/" + coll] =
       johnU(coll, new ServerCollection({}, true).handler());
   }
   let server = httpd_setup(handlers);
   Service.serverURL = server.baseURI;
 
   try {
@@ -45,32 +48,32 @@ add_identity_test(this, function* test_m
     let orig  = Service._freshStart;
     Service._freshStart = function() {
       _("Called _freshStart.");
       orig.call(Service);
       fresh++;
     };
 
     _("Startup, no meta/global: freshStart called once.");
-    Service.sync();
+    yield sync_and_validate_telem();
     do_check_eq(fresh, 1);
     fresh = 0;
 
     _("Regular sync: no need to freshStart.");
     Service.sync();
     do_check_eq(fresh, 0);
 
     _("Simulate a bad info/collections.");
     delete johnColls.crypto;
-    Service.sync();
+    yield sync_and_validate_telem();
     do_check_eq(fresh, 1);
     fresh = 0;
 
     _("Regular sync: no need to freshStart.");
-    Service.sync();
+    yield sync_and_validate_telem();
     do_check_eq(fresh, 0);
 
   } finally {
     Svc.Prefs.resetBranch("");
     let deferred = Promise.defer();
     server.stop(deferred.resolve);
     yield deferred.promise;
   }
--- a/services/sync/tests/unit/test_corrupt_keys.js
+++ b/services/sync/tests/unit/test_corrupt_keys.js
@@ -54,16 +54,17 @@ add_task(function* test_locally_changed_
       getBrowserState: () => JSON.stringify(myTabs)
     };
 
     setBasicCredentials("johndoe", "password", passphrase);
     Service.serverURL = server.baseURI;
     Service.clusterURL = server.baseURI;
 
     Service.engineManager.register(HistoryEngine);
+    Service.engineManager.unregister("addons");
 
     function corrupt_local_keys() {
       Service.collectionKeys._default.keyPair = [Svc.Crypto.generateRandomKey(),
                                                  Svc.Crypto.generateRandomKey()];
     }
 
     _("Setting meta.");
 
@@ -81,17 +82,17 @@ add_task(function* test_locally_changed_
     serverKeys.encrypt(Service.identity.syncKeyBundle);
     do_check_true(serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success);
 
     // Check that login works.
     do_check_true(Service.login("johndoe", "ilovejane", passphrase));
     do_check_true(Service.isLoggedIn);
 
     // Sync should upload records.
-    Service.sync();
+    yield sync_and_validate_telem();
 
     // Tabs exist.
     _("Tabs modified: " + johndoe.modified("tabs"));
     do_check_true(johndoe.modified("tabs") > 0);
 
     let coll_modified = Service.collectionKeys.lastModified;
 
     // Let's create some server side history records.
@@ -134,17 +135,19 @@ add_task(function* test_locally_changed_
     // Fill local key cache with bad data.
     corrupt_local_keys();
     _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
 
     do_check_eq(hmacErrorCount, 0);
 
     _("HMAC error count: " + hmacErrorCount);
     // Now syncing should succeed, after one HMAC error.
-    Service.sync();
+    let ping = yield wait_for_ping(() => Service.sync(), true);
+    equal(ping.engines.find(e => e.name == "history").incoming.applied, 5);
+
     do_check_eq(hmacErrorCount, 1);
     _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
 
     // And look! We downloaded history!
     let store = Service.engineManager.get("history")._store;
     do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--0"));
     do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--1"));
     do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--2"));
@@ -178,17 +181,19 @@ add_task(function* test_locally_changed_
 
     _("Server key time hasn't changed.");
     do_check_eq(johndoe.modified("crypto"), old_key_time);
 
     _("Resetting HMAC error timer.");
     Service.lastHMACEvent = 0;
 
     _("Syncing...");
-    Service.sync();
+    ping = yield sync_and_validate_telem(true);
+
+    do_check_eq(ping.engines.find(e => e.name == "history").incoming.failed, 5);
     _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
     _("Server keys have been updated, and we skipped over 5 more HMAC errors without adjusting history.");
     do_check_true(johndoe.modified("crypto") > old_key_time);
     do_check_eq(hmacErrorCount, 6);
     do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--5"));
     do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--6"));
     do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--7"));
     do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--8"));
@@ -199,16 +204,17 @@ add_task(function* test_locally_changed_
     server.stop(deferred.resolve);
     yield deferred.promise;
   }
 });
 
 function run_test() {
   let logger = Log.repository.rootLogger;
   Log.repository.rootLogger.addAppender(new Log.DumpAppender());
+  validate_all_future_pings();
 
   ensureLegacyIdentityManager();
 
   run_next_test();
 }
 
 /**
  * Asynchronously check a url is visited.
--- a/services/sync/tests/unit/test_engine.js
+++ b/services/sync/tests/unit/test_engine.js
@@ -166,20 +166,22 @@ add_test(function test_enabled() {
 
 add_test(function test_sync() {
   let engine = new SteamEngine("Steam", Service);
   try {
     _("Engine.sync doesn't call _sync if it's not enabled");
     do_check_false(engine.enabled);
     do_check_false(engine.wasSynced);
     engine.sync();
+
     do_check_false(engine.wasSynced);
 
     _("Engine.sync calls _sync if it's enabled");
     engine.enabled = true;
+
     engine.sync();
     do_check_true(engine.wasSynced);
     do_check_eq(engineObserver.topics[0], "weave:engine:sync:start");
     do_check_eq(engineObserver.topics[1], "weave:engine:sync:finish");
     run_next_test();
   } finally {
     Svc.Prefs.resetBranch("");
     engine.wasSynced = false;
--- a/services/sync/tests/unit/test_errorhandler.js
+++ b/services/sync/tests/unit/test_errorhandler.js
@@ -161,17 +161,17 @@ function clean() {
   errorHandler.didReportProlongedError = false;
 }
 
 add_identity_test(this, function* test_401_logout() {
   let server = sync_httpd_setup();
   yield setUp(server);
 
   // By calling sync, we ensure we're logged in.
-  Service.sync();
+  yield sync_and_validate_telem();
   do_check_eq(Status.sync, SYNC_SUCCEEDED);
   do_check_true(Service.isLoggedIn);
 
   let deferred = Promise.defer();
   Svc.Obs.add("weave:service:sync:error", onSyncError);
   function onSyncError() {
     _("Got weave:service:sync:error in first sync.");
     Svc.Obs.remove("weave:service:sync:error", onSyncError);
@@ -196,32 +196,39 @@ add_identity_test(this, function* test_4
     Svc.Obs.add("weave:service:login:error", onLoginError);
   }
 
   // Make sync fail due to login rejected.
   yield configureIdentity({username: "janedoe"});
   Service._updateCachedURLs();
 
   _("Starting first sync.");
-  Service.sync();
+  let ping = yield sync_and_validate_telem(true);
+  deepEqual(ping.failureReason, { name: "httperror", code: 401 });
   _("First sync done.");
   yield deferred.promise;
 });
 
 add_identity_test(this, function* test_credentials_changed_logout() {
   let server = sync_httpd_setup();
   yield setUp(server);
 
   // By calling sync, we ensure we're logged in.
-  Service.sync();
+  yield sync_and_validate_telem();
   do_check_eq(Status.sync, SYNC_SUCCEEDED);
   do_check_true(Service.isLoggedIn);
 
   generateCredentialsChangedFailure();
-  Service.sync();
+
+  let ping = yield sync_and_validate_telem(true);
+  equal(ping.status.sync, CREDENTIALS_CHANGED);
+  deepEqual(ping.failureReason, {
+    name: "unexpectederror",
+    error: "Error: Aborting sync, remote setup failed"
+  });
 
   do_check_eq(Status.sync, CREDENTIALS_CHANGED);
   do_check_false(Service.isLoggedIn);
 
   // Clean up.
   Service.startOver();
   let deferred = Promise.defer();
   server.stop(deferred.resolve);
@@ -534,23 +541,30 @@ add_identity_test(this, function* test_s
   do_check_true(Service.isLoggedIn);
 
   generateCredentialsChangedFailure();
 
   let deferred = Promise.defer();
   Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
     Svc.Obs.remove("weave:ui:sync:error", onSyncError);
     do_check_eq(Status.sync, CREDENTIALS_CHANGED);
-
-    clean();
-    server.stop(deferred.resolve);
+    // If we clean this tick, telemetry won't get the right error
+    server.stop(() => {
+      clean();
+      deferred.resolve();
+    });
   });
 
   setLastSync(NON_PROLONGED_ERROR_DURATION);
-  errorHandler.syncAndReportErrors();
+  let ping = yield wait_for_ping(() => errorHandler.syncAndReportErrors(), true);
+  equal(ping.status.sync, CREDENTIALS_CHANGED);
+  deepEqual(ping.failureReason, {
+    name: "unexpectederror",
+    error: "Error: Aborting sync, remote setup failed"
+  });
   yield deferred.promise;
 });
 
 // XXX - how to arrange for 'Service.identity.basicPassword = null;' in
 // an fxaccounts environment?
 add_task(function* test_login_syncAndReportErrors_prolonged_non_network_error() {
   // Test prolonged, non-network errors are
   // reported when calling syncAndReportErrors.
@@ -584,23 +598,30 @@ add_identity_test(this, function* test_s
   do_check_true(Service.isLoggedIn);
 
   generateCredentialsChangedFailure();
 
   let deferred = Promise.defer();
   Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
     Svc.Obs.remove("weave:ui:sync:error", onSyncError);
     do_check_eq(Status.sync, CREDENTIALS_CHANGED);
-
-    clean();
-    server.stop(deferred.resolve);
+    // If we clean this tick, telemetry won't get the right error
+    server.stop(() => {
+      clean();
+      deferred.resolve();
+    });
   });
 
   setLastSync(PROLONGED_ERROR_DURATION);
-  errorHandler.syncAndReportErrors();
+  let ping = yield wait_for_ping(() => errorHandler.syncAndReportErrors(), true);
+  equal(ping.status.sync, CREDENTIALS_CHANGED);
+  deepEqual(ping.failureReason, {
+    name: "unexpectederror",
+    error: "Error: Aborting sync, remote setup failed"
+  });
   yield deferred.promise;
 });
 
 add_identity_test(this, function* test_login_syncAndReportErrors_network_error() {
   // Test network errors are reported when calling syncAndReportErrors.
   yield configureIdentity({username: "broken.wipe"});
   Service.serverURL  = fakeServerUrl;
   Service.clusterURL = fakeServerUrl;
@@ -710,23 +731,30 @@ add_task(function* test_sync_prolonged_n
 
   generateCredentialsChangedFailure();
 
   let deferred = Promise.defer();
   Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
     Svc.Obs.remove("weave:ui:sync:error", onSyncError);
     do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
     do_check_true(errorHandler.didReportProlongedError);
-
-    clean();
-    server.stop(deferred.resolve);
+    server.stop(() => {
+      clean();
+      deferred.resolve();
+    });
   });
 
   setLastSync(PROLONGED_ERROR_DURATION);
-  Service.sync();
+
+  let ping = yield sync_and_validate_telem(true);
+  equal(ping.status.sync, PROLONGED_SYNC_FAILURE);
+  deepEqual(ping.failureReason, {
+    name: "unexpectederror",
+    error: "Error: Aborting sync, remote setup failed"
+  });
   yield deferred.promise;
 });
 
 add_identity_test(this, function* test_login_prolonged_network_error() {
   // Test prolonged, network errors are reported
   yield configureIdentity({username: "johndoe"});
   Service.serverURL  = fakeServerUrl;
   Service.clusterURL = fakeServerUrl;
@@ -875,22 +903,27 @@ add_identity_test(this, function* test_s
   Svc.Obs.add("weave:ui:sync:finish", function onSyncFinish() {
     Svc.Obs.remove("weave:ui:sync:finish", onSyncFinish);
 
     do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
     do_check_eq(Status.sync, SERVER_MAINTENANCE);
     do_check_false(errorHandler.didReportProlongedError);
 
     Svc.Obs.remove("weave:ui:sync:error", onSyncError);
-    clean();
-    server.stop(deferred.resolve);
+    server.stop(() => {
+      clean();
+      deferred.resolve();
+    })
   });
 
   setLastSync(NON_PROLONGED_ERROR_DURATION);
-  Service.sync();
+  let ping = yield sync_and_validate_telem(true);
+  equal(ping.status.sync, SERVER_MAINTENANCE);
+  deepEqual(ping.engines.find(e => e.failureReason).failureReason, { name: "httperror", code: 503 })
+
   yield deferred.promise;
 });
 
 add_identity_test(this, function* test_info_collections_login_server_maintenance_error() {
   // Test info/collections server maintenance errors are not reported.
   let server = sync_httpd_setup();
   yield setUp(server);
 
@@ -1035,24 +1068,29 @@ add_task(function* test_sync_prolonged_s
 
   let deferred = Promise.defer();
   Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() {
     Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
     do_check_eq(Status.service, SYNC_FAILED);
     do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
     do_check_true(errorHandler.didReportProlongedError);
 
-    clean();
-    server.stop(deferred.resolve);
+    server.stop(() => {
+      clean();
+      deferred.resolve();
+    });
   });
 
   do_check_eq(Status.service, STATUS_OK);
 
   setLastSync(PROLONGED_ERROR_DURATION);
-  Service.sync();
+  let ping = yield sync_and_validate_telem(true);
+  deepEqual(ping.status.sync, PROLONGED_SYNC_FAILURE);
+  deepEqual(ping.engines.find(e => e.failureReason).failureReason,
+            { name: "httperror", code: 503 });
   yield deferred.promise;
 });
 
 add_identity_test(this, function* test_info_collections_login_prolonged_server_maintenance_error(){
   // Test info/collections prolonged server maintenance errors are reported.
   let server = sync_httpd_setup();
   yield setUp(server);
 
@@ -1259,27 +1297,29 @@ add_identity_test(this, function* test_w
   Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() {
     Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
     do_check_true(Status.enforceBackoff);
     do_check_eq(backoffInterval, 42);
     do_check_eq(Status.service, SYNC_FAILED);
     do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
     do_check_eq(Svc.Prefs.get("firstSync"), "wipeRemote");
     do_check_true(errorHandler.didReportProlongedError);
-
-    clean();
-    server.stop(deferred.resolve);
+    server.stop(() => {
+      clean();
+      deferred.resolve();
+    });
   });
 
   do_check_false(Status.enforceBackoff);
   do_check_eq(Status.service, STATUS_OK);
 
   Svc.Prefs.set("firstSync", "wipeRemote");
   setLastSync(PROLONGED_ERROR_DURATION);
-  Service.sync();
+  let ping = yield sync_and_validate_telem(true);
+  deepEqual(ping.failureReason, { name: "httperror", code: 503 });
   yield deferred.promise;
 });
 
 add_task(function* test_sync_syncAndReportErrors_server_maintenance_error() {
   // Test server maintenance errors are reported
   // when calling syncAndReportErrors.
   let server = sync_httpd_setup();
   yield setUp(server);
@@ -1765,20 +1805,20 @@ add_identity_test(this, function* test_w
   setLastSync(PROLONGED_ERROR_DURATION);
   errorHandler.syncAndReportErrors();
   yield deferred.promise;
 });
 
 add_task(function* test_sync_engine_generic_fail() {
   let server = sync_httpd_setup();
 
-  let engine = engineManager.get("catapult");
+let engine = engineManager.get("catapult");
   engine.enabled = true;
   engine.sync = function sync() {
-    Svc.Obs.notify("weave:engine:sync:error", "", "catapult");
+    Svc.Obs.notify("weave:engine:sync:error", ENGINE_UNKNOWN_FAIL, "catapult");
   };
 
   let log = Log.repository.getLogger("Sync.ErrorHandler");
   Svc.Prefs.set("log.appender.file.logOnError", true);
 
   do_check_eq(Status.engines["catapult"], undefined);
 
   let deferred = Promise.defer();
@@ -1803,22 +1843,28 @@ add_task(function* test_sync_engine_gene
       let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
       do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName);
 
       clean();
 
       let syncErrors = sumHistogram("WEAVE_ENGINE_SYNC_ERRORS", { key: "catapult" });
       do_check_true(syncErrors, 1);
 
-      server.stop(deferred.resolve);
+      server.stop(() => {
+        clean();
+        deferred.resolve();
+      });
     });
   });
 
   do_check_true(yield setUp(server));
-  Service.sync();
+  let ping = yield sync_and_validate_telem(true);
+  deepEqual(ping.status.service, SYNC_FAILED_PARTIAL);
+  deepEqual(ping.engines.find(e => e.status).status, ENGINE_UNKNOWN_FAIL);
+
   yield deferred.promise;
 });
 
 add_test(function test_logs_on_sync_error_despite_shouldReportError() {
   _("Ensure that an error is still logged when weave:service:sync:error " +
     "is notified, despite shouldReportError returning false.");
 
   let log = Log.repository.getLogger("Sync.ErrorHandler");
--- a/services/sync/tests/unit/test_errorhandler_filelog.js
+++ b/services/sync/tests/unit/test_errorhandler_filelog.js
@@ -30,16 +30,18 @@ function setLastSync(lastSyncValue) {
 function run_test() {
   initTestLogging("Trace");
 
   Log.repository.getLogger("Sync.LogManager").level = Log.Level.Trace;
   Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
   Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
   Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
 
+  validate_all_future_pings();
+
   run_next_test();
 }
 
 add_test(function test_noOutput() {
   // Ensure that the log appender won't print anything.
   errorHandler._logManager._fileAppender.level = Log.Level.Fatal + 1;
 
   // Clear log output from startup.
--- a/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js
+++ b/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js
@@ -271,11 +271,12 @@ add_identity_test(this, function* test_r
   } finally {
     Status.resetSync();
     Service.startOver();
   }
   yield promiseStopServer(server);
 });
 
 function run_test() {
+  validate_all_future_pings();
   engineManager.register(CatapultEngine);
   run_next_test();
 }
--- a/services/sync/tests/unit/test_hmac_error.js
+++ b/services/sync/tests/unit/test_hmac_error.js
@@ -44,17 +44,17 @@ function shared_setup() {
   let global      = new ServerWBO("global", {engines: engines});
   let keysWBO     = new ServerWBO("keys");
   let rotaryColl  = new ServerCollection({}, true);
   let clientsColl = new ServerCollection({}, true);
 
   return [engine, rotaryColl, clientsColl, keysWBO, global];
 }
 
-add_test(function hmac_error_during_404() {
+add_task(function *hmac_error_during_404() {
   _("Attempt to replicate the HMAC error setup.");
   let [engine, rotaryColl, clientsColl, keysWBO, global] = shared_setup();
 
   // Hand out 404s for crypto/keys.
   let keysHandler    = keysWBO.handler();
   let key404Counter  = 0;
   let keys404Handler = function (request, response) {
     if (key404Counter > 0) {
@@ -78,31 +78,32 @@ add_test(function hmac_error_during_404(
     "/1.1/foo/storage/rotary": upd("rotary", rotaryColl.handler())
   };
 
   let server = sync_httpd_setup(handlers);
   Service.serverURL = server.baseURI;
 
   try {
     _("Syncing.");
-    Service.sync();
+    yield sync_and_validate_telem();
+
     _("Partially resetting client, as if after a restart, and forcing redownload.");
     Service.collectionKeys.clear();
     engine.lastSync = 0;        // So that we redownload records.
     key404Counter = 1;
     _("---------------------------");
-    Service.sync();
+    yield sync_and_validate_telem();
     _("---------------------------");
 
     // Two rotary items, one client record... no errors.
     do_check_eq(hmacErrorCount, 0)
   } finally {
     Svc.Prefs.resetBranch("");
     Service.recordManager.clearCache();
-    server.stop(run_next_test);
+    yield new Promise(resolve => server.stop(resolve));
   }
 });
 
 add_test(function hmac_error_during_node_reassignment() {
   _("Attempt to replicate an HMAC error during node reassignment.");
   let [engine, rotaryColl, clientsColl, keysWBO, global] = shared_setup();
 
   let collectionsHelper = track_collections_helper();
--- a/services/sync/tests/unit/test_node_reassignment.js
+++ b/services/sync/tests/unit/test_node_reassignment.js
@@ -18,17 +18,17 @@ Service.engineManager.clear();
 function run_test() {
   Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace;
   Log.repository.getLogger("Sync.ErrorHandler").level  = Log.Level.Trace;
   Log.repository.getLogger("Sync.Resource").level      = Log.Level.Trace;
   Log.repository.getLogger("Sync.RESTRequest").level   = Log.Level.Trace;
   Log.repository.getLogger("Sync.Service").level       = Log.Level.Trace;
   Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
   initTestLogging();
-
+  validate_all_future_pings();
   ensureLegacyIdentityManager();
 
   Service.engineManager.register(RotaryEngine);
 
   // None of the failures in this file should result in a UI error.
   function onUIError() {
     do_throw("Errors should not be presented in the UI.");
   }
--- a/services/sync/tests/unit/test_service_startup.js
+++ b/services/sync/tests/unit/test_service_startup.js
@@ -5,16 +5,17 @@ Cu.import("resource://services-common/ob
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 Svc.Prefs.set("registerEngines", "Tab,Bookmarks,Form,History");
 Cu.import("resource://services-sync/service.js");
 
 function run_test() {
+  validate_all_future_pings();
   _("When imported, Service.onStartup is called");
   initTestLogging("Trace");
 
   let xps = Cc["@mozilla.org/weave/service;1"]
               .getService(Ci.nsISupports)
               .wrappedJSObject;
   do_check_false(xps.enabled);
 
--- a/services/sync/tests/unit/test_service_sync_locked.js
+++ b/services/sync/tests/unit/test_service_sync_locked.js
@@ -1,15 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
 function run_test() {
+  validate_all_future_pings();
   let debug = [];
   let info  = [];
 
   function augmentLogger(old) {
     let d = old.debug;
     let i = old.info;
     // For the purposes of this test we don't need to do full formatting
     // of the 2nd param, as the ones we care about are always strings.
--- a/services/sync/tests/unit/test_service_sync_remoteSetup.js
+++ b/services/sync/tests/unit/test_service_sync_remoteSetup.js
@@ -5,16 +5,17 @@ Cu.import("resource://gre/modules/Log.js
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/keys.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/fakeservices.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 function run_test() {
+  validate_all_future_pings();
   let logger = Log.repository.rootLogger;
   Log.repository.rootLogger.addAppender(new Log.DumpAppender());
 
   let guidSvc = new FakeGUIDService();
   let clients = new ServerCollection();
   let meta_global = new ServerWBO('global');
 
   let collectionsHelper = track_collections_helper();
--- a/services/sync/tests/unit/test_service_sync_specified.js
+++ b/services/sync/tests/unit/test_service_sync_specified.js
@@ -72,16 +72,17 @@ function setUp() {
   });
   new SyncTestingInfrastructure(server, "johndoe", "ilovejane",
                                 "abcdeabcdeabcdeabcdeabcdea");
   return server;
 }
 
 function run_test() {
   initTestLogging("Trace");
+  validate_all_future_pings();
   Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
   Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
 
   run_next_test();
 }
 
 add_test(function test_noEngines() {
   _("Test: An empty array of engines to sync does nothing.");
--- a/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js
+++ b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js
@@ -81,16 +81,17 @@ function setUp(server) {
 
 const PAYLOAD = 42;
 
 
 function run_test() {
   initTestLogging("Trace");
   Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
   Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
+  validate_all_future_pings();
 
   run_next_test();
 }
 
 add_test(function test_newAccount() {
   _("Test: New account does not disable locally enabled engines.");
   let engine = Service.engineManager.get("steam");
   let server = sync_httpd_setup({
--- a/services/sync/tests/unit/test_syncengine_sync.js
+++ b/services/sync/tests/unit/test_syncengine_sync.js
@@ -10,23 +10,32 @@ Cu.import("resource://services-sync/serv
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/rotaryengine.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 function makeRotaryEngine() {
   return new RotaryEngine(Service);
 }
 
-function cleanAndGo(server) {
+function clean() {
   Svc.Prefs.resetBranch("");
   Svc.Prefs.set("log.logger.engine.rotary", "Trace");
   Service.recordManager.clearCache();
+}
+
+function cleanAndGo(server) {
+  clean();
   server.stop(run_next_test);
 }
 
+function promiseClean(server) {
+  clean();
+  return new Promise(resolve => server.stop(resolve));
+}
+
 function configureService(server, username, password) {
   Service.clusterURL = server.baseURI;
 
   Service.identity.account = username || "foo";
   Service.identity.basicPassword = password || "password";
 }
 
 function createServerAndConfigureClient() {
@@ -662,17 +671,17 @@ add_test(function test_processIncoming_m
     }
 
   } finally {
     cleanAndGo(server);
   }
 });
 
 
-add_test(function test_processIncoming_store_toFetch() {
+add_task(function *test_processIncoming_store_toFetch() {
   _("If processIncoming fails in the middle of a batch on mobile, state is saved in toFetch and lastSync.");
   Service.identity.username = "foo";
   Svc.Prefs.set("client.type", "mobile");
 
   // A collection that throws at the fourth get.
   let collection = new ServerCollection();
   collection._get_calls = 0;
   collection._get = collection.get;
@@ -709,33 +718,32 @@ add_test(function test_processIncoming_s
   try {
 
     // Confirm initial environment
     do_check_eq(engine.lastSync, 0);
     do_check_empty(engine._store.items);
 
     let error;
     try {
-      engine.sync();
+      yield sync_engine_and_validate_telem(engine, true);
     } catch (ex) {
       error = ex;
     }
-    do_check_true(!!error);
 
     // Only the first two batches have been applied.
     do_check_eq(Object.keys(engine._store.items).length,
                 MOBILE_BATCH_SIZE * 2);
 
     // The third batch is stuck in toFetch. lastSync has been moved forward to
     // the last successful item's timestamp.
     do_check_eq(engine.toFetch.length, MOBILE_BATCH_SIZE);
     do_check_eq(engine.lastSync, collection.wbo("record-no-99").modified);
 
   } finally {
-    cleanAndGo(server);
+    yield promiseClean(server);
   }
 });
 
 
 add_test(function test_processIncoming_resume_toFetch() {
   _("toFetch and previousFailed items left over from previous syncs are fetched on the next sync, along with new items.");
   Service.identity.username = "foo";
 
@@ -1216,17 +1224,17 @@ add_test(function test_processIncoming_f
     do_check_eq(batchDownload(BOGUS_RECORDS.length), 4);
 
   } finally {
     cleanAndGo(server);
   }
 });
 
 
-add_test(function test_processIncoming_decrypt_failed() {
+add_task(function *test_processIncoming_decrypt_failed() {
   _("Ensure that records failing to decrypt are either replaced or refetched.");
 
   Service.identity.username = "foo";
 
   // Some good and some bogus records. One doesn't contain valid JSON,
   // the other will throw during decrypt.
   let collection = new ServerCollection();
   collection._wbos.flying = new ServerWBO(
@@ -1275,31 +1283,34 @@ add_test(function test_processIncoming_d
     let observerData;
     Svc.Obs.add("weave:engine:sync:applied", function onApplied(subject, data) {
       Svc.Obs.remove("weave:engine:sync:applied", onApplied);
       observerSubject = subject;
       observerData = data;
     });
 
     engine.lastSync = collection.wbo("nojson").modified - 1;
-    engine.sync();
+    let ping = yield sync_engine_and_validate_telem(engine, true);
+    do_check_eq(ping.engines[0].incoming.applied, 2);
+    do_check_eq(ping.engines[0].incoming.failed, 4);
+    do_check_eq(ping.engines[0].incoming.newFailed, 4);
 
     do_check_eq(engine.previousFailed.length, 4);
     do_check_eq(engine.previousFailed[0], "nojson");
     do_check_eq(engine.previousFailed[1], "nojson2");
     do_check_eq(engine.previousFailed[2], "nodecrypt");
     do_check_eq(engine.previousFailed[3], "nodecrypt2");
 
     // Ensure the observer was notified
     do_check_eq(observerData, engine.name);
     do_check_eq(observerSubject.applied, 2);
     do_check_eq(observerSubject.failed, 4);
 
   } finally {
-    cleanAndGo(server);
+    yield promiseClean(server);
   }
 });
 
 
 add_test(function test_uploadOutgoing_toEmptyServer() {
   _("SyncEngine._uploadOutgoing uploads new records to server");
 
   Service.identity.username = "foo";
@@ -1353,17 +1364,17 @@ add_test(function test_uploadOutgoing_to
     do_check_eq(collection.payload("flying"), undefined);
 
   } finally {
     cleanAndGo(server);
   }
 });
 
 
-add_test(function test_uploadOutgoing_failed() {
+add_task(function *test_uploadOutgoing_failed() {
   _("SyncEngine._uploadOutgoing doesn't clear the tracker of objects that failed to upload.");
 
   Service.identity.username = "foo";
   let collection = new ServerCollection();
   // We only define the "flying" WBO on the server, not the "scotsman"
   // and "peppercorn" ones.
   collection._wbos.flying = new ServerWBO('flying');
 
@@ -1396,32 +1407,32 @@ add_test(function test_uploadOutgoing_fa
     // Confirm initial environment
     do_check_eq(engine.lastSyncLocal, 0);
     do_check_eq(collection.payload("flying"), undefined);
     do_check_eq(engine._tracker.changedIDs['flying'], FLYING_CHANGED);
     do_check_eq(engine._tracker.changedIDs['scotsman'], SCOTSMAN_CHANGED);
     do_check_eq(engine._tracker.changedIDs['peppercorn'], PEPPERCORN_CHANGED);
 
     engine.enabled = true;
-    engine.sync();
+    yield sync_engine_and_validate_telem(engine, true);
 
     // Local timestamp has been set.
     do_check_true(engine.lastSyncLocal > 0);
 
     // Ensure the 'flying' record has been uploaded and is no longer marked.
     do_check_true(!!collection.payload("flying"));
     do_check_eq(engine._tracker.changedIDs['flying'], undefined);
 
     // The 'scotsman' and 'peppercorn' records couldn't be uploaded so
     // they weren't cleared from the tracker.
     do_check_eq(engine._tracker.changedIDs['scotsman'], SCOTSMAN_CHANGED);
     do_check_eq(engine._tracker.changedIDs['peppercorn'], PEPPERCORN_CHANGED);
 
   } finally {
-    cleanAndGo(server);
+    yield promiseClean(server);
   }
 });
 
 
 add_test(function test_uploadOutgoing_MAX_UPLOAD_RECORDS() {
   _("SyncEngine._uploadOutgoing uploads in batches of MAX_UPLOAD_RECORDS");
 
   Service.identity.username = "foo";
@@ -1694,17 +1705,17 @@ add_test(function test_syncFinish_delete
     do_check_eq(engine._delete.ids, undefined);
 
   } finally {
     cleanAndGo(server);
   }
 });
 
 
-add_test(function test_sync_partialUpload() {
+add_task(function *test_sync_partialUpload() {
   _("SyncEngine.sync() keeps changedIDs that couldn't be uploaded.");
 
   Service.identity.username = "foo";
 
   let collection = new ServerCollection();
   let server = sync_httpd_setup({
       "/1.1/foo/storage/rotary": collection.handler()
   });
@@ -1742,21 +1753,22 @@ add_test(function test_sync_partialUploa
   meta_global.payload.engines = {rotary: {version: engine.version,
                                          syncID: engine.syncID}};
 
   try {
 
     engine.enabled = true;
     let error;
     try {
-      engine.sync();
+      yield sync_engine_and_validate_telem(engine, true);
     } catch (ex) {
       error = ex;
     }
-    do_check_true(!!error);
+
+    ok(!!error);
 
     // The timestamp has been updated.
     do_check_true(engine.lastSyncLocal > 456);
 
     for (let i = 0; i < 234; i++) {
       let id = 'record-no-' + i;
       // Ensure failed records are back in the tracker:
       // * records no. 23 and 42 were rejected by the server,
@@ -1764,17 +1776,17 @@ add_test(function test_sync_partialUploa
       //   hard on the 3rd upload.
       if ((i == 23) || (i == 42) || (i >= 200))
         do_check_eq(engine._tracker.changedIDs[id], i);
       else
         do_check_false(id in engine._tracker.changedIDs);
     }
 
   } finally {
-    cleanAndGo(server);
+    yield promiseClean(server);
   }
 });
 
 add_test(function test_canDecrypt_noCryptoKeys() {
   _("SyncEngine.canDecrypt returns false if the engine fails to decrypt items on the server, e.g. due to a missing crypto key collection.");
   Service.identity.username = "foo";
 
   // Wipe collection keys so we can test the desired scenario.
--- a/services/sync/tests/unit/test_syncscheduler.js
+++ b/services/sync/tests/unit/test_syncscheduler.js
@@ -85,16 +85,17 @@ function cleanUpAndGo(server) {
   return deferred.promise;
 }
 
 function run_test() {
   initTestLogging("Trace");
 
   Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
   Log.repository.getLogger("Sync.scheduler").level = Log.Level.Trace;
+  validate_all_future_pings();
 
   // The scheduler checks Weave.fxaEnabled to determine whether to use
   // FxA defaults or legacy defaults.  As .fxaEnabled checks the username, we
   // set a username here then reset the default to ensure they are used.
   ensureLegacyIdentityManager();
   setBasicCredentials("johndoe");
   scheduler.setDefaults();
 
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_telemetry.js
@@ -0,0 +1,409 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://services-common/observers.js");
+Cu.import("resource://services-sync/telemetry.js");
+Cu.import("resource://services-sync/service.js");
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/resource.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/engines/bookmarks.js");
+Cu.import("resource://services-sync/engines/clients.js");
+Cu.import("resource://testing-common/services/sync/utils.js");
+Cu.import("resource://testing-common/services/sync/fxa_utils.js");
+Cu.import("resource://testing-common/services/sync/rotaryengine.js");
+
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://services-sync/util.js");
+
+initTestLogging("Trace");
+
+function SteamStore(engine) {
+  Store.call(this, "Steam", engine);
+}
+
+SteamStore.prototype = {
+  __proto__: Store.prototype,
+};
+
+function SteamTracker(name, engine) {
+  Tracker.call(this, name || "Steam", engine);
+}
+
+SteamTracker.prototype = {
+  __proto__: Tracker.prototype
+};
+
+function SteamEngine(service) {
+  Engine.call(this, "steam", service);
+}
+
+SteamEngine.prototype = {
+  __proto__: Engine.prototype,
+  _storeObj: SteamStore,
+  _trackerObj: SteamTracker,
+  _errToThrow: null,
+  _sync() {
+    if (this._errToThrow) {
+      throw this._errToThrow;
+    }
+  }
+};
+
+function cleanAndGo(server) {
+  Svc.Prefs.resetBranch("");
+  Svc.Prefs.set("log.logger.engine.rotary", "Trace");
+  Service.recordManager.clearCache();
+  return new Promise(resolve => server.stop(resolve));
+}
+
+// Avoid addon manager complaining about not being initialized
+Service.engineManager.unregister("addons");
+
+add_identity_test(this, function *test_basic() {
+  let helper = track_collections_helper();
+  let upd = helper.with_updated_collection;
+
+  yield configureIdentity({ username: "johndoe" });
+  let handlers = {
+    "/1.1/johndoe/info/collections": helper.handler,
+    "/1.1/johndoe/storage/crypto/keys": upd("crypto", new ServerWBO("keys").handler()),
+    "/1.1/johndoe/storage/meta/global": upd("meta",  new ServerWBO("global").handler())
+  };
+
+  let collections = ["clients", "bookmarks", "forms", "history", "passwords", "prefs", "tabs"];
+
+  for (let coll of collections) {
+    handlers["/1.1/johndoe/storage/" + coll] = upd(coll, new ServerCollection({}, true).handler());
+  }
+
+  let server = httpd_setup(handlers);
+  Service.serverURL = server.baseURI;
+
+  yield sync_and_validate_telem(true);
+
+  yield new Promise(resolve => server.stop(resolve));
+});
+
+add_task(function* test_processIncoming_error() {
+  let engine = new BookmarksEngine(Service);
+  let store  = engine._store;
+  let server = serverForUsers({"foo": "password"}, {
+    meta: {global: {engines: {bookmarks: {version: engine.version,
+                                           syncID: engine.syncID}}}},
+    bookmarks: {}
+  });
+  new SyncTestingInfrastructure(server.server);
+  let collection = server.user("foo").collection("bookmarks");
+  try {
+    // Create a bogus record that when synced down will provoke a
+    // network error which in turn provokes an exception in _processIncoming.
+    const BOGUS_GUID = "zzzzzzzzzzzz";
+    let bogus_record = collection.insert(BOGUS_GUID, "I'm a bogus record!");
+    bogus_record.get = function get() {
+      throw "Sync this!";
+    };
+    // Make the 10 minutes old so it will only be synced in the toFetch phase.
+    bogus_record.modified = Date.now() / 1000 - 60 * 10;
+    engine.lastSync = Date.now() / 1000 - 60;
+    engine.toFetch = [BOGUS_GUID];
+
+    let error, ping;
+    try {
+      yield sync_engine_and_validate_telem(engine, true, errPing => ping = errPing);
+    } catch(ex) {
+      error = ex;
+    }
+    ok(!!error);
+    ok(!!ping);
+    equal(ping.uid, "0".repeat(32));
+    deepEqual(ping.failureReason, {
+      name: "othererror",
+      error: "error.engine.reason.record_download_fail"
+    });
+
+    equal(ping.engines.length, 1);
+    equal(ping.engines[0].name, "bookmarks");
+    deepEqual(ping.engines[0].failureReason, {
+      name: "othererror",
+      error: "error.engine.reason.record_download_fail"
+    });
+
+  } finally {
+    store.wipe();
+    yield cleanAndGo(server);
+  }
+});
+
+add_task(function *test_uploading() {
+  let engine = new BookmarksEngine(Service);
+  let store  = engine._store;
+  let server = serverForUsers({"foo": "password"}, {
+    meta: {global: {engines: {bookmarks: {version: engine.version,
+                                           syncID: engine.syncID}}}},
+    bookmarks: {}
+  });
+  new SyncTestingInfrastructure(server.server);
+
+  let parent = PlacesUtils.toolbarFolderId;
+  let uri = Utils.makeURI("http://getfirefox.com/");
+  let title = "Get Firefox";
+
+  let bmk_id = PlacesUtils.bookmarks.insertBookmark(parent, uri,
+    PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
+
+  let guid = store.GUIDForId(bmk_id);
+  let record = store.createRecord(guid);
+
+  let collection = server.user("foo").collection("bookmarks");
+  try {
+    let ping = yield sync_engine_and_validate_telem(engine, false);
+    ok(!!ping);
+    equal(ping.engines.length, 1);
+    equal(ping.engines[0].name, "bookmarks");
+    ok(!!ping.engines[0].outgoing);
+    greater(ping.engines[0].outgoing[0].sent, 0)
+    ok(!ping.engines[0].incoming);
+
+    PlacesUtils.bookmarks.setItemTitle(bmk_id, "New Title");
+
+    store.wipe();
+    engine.resetClient();
+
+    ping = yield sync_engine_and_validate_telem(engine, false);
+
+    equal(ping.engines.length, 1);
+    equal(ping.engines[0].name, "bookmarks");
+    equal(ping.engines[0].outgoing.length, 1);
+    ok(!!ping.engines[0].incoming);
+
+  } finally {
+    // Clean up.
+    store.wipe();
+    yield cleanAndGo(server);
+  }
+});
+
+add_task(function *test_upload_failed() {
+  Service.identity.username = "foo";
+  let collection = new ServerCollection();
+  collection._wbos.flying = new ServerWBO('flying');
+
+  let server = sync_httpd_setup({
+      "/1.1/foo/storage/rotary": collection.handler()
+  });
+
+  let syncTesting = new SyncTestingInfrastructure(server);
+
+  let engine = new RotaryEngine(Service);
+  engine.lastSync = 123; // needs to be non-zero so that tracker is queried
+  engine.lastSyncLocal = 456;
+  engine._store.items = {
+    flying: "LNER Class A3 4472",
+    scotsman: "Flying Scotsman",
+    peppercorn: "Peppercorn Class"
+  };
+  const FLYING_CHANGED = 12345;
+  const SCOTSMAN_CHANGED = 23456;
+  const PEPPERCORN_CHANGED = 34567;
+  engine._tracker.addChangedID("flying", FLYING_CHANGED);
+  engine._tracker.addChangedID("scotsman", SCOTSMAN_CHANGED);
+  engine._tracker.addChangedID("peppercorn", PEPPERCORN_CHANGED);
+
+  let meta_global = Service.recordManager.set(engine.metaURL, new WBORecord(engine.metaURL));
+  meta_global.payload.engines = { rotary: { version: engine.version, syncID: engine.syncID } };
+
+  try {
+    engine.enabled = true;
+    let ping = yield sync_engine_and_validate_telem(engine, true);
+    ok(!!ping);
+    equal(ping.engines.length, 1);
+    equal(ping.engines[0].incoming, null);
+    deepEqual(ping.engines[0].outgoing, [{ sent: 3, failed: 2 }]);
+    engine.lastSync = 123;
+    engine.lastSyncLocal = 456;
+
+    ping = yield sync_engine_and_validate_telem(engine, true);
+    ok(!!ping);
+    equal(ping.engines.length, 1);
+    equal(ping.engines[0].incoming.reconciled, 1);
+    deepEqual(ping.engines[0].outgoing, [{ sent: 2, failed: 2 }]);
+
+  } finally {
+    yield cleanAndGo(server);
+  }
+});
+
+add_task(function *test_sync_partialUpload() {
+  Service.identity.username = "foo";
+
+  let collection = new ServerCollection();
+  let server = sync_httpd_setup({
+      "/1.1/foo/storage/rotary": collection.handler()
+  });
+  let syncTesting = new SyncTestingInfrastructure(server);
+  generateNewKeys(Service.collectionKeys);
+
+  let engine = new RotaryEngine(Service);
+  engine.lastSync = 123;
+  engine.lastSyncLocal = 456;
+
+
+  // Create a bunch of records (and server side handlers)
+  for (let i = 0; i < 234; i++) {
+    let id = 'record-no-' + i;
+    engine._store.items[id] = "Record No. " + i;
+    engine._tracker.addChangedID(id, i);
+    // Let two items in the first upload batch fail.
+    if (i != 23 && i != 42) {
+      collection.insert(id);
+    }
+  }
+
+  let meta_global = Service.recordManager.set(engine.metaURL,
+                                              new WBORecord(engine.metaURL));
+  meta_global.payload.engines = {rotary: {version: engine.version,
+                                          syncID: engine.syncID}};
+
+  try {
+    engine.enabled = true;
+    let ping = yield sync_engine_and_validate_telem(engine, true);
+
+    ok(!!ping);
+    ok(!ping.failureReason);
+    equal(ping.engines.length, 1);
+    equal(ping.engines[0].name, "rotary");
+    ok(!ping.engines[0].incoming);
+    ok(!ping.engines[0].failureReason);
+    deepEqual(ping.engines[0].outgoing, [{ sent: 234, failed: 2 }]);
+
+    collection.post = function() { throw "Failure"; }
+
+    engine._store.items["record-no-1000"] = "Record No. 1000";
+    engine._tracker.addChangedID("record-no-1000", 1000);
+    collection.insert("record-no-1000", 1000);
+
+    engine.lastSync = 123;
+    engine.lastSyncLocal = 456;
+    ping = null;
+
+    try {
+      // should throw
+      yield sync_engine_and_validate_telem(engine, true, errPing => ping = errPing);
+    } catch (e) {}
+    // It would be nice if we had a more descriptive error for this...
+    let uploadFailureError = {
+      name: "othererror",
+      error: "error.engine.reason.record_upload_fail"
+    };
+
+    ok(!!ping);
+    deepEqual(ping.failureReason, uploadFailureError);
+    equal(ping.engines.length, 1);
+    equal(ping.engines[0].name, "rotary");
+    deepEqual(ping.engines[0].incoming, {
+      failed: 1,
+      newFailed: 1,
+      reconciled: 232
+    });
+    ok(!ping.engines[0].outgoing);
+    deepEqual(ping.engines[0].failureReason, uploadFailureError);
+
+  } finally {
+    yield cleanAndGo(server);
+  }
+});
+
+add_task(function* test_generic_engine_fail() {
+  Service.engineManager.register(SteamEngine);
+  let engine = Service.engineManager.get("steam");
+  engine.enabled = true;
+  let store  = engine._store;
+  let server = serverForUsers({"foo": "password"}, {
+    meta: {global: {engines: {steam: {version: engine.version,
+                                      syncID: engine.syncID}}}},
+    steam: {}
+  });
+  new SyncTestingInfrastructure(server.server);
+  let e = new Error("generic failure message")
+  engine._errToThrow = e;
+
+  try {
+    let ping = yield sync_and_validate_telem(true);
+    equal(ping.status.service, SYNC_FAILED_PARTIAL);
+    deepEqual(ping.engines.find(e => e.name === "steam").failureReason, {
+      name: "unexpectederror",
+      error: String(e)
+    });
+  } finally {
+    Service.engineManager.unregister(engine);
+    yield cleanAndGo(server);
+  }
+});
+
+add_task(function* test_initial_sync_engines() {
+  Service.engineManager.register(SteamEngine);
+  let engine = Service.engineManager.get("steam");
+  engine.enabled = true;
+  let store  = engine._store;
+  let engines = {};
+  // These are the only ones who actually have things to sync at startup.
+  let engineNames = ["clients", "bookmarks", "prefs", "tabs"];
+  let conf = { meta: { global: { engines } } };
+  for (let e of engineNames) {
+    engines[e] = { version: engine.version, syncID: engine.syncID };
+    conf[e] = {};
+  }
+  let server = serverForUsers({"foo": "password"}, conf);
+  new SyncTestingInfrastructure(server.server);
+  try {
+    let ping = yield wait_for_ping(() => Service.sync(), true);
+
+    equal(ping.engines.find(e => e.name === "clients").outgoing[0].sent, 1);
+    equal(ping.engines.find(e => e.name === "tabs").outgoing[0].sent, 1);
+
+    // for the rest we don't care about specifics
+    for (let e of ping.engines) {
+      if (!engineNames.includes(engine.name)) {
+        continue;
+      }
+      greaterOrEqual(e.took, 1);
+      ok(!!e.outgoing)
+      equal(e.outgoing.length, 1);
+      notEqual(e.outgoing[0].sent, undefined);
+      equal(e.outgoing[0].failed, undefined);
+    }
+  } finally {
+    yield cleanAndGo(server);
+  }
+});
+
+add_task(function* test_nserror() {
+  Service.engineManager.register(SteamEngine);
+  let engine = Service.engineManager.get("steam");
+  engine.enabled = true;
+  let store  = engine._store;
+  let server = serverForUsers({"foo": "password"}, {
+    meta: {global: {engines: {steam: {version: engine.version,
+                                      syncID: engine.syncID}}}},
+    steam: {}
+  });
+  new SyncTestingInfrastructure(server.server);
+  engine._errToThrow = Components.Exception("NS_ERROR_UNKNOWN_HOST", Cr.NS_ERROR_UNKNOWN_HOST);
+  try {
+    let ping = yield sync_and_validate_telem(true);
+    deepEqual(ping.status, {
+      service: SYNC_FAILED_PARTIAL,
+      sync: LOGIN_FAILED_NETWORK_ERROR
+    });
+    let enginePing = ping.engines.find(e => e.name === "steam");
+    deepEqual(enginePing.failureReason, {
+      name: "nserror",
+      code: Cr.NS_ERROR_UNKNOWN_HOST
+    });
+  } finally {
+    Service.engineManager.unregister(engine);
+    yield cleanAndGo(server);
+  }
+});
--- a/services/sync/tests/unit/xpcshell.ini
+++ b/services/sync/tests/unit/xpcshell.ini
@@ -6,16 +6,17 @@ skip-if = toolkit == 'gonk'
 support-files =
   addon1-search.xml
   bootstrap1-search.xml
   fake_login_manager.js
   missing-sourceuri.xml
   missing-xpi-search.xml
   places_v10_from_v11.sqlite
   rewrite-search.xml
+  sync_ping_schema.json
   !/services/common/tests/unit/head_helpers.js
   !/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
 
 # The manifest is roughly ordered from low-level to high-level. When making
 # systemic sweeping changes, this makes it easier to identify errors closer to
 # the source.
 
 # Ensure we can import everything.
@@ -180,8 +181,10 @@ support-files = prefs_test_prefs_store.j
 
 [test_warn_on_truncated_response.js]
 
 # FxA migration
 [test_fxa_migration.js]
 
 # Synced tabs.
 [test_syncedtabs.js]
+
+[test_telemetry.js]
new file mode 100644
--- /dev/null
+++ b/testing/modules/ajv-4.1.1.js
@@ -0,0 +1,7650 @@
+this.EXPORTED_SYMBOLS = ["Ajv"];
+
+/*
+ * ajv 4.1.1: Another JSON Schema Validator
+ *
+ * https://github.com/epoberezkin/ajv
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Evgeny Poberezkin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// Everything below this comment was taken from the `dist/ajv.bundle.js` in the
+// ajv node module. (You can get this by running `npm install ajv` in an empty
+// directory and copying `node_modules/ajv/dist/ajv.bundle.js`). 
+
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Ajv = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+'use strict';
+
+module.exports = {
+  setup: setupAsync,
+  compile: compileAsync
+};
+
+
+var util = require('./compile/util');
+
+var ASYNC = {
+  '*': checkGenerators,
+  'co*': checkGenerators,
+  'es7': checkAsyncFunction
+};
+
+var TRANSPILE = {
+  'nodent': getNodent,
+  'regenerator': getRegenerator
+};
+
+var MODES = [
+  { async: 'co*' },
+  { async: 'es7', transpile: 'nodent' },
+  { async: 'co*', transpile: 'regenerator' }
+];
+
+
+var regenerator, nodent;
+
+
+function setupAsync(opts, required) {
+  if (required !== false) required = true;
+  var async = opts.async
+    , transpile = opts.transpile
+    , check;
+
+  switch (typeof transpile) {
+    case 'string':
+      var get = TRANSPILE[transpile];
+      if (!get) throw new Error('bad transpiler: ' + transpile);
+      return (opts._transpileFunc = get(opts, required));
+    case 'undefined':
+    case 'boolean':
+      if (typeof async == 'string') {
+        check = ASYNC[async];
+        if (!check) throw new Error('bad async mode: ' + async);
+        return (opts.transpile = check(opts, required));
+      }
+
+      for (var i=0; i<MODES.length; i++) {
+        var _opts = MODES[i];
+        if (setupAsync(_opts, false)) {
+          util.copy(_opts, opts);
+          return opts.transpile;
+        }
+      }
+      /* istanbul ignore next */
+      throw new Error('generators, nodent and regenerator are not available');
+    case 'function':
+      return (opts._transpileFunc = opts.transpile);
+    default:
+      throw new Error('bad transpiler: ' + transpile);
+  }
+}
+
+
+function checkGenerators(opts, required) {
+  /* jshint evil: true */
+  try {
+    eval('(function*(){})()');
+    return true;
+  } catch(e) {
+    /* istanbul ignore next */
+    if (required) throw new Error('generators not supported');
+  }
+}
+
+
+function checkAsyncFunction(opts, required) {
+  /* jshint evil: true */
+  try {
+    eval('(async function(){})()');
+    /* istanbul ignore next */
+    return true;
+  } catch(e) {
+    if (required) throw new Error('es7 async functions not supported');
+  }
+}
+
+
+function getRegenerator(opts, required) {
+  try {
+    if (!regenerator) {
+      regenerator = require('' + 'regenerator');
+      regenerator.runtime();
+    }
+    if (!opts.async || opts.async === true)
+      opts.async = 'es7';
+    return regeneratorTranspile;
+  } catch(e) {
+    /* istanbul ignore next */
+    if (required) throw new Error('regenerator not available');
+  }
+}
+
+
+function regeneratorTranspile(code) {
+  return regenerator.compile(code).code;
+}
+
+
+function getNodent(opts, required) {
+  /* jshint evil: true */
+  try {
+    if (!nodent) nodent = require('' + 'nodent')({ log: false, dontInstallRequireHook: true });
+    if (opts.async != 'es7') {
+      if (opts.async && opts.async !== true) console.warn('nodent transpiles only es7 async functions');
+      opts.async = 'es7';
+    }
+    return nodentTranspile;
+  } catch(e) {
+    /* istanbul ignore next */
+    if (required) throw new Error('nodent not available');
+  }
+}
+
+
+function nodentTranspile(code) {
+  return nodent.compile(code, '', { promises: true, sourcemap: false }).code;
+}
+
+
+/**
+ * Creates validating function for passed schema with asynchronous loading of missing schemas.
+ * `loadSchema` option should be a function that accepts schema uri and node-style callback.
+ * @this  Ajv
+ * @param {Object}   schema schema object
+ * @param {Function} callback node-style callback, it is always called with 2 parameters: error (or null) and validating function.
+ */
+function compileAsync(schema, callback) {
+  /* eslint no-shadow: 0 */
+  /* jshint validthis: true */
+  var schemaObj;
+  var self = this;
+  try {
+    schemaObj = this._addSchema(schema);
+  } catch(e) {
+    setTimeout(function() { callback(e); });
+    return;
+  }
+  if (schemaObj.validate) {
+    setTimeout(function() { callback(null, schemaObj.validate); });
+  } else {
+    if (typeof this._opts.loadSchema != 'function')
+      throw new Error('options.loadSchema should be a function');
+    _compileAsync(schema, callback, true);
+  }
+
+
+  function _compileAsync(schema, callback, firstCall) {
+    var validate;
+    try { validate = self.compile(schema); }
+    catch(e) {
+      if (e.missingSchema) loadMissingSchema(e);
+      else deferCallback(e);
+      return;
+    }
+    deferCallback(null, validate);
+
+    function loadMissingSchema(e) {
+      var ref = e.missingSchema;
+      if (self._refs[ref] || self._schemas[ref])
+        return callback(new Error('Schema ' + ref + ' is loaded but' + e.missingRef + 'cannot be resolved'));
+      var _callbacks = self._loadingSchemas[ref];
+      if (_callbacks) {
+        if (typeof _callbacks == 'function')
+          self._loadingSchemas[ref] = [_callbacks, schemaLoaded];
+        else
+          _callbacks[_callbacks.length] = schemaLoaded;
+      } else {
+        self._loadingSchemas[ref] = schemaLoaded;
+        self._opts.loadSchema(ref, function (err, sch) {
+          var _callbacks = self._loadingSchemas[ref];
+          delete self._loadingSchemas[ref];
+          if (typeof _callbacks == 'function') {
+            _callbacks(err, sch);
+          } else {
+            for (var i=0; i<_callbacks.length; i++)
+              _callbacks[i](err, sch);
+          }
+        });
+      }
+
+      function schemaLoaded(err, sch) {
+        if (err) return callback(err);
+        if (!(self._refs[ref] || self._schemas[ref])) {
+          try {
+            self.addSchema(sch, ref);
+          } catch(e) {
+            callback(e);
+            return;
+          }
+        }
+        _compileAsync(schema, callback);
+      }
+    }
+
+    function deferCallback(err, validate) {
+      if (firstCall) setTimeout(function() { callback(err, validate); });
+      else return callback(err, validate);
+    }
+  }
+}
+
+},{"./compile/util":10}],2:[function(require,module,exports){
+'use strict';
+
+
+var Cache = module.exports = function Cache() {
+  this._cache = {};
+};
+
+
+Cache.prototype.put = function Cache_put(key, value) {
+  this._cache[key] = value;
+};
+
+
+Cache.prototype.get = function Cache_get(key) {
+  return this._cache[key];
+};
+
+
+Cache.prototype.del = function Cache_del(key) {
+  delete this._cache[key];
+};
+
+
+Cache.prototype.clear = function Cache_clear() {
+  this._cache = {};
+};
+
+},{}],3:[function(require,module,exports){
+'use strict';
+
+//all requires must be explicit because browserify won't work with dynamic requires
+module.exports = {
+  '$ref': require('../dotjs/ref'),
+  allOf: require('../dotjs/allOf'),
+  anyOf: require('../dotjs/anyOf'),
+  dependencies: require('../dotjs/dependencies'),
+  'enum': require('../dotjs/enum'),
+  format: require('../dotjs/format'),
+  items: require('../dotjs/items'),
+  maximum: require('../dotjs/_limit'),
+  minimum: require('../dotjs/_limit'),
+  maxItems: require('../dotjs/_limitItems'),
+  minItems: require('../dotjs/_limitItems'),
+  maxLength: require('../dotjs/_limitLength'),
+  minLength: require('../dotjs/_limitLength'),
+  maxProperties: require('../dotjs/_limitProperties'),
+  minProperties: require('../dotjs/_limitProperties'),
+  multipleOf: require('../dotjs/multipleOf'),
+  not: require('../dotjs/not'),
+  oneOf: require('../dotjs/oneOf'),
+  pattern: require('../dotjs/pattern'),
+  properties: require('../dotjs/properties'),
+  required: require('../dotjs/required'),
+  uniqueItems: require('../dotjs/uniqueItems'),
+  validate: require('../dotjs/validate')
+};
+
+},{"../dotjs/_limit":13,"../dotjs/_limitItems":14,"../dotjs/_limitLength":15,"../dotjs/_limitProperties":16,"../dotjs/allOf":17,"../dotjs/anyOf":18,"../dotjs/dependencies":20,"../dotjs/enum":21,"../dotjs/format":22,"../dotjs/items":23,"../dotjs/multipleOf":24,"../dotjs/not":25,"../dotjs/oneOf":26,"../dotjs/pattern":27,"../dotjs/properties":29,"../dotjs/ref":30,"../dotjs/required":31,"../dotjs/uniqueItems":33,"../dotjs/validate":34}],4:[function(require,module,exports){
+'use strict';
+
+module.exports = function equal(a, b) {
+  if (a === b) return true;
+
+  var arrA = Array.isArray(a)
+    , arrB = Array.isArray(b)
+    , i;
+
+  if (arrA && arrB) {
+    if (a.length != b.length) return false;
+    for (i = 0; i < a.length; i++)
+      if (!equal(a[i], b[i])) return false;
+    return true;
+  }
+
+  if (arrA != arrB) return false;
+
+  if (a && b && typeof a === 'object' && typeof b === 'object') {
+    var keys = Object.keys(a);
+
+    if (keys.length !== Object.keys(b).length) return false;
+
+    for (i = 0; i < keys.length; i++)
+      if (b[keys[i]] === undefined) return false;
+
+    for (i = 0; i < keys.length; i++)
+      if(!equal(a[keys[i]], b[keys[i]])) return false;
+
+    return true;
+  }
+
+  return false;
+};
+
+},{}],5:[function(require,module,exports){
+'use strict';
+
+var util = require('./util');
+
+var DATE = /^\d\d\d\d-(\d\d)-(\d\d)$/;
+var DAYS = [0,31,29,31,30,31,30,31,31,30,31,30,31];
+var TIME = /^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d:\d\d)?$/i;
+var HOSTNAME = /^[a-z](?:(?:[-0-9a-z]{0,61})?[0-9a-z])?(\.[a-z](?:(?:[-0-9a-z]{0,61})?[0-9a-z])?)*$/i;
+var URI = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@\/?]|%[0-9a-f]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'()*+,;=:@\/?]|%[0-9a-f]{2})*)?$/i;
+var UUID = /^(?:urn\:uuid\:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i;
+var JSON_POINTER = /^(?:\/(?:[^~\/]|~0|~1)+)*(?:\/)?$|^\#(?:\/(?:[a-z0-9_\-\.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)+)*(?:\/)?$/i;
+var RELATIVE_JSON_POINTER = /^(?:0|[1-9][0-9]*)(?:\#|(?:\/(?:[^~\/]|~0|~1)+)*(?:\/)?)$/;
+
+
+module.exports = formats;
+
+function formats(mode) {
+  mode = mode == 'full' ? 'full' : 'fast';
+  var formatDefs = util.copy(formats[mode]);
+  for (var fName in formats.compare) {
+    formatDefs[fName] = {
+      validate: formatDefs[fName],
+      compare: formats.compare[fName]
+    };
+  }
+  return formatDefs;
+}
+
+
+formats.fast = {
+  // date: http://tools.ietf.org/html/rfc3339#section-5.6
+  date: /^\d\d\d\d-[0-1]\d-[0-3]\d$/,
+  // date-time: http://tools.ietf.org/html/rfc3339#section-5.6
+  time: /^[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:z|[+-]\d\d:\d\d)?$/i,
+  'date-time': /^\d\d\d\d-[0-1]\d-[0-3]\d[t\s][0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:z|[+-]\d\d:\d\d)$/i,
+  // uri: https://github.com/mafintosh/is-my-json-valid/blob/master/formats.js
+  uri: /^(?:[a-z][a-z0-9+-.]*)?(?:\:|\/)\/?[^\s]*$/i,
+  // email (sources from jsen validator):
+  // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363
+  // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'willful violation')
+  email: /^[a-z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i,
+  hostname: HOSTNAME,
+  // optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html
+  ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,
+  // optimized http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses
+  ipv6: /^\s*(?:(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){5}(?:(?:(?::[0-9a-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[0-9a-f]{1,4}){1,7})|(?:(?::[0-9a-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(?:%.+)?\s*$/i,
+  regex: regex,
+  // uuid: http://tools.ietf.org/html/rfc4122
+  uuid: UUID,
+  // JSON-pointer: https://tools.ietf.org/html/rfc6901
+  // uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A
+  'json-pointer': JSON_POINTER,
+  // relative JSON-pointer: http://tools.ietf.org/html/draft-luff-relative-json-pointer-00
+  'relative-json-pointer': RELATIVE_JSON_POINTER
+};
+
+
+formats.full = {
+  date: date,
+  time: time,
+  'date-time': date_time,
+  uri: uri,
+  email: /^[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&''*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,
+  hostname: hostname,
+  ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,
+  ipv6: /^\s*(?:(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){5}(?:(?:(?::[0-9a-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[0-9a-f]{1,4}){1,7})|(?:(?::[0-9a-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(?:%.+)?\s*$/i,
+  regex: regex,
+  uuid: UUID,
+  'json-pointer': JSON_POINTER,
+  'relative-json-pointer': RELATIVE_JSON_POINTER
+};
+
+
+formats.compare = {
+  date: compareDate,
+  time: compareTime,
+  'date-time': compareDateTime
+};
+
+
+function date(str) {
+  // full-date from http://tools.ietf.org/html/rfc3339#section-5.6
+  var matches = str.match(DATE);
+  if (!matches) return false;
+
+  var month = +matches[1];
+  var day = +matches[2];
+  return month >= 1 && month <= 12 && day >= 1 && day <= DAYS[month];
+}
+
+
+function time(str, full) {
+  var matches = str.match(TIME);
+  if (!matches) return false;
+
+  var hour = matches[1];
+  var minute = matches[2];
+  var second = matches[3];
+  var timeZone = matches[5];
+  return hour <= 23 && minute <= 59 && second <= 59 && (!full || timeZone);
+}
+
+
+var DATE_TIME_SEPARATOR = /t|\s/i;
+function date_time(str) {
+  // http://tools.ietf.org/html/rfc3339#section-5.6
+  var dateTime = str.split(DATE_TIME_SEPARATOR);
+  return date(dateTime[0]) && time(dateTime[1], true);
+}
+
+
+function hostname(str) {
+  // http://tools.ietf.org/html/rfc1034#section-3.5
+  return str.length <= 255 && HOSTNAME.test(str);
+}
+
+
+var NOT_URI_FRAGMENT = /\/|\:/;
+function uri(str) {
+  // http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + optional protocol + required "."
+  return NOT_URI_FRAGMENT.test(str) && URI.test(str);
+}
+
+
+function regex(str) {
+  try {
+    new RegExp(str);
+    return true;
+  } catch(e) {
+    return false;
+  }
+}
+
+
+function compareDate(d1, d2) {
+  if (!(d1 && d2)) return;
+  if (d1 > d2) return 1;
+  if (d1 < d2) return -1;
+  if (d1 === d2) return 0;
+}
+
+
+function compareTime(t1, t2) {
+  if (!(t1 && t2)) return;
+  t1 = t1.match(TIME);
+  t2 = t2.match(TIME);
+  if (!(t1 && t2)) return;
+  t1 = t1[1] + t1[2] + t1[3] + (t1[4]||'');
+  t2 = t2[1] + t2[2] + t2[3] + (t2[4]||'');
+  if (t1 > t2) return 1;
+  if (t1 < t2) return -1;
+  if (t1 === t2) return 0;
+}
+
+
+function compareDateTime(dt1, dt2) {
+  if (!(dt1 && dt2)) return;
+  dt1 = dt1.split(DATE_TIME_SEPARATOR);
+  dt2 = dt2.split(DATE_TIME_SEPARATOR);
+  var res = compareDate(dt1[0], dt2[0]);
+  if (res === undefined) return;
+  return res || compareTime(dt1[1], dt2[1]);
+}
+
+},{"./util":10}],6:[function(require,module,exports){
+'use strict';
+
+var resolve = require('./resolve')
+  , util = require('./util')
+  , stableStringify = require('json-stable-stringify')
+  , async = require('../async');
+
+var beautify = (function() { try { return require('' + 'js-beautify').js_beautify; } catch(e) {/*empty*/} })();
+
+var validateGenerator = require('../dotjs/validate');
+
+module.exports = compile;
+
+
+/**
+ * Compiles schema to validation function
+ * @this   Ajv
+ * @param  {Object} schema schema object
+ * @param  {Object} root object with information about the root schema for this schema
+ * @param  {Object} localRefs the hash of local references inside the schema (created by resolve.id), used for inline resolution
+ * @param  {String} baseId base ID for IDs in the schema
+ * @return {Function} validation function
+ */
+function compile(schema, root, localRefs, baseId) {
+  /* jshint validthis: true, evil: true */
+  /* eslint no-shadow: 0 */
+  var self = this
+    , opts = this._opts
+    , refVal = [ undefined ]
+    , refs = {}
+    , patterns = []
+    , patternsHash = {}
+    , defaults = []
+    , defaultsHash = {}
+    , customRules = [];
+
+  root = root || { schema: schema, refVal: refVal, refs: refs };
+
+  var formats = this._formats;
+  var RULES = this.RULES;
+
+  return localCompile(schema, root, localRefs, baseId);
+
+
+  function localCompile(_schema, _root, localRefs, baseId) {
+    var isRoot = !_root || (_root && _root.schema == _schema);
+    if (_root.schema != root.schema)
+      return compile.call(self, _schema, _root, localRefs, baseId);
+
+    var $async = _schema.$async === true;
+    if ($async && !opts.transpile) async.setup(opts);
+
+    var sourceCode = validateGenerator({
+      isTop: true,
+      schema: _schema,
+      isRoot: isRoot,
+      baseId: baseId,
+      root: _root,
+      schemaPath: '',
+      errSchemaPath: '#',
+      errorPath: '""',
+      RULES: RULES,
+      validate: validateGenerator,
+      util: util,
+      resolve: resolve,
+      resolveRef: resolveRef,
+      usePattern: usePattern,
+      useDefault: useDefault,
+      useCustomRule: useCustomRule,
+      opts: opts,
+      formats: formats,
+      self: self
+    });
+
+    sourceCode = vars(refVal, refValCode) + vars(patterns, patternCode)
+                   + vars(defaults, defaultCode) + vars(customRules, customRuleCode)
+                   + sourceCode;
+
+    if (opts.beautify) {
+      /* istanbul ignore else */
+      if (beautify) sourceCode = beautify(sourceCode, opts.beautify);
+      else console.error('"npm install js-beautify" to use beautify option');
+    }
+    // console.log('\n\n\n *** \n', sourceCode);
+    var validate, validateCode
+      , transpile = opts._transpileFunc;
+    try {
+      validateCode = $async && transpile
+                      ? transpile(sourceCode)
+                      : sourceCode;
+      eval(validateCode);
+      refVal[0] = validate;
+    } catch(e) {
+      console.error('Error compiling schema, function code:', validateCode);
+      throw e;
+    }
+
+    validate.schema = _schema;
+    validate.errors = null;
+    validate.refs = refs;
+    validate.refVal = refVal;
+    validate.root = isRoot ? validate : _root;
+    if ($async) validate.$async = true;
+    validate.sourceCode = sourceCode;
+
+    return validate;
+  }
+
+  function resolveRef(baseId, ref, isRoot) {
+    ref = resolve.url(baseId, ref);
+    var refIndex = refs[ref];
+    var _refVal, refCode;
+    if (refIndex !== undefined) {
+      _refVal = refVal[refIndex];
+      refCode = 'refVal[' + refIndex + ']';
+      return resolvedRef(_refVal, refCode);
+    }
+    if (!isRoot) {
+      var rootRefId = root.refs[ref];
+      if (rootRefId !== undefined) {
+        _refVal = root.refVal[rootRefId];
+        refCode = addLocalRef(ref, _refVal);
+        return resolvedRef(_refVal, refCode);
+      }
+    }
+
+    refCode = addLocalRef(ref);
+    var v = resolve.call(self, localCompile, root, ref);
+    if (!v) {
+      var localSchema = localRefs && localRefs[ref];
+      if (localSchema) {
+        v = resolve.inlineRef(localSchema, opts.inlineRefs)
+            ? localSchema
+            : compile.call(self, localSchema, root, localRefs, baseId);
+      }
+    }
+
+    if (v) {
+      replaceLocalRef(ref, v);
+      return resolvedRef(v, refCode);
+    }
+  }
+
+  function addLocalRef(ref, v) {
+    var refId = refVal.length;
+    refVal[refId] = v;
+    refs[ref] = refId;
+    return 'refVal' + refId;
+  }
+
+  function replaceLocalRef(ref, v) {
+    var refId = refs[ref];
+    refVal[refId] = v;
+  }
+
+  function resolvedRef(refVal, code) {
+    return typeof refVal == 'object'
+            ? { code: code, schema: refVal, inline: true }
+            : { code: code, $async: refVal && refVal.$async };
+  }
+
+  function usePattern(regexStr) {
+    var index = patternsHash[regexStr];
+    if (index === undefined) {
+      index = patternsHash[regexStr] = patterns.length;
+      patterns[index] = regexStr;
+    }
+    return 'pattern' + index;
+  }
+
+  function useDefault(value) {
+    switch (typeof value) {
+      case 'boolean':
+      case 'number':
+        return '' + value;
+      case 'string':
+        return util.toQuotedString(value);
+      case 'object':
+        if (value === null) return 'null';
+        var valueStr = stableStringify(value);
+        var index = defaultsHash[valueStr];
+        if (index === undefined) {
+          index = defaultsHash[valueStr] = defaults.length;
+          defaults[index] = value;
+        }
+        return 'default' + index;
+    }
+  }
+
+  function useCustomRule(rule, schema, parentSchema, it) {
+    var compile = rule.definition.compile
+      , inline = rule.definition.inline
+      , macro = rule.definition.macro;
+
+    var validate;
+    if (compile) {
+      validate = compile.call(self, schema, parentSchema);
+    } else if (macro) {
+      validate = macro.call(self, schema, parentSchema);
+      if (opts.validateSchema !== false) self.validateSchema(validate, true);
+    } else if (inline) {
+      validate = inline.call(self, it, rule.keyword, schema, parentSchema);
+    } else {
+      validate = rule.definition.validate;
+    }
+
+    var index = customRules.length;
+    customRules[index] = validate;
+
+    return {
+      code: 'customRule' + index,
+      validate: validate
+    };
+  }
+}
+
+
+function patternCode(i, patterns) {
+  return 'var pattern' + i + ' = new RegExp(' + util.toQuotedString(patterns[i]) + ');';
+}
+
+
+function defaultCode(i) {
+  return 'var default' + i + ' = defaults[' + i + '];';
+}
+
+
+function refValCode(i, refVal) {
+  return refVal[i] ? 'var refVal' + i + ' = refVal[' + i + '];' : '';
+}
+
+
+function customRuleCode(i) {
+  return 'var customRule' + i + ' = customRules[' + i + '];';
+}
+
+
+function vars(arr, statement) {
+  if (!arr.length) return '';
+  var code = '';
+  for (var i=0; i<arr.length; i++)
+    code += statement(i, arr);
+  return code;
+}
+
+
+/*eslint-disable no-unused-vars */
+
+/**
+ * Functions below are used inside compiled validations function
+ */
+
+var co = require('co');
+
+var ucs2length = util.ucs2length;
+
+var equal = require('./equal');
+
+// this error is thrown by async schemas to return validation errors via exception
+var ValidationError = require('./validation_error');
+
+/*eslint-enable no-unused-vars */
+
+},{"../async":1,"../dotjs/validate":34,"./equal":4,"./resolve":7,"./util":10,"./validation_error":11,"co":45,"json-stable-stringify":46}],7:[function(require,module,exports){
+'use strict';
+
+var url = require('url')
+  , equal = require('./equal')
+  , util = require('./util')
+  , SchemaObject = require('./schema_obj');
+
+module.exports = resolve;
+
+resolve.normalizeId = normalizeId;
+resolve.fullPath = getFullPath;
+resolve.url = resolveUrl;
+resolve.ids = resolveIds;
+resolve.inlineRef = inlineRef;
+
+/**
+ * [resolve and compile the references ($ref)]
+ * @this   Ajv
+ * @param  {Function} compile reference to schema compilation funciton (localCompile)
+ * @param  {Object} root object with information about the root schema for the current schema
+ * @param  {String} ref reference to resolve
+ * @return {Object|Function} schema object (if the schema can be inlined) or validation function
+ */
+function resolve(compile, root, ref) {
+  /* jshint validthis: true */
+  var refVal = this._refs[ref];
+  if (typeof refVal == 'string') {
+    if (this._refs[refVal]) refVal = this._refs[refVal];
+    else return resolve.call(this, compile, root, refVal);
+  }
+
+  refVal = refVal || this._schemas[ref];
+  if (refVal instanceof SchemaObject) {
+    return inlineRef(refVal.schema, this._opts.inlineRefs)
+            ? refVal.schema
+            : refVal.validate || this._compile(refVal);
+  }
+
+  var res = _resolve.call(this, root, ref);
+  var schema, v, baseId;
+  if (res) {
+    schema = res.schema;
+    root = res.root;
+    baseId = res.baseId;
+  }
+
+  if (schema instanceof SchemaObject) {
+    v = schema.validate || compile.call(this, schema.schema, root, undefined, baseId);
+  } else if (schema) {
+    v = inlineRef(schema, this._opts.inlineRefs)
+        ? schema
+        : compile.call(this, schema, root, undefined, baseId);
+  }
+
+  return v;
+}
+
+
+/* @this Ajv */
+function _resolve(root, ref) {
+  /* jshint validthis: true */
+  var p = url.parse(ref, false, true)
+    , refPath = _getFullPath(p)
+    , baseId = getFullPath(root.schema.id);
+  if (refPath !== baseId) {
+    var id = normalizeId(refPath);
+    var refVal = this._refs[id];
+    if (typeof refVal == 'string') {
+      return resolveRecursive.call(this, root, refVal, p);
+    } else if (refVal instanceof SchemaObject) {
+      if (!refVal.validate) this._compile(refVal);
+      root = refVal;
+    } else {
+      refVal = this._schemas[id];
+      if (refVal instanceof SchemaObject) {
+        if (!refVal.validate) this._compile(refVal);
+        if (id == normalizeId(ref))
+          return { schema: refVal, root: root, baseId: baseId };
+        root = refVal;
+      }
+    }
+    if (!root.schema) return;
+    baseId = getFullPath(root.schema.id);
+  }
+  return getJsonPointer.call(this, p, baseId, root.schema, root);
+}
+
+
+/* @this Ajv */
+function resolveRecursive(root, ref, parsedRef) {
+  /* jshint validthis: true */
+  var res = _resolve.call(this, root, ref);
+  if (res) {
+    var schema = res.schema;
+    var baseId = res.baseId;
+    root = res.root;
+    if (schema.id) baseId = resolveUrl(baseId, schema.id);
+    return getJsonPointer.call(this, parsedRef, baseId, schema, root);
+  }
+}
+
+
+var PREVENT_SCOPE_CHANGE = util.toHash(['properties', 'patternProperties', 'enum', 'dependencies', 'definitions']);
+/* @this Ajv */
+function getJsonPointer(parsedRef, baseId, schema, root) {
+  /* jshint validthis: true */
+  parsedRef.hash = parsedRef.hash || '';
+  if (parsedRef.hash.slice(0,2) != '#/') return;
+  var parts = parsedRef.hash.split('/');
+
+  for (var i = 1; i < parts.length; i++) {
+    var part = parts[i];
+    if (part) {
+      part = util.unescapeFragment(part);
+      schema = schema[part];
+      if (!schema) break;
+      if (schema.id && !PREVENT_SCOPE_CHANGE[part]) baseId = resolveUrl(baseId, schema.id);
+      if (schema.$ref) {
+        var $ref = resolveUrl(baseId, schema.$ref);
+        var res = _resolve.call(this, root, $ref);
+        if (res) {
+          schema = res.schema;
+          root = res.root;
+          baseId = res.baseId;
+        }
+      }
+    }
+  }
+  if (schema && schema != root.schema)
+    return { schema: schema, root: root, baseId: baseId };
+}
+
+
+var SIMPLE_INLINED = util.toHash([
+  'type', 'format', 'pattern',
+  'maxLength', 'minLength',
+  'maxProperties', 'minProperties',
+  'maxItems', 'minItems',
+  'maximum', 'minimum',
+  'uniqueItems', 'multipleOf',
+  'required', 'enum'
+]);
+function inlineRef(schema, limit) {
+  if (limit === false) return false;
+  if (limit === undefined || limit === true) return checkNoRef(schema);
+  else if (limit) return countKeys(schema) <= limit;
+}
+
+
+function checkNoRef(schema) {
+  var item;
+  if (Array.isArray(schema)) {
+    for (var i=0; i<schema.length; i++) {
+      item = schema[i];
+      if (typeof item == 'object' && !checkNoRef(item)) return false;
+    }
+  } else {
+    for (var key in schema) {
+      if (key == '$ref') return false;
+      item = schema[key];
+      if (typeof item == 'object' && !checkNoRef(item)) return false;
+    }
+  }
+  return true;
+}
+
+
+function countKeys(schema) {
+  var count = 0, item;
+  if (Array.isArray(schema)) {
+    for (var i=0; i<schema.length; i++) {
+      item = schema[i];
+      if (typeof item == 'object') count += countKeys(item);
+      if (count == Infinity) return Infinity;
+    }
+  } else {
+    for (var key in schema) {
+      if (key == '$ref') return Infinity;
+      if (SIMPLE_INLINED[key]) {
+        count++;
+      } else {
+        item = schema[key];
+        if (typeof item == 'object') count += countKeys(item) + 1;
+        if (count == Infinity) return Infinity;
+      }
+    }
+  }
+  return count;
+}
+
+
+function getFullPath(id, normalize) {
+  if (normalize !== false) id = normalizeId(id);
+  var p = url.parse(id, false, true);
+  return _getFullPath(p);
+}
+
+
+function _getFullPath(p) {
+  return (p.protocol||'') + (p.protocol?'//':'') + (p.host||'') + (p.path||'')  + '#';
+}
+
+
+var TRAILING_SLASH_HASH = /#\/?$/;
+function normalizeId(id) {
+  return id ? id.replace(TRAILING_SLASH_HASH, '') : '';
+}
+
+
+function resolveUrl(baseId, id) {
+  id = normalizeId(id);
+  return url.resolve(baseId, id);
+}
+
+
+/* @this Ajv */
+function resolveIds(schema) {
+  /* eslint no-shadow: 0 */
+  /* jshint validthis: true */
+  var id = normalizeId(schema.id);
+  var localRefs = {};
+  _resolveIds.call(this, schema, getFullPath(id, false), id);
+  return localRefs;
+
+  /* @this Ajv */
+  function _resolveIds(schema, fullPath, baseId) {
+    /* jshint validthis: true */
+    if (Array.isArray(schema)) {
+      for (var i=0; i<schema.length; i++)
+        _resolveIds.call(this, schema[i], fullPath+'/'+i, baseId);
+    } else if (schema && typeof schema == 'object') {
+      if (typeof schema.id == 'string') {
+        var id = baseId = baseId
+                          ? url.resolve(baseId, schema.id)
+                          : schema.id;
+        id = normalizeId(id);
+
+        var refVal = this._refs[id];
+        if (typeof refVal == 'string') refVal = this._refs[refVal];
+        if (refVal && refVal.schema) {
+          if (!equal(schema, refVal.schema))
+            throw new Error('id "' + id + '" resolves to more than one schema');
+        } else if (id != normalizeId(fullPath)) {
+          if (id[0] == '#') {
+            if (localRefs[id] && !equal(schema, localRefs[id]))
+              throw new Error('id "' + id + '" resolves to more than one schema');
+            localRefs[id] = schema;
+          } else {
+            this._refs[id] = fullPath;
+          }
+        }
+      }
+      for (var key in schema)
+        _resolveIds.call(this, schema[key], fullPath+'/'+util.escapeFragment(key), baseId);
+    }
+  }
+}
+
+},{"./equal":4,"./schema_obj":9,"./util":10,"url":43}],8:[function(require,module,exports){
+'use strict';
+
+var ruleModules = require('./_rules')
+  , util = require('./util');
+
+module.exports = function rules() {
+  var RULES = [
+    { type: 'number',
+      rules: [ 'maximum', 'minimum', 'multipleOf'] },
+    { type: 'string',
+      rules: [ 'maxLength', 'minLength', 'pattern', 'format' ] },
+    { type: 'array',
+      rules: [ 'maxItems', 'minItems', 'uniqueItems', 'items' ] },
+    { type: 'object',
+      rules: [ 'maxProperties', 'minProperties', 'required', 'dependencies', 'properties' ] },
+    { rules: [ '$ref', 'enum', 'not', 'anyOf', 'oneOf', 'allOf' ] }
+  ];
+
+  RULES.all = [ 'type', 'additionalProperties', 'patternProperties' ];
+  RULES.keywords = [ 'additionalItems', '$schema', 'id', 'title', 'description', 'default' ];
+  RULES.types = [ 'number', 'integer', 'string', 'array', 'object', 'boolean', 'null' ];
+
+  RULES.forEach(function (group) {
+    group.rules = group.rules.map(function (keyword) {
+      RULES.all.push(keyword);
+      return {
+        keyword: keyword,
+        code: ruleModules[keyword]
+      };
+    });
+  });
+
+  RULES.keywords = util.toHash(RULES.all.concat(RULES.keywords));
+  RULES.all = util.toHash(RULES.all);
+  RULES.types = util.toHash(RULES.types);
+
+  return RULES;
+};
+
+},{"./_rules":3,"./util":10}],9:[function(require,module,exports){
+'use strict';
+
+var util = require('./util');
+
+module.exports = SchemaObject;
+
+function SchemaObject(obj) {
+  util.copy(obj, this);
+}
+
+},{"./util":10}],10:[function(require,module,exports){
+'use strict';
+
+
+module.exports = {
+  copy: copy,
+  checkDataType: checkDataType,
+  checkDataTypes: checkDataTypes,
+  coerceToTypes: coerceToTypes,
+  toHash: toHash,
+  getProperty: getProperty,
+  escapeQuotes: escapeQuotes,
+  ucs2length: ucs2length,
+  varOccurences: varOccurences,
+  varReplace: varReplace,
+  cleanUpCode: cleanUpCode,
+  cleanUpVarErrors: cleanUpVarErrors,
+  schemaHasRules: schemaHasRules,
+  stableStringify: require('json-stable-stringify'),
+  toQuotedString: toQuotedString,
+  getPathExpr: getPathExpr,
+  getPath: getPath,
+  getData: getData,
+  unescapeFragment: unescapeFragment,
+  escapeFragment: escapeFragment,
+  escapeJsonPointer: escapeJsonPointer
+};
+
+
+function copy(o, to) {
+  to = to || {};
+  for (var key in o) to[key] = o[key];
+  return to;
+}
+
+
+function checkDataType(dataType, data, negate) {
+  var EQUAL = negate ? ' !== ' : ' === '
+    , AND = negate ? ' || ' : ' && '
+    , OK = negate ? '!' : ''
+    , NOT = negate ? '' : '!';
+  switch (dataType) {
+    case 'null': return data + EQUAL + 'null';
+    case 'array': return OK + 'Array.isArray(' + data + ')';
+    case 'object': return '(' + OK + data + AND +
+                          'typeof ' + data + EQUAL + '"object"' + AND +
+                          NOT + 'Array.isArray(' + data + '))';
+    case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND +
+                           NOT + '(' + data + ' % 1)' +
+                           AND + data + EQUAL + data + ')';
+    default: return 'typeof ' + data + EQUAL + '"' + dataType + '"';
+  }
+}
+
+
+function checkDataTypes(dataTypes, data) {
+  switch (dataTypes.length) {
+    case 1: return checkDataType(dataTypes[0], data, true);
+    default:
+      var code = '';
+      var types = toHash(dataTypes);
+      if (types.array && types.object) {
+        code = types.null ? '(': '(!' + data + ' || ';
+        code += 'typeof ' + data + ' !== "object")';
+        delete types.null;
+        delete types.array;
+        delete types.object;
+      }
+      if (types.number) delete types.integer;
+      for (var t in types)
+        code += (code ? ' && ' : '' ) + checkDataType(t, data, true);
+
+      return code;
+  }
+}
+
+
+var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]);
+function coerceToTypes(dataTypes) {
+  if (Array.isArray(dataTypes)) {
+    var types = [];
+    for (var i=0; i<dataTypes.length; i++) {
+      var t = dataTypes[i];
+      if (COERCE_TO_TYPES[t]) types[types.length] = t;
+    }
+    if (types.length) return types;
+  } else if (COERCE_TO_TYPES[dataTypes]) {
+    return [dataTypes];
+  }
+}
+
+
+function toHash(arr) {
+  var hash = {};
+  for (var i=0; i<arr.length; i++) hash[arr[i]] = true;
+  return hash;
+}
+
+
+var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i;
+var SINGLE_QUOTE = /'|\\/g;
+function getProperty(key) {
+  return typeof key == 'number'
+          ? '[' + key + ']'
+          : IDENTIFIER.test(key)
+            ? '.' + key
+            : "['" + key.replace(SINGLE_QUOTE, '\\$&') + "']";
+}
+
+
+function escapeQuotes(str) {
+  return str.replace(SINGLE_QUOTE, '\\$&');
+}
+
+
+// https://mathiasbynens.be/notes/javascript-encoding
+// https://github.com/bestiejs/punycode.js - punycode.ucs2.decode
+function ucs2length(str) {
+  var length = 0
+    , len = str.length
+    , pos = 0
+    , value;
+  while (pos < len) {
+    length++;
+    value = str.charCodeAt(pos++);
+    if (value >= 0xD800 && value <= 0xDBFF && pos < len) {
+      // high surrogate, and there is a next character
+      value = str.charCodeAt(pos);
+      if ((value & 0xFC00) == 0xDC00) pos++; // low surrogate
+    }
+  }
+  return length;
+}
+
+
+function varOccurences(str, dataVar) {
+  dataVar += '[^0-9]';
+  var matches = str.match(new RegExp(dataVar, 'g'));
+  return matches ? matches.length : 0;
+}
+
+
+function varReplace(str, dataVar, expr) {
+  dataVar += '([^0-9])';
+  expr = expr.replace(/\$/g, '$$$$');
+  return str.replace(new RegExp(dataVar, 'g'), expr + '$1');
+}
+
+
+var EMPTY_ELSE = /else\s*{\s*}/g
+  , EMPTY_IF_NO_ELSE = /if\s*\([^)]+\)\s*\{\s*\}(?!\s*else)/g
+  , EMPTY_IF_WITH_ELSE = /if\s*\(([^)]+)\)\s*\{\s*\}\s*else(?!\s*if)/g;
+function cleanUpCode(out) {
+  return out.replace(EMPTY_ELSE, '')
+            .replace(EMPTY_IF_NO_ELSE, '')
+            .replace(EMPTY_IF_WITH_ELSE, 'if (!($1))');
+}
+
+
+var ERRORS_REGEXP = /[^v\.]errors/g
+  , REMOVE_ERRORS = /var errors = 0;|var vErrors = null;|validate.errors = vErrors;/g
+  , REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g
+  , RETURN_VALID = 'return errors === 0;'
+  , RETURN_TRUE = 'validate.errors = null; return true;'
+  , RETURN_ASYNC = /if \(errors === 0\) return true;\s*else throw new ValidationError\(vErrors\);/
+  , RETURN_TRUE_ASYNC = 'return true;';
+
+function cleanUpVarErrors(out, async) {
+  var matches = out.match(ERRORS_REGEXP);
+  if (!matches || matches.length !== 2) return out;
+  return async
+          ? out.replace(REMOVE_ERRORS_ASYNC, '')
+               .replace(RETURN_ASYNC, RETURN_TRUE_ASYNC)
+          : out.replace(REMOVE_ERRORS, '')
+               .replace(RETURN_VALID, RETURN_TRUE);
+}
+
+
+function schemaHasRules(schema, rules) {
+  for (var key in schema) if (rules[key]) return true;
+}
+
+
+function toQuotedString(str) {
+  return '\'' + escapeQuotes(str) + '\'';
+}
+
+
+function getPathExpr(currentPath, expr, jsonPointers, isNumber) {
+  var path = jsonPointers // false by default
+              ? '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')')
+              : (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\'');
+  return joinPaths(currentPath, path);
+}
+
+
+function getPath(currentPath, prop, jsonPointers) {
+  var path = jsonPointers // false by default
+              ? toQuotedString('/' + escapeJsonPointer(prop))
+              : toQuotedString(getProperty(prop));
+  return joinPaths(currentPath, path);
+}
+
+
+var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;
+function getData($data, lvl, paths) {
+  var matches = $data.match(RELATIVE_JSON_POINTER);
+  if (!matches) throw new Error('Invalid relative JSON-pointer: ' + $data);
+  var up = +matches[1];
+  var jsonPointer = matches[2];
+  if (jsonPointer == '#') {
+    if (up >= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl);
+    return paths[lvl - up];
+  }
+
+  if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl);
+  var data = 'data' + ((lvl - up) || '');
+  if (!jsonPointer) return data;
+
+  var expr = data;
+  var segments = jsonPointer.split('/');
+  for (var i=0; i<segments.length; i++) {
+    var segment = segments[i];
+    if (segment) {
+      data += getProperty(unescapeJsonPointer(segment));
+      expr += ' && ' + data;
+    }
+  }
+  return expr;
+}
+
+
+function joinPaths (a, b) {
+  if (a == '""') return b;
+  return (a + ' + ' + b).replace(/' \+ '/g, '');
+}
+
+
+function unescapeFragment(str) {
+  return unescapeJsonPointer(decodeURIComponent(str));
+}
+
+
+function escapeFragment(str) {
+  return encodeURIComponent(escapeJsonPointer(str));
+}
+
+
+function escapeJsonPointer(str) {
+  return str.replace(/~/g, '~0').replace(/\//g, '~1');
+}
+
+
+function unescapeJsonPointer(str) {
+  return str.replace(/~1/g, '/').replace(/~0/g, '~');
+}
+
+},{"json-stable-stringify":46}],11:[function(require,module,exports){
+'use strict';
+
+module.exports = ValidationError;
+
+
+function ValidationError(errors) {
+  this.message = 'validation failed';
+  this.errors = errors;
+  this.ajv = this.validation = true;
+}
+
+
+ValidationError.prototype = Object.create(Error.prototype);
+ValidationError.prototype.constructor = ValidationError;
+
+},{}],12:[function(require,module,exports){
+'use strict';
+module.exports = function generate__formatLimit(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  out += 'var ' + ($valid) + ' = undefined;';
+  if (it.opts.format === false) {
+    out += ' ' + ($valid) + ' = true; ';
+    return out;
+  }
+  var $schemaFormat = it.schema.format,
+    $isDataFormat = it.opts.v5 && $schemaFormat.$data,
+    $closingBraces = '';
+  if ($isDataFormat) {
+    var $schemaValueFormat = it.util.getData($schemaFormat.$data, $dataLvl, it.dataPathArr),
+      $format = 'format' + $lvl,
+      $compare = 'compare' + $lvl;
+    out += ' var ' + ($format) + ' = formats[' + ($schemaValueFormat) + '] , ' + ($compare) + ' = ' + ($format) + ' && ' + ($format) + '.compare;';
+  } else {
+    var $format = it.formats[$schemaFormat];
+    if (!($format && $format.compare)) {
+      out += '  ' + ($valid) + ' = true; ';
+      return out;
+    }
+    var $compare = 'formats' + it.util.getProperty($schemaFormat) + '.compare';
+  }
+  var $isMax = $keyword == 'formatMaximum',
+    $exclusiveKeyword = 'formatExclusive' + ($isMax ? 'Maximum' : 'Minimum'),
+    $schemaExcl = it.schema[$exclusiveKeyword],
+    $isDataExcl = it.opts.v5 && $schemaExcl && $schemaExcl.$data,
+    $op = $isMax ? '<' : '>',
+    $result = 'result' + $lvl;
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  if ($isDataExcl) {
+    var $schemaValueExcl = it.util.getData($schemaExcl.$data, $dataLvl, it.dataPathArr),
+      $exclusive = 'exclusive' + $lvl,
+      $opExpr = 'op' + $lvl,
+      $opStr = '\' + ' + $opExpr + ' + \'';
+    out += ' var schemaExcl' + ($lvl) + ' = ' + ($schemaValueExcl) + '; ';
+    $schemaValueExcl = 'schemaExcl' + $lvl;
+    out += ' if (typeof ' + ($schemaValueExcl) + ' != \'boolean\' && ' + ($schemaValueExcl) + ' !== undefined) { ' + ($valid) + ' = false; ';
+    var $errorKeyword = $exclusiveKeyword;
+    var $$outStack = $$outStack || [];
+    $$outStack.push(out);
+    out = ''; /* istanbul ignore else */
+    if (it.createErrors !== false) {
+      out += ' { keyword: \'' + ($errorKeyword || '_formatExclusiveLimit') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: {} ';
+      if (it.opts.messages !== false) {
+        out += ' , message: \'' + ($exclusiveKeyword) + ' should be boolean\' ';
+      }
+      if (it.opts.verbose) {
+        out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+      }
+      out += ' } ';
+    } else {
+      out += ' {} ';
+    }
+    var __err = out;
+    out = $$outStack.pop();
+    if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+      if (it.async) {
+        out += ' throw new ValidationError([' + (__err) + ']); ';
+      } else {
+        out += ' validate.errors = [' + (__err) + ']; return false; ';
+      }
+    } else {
+      out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+    }
+    out += ' }  ';
+    if ($breakOnError) {
+      $closingBraces += '}';
+      out += ' else { ';
+    }
+    if ($isData) {
+      out += ' if (' + ($schemaValue) + ' === undefined) ' + ($valid) + ' = true; else if (typeof ' + ($schemaValue) + ' != \'string\') ' + ($valid) + ' = false; else { ';
+      $closingBraces += '}';
+    }
+    if ($isDataFormat) {
+      out += ' if (!' + ($compare) + ') ' + ($valid) + ' = true; else { ';
+      $closingBraces += '}';
+    }
+    out += ' var ' + ($result) + ' = ' + ($compare) + '(' + ($data) + ',  ';
+    if ($isData) {
+      out += '' + ($schemaValue);
+    } else {
+      out += '' + (it.util.toQuotedString($schema));
+    }
+    out += ' ); if (' + ($result) + ' === undefined) ' + ($valid) + ' = false; var ' + ($exclusive) + ' = ' + ($schemaValueExcl) + ' === true; if (' + ($valid) + ' === undefined) { ' + ($valid) + ' = ' + ($exclusive) + ' ? ' + ($result) + ' ' + ($op) + ' 0 : ' + ($result) + ' ' + ($op) + '= 0; } if (!' + ($valid) + ') var op' + ($lvl) + ' = ' + ($exclusive) + ' ? \'' + ($op) + '\' : \'' + ($op) + '=\';';
+  } else {
+    var $exclusive = $schemaExcl === true,
+      $opStr = $op;
+    if (!$exclusive) $opStr += '=';
+    var $opExpr = '\'' + $opStr + '\'';
+    if ($isData) {
+      out += ' if (' + ($schemaValue) + ' === undefined) ' + ($valid) + ' = true; else if (typeof ' + ($schemaValue) + ' != \'string\') ' + ($valid) + ' = false; else { ';
+      $closingBraces += '}';
+    }
+    if ($isDataFormat) {
+      out += ' if (!' + ($compare) + ') ' + ($valid) + ' = true; else { ';
+      $closingBraces += '}';
+    }
+    out += ' var ' + ($result) + ' = ' + ($compare) + '(' + ($data) + ',  ';
+    if ($isData) {
+      out += '' + ($schemaValue);
+    } else {
+      out += '' + (it.util.toQuotedString($schema));
+    }
+    out += ' ); if (' + ($result) + ' === undefined) ' + ($valid) + ' = false; if (' + ($valid) + ' === undefined) ' + ($valid) + ' = ' + ($result) + ' ' + ($op);
+    if (!$exclusive) {
+      out += '=';
+    }
+    out += ' 0;';
+  }
+  out += '' + ($closingBraces) + 'if (!' + ($valid) + ') { ';
+  var $errorKeyword = $keyword;
+  var $$outStack = $$outStack || [];
+  $$outStack.push(out);
+  out = ''; /* istanbul ignore else */
+  if (it.createErrors !== false) {
+    out += ' { keyword: \'' + ($errorKeyword || '_formatLimit') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { comparison: ' + ($opExpr) + ', limit:  ';
+    if ($isData) {
+      out += '' + ($schemaValue);
+    } else {
+      out += '' + (it.util.toQuotedString($schema));
+    }
+    out += ' , exclusive: ' + ($exclusive) + ' } ';
+    if (it.opts.messages !== false) {
+      out += ' , message: \'should be ' + ($opStr) + ' "';
+      if ($isData) {
+        out += '\' + ' + ($schemaValue) + ' + \'';
+      } else {
+        out += '' + (it.util.escapeQuotes($schema));
+      }
+      out += '"\' ';
+    }
+    if (it.opts.verbose) {
+      out += ' , schema:  ';
+      if ($isData) {
+        out += 'validate.schema' + ($schemaPath);
+      } else {
+        out += '' + (it.util.toQuotedString($schema));
+      }
+      out += '         , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+    }
+    out += ' } ';
+  } else {
+    out += ' {} ';
+  }
+  var __err = out;
+  out = $$outStack.pop();
+  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+    if (it.async) {
+      out += ' throw new ValidationError([' + (__err) + ']); ';
+    } else {
+      out += ' validate.errors = [' + (__err) + ']; return false; ';
+    }
+  } else {
+    out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+  }
+  out += '}';
+  return out;
+}
+
+},{}],13:[function(require,module,exports){
+'use strict';
+module.exports = function generate__limit(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  var $isMax = $keyword == 'maximum',
+    $exclusiveKeyword = $isMax ? 'exclusiveMaximum' : 'exclusiveMinimum',
+    $schemaExcl = it.schema[$exclusiveKeyword],
+    $isDataExcl = it.opts.v5 && $schemaExcl && $schemaExcl.$data,
+    $op = $isMax ? '<' : '>',
+    $notOp = $isMax ? '>' : '<';
+  if ($isDataExcl) {
+    var $schemaValueExcl = it.util.getData($schemaExcl.$data, $dataLvl, it.dataPathArr),
+      $exclusive = 'exclusive' + $lvl,
+      $opExpr = 'op' + $lvl,
+      $opStr = '\' + ' + $opExpr + ' + \'';
+    out += ' var schemaExcl' + ($lvl) + ' = ' + ($schemaValueExcl) + '; ';
+    $schemaValueExcl = 'schemaExcl' + $lvl;
+    out += ' var exclusive' + ($lvl) + '; if (typeof ' + ($schemaValueExcl) + ' != \'boolean\' && typeof ' + ($schemaValueExcl) + ' != \'undefined\') { ';
+    var $errorKeyword = $exclusiveKeyword;
+    var $$outStack = $$outStack || [];
+    $$outStack.push(out);
+    out = ''; /* istanbul ignore else */
+    if (it.createErrors !== false) {
+      out += ' { keyword: \'' + ($errorKeyword || '_exclusiveLimit') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: {} ';
+      if (it.opts.messages !== false) {
+        out += ' , message: \'' + ($exclusiveKeyword) + ' should be boolean\' ';
+      }
+      if (it.opts.verbose) {
+        out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+      }
+      out += ' } ';
+    } else {
+      out += ' {} ';
+    }
+    var __err = out;
+    out = $$outStack.pop();
+    if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+      if (it.async) {
+        out += ' throw new ValidationError([' + (__err) + ']); ';
+      } else {
+        out += ' validate.errors = [' + (__err) + ']; return false; ';
+      }
+    } else {
+      out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+    }
+    out += ' } else if( ';
+    if ($isData) {
+      out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || ';
+    }
+    out += ' ((exclusive' + ($lvl) + ' = ' + ($schemaValueExcl) + ' === true) ? ' + ($data) + ' ' + ($notOp) + '= ' + ($schemaValue) + ' : ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ') || ' + ($data) + ' !== ' + ($data) + ') { var op' + ($lvl) + ' = exclusive' + ($lvl) + ' ? \'' + ($op) + '\' : \'' + ($op) + '=\';';
+  } else {
+    var $exclusive = $schemaExcl === true,
+      $opStr = $op;
+    if (!$exclusive) $opStr += '=';
+    var $opExpr = '\'' + $opStr + '\'';
+    out += ' if ( ';
+    if ($isData) {
+      out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || ';
+    }
+    out += ' ' + ($data) + ' ' + ($notOp);
+    if ($exclusive) {
+      out += '=';
+    }
+    out += ' ' + ($schemaValue) + ' || ' + ($data) + ' !== ' + ($data) + ') {';
+  }
+  var $errorKeyword = $keyword;
+  var $$outStack = $$outStack || [];
+  $$outStack.push(out);
+  out = ''; /* istanbul ignore else */
+  if (it.createErrors !== false) {
+    out += ' { keyword: \'' + ($errorKeyword || '_limit') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { comparison: ' + ($opExpr) + ', limit: ' + ($schemaValue) + ', exclusive: ' + ($exclusive) + ' } ';
+    if (it.opts.messages !== false) {
+      out += ' , message: \'should be ' + ($opStr) + ' ';
+      if ($isData) {
+        out += '\' + ' + ($schemaValue);
+      } else {
+        out += '' + ($schema) + '\'';
+      }
+    }
+    if (it.opts.verbose) {
+      out += ' , schema:  ';
+      if ($isData) {
+        out += 'validate.schema' + ($schemaPath);
+      } else {
+        out += '' + ($schema);
+      }
+      out += '         , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+    }
+    out += ' } ';
+  } else {
+    out += ' {} ';
+  }
+  var __err = out;
+  out = $$outStack.pop();
+  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+    if (it.async) {
+      out += ' throw new ValidationError([' + (__err) + ']); ';
+    } else {
+      out += ' validate.errors = [' + (__err) + ']; return false; ';
+    }
+  } else {
+    out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+  }
+  out += ' } ';
+  if ($breakOnError) {
+    out += ' else { ';
+  }
+  return out;
+}
+
+},{}],14:[function(require,module,exports){
+'use strict';
+module.exports = function generate__limitItems(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  var $op = $keyword == 'maxItems' ? '>' : '<';
+  out += 'if ( ';
+  if ($isData) {
+    out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || ';
+  }
+  out += ' ' + ($data) + '.length ' + ($op) + ' ' + ($schemaValue) + ') { ';
+  var $errorKeyword = $keyword;
+  var $$outStack = $$outStack || [];
+  $$outStack.push(out);
+  out = ''; /* istanbul ignore else */
+  if (it.createErrors !== false) {
+    out += ' { keyword: \'' + ($errorKeyword || '_limitItems') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { limit: ' + ($schemaValue) + ' } ';
+    if (it.opts.messages !== false) {
+      out += ' , message: \'should NOT have ';
+      if ($keyword == 'maxItems') {
+        out += 'more';
+      } else {
+        out += 'less';
+      }
+      out += ' than ';
+      if ($isData) {
+        out += '\' + ' + ($schemaValue) + ' + \'';
+      } else {
+        out += '' + ($schema);
+      }
+      out += ' items\' ';
+    }
+    if (it.opts.verbose) {
+      out += ' , schema:  ';
+      if ($isData) {
+        out += 'validate.schema' + ($schemaPath);
+      } else {
+        out += '' + ($schema);
+      }
+      out += '         , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+    }
+    out += ' } ';
+  } else {
+    out += ' {} ';
+  }
+  var __err = out;
+  out = $$outStack.pop();
+  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+    if (it.async) {
+      out += ' throw new ValidationError([' + (__err) + ']); ';
+    } else {
+      out += ' validate.errors = [' + (__err) + ']; return false; ';
+    }
+  } else {
+    out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+  }
+  out += '} ';
+  if ($breakOnError) {
+    out += ' else { ';
+  }
+  return out;
+}
+
+},{}],15:[function(require,module,exports){
+'use strict';
+module.exports = function generate__limitLength(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  var $op = $keyword == 'maxLength' ? '>' : '<';
+  out += 'if ( ';
+  if ($isData) {
+    out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || ';
+  }
+  if (it.opts.unicode === false) {
+    out += ' ' + ($data) + '.length ';
+  } else {
+    out += ' ucs2length(' + ($data) + ') ';
+  }
+  out += ' ' + ($op) + ' ' + ($schemaValue) + ') { ';
+  var $errorKeyword = $keyword;
+  var $$outStack = $$outStack || [];
+  $$outStack.push(out);
+  out = ''; /* istanbul ignore else */
+  if (it.createErrors !== false) {
+    out += ' { keyword: \'' + ($errorKeyword || '_limitLength') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { limit: ' + ($schemaValue) + ' } ';
+    if (it.opts.messages !== false) {
+      out += ' , message: \'should NOT be ';
+      if ($keyword == 'maxLength') {
+        out += 'longer';
+      } else {
+        out += 'shorter';
+      }
+      out += ' than ';
+      if ($isData) {
+        out += '\' + ' + ($schemaValue) + ' + \'';
+      } else {
+        out += '' + ($schema);
+      }
+      out += ' characters\' ';
+    }
+    if (it.opts.verbose) {
+      out += ' , schema:  ';
+      if ($isData) {
+        out += 'validate.schema' + ($schemaPath);
+      } else {
+        out += '' + ($schema);
+      }
+      out += '         , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+    }
+    out += ' } ';
+  } else {
+    out += ' {} ';
+  }
+  var __err = out;
+  out = $$outStack.pop();
+  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+    if (it.async) {
+      out += ' throw new ValidationError([' + (__err) + ']); ';
+    } else {
+      out += ' validate.errors = [' + (__err) + ']; return false; ';
+    }
+  } else {
+    out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+  }
+  out += '} ';
+  if ($breakOnError) {
+    out += ' else { ';
+  }
+  return out;
+}
+
+},{}],16:[function(require,module,exports){
+'use strict';
+module.exports = function generate__limitProperties(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  var $op = $keyword == 'maxProperties' ? '>' : '<';
+  out += 'if ( ';
+  if ($isData) {
+    out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || ';
+  }
+  out += ' Object.keys(' + ($data) + ').length ' + ($op) + ' ' + ($schemaValue) + ') { ';
+  var $errorKeyword = $keyword;
+  var $$outStack = $$outStack || [];
+  $$outStack.push(out);
+  out = ''; /* istanbul ignore else */
+  if (it.createErrors !== false) {
+    out += ' { keyword: \'' + ($errorKeyword || '_limitProperties') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { limit: ' + ($schemaValue) + ' } ';
+    if (it.opts.messages !== false) {
+      out += ' , message: \'should NOT have ';
+      if ($keyword == 'maxProperties') {
+        out += 'more';
+      } else {
+        out += 'less';
+      }
+      out += ' than ';
+      if ($isData) {
+        out += '\' + ' + ($schemaValue) + ' + \'';
+      } else {
+        out += '' + ($schema);
+      }
+      out += ' properties\' ';
+    }
+    if (it.opts.verbose) {
+      out += ' , schema:  ';
+      if ($isData) {
+        out += 'validate.schema' + ($schemaPath);
+      } else {
+        out += '' + ($schema);
+      }
+      out += '         , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+    }
+    out += ' } ';
+  } else {
+    out += ' {} ';
+  }
+  var __err = out;
+  out = $$outStack.pop();
+  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+    if (it.async) {
+      out += ' throw new ValidationError([' + (__err) + ']); ';
+    } else {
+      out += ' validate.errors = [' + (__err) + ']; return false; ';
+    }
+  } else {
+    out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+  }
+  out += '} ';
+  if ($breakOnError) {
+    out += ' else { ';
+  }
+  return out;
+}
+
+},{}],17:[function(require,module,exports){
+'use strict';
+module.exports = function generate_allOf(it, $keyword) {
+  var out = ' ';
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $it = it.util.copy(it);
+  var $closingBraces = '';
+  $it.level++;
+  var $currentBaseId = $it.baseId;
+  var arr1 = $schema;
+  if (arr1) {
+    var $sch, $i = -1,
+      l1 = arr1.length - 1;
+    while ($i < l1) {
+      $sch = arr1[$i += 1];
+      if (it.util.schemaHasRules($sch, it.RULES.all)) {
+        $it.schema = $sch;
+        $it.schemaPath = $schemaPath + '[' + $i + ']';
+        $it.errSchemaPath = $errSchemaPath + '/' + $i;
+        out += '  ' + (it.validate($it)) + ' ';
+        $it.baseId = $currentBaseId;
+        if ($breakOnError) {
+          out += ' if (valid' + ($it.level) + ') { ';
+          $closingBraces += '}';
+        }
+      }
+    }
+  }
+  if ($breakOnError) {
+    out += ' ' + ($closingBraces.slice(0, -1));
+  }
+  out = it.util.cleanUpCode(out);
+  return out;
+}
+
+},{}],18:[function(require,module,exports){
+'use strict';
+module.exports = function generate_anyOf(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  var $errs = 'errs__' + $lvl;
+  var $it = it.util.copy(it);
+  var $closingBraces = '';
+  $it.level++;
+  var $noEmptySchema = $schema.every(function($sch) {
+    return it.util.schemaHasRules($sch, it.RULES.all);
+  });
+  if ($noEmptySchema) {
+    var $currentBaseId = $it.baseId;
+    out += ' var ' + ($errs) + ' = errors; var ' + ($valid) + ' = false;  ';
+    var $wasComposite = it.compositeRule;
+    it.compositeRule = $it.compositeRule = true;
+    var arr1 = $schema;
+    if (arr1) {
+      var $sch, $i = -1,
+        l1 = arr1.length - 1;
+      while ($i < l1) {
+        $sch = arr1[$i += 1];
+        $it.schema = $sch;
+        $it.schemaPath = $schemaPath + '[' + $i + ']';
+        $it.errSchemaPath = $errSchemaPath + '/' + $i;
+        out += '  ' + (it.validate($it)) + ' ';
+        $it.baseId = $currentBaseId;
+        out += ' ' + ($valid) + ' = ' + ($valid) + ' || valid' + ($it.level) + '; if (!' + ($valid) + ') { ';
+        $closingBraces += '}';
+      }
+    }
+    it.compositeRule = $it.compositeRule = $wasComposite;
+    out += ' ' + ($closingBraces) + ' if (!' + ($valid) + ') {  var err =   '; /* istanbul ignore else */
+    if (it.createErrors !== false) {
+      out += ' { keyword: \'' + ($errorKeyword || 'anyOf') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: {} ';
+      if (it.opts.messages !== false) {
+        out += ' , message: \'should match some schema in anyOf\' ';
+      }
+      if (it.opts.verbose) {
+        out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+      }
+      out += ' } ';
+    } else {
+      out += ' {} ';
+    }
+    out += ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } else {  errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; } ';
+    if (it.opts.allErrors) {
+      out += ' } ';
+    }
+    out = it.util.cleanUpCode(out);
+  } else {
+    if ($breakOnError) {
+      out += ' if (true) { ';
+    }
+  }
+  return out;
+}
+
+},{}],19:[function(require,module,exports){
+'use strict';
+module.exports = function generate_constant(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  if (!$isData) {
+    out += ' var schema' + ($lvl) + ' = validate.schema' + ($schemaPath) + ';';
+  }
+  out += 'var ' + ($valid) + ' = equal(' + ($data) + ', schema' + ($lvl) + '); if (!' + ($valid) + ') {   ';
+  var $$outStack = $$outStack || [];
+  $$outStack.push(out);
+  out = ''; /* istanbul ignore else */
+  if (it.createErrors !== false) {
+    out += ' { keyword: \'' + ($errorKeyword || 'constant') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: {} ';
+    if (it.opts.messages !== false) {
+      out += ' , message: \'should be equal to constant\' ';
+    }
+    if (it.opts.verbose) {
+      out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+    }
+    out += ' } ';
+  } else {
+    out += ' {} ';
+  }
+  var __err = out;
+  out = $$outStack.pop();
+  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+    if (it.async) {
+      out += ' throw new ValidationError([' + (__err) + ']); ';
+    } else {
+      out += ' validate.errors = [' + (__err) + ']; return false; ';
+    }
+  } else {
+    out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+  }
+  out += ' }';
+  return out;
+}
+
+},{}],20:[function(require,module,exports){
+'use strict';
+module.exports = function generate_dependencies(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $errs = 'errs__' + $lvl;
+  var $it = it.util.copy(it);
+  var $closingBraces = '';
+  $it.level++;
+  var $schemaDeps = {},
+    $propertyDeps = {};
+  for ($property in $schema) {
+    var $sch = $schema[$property];
+    var $deps = Array.isArray($sch) ? $propertyDeps : $schemaDeps;
+    $deps[$property] = $sch;
+  }
+  out += 'var ' + ($errs) + ' = errors;';
+  var $currentErrorPath = it.errorPath;
+  out += 'var missing' + ($lvl) + ';';
+  for (var $property in $propertyDeps) {
+    $deps = $propertyDeps[$property];
+    out += ' if (' + ($data) + (it.util.getProperty($property)) + ' !== undefined ';
+    if ($breakOnError) {
+      out += ' && ( ';
+      var arr1 = $deps;
+      if (arr1) {
+        var _$property, $i = -1,
+          l1 = arr1.length - 1;
+        while ($i < l1) {
+          _$property = arr1[$i += 1];
+          if ($i) {
+            out += ' || ';
+          }
+          var $prop = it.util.getProperty(_$property);
+          out += ' ( ' + ($data) + ($prop) + ' === undefined && (missing' + ($lvl) + ' = ' + (it.util.toQuotedString(it.opts.jsonPointers ? _$property : $prop)) + ') ) ';
+        }
+      }
+      out += ')) {  ';
+      var $propertyPath = 'missing' + $lvl,
+        $missingProperty = '\' + ' + $propertyPath + ' + \'';
+      if (it.opts._errorDataPathProperty) {
+        it.errorPath = it.opts.jsonPointers ? it.util.getPathExpr($currentErrorPath, $propertyPath, true) : $currentErrorPath + ' + ' + $propertyPath;
+      }
+      var $$outStack = $$outStack || [];
+      $$outStack.push(out);
+      out = ''; /* istanbul ignore else */
+      if (it.createErrors !== false) {
+        out += ' { keyword: \'' + ($errorKeyword || 'dependencies') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { property: \'' + (it.util.escapeQuotes($property)) + '\', missingProperty: \'' + ($missingProperty) + '\', depsCount: ' + ($deps.length) + ', deps: \'' + (it.util.escapeQuotes($deps.length == 1 ? $deps[0] : $deps.join(", "))) + '\' } ';
+        if (it.opts.messages !== false) {
+          out += ' , message: \'should have ';
+          if ($deps.length == 1) {
+            out += 'property ' + (it.util.escapeQuotes($deps[0]));
+          } else {
+            out += 'properties ' + (it.util.escapeQuotes($deps.join(", ")));
+          }
+          out += ' when property ' + (it.util.escapeQuotes($property)) + ' is present\' ';
+        }
+        if (it.opts.verbose) {
+          out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+        }
+        out += ' } ';
+      } else {
+        out += ' {} ';
+      }
+      var __err = out;
+      out = $$outStack.pop();
+      if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+        if (it.async) {
+          out += ' throw new ValidationError([' + (__err) + ']); ';
+        } else {
+          out += ' validate.errors = [' + (__err) + ']; return false; ';
+        }
+      } else {
+        out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+      }
+    } else {
+      out += ' ) { ';
+      var arr2 = $deps;
+      if (arr2) {
+        var $reqProperty, i2 = -1,
+          l2 = arr2.length - 1;
+        while (i2 < l2) {
+          $reqProperty = arr2[i2 += 1];
+          var $prop = it.util.getProperty($reqProperty),
+            $missingProperty = it.util.escapeQuotes($reqProperty);
+          if (it.opts._errorDataPathProperty) {
+            it.errorPath = it.util.getPath($currentErrorPath, $reqProperty, it.opts.jsonPointers);
+          }
+          out += ' if (' + ($data) + ($prop) + ' === undefined) {  var err =   '; /* istanbul ignore else */
+          if (it.createErrors !== false) {
+            out += ' { keyword: \'' + ($errorKeyword || 'dependencies') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { property: \'' + (it.util.escapeQuotes($property)) + '\', missingProperty: \'' + ($missingProperty) + '\', depsCount: ' + ($deps.length) + ', deps: \'' + (it.util.escapeQuotes($deps.length == 1 ? $deps[0] : $deps.join(", "))) + '\' } ';
+            if (it.opts.messages !== false) {
+              out += ' , message: \'should have ';
+              if ($deps.length == 1) {
+                out += 'property ' + (it.util.escapeQuotes($deps[0]));
+              } else {
+                out += 'properties ' + (it.util.escapeQuotes($deps.join(", ")));
+              }
+              out += ' when property ' + (it.util.escapeQuotes($property)) + ' is present\' ';
+            }
+            if (it.opts.verbose) {
+              out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+            }
+            out += ' } ';
+          } else {
+            out += ' {} ';
+          }
+          out += ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } ';
+        }
+      }
+    }
+    out += ' }   ';
+    if ($breakOnError) {
+      $closingBraces += '}';
+      out += ' else { ';
+    }
+  }
+  it.errorPath = $currentErrorPath;
+  var $currentBaseId = $it.baseId;
+  for (var $property in $schemaDeps) {
+    var $sch = $schemaDeps[$property];
+    if (it.util.schemaHasRules($sch, it.RULES.all)) {
+      out += ' valid' + ($it.level) + ' = true; if (' + ($data) + '[\'' + ($property) + '\'] !== undefined) { ';
+      $it.schema = $sch;
+      $it.schemaPath = $schemaPath + it.util.getProperty($property);
+      $it.errSchemaPath = $errSchemaPath + '/' + it.util.escapeFragment($property);
+      out += '  ' + (it.validate($it)) + ' ';
+      $it.baseId = $currentBaseId;
+      out += ' }  ';
+      if ($breakOnError) {
+        out += ' if (valid' + ($it.level) + ') { ';
+        $closingBraces += '}';
+      }
+    }
+  }
+  if ($breakOnError) {
+    out += '   ' + ($closingBraces) + ' if (' + ($errs) + ' == errors) {';
+  }
+  out = it.util.cleanUpCode(out);
+  return out;
+}
+
+},{}],21:[function(require,module,exports){
+'use strict';
+module.exports = function generate_enum(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  var $i = 'i' + $lvl;
+  if (!$isData) {
+    out += ' var schema' + ($lvl) + ' = validate.schema' + ($schemaPath) + ';';
+  }
+  out += 'var ' + ($valid) + ';';
+  if ($isData) {
+    out += ' if (schema' + ($lvl) + ' === undefined) ' + ($valid) + ' = true; else if (!Array.isArray(schema' + ($lvl) + ')) ' + ($valid) + ' = false; else {';
+  }
+  out += '' + ($valid) + ' = false;for (var ' + ($i) + '=0; ' + ($i) + '<schema' + ($lvl) + '.length; ' + ($i) + '++) if (equal(' + ($data) + ', schema' + ($lvl) + '[' + ($i) + '])) { ' + ($valid) + ' = true; break; }';
+  if ($isData) {
+    out += '  }  ';
+  }
+  out += ' if (!' + ($valid) + ') {   ';
+  var $$outStack = $$outStack || [];
+  $$outStack.push(out);
+  out = ''; /* istanbul ignore else */
+  if (it.createErrors !== false) {
+    out += ' { keyword: \'' + ($errorKeyword || 'enum') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: {} ';
+    if (it.opts.messages !== false) {
+      out += ' , message: \'should be equal to one of the allowed values\' ';
+    }
+    if (it.opts.verbose) {
+      out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+    }
+    out += ' } ';
+  } else {
+    out += ' {} ';
+  }
+  var __err = out;
+  out = $$outStack.pop();
+  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+    if (it.async) {
+      out += ' throw new ValidationError([' + (__err) + ']); ';
+    } else {
+      out += ' validate.errors = [' + (__err) + ']; return false; ';
+    }
+  } else {
+    out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+  }
+  out += ' }';
+  if ($breakOnError) {
+    out += ' else { ';
+  }
+  return out;
+}
+
+},{}],22:[function(require,module,exports){
+'use strict';
+module.exports = function generate_format(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  if (it.opts.format === false) {
+    if ($breakOnError) {
+      out += ' if (true) { ';
+    }
+    return out;
+  }
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  if ($isData) {
+    var $format = 'format' + $lvl;
+    out += ' var ' + ($format) + ' = formats[' + ($schemaValue) + ']; var isObject' + ($lvl) + ' = typeof ' + ($format) + ' == \'object\' && !(' + ($format) + ' instanceof RegExp) && ' + ($format) + '.validate; if (isObject' + ($lvl) + ') { var async' + ($lvl) + ' = ' + ($format) + '.async; ' + ($format) + ' = ' + ($format) + '.validate; } if (  ';
+    if ($isData) {
+      out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'string\') || ';
+    }
+    out += ' (' + ($format) + ' && !(typeof ' + ($format) + ' == \'function\' ? ';
+    if (it.async) {
+      out += ' (async' + ($lvl) + ' ? ' + (it.yieldAwait) + ' ' + ($format) + '(' + ($data) + ') : ' + ($format) + '(' + ($data) + ')) ';
+    } else {
+      out += ' ' + ($format) + '(' + ($data) + ') ';
+    }
+    out += ' : ' + ($format) + '.test(' + ($data) + ')))) {';
+  } else {
+    var $format = it.formats[$schema];
+    if (!$format) {
+      if ($breakOnError) {
+        out += ' if (true) { ';
+      }
+      return out;
+    }
+    var $isObject = typeof $format == 'object' && !($format instanceof RegExp) && $format.validate;
+    if ($isObject) {
+      var $async = $format.async === true;
+      $format = $format.validate;
+    }
+    if ($async) {
+      if (!it.async) throw new Error('async format in sync schema');
+      var $formatRef = 'formats' + it.util.getProperty($schema) + '.validate';
+      out += ' if (!(' + (it.yieldAwait) + ' ' + ($formatRef) + '(' + ($data) + '))) { ';
+    } else {
+      out += ' if (! ';
+      var $formatRef = 'formats' + it.util.getProperty($schema);
+      if ($isObject) $formatRef += '.validate';
+      if (typeof $format == 'function') {
+        out += ' ' + ($formatRef) + '(' + ($data) + ') ';
+      } else {
+        out += ' ' + ($formatRef) + '.test(' + ($data) + ') ';
+      }
+      out += ') { ';
+    }
+  }
+  var $$outStack = $$outStack || [];
+  $$outStack.push(out);
+  out = ''; /* istanbul ignore else */
+  if (it.createErrors !== false) {
+    out += ' { keyword: \'' + ($errorKeyword || 'format') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { format:  ';
+    if ($isData) {
+      out += '' + ($schemaValue);
+    } else {
+      out += '' + (it.util.toQuotedString($schema));
+    }
+    out += '  } ';
+    if (it.opts.messages !== false) {
+      out += ' , message: \'should match format "';
+      if ($isData) {
+        out += '\' + ' + ($schemaValue) + ' + \'';
+      } else {
+        out += '' + (it.util.escapeQuotes($schema));
+      }
+      out += '"\' ';
+    }
+    if (it.opts.verbose) {
+      out += ' , schema:  ';
+      if ($isData) {
+        out += 'validate.schema' + ($schemaPath);
+      } else {
+        out += '' + (it.util.toQuotedString($schema));
+      }
+      out += '         , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+    }
+    out += ' } ';
+  } else {
+    out += ' {} ';
+  }
+  var __err = out;
+  out = $$outStack.pop();
+  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+    if (it.async) {
+      out += ' throw new ValidationError([' + (__err) + ']); ';
+    } else {
+      out += ' validate.errors = [' + (__err) + ']; return false; ';
+    }
+  } else {
+    out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+  }
+  out += ' } ';
+  if ($breakOnError) {
+    out += ' else { ';
+  }
+  return out;
+}
+
+},{}],23:[function(require,module,exports){
+'use strict';
+module.exports = function generate_items(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  var $errs = 'errs__' + $lvl;
+  var $it = it.util.copy(it);
+  var $closingBraces = '';
+  $it.level++;
+  var $dataNxt = $it.dataLevel = it.dataLevel + 1,
+    $nextData = 'data' + $dataNxt,
+    $currentBaseId = it.baseId;
+  out += 'var ' + ($errs) + ' = errors;var ' + ($valid) + ';';
+  if (Array.isArray($schema)) {
+    var $additionalItems = it.schema.additionalItems;
+    if ($additionalItems === false) {
+      out += ' ' + ($valid) + ' = ' + ($data) + '.length <= ' + ($schema.length) + '; ';
+      var $currErrSchemaPath = $errSchemaPath;
+      $errSchemaPath = it.errSchemaPath + '/additionalItems';
+      out += '  if (!' + ($valid) + ') {   ';
+      var $$outStack = $$outStack || [];
+      $$outStack.push(out);
+      out = ''; /* istanbul ignore else */
+      if (it.createErrors !== false) {
+        out += ' { keyword: \'' + ($errorKeyword || 'additionalItems') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { limit: ' + ($schema.length) + ' } ';
+        if (it.opts.messages !== false) {
+          out += ' , message: \'should NOT have more than ' + ($schema.length) + ' items\' ';
+        }
+        if (it.opts.verbose) {
+          out += ' , schema: false , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+        }
+        out += ' } ';
+      } else {
+        out += ' {} ';
+      }
+      var __err = out;
+      out = $$outStack.pop();
+      if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+        if (it.async) {
+          out += ' throw new ValidationError([' + (__err) + ']); ';
+        } else {
+          out += ' validate.errors = [' + (__err) + ']; return false; ';
+        }
+      } else {
+        out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+      }
+      out += ' } ';
+      $errSchemaPath = $currErrSchemaPath;
+      if ($breakOnError) {
+        $closingBraces += '}';
+        out += ' else { ';
+      }
+    }
+    var arr1 = $schema;
+    if (arr1) {
+      var $sch, $i = -1,
+        l1 = arr1.length - 1;
+      while ($i < l1) {
+        $sch = arr1[$i += 1];
+        if (it.util.schemaHasRules($sch, it.RULES.all)) {
+          out += ' valid' + ($it.level) + ' = true; if (' + ($data) + '.length > ' + ($i) + ') { ';
+          var $passData = $data + '[' + $i + ']';
+          $it.schema = $sch;
+          $it.schemaPath = $schemaPath + '[' + $i + ']';
+          $it.errSchemaPath = $errSchemaPath + '/' + $i;
+          $it.errorPath = it.util.getPathExpr(it.errorPath, $i, it.opts.jsonPointers, true);
+          $it.dataPathArr[$dataNxt] = $i;
+          var $code = it.validate($it);
+          $it.baseId = $currentBaseId;
+          if (it.util.varOccurences($code, $nextData) < 2) {
+            out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' ';
+          } else {
+            out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' ';
+          }
+          out += ' }  ';
+          if ($breakOnError) {
+            out += ' if (valid' + ($it.level) + ') { ';
+            $closingBraces += '}';
+          }
+        }
+      }
+    }
+    if (typeof $additionalItems == 'object' && it.util.schemaHasRules($additionalItems, it.RULES.all)) {
+      $it.schema = $additionalItems;
+      $it.schemaPath = it.schemaPath + '.additionalItems';
+      $it.errSchemaPath = it.errSchemaPath + '/additionalItems';
+      out += ' valid' + ($it.level) + ' = true; if (' + ($data) + '.length > ' + ($schema.length) + ') {  for (var i' + ($lvl) + ' = ' + ($schema.length) + '; i' + ($lvl) + ' < ' + ($data) + '.length; i' + ($lvl) + '++) { ';
+      $it.errorPath = it.util.getPathExpr(it.errorPath, 'i' + $lvl, it.opts.jsonPointers, true);
+      var $passData = $data + '[i' + $lvl + ']';
+      $it.dataPathArr[$dataNxt] = 'i' + $lvl;
+      var $code = it.validate($it);
+      $it.baseId = $currentBaseId;
+      if (it.util.varOccurences($code, $nextData) < 2) {
+        out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' ';
+      } else {
+        out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' ';
+      }
+      if ($breakOnError) {
+        out += ' if (!valid' + ($it.level) + ') break; ';
+      }
+      out += ' } }  ';
+      if ($breakOnError) {
+        out += ' if (valid' + ($it.level) + ') { ';
+        $closingBraces += '}';
+      }
+    }
+  } else if (it.util.schemaHasRules($schema, it.RULES.all)) {
+    $it.schema = $schema;
+    $it.schemaPath = $schemaPath;
+    $it.errSchemaPath = $errSchemaPath;
+    out += '  for (var i' + ($lvl) + ' = ' + (0) + '; i' + ($lvl) + ' < ' + ($data) + '.length; i' + ($lvl) + '++) { ';
+    $it.errorPath = it.util.getPathExpr(it.errorPath, 'i' + $lvl, it.opts.jsonPointers, true);
+    var $passData = $data + '[i' + $lvl + ']';
+    $it.dataPathArr[$dataNxt] = 'i' + $lvl;
+    var $code = it.validate($it);
+    $it.baseId = $currentBaseId;
+    if (it.util.varOccurences($code, $nextData) < 2) {
+      out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' ';
+    } else {
+      out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' ';
+    }
+    if ($breakOnError) {
+      out += ' if (!valid' + ($it.level) + ') break; ';
+    }
+    out += ' }  ';
+    if ($breakOnError) {
+      out += ' if (valid' + ($it.level) + ') { ';
+      $closingBraces += '}';
+    }
+  }
+  if ($breakOnError) {
+    out += ' ' + ($closingBraces) + ' if (' + ($errs) + ' == errors) {';
+  }
+  out = it.util.cleanUpCode(out);
+  return out;
+}
+
+},{}],24:[function(require,module,exports){
+'use strict';
+module.exports = function generate_multipleOf(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  out += 'var division' + ($lvl) + ';if (';
+  if ($isData) {
+    out += ' ' + ($schemaValue) + ' !== undefined && ( typeof ' + ($schemaValue) + ' != \'number\' || ';
+  }
+  out += ' (division' + ($lvl) + ' = ' + ($data) + ' / ' + ($schemaValue) + ', ';
+  if (it.opts.multipleOfPrecision) {
+    out += ' Math.abs(Math.round(division' + ($lvl) + ') - division' + ($lvl) + ') > 1e-' + (it.opts.multipleOfPrecision) + ' ';
+  } else {
+    out += ' division' + ($lvl) + ' !== parseInt(division' + ($lvl) + ') ';
+  }
+  out += ' ) ';
+  if ($isData) {
+    out += '  )  ';
+  }
+  out += ' ) {   ';
+  var $$outStack = $$outStack || [];
+  $$outStack.push(out);
+  out = ''; /* istanbul ignore else */
+  if (it.createErrors !== false) {
+    out += ' { keyword: \'' + ($errorKeyword || 'multipleOf') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { multipleOf: ' + ($schemaValue) + ' } ';
+    if (it.opts.messages !== false) {
+      out += ' , message: \'should be multiple of ';
+      if ($isData) {
+        out += '\' + ' + ($schemaValue);
+      } else {
+        out += '' + ($schema) + '\'';
+      }
+    }
+    if (it.opts.verbose) {
+      out += ' , schema:  ';
+      if ($isData) {
+        out += 'validate.schema' + ($schemaPath);
+      } else {
+        out += '' + ($schema);
+      }
+      out += '         , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+    }
+    out += ' } ';
+  } else {
+    out += ' {} ';
+  }
+  var __err = out;
+  out = $$outStack.pop();
+  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+    if (it.async) {
+      out += ' throw new ValidationError([' + (__err) + ']); ';
+    } else {
+      out += ' validate.errors = [' + (__err) + ']; return false; ';
+    }
+  } else {
+    out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+  }
+  out += '} ';
+  if ($breakOnError) {
+    out += ' else { ';
+  }
+  return out;
+}
+
+},{}],25:[function(require,module,exports){
+'use strict';
+module.exports = function generate_not(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $errs = 'errs__' + $lvl;
+  var $it = it.util.copy(it);
+  $it.level++;
+  if (it.util.schemaHasRules($schema, it.RULES.all)) {
+    $it.schema = $schema;
+    $it.schemaPath = $schemaPath;
+    $it.errSchemaPath = $errSchemaPath;
+    out += ' var ' + ($errs) + ' = errors;  ';
+    var $wasComposite = it.compositeRule;
+    it.compositeRule = $it.compositeRule = true;
+    $it.createErrors = false;
+    var $allErrorsOption;
+    if ($it.opts.allErrors) {
+      $allErrorsOption = $it.opts.allErrors;
+      $it.opts.allErrors = false;
+    }
+    out += ' ' + (it.validate($it)) + ' ';
+    $it.createErrors = true;
+    if ($allErrorsOption) $it.opts.allErrors = $allErrorsOption;
+    it.compositeRule = $it.compositeRule = $wasComposite;
+    out += ' if (valid' + ($it.level) + ') {   ';
+    var $$outStack = $$outStack || [];
+    $$outStack.push(out);
+    out = ''; /* istanbul ignore else */
+    if (it.createErrors !== false) {
+      out += ' { keyword: \'' + ($errorKeyword || 'not') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: {} ';
+      if (it.opts.messages !== false) {
+        out += ' , message: \'should NOT be valid\' ';
+      }
+      if (it.opts.verbose) {
+        out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+      }
+      out += ' } ';
+    } else {
+      out += ' {} ';
+    }
+    var __err = out;
+    out = $$outStack.pop();
+    if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+      if (it.async) {
+        out += ' throw new ValidationError([' + (__err) + ']); ';
+      } else {
+        out += ' validate.errors = [' + (__err) + ']; return false; ';
+      }
+    } else {
+      out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+    }
+    out += ' } else {  errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; } ';
+    if (it.opts.allErrors) {
+      out += ' } ';
+    }
+  } else {
+    out += '  var err =   '; /* istanbul ignore else */
+    if (it.createErrors !== false) {
+      out += ' { keyword: \'' + ($errorKeyword || 'not') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: {} ';
+      if (it.opts.messages !== false) {
+        out += ' , message: \'should NOT be valid\' ';
+      }
+      if (it.opts.verbose) {
+        out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+      }
+      out += ' } ';
+    } else {
+      out += ' {} ';
+    }
+    out += ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+    if ($breakOnError) {
+      out += ' if (false) { ';
+    }
+  }
+  return out;
+}
+
+},{}],26:[function(require,module,exports){
+'use strict';
+module.exports = function generate_oneOf(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  var $errs = 'errs__' + $lvl;
+  var $it = it.util.copy(it);
+  var $closingBraces = '';
+  $it.level++;
+  out += 'var ' + ($errs) + ' = errors;var prevValid' + ($lvl) + ' = false;var ' + ($valid) + ' = false;';
+  var $currentBaseId = $it.baseId;
+  var $wasComposite = it.compositeRule;
+  it.compositeRule = $it.compositeRule = true;
+  var arr1 = $schema;
+  if (arr1) {
+    var $sch, $i = -1,
+      l1 = arr1.length - 1;
+    while ($i < l1) {
+      $sch = arr1[$i += 1];
+      if (it.util.schemaHasRules($sch, it.RULES.all)) {
+        $it.schema = $sch;
+        $it.schemaPath = $schemaPath + '[' + $i + ']';
+        $it.errSchemaPath = $errSchemaPath + '/' + $i;
+        out += '  ' + (it.validate($it)) + ' ';
+        $it.baseId = $currentBaseId;
+      } else {
+        out += ' var valid' + ($it.level) + ' = true; ';
+      }
+      if ($i) {
+        out += ' if (valid' + ($it.level) + ' && prevValid' + ($lvl) + ') ' + ($valid) + ' = false; else { ';
+        $closingBraces += '}';
+      }
+      out += ' if (valid' + ($it.level) + ') ' + ($valid) + ' = prevValid' + ($lvl) + ' = true;';
+    }
+  }
+  it.compositeRule = $it.compositeRule = $wasComposite;
+  out += '' + ($closingBraces) + 'if (!' + ($valid) + ') {   ';
+  var $$outStack = $$outStack || [];
+  $$outStack.push(out);
+  out = ''; /* istanbul ignore else */
+  if (it.createErrors !== false) {
+    out += ' { keyword: \'' + ($errorKeyword || 'oneOf') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: {} ';
+    if (it.opts.messages !== false) {
+      out += ' , message: \'should match exactly one schema in oneOf\' ';
+    }
+    if (it.opts.verbose) {
+      out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+    }
+    out += ' } ';
+  } else {
+    out += ' {} ';
+  }
+  var __err = out;
+  out = $$outStack.pop();
+  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+    if (it.async) {
+      out += ' throw new ValidationError([' + (__err) + ']); ';
+    } else {
+      out += ' validate.errors = [' + (__err) + ']; return false; ';
+    }
+  } else {
+    out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+  }
+  out += '} else {  errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; }';
+  if (it.opts.allErrors) {
+    out += ' } ';
+  }
+  return out;
+}
+
+},{}],27:[function(require,module,exports){
+'use strict';
+module.exports = function generate_pattern(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  var $regexp = $isData ? '(new RegExp(' + $schemaValue + '))' : it.usePattern($schema);
+  out += 'if ( ';
+  if ($isData) {
+    out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'string\') || ';
+  }
+  out += ' !' + ($regexp) + '.test(' + ($data) + ') ) {   ';
+  var $$outStack = $$outStack || [];
+  $$outStack.push(out);
+  out = ''; /* istanbul ignore else */
+  if (it.createErrors !== false) {
+    out += ' { keyword: \'' + ($errorKeyword || 'pattern') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { pattern:  ';
+    if ($isData) {
+      out += '' + ($schemaValue);
+    } else {
+      out += '' + (it.util.toQuotedString($schema));
+    }
+    out += '  } ';
+    if (it.opts.messages !== false) {
+      out += ' , message: \'should match pattern "';
+      if ($isData) {
+        out += '\' + ' + ($schemaValue) + ' + \'';
+      } else {
+        out += '' + (it.util.escapeQuotes($schema));
+      }
+      out += '"\' ';
+    }
+    if (it.opts.verbose) {
+      out += ' , schema:  ';
+      if ($isData) {
+        out += 'validate.schema' + ($schemaPath);
+      } else {
+        out += '' + (it.util.toQuotedString($schema));
+      }
+      out += '         , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+    }
+    out += ' } ';
+  } else {
+    out += ' {} ';
+  }
+  var __err = out;
+  out = $$outStack.pop();
+  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+    if (it.async) {
+      out += ' throw new ValidationError([' + (__err) + ']); ';
+    } else {
+      out += ' validate.errors = [' + (__err) + ']; return false; ';
+    }
+  } else {
+    out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+  }
+  out += '} ';
+  if ($breakOnError) {
+    out += ' else { ';
+  }
+  return out;
+}
+
+},{}],28:[function(require,module,exports){
+'use strict';
+module.exports = function generate_patternRequired(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  var $key = 'key' + $lvl,
+    $matched = 'patternMatched' + $lvl,
+    $closingBraces = '',
+    $ownProperties = it.opts.ownProperties;
+  out += 'var ' + ($valid) + ' = true;';
+  var arr1 = $schema;
+  if (arr1) {
+    var $pProperty, i1 = -1,
+      l1 = arr1.length - 1;
+    while (i1 < l1) {
+      $pProperty = arr1[i1 += 1];
+      out += ' var ' + ($matched) + ' = false; for (var ' + ($key) + ' in ' + ($data) + ') {  ';
+      if ($ownProperties) {
+        out += ' if (!Object.prototype.hasOwnProperty.call(' + ($data) + ', ' + ($key) + ')) continue; ';
+      }
+      out += ' ' + ($matched) + ' = ' + (it.usePattern($pProperty)) + '.test(' + ($key) + '); if (' + ($matched) + ') break; } ';
+      var $missingPattern = it.util.escapeQuotes($pProperty);
+      out += ' if (!' + ($matched) + ') { ' + ($valid) + ' = false;  var err =   '; /* istanbul ignore else */
+      if (it.createErrors !== false) {
+        out += ' { keyword: \'' + ($errorKeyword || 'patternRequired') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { missingPattern: \'' + ($missingPattern) + '\' } ';
+        if (it.opts.messages !== false) {
+          out += ' , message: \'should have property matching pattern \\\'' + ($missingPattern) + '\\\'\' ';
+        }
+        if (it.opts.verbose) {
+          out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+        }
+        out += ' } ';
+      } else {
+        out += ' {} ';
+      }
+      out += ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; }   ';
+      if ($breakOnError) {
+        $closingBraces += '}';
+        out += ' else { ';
+      }
+    }
+  }
+  out += '' + ($closingBraces);
+  return out;
+}
+
+},{}],29:[function(require,module,exports){
+'use strict';
+module.exports = function generate_properties(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  var $errs = 'errs__' + $lvl;
+  var $it = it.util.copy(it);
+  var $closingBraces = '';
+  $it.level++;
+  var $key = 'key' + $lvl,
+    $dataNxt = $it.dataLevel = it.dataLevel + 1,
+    $nextData = 'data' + $dataNxt;
+  var $schemaKeys = Object.keys($schema || {}),
+    $pProperties = it.schema.patternProperties || {},
+    $pPropertyKeys = Object.keys($pProperties),
+    $aProperties = it.schema.additionalProperties,
+    $someProperties = $schemaKeys.length || $pPropertyKeys.length,
+    $noAdditional = $aProperties === false,
+    $additionalIsSchema = typeof $aProperties == 'object' && Object.keys($aProperties).length,
+    $removeAdditional = it.opts.removeAdditional,
+    $checkAdditional = $noAdditional || $additionalIsSchema || $removeAdditional,
+    $ownProperties = it.opts.ownProperties,
+    $currentBaseId = it.baseId;
+  var $required = it.schema.required;
+  if ($required && !(it.opts.v5 && $required.$data) && $required.length < it.opts.loopRequired) var $requiredHash = it.util.toHash($required);
+  if (it.opts.v5) {
+    var $pgProperties = it.schema.patternGroups || {},
+      $pgPropertyKeys = Object.keys($pgProperties);
+  }
+  out += 'var ' + ($errs) + ' = errors;var valid' + ($it.level) + ' = true;';
+  if ($checkAdditional) {
+    out += ' for (var ' + ($key) + ' in ' + ($data) + ') {  ';
+    if ($ownProperties) {
+      out += ' if (!Object.prototype.hasOwnProperty.call(' + ($data) + ', ' + ($key) + ')) continue; ';
+    }
+    if ($someProperties) {
+      out += ' var isAdditional' + ($lvl) + ' = !(false ';
+      if ($schemaKeys.length) {
+        if ($schemaKeys.length > 5) {
+          out += ' || validate.schema' + ($schemaPath) + '[' + ($key) + '] ';
+        } else {
+          var arr1 = $schemaKeys;
+          if (arr1) {
+            var $propertyKey, i1 = -1,
+              l1 = arr1.length - 1;
+            while (i1 < l1) {
+              $propertyKey = arr1[i1 += 1];
+              out += ' || ' + ($key) + ' == ' + (it.util.toQuotedString($propertyKey)) + ' ';
+            }
+          }
+        }
+      }
+      if ($pPropertyKeys.length) {
+        var arr2 = $pPropertyKeys;
+        if (arr2) {
+          var $pProperty, $i = -1,
+            l2 = arr2.length - 1;
+          while ($i < l2) {
+            $pProperty = arr2[$i += 1];
+            out += ' || ' + (it.usePattern($pProperty)) + '.test(' + ($key) + ') ';
+          }
+        }
+      }
+      if (it.opts.v5 && $pgPropertyKeys && $pgPropertyKeys.length) {
+        var arr3 = $pgPropertyKeys;
+        if (arr3) {
+          var $pgProperty, $i = -1,
+            l3 = arr3.length - 1;
+          while ($i < l3) {
+            $pgProperty = arr3[$i += 1];
+            out += ' || ' + (it.usePattern($pgProperty)) + '.test(' + ($key) + ') ';
+          }
+        }
+      }
+      out += ' ); if (isAdditional' + ($lvl) + ') { ';
+    }
+    if ($removeAdditional == 'all') {
+      out += ' delete ' + ($data) + '[' + ($key) + ']; ';
+    } else {
+      var $currentErrorPath = it.errorPath;
+      var $additionalProperty = '\' + key' + $lvl + ' + \'';
+      if (it.opts._errorDataPathProperty) {
+        it.errorPath = it.util.getPathExpr(it.errorPath, 'key' + $lvl, it.opts.jsonPointers);
+      }
+      if ($noAdditional) {
+        if ($removeAdditional) {
+          out += ' delete ' + ($data) + '[' + ($key) + ']; ';
+        } else {
+          out += ' valid' + ($it.level) + ' = false; ';
+          var $currErrSchemaPath = $errSchemaPath;
+          $errSchemaPath = it.errSchemaPath + '/additionalProperties';
+          var $$outStack = $$outStack || [];
+          $$outStack.push(out);
+          out = ''; /* istanbul ignore else */
+          if (it.createErrors !== false) {
+            out += ' { keyword: \'' + ($errorKeyword || 'additionalProperties') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { additionalProperty: \'' + ($additionalProperty) + '\' } ';
+            if (it.opts.messages !== false) {
+              out += ' , message: \'should NOT have additional properties\' ';
+            }
+            if (it.opts.verbose) {
+              out += ' , schema: false , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+            }
+            out += ' } ';
+          } else {
+            out += ' {} ';
+          }
+          var __err = out;
+          out = $$outStack.pop();
+          if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+            if (it.async) {
+              out += ' throw new ValidationError([' + (__err) + ']); ';
+            } else {
+              out += ' validate.errors = [' + (__err) + ']; return false; ';
+            }
+          } else {
+            out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+          }
+          $errSchemaPath = $currErrSchemaPath;
+          if ($breakOnError) {
+            out += ' break; ';
+          }
+        }
+      } else if ($additionalIsSchema) {
+        if ($removeAdditional == 'failing') {
+          out += ' var ' + ($errs) + ' = errors;  ';
+          var $wasComposite = it.compositeRule;
+          it.compositeRule = $it.compositeRule = true;
+          $it.schema = $aProperties;
+          $it.schemaPath = it.schemaPath + '.additionalProperties';
+          $it.errSchemaPath = it.errSchemaPath + '/additionalProperties';
+          $it.errorPath = it.opts._errorDataPathProperty ? it.errorPath : it.util.getPathExpr(it.errorPath, 'key' + $lvl, it.opts.jsonPointers);
+          var $passData = $data + '[key' + $lvl + ']';
+          $it.dataPathArr[$dataNxt] = 'key' + $lvl;
+          var $code = it.validate($it);
+          $it.baseId = $currentBaseId;
+          if (it.util.varOccurences($code, $nextData) < 2) {
+            out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' ';
+          } else {
+            out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' ';
+          }
+          out += ' if (!valid' + ($it.level) + ') { errors = ' + ($errs) + '; if (validate.errors !== null) { if (errors) validate.errors.length = errors; else validate.errors = null; } delete ' + ($data) + '[' + ($key) + ']; }  ';
+          it.compositeRule = $it.compositeRule = $wasComposite;
+        } else {
+          $it.schema = $aProperties;
+          $it.schemaPath = it.schemaPath + '.additionalProperties';
+          $it.errSchemaPath = it.errSchemaPath + '/additionalProperties';
+          $it.errorPath = it.opts._errorDataPathProperty ? it.errorPath : it.util.getPathExpr(it.errorPath, 'key' + $lvl, it.opts.jsonPointers);
+          var $passData = $data + '[key' + $lvl + ']';
+          $it.dataPathArr[$dataNxt] = 'key' + $lvl;
+          var $code = it.validate($it);
+          $it.baseId = $currentBaseId;
+          if (it.util.varOccurences($code, $nextData) < 2) {
+            out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' ';
+          } else {
+            out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' ';
+          }
+          if ($breakOnError) {
+            out += ' if (!valid' + ($it.level) + ') break; ';
+          }
+        }
+      }
+      it.errorPath = $currentErrorPath;
+    }
+    if ($someProperties) {
+      out += ' } ';
+    }
+    out += ' }  ';
+    if ($breakOnError) {
+      out += ' if (valid' + ($it.level) + ') { ';
+      $closingBraces += '}';
+    }
+  }
+  var $useDefaults = it.opts.useDefaults && !it.compositeRule;
+  if ($schemaKeys.length) {
+    var arr4 = $schemaKeys;
+    if (arr4) {
+      var $propertyKey, i4 = -1,
+        l4 = arr4.length - 1;
+      while (i4 < l4) {
+        $propertyKey = arr4[i4 += 1];
+        var $sch = $schema[$propertyKey];
+        if (it.util.schemaHasRules($sch, it.RULES.all)) {
+          var $prop = it.util.getProperty($propertyKey),
+            $passData = $data + $prop,
+            $hasDefault = $useDefaults && $sch.default !== undefined;
+          $it.schema = $sch;
+          $it.schemaPath = $schemaPath + $prop;
+          $it.errSchemaPath = $errSchemaPath + '/' + it.util.escapeFragment($propertyKey);
+          $it.errorPath = it.util.getPath(it.errorPath, $propertyKey, it.opts.jsonPointers);
+          $it.dataPathArr[$dataNxt] = it.util.toQuotedString($propertyKey);
+          var $code = it.validate($it);
+          $it.baseId = $currentBaseId;
+          if (it.util.varOccurences($code, $nextData) < 2) {
+            $code = it.util.varReplace($code, $nextData, $passData);
+            var $useData = $passData;
+          } else {
+            var $useData = $nextData;
+            out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ';
+          }
+          if ($hasDefault) {
+            out += ' ' + ($code) + ' ';
+          } else {
+            if ($requiredHash && $requiredHash[$propertyKey]) {
+              out += ' if (' + ($useData) + ' === undefined) { valid' + ($it.level) + ' = false; ';
+              var $currentErrorPath = it.errorPath,
+                $currErrSchemaPath = $errSchemaPath,
+                $missingProperty = it.util.escapeQuotes($propertyKey);
+              if (it.opts._errorDataPathProperty) {
+                it.errorPath = it.util.getPath($currentErrorPath, $propertyKey, it.opts.jsonPointers);
+              }
+              $errSchemaPath = it.errSchemaPath + '/required';
+              var $$outStack = $$outStack || [];
+              $$outStack.push(out);
+              out = ''; /* istanbul ignore else */
+              if (it.createErrors !== false) {
+                out += ' { keyword: \'' + ($errorKeyword || 'required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { missingProperty: \'' + ($missingProperty) + '\' } ';
+                if (it.opts.messages !== false) {
+                  out += ' , message: \'';
+                  if (it.opts._errorDataPathProperty) {
+                    out += 'is a required property';
+                  } else {
+                    out += 'should have required property \\\'' + ($missingProperty) + '\\\'';
+                  }
+                  out += '\' ';
+                }
+                if (it.opts.verbose) {
+                  out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+                }
+                out += ' } ';
+              } else {
+                out += ' {} ';
+              }
+              var __err = out;
+              out = $$outStack.pop();
+              if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+                if (it.async) {
+                  out += ' throw new ValidationError([' + (__err) + ']); ';
+                } else {
+                  out += ' validate.errors = [' + (__err) + ']; return false; ';
+                }
+              } else {
+                out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+              }
+              $errSchemaPath = $currErrSchemaPath;
+              it.errorPath = $currentErrorPath;
+              out += ' } else { ';
+            } else {
+              if ($breakOnError) {
+                out += ' if (' + ($useData) + ' === undefined) { valid' + ($it.level) + ' = true; } else { ';
+              } else {
+                out += ' if (' + ($useData) + ' !== undefined) { ';
+              }
+            }
+            out += ' ' + ($code) + ' } ';
+          }
+        }
+        if ($breakOnError) {
+          out += ' if (valid' + ($it.level) + ') { ';
+          $closingBraces += '}';
+        }
+      }
+    }
+  }
+  var arr5 = $pPropertyKeys;
+  if (arr5) {
+    var $pProperty, i5 = -1,
+      l5 = arr5.length - 1;
+    while (i5 < l5) {
+      $pProperty = arr5[i5 += 1];
+      var $sch = $pProperties[$pProperty];
+      if (it.util.schemaHasRules($sch, it.RULES.all)) {
+        $it.schema = $sch;
+        $it.schemaPath = it.schemaPath + '.patternProperties' + it.util.getProperty($pProperty);
+        $it.errSchemaPath = it.errSchemaPath + '/patternProperties/' + it.util.escapeFragment($pProperty);
+        out += ' for (var ' + ($key) + ' in ' + ($data) + ') {  ';
+        if ($ownProperties) {
+          out += ' if (!Object.prototype.hasOwnProperty.call(' + ($data) + ', ' + ($key) + ')) continue; ';
+        }
+        out += ' if (' + (it.usePattern($pProperty)) + '.test(' + ($key) + ')) { ';
+        $it.errorPath = it.util.getPathExpr(it.errorPath, 'key' + $lvl, it.opts.jsonPointers);
+        var $passData = $data + '[key' + $lvl + ']';
+        $it.dataPathArr[$dataNxt] = 'key' + $lvl;
+        var $code = it.validate($it);
+        $it.baseId = $currentBaseId;
+        if (it.util.varOccurences($code, $nextData) < 2) {
+          out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' ';
+        } else {
+          out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' ';
+        }
+        if ($breakOnError) {
+          out += ' if (!valid' + ($it.level) + ') break; ';
+        }
+        out += ' } ';
+        if ($breakOnError) {
+          out += ' else valid' + ($it.level) + ' = true; ';
+        }
+        out += ' }  ';
+        if ($breakOnError) {
+          out += ' if (valid' + ($it.level) + ') { ';
+          $closingBraces += '}';
+        }
+      }
+    }
+  }
+  if (it.opts.v5) {
+    var arr6 = $pgPropertyKeys;
+    if (arr6) {
+      var $pgProperty, i6 = -1,
+        l6 = arr6.length - 1;
+      while (i6 < l6) {
+        $pgProperty = arr6[i6 += 1];
+        var $pgSchema = $pgProperties[$pgProperty],
+          $sch = $pgSchema.schema;
+        if (it.util.schemaHasRules($sch, it.RULES.all)) {
+          $it.schema = $sch;
+          $it.schemaPath = it.schemaPath + '.patternGroups' + it.util.getProperty($pgProperty) + '.schema';
+          $it.errSchemaPath = it.errSchemaPath + '/patternGroups/' + it.util.escapeFragment($pgProperty) + '/schema';
+          out += ' var pgPropCount' + ($lvl) + ' = 0; for (var ' + ($key) + ' in ' + ($data) + ') {  ';
+          if ($ownProperties) {
+            out += ' if (!Object.prototype.hasOwnProperty.call(' + ($data) + ', ' + ($key) + ')) continue; ';
+          }
+          out += ' if (' + (it.usePattern($pgProperty)) + '.test(' + ($key) + ')) { pgPropCount' + ($lvl) + '++; ';
+          $it.errorPath = it.util.getPathExpr(it.errorPath, 'key' + $lvl, it.opts.jsonPointers);
+          var $passData = $data + '[key' + $lvl + ']';
+          $it.dataPathArr[$dataNxt] = 'key' + $lvl;
+          var $code = it.validate($it);
+          $it.baseId = $currentBaseId;
+          if (it.util.varOccurences($code, $nextData) < 2) {
+            out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' ';
+          } else {
+            out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' ';
+          }
+          if ($breakOnError) {
+            out += ' if (!valid' + ($it.level) + ') break; ';
+          }
+          out += ' } ';
+          if ($breakOnError) {
+            out += ' else valid' + ($it.level) + ' = true; ';
+          }
+          out += ' }  ';
+          if ($breakOnError) {
+            out += ' if (valid' + ($it.level) + ') { ';
+            $closingBraces += '}';
+          }
+          var $pgMin = $pgSchema.minimum,
+            $pgMax = $pgSchema.maximum;
+          if ($pgMin !== undefined || $pgMax !== undefined) {
+            out += ' var ' + ($valid) + ' = true; ';
+            var $currErrSchemaPath = $errSchemaPath;
+            if ($pgMin !== undefined) {
+              var $limit = $pgMin,
+                $reason = 'minimum',
+                $moreOrLess = 'less';
+              out += ' ' + ($valid) + ' = pgPropCount' + ($lvl) + ' >= ' + ($pgMin) + '; ';
+              $errSchemaPath = it.errSchemaPath + '/patternGroups/minimum';
+              out += '  if (!' + ($valid) + ') {   ';
+              var $$outStack = $$outStack || [];
+              $$outStack.push(out);
+              out = ''; /* istanbul ignore else */
+              if (it.createErrors !== false) {
+                out += ' { keyword: \'' + ($errorKeyword || 'patternGroups') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { reason: \'' + ($reason) + '\', limit: ' + ($limit) + ', pattern: \'' + (it.util.escapeQuotes($pgProperty)) + '\' } ';
+                if (it.opts.messages !== false) {
+                  out += ' , message: \'should NOT have ' + ($moreOrLess) + ' than ' + ($limit) + ' properties matching pattern "' + (it.util.escapeQuotes($pgProperty)) + '"\' ';
+                }
+                if (it.opts.verbose) {
+                  out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+                }
+                out += ' } ';
+              } else {
+                out += ' {} ';
+              }
+              var __err = out;
+              out = $$outStack.pop();
+              if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+                if (it.async) {
+                  out += ' throw new ValidationError([' + (__err) + ']); ';
+                } else {
+                  out += ' validate.errors = [' + (__err) + ']; return false; ';
+                }
+              } else {
+                out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+              }
+              out += ' } ';
+              if ($pgMax !== undefined) {
+                out += ' else ';
+              }
+            }
+            if ($pgMax !== undefined) {
+              var $limit = $pgMax,
+                $reason = 'maximum',
+                $moreOrLess = 'more';
+              out += ' ' + ($valid) + ' = pgPropCount' + ($lvl) + ' <= ' + ($pgMax) + '; ';
+              $errSchemaPath = it.errSchemaPath + '/patternGroups/maximum';
+              out += '  if (!' + ($valid) + ') {   ';
+              var $$outStack = $$outStack || [];
+              $$outStack.push(out);
+              out = ''; /* istanbul ignore else */
+              if (it.createErrors !== false) {
+                out += ' { keyword: \'' + ($errorKeyword || 'patternGroups') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { reason: \'' + ($reason) + '\', limit: ' + ($limit) + ', pattern: \'' + (it.util.escapeQuotes($pgProperty)) + '\' } ';
+                if (it.opts.messages !== false) {
+                  out += ' , message: \'should NOT have ' + ($moreOrLess) + ' than ' + ($limit) + ' properties matching pattern "' + (it.util.escapeQuotes($pgProperty)) + '"\' ';
+                }
+                if (it.opts.verbose) {
+                  out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+                }
+                out += ' } ';
+              } else {
+                out += ' {} ';
+              }
+              var __err = out;
+              out = $$outStack.pop();
+              if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+                if (it.async) {
+                  out += ' throw new ValidationError([' + (__err) + ']); ';
+                } else {
+                  out += ' validate.errors = [' + (__err) + ']; return false; ';
+                }
+              } else {
+                out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+              }
+              out += ' } ';
+            }
+            $errSchemaPath = $currErrSchemaPath;
+            if ($breakOnError) {
+              out += ' if (' + ($valid) + ') { ';
+              $closingBraces += '}';
+            }
+          }
+        }
+      }
+    }
+  }
+  if ($breakOnError) {
+    out += ' ' + ($closingBraces) + ' if (' + ($errs) + ' == errors) {';
+  }
+  out = it.util.cleanUpCode(out);
+  return out;
+}
+
+},{}],30:[function(require,module,exports){
+'use strict';
+module.exports = function generate_ref(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  var $async, $refCode;
+  if ($schema == '#' || $schema == '#/') {
+    if (it.isRoot) {
+      $async = it.async;
+      $refCode = 'validate';
+    } else {
+      $async = it.root.schema.$async === true;
+      $refCode = 'root.refVal[0]';
+    }
+  } else {
+    var $refVal = it.resolveRef(it.baseId, $schema, it.isRoot);
+    if ($refVal === undefined) {
+      var $message = 'can\'t resolve reference ' + $schema + ' from id ' + it.baseId;
+      if (it.opts.missingRefs == 'fail') {
+        console.log($message);
+        var $$outStack = $$outStack || [];
+        $$outStack.push(out);
+        out = ''; /* istanbul ignore else */
+        if (it.createErrors !== false) {
+          out += ' { keyword: \'' + ($errorKeyword || '$ref') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { ref: \'' + (it.util.escapeQuotes($schema)) + '\' } ';
+          if (it.opts.messages !== false) {
+            out += ' , message: \'can\\\'t resolve reference ' + (it.util.escapeQuotes($schema)) + '\' ';
+          }
+          if (it.opts.verbose) {
+            out += ' , schema: ' + (it.util.toQuotedString($schema)) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+          }
+          out += ' } ';
+        } else {
+          out += ' {} ';
+        }
+        var __err = out;
+        out = $$outStack.pop();
+        if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+          if (it.async) {
+            out += ' throw new ValidationError([' + (__err) + ']); ';
+          } else {
+            out += ' validate.errors = [' + (__err) + ']; return false; ';
+          }
+        } else {
+          out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+        }
+        if ($breakOnError) {
+          out += ' if (false) { ';
+        }
+      } else if (it.opts.missingRefs == 'ignore') {
+        console.log($message);
+        if ($breakOnError) {
+          out += ' if (true) { ';
+        }
+      } else {
+        var $error = new Error($message);
+        $error.missingRef = it.resolve.url(it.baseId, $schema);
+        $error.missingSchema = it.resolve.normalizeId(it.resolve.fullPath($error.missingRef));
+        throw $error;
+      }
+    } else if ($refVal.inline) {
+      var $it = it.util.copy(it);
+      $it.level++;
+      $it.schema = $refVal.schema;
+      $it.schemaPath = '';
+      $it.errSchemaPath = $schema;
+      var $code = it.validate($it).replace(/validate\.schema/g, $refVal.code);
+      out += ' ' + ($code) + ' ';
+      if ($breakOnError) {
+        out += ' if (valid' + ($it.level) + ') { ';
+      }
+    } else {
+      $async = $refVal.$async === true;
+      $refCode = $refVal.code;
+    }
+  }
+  if ($refCode) {
+    var $$outStack = $$outStack || [];
+    $$outStack.push(out);
+    out = '';
+    if (it.opts.passContext) {
+      out += ' ' + ($refCode) + '.call(this, ';
+    } else {
+      out += ' ' + ($refCode) + '( ';
+    }
+    out += ' ' + ($data) + ', (dataPath || \'\')';
+    if (it.errorPath != '""') {
+      out += ' + ' + (it.errorPath);
+    }
+    if ($dataLvl) {
+      out += ' , data' + (($dataLvl - 1) || '') + ' , ' + (it.dataPathArr[$dataLvl]) + ' ';
+    } else {
+      out += ' , parentData , parentDataProperty ';
+    }
+    out += ')  ';
+    var __callValidate = out;
+    out = $$outStack.pop();
+    if ($async) {
+      if (!it.async) throw new Error('async schema referenced by sync schema');
+      out += ' try { ';
+      if ($breakOnError) {
+        out += 'var ' + ($valid) + ' =';
+      }
+      out += ' ' + (it.yieldAwait) + ' ' + (__callValidate) + '; } catch (e) { if (!(e instanceof ValidationError)) throw e; if (vErrors === null) vErrors = e.errors; else vErrors = vErrors.concat(e.errors); errors = vErrors.length; } ';
+      if ($breakOnError) {
+        out += ' if (' + ($valid) + ') { ';
+      }
+    } else {
+      out += ' if (!' + (__callValidate) + ') { if (vErrors === null) vErrors = ' + ($refCode) + '.errors; else vErrors = vErrors.concat(' + ($refCode) + '.errors); errors = vErrors.length; } ';
+      if ($breakOnError) {
+        out += ' else { ';
+      }
+    }
+  }
+  return out;
+}
+
+},{}],31:[function(require,module,exports){
+'use strict';
+module.exports = function generate_required(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  if (!$isData) {
+    if ($schema.length < it.opts.loopRequired && it.schema.properties && Object.keys(it.schema.properties).length) {
+      var $required = [];
+      var arr1 = $schema;
+      if (arr1) {
+        var $property, i1 = -1,
+          l1 = arr1.length - 1;
+        while (i1 < l1) {
+          $property = arr1[i1 += 1];
+          var $propertySch = it.schema.properties[$property];
+          if (!($propertySch && it.util.schemaHasRules($propertySch, it.RULES.all))) {
+            $required[$required.length] = $property;
+          }
+        }
+      }
+    } else {
+      var $required = $schema;
+    }
+  }
+  if ($isData || $required.length) {
+    var $currentErrorPath = it.errorPath,
+      $loopRequired = $isData || $required.length >= it.opts.loopRequired;
+    if ($breakOnError) {
+      out += ' var missing' + ($lvl) + '; ';
+      if ($loopRequired) {
+        if (!$isData) {
+          out += ' var schema' + ($lvl) + ' = validate.schema' + ($schemaPath) + '; ';
+        }
+        var $i = 'i' + $lvl,
+          $propertyPath = 'schema' + $lvl + '[' + $i + ']',
+          $missingProperty = '\' + ' + $propertyPath + ' + \'';
+        if (it.opts._errorDataPathProperty) {
+          it.errorPath = it.util.getPathExpr($currentErrorPath, $propertyPath, it.opts.jsonPointers);
+        }
+        out += ' var ' + ($valid) + ' = true; ';
+        if ($isData) {
+          out += ' if (schema' + ($lvl) + ' === undefined) ' + ($valid) + ' = true; else if (!Array.isArray(schema' + ($lvl) + ')) ' + ($valid) + ' = false; else {';
+        }
+        out += ' for (var ' + ($i) + ' = 0; ' + ($i) + ' < schema' + ($lvl) + '.length; ' + ($i) + '++) { ' + ($valid) + ' = ' + ($data) + '[schema' + ($lvl) + '[' + ($i) + ']] !== undefined; if (!' + ($valid) + ') break; } ';
+        if ($isData) {
+          out += '  }  ';
+        }
+        out += '  if (!' + ($valid) + ') {   ';
+        var $$outStack = $$outStack || [];
+        $$outStack.push(out);
+        out = ''; /* istanbul ignore else */
+        if (it.createErrors !== false) {
+          out += ' { keyword: \'' + ($errorKeyword || 'required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { missingProperty: \'' + ($missingProperty) + '\' } ';
+          if (it.opts.messages !== false) {
+            out += ' , message: \'';
+            if (it.opts._errorDataPathProperty) {
+              out += 'is a required property';
+            } else {
+              out += 'should have required property \\\'' + ($missingProperty) + '\\\'';
+            }
+            out += '\' ';
+          }
+          if (it.opts.verbose) {
+            out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+          }
+          out += ' } ';
+        } else {
+          out += ' {} ';
+        }
+        var __err = out;
+        out = $$outStack.pop();
+        if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+          if (it.async) {
+            out += ' throw new ValidationError([' + (__err) + ']); ';
+          } else {
+            out += ' validate.errors = [' + (__err) + ']; return false; ';
+          }
+        } else {
+          out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+        }
+        out += ' } else { ';
+      } else {
+        out += ' if ( ';
+        var arr2 = $required;
+        if (arr2) {
+          var _$property, $i = -1,
+            l2 = arr2.length - 1;
+          while ($i < l2) {
+            _$property = arr2[$i += 1];
+            if ($i) {
+              out += ' || ';
+            }
+            var $prop = it.util.getProperty(_$property);
+            out += ' ( ' + ($data) + ($prop) + ' === undefined && (missing' + ($lvl) + ' = ' + (it.util.toQuotedString(it.opts.jsonPointers ? _$property : $prop)) + ') ) ';
+          }
+        }
+        out += ') {  ';
+        var $propertyPath = 'missing' + $lvl,
+          $missingProperty = '\' + ' + $propertyPath + ' + \'';
+        if (it.opts._errorDataPathProperty) {
+          it.errorPath = it.opts.jsonPointers ? it.util.getPathExpr($currentErrorPath, $propertyPath, true) : $currentErrorPath + ' + ' + $propertyPath;
+        }
+        var $$outStack = $$outStack || [];
+        $$outStack.push(out);
+        out = ''; /* istanbul ignore else */
+        if (it.createErrors !== false) {
+          out += ' { keyword: \'' + ($errorKeyword || 'required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { missingProperty: \'' + ($missingProperty) + '\' } ';
+          if (it.opts.messages !== false) {
+            out += ' , message: \'';
+            if (it.opts._errorDataPathProperty) {
+              out += 'is a required property';
+            } else {
+              out += 'should have required property \\\'' + ($missingProperty) + '\\\'';
+            }
+            out += '\' ';
+          }
+          if (it.opts.verbose) {
+            out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+          }
+          out += ' } ';
+        } else {
+          out += ' {} ';
+        }
+        var __err = out;
+        out = $$outStack.pop();
+        if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+          if (it.async) {
+            out += ' throw new ValidationError([' + (__err) + ']); ';
+          } else {
+            out += ' validate.errors = [' + (__err) + ']; return false; ';
+          }
+        } else {
+          out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+        }
+        out += ' } else { ';
+      }
+    } else {
+      if ($loopRequired) {
+        if (!$isData) {
+          out += ' var schema' + ($lvl) + ' = validate.schema' + ($schemaPath) + '; ';
+        }
+        var $i = 'i' + $lvl,
+          $propertyPath = 'schema' + $lvl + '[' + $i + ']',
+          $missingProperty = '\' + ' + $propertyPath + ' + \'';
+        if (it.opts._errorDataPathProperty) {
+          it.errorPath = it.util.getPathExpr($currentErrorPath, $propertyPath, it.opts.jsonPointers);
+        }
+        if ($isData) {
+          out += ' if (schema' + ($lvl) + ' && !Array.isArray(schema' + ($lvl) + ')) {  var err =   '; /* istanbul ignore else */
+          if (it.createErrors !== false) {
+            out += ' { keyword: \'' + ($errorKeyword || 'required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { missingProperty: \'' + ($missingProperty) + '\' } ';
+            if (it.opts.messages !== false) {
+              out += ' , message: \'';
+              if (it.opts._errorDataPathProperty) {
+                out += 'is a required property';
+              } else {
+                out += 'should have required property \\\'' + ($missingProperty) + '\\\'';
+              }
+              out += '\' ';
+            }
+            if (it.opts.verbose) {
+              out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+            }
+            out += ' } ';
+          } else {
+            out += ' {} ';
+          }
+          out += ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } else if (schema' + ($lvl) + ' !== undefined) { ';
+        }
+        out += ' for (var ' + ($i) + ' = 0; ' + ($i) + ' < schema' + ($lvl) + '.length; ' + ($i) + '++) { if (' + ($data) + '[schema' + ($lvl) + '[' + ($i) + ']] === undefined) {  var err =   '; /* istanbul ignore else */
+        if (it.createErrors !== false) {
+          out += ' { keyword: \'' + ($errorKeyword || 'required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { missingProperty: \'' + ($missingProperty) + '\' } ';
+          if (it.opts.messages !== false) {
+            out += ' , message: \'';
+            if (it.opts._errorDataPathProperty) {
+              out += 'is a required property';
+            } else {
+              out += 'should have required property \\\'' + ($missingProperty) + '\\\'';
+            }
+            out += '\' ';
+          }
+          if (it.opts.verbose) {
+            out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+          }
+          out += ' } ';
+        } else {
+          out += ' {} ';
+        }
+        out += ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } } ';
+        if ($isData) {
+          out += '  }  ';
+        }
+      } else {
+        var arr3 = $required;
+        if (arr3) {
+          var $reqProperty, i3 = -1,
+            l3 = arr3.length - 1;
+          while (i3 < l3) {
+            $reqProperty = arr3[i3 += 1];
+            var $prop = it.util.getProperty($reqProperty),
+              $missingProperty = it.util.escapeQuotes($reqProperty);
+            if (it.opts._errorDataPathProperty) {
+              it.errorPath = it.util.getPath($currentErrorPath, $reqProperty, it.opts.jsonPointers);
+            }
+            out += ' if (' + ($data) + ($prop) + ' === undefined) {  var err =   '; /* istanbul ignore else */
+            if (it.createErrors !== false) {
+              out += ' { keyword: \'' + ($errorKeyword || 'required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { missingProperty: \'' + ($missingProperty) + '\' } ';
+              if (it.opts.messages !== false) {
+                out += ' , message: \'';
+                if (it.opts._errorDataPathProperty) {
+                  out += 'is a required property';
+                } else {
+                  out += 'should have required property \\\'' + ($missingProperty) + '\\\'';
+                }
+                out += '\' ';
+              }
+              if (it.opts.verbose) {
+                out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+              }
+              out += ' } ';
+            } else {
+              out += ' {} ';
+            }
+            out += ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } ';
+          }
+        }
+      }
+    }
+    it.errorPath = $currentErrorPath;
+  } else if ($breakOnError) {
+    out += ' if (true) {';
+  }
+  return out;
+}
+
+},{}],32:[function(require,module,exports){
+'use strict';
+module.exports = function generate_switch(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  var $errs = 'errs__' + $lvl;
+  var $it = it.util.copy(it);
+  var $closingBraces = '';
+  $it.level++;
+  var $ifPassed = 'ifPassed' + it.level,
+    $currentBaseId = $it.baseId,
+    $shouldContinue;
+  out += 'var ' + ($ifPassed) + ';';
+  var arr1 = $schema;
+  if (arr1) {
+    var $sch, $caseIndex = -1,
+      l1 = arr1.length - 1;
+    while ($caseIndex < l1) {
+      $sch = arr1[$caseIndex += 1];
+      if ($caseIndex && !$shouldContinue) {
+        out += ' if (!' + ($ifPassed) + ') { ';
+        $closingBraces += '}';
+      }
+      if ($sch.if && it.util.schemaHasRules($sch.if, it.RULES.all)) {
+        out += ' var ' + ($errs) + ' = errors;   ';
+        var $wasComposite = it.compositeRule;
+        it.compositeRule = $it.compositeRule = true;
+        $it.createErrors = false;
+        $it.schema = $sch.if;
+        $it.schemaPath = $schemaPath + '[' + $caseIndex + '].if';
+        $it.errSchemaPath = $errSchemaPath + '/' + $caseIndex + '/if';
+        out += '  ' + (it.validate($it)) + ' ';
+        $it.baseId = $currentBaseId;
+        $it.createErrors = true;
+        it.compositeRule = $it.compositeRule = $wasComposite;
+        out += ' ' + ($ifPassed) + ' = valid' + ($it.level) + '; if (' + ($ifPassed) + ') {  ';
+        if (typeof $sch.then == 'boolean') {
+          if ($sch.then === false) {
+            var $$outStack = $$outStack || [];
+            $$outStack.push(out);
+            out = ''; /* istanbul ignore else */
+            if (it.createErrors !== false) {
+              out += ' { keyword: \'' + ($errorKeyword || 'switch') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { caseIndex: ' + ($caseIndex) + ' } ';
+              if (it.opts.messages !== false) {
+                out += ' , message: \'should pass "switch" keyword validation\' ';
+              }
+              if (it.opts.verbose) {
+                out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+              }
+              out += ' } ';
+            } else {
+              out += ' {} ';
+            }
+            var __err = out;
+            out = $$outStack.pop();
+            if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+              if (it.async) {
+                out += ' throw new ValidationError([' + (__err) + ']); ';
+              } else {
+                out += ' validate.errors = [' + (__err) + ']; return false; ';
+              }
+            } else {
+              out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+            }
+          }
+          out += ' var valid' + ($it.level) + ' = ' + ($sch.then) + '; ';
+        } else {
+          $it.schema = $sch.then;
+          $it.schemaPath = $schemaPath + '[' + $caseIndex + '].then';
+          $it.errSchemaPath = $errSchemaPath + '/' + $caseIndex + '/then';
+          out += '  ' + (it.validate($it)) + ' ';
+          $it.baseId = $currentBaseId;
+        }
+        out += '  } else {  errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; } } ';
+      } else {
+        out += ' ' + ($ifPassed) + ' = true;  ';
+        if (typeof $sch.then == 'boolean') {
+          if ($sch.then === false) {
+            var $$outStack = $$outStack || [];
+            $$outStack.push(out);
+            out = ''; /* istanbul ignore else */
+            if (it.createErrors !== false) {
+              out += ' { keyword: \'' + ($errorKeyword || 'switch') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { caseIndex: ' + ($caseIndex) + ' } ';
+              if (it.opts.messages !== false) {
+                out += ' , message: \'should pass "switch" keyword validation\' ';
+              }
+              if (it.opts.verbose) {
+                out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+              }
+              out += ' } ';
+            } else {
+              out += ' {} ';
+            }
+            var __err = out;
+            out = $$outStack.pop();
+            if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+              if (it.async) {
+                out += ' throw new ValidationError([' + (__err) + ']); ';
+              } else {
+                out += ' validate.errors = [' + (__err) + ']; return false; ';
+              }
+            } else {
+              out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+            }
+          }
+          out += ' var valid' + ($it.level) + ' = ' + ($sch.then) + '; ';
+        } else {
+          $it.schema = $sch.then;
+          $it.schemaPath = $schemaPath + '[' + $caseIndex + '].then';
+          $it.errSchemaPath = $errSchemaPath + '/' + $caseIndex + '/then';
+          out += '  ' + (it.validate($it)) + ' ';
+          $it.baseId = $currentBaseId;
+        }
+      }
+      $shouldContinue = $sch.continue
+    }
+  }
+  out += '' + ($closingBraces) + 'var ' + ($valid) + ' = valid' + ($it.level) + '; ';
+  out = it.util.cleanUpCode(out);
+  return out;
+}
+
+},{}],33:[function(require,module,exports){
+'use strict';
+module.exports = function generate_uniqueItems(it, $keyword) {
+  var out = ' ';
+  var $lvl = it.level;
+  var $dataLvl = it.dataLevel;
+  var $schema = it.schema[$keyword];
+  var $schemaPath = it.schemaPath + '.' + $keyword;
+  var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
+  var $breakOnError = !it.opts.allErrors;
+  var $errorKeyword;
+  var $data = 'data' + ($dataLvl || '');
+  var $valid = 'valid' + $lvl;
+  var $isData = it.opts.v5 && $schema.$data;
+  var $schemaValue = $isData ? it.util.getData($schema.$data, $dataLvl, it.dataPathArr) : $schema;
+  if ($isData) {
+    out += ' var schema' + ($lvl) + ' = ' + ($schemaValue) + '; ';
+    $schemaValue = 'schema' + $lvl;
+  }
+  if (($schema || $isData) && it.opts.uniqueItems !== false) {
+    if ($isData) {
+      out += ' var ' + ($valid) + '; if (' + ($schemaValue) + ' === false || ' + ($schemaValue) + ' === undefined) ' + ($valid) + ' = true; else if (typeof ' + ($schemaValue) + ' != \'boolean\') ' + ($valid) + ' = false; else { ';
+    }
+    out += ' var ' + ($valid) + ' = true; if (' + ($data) + '.length > 1) { var i = ' + ($data) + '.length, j; outer: for (;i--;) { for (j = i; j--;) { if (equal(' + ($data) + '[i], ' + ($data) + '[j])) { ' + ($valid) + ' = false; break outer; } } } } ';
+    if ($isData) {
+      out += '  }  ';
+    }
+    out += ' if (!' + ($valid) + ') {   ';
+    var $$outStack = $$outStack || [];
+    $$outStack.push(out);
+    out = ''; /* istanbul ignore else */
+    if (it.createErrors !== false) {
+      out += ' { keyword: \'' + ($errorKeyword || 'uniqueItems') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { i: i, j: j } ';
+      if (it.opts.messages !== false) {
+        out += ' , message: \'should NOT have duplicate items (items ## \' + j + \' and \' + i + \' are identical)\' ';
+      }
+      if (it.opts.verbose) {
+        out += ' , schema:  ';
+        if ($isData) {
+          out += 'validate.schema' + ($schemaPath);
+        } else {
+          out += '' + ($schema);
+        }
+        out += '         , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+      }
+      out += ' } ';
+    } else {
+      out += ' {} ';
+    }
+    var __err = out;
+    out = $$outStack.pop();
+    if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+      if (it.async) {
+        out += ' throw new ValidationError([' + (__err) + ']); ';
+      } else {
+        out += ' validate.errors = [' + (__err) + ']; return false; ';
+      }
+    } else {
+      out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+    }
+    out += ' } ';
+    if ($breakOnError) {
+      out += ' else { ';
+    }
+  } else {
+    if ($breakOnError) {
+      out += ' if (true) { ';
+    }
+  }
+  return out;
+}
+
+},{}],34:[function(require,module,exports){
+'use strict';
+module.exports = function generate_validate(it, $keyword) {
+  var out = '';
+  var $async = it.schema.$async === true;
+  if (it.isTop) {
+    var $top = it.isTop,
+      $lvl = it.level = 0,
+      $dataLvl = it.dataLevel = 0,
+      $data = 'data';
+    it.rootId = it.resolve.fullPath(it.root.schema.id);
+    it.baseId = it.baseId || it.rootId;
+    if ($async) {
+      it.async = true;
+      var $es7 = it.opts.async == 'es7';
+      it.yieldAwait = $es7 ? 'await' : 'yield';
+    }
+    delete it.isTop;
+    it.dataPathArr = [undefined];
+    out += ' validate = ';
+    if ($async) {
+      if ($es7) {
+        out += ' (async function ';
+      } else {
+        if (it.opts.async == 'co*') {
+          out += 'co.wrap';
+        }
+        out += '(function* ';
+      }
+    } else {
+      out += ' (function ';
+    }
+    out += ' (data, dataPath, parentData, parentDataProperty) { \'use strict\'; var vErrors = null; ';
+    out += ' var errors = 0;     ';
+  } else {
+    var $lvl = it.level,
+      $dataLvl = it.dataLevel,
+      $data = 'data' + ($dataLvl || '');
+    if (it.schema.id) it.baseId = it.resolve.url(it.baseId, it.schema.id);
+    if ($async && !it.async) throw new Error('async schema in sync schema');
+    out += ' var errs_' + ($lvl) + ' = errors;';
+  }
+  var $valid = 'valid' + $lvl,
+    $breakOnError = !it.opts.allErrors,
+    $closingBraces1 = '',
+    $closingBraces2 = '',
+    $errorKeyword;
+  var $typeSchema = it.schema.type,
+    $typeIsArray = Array.isArray($typeSchema);
+  if ($typeSchema && it.opts.coerceTypes) {
+    var $coerceToTypes = it.util.coerceToTypes($typeSchema);
+    if ($coerceToTypes) {
+      var $schemaPath = it.schemaPath + '.type',
+        $errSchemaPath = it.errSchemaPath + '/type',
+        $method = $typeIsArray ? 'checkDataTypes' : 'checkDataType';
+      out += ' if (' + (it.util[$method]($typeSchema, $data, true)) + ') {  ';
+      var $dataType = 'dataType' + $lvl,
+        $coerced = 'coerced' + $lvl;
+      out += ' var ' + ($dataType) + ' = typeof ' + ($data) + '; var ' + ($coerced) + ' = undefined; ';
+      var $bracesCoercion = '';
+      var arr1 = $coerceToTypes;
+      if (arr1) {
+        var $type, $i = -1,
+          l1 = arr1.length - 1;
+        while ($i < l1) {
+          $type = arr1[$i += 1];
+          if ($i) {
+            out += ' if (' + ($coerced) + ' === undefined) { ';
+            $bracesCoercion += '}';
+          }
+          if ($type == 'string') {
+            out += ' if (' + ($dataType) + ' == \'number\' || ' + ($dataType) + ' == \'boolean\') ' + ($coerced) + ' = \'\' + ' + ($data) + '; else if (' + ($data) + ' === null) ' + ($coerced) + ' = \'\'; ';
+          } else if ($type == 'number' || $type == 'integer') {
+            out += ' if (' + ($dataType) + ' == \'boolean\' || ' + ($data) + ' === null || (' + ($dataType) + ' == \'string\' && ' + ($data) + ' && ' + ($data) + ' == +' + ($data) + ' ';
+            if ($type == 'integer') {
+              out += ' && !(' + ($data) + ' % 1)';
+            }
+            out += ')) ' + ($coerced) + ' = +' + ($data) + '; ';
+          } else if ($type == 'boolean') {
+            out += ' if (' + ($data) + ' === \'false\' || ' + ($data) + ' === 0 || ' + ($data) + ' === null) ' + ($coerced) + ' = false; else if (' + ($data) + ' === \'true\' || ' + ($data) + ' === 1) ' + ($coerced) + ' = true; ';
+          } else if ($type == 'null') {
+            out += ' if (' + ($data) + ' === \'\' || ' + ($data) + ' === 0 || ' + ($data) + ' === false) ' + ($coerced) + ' = null; ';
+          }
+        }
+      }
+      out += ' ' + ($bracesCoercion) + ' if (' + ($coerced) + ' === undefined) {   ';
+      var $$outStack = $$outStack || [];
+      $$outStack.push(out);
+      out = ''; /* istanbul ignore else */
+      if (it.createErrors !== false) {
+        out += ' { keyword: \'' + ($errorKeyword || 'type') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { type: \'';
+        if ($typeIsArray) {
+          out += '' + ($typeSchema.join(","));
+        } else {
+          out += '' + ($typeSchema);
+        }
+        out += '\' } ';
+        if (it.opts.messages !== false) {
+          out += ' , message: \'should be ';
+          if ($typeIsArray) {
+            out += '' + ($typeSchema.join(","));
+          } else {
+            out += '' + ($typeSchema);
+          }
+          out += '\' ';
+        }
+        if (it.opts.verbose) {
+          out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+        }
+        out += ' } ';
+      } else {
+        out += ' {} ';
+      }
+      var __err = out;
+      out = $$outStack.pop();
+      if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+        if (it.async) {
+          out += ' throw new ValidationError([' + (__err) + ']); ';
+        } else {
+          out += ' validate.errors = [' + (__err) + ']; return false; ';
+        }
+      } else {
+        out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+      }
+      out += ' } else { ';
+      if ($dataLvl) {
+        var $parentData = 'data' + (($dataLvl - 1) || ''),
+          $dataProperty = it.dataPathArr[$dataLvl];
+        out += ' ' + ($data) + ' = ' + ($parentData) + '[' + ($dataProperty) + '] = ' + ($coerced) + '; ';
+      } else {
+        out += ' data = ' + ($coerced) + '; if (parentData !== undefined) parentData[parentDataProperty] = ' + ($coerced) + '; ';
+      }
+      out += ' } } ';
+    }
+  }
+  var arr2 = it.RULES;
+  if (arr2) {
+    var $rulesGroup, i2 = -1,
+      l2 = arr2.length - 1;
+    while (i2 < l2) {
+      $rulesGroup = arr2[i2 += 1];
+      if ($shouldUseGroup($rulesGroup)) {
+        if ($rulesGroup.type) {
+          out += ' if (' + (it.util.checkDataType($rulesGroup.type, $data)) + ') { ';
+        }
+        if (it.opts.useDefaults && !it.compositeRule) {
+          if ($rulesGroup.type == 'object' && it.schema.properties) {
+            var $schema = it.schema.properties,
+              $schemaKeys = Object.keys($schema);
+            var arr3 = $schemaKeys;
+            if (arr3) {
+              var $propertyKey, i3 = -1,
+                l3 = arr3.length - 1;
+              while (i3 < l3) {
+                $propertyKey = arr3[i3 += 1];
+                var $sch = $schema[$propertyKey];
+                if ($sch.default !== undefined) {
+                  var $passData = $data + it.util.getProperty($propertyKey);
+                  out += '  if (' + ($passData) + ' === undefined) ' + ($passData) + ' = ';
+                  if (it.opts.useDefaults == 'shared') {
+                    out += ' ' + (it.useDefault($sch.default)) + ' ';
+                  } else {
+                    out += ' ' + (JSON.stringify($sch.default)) + ' ';
+                  }
+                  out += '; ';
+                }
+              }
+            }
+          } else if ($rulesGroup.type == 'array' && Array.isArray(it.schema.items)) {
+            var arr4 = it.schema.items;
+            if (arr4) {
+              var $sch, $i = -1,
+                l4 = arr4.length - 1;
+              while ($i < l4) {
+                $sch = arr4[$i += 1];
+                if ($sch.default !== undefined) {
+                  var $passData = $data + '[' + $i + ']';
+                  out += '  if (' + ($passData) + ' === undefined) ' + ($passData) + ' = ';
+                  if (it.opts.useDefaults == 'shared') {
+                    out += ' ' + (it.useDefault($sch.default)) + ' ';
+                  } else {
+                    out += ' ' + (JSON.stringify($sch.default)) + ' ';
+                  }
+                  out += '; ';
+                }
+              }
+            }
+          }
+        }
+        var arr5 = $rulesGroup.rules;
+        if (arr5) {
+          var $rule, i5 = -1,
+            l5 = arr5.length - 1;
+          while (i5 < l5) {
+            $rule = arr5[i5 += 1];
+            if ($shouldUseRule($rule)) {
+              if ($rule.custom) {
+                var $schema = it.schema[$rule.keyword],
+                  $ruleValidate = it.useCustomRule($rule, $schema, it.schema, it),
+                  $ruleErrs = $ruleValidate.code + '.errors',
+                  $schemaPath = it.schemaPath + '.' + $rule.keyword,
+                  $errSchemaPath = it.errSchemaPath + '/' + $rule.keyword,
+                  $errs = 'errs' + $lvl,
+                  $i = 'i' + $lvl,
+                  $ruleErr = 'ruleErr' + $lvl,
+                  $rDef = $rule.definition,
+                  $asyncKeyword = $rDef.async,
+                  $inline = $rDef.inline,
+                  $macro = $rDef.macro;
+                if ($asyncKeyword && !it.async) throw new Error('async keyword in sync schema');
+                if (!($inline || $macro)) {
+                  out += '' + ($ruleErrs) + ' = null;';
+                }
+                out += 'var ' + ($errs) + ' = errors;var valid' + ($lvl) + ';';
+                if ($inline && $rDef.statements) {
+                  out += ' ' + ($ruleValidate.validate);
+                } else if ($macro) {
+                  var $it = it.util.copy(it);
+                  $it.level++;
+                  $it.schema = $ruleValidate.validate;
+                  $it.schemaPath = '';
+                  var $wasComposite = it.compositeRule;
+                  it.compositeRule = $it.compositeRule = true;
+                  var $code = it.validate($it).replace(/validate\.schema/g, $ruleValidate.code);
+                  it.compositeRule = $it.compositeRule = $wasComposite;
+                  out += ' ' + ($code);
+                } else if ($rDef.compile || $rDef.validate) {
+                  var $$outStack = $$outStack || [];
+                  $$outStack.push(out);
+                  out = '';
+                  out += '  ' + ($ruleValidate.code) + '.call( ';
+                  if (it.opts.passContext) {
+                    out += 'this';
+                  } else {
+                    out += 'self';
+                  }
+                  if ($rDef.compile || $rDef.schema === false) {
+                    out += ' , ' + ($data) + ' ';
+                  } else {
+                    out += ' , validate.schema' + ($schemaPath) + ' , ' + ($data) + ' , validate.schema' + (it.schemaPath) + ' ';
+                  }
+                  out += ' , (dataPath || \'\')';
+                  if (it.errorPath != '""') {
+                    out += ' + ' + (it.errorPath);
+                  }
+                  if ($dataLvl) {
+                    out += ' , data' + (($dataLvl - 1) || '') + ' , ' + (it.dataPathArr[$dataLvl]) + ' ';
+                  } else {
+                    out += ' , parentData , parentDataProperty ';
+                  }
+                  out += ' )  ';
+                  var def_callRuleValidate = out;
+                  out = $$outStack.pop();
+                  if ($rDef.errors !== false) {
+                    if ($asyncKeyword) {
+                      $ruleErrs = 'customErrors' + $lvl;
+                      out += ' var ' + ($ruleErrs) + ' = null; try { valid' + ($lvl) + ' = ' + (it.yieldAwait) + (def_callRuleValidate) + '; } catch (e) { valid' + ($lvl) + ' = false; if (e instanceof ValidationError) ' + ($ruleErrs) + ' = e.errors; else throw e; } ';
+                    } else {
+                      out += ' ' + ($ruleValidate.code) + '.errors = null; ';
+                    }
+                  }
+                }
+                out += 'if (! ';
+                if ($inline) {
+                  if ($rDef.statements) {
+                    out += ' valid' + ($lvl) + ' ';
+                  } else {
+                    out += ' (' + ($ruleValidate.validate) + ') ';
+                  }
+                } else if ($macro) {
+                  out += ' valid' + ($it.level) + ' ';
+                } else {
+                  if ($asyncKeyword) {
+                    if ($rDef.errors === false) {
+                      out += ' (' + (it.yieldAwait) + (def_callRuleValidate) + ') ';
+                    } else {
+                      out += ' valid' + ($lvl) + ' ';
+                    }
+                  } else {
+                    out += ' ' + (def_callRuleValidate) + ' ';
+                  }
+                }
+                out += ') { ';
+                $errorKeyword = $rule.keyword;
+                var $$outStack = $$outStack || [];
+                $$outStack.push(out);
+                out = '';
+                var $$outStack = $$outStack || [];
+                $$outStack.push(out);
+                out = ''; /* istanbul ignore else */
+                if (it.createErrors !== false) {
+                  out += ' { keyword: \'' + ($errorKeyword || 'custom') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { keyword: \'' + ($rule.keyword) + '\' } ';
+                  if (it.opts.messages !== false) {
+                    out += ' , message: \'should pass "' + ($rule.keyword) + '" keyword validation\' ';
+                  }
+                  if (it.opts.verbose) {
+                    out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+                  }
+                  out += ' } ';
+                } else {
+                  out += ' {} ';
+                }
+                var __err = out;
+                out = $$outStack.pop();
+                if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+                  if (it.async) {
+                    out += ' throw new ValidationError([' + (__err) + ']); ';
+                  } else {
+                    out += ' validate.errors = [' + (__err) + ']; return false; ';
+                  }
+                } else {
+                  out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+                }
+                var def_customError = out;
+                out = $$outStack.pop();
+                if ($inline) {
+                  if ($rDef.errors) {
+                    if ($rDef.errors != 'full') {
+                      out += '  for (var ' + ($i) + '=' + ($errs) + '; ' + ($i) + '<errors; ' + ($i) + '++) { var ' + ($ruleErr) + ' = vErrors[' + ($i) + ']; if (' + ($ruleErr) + '.dataPath === undefined) { ' + ($ruleErr) + '.dataPath = (dataPath || \'\') + ' + (it.errorPath) + '; } if (' + ($ruleErr) + '.schemaPath === undefined) { ' + ($ruleErr) + '.schemaPath = "' + ($errSchemaPath) + '"; } ';
+                      if (it.opts.verbose) {
+                        out += ' ' + ($ruleErr) + '.schema = validate.schema' + ($schemaPath) + '; ' + ($ruleErr) + '.data = ' + ($data) + '; ';
+                      }
+                      out += ' } ';
+                    }
+                  } else {
+                    if ($rDef.errors === false) {
+                      out += ' ' + (def_customError) + ' ';
+                    } else {
+                      out += ' if (' + ($errs) + ' == errors) { ' + (def_customError) + ' } else {  for (var ' + ($i) + '=' + ($errs) + '; ' + ($i) + '<errors; ' + ($i) + '++) { var ' + ($ruleErr) + ' = vErrors[' + ($i) + ']; if (' + ($ruleErr) + '.dataPath === undefined) { ' + ($ruleErr) + '.dataPath = (dataPath || \'\') + ' + (it.errorPath) + '; } if (' + ($ruleErr) + '.schemaPath === undefined) { ' + ($ruleErr) + '.schemaPath = "' + ($errSchemaPath) + '"; } ';
+                      if (it.opts.verbose) {
+                        out += ' ' + ($ruleErr) + '.schema = validate.schema' + ($schemaPath) + '; ' + ($ruleErr) + '.data = ' + ($data) + '; ';
+                      }
+                      out += ' } } ';
+                    }
+                  }
+                } else if ($macro) {
+                  out += '   var err =   '; /* istanbul ignore else */
+                  if (it.createErrors !== false) {
+                    out += ' { keyword: \'' + ($errorKeyword || 'custom') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { keyword: \'' + ($rule.keyword) + '\' } ';
+                    if (it.opts.messages !== false) {
+                      out += ' , message: \'should pass "' + ($rule.keyword) + '" keyword validation\' ';
+                    }
+                    if (it.opts.verbose) {
+                      out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+                    }
+                    out += ' } ';
+                  } else {
+                    out += ' {} ';
+                  }
+                  out += ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+                  if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+                    if (it.async) {
+                      out += ' throw new ValidationError(vErrors); ';
+                    } else {
+                      out += ' validate.errors = vErrors; return false ';
+                    }
+                  }
+                } else {
+                  if ($rDef.errors === false) {
+                    out += ' ' + (def_customError) + ' ';
+                  } else {
+                    out += ' if (Array.isArray(' + ($ruleErrs) + ')) { if (vErrors === null) vErrors = ' + ($ruleErrs) + '; else vErrors.concat(' + ($ruleErrs) + '); errors = vErrors.length;  for (var ' + ($i) + '=' + ($errs) + '; ' + ($i) + '<errors; ' + ($i) + '++) { var ' + ($ruleErr) + ' = vErrors[' + ($i) + '];  ' + ($ruleErr) + '.dataPath = (dataPath || \'\') + ' + (it.errorPath) + ';   ' + ($ruleErr) + '.schemaPath = "' + ($errSchemaPath) + '";  ';
+                    if (it.opts.verbose) {
+                      out += ' ' + ($ruleErr) + '.schema = validate.schema' + ($schemaPath) + '; ' + ($ruleErr) + '.data = ' + ($data) + '; ';
+                    }
+                    out += ' } } else { ' + (def_customError) + ' } ';
+                  }
+                }
+                $errorKeyword = undefined;
+                out += ' } ';
+                if ($breakOnError) {
+                  out += ' else { ';
+                }
+              } else {
+                out += ' ' + ($rule.code(it, $rule.keyword)) + ' ';
+              }
+              if ($breakOnError) {
+                $closingBraces1 += '}';
+              }
+            }
+          }
+        }
+        if ($breakOnError) {
+          out += ' ' + ($closingBraces1) + ' ';
+          $closingBraces1 = '';
+        }
+        if ($rulesGroup.type) {
+          out += ' } ';
+          if ($typeSchema && $typeSchema === $rulesGroup.type) {
+            var $typeChecked = true;
+            out += ' else { ';
+            var $schemaPath = it.schemaPath + '.type',
+              $errSchemaPath = it.errSchemaPath + '/type';
+            var $$outStack = $$outStack || [];
+            $$outStack.push(out);
+            out = ''; /* istanbul ignore else */
+            if (it.createErrors !== false) {
+              out += ' { keyword: \'' + ($errorKeyword || 'type') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { type: \'';
+              if ($typeIsArray) {
+                out += '' + ($typeSchema.join(","));
+              } else {
+                out += '' + ($typeSchema);
+              }
+              out += '\' } ';
+              if (it.opts.messages !== false) {
+                out += ' , message: \'should be ';
+                if ($typeIsArray) {
+                  out += '' + ($typeSchema.join(","));
+                } else {
+                  out += '' + ($typeSchema);
+                }
+                out += '\' ';
+              }
+              if (it.opts.verbose) {
+                out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+              }
+              out += ' } ';
+            } else {
+              out += ' {} ';
+            }
+            var __err = out;
+            out = $$outStack.pop();
+            if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+              if (it.async) {
+                out += ' throw new ValidationError([' + (__err) + ']); ';
+              } else {
+                out += ' validate.errors = [' + (__err) + ']; return false; ';
+              }
+            } else {
+              out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+            }
+            out += ' } ';
+          }
+        }
+        if ($breakOnError) {
+          out += ' if (errors === ';
+          if ($top) {
+            out += '0';
+          } else {
+            out += 'errs_' + ($lvl);
+          }
+          out += ') { ';
+          $closingBraces2 += '}';
+        }
+      }
+    }
+  }
+  if ($typeSchema && !$typeChecked && !(it.opts.coerceTypes && $coerceToTypes)) {
+    var $schemaPath = it.schemaPath + '.type',
+      $errSchemaPath = it.errSchemaPath + '/type',
+      $method = $typeIsArray ? 'checkDataTypes' : 'checkDataType';
+    out += ' if (' + (it.util[$method]($typeSchema, $data, true)) + ') {   ';
+    var $$outStack = $$outStack || [];
+    $$outStack.push(out);
+    out = ''; /* istanbul ignore else */
+    if (it.createErrors !== false) {
+      out += ' { keyword: \'' + ($errorKeyword || 'type') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: "' + ($errSchemaPath) + '" , params: { type: \'';
+      if ($typeIsArray) {
+        out += '' + ($typeSchema.join(","));
+      } else {
+        out += '' + ($typeSchema);
+      }
+      out += '\' } ';
+      if (it.opts.messages !== false) {
+        out += ' , message: \'should be ';
+        if ($typeIsArray) {
+          out += '' + ($typeSchema.join(","));
+        } else {
+          out += '' + ($typeSchema);
+        }
+        out += '\' ';
+      }
+      if (it.opts.verbose) {
+        out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
+      }
+      out += ' } ';
+    } else {
+      out += ' {} ';
+    }
+    var __err = out;
+    out = $$outStack.pop();
+    if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */
+      if (it.async) {
+        out += ' throw new ValidationError([' + (__err) + ']); ';
+      } else {
+        out += ' validate.errors = [' + (__err) + ']; return false; ';
+      }
+    } else {
+      out += ' var err = ' + (__err) + ';  if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
+    }
+    out += ' }';
+  }
+  if ($breakOnError) {
+    out += ' ' + ($closingBraces2) + ' ';
+  }
+  if ($top) {
+    if ($async) {
+      out += ' if (errors === 0) return true;           ';
+      out += ' else throw new ValidationError(vErrors); ';
+    } else {
+      out += ' validate.errors = vErrors; ';
+      out += ' return errors === 0;       ';
+    }
+    out += ' });';
+  } else {
+    out += ' var ' + ($valid) + ' = errors === errs_' + ($lvl) + ';';
+  }
+  out = it.util.cleanUpCode(out);
+  if ($top && $breakOnError) {
+    out = it.util.cleanUpVarErrors(out, $async);
+  }
+
+  function $shouldUseGroup($rulesGroup) {
+    for (var i = 0; i < $rulesGroup.rules.length; i++)
+      if ($shouldUseRule($rulesGroup.rules[i])) return true;
+  }
+
+  function $shouldUseRule($rule) {
+    return it.schema[$rule.keyword] !== undefined || ($rule.keyword == 'properties' && (it.schema.additionalProperties === false || typeof it.schema.additionalProperties == 'object' || (it.schema.patternProperties && Object.keys(it.schema.patternProperties).length) || (it.opts.v5 && it.schema.patternGroups && Object.keys(it.schema.patternGroups).length)));
+  }
+  return out;
+}
+
+},{}],35:[function(require,module,exports){
+'use strict';
+
+var IDENTIFIER = /^[a-z_$][a-z0-9_$]*$/i;
+
+/**
+ * Define custom keyword
+ * @this  Ajv
+ * @param {String} keyword custom keyword, should be a valid identifier, should be different from all standard, custom and macro keywords.
+ * @param {Object} definition keyword definition object with properties `type` (type(s) which the keyword applies to), `validate` or `compile`.
+ */
+module.exports = function addKeyword(keyword, definition) {
+  /* eslint no-shadow: 0 */
+  var self = this;
+  if (this.RULES.keywords[keyword])
+    throw new Error('Keyword ' + keyword + ' is already defined');
+
+  if (!IDENTIFIER.test(keyword))
+    throw new Error('Keyword ' + keyword + ' is not a valid identifier');
+
+  if (definition) {
+    var dataType = definition.type;
+    if (Array.isArray(dataType)) {
+      var i, len = dataType.length;
+      for (i=0; i<len; i++) checkDataType(dataType[i]);
+      for (i=0; i<len; i++) _addRule(keyword, dataType[i], definition);
+    } else {
+      if (dataType) checkDataType(dataType);
+      _addRule(keyword, dataType, definition);
+    }
+  }
+
+  this.RULES.keywords[keyword] = true;
+  this.RULES.all[keyword] = true;
+
+
+  function _addRule(keyword, dataType, definition) {
+    var ruleGroup;
+    for (var i=0; i<self.RULES.length; i++) {
+      var rg = self.RULES[i];
+      if (rg.type == dataType) {
+        ruleGroup = rg;
+        break;
+      }
+    }
+
+    if (!ruleGroup) {
+      ruleGroup = { type: dataType, rules: [] };
+      self.RULES.push(ruleGroup);
+    }
+
+    var rule = { keyword: keyword, definition: definition, custom: true };
+    ruleGroup.rules.push(rule);
+  }
+
+
+  function checkDataType(dataType) {
+    if (!self.RULES.types[dataType]) throw new Error('Unknown type ' + dataType);
+  }
+};
+
+},{}],36:[function(require,module,exports){
+module.exports={
+    "id": "http://json-schema.org/draft-04/schema#",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Core schema meta-schema",
+    "definitions": {
+        "schemaArray": {
+            "type": "array",
+            "minItems": 1,
+            "items": { "$ref": "#" }
+        },
+        "positiveInteger": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "positiveIntegerDefault0": {
+            "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
+        },
+        "simpleTypes": {
+            "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+        },
+        "stringArray": {
+            "type": "array",
+            "items": { "type": "string" },
+            "minItems": 1,
+            "uniqueItems": true
+        }
+    },
+    "type": "object",
+    "properties": {
+        "id": {
+            "type": "string",
+            "format": "uri"
+        },
+        "$schema": {
+            "type": "string",
+            "format": "uri"
+        },
+        "title": {
+            "type": "string"
+        },
+        "description": {
+            "type": "string"
+        },
+        "default": {},
+        "multipleOf": {
+            "type": "number",
+            "minimum": 0,
+            "exclusiveMinimum": true
+        },
+        "maximum": {
+            "type": "number"
+        },
+        "exclusiveMaximum": {
+            "type": "boolean",
+            "default": false
+        },
+        "minimum": {
+            "type": "number"
+        },
+        "exclusiveMinimum": {
+            "type": "boolean",
+            "default": false
+        },
+        "maxLength": { "$ref": "#/definitions/positiveInteger" },
+        "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "pattern": {
+            "type": "string",
+            "format": "regex"
+        },
+        "additionalItems": {
+            "anyOf": [
+                { "type": "boolean" },
+                { "$ref": "#" }
+            ],
+            "default": {}
+        },
+        "items": {
+            "anyOf": [
+                { "$ref": "#" },
+                { "$ref": "#/definitions/schemaArray" }
+            ],
+            "default": {}
+        },
+        "maxItems": { "$ref": "#/definitions/positiveInteger" },
+        "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "uniqueItems": {
+            "type": "boolean",
+            "default": false
+        },
+        "maxProperties": { "$ref": "#/definitions/positiveInteger" },
+        "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "required": { "$ref": "#/definitions/stringArray" },
+        "additionalProperties": {
+            "anyOf": [
+                { "type": "boolean" },
+                { "$ref": "#" }
+            ],
+            "default": {}
+        },
+        "definitions": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "properties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "patternProperties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "dependencies": {
+            "type": "object",
+            "additionalProperties": {
+                "anyOf": [
+                    { "$ref": "#" },
+                    { "$ref": "#/definitions/stringArray" }
+                ]
+            }
+        },
+        "enum": {
+            "type": "array",
+            "minItems": 1,
+            "uniqueItems": true
+        },
+        "type": {
+            "anyOf": [
+                { "$ref": "#/definitions/simpleTypes" },
+                {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/simpleTypes" },
+                    "minItems": 1,
+                    "uniqueItems": true
+                }
+            ]
+        },
+        "allOf": { "$ref": "#/definitions/schemaArray" },
+        "anyOf": { "$ref": "#/definitions/schemaArray" },
+        "oneOf": { "$ref": "#/definitions/schemaArray" },
+        "not": { "$ref": "#" }
+    },
+    "dependencies": {
+        "exclusiveMaximum": [ "maximum" ],
+        "exclusiveMinimum": [ "minimum" ]
+    },
+    "default": {}
+}
+
+},{}],37:[function(require,module,exports){
+module.exports={
+    "id": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Core schema meta-schema (v5 proposals)",
+    "definitions": {
+        "schemaArray": {
+            "type": "array",
+            "minItems": 1,
+            "items": { "$ref": "#" }
+        },
+        "positiveInteger": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "positiveIntegerDefault0": {
+            "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
+        },
+        "simpleTypes": {
+            "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+        },
+        "stringArray": {
+            "type": "array",
+            "items": { "type": "string" },
+            "minItems": 1,
+            "uniqueItems": true
+        },
+        "$data": {
+            "type": "object",
+            "required": [ "$data" ],
+            "properties": {
+                "$data": {
+                    "type": "string",
+                    "format": "relative-json-pointer"
+                }
+            },
+            "additionalProperties": false
+        }
+    },
+    "type": "object",
+    "properties": {
+        "id": {
+            "type": "string",
+            "format": "uri"
+        },
+        "$schema": {
+            "type": "string",
+            "format": "uri"
+        },
+        "title": {
+            "type": "string"
+        },
+        "description": {
+            "type": "string"
+        },
+        "default": {},
+        "multipleOf": {
+            "anyOf": [
+                {
+                    "type": "number",
+                    "minimum": 0,
+                    "exclusiveMinimum": true
+                },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "maximum": {
+            "anyOf": [
+                { "type": "number" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "exclusiveMaximum": {
+            "anyOf": [
+                {
+                    "type": "boolean",
+                    "default": false
+                },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "minimum": {
+            "anyOf": [
+                { "type": "number" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "exclusiveMinimum": {
+            "anyOf": [
+                {
+                    "type": "boolean",
+                    "default": false
+                },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "maxLength": {
+            "anyOf": [
+                { "$ref": "#/definitions/positiveInteger" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "minLength": {
+            "anyOf": [
+                { "$ref": "#/definitions/positiveIntegerDefault0" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "pattern": {
+            "anyOf": [
+                {
+                    "type": "string",
+                    "format": "regex"
+                },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "additionalItems": {
+            "anyOf": [
+                { "type": "boolean" },
+                { "$ref": "#" },
+                { "$ref": "#/definitions/$data" }
+            ],
+            "default": {}
+        },
+        "items": {
+            "anyOf": [
+                { "$ref": "#" },
+                { "$ref": "#/definitions/schemaArray" }
+            ],
+            "default": {}
+        },
+        "maxItems": {
+            "anyOf": [
+                { "$ref": "#/definitions/positiveInteger" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "minItems": {
+            "anyOf": [
+                { "$ref": "#/definitions/positiveIntegerDefault0" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "uniqueItems": {
+            "anyOf": [
+                {
+                    "type": "boolean",
+                    "default": false
+                },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "maxProperties": {
+            "anyOf": [
+                { "$ref": "#/definitions/positiveInteger" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "minProperties": {
+            "anyOf": [
+                { "$ref": "#/definitions/positiveIntegerDefault0" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "required": {
+            "anyOf": [
+                { "$ref": "#/definitions/stringArray" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "additionalProperties": {
+            "anyOf": [
+                { "type": "boolean" },
+                { "$ref": "#" },
+                { "$ref": "#/definitions/$data" }
+            ],
+            "default": {}
+        },
+        "definitions": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "properties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "patternProperties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "dependencies": {
+            "type": "object",
+            "additionalProperties": {
+                "anyOf": [
+                    { "$ref": "#" },
+                    { "$ref": "#/definitions/stringArray" }
+                ]
+            }
+        },
+        "enum": {
+            "anyOf": [
+                {
+                    "type": "array",
+                    "minItems": 1,
+                    "uniqueItems": true
+                },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "type": {
+            "anyOf": [
+                { "$ref": "#/definitions/simpleTypes" },
+                {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/simpleTypes" },
+                    "minItems": 1,
+                    "uniqueItems": true
+                }
+            ]
+        },
+        "allOf": { "$ref": "#/definitions/schemaArray" },
+        "anyOf": { "$ref": "#/definitions/schemaArray" },
+        "oneOf": { "$ref": "#/definitions/schemaArray" },
+        "not": { "$ref": "#" },
+        "format": {
+            "anyOf": [
+                { "type": "string" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "formatMaximum": {
+            "anyOf": [
+                { "type": "string" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "formatMinimum": {
+            "anyOf": [
+                { "type": "string" },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "formatExclusiveMaximum": {
+            "anyOf": [
+                {
+                    "type": "boolean",
+                    "default": false
+                },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "formatExclusiveMinimum": {
+            "anyOf": [
+                {
+                    "type": "boolean",
+                    "default": false
+                },
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "constant": {
+            "anyOf": [
+                {},
+                { "$ref": "#/definitions/$data" }
+            ]
+        },
+        "contains": { "$ref": "#" },
+        "patternGroups": {
+            "type": "object",
+            "additionalProperties": {
+                "type": "object",
+                "required": [ "schema" ],
+                "properties": {
+                    "maximum": {
+                        "anyOf": [
+                            { "$ref": "#/definitions/positiveInteger" },
+                            { "$ref": "#/definitions/$data" }
+                        ]
+                    },
+                    "minimum": {
+                        "anyOf": [
+                            { "$ref": "#/definitions/positiveIntegerDefault0" },
+                            { "$ref": "#/definitions/$data" }
+                        ]
+                    },
+                    "schema": { "$ref": "#" }
+                },
+                "additionalProperties": false
+            },
+            "default": {}
+        },
+        "switch": {
+            "type": "array",
+            "items": {
+                "required": [ "then" ],
+                "properties": {
+                    "if": { "$ref": "#" },
+                    "then": {
+                        "anyOf": [
+                            { "type": "boolean" },
+                            { "$ref": "#" }
+                        ]
+                    },
+                    "continue": { "type": "boolean" }
+                },
+                "additionalProperties": false,
+                "dependencies": {
+                    "continue": [ "if" ]
+                }
+            }
+        }
+    },
+    "dependencies": {
+        "exclusiveMaximum": [ "maximum" ],
+        "exclusiveMinimum": [ "minimum" ],
+        "formatMaximum": [ "format" ],
+        "formatMinimum": [ "format" ],
+        "formatExclusiveMaximum": [ "formatMaximum" ],
+        "formatExclusiveMinimum": [ "formatMinimum" ]
+    },
+    "default": {}
+}
+
+},{}],38:[function(require,module,exports){
+'use strict';
+
+var META_SCHEMA_ID = 'https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json';
+
+module.exports = {
+  enable: enableV5,
+  META_SCHEMA_ID: META_SCHEMA_ID
+};
+
+
+function enableV5(ajv) {
+  var inlineFunctions = {
+    'switch': require('./dotjs/switch'),
+    'constant': require('./dotjs/constant'),
+    '_formatLimit': require('./dotjs/_formatLimit'),
+    'patternRequired': require('./dotjs/patternRequired')
+  };
+
+  if (ajv._opts.meta !== false) {
+    var metaSchema = require('./refs/json-schema-v5.json');
+    ajv.addMetaSchema(metaSchema, META_SCHEMA_ID);
+  }
+  _addKeyword('constant');
+  ajv.addKeyword('contains', { type: 'array', macro: containsMacro });
+
+  _addKeyword('formatMaximum', 'string', inlineFunctions._formatLimit);
+  _addKeyword('formatMinimum', 'string', inlineFunctions._formatLimit);
+  ajv.addKeyword('formatExclusiveMaximum');
+  ajv.addKeyword('formatExclusiveMinimum');
+
+  ajv.addKeyword('patternGroups'); // implemented in properties.jst
+  _addKeyword('patternRequired', 'object');
+  _addKeyword('switch');
+
+
+  function _addKeyword(keyword, types, inlineFunc) {
+    var definition = {
+      inline: inlineFunc || inlineFunctions[keyword],
+      statements: true,
+      errors: 'full'
+    };
+    if (types) definition.type = types;
+    ajv.addKeyword(keyword, definition);
+  }
+}
+
+
+function containsMacro(schema) {
+  return {
+    not: { items: { not: schema } }
+  };
+}
+
+},{"./dotjs/_formatLimit":12,"./dotjs/constant":19,"./dotjs/patternRequired":28,"./dotjs/switch":32,"./refs/json-schema-v5.json":37}],39:[function(require,module,exports){
+(function (global){
+/*! https://mths.be/punycode v1.4.1 by @mathias */
+;(function(root) {
+
+  /** Detect free variables */
+  var freeExports = typeof exports == 'object' && exports &&
+    !exports.nodeType && exports;
+  var freeModule = typeof module == 'object' && module &&
+    !module.nodeType && module;
+  var freeGlobal = typeof global == 'object' && global;
+  if (
+    freeGlobal.global === freeGlobal ||
+    freeGlobal.window === freeGlobal ||
+    freeGlobal.self === freeGlobal
+  ) {
+    root = freeGlobal;
+  }
+
+  /**
+   * The `punycode` object.
+   * @name punycode
+   * @type Object
+   */
+  var punycode,
+
+  /** Highest positive signed 32-bit float value */
+  maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
+
+  /** Bootstring parameters */
+  base = 36,
+  tMin = 1,
+  tMax = 26,
+  skew = 38,
+  damp = 700,
+  initialBias = 72,
+  initialN = 128, // 0x80
+  delimiter = '-', // '\x2D'
+
+  /** Regular expressions */
+  regexPunycode = /^xn--/,
+  regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars
+  regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators
+
+  /** Error messages */
+  errors = {
+    'overflow': 'Overflow: input needs wider integers to process',
+    'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
+    'invalid-input': 'Invalid input'
+  },
+
+  /** Convenience shortcuts */
+  baseMinusTMin = base - tMin,
+  floor = Math.floor,
+  stringFromCharCode = String.fromCharCode,
+
+  /** Temporary variable */
+  key;
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * A generic error utility function.
+   * @private
+   * @param {String} type The error type.
+   * @returns {Error} Throws a `RangeError` with the applicable error message.
+   */
+  function error(type) {
+    throw new RangeError(errors[type]);
+  }
+
+  /**
+   * A generic `Array#map` utility function.
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} callback The function that gets called for every array
+   * item.
+   * @returns {Array} A new array of values returned by the callback function.
+   */
+  function map(array, fn) {
+    var length = array.length;
+    var result = [];
+    while (length--) {
+      result[length] = fn(array[length]);
+    }
+    return result;
+  }
+
+  /**
+   * A simple `Array#map`-like wrapper to work with domain name strings or email
+   * addresses.
+   * @private
+   * @param {String} domain The domain name or email address.
+   * @param {Function} callback The function that gets called for every
+   * character.
+   * @returns {Array} A new string of characters returned by the callback
+   * function.
+   */
+  function mapDomain(string, fn) {
+    var parts = string.split('@');
+    var result = '';
+    if (parts.length > 1) {
+      // In email addresses, only the domain name should be punycoded. Leave
+      // the local part (i.e. everything up to `@`) intact.
+      result = parts[0] + '@';
+      string = parts[1];
+    }
+    // Avoid `split(regex)` for IE8 compatibility. See #17.
+    string = string.replace(regexSeparators, '\x2E');
+    var labels = string.split('.');
+    var encoded = map(labels, fn).join('.');
+    return result + encoded;
+  }
+
+  /**
+   * Creates an array containing the numeric code points of each Unicode
+   * character in the string. While JavaScript uses UCS-2 internally,
+   * this function will convert a pair of surrogate halves (each of which
+   * UCS-2 exposes as separate characters) into a single code point,
+   * matching UTF-16.
+   * @see `punycode.ucs2.encode`
+   * @see <https://mathiasbynens.be/notes/javascript-encoding>
+   * @memberOf punycode.ucs2
+   * @name decode
+   * @param {String} string The Unicode input string (UCS-2).
+   * @returns {Array} The new array of code points.
+   */
+  function ucs2decode(string) {
+    var output = [],
+        counter = 0,
+        length = string.length,
+        value,
+        extra;