commit 26d3ab592d2a9cb20ec9aa560423e2db1998b269
Author: Pavel Fidranský <fidransky@outlook.com>
Date:   Sun Oct 28 21:09:04 2018 +0100

    input data are validated against JSON Schema with AJV

diff --git a/sources/src/main/webapp/imiger-raw-input-schema.json b/sources/src/main/webapp/imiger-raw-input-schema.json
new file mode 100644
index 0000000..3c43e0a
--- /dev/null
+++ b/sources/src/main/webapp/imiger-raw-input-schema.json
@@ -0,0 +1,231 @@
+{
+	"$schema": "http://json-schema.org/draft-07/schema#",
+	"$id": "http://example.com/product.schema.json",
+	"title": "IMiGEr - raw input schema",
+	"description": "Diagram displayable by IMiGEr tool using raw input",
+	"type": "object",
+	"properties": {
+		"attributeTypes": {
+			"type": "array",
+			"items": {
+				"type": "object",
+				"properties": {
+					"dataType": {
+						"type": "string"
+					},
+					"name": {
+						"type": "string"
+					},
+					"text": {
+						"type": "string"
+					}
+				},
+				"required": [ "dataType", "name" ]
+			},
+			"minItems": 1,
+			"uniqueItems": true
+		},
+		"edgeArchetypes": {
+			"type": "array",
+			"items": {
+				"type": "object",
+				"properties": {
+					"name": {
+						"type": "string"
+					},
+					"text": {
+						"type": "string"
+					}
+				},
+				"required": [ "name" ]
+			},
+			"minItems": 1,
+			"uniqueItems": true
+		},
+		"vertexArchetypes": {
+			"type": "array",
+			"items": {
+				"type": "object",
+				"properties": {
+					"icon": {
+						"type": "string"
+					},
+					"name": {
+						"type": "string"
+					},
+					"text": {
+						"type": "string"
+					}
+				},
+				"required": [ "name" ]
+			},
+			"minItems": 1,
+			"uniqueItems": true
+		},
+		"vertices": {
+			"type": "array",
+			"items": {
+				"type": "object",
+				"properties": {
+					"archetype": {
+						"type": "integer",
+						"minimum": 0
+					},
+					"attributes": {
+						"type": "array",
+						"items": {
+							"type": "array",
+							"minItems": 2,
+							"maxItems": 2
+						}
+					},
+					"id": {
+						"type": "integer"
+					},
+					"name": {
+						"type": "string"
+					},
+					"text": {
+						"type": "string"
+					},
+					"position": {
+						"type": "object",
+						"properties": {
+							"x": {
+								"type": "number"
+							},
+							"y": {
+								"type": "number"
+							}
+						},
+						"required": [ "x", "y" ]
+					}
+				},
+				"required": [ "id", "name" ]
+			},
+			"minItems": 1,
+			"uniqueItems": true
+		},
+		"edges": {
+			"type": "array",
+			"items": {
+				"type": "object",
+				"properties": {
+					"subedgeInfo": {
+						"type": "array",
+						"items": {
+							"type": "object",
+							"properties": {
+								"id": {
+									"type": "integer"
+								},
+								"archetype": {
+									"type": "integer",
+									"minimum": 0
+								},
+								"attributes": {
+									"type": "array",
+									"items": {
+										"type": "array",
+										"minItems": 2,
+										"maxItems": 2
+									}
+								}
+							},
+							"required": [ "id", "archetype", "attributes" ]
+						}
+					},
+					"id": {
+						"type": "integer"
+					},
+					"text": {
+						"type": "string"
+					},
+					"from": {
+						"type": "integer"
+					},
+					"to": {
+						"type": "integer"
+					}
+				},
+				"required": [ "subedgeInfo", "id", "from", "to" ]
+			},
+			"minItems": 1,
+			"uniqueItems": true
+		},
+		"possibleEnumValues": {
+			"type": "object"
+		},
+		"groups": {
+			"type": "array",
+			"items": {
+				"type": "object",
+				"properties": {
+					"id": {
+						"type": "integer"
+					},
+					"name": {
+						"type": "string"
+					},
+					"verticesId": {
+						"type": "array",
+						"items": {
+							"type": "integer"
+						},
+						"minItems": 1
+					},
+					"verticesEdgeFromId": {
+						"type": "array",
+						"items": {
+							"type": "integer"
+						}
+					},
+					"verticesEdgeToId": {
+						"type": "array",
+						"items": {
+							"type": "integer"
+						}
+					},
+					"position": {
+						"type": "object",
+						"properties": {
+							"x": {
+								"type": "number"
+							},
+							"y": {
+								"type": "number"
+							}
+						},
+						"required": [ "x", "y" ]
+					}
+				},
+				"required": [ "id", "name", "verticesId" ]
+			},
+			"uniqueItems": true
+		},
+		"sideBar": {
+			"type": "array",
+			"items": {
+				"type": "object",
+				"properties": {
+					"id": {
+						"type": "string",
+						"pattern": "^(group|vertex)-[0-9]+$"
+					},
+					"isIconsDisplayed": {
+						"type": "boolean"
+					}
+				},
+				"required": [ "id", "isIconsDisplayed" ]
+			},
+			"uniqueItems": true
+		},
+		"highlightedVertex": {
+			"type": "string"
+		},
+		"highlightedEdge": {
+			"type": "string"
+		}
+	},
+	"required": [ "attributeTypes", "edgeArchetypes", "vertexArchetypes", "vertices", "edges", "possibleEnumValues", "groups", "sideBar", "highlightedVertex", "highlightedEdge" ]
+}
diff --git a/sources/src/main/webapp/js/errors/ajvValidationError.js b/sources/src/main/webapp/js/errors/ajvValidationError.js
new file mode 100644
index 0000000..2a33275
--- /dev/null
+++ b/sources/src/main/webapp/js/errors/ajvValidationError.js
@@ -0,0 +1,10 @@
+/**
+ * Error thrown when AJV JSON validation fails.
+ */
+class AJVValidationError extends Error {
+	constructor(message, errors) {
+		super(message);
+		this.name = new.target.name;
+		this.errors = errors;
+	}
+}
diff --git a/sources/src/main/webapp/js/services/graphLoader.js b/sources/src/main/webapp/js/services/graphLoader.js
index a8a240d..7c9a995 100644
--- a/sources/src/main/webapp/js/services/graphLoader.js
+++ b/sources/src/main/webapp/js/services/graphLoader.js
@@ -1,13 +1,20 @@
 class GraphLoader {
+	/**
+	 * @constructor
+	 */
+	constructor() {
+		this._ajv = new Ajv();
+	}
 
 	/**
 	 * Loads a new graph using graph data passed as parameters.
 	 * @param {object} data Data of the graph.
-	 * @throws {InvalidArgumentError} Thrown when either graph data are incomplete.
+	 * @throws {AJVValidationError} Thrown when graph data are incomplete.
 	 */
 	run(data) {
-		if (Utils.isUndefined(data.vertices) || Utils.isUndefined(data.edges)) {
-			throw new InvalidArgumentError('Invalid data.');
+		let isValid = this._ajv.validate(GraphLoader.rawInputSchema, data);
+		if (isValid === false) {
+			throw new AJVValidationError(this._ajv.errorsText(), this._ajv.errors);
 		}
 
 		const canvasSize = ((data.vertices.length * 75) / Math.round(Math.sqrt(data.vertices.length))) + 1000;
@@ -190,3 +197,4 @@ class GraphLoader {
 	}
 }
 
+GraphLoader.rawInputSchema = JSON.parse(document.getElementById('imigerRawInputSchema').textContent);
diff --git a/sources/src/main/webapp/js/showGraphApp.js b/sources/src/main/webapp/js/showGraphApp.js
index 4882758..9f61980 100644
--- a/sources/src/main/webapp/js/showGraphApp.js
+++ b/sources/src/main/webapp/js/showGraphApp.js
@@ -172,6 +172,11 @@ class ShowGraphApp extends App {
 						console.error(error);
 						alert('Something went wrong. Check console for more details.');
 				}
+
+			} else if (error instanceof AJVValidationError) {
+				console.error(error);
+				alert('Invalid input data: ' + error.message + '. Check console for more details.');
+
 			} else {
 				console.error(error);
 				alert('Something went wrong. Check console for more details.');
diff --git a/sources/src/main/webapp/package-lock.json b/sources/src/main/webapp/package-lock.json
index ba947f8..46a1a7b 100644
--- a/sources/src/main/webapp/package-lock.json
+++ b/sources/src/main/webapp/package-lock.json
@@ -4,6 +4,17 @@
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
+    "ajv": {
+      "version": "6.5.4",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz",
+      "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==",
+      "requires": {
+        "fast-deep-equal": "^2.0.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
     "babylon": {
       "version": "7.0.0-beta.19",
       "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz",
@@ -31,6 +42,16 @@
       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
       "dev": true
     },
+    "fast-deep-equal": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+      "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
+    },
     "graceful-fs": {
       "version": "4.1.11",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
@@ -71,6 +92,11 @@
         "underscore": "~1.8.3"
       }
     },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+    },
     "klaw": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz",
@@ -101,6 +127,11 @@
         "minimist": "0.0.8"
       }
     },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+    },
     "requizzle": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz",
@@ -168,6 +199,14 @@
         }
       }
     },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
     "xmlcreate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz",
diff --git a/sources/src/main/webapp/package.json b/sources/src/main/webapp/package.json
index caa55d6..bd37111 100644
--- a/sources/src/main/webapp/package.json
+++ b/sources/src/main/webapp/package.json
@@ -17,6 +17,7 @@
     "jsdoc": "^3.5.5"
   },
   "dependencies": {
+    "ajv": "^6.5.4",
     "html-tags": "^2.0.0",
     "save-svg-as-png": "^1.4.6",
     "spin.js": "^4.0.0",
diff --git a/sources/src/main/webapp/showGraph.jsp b/sources/src/main/webapp/showGraph.jsp
index 04cdcdc..eea00d2 100644
--- a/sources/src/main/webapp/showGraph.jsp
+++ b/sources/src/main/webapp/showGraph.jsp
@@ -31,7 +31,9 @@
 
 		<script id="htmlTags" type="application/json"><%@ include file="node_modules/html-tags/html-tags.json" %></script>
 		<script id="svgTags" type="application/json"><%@ include file="node_modules/svg-tags/lib/svg-tags.json" %></script>
+		<script id="imigerRawInputSchema" type="application/json"><%@ include file="imiger-raw-input-schema.json" %></script>
 
+		<script src="node_modules/ajv/dist/ajv.min.js"></script>
 		<script src="node_modules/save-svg-as-png/lib/saveSvgAsPng.js"></script>
 
 		<script src="js/components/generic/modalWindow.js"></script>
@@ -61,6 +63,7 @@
 		<script src="js/components/vertexPopover.js"></script>
 		<script src="js/components/viewport.js"></script>
 
+		<script src="js/errors/ajvValidationError.js"></script>
 		<script src="js/errors/httpError.js"></script>
 		<script src="js/errors/invalidArgumentError.js"></script>
 
