some thoughts about request handling
This commit is contained in:
parent
73e91e658f
commit
437e76067f
163
API/api.cpp
163
API/api.cpp
@ -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 {
|
||||||
|
17
API/api.h
17
API/api.h
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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];
|
||||||
|
}
|
||||||
|
12
API/codes.h
12
API/codes.h
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
7
root.cpp
7
root.cpp
@ -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
1
root.h
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user