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*/ /*jslint nomen: true, maxlen: 200, indent: 2*/
/*global window, rJS, console, RSVP, jQuery, jIO, tv4, URI, JSON, $, btoa */ /*global window, rJS, RSVP, btoa, URI, Validator, jIO, JSON, $RefParser */
(function (window, rJS, $, RSVP, btoa, URI, tv4) { (function (window, rJS, RSVP, btoa, URI, Validator, jIO, JSON, $RefParser) {
"use strict"; "use strict";
var gk = rJS(window);
function getJSON(url) { function getJSON(url) {
var uri = URI(url), var uri = URI(url),
headers = {}, headers = {},
...@@ -31,196 +29,7 @@ ...@@ -31,196 +29,7 @@
}); });
} }
function resolveLocalReference(ref, schema) { rJS(window)
// 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;
})
.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);
});
}
function getMetaJSONSchema(serialisation) {
if (serialisation === "xml") {
return getJSON("slapos_load_meta_schema_xml.json");
}
if (serialisation === "json-in-xml") {
return getJSON("slapos_load_meta_schema_json_in_xml.json");
}
return getJSON("slapos_load_meta_schema.json");
}
function validateJSONSchema(json, base_url, serialisation) {
return getMetaJSONSchema(serialisation)
.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);
});
}
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);
});
}
gk
.declareMethod("getBaseUrl", function (url) { .declareMethod("getBaseUrl", function (url) {
var base_url, url_uri = URI(url); var base_url, url_uri = URI(url);
base_url = url_uri.path().split("/"); base_url = url_uri.path().split("/");
...@@ -229,20 +38,53 @@ ...@@ -229,20 +38,53 @@
return base_url; return base_url;
}) })
.declareMethod("loadJSONSchema", function (url, serialisation) { .declareMethod("loadJSONSchema", function (url, serialisation) {
var gadget = this; var meta_schema_url = "slapos_load_meta_schema.json";
return getJSON(url)
.push(function (json) { if (serialisation === "xml") {
return gadget.getBaseUrl(url) meta_schema_url = "slapos_load_meta_schema_xml.json";
.push(function (base_url) { }
return validateJSONSchema(json, base_url, serialisation);
if (serialisation === "json-in-xml") {
meta_schema_url = "slapos_load_meta_schema_json_in_xml.json";
}
return getJSON(meta_schema_url)
.push(function (meta_schema) {
return new RSVP.Queue()
.push(function () {
return $RefParser
.dereference(url)
.then(function (schema) {
return schema;
});
})
.push(function (schema) {
var validator = new Validator(JSON.parse(meta_schema), '7');
if (!validator.validate(schema)) {
throw new Error("Non valid JSON schema " + JSON.stringify(schema));
}
return schema;
}); });
}); });
}) })
.declareMethod("loadSoftwareJSON", function (url) { .declareMethod("loadSoftwareJSON", function (url) {
return getJSON(url) return getJSON(url)
.push(function (json) { .push(function (software_cfg_json) {
return validateSoftwareJSONSchema(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 @@ ...@@ -255,13 +97,16 @@
} }
} }
return getJSON(parameter_schema_url) return new RSVP.Queue()
.push(function (json) { .push(function () {
var schema = JSON.parse(json); return $RefParser
return expandSchema(schema, schema, base_url) .dereference(parameter_schema_url)
.push(function (loaded_json) { .then(function (schema) {
return tv4.validateMultiple(generated_json, loaded_json); 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 @@ ...@@ -236,7 +236,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>982.28125.59086.62805</string> </value> <value> <string>1003.49132.42731.30429</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -254,7 +254,7 @@ ...@@ -254,7 +254,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1583836689.5</float> <float>1666203750.58</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
jQuery, URI, vkbeautify, domsugar, Boolean */ jQuery, URI, vkbeautify, domsugar, Boolean */
(function (window, document, rJS, $, XMLSerializer, jQuery, vkbeautify, (function (window, document, rJS, $, XMLSerializer, jQuery, vkbeautify,
loopEventListener, domsugar, Boolean) { domsugar, Boolean) {
"use strict"; "use strict";
var DISPLAY_JSON_FORM = 'display_json_form', var DISPLAY_JSON_FORM = 'display_json_form',
...@@ -268,6 +268,15 @@ ...@@ -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) { for (key in json_field.properties) {
if (json_field.properties.hasOwnProperty(key)) { if (json_field.properties.hasOwnProperty(key)) {
div = document.createElement("div"); div = document.createElement("div");
...@@ -291,7 +300,10 @@ ...@@ -291,7 +300,10 @@
path + "/" + key); path + "/" + key);
} else { } else {
input = render_field( 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.name = path + "/" + key;
input.setAttribute("class", "slapos-parameter"); input.setAttribute("class", "slapos-parameter");
input.setAttribute("placeholder", " "); input.setAttribute("placeholder", " ");
...@@ -577,11 +589,9 @@ ...@@ -577,11 +589,9 @@
parameter_hash_input = g.element.querySelectorAll('.parameter_hash_output')[0], parameter_hash_input = g.element.querySelectorAll('.parameter_hash_output')[0],
field_name, field_name,
div, div,
divm,
missing_index,
missing_field_name,
xml_output, xml_output,
input_field; input_field,
error_dict;
$(g.element.querySelectorAll("span.error")).each(function (i, span) { $(g.element.querySelectorAll("span.error")).each(function (i, span) {
span.textContent = ""; span.textContent = "";
...@@ -590,21 +600,20 @@ ...@@ -590,21 +600,20 @@
$(g.element.querySelectorAll("div.error-input")).each(function (i, div) { $(g.element.querySelectorAll("div.error-input")).each(function (i, div) {
div.setAttribute("class", ""); div.setAttribute("class", "");
}); });
if (serialisation_type === "json-in-xml") { if (serialisation_type === "json-in-xml") {
xml_output = jsonDictToParameterJSONInXML(json_dict); xml_output = jsonDictToParameterJSONInXML(json_dict);
} else { } else {
xml_output = jsonDictToParameterXML(json_dict); xml_output = jsonDictToParameterXML(json_dict);
} }
parameter_hash_input.value = btoa(xml_output); parameter_hash_input.value = btoa(xml_output);
// g.options.value.parameter.parameter_hash = btoa(xml_output);
// console.log(parameter_hash_input.value); // Update fields if errors exist
// console.log(xml_output);
if (validation.valid) {
return xml_output;
}
for (error_index in validation.errors) { for (error_index in validation.errors) {
if (validation.errors.hasOwnProperty(error_index)) { 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 !== "") { if (field_name !== "") {
input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']"); input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
if (input_field === null) { if (input_field === null) {
...@@ -613,33 +622,21 @@ ...@@ -613,33 +622,21 @@
} }
div = input_field.parentNode; div = input_field.parentNode;
div.setAttribute("class", "slapos-parameter error-input"); div.setAttribute("class", "slapos-parameter error-input");
div.querySelector("span.error").textContent = validation.errors[error_index].message; div.querySelector("span.error").textContent = validation.errors[error_index].error;
} else if (validation.errors[error_index].code == "302") { } else if (error_dict.keyword === "required") {
// OBJECT_REQUIRED use case // Specific use case for required
field_name = "/" + validation.errors[error_index].params.key; field_name = "/" + error_dict.key;
input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']"); input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
if (input_field === null) { if (input_field === null) {
field_name = field_name.split("/").slice(0, -1).join("/"); field_name = field_name.split("/").slice(0, -1).join("/");
input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']"); input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
} }
div = input_field.parentNode; if (input_field !== null) {
div.setAttribute("class", "slapos-parameter error-input"); div = input_field.parentNode;
div.querySelector("span.error").textContent = validation.errors[error_index].message; div.setAttribute("class", "slapos-parameter error-input");
} 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; return xml_output;
...@@ -1105,22 +1102,5 @@ ...@@ -1105,22 +1102,5 @@
}); });
}, {mutex: 'statechange'}); }, {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, }(window, document, rJS, $, XMLSerializer, jQuery, vkbeautify,
rJS.loopEventListener, domsugar, Boolean)); domsugar, Boolean));
\ No newline at end of file \ No newline at end of file
...@@ -280,7 +280,7 @@ ...@@ -280,7 +280,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>1003.46178.61021.55398</string> </value> <value> <string>1003.49163.51970.27118</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -298,7 +298,7 @@ ...@@ -298,7 +298,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1666027007.74</float> <float>1666206917.22</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </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