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* 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 {
void operator () (QNetworkReply* reply) {
reply->deleteLater();
@ -30,6 +44,13 @@ API::State API::getState() const {
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) {
if (address == path)
return;
@ -45,6 +66,40 @@ void API::setAddress(const QUrl& path) {
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) {
qDebug() << "Testing" << path;
if (state == Offline)
@ -66,29 +121,17 @@ void API::onTestFinished(QNetworkReply* reply, const QUrl& addr, const QJSValue&
if (error != QNetworkReply::NoError)
return callCallback(finished, reply->errorString());
QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader);
if (!
contentType.isValid() ||
!contentType.canConvert<QString>() ||
contentType.toString() != json
) {
return callCallback(finished, "wrong response content type");
}
std::optional<QVariantMap> data = readResult(reply);
if (!validateResponse(data, testStructure))
return callCallback(finished, "Malformed response");
QByteArray data = reply->readAll();
QJsonDocument document = QJsonDocument::fromJson(data);
QJsonObject rootObj = document.object();
QString type = data->value("type").toString();
if (type != "pica")
return callCallback(finished, "server of this type (" + type + ") is not supported");
QJsonValue type = rootObj.value("type");
QJsonValue version = rootObj.value("version");
if (!type.isString() || !version.isString())
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");
QString version = data->value("version").toString();
if (version != "0.0.1")
return callCallback(finished, "server of this version (" + version + ") is not supported");
callCallback(finished, QString(), {QJSValue(true)});
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 {
std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply);
QNetworkReply::NetworkError error = reply->error();
if (error != QNetworkReply::NoError)
return callCallback(finished, reply->errorString());
QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader);
if (!
contentType.isValid() ||
!contentType.canConvert<QString>() ||
contentType.toString() != json
) {
return callCallback(finished, "wrong response content type");
std::optional<QVariantMap> data = readResult(reply);
std::optional<Codes::Register> code;
QString detail;
bool success = false;
if (validateResponse(data, resultStructure)) {
code = Codes::convertRegister(data->value("result").toInt());
success = code.value() == Codes::Register::success;
if (!success)
detail = Codes::description(code.value());
}
QByteArray data = reply->readAll();
QJsonDocument document = QJsonDocument::fromJson(data);
QJsonObject rootObj = document.object();
if (error != QNetworkReply::NoError)
return callCallback(finished, reply->errorString() + (success ? "" : ": " + detail));
QJsonValue res = rootObj.value("result");
if (!res.isDouble())
return callCallback(finished, "malformed json");
if (!code)
return callCallback(finished, "Malformed result");
Codes::Register result = Codes::convertRegister(res.toInt());
bool success = result == Codes::Register::success;
QString err;
if (!success)
err = Codes::description(result);
callCallback(finished, err, {QJSValue(success)});
callCallback(finished, detail, {QJSValue(success)});
}
void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) {
std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply);
QNetworkReply::NetworkError error = reply->error();
if (error != QNetworkReply::NoError)
return callCallback(finished, reply->errorString());
QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader);
if (!
contentType.isValid() ||
!contentType.canConvert<QString>() ||
contentType.toString() != json
) {
return callCallback(finished, "wrong response content type");
std::optional<QVariantMap> data = readResult(reply);
std::optional<Codes::Login> code;
QString detail;
bool success = false;
if (validateResponse(data, resultStructure)) {
code = Codes::convertLogin(data->value("result").toInt());
success = code.value() == Codes::Login::success;
if (!success)
detail = Codes::description(code.value());
}
QByteArray data = reply->readAll();
QJsonDocument document = QJsonDocument::fromJson(data);
QJsonObject rootObj = document.object();
if (error != QNetworkReply::NoError)
return callCallback(finished, reply->errorString() + (success ? "" : ": " + detail));
QJsonValue res = rootObj.value("result");
if (!res.isDouble())
return callCallback(finished, "malformed json");
if (!code)
return callCallback(finished, "Malformed result");
//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 {

View File

@ -1,6 +1,8 @@
#pragma once
#include <memory>
#include <optional>
#include <map>
#include <QObject>
#include <QString>
@ -14,7 +16,13 @@ class API : public QObject {
friend class Root;
Q_OBJECT
public:
enum State {Offline, NoServer, NotAuthenticated, Authenticated};
enum State {
Offline,
NoServer,
NotAuthenticated,
Authenticating,
Authenticated
};
Q_ENUM(State)
private:
@ -26,11 +34,14 @@ public:
QUrl getAddress() const;
State getState() const;
void setTokens(const QString access, const QString& renew);
signals:
void addressChanged(const QUrl& path);
void stateChanged(State state);
void storeTokens(const QString& access, const QString& renew);
public slots:
void test(const QString& path, const QJSValue& finished = QJSValue());
void sendRegister(const QString& login, const QString& password, const QJSValue& finished = QJSValue());
@ -44,9 +55,13 @@ private slots:
private:
void callCallback(const QJSValue& callback, const QString& error = QString(), const QJSValueList& arguments = QJSValueList()) const;
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:
QUrl address;
QNetworkAccessManager network;
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"
});
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) {
if (source < 0) {
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) {
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
};
enum class Login {
success,
noLogin,
emptyLogin,
noPassword,
emptyPassword,
wrongCredentials,
unknownError
};
Register convertRegister (int source);
Login convertLogin (int source);
QString description (Register code);
QString description (Login code);
}

View File

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

View File

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

View File

@ -28,6 +28,7 @@ Root::Root(const QUrl& root, int& argc, char* argv[]) :
qRegisterMetaType<API>("API");
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) {
return &api;
@ -68,3 +69,9 @@ void Root::onAPIAddressChanged(const QUrl& url) {
QSettings settings;
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:
void onObjectCreated(QObject* obj, const QUrl& objUrl);
void onAPIAddressChanged(const QUrl& url);
void onStoreTokens(const QString& access, const QString& renew);
private:
QUrl root;