forked from qt-creator/qt-creator
Axivion: Analyze Dashboard server response for errors
Change-Id: Id66c3ad5d8a6c7d73e7ad781893c936b0829cfbf Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -14,4 +14,5 @@ add_qtc_plugin(Axivion
|
||||
dashboard/dto.cpp dashboard/dto.h
|
||||
dashboard/concat.cpp dashboard/concat.h
|
||||
dashboard/dashboardclient.cpp dashboard/dashboardclient.h
|
||||
dashboard/error.h dashboard/error.cpp
|
||||
)
|
||||
|
||||
@@ -26,14 +26,12 @@ QtcPlugin {
|
||||
"axivionsettings.cpp",
|
||||
"axivionsettings.h",
|
||||
"axiviontr.h",
|
||||
"dashboard/dashboardclient.cpp",
|
||||
"dashboard/dashboardclient.h",
|
||||
]
|
||||
|
||||
cpp.includePaths: base.concat(["."]) // needed for the generated stuff below
|
||||
|
||||
Group {
|
||||
name: "Generated DTOs"
|
||||
name: "Dashboard Communication"
|
||||
prefix: "dashboard/"
|
||||
|
||||
files: [
|
||||
@@ -41,6 +39,10 @@ QtcPlugin {
|
||||
"concat.h",
|
||||
"dto.cpp",
|
||||
"dto.h",
|
||||
"dashboardclient.cpp",
|
||||
"dashboardclient.h",
|
||||
"error.cpp",
|
||||
"error.h",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,8 @@ void AxivionPluginPrivate::handleProjectInfo(DashboardClient::RawProjectInfo raw
|
||||
{
|
||||
m_runningQuery = false;
|
||||
if (!rawInfo) {
|
||||
Core::MessageManager::writeFlashing(QStringLiteral(u"Axivion: ") + rawInfo.error());
|
||||
Core::MessageManager::writeFlashing(
|
||||
QStringLiteral(u"Axivion: %1").arg(rawInfo.error().message()));
|
||||
return;
|
||||
}
|
||||
m_currentProjectInfo = std::make_shared<const DashboardClient::ProjectInfo>(std::move(rawInfo.value()));
|
||||
|
||||
@@ -30,13 +30,17 @@ static void deleteLater(QObject *obj)
|
||||
obj->deleteLater();
|
||||
}
|
||||
|
||||
using RawBody = Utils::expected_str<DataWithOrigin<QByteArray>>;
|
||||
using RawBody = Utils::expected<DataWithOrigin<QByteArray>, Error>;
|
||||
|
||||
static constexpr int httpStatusCodeOk = 200;
|
||||
static constexpr QLatin1String jsonContentType{ "application/json" };
|
||||
|
||||
class RawBodyReader final
|
||||
{
|
||||
public:
|
||||
RawBodyReader(std::shared_ptr<QNetworkReply> reply)
|
||||
: m_reply(std::move(reply))
|
||||
RawBodyReader(std::shared_ptr<QNetworkReply> reply, QAnyStringView expectedContentType)
|
||||
: m_reply(std::move(reply)),
|
||||
m_expectedContentType(expectedContentType)
|
||||
{ }
|
||||
|
||||
~RawBodyReader() { }
|
||||
@@ -44,19 +48,47 @@ public:
|
||||
RawBody operator()()
|
||||
{
|
||||
QNetworkReply::NetworkError error = m_reply->error();
|
||||
if (error != QNetworkReply::NetworkError::NoError)
|
||||
return tl::make_unexpected(QString::number(error)
|
||||
+ QLatin1String(": ")
|
||||
+ m_reply->errorString());
|
||||
return DataWithOrigin(m_reply->url(), m_reply->readAll());
|
||||
int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QString contentType = m_reply->header(QNetworkRequest::ContentTypeHeader)
|
||||
.toString()
|
||||
.split(';')
|
||||
.constFirst()
|
||||
.trimmed()
|
||||
.toLower();
|
||||
if (error == QNetworkReply::NetworkError::NoError
|
||||
&& statusCode == httpStatusCodeOk
|
||||
&& contentType == m_expectedContentType) {
|
||||
return DataWithOrigin(m_reply->url(), m_reply->readAll());
|
||||
}
|
||||
if (contentType == jsonContentType) {
|
||||
try {
|
||||
return tl::make_unexpected(DashboardError(
|
||||
m_reply->url(),
|
||||
statusCode,
|
||||
m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
|
||||
Dto::ErrorDto::deserialize(m_reply->readAll())));
|
||||
} catch (const Dto::invalid_dto_exception &) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (statusCode != 0) {
|
||||
return tl::make_unexpected(HttpError(
|
||||
m_reply->url(),
|
||||
statusCode,
|
||||
m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
|
||||
QString::fromUtf8(m_reply->readAll()))); // encoding?
|
||||
}
|
||||
return tl::make_unexpected(
|
||||
NetworkError(m_reply->url(), error, m_reply->errorString()));
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<QNetworkReply> m_reply;
|
||||
QAnyStringView m_expectedContentType;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
static Utils::expected_str<DataWithOrigin<T>> RawBodyParser(RawBody rawBody)
|
||||
static Utils::expected<DataWithOrigin<T>, Error> RawBodyParser(RawBody rawBody)
|
||||
{
|
||||
if (!rawBody)
|
||||
return tl::make_unexpected(std::move(rawBody.error()));
|
||||
@@ -65,7 +97,8 @@ static Utils::expected_str<DataWithOrigin<T>> RawBodyParser(RawBody rawBody)
|
||||
return DataWithOrigin(std::move(rawBody.value().origin),
|
||||
std::move(data));
|
||||
} catch (const Dto::invalid_dto_exception &e) {
|
||||
return tl::make_unexpected(QString::fromUtf8(e.what()));
|
||||
return tl::make_unexpected(GeneralError(std::move(rawBody.value().origin),
|
||||
QString::fromUtf8(e.what())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +124,7 @@ QFuture<DashboardClient::RawProjectInfo> DashboardClient::fetchProjectInfo(const
|
||||
std::shared_ptr<QNetworkReply> reply{ this->m_networkAccessManager.get(request), deleteLater };
|
||||
return QtFuture::connect(reply.get(), &QNetworkReply::finished)
|
||||
.onCanceled(reply.get(), [reply] { reply->abort(); })
|
||||
.then(RawBodyReader(reply))
|
||||
.then(RawBodyReader(reply, jsonContentType))
|
||||
.then(QtFuture::Launch::Async, &RawBodyParser<Dto::ProjectInfoDto>);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include "dashboard/dto.h"
|
||||
#include "dashboard/error.h"
|
||||
|
||||
#include <utils/expected.h>
|
||||
#include <utils/networkaccessmanager.h>
|
||||
@@ -32,7 +33,7 @@ class DashboardClient
|
||||
{
|
||||
public:
|
||||
using ProjectInfo = DataWithOrigin<Dto::ProjectInfoDto>;
|
||||
using RawProjectInfo = Utils::expected_str<ProjectInfo>;
|
||||
using RawProjectInfo = Utils::expected<ProjectInfo, Error>;
|
||||
|
||||
DashboardClient(Utils::NetworkAccessManager &networkAccessManager);
|
||||
|
||||
|
||||
107
src/plugins/axivion/dashboard/error.cpp
Normal file
107
src/plugins/axivion/dashboard/error.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2022-current by Axivion GmbH
|
||||
* https://www.axivion.com/
|
||||
*
|
||||
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
*/
|
||||
|
||||
#include "dashboard/error.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace Axivion::Internal
|
||||
{
|
||||
|
||||
CommunicationError::CommunicationError(QUrl replyUrl)
|
||||
: replyUrl(std::move(replyUrl))
|
||||
{
|
||||
}
|
||||
|
||||
GeneralError::GeneralError(QUrl replyUrl, QString message)
|
||||
: CommunicationError(replyUrl),
|
||||
message(std::move(message))
|
||||
{
|
||||
}
|
||||
|
||||
NetworkError::NetworkError(QUrl replyUrl,
|
||||
QNetworkReply::NetworkError networkError,
|
||||
QString networkErrorString)
|
||||
: CommunicationError(std::move(replyUrl)),
|
||||
networkError(std::move(networkError)),
|
||||
networkErrorString(std::move(networkErrorString))
|
||||
{
|
||||
}
|
||||
|
||||
HttpError::HttpError(QUrl replyUrl, int httpStatusCode, QString httpReasonPhrase, QString body)
|
||||
: CommunicationError(std::move(replyUrl)),
|
||||
httpStatusCode(httpStatusCode),
|
||||
httpReasonPhrase(std::move(httpReasonPhrase)),
|
||||
body(std::move(body))
|
||||
{
|
||||
}
|
||||
|
||||
DashboardError::DashboardError(QUrl replyUrl, int httpStatusCode, QString httpReasonPhrase, Dto::ErrorDto error)
|
||||
: CommunicationError(std::move(replyUrl)),
|
||||
httpStatusCode(httpStatusCode),
|
||||
httpReasonPhrase(std::move(httpReasonPhrase)),
|
||||
dashboardVersion(std::move(error.dashboardVersionNumber)),
|
||||
type(std::move(error.type)),
|
||||
message(std::move(error.message))
|
||||
{
|
||||
}
|
||||
|
||||
Error::Error(GeneralError error) : m_error(std::move(error))
|
||||
{
|
||||
}
|
||||
|
||||
Error::Error(NetworkError error) : m_error(std::move(error))
|
||||
{
|
||||
}
|
||||
|
||||
Error::Error(HttpError error) : m_error(std::move(error))
|
||||
{
|
||||
}
|
||||
|
||||
Error::Error(DashboardError error) : m_error(std::move(error))
|
||||
{
|
||||
}
|
||||
|
||||
// https://www.cppstories.com/2018/09/visit-variants/
|
||||
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>; // line not needed in C++20...
|
||||
|
||||
QString Error::message() const
|
||||
{
|
||||
return std::visit(
|
||||
overloaded{
|
||||
[](const GeneralError &error) {
|
||||
return QStringLiteral(u"GeneralError (%1) %2")
|
||||
.arg(error.replyUrl.toString(),
|
||||
error.message);
|
||||
},
|
||||
[](const NetworkError &error) {
|
||||
return QStringLiteral(u"NetworkError (%1) %2: %3")
|
||||
.arg(error.replyUrl.toString(),
|
||||
QString::number(error.networkError),
|
||||
error.networkErrorString);
|
||||
},
|
||||
[](const HttpError &error) {
|
||||
return QStringLiteral(u"HttpError (%1) %2: %3\n%4")
|
||||
.arg(error.replyUrl.toString(),
|
||||
QString::number(error.httpStatusCode),
|
||||
error.httpReasonPhrase,
|
||||
error.body);
|
||||
},
|
||||
[](const DashboardError &error) {
|
||||
return QStringLiteral(u"DashboardError (%1) [%2 %3] %4: %5")
|
||||
.arg(error.replyUrl.toString(),
|
||||
QString::number(error.httpStatusCode),
|
||||
error.httpReasonPhrase,
|
||||
error.type,
|
||||
error.message);
|
||||
},
|
||||
}, this->m_error);
|
||||
}
|
||||
|
||||
} // namespace Axivion::Internal
|
||||
86
src/plugins/axivion/dashboard/error.h
Normal file
86
src/plugins/axivion/dashboard/error.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022-current by Axivion GmbH
|
||||
* https://www.axivion.com/
|
||||
*
|
||||
* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
*/
|
||||
|
||||
#include "dashboard/dto.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace Axivion::Internal
|
||||
{
|
||||
|
||||
class CommunicationError
|
||||
{
|
||||
public:
|
||||
QUrl replyUrl;
|
||||
|
||||
CommunicationError(QUrl replyUrl);
|
||||
};
|
||||
|
||||
class GeneralError : public CommunicationError
|
||||
{
|
||||
public:
|
||||
QString message;
|
||||
|
||||
GeneralError(QUrl replyUrl, QString message);
|
||||
};
|
||||
|
||||
class NetworkError : public CommunicationError
|
||||
{
|
||||
public:
|
||||
QNetworkReply::NetworkError networkError;
|
||||
QString networkErrorString;
|
||||
|
||||
NetworkError(QUrl replyUrl, QNetworkReply::NetworkError networkError, QString networkErrorString);
|
||||
};
|
||||
|
||||
class HttpError : public CommunicationError
|
||||
{
|
||||
public:
|
||||
int httpStatusCode;
|
||||
QString httpReasonPhrase;
|
||||
QString body;
|
||||
|
||||
HttpError(QUrl replyUrl, int httpStatusCode, QString httpReasonPhrase, QString body);
|
||||
};
|
||||
|
||||
class DashboardError : public CommunicationError
|
||||
{
|
||||
public:
|
||||
int httpStatusCode;
|
||||
QString httpReasonPhrase;
|
||||
std::optional<QString> dashboardVersion;
|
||||
QString type;
|
||||
QString message;
|
||||
|
||||
DashboardError(QUrl replyUrl, int httpStatusCode, QString httpReasonPhrase, Dto::ErrorDto error);
|
||||
};
|
||||
|
||||
class Error
|
||||
{
|
||||
public:
|
||||
Error(GeneralError error);
|
||||
|
||||
Error(NetworkError error);
|
||||
|
||||
Error(HttpError error);
|
||||
|
||||
Error(DashboardError error);
|
||||
|
||||
QString message() const;
|
||||
|
||||
private:
|
||||
std::variant<GeneralError,
|
||||
NetworkError,
|
||||
HttpError,
|
||||
DashboardError> m_error;
|
||||
};
|
||||
|
||||
} // namespace Axivion::Internal
|
||||
Reference in New Issue
Block a user