diff --git a/src/layout/LayoutLoader.cpp b/src/layout/LayoutLoader.cpp index c1769ba..8dbe304 100644 --- a/src/layout/LayoutLoader.cpp +++ b/src/layout/LayoutLoader.cpp @@ -1,6 +1,7 @@ #include "LayoutLoader.h" #include +#include #include #include #include @@ -48,6 +49,10 @@ std::optional parseKey(const QJsonValue &value, QString *error) KeyDef key; if (value.isString()) { key.label = value.toString(); + if (key.label.isEmpty()) { + *error = QStringLiteral("empty key \"\""); + return std::nullopt; + } return key; } if (!value.isObject()) { @@ -104,6 +109,14 @@ std::optional parseKey(const QJsonValue &value, QString *error) *error = QStringLiteral("modifier must be one of %1").arg(ModifierNames.join(u", ")); return std::nullopt; } + if (key.repeat && key.type != KeyType::Keysym) { + *error = QStringLiteral("\"%1\": only keysym keys repeat").arg(typeName); + return std::nullopt; + } + if (key.stretch && (key.type == KeyType::Letters || key.type == KeyType::Keys)) { + *error = QStringLiteral("\"%1\" rows can't stretch; their keys come from the keymap").arg(typeName); + return std::nullopt; + } if (key.width <= 0) { *error = QStringLiteral("key \"%1\" has a non-positive width").arg(key.label); return std::nullopt; @@ -197,6 +210,10 @@ std::optional parse(const QByteArray &json, QString *error) auto layer = parseLayer(layerValue.toObject(), error); if (!layer) return std::nullopt; + if (layout.layer(layer->name)) { + *error = QStringLiteral("duplicate layer \"%1\"").arg(layer->name); + return std::nullopt; + } layout.layers.append(*layer); } @@ -204,6 +221,10 @@ std::optional parse(const QByteArray &json, QString *error) auto section = parseSection(sectionValue.toObject(), error); if (!section) return std::nullopt; + if (std::ranges::any_of(layout.sections, [&](const Section &s) { return s.id == section->id; })) { + *error = QStringLiteral("duplicate section \"%1\"").arg(section->id); + return std::nullopt; + } layout.sections.append(*section); } diff --git a/tests/tst_layoutloader.cpp b/tests/tst_layoutloader.cpp index 96670f7..7be77f1 100644 --- a/tests/tst_layoutloader.cpp +++ b/tests/tst_layoutloader.cpp @@ -40,6 +40,34 @@ private Q_SLOTS: QVERIFY(error.contains(u"warp")); } + void rejectsInvalidTemplates_data() + { + QTest::addColumn("json"); + QTest::addColumn("why"); + const auto row = [](const char *key) { + return QByteArray(R"({"id": "t", "layers": [{"name": "l", "rows": [[)") + key + "]]}]}"; + }; + QTest::newRow("empty key") << row(R"("")") << u"empty"_s; + QTest::newRow("repeat") << row(R"({"type": "backspace", "repeat": true})") << u"repeat"_s; + QTest::newRow("stretch") << row(R"({"type": "letters", "row": 0, "stretch": true})") << u"stretch"_s; + QTest::newRow("layer twice") + << QByteArray(R"({"id": "t", "layers": [{"name": "l", "rows": [["a"]]}, {"name": "l", "rows": [["b"]]}]})") + << u"duplicate layer"_s; + QTest::newRow("section twice") + << QByteArray(R"({"id": "t", "layers": [{"name": "l", "rows": [["a"]]}], "sections": [)" + R"({"id": "s", "rows": [["b"]]}, {"id": "s", "rows": [["c"]]}]})") + << u"duplicate section"_s; + } + + void rejectsInvalidTemplates() + { + QFETCH(QByteArray, json); + QFETCH(QString, why); + QString error; + QVERIFY(!LayoutLoader::parse(json, &error)); + QVERIFY2(error.contains(why), qPrintable(error)); + } + void rejectsMissingId() { QString error;