Commit 0ff97d42 authored by Boris Kocherov's avatar Boris Kocherov

erp5_json_form: update version from https://lab.nexedi.com/bk/rjs_json_form

parent c67dad31
......@@ -5,6 +5,12 @@
"use strict";
var expandSchema;
function arrayIntersect(x, y) {
return x.filter(function (value) {
return y.indexOf(value) >= 0;
});
}
function URLwithJio(url, base_url) {
var urn_prefix,
pathname,
......@@ -65,19 +71,40 @@
return target;
}
function checkCircular(g, path, url) {
var required_stack,
function checkCircular(urls, path, url) {
var stack,
idx,
prev_field_path = getMaxPathInDict(g.props.schema_required_urls, path);
required_stack = g.props.schema_required_urls[prev_field_path] || [];
idx = required_stack.indexOf(url);
prev_field_path = getMaxPathInDict(urls, path);
stack = urls[prev_field_path] || [];
idx = stack.indexOf(url);
if (idx >= 0) {
if (path === prev_field_path && idx === 0) {
return;
}
throw new Error("Circular reference detected");
return true;
}
// copy and add url as first element
urls[path] = [url].concat(stack);
}
function checkHardCircular(g, path, url) {
return checkCircular(g.props.schema_required_urls, path, url);
}
function checkAndMarkSoftCircular(g, schema_arr, path, url) {
var ret = true;
// if schema_arr.length > 1 selection rendered in any case
// so we not need checkCircular and have small optimisation
if (schema_arr.length === 1) {
ret = checkCircular(g.props.schema_urls, path, url);
schema_arr[0].circular = ret;
}
if (ret) {
// if schema_arr.length > 1 selection rendered and loop break
// if circular found selection rendered and loop break
// so we can begin from start
g.props.schema_urls[path] = [];
}
g.props.schema_required_urls[path] = [url].concat(required_stack);
}
function convertToRealWorldSchemaPath(g, path) {
......@@ -255,7 +282,9 @@
}
queue
.push(function (json) {
checkCircular(g, path, url);
if (checkHardCircular(g, path, url)) {
throw new Error("Circular reference detected");
}
return resolveLocalReference(json, hash);
});
}
......@@ -299,17 +328,146 @@
return expandSchema(g, schema_part, path, $ref);
})
.push(function (schema_arr) {
// if length array > 1 form rendered on demand already
// so not needed circular detection
if (schema_arr.length === 1) {
// XXX need smart circular detection in this place
schema_arr[0].circular = true;
}
checkAndMarkSoftCircular(g, schema_arr, path, url);
return schema_arr;
});
}
function allOf(g, schema_array, schema_path) {
function mergeSchemas(x, y, doesntcopy) {
if (x === true && y === true) {
return true;
}
if (x === false || y === false) {
return false;
}
var key,
p;
if (x === true) {
x = {};
} else if (!doesntcopy) {
x = JSON.parse(JSON.stringify(x));
}
if (y === true) {
y = {};
}
for (key in y) {
if (y.hasOwnProperty(key)) {
if (x.hasOwnProperty(key)) {
switch (key) {
case "maxProperties":
case "maxLength":
case "maxItems":
case "maximum":
case "exclusiveMaximum":
if (y[key] < x[key]) {
x[key] = y[key];
}
break;
case "minProperties":
case "minItems":
case "minLength":
case "minimum":
case "exclusiveMinimum":
if (x[key] < y[key]) {
x[key] = y[key];
}
break;
case "additionalProperties":
case "additionalItems":
case "contains":
case "propertyNames":
mergeSchemas(x[key], y[key], true);
break;
case "items":
// XXX items can be array
mergeSchemas(x[key], y[key], true);
break;
case "contentEncoding":
case "contentMediaType":
if (x[key] !== y[key]) {
return false;
}
break;
case "multipleOf":
x[key] = x[key] * y[key];
break;
case "type":
if (typeof x.type === "string") {
if (typeof y.type === "string") {
if (x.type !== y.type) {
return false;
}
} else if (y.type.indexOf(x.type) === -1) {
return false;
}
} else {
if (typeof y.type === "string") {
if (x.type.indexOf(y.type) === -1) {
return false;
}
} else {
x.type = arrayIntersect(x.type, y.type);
if (x.type.length === 0) {
return false;
}
}
}
break;
case "properties":
case "patternProperties":
for (p in y[key]) {
if (y[key].hasOwnProperty(p)) {
if (x[key].hasOwnProperty(p)) {
mergeSchemas(x[key][p], y[key][p], true);
} else {
x[key][p] = y[key][p];
}
}
}
break;
case "pattern":
// XXX regex string merge
case "dependencies":
// XXX find solution how merge
x[key] = y[key];
break;
case "required":
for (p = 0; p < y.required.length; p += 1) {
if (x.required.indexOf(y.required[p]) < 0) {
x.required.push(y.required[p]);
}
}
break;
case "uniqueItems":
x[key] = y[key];
break;
case "allOf":
case "anyOf":
case "$ref":
case "id":
case "$id":
// XXX
break;
default:
// XXX
x[key] = y[key];
}
} else {
switch (key) {
case "allOf":
case "anyOf":
case "$ref":
break;
default:
x[key] = y[key];
}
}
}
}
return x;
}
function allOf(g, schema_array, schema_path, base_schema) {
return RSVP.Queue()
.push(function () {
var i,
......@@ -323,10 +481,8 @@
var i,
x,
y,
key,
next_schema,
schema,
schema_item,
summ_arr;
for (i = 0; i < arr.length - 1; i += 1) {
summ_arr = [];
......@@ -334,56 +490,31 @@
for (y = 0; y < arr[i + 1].length; y += 1) {
schema = arr[i][x].schema;
next_schema = arr[i + 1][y].schema;
if (schema === true && next_schema === true) {
schema_item = {
schema: true,
schema_path: arr[i][x].schema_path
};
} else if (schema === false || next_schema === false) {
schema_item = {
schema: false,
schema_path: arr[i][x].schema_path
};
} else {
if (schema === true) {
schema = {};
}
if (next_schema === true) {
next_schema = {};
}
// copy before change
schema = JSON.parse(JSON.stringify(schema));
for (key in next_schema) {
if (next_schema.hasOwnProperty(key)) {
if (schema.hasOwnProperty(key)) {
// XXX need use many many rules for merging
schema[key] = next_schema[key];
} else {
schema[key] = next_schema[key];
}
}
}
schema_item = {
schema: schema,
schema_path: arr[i][x].schema_path
};
}
summ_arr.push(schema_item);
schema = mergeSchemas(schema, next_schema);
summ_arr.push({
schema: schema,
// XXX we loss path arr[i + 1][y].schema_path
schema_path: arr[i][x].schema_path
});
}
}
arr[i + 1] = summ_arr;
}
return arr[arr.length - 1];
for (x = 0; x < summ_arr.length; x += 1) {
summ_arr[x].schema = mergeSchemas(summ_arr[x].schema, base_schema);
}
return summ_arr;
});
}
function anyOf(g, schema_array, schema_path) {
function anyOf(g, schema, schema_path) {
var schema_array = schema.anyOf;
return RSVP.Queue()
.push(function () {
var i,
arr = [];
for (i = 0; i < schema_array.length; i += 1) {
arr.push(expandSchema(g, schema_array[i], schema_path + '/anyOf/' + i.toString()));
arr.push(expandSchema(g, mergeSchemas(schema_array[i], schema), schema_path + '/anyOf/' + i.toString()));
}
return RSVP.all(arr);
})
......@@ -413,10 +544,10 @@
schema = true;
}
if (schema.anyOf !== undefined) {
return anyOf(g, schema.anyOf, schema_path);
return anyOf(g, schema, schema_path);
}
if (schema.allOf !== undefined) {
return allOf(g, schema.allOf, schema_path);
return allOf(g, schema.allOf, schema_path, schema);
}
if (schema.$ref) {
return loadJSONSchema(g, schema.$ref, schema_path);
......@@ -627,11 +758,19 @@
"http://json-schema.org/draft-07/schema": "json-schema/schema7.json",
"http://json-schema.org/schema": "json-schema/schema7.json"
};
// schema_urls[path] = [
// stack urls
// "url1",
// "url2"
// ]
// used for break soft circular relation of schemas
g.props.schema_urls = {};
// schema_required_urls[path] = [
// stack required urls, on every unrequired field stack begining from []
// "url1",
// "url2"
// ]
// used for break hard circular relation of schemas
g.props.schema_required_urls = {};
// schema_resolve_errors[schema_url] = {
// schemaPath: local_schema_path,
......
......@@ -199,12 +199,15 @@
return input;
}
function render_const(schema, json_document) {
function render_const(g, schema, json_document) {
var input = document.createElement("input"),
ser_doc = JSON.stringify(json_document),
ser_const = JSON.stringify(schema.const);
input.setAttribute('readonly', true);
if (json_document === undefined || deepEqual(json_document, schema.const)) {
if (json_document === undefined) {
g.props.changed = true;
}
input.setAttribute('data-origin-value', ser_const);
input.value = ser_const;
} else {
......@@ -636,6 +639,13 @@
}
}
if (json_document === undefined) {
if (schema.hasOwnProperty('default')) {
json_document = schema.default;
gadget.props.changed = true;
}
}
// XXX add failback rendering if json_document not array
// input = render_textarea(schema, default_value, "array");
return RSVP.Queue()
......@@ -880,7 +890,7 @@
div_input.setAttribute("class", "input");
if (json_field.const !== undefined) {
input = render_const(json_field, default_value);
input = render_const(gadget, json_field, default_value);
} else if (json_field.enum !== undefined) {
input = render_enum(gadget, json_field, default_value);
// XXX take in account existing type with enum
......@@ -888,7 +898,7 @@
}
if (!input && type === "null") {
input = render_const({const: null}, default_value);
input = render_const(gadget, {const: null}, default_value);
}
if (!input && type === "boolean") {
......@@ -1208,7 +1218,12 @@
}
if (default_dict === undefined) {
default_dict = {};
if (json_field.hasOwnProperty('default')) {
default_dict = json_field.default;
g.props.changed = true;
} else {
default_dict = {};
}
}
return expandProperties(g, json_field.properties, schema_path + '/properties/', required)
......@@ -1222,6 +1237,7 @@
if (properties.hasOwnProperty(key)) {
schema_arr = properties[key];
s_o = schemaArrFilteredByDocument(schema_arr, default_dict[key]);
// XXX need schema merge with patternProperties passed key
if (checkSchemaArrOneChoise(schema_arr)) {
if (required.indexOf(key) >= 0) {
used_properties[key] = false;
......@@ -1312,30 +1328,44 @@
})
.push(function () {
var queue = RSVP.Queue(),
key,
additionalProperties;
// XXX for pattern properties needs schemas merge for
// all passed patterns
if (json_field.patternProperties !== undefined) {
// XXX need loop on any pattern properties
if (json_field.patternProperties['.*'] !== undefined) {
queue
.push(render_object_additionalProperty.bind(g,
for (key in json_field.patternProperties) {
if (json_field.patternProperties.hasOwnProperty(key)) {
if (key === ".*" ||
key === "^.*$" ||
key === ".*$" ||
key === "^.*"
) {
// additionalProperties nether used in this case
additionalProperties = false;
}
queue
.push(render_object_additionalProperty.bind(g,
g,
".* property",
key + " property",
default_dict,
path,
json_field.patternProperties['.*'],
schema_path + '/patternProperties/.*',
json_field.patternProperties[key],
schema_path + '/patternProperties/' + key,
used_properties,
element_append
))
.push(root_append);
.push(root_append);
}
}
}
if (json_field.additionalProperties === undefined) {
additionalProperties = true;
} else {
additionalProperties = json_field.additionalProperties;
if (additionalProperties === undefined) {
if (json_field.additionalProperties === undefined) {
additionalProperties = true;
} else {
additionalProperties = json_field.additionalProperties;
}
}
if (additionalProperties !== false) {
queue
......
......@@ -6,7 +6,11 @@ This code is released into the "public domain" by its author(s). Anybody may us
If you find a bug or make an improvement, it would be courteous to let the author know, but it is not compulsory.
*/
/*global module, define*/
/*jslint indent: 2, white: true*/
/*jshint -W014: true, -W089: true, -W084: true, -W069: true*/
(function (global, factory) {
"use strict";
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
......@@ -18,6 +22,7 @@ If you find a bug or make an improvement, it would be courteous to let the autho
global.tv4 = factory();
}
}(this, function () {
"use strict";
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FObject%2Fkeys
if (!Object.keys) {
......@@ -82,6 +87,7 @@ if(!Array.isArray) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FArray%2FindexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
/*jslint bitwise: true */
if (this === null) {
throw new TypeError();
}
......@@ -262,7 +268,7 @@ function uriTemplateSubstitution(spec) {
result += "=";
}
}
if (varSpec.truncate != null) {
if (varSpec.truncate !== null) {
value = value.substring(0, varSpec.truncate);
}
result += shouldEscape ? encodeURIComponent(value).replace(/!/g, "%21"): notReallyPercentEncode(value);
......@@ -533,9 +539,22 @@ ValidatorContext.prototype.reset = function () {
this.errors = [];
};
ValidatorContext.prototype.validateAllValidators = function validateAllValidators(data, schema, dataPointerPath) {
return this.validateBasic(data, schema, dataPointerPath)
|| this.validateNumeric(data, schema, dataPointerPath)
|| this.validateString(data, schema, dataPointerPath)
|| this.validateArray(data, schema, dataPointerPath)
|| this.validateObject(data, schema, dataPointerPath)
|| this.validateCombinations(data, schema, dataPointerPath)
|| this.validateHypermedia(data, schema, dataPointerPath)
|| this.validateFormat(data, schema, dataPointerPath)
|| this.validateDefinedKeywords(data, schema, dataPointerPath)
|| null;
};
ValidatorContext.prototype.validateAll = function (data, schema, dataPathParts, schemaPathParts, dataPointerPath) {
var topLevel;
if (!schema) {
if (schema === undefined || schema === true) {
return null;
} else if (schema instanceof ValidationError) {
this.errors.push(schema);
......@@ -597,16 +616,7 @@ ValidatorContext.prototype.validateAll = function (data, schema, dataPathParts,
}
var errorCount = this.errors.length;
var error = this.validateBasic(data, schema, dataPointerPath)
|| this.validateNumeric(data, schema, dataPointerPath)
|| this.validateString(data, schema, dataPointerPath)
|| this.validateArray(data, schema, dataPointerPath)
|| this.validateObject(data, schema, dataPointerPath)
|| this.validateCombinations(data, schema, dataPointerPath)
|| this.validateHypermedia(data, schema, dataPointerPath)
|| this.validateFormat(data, schema, dataPointerPath)
|| this.validateDefinedKeywords(data, schema, dataPointerPath)
|| null;
var error = this.validateAllValidators(data, schema, dataPointerPath);
if (topLevel) {
while (this.scanned.length) {
......@@ -718,10 +728,16 @@ function recursiveCompare(A, B) {
}
ValidatorContext.prototype.validateBasic = function validateBasic(data, schema, dataPointerPath) {
if (schema === false && data !== undefined) {
return this.createError(ErrorCodes.BOOLEAN_SCHEMA_FALSE, {}, '', '', null, data, schema);
}
var error;
if (error = this.validateType(data, schema, dataPointerPath)) {
return error.prefixWith(null, "type");
}
if (error = this.validateConst(data, schema, dataPointerPath)) {
return error.prefixWith(null, "const");
}
if (error = this.validateEnum(data, schema, dataPointerPath)) {
return error.prefixWith(null, "type");
}
......@@ -752,6 +768,14 @@ ValidatorContext.prototype.validateType = function validateType(data, schema) {
return this.createError(ErrorCodes.INVALID_TYPE, {type: dataType, expected: allowedTypes.join("/")}, '', '', null, data, schema);
};
ValidatorContext.prototype.validateConst = function validateConst(data, schema) {
if (schema.const === undefined ||
recursiveCompare(data, schema.const)) {
return null;
}
return this.createError(ErrorCodes.CONST_NOT_EQUAL, {}, '', '', null, data, schema);
};
ValidatorContext.prototype.validateEnum = function validateEnum(data, schema) {
if (schema["enum"] === undefined) {
return null;
......@@ -796,18 +820,24 @@ ValidatorContext.prototype.validateMinMax = function validateMinMax(data, schema
if (data < schema.minimum) {
return this.createError(ErrorCodes.NUMBER_MINIMUM, {value: data, minimum: schema.minimum}, '', '/minimum', null, data, schema);
}
if (schema.exclusiveMinimum && data === schema.minimum) {
if (schema.exclusiveMinimum === true && data === schema.minimum) {
return this.createError(ErrorCodes.NUMBER_MINIMUM_EXCLUSIVE, {value: data, minimum: schema.minimum}, '', '/exclusiveMinimum', null, data, schema);
}
}
if ((typeof schema.exclusiveMinimum === "number") && data <= schema.exclusiveMinimum) {
return this.createError(ErrorCodes.NUMBER_MINIMUM_EXCLUSIVE, {value: data, minimum: schema.exclusiveMinimum}, '', '/exclusiveMinimum', null, data, schema);
}
if (schema.maximum !== undefined) {
if (data > schema.maximum) {
return this.createError(ErrorCodes.NUMBER_MAXIMUM, {value: data, maximum: schema.maximum}, '', '/maximum', null, data, schema);
}
if (schema.exclusiveMaximum && data === schema.maximum) {
if (schema.exclusiveMaximum === true && data === schema.maximum) {
return this.createError(ErrorCodes.NUMBER_MAXIMUM_EXCLUSIVE, {value: data, maximum: schema.maximum}, '', '/exclusiveMaximum', null, data, schema);
}
}
if ((typeof schema.exclusiveMaximum === "number") && data >= schema.exclusiveMaximum) {
return this.createError(ErrorCodes.NUMBER_MAXIMUM_EXCLUSIVE, {value: data, maximum: schema.exclusiveMaximum}, '', '/exclusiveMaximum', null, data, schema);
}
return null;
};
......@@ -878,6 +908,7 @@ ValidatorContext.prototype.validateArray = function validateArray(data, schema,
}
return this.validateArrayLength(data, schema, dataPointerPath)
|| this.validateArrayUniqueItems(data, schema, dataPointerPath)
|| this.validateArrayContains(data, schema, dataPointerPath)
|| this.validateArrayItems(data, schema, dataPointerPath)
|| null;
};
......@@ -903,6 +934,28 @@ ValidatorContext.prototype.validateArrayLength = function validateArrayLength(da
return null;
};
ValidatorContext.prototype.validateArrayContains = function validateArrayContains(data, schema, dataPointerPath) {
if ((schema.contains === true && data.length > 0) || schema.contains === undefined) {
return null;
}
var error;
if (data.length === 0 || schema.contains === false) {
error = true;
} else {
for (var i = 0; i < data.length; i++) {
error = this.validateAllValidators(data[i], schema.contains, dataPointerPath + "/" + i);
if (!error) {
return null;
}
}
error = true;
}
if (error) {
return this.createError(ErrorCodes.ARRAY_CONTAINS, {}, '', '/contains', null, data, schema);
}
return null;
};
ValidatorContext.prototype.validateArrayUniqueItems = function validateArrayUniqueItems(data, schema) {
if (schema.uniqueItems) {
for (var i = 0; i < data.length; i++) {
......@@ -1006,6 +1059,11 @@ ValidatorContext.prototype.validateObjectProperties = function validateObjectPro
for (var key in data) {
var keyPointerPath = dataPointerPath + "/" + key.replace(/~/g, '~0').replace(/\//g, '~1');
var foundMatch = false;
if (schema.propertyNames !== undefined && schema.propertyNames !== true) {
if (error = this.validateAllValidators(key, schema.propertyNames, keyPointerPath)) {
return this.createError(ErrorCodes.OBJECT_PROPERTY_NAMES, {key: key, error: error.message}, '', '/propertyNames', null, data, schema).prefixWith(key, null);
}
}
if (schema.properties !== undefined && schema.properties[key] !== undefined) {
foundMatch = true;
if (error = this.validateAll(data[key], schema.properties[key], [key], ["properties", key], keyPointerPath)) {
......@@ -1375,6 +1433,8 @@ var ErrorCodes = {
ONE_OF_MISSING: 11,
ONE_OF_MULTIPLE: 12,
NOT_PASSED: 13,
BOOLEAN_SCHEMA_FALSE: 14,
CONST_NOT_EQUAL: 15,
// Numeric errors
NUMBER_MULTIPLE_OF: 100,
NUMBER_MINIMUM: 101,
......@@ -1392,11 +1452,13 @@ var ErrorCodes = {
OBJECT_REQUIRED: 302,
OBJECT_ADDITIONAL_PROPERTIES: 303,
OBJECT_DEPENDENCY_KEY: 304,
OBJECT_PROPERTY_NAMES: 305,
// Array errors
ARRAY_LENGTH_SHORT: 400,
ARRAY_LENGTH_LONG: 401,
ARRAY_UNIQUE: 402,
ARRAY_ADDITIONAL_ITEMS: 403,
ARRAY_CONTAINS: 404,
// Custom/user-defined errors
FORMAT_CUSTOM: 500,
KEYWORD_CUSTOM: 501,
......@@ -1416,6 +1478,8 @@ var ErrorMessagesDefault = {
ONE_OF_MISSING: "Data does not match any schemas from \"oneOf\"",
ONE_OF_MULTIPLE: "Data is valid against more than one schema from \"oneOf\": indices {index1} and {index2}",
NOT_PASSED: "Data matches schema from \"not\"",
BOOLEAN_SCHEMA_FALSE: "Schema does not allow any data",
CONST_NOT_EQUAL: "Data does not match schema.const",
// Numeric errors
NUMBER_MULTIPLE_OF: "Value {value} is not a multiple of {multipleOf}",
NUMBER_MINIMUM: "Value {value} is less than minimum {minimum}",
......@@ -1433,11 +1497,13 @@ var ErrorMessagesDefault = {
OBJECT_REQUIRED: "Missing required property: {key}",
OBJECT_ADDITIONAL_PROPERTIES: "Additional properties not allowed",
OBJECT_DEPENDENCY_KEY: "Dependency failed - key must exist: {missing} (due to key: {key})",
OBJECT_PROPERTY_NAMES: "Property name \"{key}\" does not match schema with error: {error}",
// Array errors
ARRAY_LENGTH_SHORT: "Array is too short ({length}), minimum {minimum}",
ARRAY_LENGTH_LONG: "Array is too long ({length}), maximum {maximum}",
ARRAY_UNIQUE: "Array items are not unique (indices {match1} and {match2})",
ARRAY_ADDITIONAL_ITEMS: "Additional items not allowed",
ARRAY_CONTAINS: "Array are not contain item matching schema.contains",
// Format errors
FORMAT_CUSTOM: "Format validation failed ({message})",
KEYWORD_CUSTOM: "Keyword failed: {key} ({message})",
......@@ -1462,6 +1528,7 @@ function ValidationError(code, params, dataPath, schemaPath, subErrors) {
var err = new Error(this.message);
this.stack = err.stack || err.stacktrace;
if (!this.stack) {
/*jshint -W002: true*/
try {
throw err;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment