/** * Lightweight REST Client Framework * $Id$ * $HeadURL$ * $Author$ * Created by Hannes Fritsche, ESZS, 2026-08-21 */ #include "lts_rest_client.h" #include #include #include #include #include namespace LtsCommon { LtsRestClient::LtsRestClient(const QString &baseUrl, QObject *parent) : QObject(parent) , m_baseUrl(baseUrl) , m_manager(new QNetworkAccessManager(this)) { } void LtsRestClient::ping(PingSuccessCallback onSuccess, ErrorCallback onError, int timeoutMs) { const QNetworkRequest request((QUrl(m_baseUrl))); QNetworkReply *reply = m_manager->head(request); if (!reply) { if (onError) onError(QStringLiteral("Failed to initiate ping.")); return; } // 1. Setup the timer (parented to reply for auto-cleanup) QTimer *timeoutTimer = new QTimer(reply); timeoutTimer->setSingleShot(true); // 2. Connect the timeout signal to abort the request connect(timeoutTimer, &QTimer::timeout, this, [reply]() { if (reply && reply->isRunning()) reply->abort(); // Triggers finished() with OperationCanceledError }); // 3. Connect the finished signal to handle the response connect(reply, &QNetworkReply::finished, this, [reply, timeoutTimer, onSuccess, onError]() { timeoutTimer->stop(); reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { if (onError) { if (reply->error() == QNetworkReply::OperationCanceledError) onError(QStringLiteral("Ping timed out.")); else onError(reply->errorString()); } return; } const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (onSuccess) onSuccess(statusCode); }); // 4. Start the timer only after all connections are successfully established timeoutTimer->start(timeoutMs); } void LtsRestClient::get(const QString &endpoint, SuccessCallback onSuccess, ErrorCallback onError) { sendRequest(QStringLiteral("GET"), endpoint, QByteArray(), onSuccess, onError); } void LtsRestClient::post(const QString &endpoint, const QJsonDocument &json, SuccessCallback onSuccess, ErrorCallback onError) { sendRequest(QStringLiteral("POST"), endpoint, json.toJson(), onSuccess, onError); } void LtsRestClient::put(const QString &endpoint, const QJsonDocument &json, SuccessCallback onSuccess, ErrorCallback onError) { sendRequest(QStringLiteral("PUT"), endpoint, json.toJson(), onSuccess, onError); } void LtsRestClient::del(const QString &endpoint, SuccessCallback onSuccess, ErrorCallback onError) { sendRequest(QStringLiteral("DELETE"), endpoint, QByteArray(), onSuccess, onError); } void LtsRestClient::sendRequest(const QString &method, const QString &endpoint, const QByteArray &data, SuccessCallback onSuccess, ErrorCallback onError) { QUrl url(m_baseUrl); url.setPath(url.path() + endpoint); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); QNetworkReply *reply = nullptr; if (method == QStringLiteral("GET")) reply = m_manager->get(request); else if (method == QStringLiteral("POST")) reply = m_manager->post(request, data); else if (method == QStringLiteral("PUT")) reply = m_manager->put(request, data); else if (method == QStringLiteral("DELETE")) reply = m_manager->deleteResource(request); if (!reply) { if (onError) onError(QStringLiteral("Failed to initiate request.")); return; } connect(reply, &QNetworkReply::finished, this, [reply, onSuccess, onError]() { reply->deleteLater(); // Handle network errors if (reply->error() != QNetworkReply::NoError) { if (onError) onError(reply->errorString()); return; } // Handle HTTP errors const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (statusCode >= 400) { if (onError) onError(QStringLiteral("HTTP Error %1").arg(statusCode)); return; } // Parse JSON safely QJsonParseError parseError; const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &parseError); if (parseError.error != QJsonParseError::NoError) { if (onError) onError(QStringLiteral("JSON Parse Error: ") + parseError.errorString()); return; } if (onSuccess) onSuccess(doc); }); } } // namespace LtsCommon