JSON Auto correction + hyperion schema split for better readability (#452)

* revoke schema split

* add "getAutoCorrectedConfig" function

* revoke schema split

* revoke schema split

* revoke schema split

* Prevent compiler error if none grabber is available

* revoke schema split

* add "getAutoCorrectedConfig" function

* revoke schema split

* remove "configMigrator"

* remove "configMigrator"

* Change TestConfigFile to show how the autocorrection works

* revoke schema split

* revoke schema split

* remove "ConfigMigrator"

* remove "ConfigMigrator"

* remove "ConfigMigratorBase"

* remove "ConfigMigratorBase"

* Add QJsonUtils.h

* added ability "ignore-required"

It has been added the ability to correct the configuration without having to pay attention to the keyword "required" in the hyperion schema

* Allow Comments in Hyperion Schema

* add ability to ignore the "required" keyword in hyperion schema

* add ability to ignore the "required" keyword in hyperion schema

* add ability to ignore the "required" keyword in hyperion schema

* //Allow Comments in Hyperion Schema

* Update jsonschema.py to version 0.8.0 to support ...

references in json schema

* add RefResolver from jsonschema.py to resolve

references in hyperion schema

* remove dupe code

* split the hyperion schema in separatly files

For better readability

* add function "resolveReferences" to resolve

references in hyperion schema.

* remove CURRENT_CONFIG_VERSION

* remove CURRENT_CONFIG_VERSION

* split the hyperion schema in separatly files

For better readability

* Create schema-backgroundEffect.json

* Add the rest of the Hyperion schema via upload

* Remove Comments in config file

* Add return variable to function writeJson().

fix function resolveReferences().
edit function load() to handle QPair result from schemaChecker.

* edit function validate() to return QPair variable

* fit function loadEffectDefinition()

* fit function checkJson()

*  Expand error check by dividing

"_error" variable in "_error" and "_schemaError".
Replace variable "bool" in validate() in QPair

* Extend function "cmd_cfg_set" to handle auto correction

* Extend function "loadConfig" to handle auto correction

* fix function loadConfig()
This commit is contained in:
Paulchen Panther 2017-07-30 13:32:10 +02:00 committed by brindosch
parent 622a171808
commit 5bd020a570
45 changed files with 2356 additions and 2254 deletions

View File

@ -14,7 +14,6 @@ SET ( HYPERION_VERSION_STABLE OFF )
SET ( HYPERION_VERSION_MAJOR 2 ) SET ( HYPERION_VERSION_MAJOR 2 )
SET ( HYPERION_VERSION_MINOR 0 ) SET ( HYPERION_VERSION_MINOR 0 )
SET ( HYPERION_VERSION_PATCH 0 ) SET ( HYPERION_VERSION_PATCH 0 )
SET ( CURRENT_CONFIG_VERSION 2 )
SET ( DEFAULT_AMLOGIC OFF ) SET ( DEFAULT_AMLOGIC OFF )
SET ( DEFAULT_DISPMANX OFF ) SET ( DEFAULT_DISPMANX OFF )
@ -160,7 +159,7 @@ IF ( ${CHECK_EFFECTS_FAILED} )
ENDIF () ENDIF ()
EXECUTE_PROCESS ( EXECUTE_PROCESS (
COMMAND python test/jsonchecks/checkschema.py config/hyperion.config.json.default libsrc/hyperion/schemas/hyperion.schema-${CURRENT_CONFIG_VERSION}.json COMMAND python test/jsonchecks/checkschema.py config/hyperion.config.json.default libsrc/hyperion/hyperion.schema.json
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
RESULT_VARIABLE CHECK_CONFIG_FAILED RESULT_VARIABLE CHECK_CONFIG_FAILED
) )

View File

@ -30,9 +30,6 @@
// Define to enable profiler for development purpose // Define to enable profiler for development purpose
#cmakedefine ENABLE_PROFILER #cmakedefine ENABLE_PROFILER
// Define version id of current config
#define CURRENT_CONFIG_VERSION ${CURRENT_CONFIG_VERSION}
// the hyperion build id string // the hyperion build id string
#define HYPERION_BUILD_ID "${HYPERION_BUILD_ID}" #define HYPERION_BUILD_ID "${HYPERION_BUILD_ID}"

View File

@ -40,7 +40,7 @@ $(document).ready( function() {
var r = new FileReader(); var r = new FileReader();
r.onload = function(e) r.onload = function(e)
{ {
var content = e.target.result; var content = e.target.result.replace(/[^:]?\/\/.*/g, ''); //remove Comments
//check file is json //check file is json
var check = isJsonString(content); var check = isJsonString(content);
@ -56,7 +56,7 @@ $(document).ready( function() {
if(typeof content.leds === 'undefined' || typeof content.general === 'undefined') if(typeof content.leds === 'undefined' || typeof content.general === 'undefined')
{ {
showInfoDialog('error', "", $.i18n('infoDialog_import_hyperror_text', f.name)); showInfoDialog('error', "", $.i18n('infoDialog_import_hyperror_text', f.name));
dis_imp_btn(true); dis_imp_btn(true);
} }
else else
{ {

View File

@ -25,17 +25,14 @@ public:
// create the validator // create the validator
QJsonSchemaChecker schemaChecker; QJsonSchemaChecker schemaChecker;
schemaChecker.setSchema(schemaTree); schemaChecker.setSchema(schemaTree);
bool valid = schemaChecker.validate(configTree);
QStringList messages = schemaChecker.getMessages(); QStringList messages = schemaChecker.getMessages();
for (int i = 0; i < messages.size(); ++i)
{ if (!schemaChecker.validate(configTree).first)
std::cout << messages[i].toStdString() << std::endl;
}
if (!valid)
{ {
for (int i = 0; i < messages.size(); ++i)
std::cout << messages[i].toStdString() << std::endl;
std::cerr << "Validation failed for configuration file: " << config.toStdString() << std::endl; std::cerr << "Validation failed for configuration file: " << config.toStdString() << std::endl;
return -3; return -3;
} }
@ -54,6 +51,7 @@ public:
throw std::runtime_error(QString("Configuration file not found: '" + path + "' (" + file.errorString() + ")").toStdString()); throw std::runtime_error(QString("Configuration file not found: '" + path + "' (" + file.errorString() + ")").toStdString());
} }
//Allow Comments in Config
QString config = QString(file.readAll()); QString config = QString(file.readAll());
config.remove(QRegularExpression("([^:]?\\/\\/.*)")); config.remove(QRegularExpression("([^:]?\\/\\/.*)"));
@ -112,23 +110,62 @@ public:
} }
} }
throw std::runtime_error(QString("ERROR: Json schema wrong: " + error.errorString() + " at Line: " + QString::number(errorLine) throw std::runtime_error(QString("ERROR: Json schema wrong: " + error.errorString() +
+ ", Column: " + QString::number(errorColumn)).toStdString() " at Line: " + QString::number(errorLine) +
); ", Column: " + QString::number(errorColumn)).toStdString());
} }
return doc.object(); return resolveReferences(doc.object());
} }
static void writeJson(const QString& filename, QJsonObject& jsonTree) static QJsonObject resolveReferences(const QJsonObject& schema)
{
QJsonObject result;
for (QJsonObject::const_iterator i = schema.begin(); i != schema.end(); ++i)
{
QString attribute = i.key();
const QJsonValue & attributeValue = *i;
if (attribute == "$ref" && attributeValue.isString())
{
try
{
result = readSchema(":/" + attributeValue.toString());
}
catch (std::runtime_error& error)
{
throw std::runtime_error(error.what());
}
}
else if (attributeValue.isObject())
result.insert(attribute, resolveReferences(attributeValue.toObject()));
else
result.insert(attribute, attributeValue);
}
return result;
}
static bool writeJson(const QString& filename, QJsonObject& jsonTree)
{ {
QJsonDocument doc; QJsonDocument doc;
doc.setObject(jsonTree); doc.setObject(jsonTree);
QByteArray configData = doc.toJson(QJsonDocument::Indented); QByteArray configData = doc.toJson(QJsonDocument::Indented);
QFile configFile(filename); QFile configFile(filename);
configFile.open(QIODevice::WriteOnly | QIODevice::Truncate); if (!configFile.open(QFile::WriteOnly | QFile::Truncate))
return false;
configFile.write(configData); configFile.write(configData);
QFile::FileError error = configFile.error();
if (error != QFile::NoError)
return false;
configFile.close(); configFile.close();
return true;
} }
}; };

View File

@ -5,6 +5,7 @@
#include <QJsonValue> #include <QJsonValue>
#include <QJsonArray> #include <QJsonArray>
#include <QStringList> #include <QStringList>
#include <QPair>
/// JsonSchemaChecker is a very basic implementation of json schema. /// JsonSchemaChecker is a very basic implementation of json schema.
/// The json schema definition draft can be found at /// The json schema definition draft can be found at
@ -33,13 +34,23 @@ public:
/// @return true upon succes /// @return true upon succes
/// ///
bool setSchema(const QJsonObject & schema); bool setSchema(const QJsonObject & schema);
/// ///
/// @brief Validate a JSON structure /// @brief Validate a JSON structure
/// @param value The JSON value to check /// @param value The JSON value to check
/// @return true when the arguments is valid according to the schema /// @param ignoreRequired Ignore the "required" keyword in hyperion schema. Default is false
/// @return The first boolean is true when the arguments is valid according to the schema. The second is true when the schema contains no errors
/// @return TODO: Check the Schema in SetSchema() function and remove the QPair result
/// ///
bool validate(const QJsonObject & value, bool ignoreRequired = false); QPair<bool, bool> validate(const QJsonObject & value, bool ignoreRequired = false);
///
/// @brief Auto correct a JSON structure
/// @param value The JSON value to correct
/// @param ignoreRequired Ignore the "required" keyword in hyperion schema. Default is false
/// @return The corrected JSON structure
///
QJsonObject getAutoCorrectedConfig(const QJsonObject & value, bool ignoreRequired = false);
/// ///
/// @return A list of error messages /// @return A list of error messages
@ -72,7 +83,7 @@ private:
/// @param[in] value The given value /// @param[in] value The given value
/// @param[in] schema The specified type (as json-value) /// @param[in] schema The specified type (as json-value)
/// ///
void checkType(const QJsonValue & value, const QJsonValue & schema); void checkType(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue);
/// ///
/// Checks is required properties of an json-object exist and if all properties are of the /// Checks is required properties of an json-object exist and if all properties are of the
@ -101,7 +112,7 @@ private:
/// @param[in] value The given value /// @param[in] value The given value
/// @param[in] schema The minimum value (as json-value) /// @param[in] schema The minimum value (as json-value)
/// ///
void checkMinimum(const QJsonValue & value, const QJsonValue & schema); void checkMinimum(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue);
/// ///
/// Checks if the given value is smaller or equal to the specified value. If this is not the /// Checks if the given value is smaller or equal to the specified value. If this is not the
@ -110,7 +121,7 @@ private:
/// @param[in] value The given value /// @param[in] value The given value
/// @param[in] schema The maximum value (as json-value) /// @param[in] schema The maximum value (as json-value)
/// ///
void checkMaximum(const QJsonValue & value, const QJsonValue & schema); void checkMaximum(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue);
/// ///
/// Checks if the given value is hugher than the specified value. If this is the /// Checks if the given value is hugher than the specified value. If this is the
@ -119,7 +130,7 @@ private:
/// @param value The given value /// @param value The given value
/// @param schema The minimum size specification (as json-value) /// @param schema The minimum size specification (as json-value)
/// ///
void checkMinLength(const QJsonValue & value, const QJsonValue & schema); void checkMinLength(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue);
/// ///
/// Checks if the given value is smaller than the specified value. If this is the /// Checks if the given value is smaller than the specified value. If this is the
@ -128,7 +139,7 @@ private:
/// @param value The given value /// @param value The given value
/// @param schema The maximum size specification (as json-value) /// @param schema The maximum size specification (as json-value)
/// ///
void checkMaxLength(const QJsonValue & value, const QJsonValue & schema); void checkMaxLength(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue);
/// ///
/// Validates all the items of an array. /// Validates all the items of an array.
@ -145,7 +156,7 @@ private:
/// @param value The json-array /// @param value The json-array
/// @param schema The minimum size specification (as json-value) /// @param schema The minimum size specification (as json-value)
/// ///
void checkMinItems(const QJsonValue & value, const QJsonValue & schema); void checkMinItems(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue);
/// ///
/// Checks if a given array has at most a maximum number of items. If this is not the case /// Checks if a given array has at most a maximum number of items. If this is not the case
@ -154,7 +165,7 @@ private:
/// @param value The json-array /// @param value The json-array
/// @param schema The maximum size specification (as json-value) /// @param schema The maximum size specification (as json-value)
/// ///
void checkMaxItems(const QJsonValue & value, const QJsonValue & schema); void checkMaxItems(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue);
/// ///
/// Checks if a given array contains only unique items. If this is not the case /// Checks if a given array contains only unique items. If this is not the case
@ -172,17 +183,23 @@ private:
/// @param value The enum value /// @param value The enum value
/// @param schema The enum schema definition /// @param schema The enum schema definition
/// ///
void checkEnum(const QJsonValue & value, const QJsonValue & schema); void checkEnum(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue);
private: private:
/// The schema of the entire json-configuration /// The schema of the entire json-configuration
QJsonObject _qSchema; QJsonObject _qSchema;
/// ignore the required value in json schema /// ignore the required value in json schema
bool _ignoreRequired; bool _ignoreRequired;
/// Auto correction variable
QString _correct;
/// The auto corrected json-configuration
QJsonObject _autoCorrected;
/// The current location into a json-configuration structure being checked /// The current location into a json-configuration structure being checked
QStringList _currentPath; QStringList _currentPath;
/// The result messages collected during the schema verification /// The result messages collected during the schema verification
QStringList _messages; QStringList _messages;
/// Flag indicating an error occured during validation /// Flag indicating an error occured during validation
bool _error; bool _error;
/// Flag indicating an schema error occured during validation
bool _schemaError;
}; };

View File

@ -0,0 +1,273 @@
#pragma once
// QT include
#include <QString>
#include <QStringList>
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonArray>
class QJsonUtils
{
public:
static void modify(QJsonObject& value, QStringList path, const QJsonValue& newValue = QJsonValue::Null, QString propertyName = "")
{
QJsonObject result;
if (!path.isEmpty())
{
if (path.first() == "[root]")
path.removeFirst();
for (QStringList::iterator it = path.begin(); it != path.end(); ++it)
{
QString current = *it;
if (current.left(1) == ".")
*it = current.mid(1, current.size()-1);
}
if (!value.isEmpty())
modifyValue(value, result, path, newValue, propertyName);
else if (newValue != QJsonValue::Null && !propertyName.isEmpty())
result[propertyName] = newValue;
}
value = result;
}
static QJsonValue create(QJsonValue schema, bool ignoreRequired = false)
{
return createValue(schema, ignoreRequired);
}
private:
static QJsonValue createValue(QJsonValue schema, bool ignoreRequired)
{
QJsonObject result;
QJsonObject obj = schema.toObject();
if (obj.find("type") != obj.end() && obj.find("type").value().isString())
{
QJsonValue ret = QJsonValue::Null;
if (obj.find("type").value().toString() == "object" && ( obj.find("required").value().toBool() || ignoreRequired ) )
ret = createValue(obj["properties"], ignoreRequired);
else if (obj.find("type").value().toString() == "array" && ( obj.find("required").value().toBool() || ignoreRequired ) )
{
QJsonArray array;
if (obj.find("default") != obj.end())
ret = obj.find("default").value();
else
{
ret = createValue(obj["items"], ignoreRequired);
if (!ret.toObject().isEmpty())
array.append(ret);
ret = array;
}
}
else if ( obj.find("required").value().toBool() || ignoreRequired )
if (obj.find("default") != obj.end())
ret = obj.find("default").value();
return ret;
}
else
{
for (QJsonObject::const_iterator i = obj.begin(); i != obj.end(); ++i)
{
QString attribute = i.key();
const QJsonValue & attributeValue = *i;
QJsonValue subValue = obj[attribute];
if (attributeValue.toObject().find("type") != attributeValue.toObject().end())
{
if (attributeValue.toObject().find("type").value().toString() == "object" && ( attributeValue.toObject().find("required").value().toBool() || ignoreRequired ) )
{
if (obj.contains("properties"))
result[attribute] = createValue(obj["properties"], ignoreRequired);
else
result[attribute] = createValue(subValue, ignoreRequired);
}
else if (attributeValue.toObject().find("type").value().toString() == "array" && ( attributeValue.toObject().find("required").value().toBool() || ignoreRequired ) )
{
QJsonArray array;
if (attributeValue.toObject().find("default") != attributeValue.toObject().end())
result[attribute] = attributeValue.toObject().find("default").value();
else
{
QJsonValue retEmpty;
retEmpty = createValue(attributeValue.toObject()["items"], ignoreRequired);
if (!retEmpty.toObject().isEmpty())
array.append(retEmpty);
result[attribute] = array;
}
}
else if ( attributeValue.toObject().find("required").value().toBool() || ignoreRequired )
{
if (attributeValue.toObject().find("default") != attributeValue.toObject().end())
result[attribute] = attributeValue.toObject().find("default").value();
else
result[attribute] = QJsonValue::Null;
}
}
}
}
return result;
}
static void modifyValue(QJsonValue source, QJsonObject& target, QStringList path, const QJsonValue& newValue, QString& property)
{
QJsonObject obj = source.toObject();
if (!obj.isEmpty())
{
for (QJsonObject::iterator i = obj.begin(); i != obj.end(); ++i)
{
QString propertyName = i.key();
QJsonValue subValue = obj[propertyName];
if (subValue.isObject())
{
if (!path.isEmpty())
{
if (propertyName == path.first())
{
path.removeFirst();
if (!path.isEmpty())
{
QJsonObject obj;
modifyValue(subValue, obj, path, newValue, property);
subValue = obj;
}
else if (newValue != QJsonValue::Null)
subValue = newValue;
else
continue;
if (!subValue.toObject().isEmpty())
target[propertyName] = subValue;
}
else
{
if (path.first() == property && newValue != QJsonValue::Null)
{
target[property] = newValue;
property = QString();
}
target[propertyName] = subValue;
}
}
else
if (!subValue.toObject().isEmpty())
target[propertyName] = subValue;
}
else if (subValue.isArray())
{
if (!path.isEmpty())
{
if (propertyName == path.first())
{
path.removeFirst();
int arrayLevel = -1;
if (!path.isEmpty())
{
if ((path.first().left(1) == "[") && (path.first().right(1) == "]"))
{
arrayLevel = path.first().mid(1, path.first().size()-2).toInt();
path.removeFirst();
}
}
QJsonArray array = subValue.toArray();
QJsonArray json_array;
for (QJsonArray::iterator i = array.begin(); i != array.end(); ++i)
{
if (!path.isEmpty())
{
QJsonObject arr;
modifyValue(*i, arr, path, newValue, property);
subValue = arr;
}
else if (newValue != QJsonValue::Null)
subValue = newValue;
else
continue;
if (!subValue.toObject().isEmpty())
json_array.append(subValue);
else if (newValue != QJsonValue::Null && arrayLevel != -1)
json_array.append( (i - array.begin() == arrayLevel) ? subValue : *i );
}
if (!json_array.isEmpty())
target[propertyName] = json_array;
else if (newValue != QJsonValue::Null && arrayLevel == -1)
target[propertyName] = newValue;
}
else
{
if (path.first() == property && newValue != QJsonValue::Null)
{
target[property] = newValue;
property = QString();
}
target[propertyName] = subValue;
}
}
else
if (!subValue.toArray().isEmpty())
target[propertyName] = subValue;
}
else
{
if (!path.isEmpty())
{
if (propertyName == path.first())
{
path.removeFirst();
if (path.isEmpty())
{
if (newValue != QJsonValue::Null && property.isEmpty())
subValue = newValue;
else
continue;
}
target[propertyName] = subValue;
}
else
{
if (path.first() == property && newValue != QJsonValue::Null)
{
target[property] = newValue;
property = QString();
}
target[propertyName] = subValue;
}
}
else
target[propertyName] = subValue;
}
}
}
else if (newValue != QJsonValue::Null && !property.isEmpty())
{
target[property] = newValue;
property = QString();
}
}
};

View File

@ -150,7 +150,7 @@ bool EffectEngine::loadEffectDefinition(const QString &path, const QString &effe
QJsonSchemaChecker schemaChecker; QJsonSchemaChecker schemaChecker;
schemaChecker.setSchema(configSchema.object()); schemaChecker.setSchema(configSchema.object());
if (!schemaChecker.validate(configEffect.object())) if (!schemaChecker.validate(configEffect.object()).first)
{ {
const QStringList & errors = schemaChecker.getMessages(); const QStringList & errors = schemaChecker.getMessages();
foreach (auto & error, errors) foreach (auto & error, errors)

View File

@ -5,9 +5,84 @@
{ {
"general" : "general" :
{ {
"type" : "object", "$ref": "schema-general.json"
"required" : true },
"logger" :
{
"$ref": "schema-logger.json"
},
"device" :
{
"$ref": "schema-device.json"
},
"color" :
{
"$ref": "schema-color.json"
},
"smoothing":
{
"$ref": "schema-smoothing.json"
},
"grabberV4L2" :
{
"$ref": "schema-grabberV4L2.json"
},
"framegrabber" :
{
"$ref": "schema-framegrabber.json"
},
"blackborderdetector" :
{
"$ref": "schema-blackborderdetector.json"
},
"kodiVideoChecker" :
{
"$ref": "schema-kodiVideoChecker.json"
},
"foregroundEffect" :
{
"$ref": "schema-foregroundEffect.json"
},
"backgroundEffect" :
{
"$ref": "schema-backgroundEffect.json"
},
"forwarder" :
{
"$ref": "schema-forwarder.json"
},
"jsonServer" :
{
"$ref": "schema-jsonServer.json"
},
"protoServer" :
{
"$ref": "schema-protoServer.json"
},
"boblightServer" :
{
"$ref": "schema-boblightServer.json"
},
"udpListener" :
{
"$ref": "schema-udpListener.json"
},
"webConfig" :
{
"$ref": "schema-webConfig.json"
},
"effects" :
{
"$ref": "schema-effects.json"
},
"ledConfig":
{
"$ref": "schema-ledConfig.json"
},
"leds":
{
"$ref": "schema-leds.json"
} }
}, },
"additionalProperties" : true "additionalProperties" : false
} }

View File

@ -1,8 +1,26 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file alias="hyperion-schema">hyperion.schema.json</file> <file alias="hyperion-schema">hyperion.schema.json</file>
<file alias="hyperion-schema-1">schemas/hyperion.schema-1.json</file> <file alias="hyperion_default.config">../../config/hyperion.config.json.default</file>
<file alias="hyperion-schema-2">schemas/hyperion.schema-2.json</file> <file alias="schema-general.json">schema/schema-general.json</file>
<file alias="hyperion_default.config">../../config/hyperion.config.json.default</file> <file alias="schema-logger.json">schema/schema-logger.json</file>
</qresource> <file alias="schema-device.json">schema/schema-device.json</file>
<file alias="schema-color.json">schema/schema-color.json</file>
<file alias="schema-smoothing.json">schema/schema-smoothing.json</file>
<file alias="schema-grabberV4L2.json">schema/schema-grabberV4L2.json</file>
<file alias="schema-framegrabber.json">schema/schema-framegrabber.json</file>
<file alias="schema-blackborderdetector.json">schema/schema-blackborderdetector.json</file>
<file alias="schema-kodiVideoChecker.json">schema/schema-kodiVideoChecker.json</file>
<file alias="schema-foregroundEffect.json">schema/schema-foregroundEffect.json</file>
<file alias="schema-backgroundEffect.json">schema/schema-backgroundEffect.json</file>
<file alias="schema-forwarder.json">schema/schema-forwarder.json</file>
<file alias="schema-jsonServer.json">schema/schema-jsonServer.json</file>
<file alias="schema-protoServer.json">schema/schema-protoServer.json</file>
<file alias="schema-boblightServer.json">schema/schema-boblightServer.json</file>
<file alias="schema-udpListener.json">schema/schema-udpListener.json</file>
<file alias="schema-webConfig.json">schema/schema-webConfig.json</file>
<file alias="schema-effects.json">schema/schema-effects.json</file>
<file alias="schema-ledConfig.json">schema/schema-ledConfig.json</file>
<file alias="schema-leds.json">schema/schema-leds.json</file>
</qresource>
</RCC> </RCC>

View File

@ -0,0 +1,48 @@
{
"type" : "object",
"title" : "edt_conf_bge_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : true,
"propertyOrder" : 1
},
"type" :
{
"type" : "string",
"title" : "edt_conf_fge_type_title",
"enum" : ["color", "effect"],
"default" : "effect",
"options" : {
"enum_titles" : ["edt_conf_enum_color", "edt_conf_enum_effect"]
},
"propertyOrder" : 2
},
"color" :
{
"type" : "array",
"format" : "colorpicker",
"title" : "edt_conf_fge_color_title",
"default" : [255,138,0],
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"minItems" : 3,
"maxItems" : 3,
"propertyOrder" : 3
},
"effect" :
{
"type" : "string",
"format" : "select",
"title" : "edt_conf_fge_effect_title",
"propertyOrder" : 4
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,72 @@
{
"type" : "object",
"title" : "edt_conf_bb_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : true,
"propertyOrder" : 1
},
"threshold" :
{
"type" : "integer",
"title" : "edt_conf_bb_threshold_title",
"minimum" : 0,
"maximum" : 100,
"default" : 5,
"append" : "edt_append_percent",
"propertyOrder" : 2
},
"unknownFrameCnt" :
{
"type" : "integer",
"title" : "edt_conf_bb_unknownFrameCnt_title",
"minimum" : 0,
"default" : 600,
"access" : "expert",
"propertyOrder" : 3
},
"borderFrameCnt" :
{
"type" : "integer",
"title" : "edt_conf_bb_borderFrameCnt_title",
"minimum" : 0,
"default" : 50,
"access" : "expert",
"propertyOrder" : 4
},
"maxInconsistentCnt" :
{
"type" : "integer",
"title" : "edt_conf_bb_maxInconsistentCnt_title",
"minimum" : 0,
"default" : 10,
"access" : "expert",
"propertyOrder" : 5
},
"blurRemoveCnt" :
{
"type" : "integer",
"title" : "edt_conf_bb_blurRemoveCnt_title",
"minimum" : 0,
"default" : 1,
"access" : "expert",
"propertyOrder" : 6
},
"mode" :
{
"type" : "string",
"title": "edt_conf_bb_mode_title",
"enum" : ["default", "classic", "osd"],
"default" : "default",
"options" : {
"enum_titles" : ["edt_conf_enum_bbdefault", "edt_conf_enum_bbclassic", "edt_conf_enum_bbosd"]
},
"propertyOrder" : 7
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,33 @@
{
"type" : "object",
"title" : "edt_conf_bobls_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : false,
"propertyOrder" : 1
},
"port" :
{
"type" : "integer",
"required" : true,
"title" : "edt_conf_general_port_title",
"minimum" : 0,
"maximum" : 65535,
"propertyOrder" : 2
},
"priority" :
{
"type" : "integer",
"title" : "edt_conf_general_priority_title",
"minimum" : 100,
"maximum" : 254,
"default" : 201,
"propertyOrder" : 3
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,257 @@
{
"type":"object",
"title" : "edt_conf_color_heading_title",
"required" : true,
"properties":
{
"imageToLedMappingType" :
{
"type" : "string",
"required" : true,
"title" : "edt_conf_color_imageToLedMappingType_title",
"enum" : ["multicolor_mean", "unicolor_mean"],
"default" : "multicolor_mean",
"options" : {
"enum_titles" : ["edt_conf_enum_multicolor_mean", "edt_conf_enum_unicolor_mean"]
},
"propertyOrder" : 1
},
"channelAdjustment" :
{
"type" : "array",
"title" : "edt_conf_color_channelAdjustment_header_title",
"required" : true,
"propertyOrder" : 3,
"items" :
{
"type" : "object",
"required" : true,
"title" : "edt_conf_color_channelAdjustment_header_itemtitle",
"properties" :
{
"id" :
{
"type" : "string",
"title" : "edt_conf_color_id_title",
"required" : true,
"default" : "A userdefined name",
"propertyOrder" : 1
},
"leds" :
{
"type" : "string",
"title" : "edt_conf_color_leds_title",
"required" : true,
"default" : "*",
"propertyOrder" : 2
},
"black" :
{
"type" : "array",
"title" : "edt_conf_color_black_title",
"format" : "colorpicker",
"required" : true,
"default": [0,0,0],
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"minItems" : 3,
"maxItems" : 3,
"propertyOrder" : 3
},
"white" :
{
"type" : "array",
"title" : "edt_conf_color_white_title",
"format" : "colorpicker",
"required" : true,
"default": [255,255,255],
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"minItems" : 3,
"maxItems" : 3,
"propertyOrder" : 4
},
"red" :
{
"type" : "array",
"title" : "edt_conf_color_red_title",
"format" : "colorpicker",
"required" : true,
"default": [255,0,0],
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"minItems" : 3,
"maxItems" : 3,
"propertyOrder" : 5
},
"green" :
{
"type" : "array",
"title" : "edt_conf_color_green_title",
"format" : "colorpicker",
"required" : true,
"default": [0,255,0],
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"minItems" : 3,
"maxItems" : 3,
"propertyOrder" : 6
},
"blue" :
{
"type" : "array",
"title" : "edt_conf_color_blue_title",
"format" : "colorpicker",
"required" : true,
"default": [0,0,255],
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"minItems" : 3,
"maxItems" : 3,
"propertyOrder" : 7
},
"cyan" :
{
"type" : "array",
"title" : "edt_conf_color_cyan_title",
"format" : "colorpicker",
"required" : true,
"default": [0,255,255],
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"minItems" : 3,
"maxItems" : 3,
"propertyOrder" : 8
},
"magenta" :
{
"type" : "array",
"title" : "edt_conf_color_magenta_title",
"format" : "colorpicker",
"required" : true,
"default": [255,0,255],
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"minItems" : 3,
"maxItems" : 3,
"propertyOrder" : 9
},
"yellow" :
{
"type" : "array",
"title" : "edt_conf_color_yellow_title",
"format" : "colorpicker",
"required" : true,
"default": [255,255,0],
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"minItems" : 3,
"maxItems" : 3,
"propertyOrder" : 10
},
"backlightThreshold" :
{
"type" : "integer",
"title" : "edt_conf_color_backlightThreshold_title",
"required" : true,
"minimum" : 0,
"maximum": 100,
"default" : 0,
"append" : "edt_append_percent",
"propertyOrder" : 11
},
"backlightColored" :
{
"type" : "boolean",
"title" : "edt_conf_color_backlightColored_title",
"required" : true,
"default" : false,
"propertyOrder" : 12
},
"brightness" :
{
"type" : "integer",
"title" : "edt_conf_color_brightness_title",
"required" : true,
"minimum" : 0,
"maximum": 100,
"default" : 100,
"append" : "edt_append_percent",
"propertyOrder" : 13
},
"brightnessCompensation" :
{
"type" : "integer",
"title" : "edt_conf_color_brightnessComp_title",
"required" : true,
"minimum" : 0,
"maximum": 100,
"default" : 90,
"append" : "edt_append_percent",
"access" : "advanced",
"propertyOrder" : 14
},
"gammaRed" :
{
"type" : "number",
"title" : "edt_conf_color_gammaRed_title",
"required" : true,
"minimum" : 0.1,
"maximum": 100.0,
"default" : 1.5,
"step" : 0.1,
"propertyOrder" : 15
},
"gammaGreen" :
{
"type" : "number",
"title" : "edt_conf_color_gammaGreen_title",
"required" : true,
"minimum" : 0.1,
"maximum": 100.0,
"default" : 1.5,
"step" : 0.1,
"propertyOrder" : 16
},
"gammaBlue" :
{
"type" : "number",
"title" : "edt_conf_color_gammaBlue_title",
"required" : true,
"minimum" : 0.1,
"maximum": 100.0,
"default" : 1.5,
"step" : 0.1,
"propertyOrder" : 17
}
},
"additionalProperties" : false
}
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,41 @@
{
"type" : "object",
"title" : "edt_dev_general_heading_title",
"required" : true,
"defaultProperties": ["ledCount","colorOrder","rewriteTime","minimumWriteTime"],
"properties" :
{
"type" :
{
"type" : "string"
},
"ledCount" :
{
"type" : "integer",
"minimum" : 0,
"title" : "edt_dev_general_ledCount_title",
"propertyOrder" : 2
},
"colorOrder" :
{
"type" : "string",
"title" : "edt_dev_general_colorOrder_title",
"enum" : ["rgb", "bgr", "rbg", "brg", "gbr", "grb"],
"default" : "rgb",
"options" : {
"enum_titles" : ["edt_conf_enum_rgb", "edt_conf_enum_bgr", "edt_conf_enum_rbg", "edt_conf_enum_brg", "edt_conf_enum_gbr", "edt_conf_enum_grb"]
},
"propertyOrder" : 3
},
"rewriteTime": {
"type": "integer",
"title":"edt_dev_general_rewriteTime_title",
"default": 1000,
"append" : "edt_append_ms",
"minimum": 0,
"access" : "expert",
"propertyOrder" : 4
}
},
"additionalProperties" : true
}

View File

@ -0,0 +1,29 @@
{
"type" : "object",
"title" : "edt_conf_effp_heading_title",
"properties" :
{
"paths" :
{
"type" : "array",
"title" : "edt_conf_effp_paths_title",
"default" : ["../custom-effects"],
"items" : {
"type": "string",
"title" : "edt_conf_effp_paths_itemtitle"
},
"propertyOrder" : 1
},
"disable" :
{
"type" : "array",
"title" : "edt_conf_effp_disable_title",
"items" : {
"type": "string",
"title" : "edt_conf_effp_disable_itemtitle"
},
"propertyOrder" : 2
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,57 @@
{
"type" : "object",
"title" : "edt_conf_fge_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : true,
"propertyOrder" : 1
},
"type" :
{
"type" : "string",
"title" : "edt_conf_fge_type_title",
"enum" : ["color", "effect"],
"default" : "effect",
"options" : {
"enum_titles" : ["edt_conf_enum_color", "edt_conf_enum_effect"]
},
"propertyOrder" : 2
},
"color" :
{
"type" : "array",
"format" : "colorpicker",
"title" : "edt_conf_fge_color_title",
"default" : [255,0,0],
"items" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"minItems" : 3,
"maxItems" : 3,
"propertyOrder" : 3
},
"effect" :
{
"type" : "string",
"format" : "select",
"title" : "edt_conf_fge_effect_title",
"propertyOrder" : 4
},
"duration_ms" :
{
"type" : "integer",
"title" : "edt_conf_fge_duration_ms_title",
"default" : 3000,
"minimum" : 100,
"append" : "edt_append_ms",
"propertyOrder" : 5
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,36 @@
{
"type" : "object",
"title" : "edt_conf_fw_heading_title",
"required" : true,
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"propertyOrder" : 1
},
"json" :
{
"type" : "array",
"title" : "edt_conf_fw_json_title",
"required" : true,
"items" : {
"type": "string",
"title" : "edt_conf_fw_json_itemtitle"
},
"propertyOrder" : 2
},
"proto" :
{
"type" : "array",
"title" : "edt_conf_fw_proto_title",
"items" : {
"type": "string",
"title" : "edt_conf_fw_proto_itemtitle"
},
"propertyOrder" : 3
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,132 @@
{
"type" : "object",
"title" : "edt_conf_fg_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : true,
"propertyOrder" : 1
},
"type" :
{
"type" : "string",
"title" : "edt_conf_fg_type_title",
"enum" : ["auto","dispmanx","amlogic","x11","framebuffer"],
"default" : "auto",
"propertyOrder" : 2
},
"width" :
{
"type" : "integer",
"title" : "edt_conf_fg_width_title",
"minimum" : 10,
"default" : 80,
"append" : "edt_append_pixel",
"propertyOrder" : 3
},
"height" :
{
"type" : "integer",
"title" : "edt_conf_fg_height_title",
"minimum" : 10,
"default" : 45,
"append" : "edt_append_pixel",
"propertyOrder" : 3
},
"frequency_Hz" :
{
"type" : "integer",
"title" : "edt_conf_fg_frequency_Hz_title",
"minimum" : 1,
"default" : 10,
"append" : "edt_append_hz",
"propertyOrder" : 4
},
"priority" :
{
"type" : "integer",
"title" : "edt_conf_general_priority_title",
"minimum" : 100,
"maximum" : 254,
"default" : 250,
"propertyOrder" : 5
},
"cropLeft" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_cropLeft_title",
"minimum" : 0,
"default" : 0,
"append" : "edt_append_pixel",
"propertyOrder" : 6
},
"cropRight" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_cropRight_title",
"minimum" : 0,
"default" : 0,
"append" : "edt_append_pixel",
"propertyOrder" : 7
},
"cropTop" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_cropTop_title",
"minimum" : 0,
"default" : 0,
"append" : "edt_append_pixel",
"propertyOrder" : 8
},
"cropBottom" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_cropBottom_title",
"minimum" : 0,
"default" : 0,
"append" : "edt_append_pixel",
"propertyOrder" : 9
},
"useXGetImage" :
{
"type" : "boolean",
"title" : "edt_conf_fg_useXGetImage_title",
"default" : false,
"propertyOrder" : 10
},
"horizontalPixelDecimation" :
{
"type" : "integer",
"title" : "edt_conf_fg_horizontalPixelDecimation_title",
"minimum" : 0,
"default" : 8,
"propertyOrder" : 11
},
"verticalPixelDecimation" :
{
"type" : "integer",
"title" : "edt_conf_fg_verticalPixelDecimation_title",
"minimum" : 0,
"default" : 8,
"propertyOrder" : 12
},
"device" :
{
"type" : "string",
"title" : "edt_conf_fg_device_title",
"default" : "/dev/fb0",
"propertyOrder" : 13
},
"display" :
{
"type" : "integer",
"title" : "edt_conf_fg_display_title",
"minimum" : 0,
"propertyOrder" : 14
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,34 @@
{
"type" : "object",
"title" : "edt_conf_gen_heading_title",
"required" : true,
"properties" :
{
"name" :
{
"type" : "string",
"title" : "edt_conf_gen_name_title",
"default" : "My Hyperion Config",
"minLength" : 4,
"maxLength" : 20,
"required" : true,
"propertyOrder" : 1
},
"showOptHelp" :
{
"type" : "boolean",
"title" : "edt_conf_gen_showOptHelp_title",
"default" : true,
"required" : true,
"propertyOrder" : 2
},
"configVersion" :
{
"type" : "integer",
"default" : 2,
"minimum" : 1,
"access" : "system",
"required" : true
}
}
}

View File

@ -0,0 +1,226 @@
{
"type":"array",
"title" : "edt_conf_v4l2_heading_title",
"minItems": 1,
"maxItems": 2,
"items":
{
"type" : "object",
"title" : "edt_conf_v4l2_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : false,
"propertyOrder" : 1
},
"device" :
{
"type" : "string",
"title" : "edt_conf_v4l2_device_title",
"default" : "auto",
"propertyOrder" : 2
},
"input" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_input_title",
"minimum" : 0,
"default" : 0,
"propertyOrder" : 3
},
"standard" :
{
"type" : "string",
"title" : "edt_conf_v4l2_standard_title",
"enum" : ["PAL","NTSC"],
"default" : "PAL",
"options" : {
"enum_titles" : ["edt_conf_enum_PAL", "edt_conf_enum_NTSC"]
},
"propertyOrder" : 4
},
"width" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_width_title",
"minimum" : -1,
"default" : -1,
"append" : "edt_append_pixel",
"propertyOrder" : 5
},
"height" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_height_title",
"minimum" : -1,
"default" : -1,
"append" : "edt_append_pixel",
"propertyOrder" : 6
},
"frameDecimation" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_frameDecimation_title",
"minimum" : 0,
"default" : 2,
"propertyOrder" : 7
},
"sizeDecimation" :
{
"type" : "integer",
"title" : "Size decimation",
"minimum" : 0,
"default" : 6,
"propertyOrder" : 8
},
"priority" :
{
"type" : "integer",
"minimum" : 100,
"maximum" : 253,
"title" : "edt_conf_general_priority_title",
"default" : 240,
"propertyOrder" : 9
},
"mode" :
{
"type" : "string",
"title" : "edt_conf_v4l2_mode_title",
"enum" : ["2D","3DSBS","3DTAB"],
"default" : "2D",
"propertyOrder" : 10
},
"useKodiChecker" :
{
"type" : "boolean",
"title" : "edt_conf_v4l2_useKodiChecker_title",
"default" : false,
"propertyOrder" : 11
},
"cropLeft" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_cropLeft_title",
"minimum" : 0,
"default" : 0,
"append" : "edt_append_pixel",
"propertyOrder" : 12
},
"cropRight" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_cropRight_title",
"minimum" : 0,
"default" : 0,
"append" : "edt_append_pixel",
"propertyOrder" : 13
},
"cropTop" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_cropTop_title",
"minimum" : 0,
"default" : 0,
"append" : "edt_append_pixel",
"propertyOrder" : 14
},
"cropBottom" :
{
"type" : "integer",
"title" : "edt_conf_v4l2_cropBottom_title",
"minimum" : 0,
"default" : 0,
"append" : "edt_append_pixel",
"propertyOrder" : 15
},
"signalDetection" :
{
"type" : "boolean",
"title" : "edt_conf_v4l2_signalDetection_title",
"default" : true,
"propertyOrder" : 16
},
"redSignalThreshold" :
{
"type" : "number",
"title" : "edt_conf_v4l2_redSignalThreshold_title",
"minimum" : 0.0,
"maximum" : 1.0,
"default" : 0.1,
"step" : 0.005,
"append" : "edt_append_percent",
"propertyOrder" : 17
},
"greenSignalThreshold" :
{
"type" : "number",
"title" : "edt_conf_v4l2_greenSignalThreshold_title",
"minimum" : 0.0,
"maximum" : 1.0,
"default" : 0.1,
"step" : 0.025,
"append" : "edt_append_percent",
"propertyOrder" : 18
},
"blueSignalThreshold" :
{
"type" : "number",
"title" : "edt_conf_v4l2_blueSignalThreshold_title",
"minimum" : 0.0,
"maximum" : 1.0,
"default" : 0.1,
"step" : 0.005,
"append" : "edt_append_percent",
"propertyOrder" : 19
},
"signalDetectionVerticalOffsetMin" :
{
"type" : "number",
"title" : "edt_conf_v4l2_signalDetectionVerticalOffsetMin_title",
"minimum" : 0.0,
"maximum" : 1.0,
"default" : 0.1,
"step" : 0.005,
"append" : "edt_append_percent",
"propertyOrder" : 20
},
"signalDetectionVerticalOffsetMax" :
{
"type" : "number",
"title" : "edt_conf_v4l2_signalDetectionVerticalOffsetMax_title",
"minimum" : 0.0,
"maximum" : 1.0,
"default" : 0.1,
"step" : 0.005,
"append" : "edt_append_percent",
"propertyOrder" : 21
},
"signalDetectionHorizontalOffsetMin" :
{
"type" : "number",
"title" : "edt_conf_v4l2_signalDetectionHorizontalOffsetMin_title",
"minimum" : 0.0,
"maximum" : 1.0,
"default" : 0.1,
"step" : 0.005,
"append" : "edt_append_percent",
"propertyOrder" : 22
},
"signalDetectionHorizontalOffsetMax" :
{
"type" : "number",
"title" : "edt_conf_v4l2_signalDetectionHorizontalOffsetMax_title",
"minimum" : 0.0,
"maximum" : 1.0,
"default" : 0.1,
"step" : 0.005,
"append" : "edt_append_percent",
"propertyOrder" : 23
}
},
"additionalProperties" : false
}
}

View File

@ -0,0 +1,18 @@
{
"type" : "object",
"required" : true,
"title" : "edt_conf_js_heading_title",
"properties" :
{
"port" :
{
"type" : "integer",
"required" : true,
"title" : "edt_conf_general_port_title",
"minimum" : 0,
"maximum" : 65535,
"default" : 19444
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,80 @@
{
"type" : "object",
"title" : "edt_conf_kodic_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : false,
"propertyOrder" : 1
},
"kodiAddress" :
{
"type" : "string",
"title" : "edt_conf_kodic_kodiAddress_title",
"default" : "127.0.0.1",
"propertyOrder" : 2
},
"kodiTcpPort" :
{
"type" : "integer",
"title" : "edt_conf_kodic_kodiTcpPort_title",
"minimum" : 0,
"maximum" : 65535,
"default" : 9090,
"propertyOrder" : 3
},
"grabVideo" :
{
"type" : "boolean",
"title" : "edt_conf_kodic_grabVideo_title",
"default" : true,
"propertyOrder" : 4
},
"grabPictures" :
{
"type" : "boolean",
"title" : "edt_conf_kodic_grabPictures_title",
"default" : true,
"propertyOrder" : 5
},
"grabAudio" :
{
"type" : "boolean",
"title" : "edt_conf_kodic_grabAudio_title",
"default" : true,
"propertyOrder" : 6
},
"grabMenu" :
{
"type" : "boolean",
"title" : "edt_conf_kodic_grabMenu_title",
"default" : false,
"propertyOrder" : 7
},
"grabPause" :
{
"type" : "boolean",
"title" : "edt_conf_kodic_grabPause_title",
"default" : false,
"propertyOrder" : 8
},
"grabScreensaver" :
{
"type" : "boolean",
"title" : "edt_conf_kodic_grabScreensaver_title",
"default" : false,
"propertyOrder" : 9
},
"enable3DDetection" :
{
"type" : "boolean",
"title" : "edt_conf_kodic_enable3DDetection_title",
"default" : false,
"propertyOrder" : 10
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,80 @@
{
"type" : "object",
"properties" :
{
"top" :
{
"type" : "integer",
"minimum" : 0,
"default" : 8
},
"bottom" :
{
"type" : "integer",
"minimum" : 0,
"default" : 8
},
"left" :
{
"type" : "integer",
"minimum" : 0,
"default" : 5
},
"right" :
{
"type" : "integer",
"minimum" : 0,
"default" : 5
},
"glength" :
{
"type" : "integer",
"minimum" : 0,
"default" : 0
},
"gpos" :
{
"type" : "integer",
"minimum" : 0,
"default" : 0
},
"position" :
{
"type" : "integer",
"default" : 0
},
"reverse" :
{
"type" : "boolean",
"default" : false
},
"hdepth" :
{
"type" : "integer",
"minimum" : 1,
"maximum" : 100,
"default" : 8
},
"vdepth" :
{
"type" : "integer",
"minimum" : 1,
"maximum" : 100,
"default" : 5
},
"overlap" :
{
"type" : "integer",
"minimum" : 0,
"default" : 0
},
"edgegap" :
{
"type" : "integer",
"minimum" : 0,
"maximum" : 50,
"default" : 0
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,70 @@
{
"type":"array",
"required":true,
"items":
{
"type":"object",
"properties":
{
"index":
{
"type":"integer",
"required":true
},
"clone":
{
"type":"integer"
},
"hscan":
{
"type":"object",
"properties":
{
"minimum":
{
"type":"number",
"minimum" : 0,
"maximum" : 1,
"required":true
},
"maximum":
{
"type":"number",
"minimum" : 0,
"maximum" : 1,
"required":true
}
},
"additionalProperties" : false
},
"vscan":
{
"type":"object",
"properties":
{
"minimum":
{
"type":"number",
"minimum" : 0,
"maximum" : 1,
"required":true
},
"maximum":
{
"type":"number",
"minimum" : 0,
"maximum" : 1,
"required":true
}
},
"additionalProperties" : false
},
"colorOrder":
{
"type": "string",
"enum" : ["rgb", "bgr", "rbg", "brg", "gbr", "grb"]
}
},
"additionalProperties" : false
}
}

View File

@ -0,0 +1,18 @@
{
"type" : "object",
"title" : "edt_conf_log_heading_title",
"properties" :
{
"level" :
{
"type" : "string",
"enum" : ["silent", "warn", "verbose", "debug"],
"title" : "edt_conf_log_level_title",
"options" : {
"enum_titles" : ["edt_conf_enum_logsilent", "edt_conf_enum_logwarn", "edt_conf_enum_logverbose", "edt_conf_enum_logdebug"]
},
"default" : "warn"
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,18 @@
{
"type" : "object",
"required" : true,
"title" : "edt_conf_ps_heading_title",
"properties" :
{
"port" :
{
"type" : "integer",
"required" : true,
"title" : "edt_conf_general_port_title",
"minimum" : 0,
"maximum" : 65535,
"default" : 19445
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,63 @@
{
"type" : "object",
"title" : "edt_conf_smooth_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : true,
"propertyOrder" : 1
},
"type" :
{
"type" : "string",
"title" : "edt_conf_smooth_type_title",
"enum" : ["linear"],
"default" : "linear",
"options" : {
"enum_titles" : ["edt_conf_enum_linear"]
},
"propertyOrder" : 2
},
"time_ms" :
{
"type" : "integer",
"title" : "edt_conf_smooth_time_ms_title",
"minimum" : 25,
"maximum": 600,
"default" : 200,
"append" : "edt_append_ms",
"propertyOrder" : 3
},
"updateFrequency" :
{
"type" : "number",
"title" : "edt_conf_smooth_updateFrequency_title",
"minimum" : 1.0,
"maximum" : 100.0,
"default" : 25.0,
"append" : "edt_append_hz",
"propertyOrder" : 4
},
"updateDelay" :
{
"type" : "integer",
"title" : "edt_conf_smooth_updateDelay_title",
"minimum" : 0,
"maximum": 2048,
"default" : 0,
"append" : "edt_append_ms",
"propertyOrder" : 5
},
"continuousOutput" :
{
"type" : "boolean",
"title" : "edt_conf_smooth_continuousOutput_title",
"default" : true,
"propertyOrder" : 6
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,56 @@
{
"type" : "object",
"title" : "edt_conf_udpl_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : false,
"propertyOrder" : 1
},
"address" :
{
"type" : "string",
"title" : "edt_conf_udpl_address_title",
"default" : "239.255.28.01",
"propertyOrder" : 2
},
"port" :
{
"type" : "integer",
"title" : "edt_conf_general_port_title",
"minimum" : 0,
"maximum" : 65535,
"default" : 2801,
"propertyOrder" : 3
},
"priority" :
{
"type" : "integer",
"title" : "edt_conf_general_priority_title",
"minimum" : 100,
"maximum" : 254,
"default" : 200,
"propertyOrder" : 4
},
"timeout" :
{
"type" : "integer",
"title" : "edt_conf_udpl_timeout_title",
"minimum" : 1000,
"default" : 10000,
"append" : "edt_append_ms",
"propertyOrder" : 5
},
"shared" :
{
"type" : "boolean",
"title" : "edt_conf_udpl_shared_title",
"default" : false,
"propertyOrder" : 6
}
},
"additionalProperties" : false
}

View File

@ -0,0 +1,33 @@
{
"type" : "object",
"title" : "edt_conf_webc_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : true,
"access" : "expert",
"propertyOrder" : 1
},
"document_root" :
{
"type" : "string",
"title" : "edt_conf_webc_docroot_title",
"access" : "expert",
"propertyOrder" : 2
},
"port" :
{
"type" : "integer",
"title" : "edt_conf_general_port_title",
"minimum" : 0,
"maximum" : 65535,
"default" : 8099,
"access" : "expert",
"propertyOrder" : 3
}
},
"additionalProperties" : false
}

View File

@ -1,386 +0,0 @@
{
"type" : "object",
"required" : true,
"properties" : {
"device" : {
"type" : "object",
"required" : true,
"properties" : {
"name" : {
"type" : "string",
"required" : true
},
"type" : {
"type" : "string",
"required" : true
},
"output" : {
"type" : "string",
"required" : true
},
"rate" : {
"type" : "integer",
"required" : true,
"minimum" : 0
},
"colorOrder" : {
"type" : "string",
"required" : false
},
"bgr-output" : {
"type" : "boolean",
"required" : false
}
},
"additionalProperties" : false
},
"color": {
"type":"object",
"required":false,
"properties": {
"hsv" : {
"type" : "object",
"required" : false,
"properties" : {
"saturationGain" : {
"type" : "number",
"required" : false,
"minimum" : 0.0
},
"valueGain" : {
"type" : "number",
"required" : false,
"minimum" : 0.0
}
},
"additionalProperties" : false
},
"hsl" : {
"type" : "object",
"required" : false,
"properties" : {
"saturationGain" : {
"type" : "number",
"required" : false,
"minimum" : 0.0
},
"luminanceGain" : {
"type" : "number",
"required" : false,
"minimum" : 0.0
},
"luminanceMinimum" : {
"type" : "number",
"required" : false,
"minimum" : 0.0
}
},
"additionalProperties" : false
},
"red": {
"type":"object",
"required":false,
"properties":{
"gamma": {
"type":"number",
"required":false
},
"blacklevel": {
"type":"number",
"required":false
},
"whitelevel": {
"type":"number",
"required":false
},
"threshold": {
"type":"number",
"required":false,
"minimum" : 0.0,
"maximum" : 1.0
}
},
"additionalProperties" : false
},
"green": {
"type":"object",
"required":false,
"properties":{
"gamma": {
"type":"number",
"required":false
},
"blacklevel": {
"type":"number",
"required":false
},
"whitelevel": {
"type":"number",
"required":false
},
"threshold": {
"type":"number",
"required":false,
"minimum" : 0.0,
"maximum" : 1.0
}
},
"additionalProperties" : false
},
"blue": {
"type":"object",
"required":false,
"properties":{
"gamma": {
"type":"number",
"required":false
},
"whitelevel": {
"type":"number",
"required":false
},
"blacklevel": {
"type":"number",
"required":false
},
"threshold": {
"type":"number",
"required":false,
"minimum" : 0.0,
"maximum" : 1.0
}
},
"additionalProperties" : false
},
"smoothing" : {
"type" : "object",
"required" : false,
"properties" : {
"type" : {
"type" : "enum",
"required" : true,
"values" : ["none", "linear"]
},
"time_ms" : {
"type" : "integer",
"required" : false,
"minimum" : 10
},
"updateFrequency" : {
"type" : "number",
"required" : false,
"minimum" : 0.001
}
},
"additionalProperties" : false
}
},
"additionalProperties" : false
},
"leds": {
"type":"array",
"required":true,
"items": {
"type":"object",
"properties": {
"index": {
"type":"integer",
"required":true
},
"hscan": {
"type":"object",
"required":true,
"properties": {
"minimum": {
"type":"number",
"required":true
},
"maximum": {
"type":"number",
"required":true
}
},
"additionalProperties" : false
},
"vscan": {
"type":"object",
"required":true,
"properties": {
"minimum": {
"type":"number",
"required":true
},
"maximum": {
"type":"number",
"required":true
}
},
"additionalProperties" : false
},
"colorOrder" : {
"type" : "string",
"required" : false
}
},
"additionalProperties" : false
}
},
"effects" :
{
"type" : "object",
"required" : false,
"properties" : {
"paths" : {
"type" : "array",
"required" : false,
"items" : {
"type" : "string"
}
}
},
"additionalProperties" : false
},
"blackborderdetector" :
{
"type" : "object",
"required" : false,
"properties" : {
"enable" : {
"type" : "boolean",
"required" : true
},
"threshold" : {
"type" : "number",
"required" : false,
"minimum" : 0.0,
"maximum" : 1.0
}
},
"additionalProperties" : false
},
"xbmcVideoChecker" :
{
"type" : "object",
"required" : false,
"properties" : {
"xbmcAddress" : {
"type" : "string",
"required" : true
},
"xbmcTcpPort" : {
"type" : "integer",
"required" : true
},
"grabVideo" : {
"type" : "boolean",
"required" : true
},
"grabPictures" : {
"type" : "boolean",
"required" : true
},
"grabAudio" : {
"type" : "boolean",
"required" : true
},
"grabMenu" : {
"type" : "boolean",
"required" : true
},
"grabPause" : {
"type" : "boolean",
"required" : false
},
"grabScreensaver" : {
"type" : "boolean",
"required" : false
},
"enable3DDetection" : {
"type" : "boolean",
"required" : false
}
},
"additionalProperties" : false
},
"bootsequence" :
{
"type" : "object",
"required" : false,
"properties" : {
"path" : {
"type" : "string",
"required" : true
},
"effect" : {
"type" : "string",
"required" : true
}
},
"additionalProperties" : false
},
"framegrabber" :
{
"type" : "object",
"required" : false,
"properties" : {
"width" : {
"type" : "integer",
"required" : true
},
"height" : {
"type" : "integer",
"required" : true
},
"frequency_Hz" : {
"type" : "integer",
"required" : true
}
},
"additionalProperties" : false
},
"jsonServer" :
{
"type" : "object",
"required" : false,
"properties" : {
"port" : {
"type" : "integer",
"required" : true,
"minimum" : 0,
"maximum" : 65535
}
},
"additionalProperties" : false
},
"protoServer" :
{
"type" : "object",
"required" : false,
"properties" : {
"port" : {
"type" : "integer",
"required" : true,
"minimum" : 0,
"maximum" : 65535
}
},
"additionalProperties" : false
},
"boblightServer" :
{
"type" : "object",
"required" : false,
"properties" : {
"port" : {
"type" : "integer",
"required" : true,
"minimum" : 0,
"maximum" : 65535
}
},
"additionalProperties" : false
}
},
"additionalProperties" : false
}

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,7 @@ SET(Utils_HEADERS
${CURRENT_HEADER_DIR}/RgbToRgbw.h ${CURRENT_HEADER_DIR}/RgbToRgbw.h
${CURRENT_HEADER_DIR}/jsonschema/QJsonFactory.h ${CURRENT_HEADER_DIR}/jsonschema/QJsonFactory.h
${CURRENT_HEADER_DIR}/jsonschema/QJsonSchemaChecker.h ${CURRENT_HEADER_DIR}/jsonschema/QJsonSchemaChecker.h
${CURRENT_HEADER_DIR}/jsonschema/QJsonUtils.h
${CURRENT_HEADER_DIR}/global_defines.h ${CURRENT_HEADER_DIR}/global_defines.h
${CURRENT_HEADER_DIR}/SysInfo.h ${CURRENT_HEADER_DIR}/SysInfo.h
) )

View File

@ -15,7 +15,6 @@
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QJsonDocument> #include <QJsonDocument>
#include <QVariantMap>
#include <QDir> #include <QDir>
#include <QImage> #include <QImage>
#include <QBuffer> #include <QBuffer>
@ -594,6 +593,7 @@ void JsonProcessor::handleServerInfoCommand(const QJsonObject&, const QString& c
ledDevices["available"] = availableLedDevices; ledDevices["available"] = availableLedDevices;
info["ledDevices"] = ledDevices; info["ledDevices"] = ledDevices;
#if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11)
// get available grabbers // get available grabbers
QJsonObject grabbers; QJsonObject grabbers;
//grabbers["active"] = ????; //grabbers["active"] = ????;
@ -605,6 +605,9 @@ void JsonProcessor::handleServerInfoCommand(const QJsonObject&, const QString& c
grabbers["available"] = availableGrabbers; grabbers["available"] = availableGrabbers;
info["grabbers"] = grabbers; info["grabbers"] = grabbers;
#else
info["grabbers"] = QString("none");
#endif
// get available components // get available components
QJsonArray component; QJsonArray component;
@ -844,45 +847,19 @@ void JsonProcessor::handleSchemaGetCommand(const QJsonObject& message, const QSt
// make sure the resources are loaded (they may be left out after static linking) // make sure the resources are loaded (they may be left out after static linking)
Q_INIT_RESOURCE(resource); Q_INIT_RESOURCE(resource);
QJsonParseError error;
// read the hyperion json schema from the resource // read the hyperion json schema from the resource
QFile schemaData(":/hyperion-schema-"+QString::number(_hyperion->getConfigVersionId())); QString schemaFile = ":/hyperion-schema";
if (!schemaData.open(QIODevice::ReadOnly)) try
{ {
std::stringstream error; schemaJson = QJsonFactory::readSchema(schemaFile);
error << "Schema not found: " << schemaData.errorString().toStdString();
throw std::runtime_error(error.str());
} }
catch(const std::runtime_error& error)
QByteArray schema = schemaData.readAll();
QJsonDocument doc = QJsonDocument::fromJson(schema, &error);
schemaData.close();
if (error.error != QJsonParseError::NoError)
{ {
// report to the user the failure and their locations in the document. throw std::runtime_error(error.what());
int errorLine(0), errorColumn(0);
for( int i=0, count=qMin( error.offset,schema.size()); i<count; ++i )
{
++errorColumn;
if(schema.at(i) == '\n' )
{
errorColumn = 0;
++errorLine;
}
}
std::stringstream sstream;
sstream << "ERROR: Json schema wrong: " << error.errorString().toStdString() << " at Line: " << errorLine << ", Column: " << errorColumn;
throw std::runtime_error(sstream.str());
} }
schemaJson = doc.object();
// collect all LED Devices // collect all LED Devices
properties = schemaJson["properties"].toObject(); properties = schemaJson["properties"].toObject();
alldevices = LedDevice::getLedDeviceSchemas(); alldevices = LedDevice::getLedDeviceSchemas();
@ -1139,7 +1116,7 @@ bool JsonProcessor::checkJson(const QJsonObject& message, const QString& schemaR
schemaChecker.setSchema(schemaJson.object()); schemaChecker.setSchema(schemaJson.object());
// check the message // check the message
if (!schemaChecker.validate(message, ignoreRequired)) if (!schemaChecker.validate(message, ignoreRequired).first)
{ {
const QStringList & errors = schemaChecker.getMessages(); const QStringList & errors = schemaChecker.getMessages();
errorMessage = "{"; errorMessage = "{";

View File

@ -5,6 +5,7 @@
// Utils-Jsonschema includes // Utils-Jsonschema includes
#include <utils/jsonschema/QJsonSchemaChecker.h> #include <utils/jsonschema/QJsonSchemaChecker.h>
#include <utils/jsonschema/QJsonUtils.h>
QJsonSchemaChecker::QJsonSchemaChecker() QJsonSchemaChecker::QJsonSchemaChecker()
{ {
@ -25,11 +26,12 @@ bool QJsonSchemaChecker::setSchema(const QJsonObject & schema)
return true; return true;
} }
bool QJsonSchemaChecker::validate(const QJsonObject & value, bool ignoreRequired) QPair<bool, bool> QJsonSchemaChecker::validate(const QJsonObject & value, bool ignoreRequired)
{ {
// initialize state // initialize state
_ignoreRequired = ignoreRequired; _ignoreRequired = ignoreRequired;
_error = false; _error = false;
_schemaError = false;
_messages.clear(); _messages.clear();
_currentPath.clear(); _currentPath.clear();
_currentPath.append("[root]"); _currentPath.append("[root]");
@ -37,7 +39,27 @@ bool QJsonSchemaChecker::validate(const QJsonObject & value, bool ignoreRequired
// validate // validate
validate(value, _qSchema); validate(value, _qSchema);
return !_error; return QPair<bool, bool>(!_error, !_schemaError);
}
QJsonObject QJsonSchemaChecker::getAutoCorrectedConfig(const QJsonObject& value, bool ignoreRequired)
{
_ignoreRequired = ignoreRequired;
QStringList sequence = QStringList() << "remove" << "modify" << "create";
_error = false;
_schemaError = false;
_messages.clear();
_autoCorrected = value;
for(const QString &correct : sequence)
{
_correct = correct;
_currentPath.clear();
_currentPath.append("[root]");
validate(_autoCorrected, _qSchema);
}
return _autoCorrected;
} }
void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &schema) void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &schema)
@ -48,15 +70,17 @@ void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &s
QString attribute = i.key(); QString attribute = i.key();
const QJsonValue & attributeValue = *i; const QJsonValue & attributeValue = *i;
QJsonObject::const_iterator defaultValue = schema.find("default");
if (attribute == "type") if (attribute == "type")
checkType(value, attributeValue); checkType(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
else if (attribute == "properties") else if (attribute == "properties")
{ {
if (value.isObject()) if (value.isObject())
checkProperties(value.toObject(), attributeValue.toObject()); checkProperties(value.toObject(), attributeValue.toObject());
else else
{ {
_error = true; _schemaError = true;
setMessage("properties attribute is only valid for objects"); setMessage("properties attribute is only valid for objects");
continue; continue;
} }
@ -76,19 +100,19 @@ void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &s
} }
else else
{ {
_error = true; _schemaError = true;
setMessage("additional properties attribute is only valid for objects"); setMessage("additional properties attribute is only valid for objects");
continue; continue;
} }
} }
else if (attribute == "minimum") else if (attribute == "minimum")
checkMinimum(value, attributeValue); checkMinimum(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
else if (attribute == "maximum") else if (attribute == "maximum")
checkMaximum(value, attributeValue); checkMaximum(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
else if (attribute == "minLength") else if (attribute == "minLength")
checkMinLength(value, attributeValue); checkMinLength(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
else if (attribute == "maxLength") else if (attribute == "maxLength")
checkMaxLength(value, attributeValue); checkMaxLength(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
else if (attribute == "items") else if (attribute == "items")
{ {
if (value.isArray()) if (value.isArray())
@ -101,13 +125,13 @@ void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &s
} }
} }
else if (attribute == "minItems") else if (attribute == "minItems")
checkMinItems(value, attributeValue); checkMinItems(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
else if (attribute == "maxItems") else if (attribute == "maxItems")
checkMaxItems(value, attributeValue); checkMaxItems(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
else if (attribute == "uniqueItems") else if (attribute == "uniqueItems")
checkUniqueItems(value, attributeValue); checkUniqueItems(value, attributeValue);
else if (attribute == "enum") else if (attribute == "enum")
checkEnum(value, attributeValue); checkEnum(value, attributeValue, (defaultValue != schema.end() ? defaultValue.value() : QJsonValue::Null));
else if (attribute == "required") else if (attribute == "required")
; // nothing to do. value is present so always oke ; // nothing to do. value is present so always oke
else if (attribute == "id") else if (attribute == "id")
@ -118,7 +142,7 @@ void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &s
else else
{ {
// no check function defined for this attribute // no check function defined for this attribute
_error = true; _schemaError = true;
setMessage("No check function defined for attribute " + attribute); setMessage("No check function defined for attribute " + attribute);
continue; continue;
} }
@ -135,7 +159,7 @@ const QStringList & QJsonSchemaChecker::getMessages() const
return _messages; return _messages;
} }
void QJsonSchemaChecker::checkType(const QJsonValue & value, const QJsonValue & schema) void QJsonSchemaChecker::checkType(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue)
{ {
QString type = schema.toString(); QString type = schema.toString();
@ -145,7 +169,12 @@ void QJsonSchemaChecker::checkType(const QJsonValue & value, const QJsonValue &
else if (type == "number") else if (type == "number")
wrongType = !value.isDouble(); wrongType = !value.isDouble();
else if (type == "integer") else if (type == "integer")
wrongType = (rint(value.toDouble()) != value.toDouble()); {
if (value.isDouble()) //check if value type not boolean (true = 1 && false = 0)
wrongType = (rint(value.toDouble()) != value.toDouble());
else
wrongType = true;
}
else if (type == "double") else if (type == "double")
wrongType = !value.isDouble(); wrongType = !value.isDouble();
else if (type == "boolean") else if (type == "boolean")
@ -160,13 +189,16 @@ void QJsonSchemaChecker::checkType(const QJsonValue & value, const QJsonValue &
wrongType = !value.isString(); wrongType = !value.isString();
else if (type == "any") else if (type == "any")
wrongType = false; wrongType = false;
// else
// assert(false);
if (wrongType) if (wrongType)
{ {
_error = true; _error = true;
setMessage(type + " expected");
if (_correct == "modify")
QJsonUtils::modify(_autoCorrected, _currentPath, defaultValue);
if (_correct == "")
setMessage(type + " expected");
} }
} }
@ -176,7 +208,7 @@ void QJsonSchemaChecker::checkProperties(const QJsonObject & value, const QJsonO
{ {
QString property = i.key(); QString property = i.key();
const QJsonValue & propertyValue = i.value(); const QJsonValue & propertyValue = *i;
_currentPath.append("." + property); _currentPath.append("." + property);
QJsonObject::const_iterator required = propertyValue.toObject().find("required"); QJsonObject::const_iterator required = propertyValue.toObject().find("required");
@ -185,11 +217,19 @@ void QJsonSchemaChecker::checkProperties(const QJsonObject & value, const QJsonO
{ {
validate(value[property], propertyValue.toObject()); validate(value[property], propertyValue.toObject());
} }
else if (required != propertyValue.toObject().end() && required.value().toBool() && !_ignoreRequired) else if (required != propertyValue.toObject().end() && propertyValue.toObject().find("required").value().toBool() && !_ignoreRequired)
{ {
_error = true; _error = true;
setMessage("missing member");
if (_correct == "create")
QJsonUtils::modify(_autoCorrected, _currentPath, QJsonUtils::create(propertyValue, _ignoreRequired), property);
if (_correct == "")
setMessage("missing member");
} }
else if (_correct == "create" && _ignoreRequired)
QJsonUtils::modify(_autoCorrected, _currentPath, QJsonUtils::create(propertyValue, _ignoreRequired), property);
_currentPath.removeLast(); _currentPath.removeLast();
} }
} }
@ -208,7 +248,12 @@ void QJsonSchemaChecker::checkAdditionalProperties(const QJsonObject & value, co
if (schema.toBool() == false) if (schema.toBool() == false)
{ {
_error = true; _error = true;
setMessage("no schema definition");
if (_correct == "remove")
QJsonUtils::modify(_autoCorrected, _currentPath);
if (_correct == "")
setMessage("no schema definition");
} }
} }
else else
@ -220,7 +265,7 @@ void QJsonSchemaChecker::checkAdditionalProperties(const QJsonObject & value, co
} }
} }
void QJsonSchemaChecker::checkMinimum(const QJsonValue & value, const QJsonValue & schema) void QJsonSchemaChecker::checkMinimum(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue)
{ {
if (!value.isDouble()) if (!value.isDouble())
{ {
@ -233,11 +278,18 @@ void QJsonSchemaChecker::checkMinimum(const QJsonValue & value, const QJsonValue
if (value.toDouble() < schema.toDouble()) if (value.toDouble() < schema.toDouble())
{ {
_error = true; _error = true;
setMessage("value is too small (minimum=" + schema.toString() + ")");
if (_correct == "modify")
(defaultValue != QJsonValue::Null) ?
QJsonUtils::modify(_autoCorrected, _currentPath, defaultValue) :
QJsonUtils::modify(_autoCorrected, _currentPath, schema);
if (_correct == "")
setMessage("value is too small (minimum=" + QString::number(schema.toDouble()) + ")");
} }
} }
void QJsonSchemaChecker::checkMaximum(const QJsonValue & value, const QJsonValue & schema) void QJsonSchemaChecker::checkMaximum(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue)
{ {
if (!value.isDouble()) if (!value.isDouble())
{ {
@ -250,11 +302,18 @@ void QJsonSchemaChecker::checkMaximum(const QJsonValue & value, const QJsonValue
if (value.toDouble() > schema.toDouble()) if (value.toDouble() > schema.toDouble())
{ {
_error = true; _error = true;
setMessage("value is too large (maximum=" + schema.toString() + ")");
if (_correct == "modify")
(defaultValue != QJsonValue::Null) ?
QJsonUtils::modify(_autoCorrected, _currentPath, defaultValue) :
QJsonUtils::modify(_autoCorrected, _currentPath, schema);
if (_correct == "")
setMessage("value is too large (maximum=" + QString::number(schema.toDouble()) + ")");
} }
} }
void QJsonSchemaChecker::checkMinLength(const QJsonValue & value, const QJsonValue & schema) void QJsonSchemaChecker::checkMinLength(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue)
{ {
if (!value.isString()) if (!value.isString())
{ {
@ -267,11 +326,18 @@ void QJsonSchemaChecker::checkMinLength(const QJsonValue & value, const QJsonVal
if (value.toString().size() < schema.toInt()) if (value.toString().size() < schema.toInt())
{ {
_error = true; _error = true;
setMessage("value is too short (minLength=" + schema.toString() + ")");
if (_correct == "modify")
(defaultValue != QJsonValue::Null) ?
QJsonUtils::modify(_autoCorrected, _currentPath, defaultValue) :
QJsonUtils::modify(_autoCorrected, _currentPath, schema);
if (_correct == "")
setMessage("value is too short (minLength=" + QString::number(schema.toInt()) + ")");
} }
} }
void QJsonSchemaChecker::checkMaxLength(const QJsonValue & value, const QJsonValue & schema) void QJsonSchemaChecker::checkMaxLength(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue)
{ {
if (!value.isString()) if (!value.isString())
{ {
@ -284,7 +350,14 @@ void QJsonSchemaChecker::checkMaxLength(const QJsonValue & value, const QJsonVal
if (value.toString().size() > schema.toInt()) if (value.toString().size() > schema.toInt())
{ {
_error = true; _error = true;
setMessage("value is too long (maxLength=" + schema.toString() + ")");
if (_correct == "modify")
(defaultValue != QJsonValue::Null) ?
QJsonUtils::modify(_autoCorrected, _currentPath, defaultValue) :
QJsonUtils::modify(_autoCorrected, _currentPath, schema);
if (_correct == "")
setMessage("value is too long (maxLength=" + QString::number(schema.toInt()) + ")");
} }
} }
@ -299,6 +372,11 @@ void QJsonSchemaChecker::checkItems(const QJsonValue & value, const QJsonObject
} }
QJsonArray jArray = value.toArray(); QJsonArray jArray = value.toArray();
if (_correct == "remove")
if (jArray.isEmpty())
QJsonUtils::modify(_autoCorrected, _currentPath);
for(int i = 0; i < jArray.size(); ++i) for(int i = 0; i < jArray.size(); ++i)
{ {
// validate each item // validate each item
@ -308,7 +386,7 @@ void QJsonSchemaChecker::checkItems(const QJsonValue & value, const QJsonObject
} }
} }
void QJsonSchemaChecker::checkMinItems(const QJsonValue & value, const QJsonValue & schema) void QJsonSchemaChecker::checkMinItems(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue)
{ {
if (!value.isArray()) if (!value.isArray())
{ {
@ -318,17 +396,22 @@ void QJsonSchemaChecker::checkMinItems(const QJsonValue & value, const QJsonValu
return; return;
} }
int minimum = schema.toInt();
QJsonArray jArray = value.toArray(); QJsonArray jArray = value.toArray();
if (static_cast<int>(jArray.size()) < minimum) if (jArray.size() < schema.toInt())
{ {
_error = true; _error = true;
setMessage("array is too large (minimum=" + QString::number(minimum) + ")");
if (_correct == "modify")
(defaultValue != QJsonValue::Null) ?
QJsonUtils::modify(_autoCorrected, _currentPath, defaultValue) :
QJsonUtils::modify(_autoCorrected, _currentPath, schema);
if (_correct == "")
setMessage("array is too small (minimum=" + QString::number(schema.toInt()) + ")");
} }
} }
void QJsonSchemaChecker::checkMaxItems(const QJsonValue & value, const QJsonValue & schema) void QJsonSchemaChecker::checkMaxItems(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue)
{ {
if (!value.isArray()) if (!value.isArray())
{ {
@ -338,13 +421,18 @@ void QJsonSchemaChecker::checkMaxItems(const QJsonValue & value, const QJsonValu
return; return;
} }
int maximum = schema.toInt();
QJsonArray jArray = value.toArray(); QJsonArray jArray = value.toArray();
if (static_cast<int>(jArray.size()) > maximum) if (jArray.size() > schema.toInt())
{ {
_error = true; _error = true;
setMessage("array is too large (maximum=" + QString::number(maximum) + ")");
if (_correct == "modify")
(defaultValue != QJsonValue::Null) ?
QJsonUtils::modify(_autoCorrected, _currentPath, defaultValue) :
QJsonUtils::modify(_autoCorrected, _currentPath, schema);
if (_correct == "")
setMessage("array is too large (maximum=" + QString::number(schema.toInt()) + ")");
} }
} }
@ -362,6 +450,8 @@ void QJsonSchemaChecker::checkUniqueItems(const QJsonValue & value, const QJsonV
{ {
// make sure no two items are identical // make sure no two items are identical
bool removeDuplicates = false;
QJsonArray jArray = value.toArray(); QJsonArray jArray = value.toArray();
for(int i = 0; i < jArray.size(); ++i) for(int i = 0; i < jArray.size(); ++i)
{ {
@ -371,14 +461,28 @@ void QJsonSchemaChecker::checkUniqueItems(const QJsonValue & value, const QJsonV
{ {
// found a value twice // found a value twice
_error = true; _error = true;
setMessage("array must have unique values"); removeDuplicates = true;
if (_correct == "")
setMessage("array must have unique values");
} }
} }
} }
if (removeDuplicates && _correct == "modify")
{
QJsonArray uniqueItemsArray;
for(int i = 0; i < jArray.size(); ++i)
if (!uniqueItemsArray.contains(jArray[i]))
uniqueItemsArray.append(jArray[i]);
QJsonUtils::modify(_autoCorrected, _currentPath, uniqueItemsArray);
}
} }
} }
void QJsonSchemaChecker::checkEnum(const QJsonValue & value, const QJsonValue & schema) void QJsonSchemaChecker::checkEnum(const QJsonValue & value, const QJsonValue & schema, const QJsonValue & defaultValue)
{ {
if (schema.isArray()) if (schema.isArray())
{ {
@ -395,7 +499,16 @@ void QJsonSchemaChecker::checkEnum(const QJsonValue & value, const QJsonValue &
// nothing found // nothing found
_error = true; _error = true;
QJsonDocument doc(schema.toArray());
QString strJson(doc.toJson(QJsonDocument::Compact)); if (_correct == "modify")
setMessage("Unknown enum value (allowed values are: " + schema.toString() + strJson+ ")"); (defaultValue != QJsonValue::Null) ?
QJsonUtils::modify(_autoCorrected, _currentPath, defaultValue) :
QJsonUtils::modify(_autoCorrected, _currentPath, schema.toArray().first());
if (_correct == "")
{
QJsonDocument doc(schema.toArray());
QString strJson(doc.toJson(QJsonDocument::Compact));
setMessage("Unknown enum value (allowed values are: " + strJson+ ")");
}
} }

View File

@ -105,14 +105,43 @@ void CgiHandler::cmd_cfg_set()
{ {
// make sure the resources are loaded (they may be left out after static linking) // make sure the resources are loaded (they may be left out after static linking)
Q_INIT_RESOURCE(resource); Q_INIT_RESOURCE(resource);
QJsonObject schemaJson = QJsonFactory::readSchema(":/hyperion-schema-"+QString::number(_hyperion->getConfigVersionId()));
QString schemaFile = ":/hyperion-schema";
QJsonObject schemaJson;
try
{
schemaJson = QJsonFactory::readSchema(schemaFile);
}
catch(const std::runtime_error& error)
{
throw std::runtime_error(error.what());
}
QJsonSchemaChecker schemaChecker; QJsonSchemaChecker schemaChecker;
schemaChecker.setSchema(schemaJson); schemaChecker.setSchema(schemaJson);
if ( schemaChecker.validate(hyperionConfigJsonObj) )
QPair<bool, bool> validate = schemaChecker.validate(hyperionConfigJsonObj);
if (validate.first && validate.second)
{ {
QJsonFactory::writeJson(_hyperion->getConfigFileName(), hyperionConfigJsonObj); QJsonFactory::writeJson(_hyperion->getConfigFileName(), hyperionConfigJsonObj);
} }
else else if (!validate.first && validate.second)
{
Warning(_log,"Errors have been found in the configuration file. Automatic correction is applied");
QStringList schemaErrors = schemaChecker.getMessages();
foreach (auto & schemaError, schemaErrors)
Info(_log, schemaError.toUtf8().constData());
hyperionConfigJsonObj = schemaChecker.getAutoCorrectedConfig(hyperionConfigJsonObj);
if (!QJsonFactory::writeJson(_hyperion->getConfigFileName(), hyperionConfigJsonObj))
throw std::runtime_error("ERROR: can not save configuration file, aborting ");
}
else //Error in Schema
{ {
QString errorMsg = "ERROR: Json validation failed: \n"; QString errorMsg = "ERROR: Json validation failed: \n";
QStringList schemaErrors = schemaChecker.getMessages(); QStringList schemaErrors = schemaChecker.getMessages();
@ -121,6 +150,7 @@ void CgiHandler::cmd_cfg_set()
Error(_log, "config write validation: %s", QSTRING_CSTR(schemaError)); Error(_log, "config write validation: %s", QSTRING_CSTR(schemaError));
errorMsg += schemaError + "\n"; errorMsg += schemaError + "\n";
} }
throw std::runtime_error(errorMsg.toStdString()); throw std::runtime_error(errorMsg.toStdString());
} }
} }

View File

@ -8,10 +8,6 @@ QT5_WRAP_CPP(Hyperiond_HEADERS_MOC ${Hyperiond_QT_HEADERS})
add_executable(hyperiond add_executable(hyperiond
${Hyperiond_QT_HEADERS} ${Hyperiond_QT_HEADERS}
${Hyperiond_HEADERS_MOC} ${Hyperiond_HEADERS_MOC}
configMigratorBase.cpp
configMigratorBase.h
configMigrator.cpp
configMigrator.h
hyperiond.cpp hyperiond.cpp
main.cpp main.cpp
) )

View File

@ -1,34 +0,0 @@
#include "configMigrator.h"
ConfigMigrator::ConfigMigrator()
{
}
ConfigMigrator::~ConfigMigrator()
{
}
bool ConfigMigrator::migrate(QString configFile, int fromVersion,int toVersion)
{
Debug(_log, "migrate config %s from version %d to %d.", configFile.toLocal8Bit().constData(), fromVersion, toVersion);
for (int v=fromVersion; v<toVersion; v++)
{
switch(v)
{
case 1: migrateFrom1(); break;
default:
throw std::runtime_error("ERROR: config migration - unknown version");
}
}
return true;
}
void ConfigMigrator::migrateFrom1()
{
throw std::runtime_error("ERROR: config migration not implemented");
}

View File

@ -1,21 +0,0 @@
#pragma once
#include "configMigratorBase.h"
#include <QString>
///
/// class that contains migration code
/// helper code goeas to base class
class ConfigMigrator : public ConfigMigratorBase
{
public:
ConfigMigrator();
~ConfigMigrator();
bool migrate(QString configFile, int fromVersion,int toVersion);
private:
void migrateFrom1();
};

View File

@ -1,11 +0,0 @@
#include "configMigratorBase.h"
ConfigMigratorBase::ConfigMigratorBase()
: _log(Logger::getInstance("ConfigMigrator"))
{
}
ConfigMigratorBase::~ConfigMigratorBase()
{
}

View File

@ -1,16 +0,0 @@
#pragma once
#include <utils/Logger.h>
#include <QString>
class ConfigMigratorBase
{
public:
ConfigMigratorBase();
~ConfigMigratorBase();
protected:
Logger * _log;
};

View File

@ -12,6 +12,7 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonValue> #include <QJsonValue>
#include <QPair>
#include <cstdint> #include <cstdint>
#include <limits> #include <limits>
@ -31,7 +32,6 @@
#include <udplistener/UDPListener.h> #include <udplistener/UDPListener.h>
#include "hyperiond.h" #include "hyperiond.h"
#include "configMigrator.h"
HyperionDaemon::HyperionDaemon(QString configFile, QObject *parent) HyperionDaemon::HyperionDaemon(QString configFile, QObject *parent)
: QObject(parent) : QObject(parent)
@ -52,7 +52,7 @@ HyperionDaemon::HyperionDaemon(QString configFile, QObject *parent)
, _hyperion(nullptr) , _hyperion(nullptr)
, _stats(nullptr) , _stats(nullptr)
{ {
loadConfig(configFile, CURRENT_CONFIG_VERSION ); loadConfig(configFile);
if (Logger::getLogLevel() == Logger::WARNING) if (Logger::getLogLevel() == Logger::WARNING)
{ {
@ -135,16 +135,16 @@ void HyperionDaemon::run()
connect(_hyperion,SIGNAL(closing()),this,SLOT(freeObjects())); connect(_hyperion,SIGNAL(closing()),this,SLOT(freeObjects()));
} }
int HyperionDaemon::tryLoadConfig(const QString & configFile, const int schemaVersion) void HyperionDaemon::loadConfig(const QString & configFile)
{ {
Info(_log, "Selected configuration file: %s", configFile.toUtf8().constData());
// make sure the resources are loaded (they may be left out after static linking) // make sure the resources are loaded (they may be left out after static linking)
Q_INIT_RESOURCE(resource); Q_INIT_RESOURCE(resource);
// read the json schema from the resource // read the json schema from the resource
QString schemaFile = ":/hyperion-schema"; QString schemaFile = ":/hyperion-schema";
if (schemaVersion > 0)
schemaFile += "-" + QString::number(schemaVersion);
QJsonObject schemaJson; QJsonObject schemaJson;
try try
{ {
@ -159,45 +159,25 @@ int HyperionDaemon::tryLoadConfig(const QString & configFile, const int schemaVe
schemaChecker.setSchema(schemaJson); schemaChecker.setSchema(schemaJson);
_qconfig = QJsonFactory::readConfig(configFile); _qconfig = QJsonFactory::readConfig(configFile);
if (!schemaChecker.validate(_qconfig)) QPair<bool, bool> validate = schemaChecker.validate(_qconfig);
if (!validate.first && validate.second)
{
Warning(_log,"Errors have been found in the configuration file. Automatic correction is applied");
_qconfig = schemaChecker.getAutoCorrectedConfig(_qconfig);
if (!QJsonFactory::writeJson(configFile, _qconfig))
throw std::runtime_error("ERROR: can not save configuration file, aborting ");
}
else if (validate.first && !validate.second) //Error in Schema
{ {
QStringList schemaErrors = schemaChecker.getMessages(); QStringList schemaErrors = schemaChecker.getMessages();
foreach (auto & schemaError, schemaErrors) foreach (auto & schemaError, schemaErrors)
{
std::cout << schemaError.toStdString() << std::endl; std::cout << schemaError.toStdString() << std::endl;
}
throw std::runtime_error("ERROR: Json validation failed"); throw std::runtime_error("ERROR: Json validation failed");
} }
const QJsonObject & generalConfig = _qconfig["general"].toObject();
return generalConfig["configVersion"].toInt(-1);
}
void HyperionDaemon::loadConfig(const QString & configFile, const int neededConfigVersion)
{
Info(_log, "Selected configuration file: %s", configFile.toUtf8().constData());
int configVersionId = tryLoadConfig(configFile,0);
// no config id found, assume legacy hyperion
if (configVersionId < 0)
{
Debug(_log, "config file has no version, assume old hyperion.");
configVersionId = tryLoadConfig(configFile,1);
}
Debug(_log, "config version: %d", configVersionId);
configVersionId = tryLoadConfig(configFile, configVersionId);
if (neededConfigVersion == configVersionId)
{
return;
}
// migrate configVersionId
ConfigMigrator migrator;
migrator.migrate(configFile, configVersionId, neededConfigVersion);
} }

View File

@ -54,9 +54,8 @@ class HyperionDaemon : public QObject
public: public:
HyperionDaemon(QString configFile, QObject *parent=nullptr); HyperionDaemon(QString configFile, QObject *parent=nullptr);
~HyperionDaemon(); ~HyperionDaemon();
int tryLoadConfig(const QString & configFile, const int schemaVersion); void loadConfig(const QString & configFile);
void loadConfig(const QString & configFile, const int neededConfigVersion);
void run(); void run();
void startInitialEffect(); void startInitialEffect();

View File

@ -1,9 +1,6 @@
// STL includes
#include <cstdlib>
// QT includes // QT includes
#include <QResource> #include <QResource>
#include <QDebug>
// JsonSchema includes // JsonSchema includes
#include <utils/jsonschema/QJsonFactory.h> #include <utils/jsonschema/QJsonFactory.h>
@ -12,7 +9,7 @@
#include <hyperion/LedString.h> #include <hyperion/LedString.h>
#include "HyperionConfig.h" #include "HyperionConfig.h"
bool loadConfig(const QString & configFile) bool loadConfig(const QString & configFile, bool correct, bool ignore)
{ {
// make sure the resources are loaded (they may be left out after static linking) // make sure the resources are loaded (they may be left out after static linking)
Q_INIT_RESOURCE(resource); Q_INIT_RESOURCE(resource);
@ -39,46 +36,75 @@ bool loadConfig(const QString & configFile)
// read and validate the configuration file from the command line // read and validate the configuration file from the command line
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
const QJsonObject jsonConfig = QJsonFactory::readConfig(configFile); QJsonObject jsonConfig = QJsonFactory::readConfig(configFile);
if (!schemaChecker.validate(jsonConfig)) if (!correct)
{ {
QStringList schemaErrors = schemaChecker.getMessages(); if (!schemaChecker.validate(jsonConfig).first)
foreach (auto & schemaError, schemaErrors)
{ {
std::cout << "config write validation: " << schemaError.toStdString() << std::endl; QStringList schemaErrors = schemaChecker.getMessages();
foreach (auto & schemaError, schemaErrors)
{
qDebug() << "config write validation: " << schemaError;
}
qDebug() << "FAILED";
exit(1);
return false;
} }
}
std::cout << "FAILED" << std::endl; else
exit(1); {
return false; jsonConfig = schemaChecker.getAutoCorrectedConfig(jsonConfig, ignore); // The second parameter is to ignore the "required" keyword in hyperion schema
QJsonFactory::writeJson(configFile, jsonConfig);
} }
return true; return true;
} }
void usage()
{
qDebug() << "Missing required configuration file to test";
qDebug() << "Usage: test_configfile <option> [configfile]";
qDebug() << "<option>:";
qDebug() << "\t--ac - for json auto correction";
qDebug() << "\t--ac-ignore-required - for json auto correction without paying attention 'required' keyword in hyperion schema";
}
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
if (argc != 2) if (argc < 2)
{ {
std::cerr << "Missing required configuration file to test" << std::endl; usage();
std::cerr << "Usage: test_configfile [configfile]" << std::endl;
return 0; return 0;
} }
const QString configFile(argv[1]); QString option = argv[1];
std::cout << "Configuration file selected: " << configFile.toStdString() << std::endl; QString configFile;
std::cout << "Attemp to load..." << std::endl;
if (option == "--ac" || option == "--ac-ignore-required")
if (argc > 2)
configFile = argv[2];
else
{
usage();
return 0;
}
else
configFile = argv[1];
qDebug() << "Configuration file selected: " << configFile;
qDebug() << "Attemp to load...";
try try
{ {
if (loadConfig(configFile)) if (loadConfig(configFile, (option == "--ac" || option == "--ac-ignore-required"), option == "--ac-ignore-required"))
std::cout << "PASSED" << std::endl; qDebug() << "PASSED";
return 0; return 0;
} }
catch (std::runtime_error exception) catch (std::runtime_error exception)
{ {
std::cout << "FAILED" << std::endl; qDebug() << "FAILED";
std::cout << exception.what() << std::endl; qDebug() << exception.what();
} }
return 1; return 1;

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
import json, sys, glob import json, sys, glob
from os import path from os import path
from jsonschema import Draft3Validator from jsonschema import Draft3Validator, RefResolver
print('-- validate json file') print('-- validate json file')
@ -11,9 +11,8 @@ schemaFileName = sys.argv[2]
try: try:
with open(schemaFileName) as schemaFile: with open(schemaFileName) as schemaFile:
with open(jsonFileName) as jsonFile: with open(jsonFileName) as jsonFile:
j = json.loads(jsonFile.read()) resolver = RefResolver('file://%s/schema/' % path.abspath(path.dirname(schemaFileName)), None)
validator = Draft3Validator(json.loads(schemaFile.read())) Draft3Validator(json.loads(schemaFile.read()), resolver=resolver).validate(json.loads(jsonFile.read()))
validator.validate(j)
except Exception as e: except Exception as e:
print('validation error: '+jsonFileName + ' '+schemaFileName+' ('+str(e)+')') print('validation error: '+jsonFileName + ' '+schemaFileName+' ('+str(e)+')')
sys.exit(1) sys.exit(1)

View File

@ -4,46 +4,71 @@ An implementation of JSON Schema for Python
The main functionality is provided by the validator classes for each of the The main functionality is provided by the validator classes for each of the
supported JSON Schema versions. supported JSON Schema versions.
Most commonly, the :function:`validate` function is the quickest way to simply Most commonly, :func:`validate` is the quickest way to simply validate a given
validate a given instance under a schema, and will create a validator for you. instance under a schema, and will create a validator for you.
""" """
from __future__ import division, unicode_literals from __future__ import division, unicode_literals
import collections import collections
import json
import itertools import itertools
import operator import operator
import re import re
import sys import sys
import warnings
__version__ = "0.7" __version__ = "0.8.0"
FLOAT_TOLERANCE = 10 ** -15
PY3 = sys.version_info[0] >= 3 PY3 = sys.version_info[0] >= 3
if PY3: if PY3:
from urllib import parse as urlparse
from urllib.parse import unquote
from urllib.request import urlopen
basestring = unicode = str basestring = unicode = str
iteritems = operator.methodcaller("items") iteritems = operator.methodcaller("items")
from urllib.parse import unquote
else: else:
from itertools import izip as zip from itertools import izip as zip
iteritems = operator.methodcaller("iteritems")
from urllib import unquote from urllib import unquote
from urllib2 import urlopen
import urlparse
iteritems = operator.methodcaller("iteritems")
FLOAT_TOLERANCE = 10 ** -15
validators = {}
def validates(version):
"""
Register the decorated validator for a ``version`` of the specification.
Registered validators and their meta schemas will be considered when
parsing ``$schema`` properties' URIs.
:argument str version: an identifier to use as the version's name
:returns: a class decorator to decorate the validator with the version
"""
def _validates(cls):
validators[version] = cls
return cls
return _validates
class UnknownType(Exception): class UnknownType(Exception):
""" """
An unknown type was given. An attempt was made to check if an instance was of an unknown type.
""" """
class InvalidRef(Exception): class RefResolutionError(Exception):
""" """
An invalid reference was given. A JSON reference failed to resolve.
""" """
@ -52,21 +77,23 @@ class SchemaError(Exception):
""" """
The provided schema is malformed. The provided schema is malformed.
The same attributes exist for ``SchemaError``s as for ``ValidationError``s. The same attributes are present as for :exc:`ValidationError`\s.
""" """
validator = None def __init__(self, message, validator=None, path=()):
super(SchemaError, self).__init__(message, validator, path)
def __init__(self, message):
super(SchemaError, self).__init__(message)
self.message = message self.message = message
self.path = [] self.path = list(path)
self.validator = validator
def __str__(self):
return self.message
class ValidationError(Exception): class ValidationError(Exception):
""" """
The instance didn't properly validate with the provided schema. The instance didn't properly validate under the provided schema.
Relevant attributes are: Relevant attributes are:
* ``message`` : a human readable message explaining the error * ``message`` : a human readable message explaining the error
@ -76,19 +103,20 @@ class ValidationError(Exception):
""" """
# the failing validator will be set externally at whatever recursion level def __init__(self, message, validator=None, path=()):
# is immediately above the validation failure # Any validator that recurses (e.g. properties and items) must append
validator = None # to the ValidationError's path to properly maintain where in the
# instance the error occurred
def __init__(self, message): super(ValidationError, self).__init__(message, validator, path)
super(ValidationError, self).__init__(message)
self.message = message self.message = message
self.path = list(path)
self.validator = validator
# Any validator that recurses must append to the ValidationError's def __str__(self):
# path (e.g., properties and items) return self.message
self.path = []
@validates("draft3")
class Draft3Validator(object): class Draft3Validator(object):
""" """
A validator for JSON Schema draft 3. A validator for JSON Schema draft 3.
@ -100,37 +128,20 @@ class Draft3Validator(object):
"number" : (int, float), "object" : dict, "string" : basestring, "number" : (int, float), "object" : dict, "string" : basestring,
} }
def __init__(self, schema, types=()): def __init__(self, schema, types=(), resolver=None):
"""
Initialize a validator.
``schema`` should be a *valid* JSON Schema object already converted to
a native Python object (typically a dict via ``json.load``).
``types`` is a mapping (or iterable of 2-tuples) containing additional
types or alternate types to verify via the 'type' property. For
instance, the default types for the 'number' JSON Schema type are
``int`` and ``float``. To override this behavior (e.g. for also
allowing ``decimal.Decimal``), pass ``types={"number" : (int, float,
decimal.Decimal)} *including* the default types if so desired, which
are fairly obvious but can be accessed via the ``DEFAULT_TYPES``
attribute on this class if necessary.
"""
self._types = dict(self.DEFAULT_TYPES) self._types = dict(self.DEFAULT_TYPES)
self._types.update(types) self._types.update(types)
self._types["any"] = tuple(self._types.values())
if resolver is None:
resolver = RefResolver.from_schema(schema)
self.resolver = resolver
self.schema = schema self.schema = schema
def is_type(self, instance, type): def is_type(self, instance, type):
""" if type == "any":
Check if an ``instance`` is of the provided (JSON Schema) ``type``. return True
elif type not in self._types:
"""
if type not in self._types:
raise UnknownType(type) raise UnknownType(type)
type = self._types[type] type = self._types[type]
@ -142,36 +153,17 @@ class Draft3Validator(object):
return isinstance(instance, type) return isinstance(instance, type)
def is_valid(self, instance, _schema=None): def is_valid(self, instance, _schema=None):
"""
Check if the ``instance`` is valid under the current schema.
Returns a bool indicating whether validation succeeded.
"""
error = next(self.iter_errors(instance, _schema), None) error = next(self.iter_errors(instance, _schema), None)
return error is None return error is None
@classmethod @classmethod
def check_schema(cls, schema): def check_schema(cls, schema):
"""
Validate a ``schema`` against the meta-schema to see if it is valid.
"""
for error in cls(cls.META_SCHEMA).iter_errors(schema): for error in cls(cls.META_SCHEMA).iter_errors(schema):
s = SchemaError(error.message) raise SchemaError(
s.path = error.path error.message, validator=error.validator, path=error.path,
s.validator = error.validator )
# I think we're safer raising these always, not yielding them
raise s
def iter_errors(self, instance, _schema=None): def iter_errors(self, instance, _schema=None):
"""
Lazily yield each of the errors in the given ``instance``.
"""
if _schema is None: if _schema is None:
_schema = self.schema _schema = self.schema
@ -183,17 +175,12 @@ class Draft3Validator(object):
errors = validator(v, instance, _schema) or () errors = validator(v, instance, _schema) or ()
for error in errors: for error in errors:
# if the validator hasn't already been set (due to recursion) # set the validator if it wasn't already set by the called fn
# make sure to set it if error.validator is None:
error.validator = error.validator or k error.validator = k
yield error yield error
def validate(self, *args, **kwargs): def validate(self, *args, **kwargs):
"""
Validate an ``instance`` under the given ``schema``.
"""
for error in self.iter_errors(*args, **kwargs): for error in self.iter_errors(*args, **kwargs):
raise error raise error
@ -201,21 +188,12 @@ class Draft3Validator(object):
types = _list(types) types = _list(types)
for type in types: for type in types:
# Ouch. Brain hurts. Two paths here, either we have a schema, then if self.is_type(type, "object"):
# check if the instance is valid under it if self.is_valid(instance, type):
if (( return
self.is_type(type, "object") and elif self.is_type(type, "string"):
self.is_valid(instance, type) if self.is_type(instance, type):
return
# Or we have a type as a string, just check if the instance is that
# type. Also, HACK: we can reach the `or` here if skip_types is
# something other than error. If so, bail out.
) or (
self.is_type(type, "string") and
(self.is_type(instance, type) or type not in self._types)
)):
return
else: else:
yield ValidationError(_types_msg(instance, types)) yield ValidationError(_types_msg(instance, types))
@ -229,14 +207,16 @@ class Draft3Validator(object):
error.path.append(property) error.path.append(property)
yield error yield error
elif subschema.get("required", False): elif subschema.get("required", False):
error = ValidationError( yield ValidationError(
"%r is a required property" % (property,) "%r is a required property" % (property,),
validator="required",
path=[property],
) )
error.path.append(property)
error.validator = "required"
yield error
def validate_patternProperties(self, patternProperties, instance, schema): def validate_patternProperties(self, patternProperties, instance, schema):
if not self.is_type(instance, "object"):
return
for pattern, subschema in iteritems(patternProperties): for pattern, subschema in iteritems(patternProperties):
for k, v in iteritems(instance): for k, v in iteritems(instance):
if re.match(pattern, k): if re.match(pattern, k):
@ -292,9 +272,10 @@ class Draft3Validator(object):
yield error yield error
def validate_additionalItems(self, aI, instance, schema): def validate_additionalItems(self, aI, instance, schema):
if not self.is_type(instance, "array"): if (
return not self.is_type(instance, "array") or
if not self.is_type(schema.get("items"), "array"): not self.is_type(schema.get("items"), "array")
):
return return
if self.is_type(aI, "object"): if self.is_type(aI, "object"):
@ -397,11 +378,7 @@ class Draft3Validator(object):
yield error yield error
def validate_ref(self, ref, instance, schema): def validate_ref(self, ref, instance, schema):
if ref != "#" and not ref.startswith("#/"): resolved = self.resolver.resolve(ref)
warnings.warn("jsonschema only supports json-pointer $refs")
return
resolved = resolve_json_pointer(self.schema, ref)
for error in self.iter_errors(instance, resolved): for error in self.iter_errors(instance, resolved):
yield error yield error
@ -490,22 +467,89 @@ Draft3Validator.META_SCHEMA = {
} }
class Validator(Draft3Validator): class RefResolver(object):
""" """
Deprecated: Use :class:`Draft3Validator` instead. Resolve JSON References.
:argument str base_uri: URI of the referring document
:argument referrer: the actual referring document
:argument dict store: a mapping from URIs to documents to cache
""" """
def __init__( def __init__(self, base_uri, referrer, store=()):
self, version=None, unknown_type="skip", unknown_property="skip", self.base_uri = base_uri
*args, **kwargs self.referrer = referrer
): self.store = dict(store, **_meta_schemas())
super(Validator, self).__init__({}, *args, **kwargs)
warnings.warn( @classmethod
"Validator is deprecated and will be removed. " def from_schema(cls, schema, *args, **kwargs):
"Use Draft3Validator instead.", """
DeprecationWarning, stacklevel=2, Construct a resolver from a JSON schema object.
)
:argument schema schema: the referring schema
:rtype: :class:`RefResolver`
"""
return cls(schema.get("id", ""), schema, *args, **kwargs)
def resolve(self, ref):
"""
Resolve a JSON ``ref``.
:argument str ref: reference to resolve
:returns: the referrant document
"""
base_uri = self.base_uri
uri, fragment = urlparse.urldefrag(urlparse.urljoin(base_uri, ref))
if uri in self.store:
document = self.store[uri]
elif not uri or uri == self.base_uri:
document = self.referrer
else:
document = self.resolve_remote(uri)
return self.resolve_fragment(document, fragment.lstrip("/"))
def resolve_fragment(self, document, fragment):
"""
Resolve a ``fragment`` within the referenced ``document``.
:argument document: the referrant document
:argument str fragment: a URI fragment to resolve within it
"""
parts = unquote(fragment).split("/") if fragment else []
for part in parts:
part = part.replace("~1", "/").replace("~0", "~")
if part not in document:
raise RefResolutionError(
"Unresolvable JSON pointer: %r" % fragment
)
document = document[part]
return document
def resolve_remote(self, uri):
"""
Resolve a remote ``uri``.
Does not check the store first.
:argument str uri: the URI to resolve
:returns: the retrieved document
"""
return json.load(urlopen(uri))
class ErrorTree(object): class ErrorTree(object):
@ -528,6 +572,11 @@ class ErrorTree(object):
return k in self._contents return k in self._contents
def __getitem__(self, k): def __getitem__(self, k):
"""
Retrieve the child tree with key ``k``.
"""
return self._contents[k] return self._contents[k]
def __setitem__(self, k, v): def __setitem__(self, k, v):
@ -537,36 +586,30 @@ class ErrorTree(object):
return iter(self._contents) return iter(self._contents)
def __len__(self): def __len__(self):
return self.total_errors
def __repr__(self):
return "<%s (%s total errors)>" % (self.__class__.__name__, len(self))
@property
def total_errors(self):
"""
The total number of errors in the entire tree, including children.
"""
child_errors = sum(len(tree) for _, tree in iteritems(self._contents)) child_errors = sum(len(tree) for _, tree in iteritems(self._contents))
return len(self.errors) + child_errors return len(self.errors) + child_errors
def __repr__(self):
return "<%s (%s errors)>" % (self.__class__.__name__, len(self))
def _meta_schemas():
def resolve_json_pointer(schema, ref):
""" """
Resolve a local reference ``ref`` within the given root ``schema``. Collect the urls and meta schemas from each known validator.
``ref`` should be a local ref whose ``#`` is still present.
""" """
if ref == "#": meta_schemas = (v.META_SCHEMA for v in validators.values())
return schema return dict((urlparse.urldefrag(m["id"])[0], m) for m in meta_schemas)
parts = ref.lstrip("#/").split("/")
parts = map(unquote, parts)
parts = [part.replace('~1', '/').replace('~0', '~') for part in parts]
try:
for part in parts:
schema = schema[part]
except KeyError:
raise InvalidRef("Unresolvable json-pointer %r" % ref)
else:
return schema
def _find_additional_properties(instance, schema): def _find_additional_properties(instance, schema):
@ -675,6 +718,19 @@ def _delist(thing):
return thing return thing
def _unbool(element, true=object(), false=object()):
"""
A hack to make True and 1 and False and 0 unique for _uniq.
"""
if element is True:
return true
elif element is False:
return false
return element
def _uniq(container): def _uniq(container):
""" """
Check if all of a container's elements are unique. Check if all of a container's elements are unique.
@ -686,17 +742,18 @@ def _uniq(container):
""" """
try: try:
return len(set(container)) == len(container) return len(set(_unbool(i) for i in container)) == len(container)
except TypeError: except TypeError:
try: try:
sort = sorted(container) sort = sorted(_unbool(i) for i in container)
sliced = itertools.islice(container, 1, None) sliced = itertools.islice(sort, 1, None)
for i, j in zip(container, sliced): for i, j in zip(sort, sliced):
if i == j: if i == j:
return False return False
except (NotImplementedError, TypeError): except (NotImplementedError, TypeError):
seen = [] seen = []
for e in container: for e in container:
e = _unbool(e)
if e in seen: if e in seen:
return False return False
seen.append(e) seen.append(e)
@ -707,28 +764,29 @@ def validate(instance, schema, cls=Draft3Validator, *args, **kwargs):
""" """
Validate an ``instance`` under the given ``schema``. Validate an ``instance`` under the given ``schema``.
First verifies that the provided schema is itself valid, since not doing so >>> validate([2, 3, 4], {"maxItems" : 2})
can lead to less obvious failures when validating. If you know it is or Traceback (most recent call last):
don't care, use ``YourValidator(schema).validate(instance)`` directly ...
instead (e.g. ``Draft3Validator``). ValidationError: [2, 3, 4] is too long
:func:`validate` will first verify that the provided schema is itself
valid, since not doing so can lead to less obvious error messages and fail
in less obvious or consistent ways. If you know you have a valid schema
already or don't care, you might prefer using the ``validate`` method
directly on a specific validator (e.g. :meth:`Draft3Validator.validate`).
``cls`` is a validator class that will be used to validate the instance. ``cls`` is a validator class that will be used to validate the instance.
By default this is a draft 3 validator. Any other provided positional and By default this is a draft 3 validator. Any other provided positional and
keyword arguments will be provided to this class when constructing a keyword arguments will be provided to this class when constructing a
validator. validator.
:raises:
:exc:`ValidationError` if the instance is invalid
:exc:`SchemaError` if the schema itself is invalid
""" """
meta_validate = kwargs.pop("meta_validate", None) cls.check_schema(schema)
if meta_validate is not None:
warnings.warn(
"meta_validate is deprecated and will be removed. If you do not "
"want to validate a schema, use Draft3Validator.validate instead.",
DeprecationWarning, stacklevel=2,
)
if meta_validate is not False: # yes this is needed since True was default
cls.check_schema(schema)
cls(schema, *args, **kwargs).validate(instance) cls(schema, *args, **kwargs).validate(instance)