Add readonly feature to JSONRPC

This commit is contained in:
Michael Zanetti 2019-11-05 23:49:01 +01:00
parent ad32ee86ce
commit 25152c5e27
6 changed files with 136 additions and 162 deletions

View File

@ -75,7 +75,7 @@ bool JsonValidator::checkRefs(const QVariantMap &map, const QVariantMap &api)
JsonValidator::Result JsonValidator::validateParams(const QVariantMap &params, const QString &method, const QVariantMap &api) JsonValidator::Result JsonValidator::validateParams(const QVariantMap &params, const QString &method, const QVariantMap &api)
{ {
QVariantMap paramDefinition = api.value("methods").toMap().value(method).toMap().value("params").toMap(); QVariantMap paramDefinition = api.value("methods").toMap().value(method).toMap().value("params").toMap();
m_result = validateMap(params, paramDefinition, api); m_result = validateMap(params, paramDefinition, api, QIODevice::WriteOnly);
m_result.setWhere(method + ", param " + m_result.where()); m_result.setWhere(method + ", param " + m_result.where());
return m_result; return m_result;
} }
@ -83,7 +83,7 @@ JsonValidator::Result JsonValidator::validateParams(const QVariantMap &params, c
JsonValidator::Result JsonValidator::validateReturns(const QVariantMap &returns, const QString &method, const QVariantMap &api) JsonValidator::Result JsonValidator::validateReturns(const QVariantMap &returns, const QString &method, const QVariantMap &api)
{ {
QVariantMap returnsDefinition = api.value("methods").toMap().value(method).toMap().value("returns").toMap(); QVariantMap returnsDefinition = api.value("methods").toMap().value(method).toMap().value("returns").toMap();
m_result = validateMap(returns, returnsDefinition, api); m_result = validateMap(returns, returnsDefinition, api, QIODevice::ReadOnly);
m_result.setWhere(method + ", returns " + m_result.where()); m_result.setWhere(method + ", returns " + m_result.where());
return m_result; return m_result;
} }
@ -91,7 +91,7 @@ JsonValidator::Result JsonValidator::validateReturns(const QVariantMap &returns,
JsonValidator::Result JsonValidator::validateNotificationParams(const QVariantMap &params, const QString &notification, const QVariantMap &api) JsonValidator::Result JsonValidator::validateNotificationParams(const QVariantMap &params, const QString &notification, const QVariantMap &api)
{ {
QVariantMap paramDefinition = api.value("notifications").toMap().value(notification).toMap().value("params").toMap(); QVariantMap paramDefinition = api.value("notifications").toMap().value(notification).toMap().value("params").toMap();
m_result = validateMap(params, paramDefinition, api); m_result = validateMap(params, paramDefinition, api, QIODevice::ReadOnly);
m_result.setWhere(notification + ", param " + m_result.where()); m_result.setWhere(notification + ", param " + m_result.where());
return m_result; return m_result;
} }
@ -101,14 +101,21 @@ JsonValidator::Result JsonValidator::result() const
return m_result; return m_result;
} }
JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const QVariantMap &definition, const QVariantMap &api) JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const QVariantMap &definition, const QVariantMap &api, QIODevice::OpenMode openMode)
{ {
// Make sure all required values are available // Make sure all required values are available
foreach (const QString &key, definition.keys()) { foreach (const QString &key, definition.keys()) {
if (key.startsWith("o:")) { QRegExp isOptional = QRegExp("^([a-z]:)*o:.*");
if (isOptional.exactMatch(key)) {
continue; continue;
} }
if (!map.contains(key)) { QRegExp isReadOnly = QRegExp("^([a-z]:)*r:.*");
if (isReadOnly.exactMatch(key) && openMode.testFlag(QIODevice::WriteOnly)) {
continue;
}
QString trimmedKey = key;
trimmedKey.remove(QRegExp("^(o:|r:)"));
if (!map.contains(trimmedKey)) {
return Result(false, "Missing required key: " + key, key); return Result(false, "Missing required key: " + key, key);
} }
} }
@ -117,6 +124,15 @@ JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const Q
foreach (const QString &key, map.keys()) { foreach (const QString &key, map.keys()) {
// Is the key allowed in here? // Is the key allowed in here?
QVariant expectedValue = definition.value(key); QVariant expectedValue = definition.value(key);
foreach (const QString &definitionKey, definition.keys()) {
QRegExp regExp = QRegExp("(o:|r:)*" + key);
if (regExp.exactMatch(definitionKey)) {
expectedValue = definition.value(definitionKey);
}
}
if (!expectedValue.isValid()) {
expectedValue = definition.value("o:" + key);
}
if (!expectedValue.isValid()) { if (!expectedValue.isValid()) {
expectedValue = definition.value("o:" + key); expectedValue = definition.value("o:" + key);
} }
@ -127,7 +143,7 @@ JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const Q
// Validate content // Validate content
QVariant value = map.value(key); QVariant value = map.value(key);
Result result = validateEntry(value, expectedValue, api); Result result = validateEntry(value, expectedValue, api, openMode);
if (!result.success()) { if (!result.success()) {
result.setWhere(key + '.' + result.where()); result.setWhere(key + '.' + result.where());
result.setErrorString(result.errorString()); result.setErrorString(result.errorString());
@ -139,7 +155,7 @@ JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const Q
return Result(true); return Result(true);
} }
JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const QVariant &definition, const QVariantMap &api) JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const QVariant &definition, const QVariantMap &api, QIODevice::OpenMode openMode)
{ {
if (definition.type() == QVariant::String) { if (definition.type() == QVariant::String) {
QString expectedTypeName = definition.toString(); QString expectedTypeName = definition.toString();
@ -168,7 +184,7 @@ JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const
} }
QString flagEnum = refDefinition.toList().first().toString(); QString flagEnum = refDefinition.toList().first().toString();
foreach (const QVariant &flagsEntry, value.toList()) { foreach (const QVariant &flagsEntry, value.toList()) {
Result result = validateEntry(flagsEntry, flagEnum, api); Result result = validateEntry(flagsEntry, flagEnum, api, openMode);
if (!result.success()) { if (!result.success()) {
return result; return result;
} }
@ -178,7 +194,7 @@ JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const
QVariantMap types = api.value("types").toMap(); QVariantMap types = api.value("types").toMap();
QVariant refDefinition = types.value(refName); QVariant refDefinition = types.value(refName);
return validateEntry(value, refDefinition, api); return validateEntry(value, refDefinition, api, openMode);
} }
JsonHandler::BasicType expectedBasicType = JsonHandler::enumNameToValue<JsonHandler::BasicType>(expectedTypeName); JsonHandler::BasicType expectedBasicType = JsonHandler::enumNameToValue<JsonHandler::BasicType>(expectedTypeName);
@ -240,7 +256,7 @@ JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const
if (value.type() != QVariant::Map) { if (value.type() != QVariant::Map) {
return Result(false, "Invalid value. Expected a map bug received: " + value.toString()); return Result(false, "Invalid value. Expected a map bug received: " + value.toString());
} }
return validateMap(value.toMap(), definition.toMap(), api); return validateMap(value.toMap(), definition.toMap(), api, openMode);
} }
if (definition.type() == QVariant::List) { if (definition.type() == QVariant::List) {
@ -250,7 +266,7 @@ JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const
return Result(false, "Expected list of " + entryDefinition.toString() + " but got value of type " + value.typeName() + "\n" + QJsonDocument::fromVariant(value).toJson()); return Result(false, "Expected list of " + entryDefinition.toString() + " but got value of type " + value.typeName() + "\n" + QJsonDocument::fromVariant(value).toJson());
} }
foreach (const QVariant &entry, value.toList()) { foreach (const QVariant &entry, value.toList()) {
Result result = validateEntry(entry, entryDefinition, api); Result result = validateEntry(entry, entryDefinition, api, openMode);
if (!result.success()) { if (!result.success()) {
return result; return result;
} }

View File

@ -23,6 +23,7 @@
#include <QPair> #include <QPair>
#include <QVariant> #include <QVariant>
#include <QIODevice>
namespace nymeaserver { namespace nymeaserver {
@ -58,8 +59,8 @@ public:
Result result() const; Result result() const;
private: private:
Result validateMap(const QVariantMap &map, const QVariantMap &definition, const QVariantMap &api); Result validateMap(const QVariantMap &map, const QVariantMap &definition, const QVariantMap &api, QIODevice::OpenMode openMode);
Result validateEntry(const QVariant &value, const QVariant &definition, const QVariantMap &api); Result validateEntry(const QVariant &value, const QVariant &definition, const QVariantMap &api, QIODevice::OpenMode openMode);
Result m_result; Result m_result;
}; };

View File

@ -173,6 +173,9 @@ void JsonHandler::registerObject(const QMetaObject &metaObject)
if (metaProperty.isUser()) { if (metaProperty.isUser()) {
name.prepend("o:"); name.prepend("o:");
} }
if (!metaProperty.isWritable()) {
name.prepend("r:");
}
QVariant typeName; QVariant typeName;
if (metaProperty.type() == QVariant::UserType) { if (metaProperty.type() == QVariant::UserType) {
if (metaProperty.typeName() == QStringLiteral("QVariant::Type")) { if (metaProperty.typeName() == QStringLiteral("QVariant::Type")) {
@ -305,12 +308,22 @@ QVariant JsonHandler::pack(const QMetaObject &metaObject, const void *value) con
continue; continue;
} }
// Manually converting QList<int>... Only QVariantList is known to the meta system // Manually converting QList<BasicType>... Only QVariantList is known to the meta system
if (propertyTypeName.startsWith("QList<int>")) { if (propertyTypeName.startsWith("QList<")) {
QVariantList list; QVariantList list;
foreach (int entry, propertyValue.value<QList<int>>()) { if (propertyTypeName == "QList<int>") {
list << entry; foreach (int entry, propertyValue.value<QList<int>>()) {
list << entry;
}
} else if (propertyTypeName == "QList<QUuid>") {
foreach (const QUuid &entry, propertyValue.value<QList<QUuid>>()) {
list << entry;
}
} else {
Q_ASSERT_X(false, this->metaObject()->className(), QString("Unhandled list type: %1").arg(propertyTypeName).toUtf8());
qCWarning(dcJsonRpc()) << "Cannot pack property of unhandled list type" << propertyTypeName;
} }
if (!list.isEmpty() || !metaProperty.isUser()) { if (!list.isEmpty() || !metaProperty.isUser()) {
ret.insert(metaProperty.name(), list); ret.insert(metaProperty.name(), list);
} }
@ -410,12 +423,20 @@ QVariant JsonHandler::unpack(const QMetaObject &metaObject, const QVariant &valu
continue; continue;
} }
if (metaProperty.typeName() == QStringLiteral("QList<int>")) { if (QString(metaProperty.typeName()).startsWith("QList<")) {
QList<int> intList; if (metaProperty.typeName() == QStringLiteral("QList<int>")) {
foreach (const QVariant &val, variant.toList()) { QList<int> intList;
intList.append(val.toInt()); foreach (const QVariant &val, variant.toList()) {
intList.append(val.toInt());
}
metaProperty.writeOnGadget(ptr, QVariant::fromValue(intList));
} else if (metaProperty.typeName() == QStringLiteral("QList<QUuid>")) {
QList<QUuid> uuidList;
foreach (const QVariant &val, variant.toList()) {
uuidList.append(val.toUuid());
}
metaProperty.writeOnGadget(ptr, QVariant::fromValue(uuidList));
} }
metaProperty.writeOnGadget(ptr, QVariant::fromValue(intList));
continue; continue;
} }

View File

@ -210,43 +210,3 @@ QDebug operator<<(QDebug dbg, const RepeatingOption &repeatingOption)
dbg.nospace() << "RepeatingOption(Mode:" << repeatingOption.mode() << ", Monthdays:" << repeatingOption.monthDays() << "Weekdays:" << repeatingOption.weekDays() << ")"; dbg.nospace() << "RepeatingOption(Mode:" << repeatingOption.mode() << ", Monthdays:" << repeatingOption.monthDays() << "Weekdays:" << repeatingOption.weekDays() << ")";
return dbg; return dbg;
} }
WeekDays::WeekDays()
{
}
WeekDays::WeekDays(const QList<int> &other): QList<int>(other)
{
}
QVariant WeekDays::get(int index) const
{
return at(index);
}
void WeekDays::put(const QVariant &value)
{
append(value.toInt());
}
MonthDays::MonthDays()
{
}
MonthDays::MonthDays(const QList<int> &other): QList<int>(other)
{
}
QVariant MonthDays::get(int index) const
{
return at(index);
}
void MonthDays::put(const QVariant &value)
{
append(value.toInt());
}

View File

@ -27,30 +27,6 @@
class QDateTime; class QDateTime;
class WeekDays: public QList<int>
{
Q_GADGET
Q_PROPERTY(int count READ count)
public:
WeekDays();
WeekDays(const QList<int> &other);
Q_INVOKABLE QVariant get(int index) const;
Q_INVOKABLE void put(const QVariant &value);
};
Q_DECLARE_METATYPE(WeekDays)
class MonthDays: public QList<int>
{
Q_GADGET
Q_PROPERTY(int count READ count)
public:
MonthDays();
MonthDays(const QList<int> &other);
Q_INVOKABLE QVariant get(int index) const;
Q_INVOKABLE void put(const QVariant &value);
};
Q_DECLARE_METATYPE(MonthDays)
class RepeatingOption class RepeatingOption
{ {
Q_GADGET Q_GADGET

View File

@ -1683,10 +1683,10 @@
}, },
"ActionType": { "ActionType": {
"displayName": "String", "displayName": "String",
"id": "Uuid",
"index": "Int", "index": "Int",
"name": "String", "name": "String",
"paramTypes": "$ref:ParamTypes" "paramTypes": "$ref:ParamTypes",
"r:id": "Uuid"
}, },
"ActionTypes": [ "ActionTypes": [
"$ref:ActionType" "$ref:ActionType"
@ -1715,51 +1715,51 @@
"$ref:CalendarItem" "$ref:CalendarItem"
], ],
"Device": { "Device": {
"deviceClassId": "Uuid",
"id": "Uuid",
"name": "String", "name": "String",
"o:parentId": "Uuid", "o:parentId": "Uuid",
"params": "$ref:ParamList", "params": "$ref:ParamList",
"r:deviceClassId": "Uuid",
"r:id": "Uuid",
"settings": "$ref:ParamList", "settings": "$ref:ParamList",
"setupComplete": "Bool", "setupComplete": "Bool",
"states": "$ref:States" "states": "$ref:States"
}, },
"DeviceClass": { "DeviceClass": {
"actionTypes": "$ref:ActionTypes", "r:actionTypes": "$ref:ActionTypes",
"browsable": "Bool", "r:browsable": "Bool",
"browserItemActionTypes": "$ref:ActionTypes", "r:browserItemActionTypes": "$ref:ActionTypes",
"createMethods": "$ref:CreateMethods", "r:createMethods": "$ref:CreateMethods",
"discoveryParamTypes": "$ref:ParamTypes", "r:discoveryParamTypes": "$ref:ParamTypes",
"displayName": "String", "r:displayName": "String",
"eventTypes": "$ref:EventTypes", "r:eventTypes": "$ref:EventTypes",
"id": "Uuid", "r:id": "Uuid",
"interfaces": "StringList", "r:interfaces": "StringList",
"name": "String", "r:name": "String",
"paramTypes": "$ref:ParamTypes", "r:paramTypes": "$ref:ParamTypes",
"pluginId": "Uuid", "r:pluginId": "Uuid",
"settingsTypes": "$ref:ParamTypes", "r:settingsTypes": "$ref:ParamTypes",
"setupMethod": "$ref:SetupMethod", "r:setupMethod": "$ref:SetupMethod",
"stateTypes": "$ref:StateTypes", "r:stateTypes": "$ref:StateTypes",
"vendorId": "Uuid" "r:vendorId": "Uuid"
}, },
"DeviceClasses": [ "DeviceClasses": [
"$ref:DeviceClass" "$ref:DeviceClass"
], ],
"DeviceDescriptor": { "DeviceDescriptor": {
"description": "String", "r:description": "String",
"deviceParams": "$ref:ParamList", "r:deviceParams": "$ref:ParamList",
"id": "Uuid", "r:id": "Uuid",
"o:deviceId": "Uuid", "r:o:deviceId": "Uuid",
"title": "String" "r:title": "String"
}, },
"DeviceDescriptors": [ "DeviceDescriptors": [
"$ref:DeviceDescriptor" "$ref:DeviceDescriptor"
], ],
"DevicePlugin": { "DevicePlugin": {
"displayName": "String", "r:displayName": "String",
"id": "Uuid", "r:id": "Uuid",
"name": "String", "r:name": "String",
"paramTypes": "$ref:ParamTypes" "r:paramTypes": "$ref:ParamTypes"
}, },
"DevicePlugins": [ "DevicePlugins": [
"$ref:DevicePlugin" "$ref:DevicePlugin"
@ -1768,9 +1768,9 @@
"$ref:Device" "$ref:Device"
], ],
"Event": { "Event": {
"deviceId": "Uuid", "r:deviceId": "Uuid",
"eventTypeId": "Uuid", "r:eventTypeId": "Uuid",
"params": "$ref:ParamList" "r:params": "$ref:ParamList"
}, },
"EventDescriptor": { "EventDescriptor": {
"o:deviceId": "Uuid", "o:deviceId": "Uuid",
@ -1784,10 +1784,10 @@
], ],
"EventType": { "EventType": {
"displayName": "String", "displayName": "String",
"id": "Uuid",
"index": "Int",
"name": "String", "name": "String",
"paramTypes": "$ref:ParamTypes" "paramTypes": "$ref:ParamTypes",
"r:id": "Uuid",
"r:index": "Int"
}, },
"EventTypes": [ "EventTypes": [
"$ref:EventType" "$ref:EventType"
@ -1800,15 +1800,15 @@
"$ref:LogEntry" "$ref:LogEntry"
], ],
"LogEntry": { "LogEntry": {
"loggingLevel": "$ref:LoggingLevel", "r:loggingLevel": "$ref:LoggingLevel",
"o:active": "Bool", "r:o:active": "Bool",
"o:deviceId": "Uuid", "r:o:deviceId": "Uuid",
"o:errorCode": "String", "r:o:errorCode": "String",
"o:eventType": "$ref:LoggingEventType", "r:o:eventType": "$ref:LoggingEventType",
"o:typeId": "Uuid", "r:o:typeId": "Uuid",
"o:value": "Variant", "r:o:value": "Variant",
"source": "$ref:LoggingSource", "r:source": "$ref:LoggingSource",
"timestamp": "Uint" "r:timestamp": "Uint"
}, },
"MqttPolicy": { "MqttPolicy": {
"allowedPublishTopicFilters": "StringList", "allowedPublishTopicFilters": "StringList",
@ -1818,15 +1818,15 @@
"username": "String" "username": "String"
}, },
"Package": { "Package": {
"canRemove": "Bool", "r:canRemove": "Bool",
"candidateVersion": "String", "r:candidateVersion": "String",
"changelog": "String", "r:changelog": "String",
"displayName": "String", "r:displayName": "String",
"id": "String", "r:id": "String",
"installedVersion": "String", "r:installedVersion": "String",
"rollbackAvailable": "Bool", "r:rollbackAvailable": "Bool",
"summary": "String", "r:summary": "String",
"updateAvailable": "Bool" "r:updateAvailable": "Bool"
}, },
"Packages": [ "Packages": [
"$ref:Package" "$ref:Package"
@ -1849,7 +1849,6 @@
], ],
"ParamType": { "ParamType": {
"displayName": "String", "displayName": "String",
"id": "Uuid",
"index": "Int", "index": "Int",
"name": "String", "name": "String",
"o:allowedValues": [ "o:allowedValues": [
@ -1861,6 +1860,7 @@
"o:minValue": "Variant", "o:minValue": "Variant",
"o:readOnly": "Bool", "o:readOnly": "Bool",
"o:unit": "$ref:Unit", "o:unit": "$ref:Unit",
"r:id": "Uuid",
"type": "$ref:BasicType" "type": "$ref:BasicType"
}, },
"ParamTypes": [ "ParamTypes": [
@ -1879,21 +1879,21 @@
"$ref:Repository" "$ref:Repository"
], ],
"Repository": { "Repository": {
"displayName": "String",
"enabled": "Bool", "enabled": "Bool",
"id": "String" "r:displayName": "String",
"r:id": "String"
}, },
"Rule": { "Rule": {
"actions": "$ref:RuleActions", "actions": "$ref:RuleActions",
"name": "String", "name": "String",
"o:active": "Bool",
"o:enabled": "Bool", "o:enabled": "Bool",
"o:eventDescriptors": "$ref:EventDescriptors", "o:eventDescriptors": "$ref:EventDescriptors",
"o:executable": "Bool", "o:executable": "Bool",
"o:exitActions": "$ref:RuleActions", "o:exitActions": "$ref:RuleActions",
"o:id": "Uuid", "o:id": "Uuid",
"o:stateEvaluator": "$ref:StateEvaluator", "o:stateEvaluator": "$ref:StateEvaluator",
"o:timeDescriptor": "$ref:TimeDescriptor" "o:timeDescriptor": "$ref:TimeDescriptor",
"r:o:active": "Bool"
}, },
"RuleAction": { "RuleAction": {
"o:actionTypeId": "Uuid", "o:actionTypeId": "Uuid",
@ -1936,8 +1936,8 @@
"sslEnabled": "Bool" "sslEnabled": "Bool"
}, },
"State": { "State": {
"stateTypeId": "Uuid", "r:stateTypeId": "Uuid",
"value": "Variant" "r:value": "Variant"
}, },
"StateDescriptor": { "StateDescriptor": {
"o:deviceId": "Uuid", "o:deviceId": "Uuid",
@ -1958,7 +1958,6 @@
"StateType": { "StateType": {
"defaultValue": "Variant", "defaultValue": "Variant",
"displayName": "String", "displayName": "String",
"id": "Uuid",
"index": "Int", "index": "Int",
"name": "String", "name": "String",
"o:maxValue": "Variant", "o:maxValue": "Variant",
@ -1967,6 +1966,7 @@
"Variant" "Variant"
], ],
"o:unit": "$ref:Unit", "o:unit": "$ref:Unit",
"r:id": "Uuid",
"type": "$ref:BasicType" "type": "$ref:BasicType"
}, },
"StateTypes": [ "StateTypes": [
@ -1998,10 +1998,10 @@
"$ref:TimeEventItem" "$ref:TimeEventItem"
], ],
"TokenInfo": { "TokenInfo": {
"creationTime": "Uint", "r:creationTime": "Uint",
"deviveName": "String", "r:deviveName": "String",
"id": "Uuid", "r:id": "Uuid",
"username": "String" "r:username": "String"
}, },
"Vendor": { "Vendor": {
"displayName": "String", "displayName": "String",
@ -2020,18 +2020,18 @@
"sslEnabled": "Bool" "sslEnabled": "Bool"
}, },
"WiredNetworkDevice": { "WiredNetworkDevice": {
"bitRate": "String", "r:bitRate": "String",
"interface": "String", "r:interface": "String",
"macAddress": "String", "r:macAddress": "String",
"pluggedIn": "Bool", "r:pluggedIn": "Bool",
"state": "$ref:NetworkDeviceState" "r:state": "$ref:NetworkDeviceState"
}, },
"WirelessAccessPoint": { "WirelessAccessPoint": {
"frequency": "Double", "r:frequency": "Double",
"macAddress": "String", "r:macAddress": "String",
"protected": "Bool", "r:protected": "Bool",
"signalStrength": "Int", "r:signalStrength": "Int",
"ssid": "String" "r:ssid": "String"
}, },
"WirelessNetworkDevice": { "WirelessNetworkDevice": {
"bitRate": "String", "bitRate": "String",