Commit 3e5d9c4f authored by Rafael Monnerat's avatar Rafael Monnerat

slapos_jio: Replace validator and dereference on parameter editor

   Replace tv4 by cfworker-jsonschema in order to support more recent schemas.
   Use ref-parser rather them custom expandSchema to build a expanded json schema.

    cfworker json schema was built from https://github.com/cfworker/cfworker/tree/main/packages/json-schema source code.
parent f7eb220e
/*jslint nomen: true, maxlen: 200, indent: 2*/
/*global window, rJS, console, RSVP, jQuery, jIO, tv4, URI, JSON, $, btoa */
(function (window, rJS, $, RSVP, btoa, URI, tv4) {
/*global window, rJS, RSVP, btoa, URI, Validator, jIO, JSON, $RefParser */
(function (window, rJS, RSVP, btoa, URI, Validator, jIO, JSON, $RefParser) {
"use strict";
var gk = rJS(window);
function getJSON(url) {
var uri = URI(url),
headers = {},
......@@ -31,218 +29,62 @@
});
}
function resolveLocalReference(ref, schema) {
// 2 here is for #/
var i, ref_path = ref.substr(2, ref.length),
parts = ref_path.split("/");
if (parts.length === 1 && parts[0] === "") {
// It was uses #/ to reference the entire json so just return it.
return schema;
}
for (i = 0; i < parts.length; i += 1) {
schema = schema[parts[i]];
}
return schema;
}
function resolveReference(partial_schema, schema, base_url) {
var parts,
external_schema,
ref = partial_schema.$ref;
if (ref === undefined) {
return new RSVP.Queue().push(function () {
return partial_schema;
});
}
if (ref.substr(0, 1) === "#") {
return new RSVP.Queue().push(function () {
return resolveLocalReference(ref, schema);
});
}
return new RSVP.Queue().push(function () {
if (URI(ref).protocol() === "") {
if (base_url !== undefined) {
ref = base_url + "/" + ref;
}
}
return getJSON(ref);
})
.push(function (json) {
external_schema = JSON.parse(json);
parts = ref.split("#");
ref = "#" + parts[1];
return resolveLocalReference(ref, external_schema);
});
}
function clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
// Inspired from https://github.com/nexedi/dream/blob/master/dream/platform/src/jsplumb/jsplumb.js#L398
function expandSchema(json_schema, full_schema, base_url) {
var i,
expanded_json_schema = clone(json_schema) || {};
if (!expanded_json_schema.properties) {
expanded_json_schema.properties = {};
}
return new RSVP.Queue().push(function () {
if (json_schema.$ref) {
return resolveReference(
json_schema,
full_schema,
base_url
)
.push(function (remote_schema) {
return expandSchema(
remote_schema,
full_schema,
base_url
);
}).push(function (referencedx) {
$.extend(expanded_json_schema, referencedx);
delete expanded_json_schema.$ref;
return true;
});
}
return true;
}).push(function () {
var property, queue = new RSVP.Queue();
function wrapperResolveReference(p) {
return resolveReference(
json_schema.properties[p],
full_schema,
base_url
).push(function (external_schema) {
// console.log(p);
return expandSchema(
external_schema,
full_schema,
base_url
)
.push(function (referencedx) {
$.extend(expanded_json_schema.properties[p], referencedx);
if (json_schema.properties[p].$ref) {
delete expanded_json_schema.properties[p].$ref;
}
return referencedx;
});
});
}
// expand ref in properties
for (property in json_schema.properties) {
if (json_schema.properties.hasOwnProperty(property)) {
queue.push(
wrapperResolveReference.bind(this, property)
);
}
}
return queue;
})
.push(function () {
var zqueue = new RSVP.Queue();
function wrapperExpandSchema(p) {
return expandSchema(
json_schema.allOf[p],
full_schema,
base_url
).push(function (referencedx) {
if (referencedx.properties) {
$.extend(
expanded_json_schema.properties,
referencedx.properties
);
delete referencedx.properties;
}
$.extend(expanded_json_schema, referencedx);
});
}
if (json_schema.allOf) {
for (i = 0; i < json_schema.allOf.length; i += 1) {
zqueue.push(wrapperExpandSchema.bind(this, i));
}
}
return zqueue;
rJS(window)
.declareMethod("getBaseUrl", function (url) {
var base_url, url_uri = URI(url);
base_url = url_uri.path().split("/");
base_url.pop();
base_url = url.split(url_uri.path())[0] + base_url.join("/");
return base_url;
})
.push(function () {
if (expanded_json_schema.allOf) {
delete expanded_json_schema.allOf;
}
if (expanded_json_schema.$ref) {
delete expanded_json_schema.$ref;
}
// console.log(expanded_json_schema);
return clone(expanded_json_schema);
});
}
.declareMethod("loadJSONSchema", function (url, serialisation) {
var meta_schema_url = "slapos_load_meta_schema.json";
function getMetaJSONSchema(serialisation) {
if (serialisation === "xml") {
return getJSON("slapos_load_meta_schema_xml.json");
meta_schema_url = "slapos_load_meta_schema_xml.json";
}
if (serialisation === "json-in-xml") {
return getJSON("slapos_load_meta_schema_json_in_xml.json");
meta_schema_url = "slapos_load_meta_schema_json_in_xml.json";
}
return getJSON("slapos_load_meta_schema.json");
}
function validateJSONSchema(json, base_url, serialisation) {
return getMetaJSONSchema(serialisation)
return getJSON(meta_schema_url)
.push(function (meta_schema) {
if (!tv4.validate(json, meta_schema)) {
throw new Error("Non valid JSON schema " + json);
}
return JSON.parse(json);
})
.push(function (schema) {
return expandSchema(schema, schema, base_url);
return new RSVP.Queue()
.push(function () {
return $RefParser
.dereference(url)
.then(function (schema) {
return schema;
});
}
function validateSoftwareJSONSchema(json) {
return getJSON("slapos_load_software_schema.json")
})
.push(function (schema) {
if (!tv4.validate(json, schema)) {
throw new Error("Non valid JSON for software.cfg.json:" + json);
}
return JSON.parse(json);
});
var validator = new Validator(JSON.parse(meta_schema), '7');
if (!validator.validate(schema)) {
throw new Error("Non valid JSON schema " + JSON.stringify(schema));
}
gk
.declareMethod("getBaseUrl", function (url) {
var base_url, url_uri = URI(url);
base_url = url_uri.path().split("/");
base_url.pop();
base_url = url.split(url_uri.path())[0] + base_url.join("/");
return base_url;
})
.declareMethod("loadJSONSchema", function (url, serialisation) {
var gadget = this;
return getJSON(url)
.push(function (json) {
return gadget.getBaseUrl(url)
.push(function (base_url) {
return validateJSONSchema(json, base_url, serialisation);
return schema;
});
});
})
.declareMethod("loadSoftwareJSON", function (url) {
return getJSON(url)
.push(function (json) {
return validateSoftwareJSONSchema(json);
.push(function (software_cfg_json) {
return new RSVP.Queue()
.push(function () {
return $RefParser
.dereference("slapos_load_software_schema.json")
.then(function (software_schema) {
return software_schema;
});
})
.push(function (software_schema) {
var software_json = JSON.parse(software_cfg_json),
validator = new Validator(software_schema, '7');
if (!validator.validate(software_json)) {
throw new Error("Non valid JSON for software.cfg.json:" + software_cfg_json);
}
return software_json;
});
});
})
......@@ -255,13 +97,16 @@
}
}
return getJSON(parameter_schema_url)
.push(function (json) {
var schema = JSON.parse(json);
return expandSchema(schema, schema, base_url)
.push(function (loaded_json) {
return tv4.validateMultiple(generated_json, loaded_json);
return new RSVP.Queue()
.push(function () {
return $RefParser
.dereference(parameter_schema_url)
.then(function (schema) {
return schema;
});
})
.push(function (schema) {
return new Validator(schema, '7', false).validate(generated_json);
});
});
}(window, rJS, $, RSVP, btoa, URI, tv4));
}(window, rJS, RSVP, btoa, URI, Validator, jIO, JSON, $RefParser));
\ No newline at end of file
......@@ -236,7 +236,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>982.28125.59086.62805</string> </value>
<value> <string>1003.49132.42731.30429</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -254,7 +254,7 @@
</tuple>
<state>
<tuple>
<float>1583836689.5</float>
<float>1666203750.58</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -3,7 +3,7 @@
jQuery, URI, vkbeautify, domsugar, Boolean */
(function (window, document, rJS, $, XMLSerializer, jQuery, vkbeautify,
loopEventListener, domsugar, Boolean) {
domsugar, Boolean) {
"use strict";
var DISPLAY_JSON_FORM = 'display_json_form',
......@@ -268,6 +268,15 @@
}
}
for (key in json_field.allOf) {
if (json_field.allOf.hasOwnProperty(key)) {
render_subform(json_field.allOf[key],
default_dict,
root,
path);
}
}
for (key in json_field.properties) {
if (json_field.properties.hasOwnProperty(key)) {
div = document.createElement("div");
......@@ -291,7 +300,10 @@
path + "/" + key);
} else {
input = render_field(
json_field.properties[key], default_dict[key], is_required);
json_field.properties[key],
default_dict[key],
is_required
);
input.name = path + "/" + key;
input.setAttribute("class", "slapos-parameter");
input.setAttribute("placeholder", " ");
......@@ -577,11 +589,9 @@
parameter_hash_input = g.element.querySelectorAll('.parameter_hash_output')[0],
field_name,
div,
divm,
missing_index,
missing_field_name,
xml_output,
input_field;
input_field,
error_dict;
$(g.element.querySelectorAll("span.error")).each(function (i, span) {
span.textContent = "";
......@@ -590,21 +600,20 @@
$(g.element.querySelectorAll("div.error-input")).each(function (i, div) {
div.setAttribute("class", "");
});
if (serialisation_type === "json-in-xml") {
xml_output = jsonDictToParameterJSONInXML(json_dict);
} else {
xml_output = jsonDictToParameterXML(json_dict);
}
parameter_hash_input.value = btoa(xml_output);
// g.options.value.parameter.parameter_hash = btoa(xml_output);
// console.log(parameter_hash_input.value);
// console.log(xml_output);
if (validation.valid) {
return xml_output;
}
// Update fields if errors exist
for (error_index in validation.errors) {
if (validation.errors.hasOwnProperty(error_index)) {
field_name = validation.errors[error_index].dataPath;
error_dict = validation.errors[error_index];
// error_dict = { error : "", instanceLocation: "#", keyword: "", keywordLocation: "" }
field_name = error_dict.instanceLocation.slice(1);
if (field_name !== "") {
input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
if (input_field === null) {
......@@ -613,34 +622,22 @@
}
div = input_field.parentNode;
div.setAttribute("class", "slapos-parameter error-input");
div.querySelector("span.error").textContent = validation.errors[error_index].message;
} else if (validation.errors[error_index].code == "302") {
// OBJECT_REQUIRED use case
field_name = "/" + validation.errors[error_index].params.key;
div.querySelector("span.error").textContent = validation.errors[error_index].error;
} else if (error_dict.keyword === "required") {
// Specific use case for required
field_name = "/" + error_dict.key;
input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
if (input_field === null) {
field_name = field_name.split("/").slice(0, -1).join("/");
input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
}
if (input_field !== null) {
div = input_field.parentNode;
div.setAttribute("class", "slapos-parameter error-input");
div.querySelector("span.error").textContent = validation.errors[error_index].message;
div.querySelector("span.error").textContent = error_dict.error;
}
}
}
for (missing_index in validation.missing) {
if (validation.missing.hasOwnProperty(missing_index)) {
missing_field_name = validation.missing[missing_index].dataPath;
input_field = g.element.querySelector(".slapos-parameter[name='/" + missing_field_name + "']");
if (input_field === null) {
missing_field_name = field_name.split("/").slice(0, -1).join("/");
input_field = g.element.querySelector(".slapos-parameter[name='/" + missing_field_name + "']");
}
divm = input_field.parentNode;
divm.setAttribute("class", "error-input");
divm.querySelector("span.error").textContent = validation.missing[missing_index].message;
}
}
return xml_output;
});
......@@ -1105,22 +1102,5 @@
});
}, {mutex: 'statechange'});
//.declareService(function () {
// var gadget = this;
//return gadget.processValidation(gadget.options.json_url)
// .fail(function (error) {
// var parameter_xml = '';
// console.log(error.stack);
// if (gadget.options.value.parameter.parameter_hash !== undefined) {
// parameter_xml = atob(gadget.options.value.parameter.parameter_hash);
// }
// return gadget.renderFailoverTextArea(parameter_xml, error.toString())
// .push(function () {
// error = undefined;
// return gadget;
// });
// });
//});
}(window, document, rJS, $, XMLSerializer, jQuery, vkbeautify,
rJS.loopEventListener, domsugar, Boolean));
\ No newline at end of file
domsugar, Boolean));
\ No newline at end of file
......@@ -280,7 +280,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1003.46178.61021.55398</string> </value>
<value> <string>1003.49163.51970.27118</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -298,7 +298,7 @@
</tuple>
<state>
<tuple>
<float>1666027007.74</float>
<float>1666206917.22</float>
<string>UTC</string>
</tuple>
</state>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>cfworker-jsonschema-validator.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>validator.js</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
This source diff could not be displayed because it is too large. You can view the blob instead.
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>ref-parser.min.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
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