Repository: mozilla-services/react-jsonschema-form (js)

Issue:
Omitting `items` from arrays causes it to throw.
### Prerequisites

- [x] I have read the [documentation](https://github.com/mozilla-services/react-jsonschema-form/blob/master/README.md#readme);
- [x] In the case of a bug report, I understand that providing a [SSCCE](http://sscce.org/) example is tremendously useful to the maintainers.

### Description

An array without the `items` field, breaks the rendered form.

I noticed some schemas I have were breaking stuff heavily in the app I'm working on. And I noticed sending invalid schemas usually results in a proper message like "the schema is invalid" or something, instead of rendering the form, but for some schemas it would just break catastrophically impacting in my whole page some times.

Upon further investigation I noticed some of my schemas had properties of type "array" with no "items" field. And I was surprised to see that in my database because I validate everything before saving. Turns out these are actually valid schemas:

>"Omitting this keyword has the same behavior as an empty schema."
http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.9

I debugged react-jsonschema-form's source code a little bit and saw that it was really throwing here:
https://github.com/mozilla-services/react-jsonschema-form/blob/9d79b71f13d87831e5c9e2d8fc7ae5e5ca7865c4/src/utils.js#L398
that is a call from here:
https://github.com/mozilla-services/react-jsonschema-form/blob/d403edc20a1d839326b8f6d0a380f84697efba2b/src/components/fields/ArrayField.js#L367
And this code tells me you guys are assuming all arrays should have the `items` property and not really checking if `schema.items` is defined, causing it to throw an uncatched exception.

### Steps to Reproduce

1. Just run this fiddle https://jsfiddle.net/h6fsjLhj/

#### Expected behavior

I know it's probably not possible to render a form for this scenario, but the tool should ignore fields like this or probably provide feedback that this type of schema is not supported.

#### Actual behavior

The tool just throws, and since catching rendering exceptions in React is not always simple, it can cause unexpected behavior in the whole app.

### Version

0.49.0

Thanks! And let me know if I can provide more info, or if there is a workaround I can use.

Pull request:
Fix #681: Warn on invalid array schema provided.
Fix #681.

I've also updated the `UnsupportedField` component to provide more details:

Invalid array schema:

![](http://i.imgur.com/gOavzBp.png)

Invalid schema type:

![](http://i.imgur.com/eKR5DWU.png)

Patch:
diff --git a/package.json b/package.json
index 65972bed7..b24a05856 100644
--- a/package.json
+++ b/package.json
@@ -73,7 +73,7 @@
     "jsdom": "^8.3.0",
     "lint-staged": "^3.3.1",
     "mocha": "^2.5.3",
-    "prettier": "^1.5.2",
+    "prettier": "^1.5.3",
     "react": "^15.5.0",
     "react-addons-test-utils": "^15.3.2",
     "react-codemirror": "^0.2.3",
diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js
index a0ba52621..c64c00753 100644
--- a/src/components/fields/ArrayField.js
+++ b/src/components/fields/ArrayField.js
@@ -1,6 +1,7 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 
+import UnsupportedField from "./UnsupportedField";
 import {
   getWidget,
   getDefaultFormState,
@@ -275,8 +276,22 @@ class ArrayField extends Component {
   };
 
   render() {
-    const { schema, uiSchema, registry = getDefaultRegistry() } = this.props;
+    const {
+      schema,
+      uiSchema,
+      idSchema,
+      registry = getDefaultRegistry(),
+    } = this.props;
     const { definitions } = registry;
+    if (!schema.hasOwnProperty("items")) {
+      return (
+        <UnsupportedField
+          schema={schema}
+          idSchema={idSchema}
+          reason="Missing items definition"
+        />
+      );
+    }
     if (isFixedItems(schema)) {
       return this.renderFixedArray();
     }
diff --git a/src/components/fields/SchemaField.js b/src/components/fields/SchemaField.js
index 1b20d54ff..b86cc1ff9 100644
--- a/src/components/fields/SchemaField.js
+++ b/src/components/fields/SchemaField.js
@@ -21,7 +21,7 @@ const COMPONENT_TYPES = {
   string: "StringField",
 };
 
-function getFieldComponent(schema, uiSchema, fields) {
+function getFieldComponent(schema, uiSchema, idSchema, fields) {
   const field = uiSchema["ui:field"];
   if (typeof field === "function") {
     return field;
@@ -30,7 +30,17 @@ function getFieldComponent(schema, uiSchema, fields) {
     return fields[field];
   }
   const componentName = COMPONENT_TYPES[schema.type];
-  return componentName in fields ? fields[componentName] : UnsupportedField;
+  return componentName in fields
+    ? fields[componentName]
+    : () => {
+        return (
+          <UnsupportedField
+            schema={schema}
+            idSchema={idSchema}
+            reason={`Unknown field type ${schema.type}`}
+          />
+        );
+      };
 }
 
 function Label(props) {
@@ -159,7 +169,7 @@ function SchemaFieldRender(props) {
     FieldTemplate = DefaultTemplate,
   } = registry;
   const schema = retrieveSchema(props.schema, definitions);
-  const FieldComponent = getFieldComponent(schema, uiSchema, fields);
+  const FieldComponent = getFieldComponent(schema, uiSchema, idSchema, fields);
   const { DescriptionField } = fields;
   const disabled = Boolean(props.disabled || uiSchema["ui:disabled"]);
   const readonly = Boolean(props.readonly || uiSchema["ui:readonly"]);
diff --git a/src/components/fields/UnsupportedField.js b/src/components/fields/UnsupportedField.js
index 6dd97e59b..1fb76cd66 100644
--- a/src/components/fields/UnsupportedField.js
+++ b/src/components/fields/UnsupportedField.js
@@ -1,10 +1,34 @@
 import React from "react";
+import PropTypes from "prop-types";
 
-export default function UnsupportedField({ schema }) {
-  // XXX render json as string so dev can inspect faulty subschema
+function UnsupportedField({ schema, idSchema, reason }) {
   return (
     <div className="unsupported-field">
-      Unsupported field schema {JSON.stringify(schema, null, 2)}.
+      <p>
+        Unsupported field schema{idSchema &&
+          idSchema.$id &&
+          <span>
+            {" for"} field <code>{idSchema.$id}</code>
+          </span>}
+        {reason &&
+          <em>
+            : {reason}
+          </em>}.
+      </p>
+      {schema &&
+        <pre>
+          {JSON.stringify(schema, null, 2)}
+        </pre>}
     </div>
   );
 }
+
+if (process.env.NODE_ENV !== "production") {
+  UnsupportedField.propTypes = {
+    schema: PropTypes.object.isRequired,
+    idSchema: PropTypes.object,
+    reason: PropTypes.string,
+  };
+}
+
+export default UnsupportedField;


Test patch:
diff --git a/test/ArrayField_test.js b/test/ArrayField_test.js
index 73166cd14..376817b11 100644
--- a/test/ArrayField_test.js
+++ b/test/ArrayField_test.js
@@ -17,6 +17,16 @@ describe("ArrayField", () => {
     sandbox.restore();
   });
 
+  describe("Unsupported array schema", () => {
+    it("should warn on missing items descriptor", () => {
+      const { node } = createFormComponent({ schema: { type: "array" } });
+
+      expect(
+        node.querySelector(".field-array > .unsupported-field").textContent
+      ).to.contain("Missing items definition");
+    });
+  });
+
   describe("List of inputs", () => {
     const schema = {
       type: "array",
diff --git a/test/SchemaField_test.js b/test/SchemaField_test.js
index 81e0d360b..8238cdbf5 100644
--- a/test/SchemaField_test.js
+++ b/test/SchemaField_test.js
@@ -20,6 +20,16 @@ describe("SchemaField", () => {
     sandbox.restore();
   });
 
+  describe("Unsupported field", () => {
+    it("should warn on invalid field type", () => {
+      const { node } = createFormComponent({ schema: { type: "invalid" } });
+
+      expect(node.querySelector(".unsupported-field").textContent).to.contain(
+        "Unknown field type invalid"
+      );
+    });
+  });
+
   describe("Custom SchemaField component", () => {
     const CustomSchemaField = function(props) {
       return (