2016-07-20 17:16:06 +02:00
|
|
|
// stdlib includes
|
|
|
|
#include <iterator>
|
|
|
|
#include <algorithm>
|
2016-08-31 17:19:41 +02:00
|
|
|
#include <math.h>
|
2016-07-20 17:16:06 +02:00
|
|
|
|
|
|
|
// Utils-Jsonschema includes
|
|
|
|
#include <utils/jsonschema/QJsonSchemaChecker.h>
|
|
|
|
|
|
|
|
QJsonSchemaChecker::QJsonSchemaChecker()
|
|
|
|
{
|
|
|
|
// empty
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonSchemaChecker::~QJsonSchemaChecker()
|
|
|
|
{
|
|
|
|
// empty
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QJsonSchemaChecker::setSchema(const QJsonObject & schema)
|
|
|
|
{
|
|
|
|
_qSchema = schema;
|
|
|
|
|
|
|
|
// TODO: check the schema
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-10-09 22:22:17 +02:00
|
|
|
bool QJsonSchemaChecker::validate(const QJsonObject & value, bool ignoreRequired)
|
2016-07-20 17:16:06 +02:00
|
|
|
{
|
|
|
|
// initialize state
|
2016-10-09 22:22:17 +02:00
|
|
|
_ignoreRequired = ignoreRequired;
|
2016-07-20 17:16:06 +02:00
|
|
|
_error = false;
|
|
|
|
_messages.clear();
|
|
|
|
_currentPath.clear();
|
2017-03-04 22:17:42 +01:00
|
|
|
_currentPath.append("[root]");
|
2016-07-20 17:16:06 +02:00
|
|
|
|
|
|
|
// validate
|
|
|
|
validate(value, _qSchema);
|
|
|
|
|
|
|
|
return !_error;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QJsonSchemaChecker::validate(const QJsonValue & value, const QJsonObject &schema)
|
|
|
|
{
|
|
|
|
// check the current json value
|
|
|
|
for (QJsonObject::const_iterator i = schema.begin(); i != schema.end(); ++i)
|
|
|
|
{
|
2016-08-31 17:19:41 +02:00
|
|
|
QString attribute = i.key();
|
2016-07-20 17:16:06 +02:00
|
|
|
const QJsonValue & attributeValue = *i;
|
2016-08-31 17:19:41 +02:00
|
|
|
|
2016-07-20 17:16:06 +02:00
|
|
|
if (attribute == "type")
|
|
|
|
checkType(value, attributeValue);
|
|
|
|
else if (attribute == "properties")
|
|
|
|
{
|
|
|
|
if (value.isObject())
|
|
|
|
checkProperties(value.toObject(), attributeValue.toObject());
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_error = true;
|
|
|
|
setMessage("properties attribute is only valid for objects");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (attribute == "additionalProperties")
|
|
|
|
{
|
|
|
|
if (value.isObject())
|
|
|
|
{
|
|
|
|
// ignore the properties which are handled by the properties attribute (if present)
|
|
|
|
QStringList ignoredProperties;
|
|
|
|
if (schema.contains("properties")) {
|
|
|
|
const QJsonObject & props = schema["properties"].toObject();
|
|
|
|
ignoredProperties = props.keys();
|
|
|
|
}
|
2016-08-31 17:19:41 +02:00
|
|
|
|
2016-07-20 17:16:06 +02:00
|
|
|
checkAdditionalProperties(value.toObject(), attributeValue, ignoredProperties);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_error = true;
|
|
|
|
setMessage("additional properties attribute is only valid for objects");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (attribute == "minimum")
|
|
|
|
checkMinimum(value, attributeValue);
|
|
|
|
else if (attribute == "maximum")
|
|
|
|
checkMaximum(value, attributeValue);
|
2017-03-24 10:17:36 +01:00
|
|
|
else if (attribute == "minLength")
|
|
|
|
checkMinLength(value, attributeValue);
|
|
|
|
else if (attribute == "maxLength")
|
|
|
|
checkMaxLength(value, attributeValue);
|
2016-07-20 17:16:06 +02:00
|
|
|
else if (attribute == "items")
|
|
|
|
{
|
|
|
|
if (value.isArray())
|
|
|
|
checkItems(value, attributeValue.toObject());
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_error = true;
|
|
|
|
setMessage("items only valid for arrays");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (attribute == "minItems")
|
|
|
|
checkMinItems(value, attributeValue);
|
|
|
|
else if (attribute == "maxItems")
|
|
|
|
checkMaxItems(value, attributeValue);
|
|
|
|
else if (attribute == "uniqueItems")
|
|
|
|
checkUniqueItems(value, attributeValue);
|
|
|
|
else if (attribute == "enum")
|
|
|
|
checkEnum(value, attributeValue);
|
|
|
|
else if (attribute == "required")
|
|
|
|
; // nothing to do. value is present so always oke
|
|
|
|
else if (attribute == "id")
|
|
|
|
; // references have already been collected
|
2016-09-15 20:42:58 +02:00
|
|
|
else if (attribute == "title" || attribute == "description" || attribute == "default" || attribute == "format"
|
2017-02-08 14:36:28 +01:00
|
|
|
|| attribute == "defaultProperties" || attribute == "propertyOrder" || attribute == "append" || attribute == "step" || attribute == "access" || attribute == "options" || attribute == "script")
|
2016-08-19 08:50:48 +02:00
|
|
|
; // nothing to do.
|
2016-07-20 17:16:06 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// no check function defined for this attribute
|
2017-01-23 23:25:12 +01:00
|
|
|
_error = true;
|
2017-03-04 22:17:42 +01:00
|
|
|
setMessage("No check function defined for attribute " + attribute);
|
2016-07-20 17:16:06 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-04 22:17:42 +01:00
|
|
|
void QJsonSchemaChecker::setMessage(const QString & message)
|
2016-07-20 17:16:06 +02:00
|
|
|
{
|
2017-03-04 22:17:42 +01:00
|
|
|
_messages.append(_currentPath.join("") +": "+message);
|
2016-07-20 17:16:06 +02:00
|
|
|
}
|
|
|
|
|
2017-03-04 22:17:42 +01:00
|
|
|
const QStringList & QJsonSchemaChecker::getMessages() const
|
2016-07-20 17:16:06 +02:00
|
|
|
{
|
|
|
|
return _messages;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QJsonSchemaChecker::checkType(const QJsonValue & value, const QJsonValue & schema)
|
|
|
|
{
|
|
|
|
QString type = schema.toString();
|
2016-08-31 17:19:41 +02:00
|
|
|
|
2016-07-20 17:16:06 +02:00
|
|
|
bool wrongType = false;
|
|
|
|
if (type == "string")
|
|
|
|
wrongType = !value.isString();
|
|
|
|
else if (type == "number")
|
|
|
|
wrongType = !value.isDouble();
|
|
|
|
else if (type == "integer")
|
|
|
|
wrongType = (rint(value.toDouble()) != value.toDouble());
|
|
|
|
else if (type == "double")
|
|
|
|
wrongType = !value.isDouble();
|
|
|
|
else if (type == "boolean")
|
|
|
|
wrongType = !value.isBool();
|
|
|
|
else if (type == "object")
|
|
|
|
wrongType = !value.isObject();
|
|
|
|
else if (type == "array")
|
|
|
|
wrongType = !value.isArray();
|
|
|
|
else if (type == "null")
|
|
|
|
wrongType = !value.isNull();
|
|
|
|
else if (type == "enum")
|
|
|
|
wrongType = !value.isString();
|
|
|
|
else if (type == "any")
|
|
|
|
wrongType = false;
|
|
|
|
// else
|
|
|
|
// assert(false);
|
|
|
|
|
|
|
|
if (wrongType)
|
|
|
|
{
|
|
|
|
_error = true;
|
2017-03-04 22:17:42 +01:00
|
|
|
setMessage(type + " expected");
|
2016-07-20 17:16:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QJsonSchemaChecker::checkProperties(const QJsonObject & value, const QJsonObject & schema)
|
2016-08-31 17:19:41 +02:00
|
|
|
{
|
2016-07-20 17:16:06 +02:00
|
|
|
for (QJsonObject::const_iterator i = schema.begin(); i != schema.end(); ++i)
|
|
|
|
{
|
|
|
|
QString property = i.key();
|
2016-08-31 17:19:41 +02:00
|
|
|
|
2016-07-20 17:16:06 +02:00
|
|
|
const QJsonValue & propertyValue = i.value();
|
|
|
|
|
2017-03-04 22:17:42 +01:00
|
|
|
_currentPath.append("." + property);
|
2016-07-20 17:16:06 +02:00
|
|
|
QJsonObject::const_iterator required = propertyValue.toObject().find("required");
|
2016-08-31 17:19:41 +02:00
|
|
|
|
2016-07-20 17:16:06 +02:00
|
|
|
if (value.contains(property))
|
|
|
|
{
|
|
|
|
validate(value[property], propertyValue.toObject());
|
|
|
|
}
|
2016-10-09 22:22:17 +02:00
|
|
|
else if (required != propertyValue.toObject().end() && required.value().toBool() && !_ignoreRequired)
|
2016-07-20 17:16:06 +02:00
|
|
|
{
|
|
|
|
_error = true;
|
|
|
|
setMessage("missing member");
|
|
|
|
}
|
2017-03-04 22:17:42 +01:00
|
|
|
_currentPath.removeLast();
|
2016-07-20 17:16:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QJsonSchemaChecker::checkAdditionalProperties(const QJsonObject & value, const QJsonValue & schema, const QStringList & ignoredProperties)
|
|
|
|
{
|
|
|
|
for (QJsonObject::const_iterator i = value.begin(); i != value.end(); ++i)
|
|
|
|
{
|
|
|
|
QString property = i.key();
|
|
|
|
if (std::find(ignoredProperties.begin(), ignoredProperties.end(), property) == ignoredProperties.end())
|
|
|
|
{
|
|
|
|
// property has no property definition. check against the definition for additional properties
|
2017-03-04 22:17:42 +01:00
|
|
|
_currentPath.append("." + property);
|
2016-07-20 17:16:06 +02:00
|
|
|
if (schema.isBool())
|
|
|
|
{
|
|
|
|
if (schema.toBool() == false)
|
|
|
|
{
|
|
|
|
_error = true;
|
|
|
|
setMessage("no schema definition");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-07-26 14:53:27 +02:00
|
|
|
validate(value[property].toObject(), schema.toObject());
|
2016-07-20 17:16:06 +02:00
|
|
|
}
|
2017-03-04 22:17:42 +01:00
|
|
|
_currentPath.removeLast();
|
2016-07-20 17:16:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QJsonSchemaChecker::checkMinimum(const QJsonValue & value, const QJsonValue & schema)
|
|
|
|
{
|
|
|
|
if (!value.isDouble())
|
|
|
|
{
|
|
|
|
// only for numeric
|
|
|
|
_error = true;
|
|
|
|
setMessage("minimum check only for numeric fields");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (value.toDouble() < schema.toDouble())
|
|
|
|
{
|
|
|
|
_error = true;
|
2017-03-04 22:17:42 +01:00
|
|
|
setMessage("value is too small (minimum=" + schema.toString() + ")");
|
2016-07-20 17:16:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QJsonSchemaChecker::checkMaximum(const QJsonValue & value, const QJsonValue & schema)
|
|
|
|
{
|
|
|
|
if (!value.isDouble())
|
|
|
|
{
|
|
|
|
// only for numeric
|
|
|
|
_error = true;
|
|
|
|
setMessage("maximum check only for numeric fields");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (value.toDouble() > schema.toDouble())
|
|
|
|
{
|
|
|
|
_error = true;
|
2017-03-04 22:17:42 +01:00
|
|
|
setMessage("value is too large (maximum=" + schema.toString() + ")");
|
2016-07-20 17:16:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-24 10:17:36 +01:00
|
|
|
void QJsonSchemaChecker::checkMinLength(const QJsonValue & value, const QJsonValue & schema)
|
|
|
|
{
|
|
|
|
if (!value.isString())
|
|
|
|
{
|
|
|
|
// only for Strings
|
|
|
|
_error = true;
|
|
|
|
setMessage("minLength check only for string fields");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (value.toString().size() < schema.toInt())
|
|
|
|
{
|
|
|
|
_error = true;
|
|
|
|
setMessage("value is too short (minLength=" + schema.toString() + ")");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QJsonSchemaChecker::checkMaxLength(const QJsonValue & value, const QJsonValue & schema)
|
|
|
|
{
|
|
|
|
if (!value.isString())
|
|
|
|
{
|
|
|
|
// only for Strings
|
|
|
|
_error = true;
|
|
|
|
setMessage("maxLength check only for string fields");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (value.toString().size() > schema.toInt())
|
|
|
|
{
|
|
|
|
_error = true;
|
|
|
|
setMessage("value is too long (maxLength=" + schema.toString() + ")");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-20 17:16:06 +02:00
|
|
|
void QJsonSchemaChecker::checkItems(const QJsonValue & value, const QJsonObject & schema)
|
|
|
|
{
|
|
|
|
if (!value.isArray())
|
|
|
|
{
|
|
|
|
// only for arrays
|
|
|
|
_error = true;
|
|
|
|
setMessage("items only valid for arrays");
|
|
|
|
return;
|
|
|
|
}
|
2016-08-31 17:19:41 +02:00
|
|
|
|
2016-07-20 17:16:06 +02:00
|
|
|
QJsonArray jArray = value.toArray();
|
|
|
|
for(int i = 0; i < jArray.size(); ++i)
|
|
|
|
{
|
|
|
|
// validate each item
|
2017-03-04 22:17:42 +01:00
|
|
|
_currentPath.append("[" + QString::number(i) + "]");
|
2016-10-16 17:34:20 +02:00
|
|
|
validate(jArray[i], schema);
|
2017-03-04 22:17:42 +01:00
|
|
|
_currentPath.removeLast();
|
2016-07-20 17:16:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QJsonSchemaChecker::checkMinItems(const QJsonValue & value, const QJsonValue & schema)
|
|
|
|
{
|
|
|
|
if (!value.isArray())
|
|
|
|
{
|
|
|
|
// only for arrays
|
|
|
|
_error = true;
|
|
|
|
setMessage("minItems only valid for arrays");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int minimum = schema.toInt();
|
|
|
|
|
|
|
|
QJsonArray jArray = value.toArray();
|
|
|
|
if (static_cast<int>(jArray.size()) < minimum)
|
|
|
|
{
|
|
|
|
_error = true;
|
2017-03-04 22:17:42 +01:00
|
|
|
setMessage("array is too large (minimum=" + QString::number(minimum) + ")");
|
2016-07-20 17:16:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QJsonSchemaChecker::checkMaxItems(const QJsonValue & value, const QJsonValue & schema)
|
|
|
|
{
|
|
|
|
if (!value.isArray())
|
|
|
|
{
|
|
|
|
// only for arrays
|
|
|
|
_error = true;
|
|
|
|
setMessage("maxItems only valid for arrays");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int maximum = schema.toInt();
|
|
|
|
|
|
|
|
QJsonArray jArray = value.toArray();
|
|
|
|
if (static_cast<int>(jArray.size()) > maximum)
|
|
|
|
{
|
|
|
|
_error = true;
|
2017-03-04 22:17:42 +01:00
|
|
|
setMessage("array is too large (maximum=" + QString::number(maximum) + ")");
|
2016-07-20 17:16:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QJsonSchemaChecker::checkUniqueItems(const QJsonValue & value, const QJsonValue & schema)
|
|
|
|
{
|
|
|
|
if (!value.isArray())
|
|
|
|
{
|
|
|
|
// only for arrays
|
|
|
|
_error = true;
|
|
|
|
setMessage("uniqueItems only valid for arrays");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (schema.toBool() == true)
|
|
|
|
{
|
|
|
|
// make sure no two items are identical
|
|
|
|
|
|
|
|
QJsonArray jArray = value.toArray();
|
|
|
|
for(int i = 0; i < jArray.size(); ++i)
|
|
|
|
{
|
|
|
|
for (int j = i+1; j < jArray.size(); ++j)
|
|
|
|
{
|
|
|
|
if (jArray[i] == jArray[j])
|
|
|
|
{
|
|
|
|
// found a value twice
|
|
|
|
_error = true;
|
|
|
|
setMessage("array must have unique values");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QJsonSchemaChecker::checkEnum(const QJsonValue & value, const QJsonValue & schema)
|
|
|
|
{
|
|
|
|
if (schema.isArray())
|
|
|
|
{
|
|
|
|
QJsonArray jArray = schema.toArray();
|
|
|
|
for(int i = 0; i < jArray.size(); ++i)
|
|
|
|
{
|
|
|
|
if (jArray[i] == value)
|
|
|
|
{
|
|
|
|
// found enum value. done.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// nothing found
|
|
|
|
_error = true;
|
|
|
|
QJsonDocument doc(schema.toArray());
|
|
|
|
QString strJson(doc.toJson(QJsonDocument::Compact));
|
2017-03-04 22:17:42 +01:00
|
|
|
setMessage("Unknown enum value (allowed values are: " + schema.toString() + strJson+ ")");
|
2016-07-26 14:53:27 +02:00
|
|
|
}
|