Generalize Result<> class, add Optional<>
authorChristian Kamm <mail@ckamm.de>
Thu, 15 Nov 2018 07:32:17 +0000 (08:32 +0100)
committerKevin Ottens <kevin.ottens@nextcloud.com>
Tue, 15 Dec 2020 09:58:24 +0000 (10:58 +0100)
To make it nicer to use outside of HTTP results.

src/common/result.h [new file with mode: 0644]
src/libsync/discovery.cpp
src/libsync/discoveryphase.cpp
src/libsync/discoveryphase.h
src/libsync/networkjobs.cpp
src/libsync/networkjobs.h
src/libsync/result.h [deleted file]

diff --git a/src/common/result.h b/src/common/result.h
new file mode 100644 (file)
index 0000000..5db087b
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#pragma once
+
+#include "asserts.h"
+
+namespace OCC {
+
+/**
+ * A Result of type T, or an Error
+ */
+template <typename T, typename Error>
+class Result
+{
+    union {
+        T _result;
+        Error _error;
+    };
+    bool _isError;
+
+public:
+    Result(T value)
+        : _result(std::move(value))
+        , _isError(false)
+    {
+    }
+    // TODO: This doesn't work if T and Error are too similar
+    Result(Error error)
+        : _error(std::move(error))
+        , _isError(true)
+    {
+    }
+
+    ~Result()
+    {
+        if (_isError)
+            _error.~Error();
+        else
+            _result.~T();
+    }
+    explicit operator bool() const { return !_isError; }
+    const T &operator*() const &
+    {
+        ASSERT(!_isError);
+        return _result;
+    }
+    T operator*() &&
+    {
+        ASSERT(!_isError);
+        return std::move(_result);
+    }
+    const Error &error() const &
+    {
+        ASSERT(_isError);
+        return _error;
+    }
+    Error error() &&
+    {
+        ASSERT(_isError);
+        return std::move(_error);
+    }
+};
+
+namespace detail {
+struct OptionalNoErrorData{};
+}
+
+template <typename T>
+class Optional : public Result<T, detail::OptionalNoErrorData>
+{
+public:
+    using Result<T, detail::OptionalNoErrorData>::Result;
+
+    Optional()
+        : Optional(detail::OptionalNoErrorData{})
+    {
+    }
+};
+
+} // namespace OCC
index 8d6c5dcf926eee5949bc8edcb75dd927f8f50187..1924187eb86a0c83720044c27dc29c55754a0e4f 100644 (file)
@@ -522,10 +522,10 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
             // we need to make a request to the server to know that the original file is deleted on the server
             _pendingAsyncJobs++;
             auto job = new RequestEtagJob(_discoveryData->_account, originalPath, this);
-            connect(job, &RequestEtagJob::finishedWithResult, this, [=](const Result<QString> &etag) mutable {
+            connect(job, &RequestEtagJob::finishedWithResult, this, [=](const HttpResult<QString> &etag) mutable {
                 _pendingAsyncJobs--;
                 QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs);
-                if (etag.errorCode() != 404 ||
+                if (etag || etag.error().code != 404 ||
                     // Somehow another item claimed this original path, consider as if it existed
                     _discoveryData->_renamedItems.contains(originalPath)) {
                     // If the file exist or if there is another error, consider it is a new file.
@@ -828,7 +828,7 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
             if (base.isVirtualFile() && isVfsWithSuffix())
                 chopVirtualFileSuffix(serverOriginalPath);
             auto job = new RequestEtagJob(_discoveryData->_account, serverOriginalPath, this);
-            connect(job, &RequestEtagJob::finishedWithResult, this, [=](const Result<QString> &etag) mutable {
+            connect(job, &RequestEtagJob::finishedWithResult, this, [=](const HttpResult<QString> &etag) mutable {
                 if (!etag || (*etag != base._etag && !item->isDirectory()) || _discoveryData->_renamedItems.contains(originalPath)) {
                     qCInfo(lcDisco) << "Can't rename because the etag has changed or the directory is gone" << originalPath;
                     // Can't be a rename, leave it as a new.
@@ -1220,17 +1220,17 @@ DiscoverySingleDirectoryJob *ProcessDirectoryJob::startAsyncServerQuery()
             if (_localQueryDone)
                 process();
         } else {
-            if (results.errorCode() == 403) {
+            if (results.error().code == 403) {
                 // 403 Forbidden can be sent by the server if the file firewall is active.
                 // A file or directory should be ignored and sync must continue. See #3490
                 qCWarning(lcDisco, "Directory access Forbidden (File Firewall?)");
                 if (_dirItem) {
                     _dirItem->_instruction = CSYNC_INSTRUCTION_IGNORE;
-                    _dirItem->_errorString = results.errorMessage();
+                    _dirItem->_errorString = results.error().message;
                     emit finished();
                     return;
                 }
-            } else if (results.errorCode() == 503) {
+            } else if (results.error().code == 503) {
                 // The server usually replies with the custom "503 Storage not available"
                 // if some path is temporarily unavailable. But in some cases a standard 503
                 // is returned too. Thus we can't distinguish the two and will treat any
@@ -1238,13 +1238,13 @@ DiscoverySingleDirectoryJob *ProcessDirectoryJob::startAsyncServerQuery()
                 qCWarning(lcDisco(), "Storage was not available!");
                 if (_dirItem) {
                     _dirItem->_instruction = CSYNC_INSTRUCTION_IGNORE;
-                    _dirItem->_errorString = results.errorMessage();
+                    _dirItem->_errorString = results.error().message;
                     emit finished();
                     return;
                 }
             }
             emit _discoveryData->fatalError(tr("Server replied with an error while reading directory '%1' : %2")
-                .arg(_currentFolder._server, results.errorMessage()));
+                .arg(_currentFolder._server, results.error().message));
         }
     });
     connect(serverJob, &DiscoverySingleDirectoryJob::firstDirectoryPermissions, this,
index e845c352d76deb65e0443bc494bf97f401dd47e7..e036f79f3fefc60193a3adb08933a614263a2bf4 100644 (file)
@@ -355,11 +355,11 @@ void DiscoverySingleDirectoryJob::lsJobFinishedWithoutErrorSlot()
     if (!_ignoredFirst) {
         // This is a sanity check, if we haven't _ignoredFirst then it means we never received any directoryListingIteratedSlot
         // which means somehow the server XML was bogus
-        emit finished({ 0, tr("Server error: PROPFIND reply is not XML formatted!") });
+        emit finished(HttpError{ 0, tr("Server error: PROPFIND reply is not XML formatted!") });
         deleteLater();
         return;
     } else if (!_error.isEmpty()) {
-        emit finished({ 0, _error });
+        emit finished(HttpError{ 0, _error });
         deleteLater();
         return;
     }
@@ -379,7 +379,7 @@ void DiscoverySingleDirectoryJob::lsJobFinishedWithErrorSlot(QNetworkReply *r)
         && !contentType.contains("application/xml; charset=utf-8")) {
         msg = tr("Server error: PROPFIND reply is not XML formatted!");
     }
-    emit finished({httpCode, msg});
+    emit finished(HttpError{ httpCode, msg });
     deleteLater();
 }
 }
index 1bd9dd06b8ec2708d9dd828274b0e2784199c161..6f6b4ffd6bb8d5c1488633387e9f30c98b4a25a9 100644 (file)
@@ -97,7 +97,7 @@ public:
 signals:
     void firstDirectoryPermissions(RemotePermissions);
     void etag(const QString &);
-    void finished(const Result<QVector<RemoteInfo>> &result);
+    void finished(const HttpResult<QVector<RemoteInfo>> &result);
 private slots:
     void directoryListingIteratedSlot(QString, const QMap<QString, QString> &);
     void lsJobFinishedWithoutErrorSlot();
index 18f9097e39521e787a6b5e839200fe5dd301040e..fc953c3cb24967a0f5839508dd52545a6f7e4e8f 100644 (file)
@@ -107,7 +107,7 @@ bool RequestEtagJob::finished()
         emit etagRetrieved(etag);
         emit finishedWithResult(etag);
     } else {
-        emit finishedWithResult({ httpCode, errorString() });
+        emit finishedWithResult(HttpError{ httpCode, errorString() });
     }
     return true;
 }
index 634e792a7a30b1dcc89f8243931b83e20e588785..a29365c3acfb00e9946747346280c7d9d381ab29 100644 (file)
@@ -18,7 +18,7 @@
 
 #include "abstractnetworkjob.h"
 
-#include "result.h"
+#include "common/result.h"
 
 #include <QBuffer>
 #include <QUrlQuery>
@@ -29,6 +29,15 @@ class QJsonObject;
 
 namespace OCC {
 
+struct HttpError
+{
+    int code; // HTTP error code
+    QString message;
+};
+
+template <typename T>
+using HttpResult = Result<T, HttpError>;
+
 /**
  * @brief The EntityExistsJob class
  * @ingroup libsync
@@ -337,7 +346,7 @@ public:
 
 signals:
     void etagRetrieved(const QString &etag);
-    void finishedWithResult(const Result<QString> &etag);
+    void finishedWithResult(const HttpResult<QString> &etag);
 
 private slots:
     bool finished() override;
diff --git a/src/libsync/result.h b/src/libsync/result.h
deleted file mode 100644 (file)
index 58f70c4..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * for more details.
- */
-
-#pragma once
-
-namespace OCC {
-
-/**
- * A Result of type T, or an Error that contains a code and a string
- *
- * The code is an HTTP error code.
- **/
-template <typename T>
-class Result
-{
-    struct Error
-    {
-        QString string;
-        int code;
-    };
-    union {
-        T _result;
-        Error _error;
-    };
-    bool _isError;
-
-public:
-    Result(T value)
-        : _result(std::move(value))
-        , _isError(false){};
-    Result(int code, QString str)
-        : _error({ std::move(str), code })
-        , _isError(true)
-    {
-    }
-    ~Result()
-    {
-        if (_isError)
-            _error.~Error();
-        else
-            _result.~T();
-    }
-    explicit operator bool() const { return !_isError; }
-    const T &operator*() const &
-    {
-        ASSERT(!_isError);
-        return _result;
-    }
-    T operator*() &&
-    {
-        ASSERT(!_isError);
-        return std::move(_result);
-    }
-    QString errorMessage() const
-    {
-        ASSERT(_isError);
-        return _error.string;
-    }
-    int errorCode() const
-    {
-        return _isError ? _error.code : 0;
-    }
-};
-
-} // namespace OCC