QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
JsonSchemaValidator.cc
Go to the documentation of this file.
2
3#include <QtCore/QFile>
4#include <QtCore/QHash>
5#include <QtCore/QJsonDocument>
6#include <QtCore/QJsonObject>
7#include <QtCore/QJsonParseError>
8#include <QtCore/QStringList>
9#include <valijson/adapters/qtjson_adapter.hpp>
10#include <valijson/schema.hpp>
11#include <valijson/schema_parser.hpp>
12#include <valijson/validation_results.hpp>
13#include <valijson/validator.hpp>
14
15#include <memory>
16
17#include "QGCLoggingCategory.h"
18
19QGC_LOGGING_CATEGORY(JsonSchemaValidatorLog, "Utilities.Parsing.Json.Schema")
20
22
23namespace {
24
25std::shared_ptr<const valijson::Schema> loadSchema(const QString& schemaResourcePath, QString& errorString)
26{
27 // Unguarded static: component-metadata validation runs on the main thread only.
28 static QHash<QString, std::shared_ptr<const valijson::Schema>> schemaCache;
29
30 if (const auto cached = schemaCache.value(schemaResourcePath)) {
31 return cached;
32 }
33
34 QFile schemaFile(schemaResourcePath);
35 if (!schemaFile.open(QIODevice::ReadOnly)) {
36 errorString = QStringLiteral("Failed to open schema resource: %1").arg(schemaResourcePath);
37 return nullptr;
38 }
39
40 QJsonParseError parseError;
41 const QJsonDocument schemaDoc = QJsonDocument::fromJson(schemaFile.readAll(), &parseError);
42 if (parseError.error != QJsonParseError::NoError) {
43 errorString = QStringLiteral("Failed to parse schema %1: %2").arg(schemaResourcePath, parseError.errorString());
44 return nullptr;
45 }
46
47 // QGC parsers pin the supported metadata version per component (CompInfoParam: version==1) while
48 // the upstream schemas require newer ones, so drop the version range and validate structure only.
49 QJsonObject schemaObj = schemaDoc.object();
50 QJsonObject schemaProps = schemaObj.value(QStringLiteral("properties")).toObject();
51 if (schemaProps.contains(QStringLiteral("version"))) {
52 QJsonObject versionSchema = schemaProps.value(QStringLiteral("version")).toObject();
53 versionSchema.remove(QStringLiteral("minimum"));
54 versionSchema.remove(QStringLiteral("maximum"));
55 schemaProps[QStringLiteral("version")] = versionSchema;
56 schemaObj[QStringLiteral("properties")] = schemaProps;
57 }
58
59 auto schema = std::make_shared<valijson::Schema>();
60 try {
61 valijson::SchemaParser parser;
62 const valijson::adapters::QtJsonAdapter schemaAdapter(schemaObj);
63 parser.populateSchema(schemaAdapter, *schema);
64 } catch (const std::exception& e) {
66 QStringLiteral("Failed to load schema %1: %2").arg(schemaResourcePath, QString::fromUtf8(e.what()));
67 return nullptr;
68 }
69
70 schemaCache.insert(schemaResourcePath, schema);
71 return schema;
72}
73
74} // namespace
75
76bool validate(const QJsonDocument& doc, const QString& schemaResourcePath, QString& errorString)
77{
78 errorString.clear();
79
80 const auto schema = loadSchema(schemaResourcePath, errorString);
81 if (!schema) {
82 return false;
83 }
84
85 valijson::Validator validator;
86 valijson::ValidationResults results;
87 const valijson::adapters::QtJsonAdapter docAdapter(doc.isArray() ? QJsonValue(doc.array())
88 : QJsonValue(doc.object()));
89
90 if (validator.validate(*schema, docAdapter, &results)) {
91 return true;
92 }
93
94 QStringList failures;
95 valijson::ValidationResults::Error error;
96 while (results.popError(error)) {
97 failures.append(QStringLiteral("%1: %2").arg(QString::fromStdString(error.jsonPointer),
98 QString::fromStdString(error.description)));
99 }
100 errorString = failures.join(QStringLiteral("; "));
101 return false;
102}
103
104} // namespace JsonSchemaValidator
QString errorString
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
bool validate(const QJsonDocument &doc, const QString &schemaResourcePath, QString &errorString)