some thoughts about request handling

This commit is contained in:
Blue 2023-12-25 17:07:51 -03:00
parent 73e91e658f
commit 437e76067f
Signed by: blue
GPG Key ID: 9B203B252A63EE38
8 changed files with 168 additions and 64 deletions

View File

@ -9,6 +9,20 @@
constexpr const char* json = "application/json"; constexpr const char* json = "application/json";
constexpr const char* urlEncoded = "application/x-www-form-urlencoded"; constexpr const char* urlEncoded = "application/x-www-form-urlencoded";
const std::map<QString, QMetaType::Type> testStructure({
{"type", QMetaType::QString},
{"version", QMetaType::QString},
});
const std::map<QString, QMetaType::Type> resultStructure({
{"result", QMetaType::Double},
});
const std::map<QString, QMetaType::Type> tokensStructure({
{"accessToken", QMetaType::QString},
{"renewToken", QMetaType::QString}
});
struct NetworkReplyDeleter { struct NetworkReplyDeleter {
void operator () (QNetworkReply* reply) { void operator () (QNetworkReply* reply) {
reply->deleteLater(); reply->deleteLater();
@ -30,6 +44,13 @@ API::State API::getState() const {
return state; return state;
} }
void API::setTokens(const QString access, const QString &renew) {
accessToken = access;
renewToken = renew;
state = Authenticating;
emit stateChanged(state);
}
void API::setAddress(const QUrl& path) { void API::setAddress(const QUrl& path) {
if (address == path) if (address == path)
return; return;
@ -45,6 +66,40 @@ void API::setAddress(const QUrl& path) {
emit stateChanged(state); emit stateChanged(state);
} }
std::optional<QVariantMap> API::readResult(QNetworkReply* reply) {
QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader);
if (!
contentType.isValid() ||
contentType.userType() != QMetaType::QString ||
contentType.toString() != json
)
return std::nullopt;
QByteArray data = reply->readAll();
QJsonDocument document = QJsonDocument::fromJson(data);
if (!document.isObject())
return std::nullopt;
QJsonObject object = document.object();
return object.toVariantMap();
}
bool API::validateResponse(const std::optional<QVariantMap>& data, const std::map<QString, QMetaType::Type>& structure) {
if (!data.has_value())
return false;
for (const std::pair<const QString, QMetaType::Type>& pair : structure) {
QVariantMap::ConstIterator itr = data->find(pair.first);
if (itr == data->end())
return false;
if (itr->userType() != pair.second)
return false;
}
return true;
}
void API::test(const QString& path, const QJSValue& finished) { void API::test(const QString& path, const QJSValue& finished) {
qDebug() << "Testing" << path; qDebug() << "Testing" << path;
if (state == Offline) if (state == Offline)
@ -66,29 +121,17 @@ void API::onTestFinished(QNetworkReply* reply, const QUrl& addr, const QJSValue&
if (error != QNetworkReply::NoError) if (error != QNetworkReply::NoError)
return callCallback(finished, reply->errorString()); return callCallback(finished, reply->errorString());
QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader); std::optional<QVariantMap> data = readResult(reply);
if (! if (!validateResponse(data, testStructure))
contentType.isValid() || return callCallback(finished, "Malformed response");
!contentType.canConvert<QString>() ||
contentType.toString() != json
) {
return callCallback(finished, "wrong response content type");
}
QByteArray data = reply->readAll(); QString type = data->value("type").toString();
QJsonDocument document = QJsonDocument::fromJson(data); if (type != "pica")
QJsonObject rootObj = document.object(); return callCallback(finished, "server of this type (" + type + ") is not supported");
QJsonValue type = rootObj.value("type"); QString version = data->value("version").toString();
QJsonValue version = rootObj.value("version"); if (version != "0.0.1")
if (!type.isString() || !version.isString()) return callCallback(finished, "server of this version (" + version + ") is not supported");
return callCallback(finished, "malformed json");
if (type.toString() != "pica")
return callCallback(finished, "server of this type (" + type.toString() + ") is not supported");
if (version.toString() != "0.0.1")
return callCallback(finished, "server of this version (" + version.toString() + ") is not supported");
callCallback(finished, QString(), {QJSValue(true)}); callCallback(finished, QString(), {QJSValue(true)});
address = ""; //to provoke singal change even if it's the same server address = ""; //to provoke singal change even if it's the same server
@ -144,59 +187,57 @@ void API::sendLogin(const QString& login, const QString& password, const QJSValu
void API::onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) const { void API::onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) const {
std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply); std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply);
QNetworkReply::NetworkError error = reply->error(); QNetworkReply::NetworkError error = reply->error();
if (error != QNetworkReply::NoError) std::optional<QVariantMap> data = readResult(reply);
return callCallback(finished, reply->errorString()); std::optional<Codes::Register> code;
QString detail;
QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader); bool success = false;
if (! if (validateResponse(data, resultStructure)) {
contentType.isValid() || code = Codes::convertRegister(data->value("result").toInt());
!contentType.canConvert<QString>() || success = code.value() == Codes::Register::success;
contentType.toString() != json if (!success)
) { detail = Codes::description(code.value());
return callCallback(finished, "wrong response content type");
} }
QByteArray data = reply->readAll(); if (error != QNetworkReply::NoError)
QJsonDocument document = QJsonDocument::fromJson(data); return callCallback(finished, reply->errorString() + (success ? "" : ": " + detail));
QJsonObject rootObj = document.object();
QJsonValue res = rootObj.value("result"); if (!code)
if (!res.isDouble()) return callCallback(finished, "Malformed result");
return callCallback(finished, "malformed json");
Codes::Register result = Codes::convertRegister(res.toInt()); callCallback(finished, detail, {QJSValue(success)});
bool success = result == Codes::Register::success;
QString err;
if (!success)
err = Codes::description(result);
callCallback(finished, err, {QJSValue(success)});
} }
void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) { void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) {
std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply); std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply);
QNetworkReply::NetworkError error = reply->error(); QNetworkReply::NetworkError error = reply->error();
if (error != QNetworkReply::NoError) std::optional<QVariantMap> data = readResult(reply);
return callCallback(finished, reply->errorString()); std::optional<Codes::Login> code;
QString detail;
QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader); bool success = false;
if (! if (validateResponse(data, resultStructure)) {
contentType.isValid() || code = Codes::convertLogin(data->value("result").toInt());
!contentType.canConvert<QString>() || success = code.value() == Codes::Login::success;
contentType.toString() != json if (!success)
) { detail = Codes::description(code.value());
return callCallback(finished, "wrong response content type");
} }
QByteArray data = reply->readAll(); if (error != QNetworkReply::NoError)
QJsonDocument document = QJsonDocument::fromJson(data); return callCallback(finished, reply->errorString() + (success ? "" : ": " + detail));
QJsonObject rootObj = document.object();
QJsonValue res = rootObj.value("result"); if (!code)
if (!res.isDouble()) return callCallback(finished, "Malformed result");
return callCallback(finished, "malformed json");
//todo if (!validateResponse(data, tokensStructure))
return callCallback(finished, "Malformed result: missing tokens");
callCallback(finished, detail, {QJSValue(success)});
state = Authenticated;
accessToken = data->value("accessToken").toString();
renewToken = data->value("renewToken").toString();
emit storeTokens(accessToken, renewToken);
emit stateChanged(state);
} }
void API::callCallback(const QJSValue& callback, const QString& error, const QJSValueList& arguments) const { void API::callCallback(const QJSValue& callback, const QString& error, const QJSValueList& arguments) const {

View File

@ -1,6 +1,8 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <optional>
#include <map>
#include <QObject> #include <QObject>
#include <QString> #include <QString>
@ -14,7 +16,13 @@ class API : public QObject {
friend class Root; friend class Root;
Q_OBJECT Q_OBJECT
public: public:
enum State {Offline, NoServer, NotAuthenticated, Authenticated}; enum State {
Offline,
NoServer,
NotAuthenticated,
Authenticating,
Authenticated
};
Q_ENUM(State) Q_ENUM(State)
private: private:
@ -26,11 +34,14 @@ public:
QUrl getAddress() const; QUrl getAddress() const;
State getState() const; State getState() const;
void setTokens(const QString access, const QString& renew);
signals: signals:
void addressChanged(const QUrl& path); void addressChanged(const QUrl& path);
void stateChanged(State state); void stateChanged(State state);
void storeTokens(const QString& access, const QString& renew);
public slots: public slots:
void test(const QString& path, const QJSValue& finished = QJSValue()); void test(const QString& path, const QJSValue& finished = QJSValue());
void sendRegister(const QString& login, const QString& password, const QJSValue& finished = QJSValue()); void sendRegister(const QString& login, const QString& password, const QJSValue& finished = QJSValue());
@ -44,9 +55,13 @@ private slots:
private: private:
void callCallback(const QJSValue& callback, const QString& error = QString(), const QJSValueList& arguments = QJSValueList()) const; void callCallback(const QJSValue& callback, const QString& error = QString(), const QJSValueList& arguments = QJSValueList()) const;
void setAddress(const QUrl& path); void setAddress(const QUrl& path);
static std::optional<QVariantMap> readResult(QNetworkReply* reply);
static bool validateResponse(const std::optional<QVariantMap>& data, const std::map<QString, QMetaType::Type>& structure);
private: private:
QUrl address; QUrl address;
QNetworkAccessManager network; QNetworkAccessManager network;
State state; State state;
QString accessToken;
QString renewToken;
}; };

View File

@ -14,6 +14,16 @@ constexpr std::array<const char*, uint8_t(Codes::Register::unknownError) + 1> re
"Unknown registration error" "Unknown registration error"
}); });
constexpr std::array<const char*, uint8_t(Codes::Login::unknownError) + 1> loginErrors({
"Successfully logged in",
"Login have not been provided",
"The login can not be empty",
"Password have not been provided",
"Password can not be empty",
"Provided login or (and) password are wrong",
"Unknown login error"
});
Codes::Register Codes::convertRegister (int source) { Codes::Register Codes::convertRegister (int source) {
if (source < 0) { if (source < 0) {
std::cerr << "Converting Register response code which is smaller than zero, falling back to unknownError" << std::endl; std::cerr << "Converting Register response code which is smaller than zero, falling back to unknownError" << std::endl;
@ -31,3 +41,21 @@ Codes::Register Codes::convertRegister (int source) {
QString Codes::description(Register code) { QString Codes::description(Register code) {
return registerErrors[(uint8_t)code]; return registerErrors[(uint8_t)code];
} }
Codes::Login Codes::convertLogin(int source) {
if (source < 0) {
std::cerr << "Converting Login response code which is smaller than zero, falling back to unknownError" << std::endl;
return Login::unknownError;
}
if (source > (int)Login::unknownError) {
std::cerr << "Converting Login response code which is bigger than biggest known, falling back to unknownError" << std::endl;
return Login::unknownError;
}
return static_cast<Login>(source);
}
QString Codes::description(Login code) {
return loginErrors[(uint8_t)code];
}

View File

@ -15,8 +15,20 @@ enum class Register {
unknownError unknownError
}; };
enum class Login {
success,
noLogin,
emptyLogin,
noPassword,
emptyPassword,
wrongCredentials,
unknownError
};
Register convertRegister (int source); Register convertRegister (int source);
Login convertLogin (int source);
QString description (Register code); QString description (Register code);
QString description (Login code);
} }

View File

@ -10,7 +10,7 @@ Page {
signal back() signal back()
title: qsTr("Chosing a server") // title: qsTr("Chosing a server")
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent

View File

@ -9,7 +9,7 @@ Page {
id: page id: page
signal pickServer(address: string) signal pickServer(address: string)
title: qsTr("Welcome") // title: qsTr("Welcome")
Column { Column {
id: column id: column

View File

@ -28,6 +28,7 @@ Root::Root(const QUrl& root, int& argc, char* argv[]) :
qRegisterMetaType<API>("API"); qRegisterMetaType<API>("API");
connect(&api, &API::addressChanged, this, &Root::onAPIAddressChanged); connect(&api, &API::addressChanged, this, &Root::onAPIAddressChanged);
connect(&api, &API::storeTokens, this, &Root::onStoreTokens);
qmlRegisterSingletonType<API>("magpie.API", 1, 0, "API", [this] (QQmlEngine *engine, QJSEngine *scriptEngine) { qmlRegisterSingletonType<API>("magpie.API", 1, 0, "API", [this] (QQmlEngine *engine, QJSEngine *scriptEngine) {
return &api; return &api;
@ -68,3 +69,9 @@ void Root::onAPIAddressChanged(const QUrl& url) {
QSettings settings; QSettings settings;
settings.setValue("address", url); settings.setValue("address", url);
} }
void Root::onStoreTokens(const QString& access, const QString& renew) {
QSettings settings;
settings.setValue("assessToken", access);
settings.setValue("renewToken", renew);
}

1
root.h
View File

@ -21,6 +21,7 @@ public:
private slots: private slots:
void onObjectCreated(QObject* obj, const QUrl& objUrl); void onObjectCreated(QObject* obj, const QUrl& objUrl);
void onAPIAddressChanged(const QUrl& url); void onAPIAddressChanged(const QUrl& url);
void onStoreTokens(const QString& access, const QString& renew);
private: private:
QUrl root; QUrl root;