aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/axivion/axivionresultparser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/axivion/axivionresultparser.cpp')
-rw-r--r--src/plugins/axivion/axivionresultparser.cpp347
1 files changed, 347 insertions, 0 deletions
diff --git a/src/plugins/axivion/axivionresultparser.cpp b/src/plugins/axivion/axivionresultparser.cpp
new file mode 100644
index 00000000000..0d8bb27b7db
--- /dev/null
+++ b/src/plugins/axivion/axivionresultparser.cpp
@@ -0,0 +1,347 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "axivionresultparser.h"
+
+#include <utils/qtcassert.h>
+
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QRegularExpression>
+
+#include <utility>
+
+namespace Axivion::Internal {
+
+static std::pair<QByteArray, QByteArray> splitHeaderAndBody(const QByteArray &input)
+{
+ QByteArray header;
+ QByteArray json;
+ int emptyLine = input.indexOf("\r\n\r\n"); // we always get \r\n as line separator
+ if (emptyLine != -1) {
+ header = input.left(emptyLine);
+ json = input.mid(emptyLine + 4);
+ } else {
+ json = input;
+ }
+ return {header, json};
+}
+
+static int httpStatus(const QByteArray &header)
+{
+ int firstHeaderEnd = header.indexOf("\r\n");
+ if (firstHeaderEnd == -1)
+ return 600; // unexpected header
+ const QString firstLine = QString::fromUtf8(header.first(firstHeaderEnd));
+ static const QRegularExpression regex(R"(^HTTP/\d\.\d (\d{3}) .*$)");
+ const QRegularExpressionMatch match = regex.match(firstLine);
+ return match.hasMatch() ? match.captured(1).toInt() : 601;
+}
+
+static BaseResult prehandleHeader(const QByteArray &header, const QByteArray &body)
+{
+ BaseResult result;
+ if (header.isEmpty()) {
+ result.error = QString::fromUtf8(body); // we likely had a curl problem
+ return result;
+ }
+ int status = httpStatus(header);
+ if ((status > 399) || (status > 299 && body.isEmpty())) { // FIXME handle some explicitly?
+ const QString statusStr = QString::number(status);
+ if (body.isEmpty() || body.startsWith('<')) // likely an html response or redirect
+ result.error = QLatin1String("(%1)").arg(statusStr);
+ else
+ result.error = QLatin1String("%1 (%2)").arg(QString::fromUtf8(body)).arg(statusStr);
+ }
+ return result;
+}
+
+static std::pair<BaseResult, QJsonDocument> prehandleHeaderAndBody(const QByteArray &header,
+ const QByteArray &body)
+{
+ BaseResult result = prehandleHeader(header, body);
+ if (!result.error.isEmpty())
+ return {result, {}};
+
+ QJsonParseError error;
+ const QJsonDocument doc = QJsonDocument::fromJson(body, &error);
+ if (error.error != QJsonParseError::NoError) {
+ result.error = error.errorString();
+ return {result, doc};
+ }
+
+ if (!doc.isObject()) {
+ result.error = "Not an object.";
+ return {result, {}};
+ }
+
+ return {result, doc};
+}
+
+static User::UserType userTypeForString(const QString &type)
+{
+ if (type == "DASHBOARD_USER")
+ return User::Dashboard;
+ if (type == "VIRTUAL_USER")
+ return User::Virtual;
+ return User::Unknown;
+}
+
+static User userFromJson(const QJsonObject &object)
+{
+ User result;
+ if (object.isEmpty()) {
+ result.error = "Not a user object.";
+ return result;
+ }
+ result.name = object.value("name").toString();
+ result.displayName = object.value("displayName").toString();
+ result.type = userTypeForString(object.value("type").toString());
+ return result;
+}
+
+static QList<User> usersFromJson(const QJsonArray &array)
+{
+ QList<User> result;
+ for (const QJsonValue &value : array) {
+ User user = userFromJson(value.toObject());
+ if (!user.error.isEmpty()) // add this error to result.error?
+ continue;
+ result.append(user);
+ }
+ return result;
+}
+
+static IssueCount issueCountFromJson(const QJsonObject &object)
+{
+ IssueCount result;
+ if (object.isEmpty()) {
+ result.error = "Not an issue count object.";
+ return result;
+ }
+ result.added = object.value("Added").toInt();
+ result.removed = object.value("Removed").toInt();
+ result.total = object.value("Total").toInt();
+ return result;
+}
+
+static QList<IssueCount> issueCountsFromJson(const QJsonObject &object)
+{
+ QList<IssueCount> result;
+
+ const QStringList keys = object.keys();
+ for (const QString &k : keys) {
+ IssueCount issue = issueCountFromJson(object.value(k).toObject());
+ if (!issue.error.isEmpty()) // add this error to result.error?
+ continue;
+ issue.issueKind = k;
+ result.append(issue);
+ }
+ return result;
+}
+
+static ResultVersion versionFromJson(const QJsonObject &object)
+{
+ ResultVersion result;
+ if (object.isEmpty()) {
+ result.error = "Not a version object.";
+ return result;
+ }
+ const QJsonValue issuesValue = object.value("issueCounts");
+ if (!issuesValue.isObject()) {
+ result.error = "Not an object (issueCounts).";
+ return result;
+ }
+ result.issueCounts = issueCountsFromJson(issuesValue.toObject());
+ result.timeStamp = object.value("date").toString();
+ result.name = object.value("name").toString();
+ result.linesOfCode = object.value("linesOfCode").toInt();
+ return result;
+}
+
+static QList<ResultVersion> versionsFromJson(const QJsonArray &array)
+{
+ QList<ResultVersion> result;
+ for (const QJsonValue &value : array) {
+ ResultVersion version = versionFromJson(value.toObject());
+ if (!version.error.isEmpty()) // add this error to result.error?
+ continue;
+ result.append(version);
+ }
+ return result;
+}
+
+static IssueKind issueKindFromJson(const QJsonObject &object)
+{
+ IssueKind result;
+ if (object.isEmpty()) {
+ result.error = "Not an issue kind object.";
+ return result;
+ }
+ result.prefix = object.value("prefix").toString();
+ result.niceSingular = object.value("niceSingularName").toString();
+ result.nicePlural = object.value("nicePluralName").toString();
+ return result;
+}
+
+static QList<IssueKind> issueKindsFromJson(const QJsonArray &array)
+{
+ QList<IssueKind> result;
+ for (const QJsonValue &value : array) {
+ IssueKind kind = issueKindFromJson(value.toObject());
+ if (!kind.error.isEmpty()) // add this error to result.error?
+ continue;
+ result.append(kind);
+ }
+ return result;
+}
+
+namespace ResultParser {
+
+DashboardInfo parseDashboardInfo(const QByteArray &input)
+{
+ DashboardInfo result;
+
+ auto [header, body] = splitHeaderAndBody(input);
+ auto [error, doc] = prehandleHeaderAndBody(header, body);
+ if (!error.error.isEmpty()) {
+ result.error = error.error;
+ return result;
+ }
+ const QJsonObject object = doc.object();
+ result.mainUrl = object.value("mainUrl").toString();
+
+ if (!object.contains("projects")) {
+ result.error = "Missing projects information.";
+ return result;
+ }
+ const QJsonValue projects = object.value("projects");
+ if (!projects.isArray()) {
+ result.error = "Projects information not an array.";
+ return result;
+ }
+ const QJsonArray array = projects.toArray();
+ for (const QJsonValue &val : array) {
+ if (!val.isObject())
+ continue;
+ const QJsonObject projectObject = val.toObject();
+ Project project;
+ project.name = projectObject.value("name").toString();
+ project.url = projectObject.value("url").toString();
+ if (project.name.isEmpty() || project.url.isEmpty())
+ continue;
+ result.projects.append(project);
+ }
+ return result;
+}
+
+ProjectInfo parseProjectInfo(const QByteArray &input)
+{
+ ProjectInfo result;
+
+ auto [header, body] = splitHeaderAndBody(input);
+ auto [error, doc] = prehandleHeaderAndBody(header, body);
+ if (!error.error.isEmpty()) {
+ result.error = error.error;
+ return result;
+ }
+
+ const QJsonObject object = doc.object();
+ result.name = object.value("name").toString();
+
+ const QJsonValue usersValue = object.value("users");
+ if (!usersValue.isArray()) {
+ result.error = "Malformed json response (users).";
+ return result;
+ }
+ result.users = usersFromJson(usersValue.toArray());
+
+ const QJsonValue versionsValue = object.value("versions");
+ if (!versionsValue.isArray()) {
+ result.error = "Malformed json response (versions).";
+ return result;
+ }
+ result.versions = versionsFromJson(versionsValue.toArray());
+
+ const QJsonValue issueKindsValue = object.value("issueKinds");
+ if (!issueKindsValue.isArray()) {
+ result.error = "Malformed json response (issueKinds).";
+ return result;
+ }
+ result.issueKinds = issueKindsFromJson(issueKindsValue.toArray());
+ return result;
+}
+
+static QRegularExpression issueCsvLineRegex(const QByteArray &firstCsvLine)
+{
+ QString pattern = "^";
+ for (const QByteArray &part : firstCsvLine.split(',')) {
+ const QString cleaned = QString::fromUtf8(part).remove(' ').chopped(1).mid(1);
+ pattern.append(QString("\"(?<" + cleaned + ">.*)\","));
+ }
+ pattern.chop(1); // remove last comma
+ pattern.append('$');
+ const QRegularExpression regex(pattern);
+ QTC_ASSERT(regex.isValid(), return {});
+ return regex;
+}
+
+static void parseCsvIssue(const QByteArray &csv, QList<ShortIssue> *issues)
+{
+ QTC_ASSERT(issues, return);
+
+ bool first = true;
+ std::optional<QRegularExpression> regex;
+ for (auto &line : csv.split('\n')) {
+ if (first) {
+ regex.emplace(issueCsvLineRegex(line));
+ first = false;
+ if (regex.value().pattern().isEmpty())
+ return;
+ continue;
+ }
+ if (line.isEmpty())
+ continue;
+ const QRegularExpressionMatch match = regex->match(QString::fromUtf8(line));
+ QTC_ASSERT(match.hasMatch(), continue);
+ // FIXME: some of these are not present for all issue kinds! Limited to SV for now
+ ShortIssue issue;
+ issue.id = match.captured("Id");
+ issue.state = match.captured("State");
+ issue.errorNumber = match.captured("ErrorNumber");
+ issue.message = match.captured("Message");
+ issue.entity = match.captured("Entity");
+ issue.filePath = match.captured("Path");
+ issue.severity = match.captured("Severity");
+ issue.lineNumber = match.captured("Line").toInt();
+ issues->append(issue);
+ }
+}
+
+IssuesList parseIssuesList(const QByteArray &input)
+{
+ IssuesList result;
+
+ auto [header, body] = splitHeaderAndBody(input);
+ BaseResult headerResult = prehandleHeader(header, body);
+ if (!headerResult.error.isEmpty()) {
+ result.error = headerResult.error;
+ return result;
+ }
+ parseCsvIssue(body, &result.issues);
+ return result;
+}
+
+QString parseRuleInfo(const QByteArray &input) // html result!
+{
+ auto [header, body] = splitHeaderAndBody(input);
+ BaseResult headerResult = prehandleHeader(header, body);
+ if (!headerResult.error.isEmpty())
+ return QString();
+ return QString::fromLocal8Bit(body);
+}
+
+} // ResultParser
+
+} // Axivion::Internal