diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2020-06-09 08:52:25 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-09 08:52:25 +0200 |
commit | 370d9b3e46332d38df8798cda208c534c58be808 (patch) | |
tree | f3db0cd7463468ff52c74446acd608356f8b8ab6 /lib | |
parent | e1f5d0aa2c33e6da6c3a609c8bc7e0b5867e748d (diff) | |
parent | 10d9ac4673e374a9ac17ff492591136520337c4c (diff) | |
download | libquotient-370d9b3e46332d38df8798cda208c534c58be808.tar.gz libquotient-370d9b3e46332d38df8798cda208c534c58be808.zip |
Merge pull request #405 from quotient-im/kitsune-basejob-store-json-response
Store JSON response in BaseJob + tweaks to the generated code
Diffstat (limited to 'lib')
153 files changed, 2794 insertions, 4694 deletions
diff --git a/lib/application-service/definitions/location.cpp b/lib/application-service/definitions/location.cpp deleted file mode 100644 index 0a054029..00000000 --- a/lib/application-service/definitions/location.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "location.h" - -using namespace Quotient; - -void JsonObjectConverter<ThirdPartyLocation>::dumpTo( - QJsonObject& jo, const ThirdPartyLocation& pod) -{ - addParam<>(jo, QStringLiteral("alias"), pod.alias); - addParam<>(jo, QStringLiteral("protocol"), pod.protocol); - addParam<>(jo, QStringLiteral("fields"), pod.fields); -} - -void JsonObjectConverter<ThirdPartyLocation>::fillFrom(const QJsonObject& jo, - ThirdPartyLocation& result) -{ - fromJson(jo.value("alias"_ls), result.alias); - fromJson(jo.value("protocol"_ls), result.protocol); - fromJson(jo.value("fields"_ls), result.fields); -} diff --git a/lib/application-service/definitions/location.h b/lib/application-service/definitions/location.h index e4c2d096..6801c99f 100644 --- a/lib/application-service/definitions/location.h +++ b/lib/application-service/definitions/location.h @@ -6,12 +6,8 @@ #include "converters.h" -#include <QtCore/QJsonObject> - namespace Quotient { -// Data structures - struct ThirdPartyLocation { /// An alias for a matrix room. QString alias; @@ -25,8 +21,18 @@ struct ThirdPartyLocation { template <> struct JsonObjectConverter<ThirdPartyLocation> { - static void dumpTo(QJsonObject& jo, const ThirdPartyLocation& pod); - static void fillFrom(const QJsonObject& jo, ThirdPartyLocation& pod); + static void dumpTo(QJsonObject& jo, const ThirdPartyLocation& pod) + { + addParam<>(jo, QStringLiteral("alias"), pod.alias); + addParam<>(jo, QStringLiteral("protocol"), pod.protocol); + addParam<>(jo, QStringLiteral("fields"), pod.fields); + } + static void fillFrom(const QJsonObject& jo, ThirdPartyLocation& pod) + { + fromJson(jo.value("alias"_ls), pod.alias); + fromJson(jo.value("protocol"_ls), pod.protocol); + fromJson(jo.value("fields"_ls), pod.fields); + } }; } // namespace Quotient diff --git a/lib/application-service/definitions/protocol.cpp b/lib/application-service/definitions/protocol.cpp deleted file mode 100644 index 8c66aa4d..00000000 --- a/lib/application-service/definitions/protocol.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "protocol.h" - -using namespace Quotient; - -void JsonObjectConverter<FieldType>::dumpTo(QJsonObject& jo, - const FieldType& pod) -{ - addParam<>(jo, QStringLiteral("regexp"), pod.regexp); - addParam<>(jo, QStringLiteral("placeholder"), pod.placeholder); -} - -void JsonObjectConverter<FieldType>::fillFrom(const QJsonObject& jo, - FieldType& result) -{ - fromJson(jo.value("regexp"_ls), result.regexp); - fromJson(jo.value("placeholder"_ls), result.placeholder); -} - -void JsonObjectConverter<ProtocolInstance>::dumpTo(QJsonObject& jo, - const ProtocolInstance& pod) -{ - addParam<>(jo, QStringLiteral("desc"), pod.desc); - addParam<IfNotEmpty>(jo, QStringLiteral("icon"), pod.icon); - addParam<>(jo, QStringLiteral("fields"), pod.fields); - addParam<>(jo, QStringLiteral("network_id"), pod.networkId); -} - -void JsonObjectConverter<ProtocolInstance>::fillFrom(const QJsonObject& jo, - ProtocolInstance& result) -{ - fromJson(jo.value("desc"_ls), result.desc); - fromJson(jo.value("icon"_ls), result.icon); - fromJson(jo.value("fields"_ls), result.fields); - fromJson(jo.value("network_id"_ls), result.networkId); -} - -void JsonObjectConverter<ThirdPartyProtocol>::dumpTo( - QJsonObject& jo, const ThirdPartyProtocol& pod) -{ - addParam<>(jo, QStringLiteral("user_fields"), pod.userFields); - addParam<>(jo, QStringLiteral("location_fields"), pod.locationFields); - addParam<>(jo, QStringLiteral("icon"), pod.icon); - addParam<>(jo, QStringLiteral("field_types"), pod.fieldTypes); - addParam<>(jo, QStringLiteral("instances"), pod.instances); -} - -void JsonObjectConverter<ThirdPartyProtocol>::fillFrom(const QJsonObject& jo, - ThirdPartyProtocol& result) -{ - fromJson(jo.value("user_fields"_ls), result.userFields); - fromJson(jo.value("location_fields"_ls), result.locationFields); - fromJson(jo.value("icon"_ls), result.icon); - fromJson(jo.value("field_types"_ls), result.fieldTypes); - fromJson(jo.value("instances"_ls), result.instances); -} diff --git a/lib/application-service/definitions/protocol.h b/lib/application-service/definitions/protocol.h index ac1e50e2..6aee9c57 100644 --- a/lib/application-service/definitions/protocol.h +++ b/lib/application-service/definitions/protocol.h @@ -6,14 +6,7 @@ #include "converters.h" -#include <QtCore/QHash> -#include <QtCore/QJsonObject> -#include <QtCore/QVector> - namespace Quotient { - -// Data structures - /// Definition of valid values for a field. struct FieldType { /// A regular expression for validation of a field's value. This may be @@ -27,8 +20,16 @@ struct FieldType { template <> struct JsonObjectConverter<FieldType> { - static void dumpTo(QJsonObject& jo, const FieldType& pod); - static void fillFrom(const QJsonObject& jo, FieldType& pod); + static void dumpTo(QJsonObject& jo, const FieldType& pod) + { + addParam<>(jo, QStringLiteral("regexp"), pod.regexp); + addParam<>(jo, QStringLiteral("placeholder"), pod.placeholder); + } + static void fillFrom(const QJsonObject& jo, FieldType& pod) + { + fromJson(jo.value("regexp"_ls), pod.regexp); + fromJson(jo.value("placeholder"_ls), pod.placeholder); + } }; struct ProtocolInstance { @@ -48,8 +49,20 @@ struct ProtocolInstance { template <> struct JsonObjectConverter<ProtocolInstance> { - static void dumpTo(QJsonObject& jo, const ProtocolInstance& pod); - static void fillFrom(const QJsonObject& jo, ProtocolInstance& pod); + static void dumpTo(QJsonObject& jo, const ProtocolInstance& pod) + { + addParam<>(jo, QStringLiteral("desc"), pod.desc); + addParam<IfNotEmpty>(jo, QStringLiteral("icon"), pod.icon); + addParam<>(jo, QStringLiteral("fields"), pod.fields); + addParam<>(jo, QStringLiteral("network_id"), pod.networkId); + } + static void fillFrom(const QJsonObject& jo, ProtocolInstance& pod) + { + fromJson(jo.value("desc"_ls), pod.desc); + fromJson(jo.value("icon"_ls), pod.icon); + fromJson(jo.value("fields"_ls), pod.fields); + fromJson(jo.value("network_id"_ls), pod.networkId); + } }; struct ThirdPartyProtocol { @@ -84,8 +97,22 @@ struct ThirdPartyProtocol { template <> struct JsonObjectConverter<ThirdPartyProtocol> { - static void dumpTo(QJsonObject& jo, const ThirdPartyProtocol& pod); - static void fillFrom(const QJsonObject& jo, ThirdPartyProtocol& pod); + static void dumpTo(QJsonObject& jo, const ThirdPartyProtocol& pod) + { + addParam<>(jo, QStringLiteral("user_fields"), pod.userFields); + addParam<>(jo, QStringLiteral("location_fields"), pod.locationFields); + addParam<>(jo, QStringLiteral("icon"), pod.icon); + addParam<>(jo, QStringLiteral("field_types"), pod.fieldTypes); + addParam<>(jo, QStringLiteral("instances"), pod.instances); + } + static void fillFrom(const QJsonObject& jo, ThirdPartyProtocol& pod) + { + fromJson(jo.value("user_fields"_ls), pod.userFields); + fromJson(jo.value("location_fields"_ls), pod.locationFields); + fromJson(jo.value("icon"_ls), pod.icon); + fromJson(jo.value("field_types"_ls), pod.fieldTypes); + fromJson(jo.value("instances"_ls), pod.instances); + } }; } // namespace Quotient diff --git a/lib/application-service/definitions/user.cpp b/lib/application-service/definitions/user.cpp deleted file mode 100644 index 17d15a20..00000000 --- a/lib/application-service/definitions/user.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "user.h" - -using namespace Quotient; - -void JsonObjectConverter<ThirdPartyUser>::dumpTo(QJsonObject& jo, - const ThirdPartyUser& pod) -{ - addParam<>(jo, QStringLiteral("userid"), pod.userid); - addParam<>(jo, QStringLiteral("protocol"), pod.protocol); - addParam<>(jo, QStringLiteral("fields"), pod.fields); -} - -void JsonObjectConverter<ThirdPartyUser>::fillFrom(const QJsonObject& jo, - ThirdPartyUser& result) -{ - fromJson(jo.value("userid"_ls), result.userid); - fromJson(jo.value("protocol"_ls), result.protocol); - fromJson(jo.value("fields"_ls), result.fields); -} diff --git a/lib/application-service/definitions/user.h b/lib/application-service/definitions/user.h index 0d1984b6..3342ef80 100644 --- a/lib/application-service/definitions/user.h +++ b/lib/application-service/definitions/user.h @@ -6,12 +6,8 @@ #include "converters.h" -#include <QtCore/QJsonObject> - namespace Quotient { -// Data structures - struct ThirdPartyUser { /// A Matrix User ID represting a third party user. QString userid; @@ -25,8 +21,18 @@ struct ThirdPartyUser { template <> struct JsonObjectConverter<ThirdPartyUser> { - static void dumpTo(QJsonObject& jo, const ThirdPartyUser& pod); - static void fillFrom(const QJsonObject& jo, ThirdPartyUser& pod); + static void dumpTo(QJsonObject& jo, const ThirdPartyUser& pod) + { + addParam<>(jo, QStringLiteral("userid"), pod.userid); + addParam<>(jo, QStringLiteral("protocol"), pod.protocol); + addParam<>(jo, QStringLiteral("fields"), pod.fields); + } + static void fillFrom(const QJsonObject& jo, ThirdPartyUser& pod) + { + fromJson(jo.value("userid"_ls), pod.userid); + fromJson(jo.value("protocol"_ls), pod.protocol); + fromJson(jo.value("fields"_ls), pod.fields); + } }; } // namespace Quotient diff --git a/lib/connection.cpp b/lib/connection.cpp index 26de0c5f..50e31d52 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -480,8 +480,8 @@ void Connection::sync(int timeout) d->syncTimeout = timeout; Filter filter; - filter.room.edit().timeline.edit().limit.emplace(100); - filter.room.edit().state.edit().lazyLoadMembers.emplace(d->lazyLoading); + filter.room.timeline.limit.emplace(100); + filter.room.state.lazyLoadMembers.emplace(d->lazyLoading); auto job = d->syncJob = callApi<SyncJob>(BackgroundRequest, d->data->lastEvent(), filter, timeout); diff --git a/lib/csapi/account-data.cpp b/lib/csapi/account-data.cpp index b7825718..6a40e908 100644 --- a/lib/csapi/account-data.cpp +++ b/lib/csapi/account-data.cpp @@ -4,18 +4,15 @@ #include "account-data.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - SetAccountDataJob::SetAccountDataJob(const QString& userId, const QString& type, const QJsonObject& content) : BaseJob(HttpVerb::Put, QStringLiteral("SetAccountDataJob"), - basePath % "/user/" % userId % "/account_data/" % type) + QStringLiteral("/_matrix/client/r0") % "/user/" % userId + % "/account_data/" % type) { setRequestData(Data(toJson(content))); } @@ -24,13 +21,14 @@ QUrl GetAccountDataJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& type) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/user/" % userId - % "/account_data/" % type); + QStringLiteral("/_matrix/client/r0") % "/user/" + % userId % "/account_data/" % type); } GetAccountDataJob::GetAccountDataJob(const QString& userId, const QString& type) : BaseJob(HttpVerb::Get, QStringLiteral("GetAccountDataJob"), - basePath % "/user/" % userId % "/account_data/" % type) + QStringLiteral("/_matrix/client/r0") % "/user/" % userId + % "/account_data/" % type) {} SetAccountDataPerRoomJob::SetAccountDataPerRoomJob(const QString& userId, @@ -38,8 +36,8 @@ SetAccountDataPerRoomJob::SetAccountDataPerRoomJob(const QString& userId, const QString& type, const QJsonObject& content) : BaseJob(HttpVerb::Put, QStringLiteral("SetAccountDataPerRoomJob"), - basePath % "/user/" % userId % "/rooms/" % roomId - % "/account_data/" % type) + QStringLiteral("/_matrix/client/r0") % "/user/" % userId + % "/rooms/" % roomId % "/account_data/" % type) { setRequestData(Data(toJson(content))); } @@ -50,14 +48,15 @@ QUrl GetAccountDataPerRoomJob::makeRequestUrl(QUrl baseUrl, const QString& type) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/user/" % userId % "/rooms/" - % roomId % "/account_data/" % type); + QStringLiteral("/_matrix/client/r0") + % "/user/" % userId % "/rooms/" % roomId + % "/account_data/" % type); } GetAccountDataPerRoomJob::GetAccountDataPerRoomJob(const QString& userId, const QString& roomId, const QString& type) : BaseJob(HttpVerb::Get, QStringLiteral("GetAccountDataPerRoomJob"), - basePath % "/user/" % userId % "/rooms/" % roomId - % "/account_data/" % type) + QStringLiteral("/_matrix/client/r0") % "/user/" % userId + % "/rooms/" % roomId % "/account_data/" % type) {} diff --git a/lib/csapi/account-data.h b/lib/csapi/account-data.h index a8dba5c0..1c7b555b 100644 --- a/lib/csapi/account-data.h +++ b/lib/csapi/account-data.h @@ -6,12 +6,8 @@ #include "jobs/basejob.h" -#include <QtCore/QJsonObject> - namespace Quotient { -// Operations - /*! \brief Set some account_data for the user. * * Set some account_data for the client. This config is only visible to the user @@ -22,12 +18,15 @@ class SetAccountDataJob : public BaseJob { public: /*! \brief Set some account_data for the user. * + * * \param userId * The ID of the user to set account_data for. The access token must be * authorized to make requests for this user ID. + * * \param type * The event type of the account_data to set. Custom types should be * namespaced to avoid clashes. + * * \param content * The content of the account_data */ @@ -44,9 +43,11 @@ class GetAccountDataJob : public BaseJob { public: /*! \brief Get some account_data for the user. * + * * \param userId * The ID of the user to get account_data for. The access token must be * authorized to make requests for this user ID. + * * \param type * The event type of the account_data to get. Custom types should be * namespaced to avoid clashes. @@ -72,14 +73,18 @@ class SetAccountDataPerRoomJob : public BaseJob { public: /*! \brief Set some account_data for the user. * + * * \param userId * The ID of the user to set account_data for. The access token must be * authorized to make requests for this user ID. + * * \param roomId * The ID of the room to set account_data on. + * * \param type * The event type of the account_data to set. Custom types should be * namespaced to avoid clashes. + * * \param content * The content of the account_data */ @@ -97,11 +102,14 @@ class GetAccountDataPerRoomJob : public BaseJob { public: /*! \brief Get some account_data for the user. * + * * \param userId * The ID of the user to set account_data for. The access token must be * authorized to make requests for this user ID. + * * \param roomId * The ID of the room to get account_data for. + * * \param type * The event type of the account_data to get. Custom types should be * namespaced to avoid clashes. diff --git a/lib/csapi/admin.cpp b/lib/csapi/admin.cpp index 14173e94..9619c441 100644 --- a/lib/csapi/admin.cpp +++ b/lib/csapi/admin.cpp @@ -4,78 +4,18 @@ #include "admin.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<GetWhoIsJob::ConnectionInfo> { - static void fillFrom(const QJsonObject& jo, - GetWhoIsJob::ConnectionInfo& result) - { - fromJson(jo.value("ip"_ls), result.ip); - fromJson(jo.value("last_seen"_ls), result.lastSeen); - fromJson(jo.value("user_agent"_ls), result.userAgent); - } -}; - -template <> -struct JsonObjectConverter<GetWhoIsJob::SessionInfo> { - static void fillFrom(const QJsonObject& jo, GetWhoIsJob::SessionInfo& result) - { - fromJson(jo.value("connections"_ls), result.connections); - } -}; - -template <> -struct JsonObjectConverter<GetWhoIsJob::DeviceInfo> { - static void fillFrom(const QJsonObject& jo, GetWhoIsJob::DeviceInfo& result) - { - fromJson(jo.value("sessions"_ls), result.sessions); - } -}; - -} // namespace Quotient - -class GetWhoIsJob::Private { -public: - QString userId; - QHash<QString, DeviceInfo> devices; -}; - QUrl GetWhoIsJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/admin/whois/" % userId); + QStringLiteral("/_matrix/client/r0") + % "/admin/whois/" % userId); } GetWhoIsJob::GetWhoIsJob(const QString& userId) : BaseJob(HttpVerb::Get, QStringLiteral("GetWhoIsJob"), - basePath % "/admin/whois/" % userId) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/admin/whois/" % userId) {} - -GetWhoIsJob::~GetWhoIsJob() = default; - -const QString& GetWhoIsJob::userId() const { return d->userId; } - -const QHash<QString, GetWhoIsJob::DeviceInfo>& GetWhoIsJob::devices() const -{ - return d->devices; -} - -BaseJob::Status GetWhoIsJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("user_id"_ls), d->userId); - fromJson(json.value("devices"_ls), d->devices); - - return Success; -} diff --git a/lib/csapi/admin.h b/lib/csapi/admin.h index 75ae1eb0..6ad7d08d 100644 --- a/lib/csapi/admin.h +++ b/lib/csapi/admin.h @@ -4,17 +4,10 @@ #pragma once -#include "converters.h" - #include "jobs/basejob.h" -#include <QtCore/QHash> -#include <QtCore/QVector> - namespace Quotient { -// Operations - /*! \brief Gets information about a particular user. * * Gets information about a particular user. @@ -66,6 +59,7 @@ public: /*! \brief Gets information about a particular user. * + * * \param userId * The user to look up. */ @@ -77,22 +71,44 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); - ~GetWhoIsJob() override; // Result properties /// The Matrix user ID of the user. - const QString& userId() const; + QString userId() const { return loadFromJson<QString>("user_id"_ls); } - /// Each key is an identitfier for one of the user's devices. - const QHash<QString, DeviceInfo>& devices() const; + /// Each key is an identifier for one of the user's devices. + QHash<QString, DeviceInfo> devices() const + { + return loadFromJson<QHash<QString, DeviceInfo>>("devices"_ls); + } +}; + +template <> +struct JsonObjectConverter<GetWhoIsJob::ConnectionInfo> { + static void fillFrom(const QJsonObject& jo, + GetWhoIsJob::ConnectionInfo& result) + { + fromJson(jo.value("ip"_ls), result.ip); + fromJson(jo.value("last_seen"_ls), result.lastSeen); + fromJson(jo.value("user_agent"_ls), result.userAgent); + } +}; -protected: - Status parseJson(const QJsonDocument& data) override; +template <> +struct JsonObjectConverter<GetWhoIsJob::SessionInfo> { + static void fillFrom(const QJsonObject& jo, GetWhoIsJob::SessionInfo& result) + { + fromJson(jo.value("connections"_ls), result.connections); + } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter<GetWhoIsJob::DeviceInfo> { + static void fillFrom(const QJsonObject& jo, GetWhoIsJob::DeviceInfo& result) + { + fromJson(jo.value("sessions"_ls), result.sessions); + } }; } // namespace Quotient diff --git a/lib/csapi/administrative_contact.cpp b/lib/csapi/administrative_contact.cpp index 36d93e73..fa4f475a 100644 --- a/lib/csapi/administrative_contact.cpp +++ b/lib/csapi/administrative_contact.cpp @@ -4,164 +4,100 @@ #include "administrative_contact.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<GetAccount3PIDsJob::ThirdPartyIdentifier> { - static void fillFrom(const QJsonObject& jo, - GetAccount3PIDsJob::ThirdPartyIdentifier& result) - { - fromJson(jo.value("medium"_ls), result.medium); - fromJson(jo.value("address"_ls), result.address); - fromJson(jo.value("validated_at"_ls), result.validatedAt); - fromJson(jo.value("added_at"_ls), result.addedAt); - } -}; - -} // namespace Quotient - -class GetAccount3PIDsJob::Private { -public: - QVector<ThirdPartyIdentifier> threepids; -}; - QUrl GetAccount3PIDsJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/account/3pid"); + QStringLiteral("/_matrix/client/r0") + % "/account/3pid"); } GetAccount3PIDsJob::GetAccount3PIDsJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetAccount3PIDsJob"), - basePath % "/account/3pid") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/account/3pid") {} -GetAccount3PIDsJob::~GetAccount3PIDsJob() = default; - -const QVector<GetAccount3PIDsJob::ThirdPartyIdentifier>& -GetAccount3PIDsJob::threepids() const +Post3PIDsJob::Post3PIDsJob(const ThreePidCredentials& threePidCreds) + : BaseJob(HttpVerb::Post, QStringLiteral("Post3PIDsJob"), + QStringLiteral("/_matrix/client/r0") % "/account/3pid") { - return d->threepids; + QJsonObject _data; + addParam<>(_data, QStringLiteral("three_pid_creds"), threePidCreds); + setRequestData(std::move(_data)); } -BaseJob::Status GetAccount3PIDsJob::parseJson(const QJsonDocument& data) +Add3PIDJob::Add3PIDJob(const QString& clientSecret, const QString& sid, + const Omittable<AuthenticationData>& auth) + : BaseJob(HttpVerb::Post, QStringLiteral("Add3PIDJob"), + QStringLiteral("/_matrix/client/r0") % "/account/3pid/add") { - auto json = data.object(); - fromJson(json.value("threepids"_ls), d->threepids); - - return Success; + QJsonObject _data; + addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth); + addParam<>(_data, QStringLiteral("client_secret"), clientSecret); + addParam<>(_data, QStringLiteral("sid"), sid); + setRequestData(std::move(_data)); } -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<Post3PIDsJob::ThreePidCredentials> { - static void dumpTo(QJsonObject& jo, - const Post3PIDsJob::ThreePidCredentials& pod) - { - addParam<>(jo, QStringLiteral("client_secret"), pod.clientSecret); - addParam<>(jo, QStringLiteral("id_server"), pod.idServer); - addParam<>(jo, QStringLiteral("sid"), pod.sid); - } -}; - -} // namespace Quotient - -Post3PIDsJob::Post3PIDsJob(const ThreePidCredentials& threePidCreds, - Omittable<bool> bind) - : BaseJob(HttpVerb::Post, QStringLiteral("Post3PIDsJob"), - basePath % "/account/3pid") +Bind3PIDJob::Bind3PIDJob(const QString& clientSecret, const QString& idServer, + const QString& idAccessToken, const QString& sid) + : BaseJob(HttpVerb::Post, QStringLiteral("Bind3PIDJob"), + QStringLiteral("/_matrix/client/r0") % "/account/3pid/bind") { QJsonObject _data; - addParam<>(_data, QStringLiteral("three_pid_creds"), threePidCreds); - addParam<IfNotEmpty>(_data, QStringLiteral("bind"), bind); - setRequestData(_data); + addParam<>(_data, QStringLiteral("client_secret"), clientSecret); + addParam<>(_data, QStringLiteral("id_server"), idServer); + addParam<>(_data, QStringLiteral("id_access_token"), idAccessToken); + addParam<>(_data, QStringLiteral("sid"), sid); + setRequestData(std::move(_data)); } Delete3pidFromAccountJob::Delete3pidFromAccountJob(const QString& medium, - const QString& address) + const QString& address, + const QString& idServer) : BaseJob(HttpVerb::Post, QStringLiteral("Delete3pidFromAccountJob"), - basePath % "/account/3pid/delete") + QStringLiteral("/_matrix/client/r0") % "/account/3pid/delete") { QJsonObject _data; + addParam<IfNotEmpty>(_data, QStringLiteral("id_server"), idServer); addParam<>(_data, QStringLiteral("medium"), medium); addParam<>(_data, QStringLiteral("address"), address); - setRequestData(_data); + setRequestData(std::move(_data)); + addExpectedKey("id_server_unbind_result"); } -class RequestTokenTo3PIDEmailJob::Private { -public: - Sid data; -}; - -RequestTokenTo3PIDEmailJob::RequestTokenTo3PIDEmailJob( - const QString& clientSecret, const QString& email, int sendAttempt, - const QString& idServer, const QString& nextLink) - : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenTo3PIDEmailJob"), - basePath % "/account/3pid/email/requestToken", false) - , d(new Private) +Unbind3pidFromAccountJob::Unbind3pidFromAccountJob(const QString& medium, + const QString& address, + const QString& idServer) + : BaseJob(HttpVerb::Post, QStringLiteral("Unbind3pidFromAccountJob"), + QStringLiteral("/_matrix/client/r0") % "/account/3pid/unbind") { QJsonObject _data; - addParam<>(_data, QStringLiteral("client_secret"), clientSecret); - addParam<>(_data, QStringLiteral("email"), email); - addParam<>(_data, QStringLiteral("send_attempt"), sendAttempt); - addParam<IfNotEmpty>(_data, QStringLiteral("next_link"), nextLink); - addParam<>(_data, QStringLiteral("id_server"), idServer); - setRequestData(_data); + addParam<IfNotEmpty>(_data, QStringLiteral("id_server"), idServer); + addParam<>(_data, QStringLiteral("medium"), medium); + addParam<>(_data, QStringLiteral("address"), address); + setRequestData(std::move(_data)); + addExpectedKey("id_server_unbind_result"); } -RequestTokenTo3PIDEmailJob::~RequestTokenTo3PIDEmailJob() = default; - -const Sid& RequestTokenTo3PIDEmailJob::data() const { return d->data; } - -BaseJob::Status RequestTokenTo3PIDEmailJob::parseJson(const QJsonDocument& data) +RequestTokenTo3PIDEmailJob::RequestTokenTo3PIDEmailJob( + const EmailValidationData& body) + : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenTo3PIDEmailJob"), + QStringLiteral("/_matrix/client/r0") + % "/account/3pid/email/requestToken", + false) { - fromJson(data, d->data); - - return Success; + setRequestData(Data(toJson(body))); } -class RequestTokenTo3PIDMSISDNJob::Private { -public: - Sid data; -}; - RequestTokenTo3PIDMSISDNJob::RequestTokenTo3PIDMSISDNJob( - const QString& clientSecret, const QString& country, - const QString& phoneNumber, int sendAttempt, const QString& idServer, - const QString& nextLink) + const MsisdnValidationData& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenTo3PIDMSISDNJob"), - basePath % "/account/3pid/msisdn/requestToken", false) - , d(new Private) -{ - QJsonObject _data; - addParam<>(_data, QStringLiteral("client_secret"), clientSecret); - addParam<>(_data, QStringLiteral("country"), country); - addParam<>(_data, QStringLiteral("phone_number"), phoneNumber); - addParam<>(_data, QStringLiteral("send_attempt"), sendAttempt); - addParam<IfNotEmpty>(_data, QStringLiteral("next_link"), nextLink); - addParam<>(_data, QStringLiteral("id_server"), idServer); - setRequestData(_data); -} - -RequestTokenTo3PIDMSISDNJob::~RequestTokenTo3PIDMSISDNJob() = default; - -const Sid& RequestTokenTo3PIDMSISDNJob::data() const { return d->data; } - -BaseJob::Status RequestTokenTo3PIDMSISDNJob::parseJson(const QJsonDocument& data) + QStringLiteral("/_matrix/client/r0") + % "/account/3pid/msisdn/requestToken", + false) { - fromJson(data, d->data); - - return Success; + setRequestData(Data(toJson(body))); } diff --git a/lib/csapi/administrative_contact.h b/lib/csapi/administrative_contact.h index af98fe9c..53c89272 100644 --- a/lib/csapi/administrative_contact.h +++ b/lib/csapi/administrative_contact.h @@ -4,18 +4,15 @@ #pragma once -#include "converters.h" - -#include "csapi/../identity/definitions/sid.h" +#include "csapi/./definitions/request_email_validation.h" +#include "csapi/./definitions/request_msisdn_validation.h" +#include "csapi/definitions/auth_data.h" +#include "csapi/definitions/request_token_response.h" #include "jobs/basejob.h" -#include <QtCore/QVector> - namespace Quotient { -// Operations - /*! \brief Gets a list of a user's third party identifiers. * * Gets a list of the third party identifiers that the homeserver has @@ -63,7 +60,6 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetAccount3PIDsJob() override; // Result properties @@ -75,19 +71,36 @@ public: /// /// Identifiers in this list may be used by the homeserver as, for example, /// identifiers that it will accept to reset the user's account password. - const QVector<ThirdPartyIdentifier>& threepids() const; - -protected: - Status parseJson(const QJsonDocument& data) override; + QVector<ThirdPartyIdentifier> threepids() const + { + return loadFromJson<QVector<ThirdPartyIdentifier>>("threepids"_ls); + } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter<GetAccount3PIDsJob::ThirdPartyIdentifier> { + static void fillFrom(const QJsonObject& jo, + GetAccount3PIDsJob::ThirdPartyIdentifier& result) + { + fromJson(jo.value("medium"_ls), result.medium); + fromJson(jo.value("address"_ls), result.address); + fromJson(jo.value("validated_at"_ls), result.validatedAt); + fromJson(jo.value("added_at"_ls), result.addedAt); + } }; /*! \brief Adds contact information to the user's account. * * Adds contact information to the user's account. + * + * This endpoint is deprecated in favour of the more specific ``/3pid/add`` + * and ``/3pid/bind`` endpoints. + * + * .. Note:: + * Previously this endpoint supported a ``bind`` parameter. This parameter + * has been removed, making this endpoint behave as though it was ``false``. + * This results in this endpoint being an equivalent to ``/3pid/bind`` rather + * than dual-purpose. */ class Post3PIDsJob : public BaseJob { public: @@ -99,6 +112,10 @@ public: QString clientSecret; /// The identity server to use. QString idServer; + /// An access token previously registered with the identity server. + /// Servers can treat this as optional to distinguish between + /// r0.5-compatible clients and this specification version. + QString idAccessToken; /// The session identifier given by the identity server. QString sid; }; @@ -107,152 +124,247 @@ public: /*! \brief Adds contact information to the user's account. * + * * \param threePidCreds * The third party credentials to associate with the account. - * \param bind - * Whether the homeserver should also bind this third party - * identifier to the account's Matrix ID with the passed identity - * server. Default: ``false``. */ - explicit Post3PIDsJob(const ThreePidCredentials& threePidCreds, - Omittable<bool> bind = none); + explicit Post3PIDsJob(const ThreePidCredentials& threePidCreds); +}; + +template <> +struct JsonObjectConverter<Post3PIDsJob::ThreePidCredentials> { + static void dumpTo(QJsonObject& jo, + const Post3PIDsJob::ThreePidCredentials& pod) + { + addParam<>(jo, QStringLiteral("client_secret"), pod.clientSecret); + addParam<>(jo, QStringLiteral("id_server"), pod.idServer); + addParam<>(jo, QStringLiteral("id_access_token"), pod.idAccessToken); + addParam<>(jo, QStringLiteral("sid"), pod.sid); + } +}; + +/*! \brief Adds contact information to the user's account. + * + * This API endpoint uses the `User-Interactive Authentication API`_. + * + * Adds contact information to the user's account. Homeservers should use 3PIDs + * added through this endpoint for password resets instead of relying on the + * identity server. + * + * Homeservers should prevent the caller from adding a 3PID to their account if + * it has already been added to another user's account on the homeserver. + */ +class Add3PIDJob : public BaseJob { +public: + /*! \brief Adds contact information to the user's account. + * + * + * \param clientSecret + * The client secret used in the session with the homeserver. + * + * \param sid + * The session identifier given by the homeserver. + * + * \param auth + * Additional authentication information for the + * user-interactive authentication API. + */ + explicit Add3PIDJob(const QString& clientSecret, const QString& sid, + const Omittable<AuthenticationData>& auth = none); +}; + +/*! \brief Binds a 3PID to the user's account through an Identity Service. + * + * Binds a 3PID to the user's account through the specified identity server. + * + * Homeservers should not prevent this request from succeeding if another user + * has bound the 3PID. Homeservers should simply proxy any errors received by + * the identity server to the caller. + * + * Homeservers should track successful binds so they can be unbound later. + */ +class Bind3PIDJob : public BaseJob { +public: + /*! \brief Binds a 3PID to the user's account through an Identity Service. + * + * + * \param clientSecret + * The client secret used in the session with the identity server. + * + * \param idServer + * The identity server to use. + * + * \param idAccessToken + * An access token previously registered with the identity server. + * + * \param sid + * The session identifier given by the identity server. + */ + explicit Bind3PIDJob(const QString& clientSecret, const QString& idServer, + const QString& idAccessToken, const QString& sid); }; /*! \brief Deletes a third party identifier from the user's account * * Removes a third party identifier from the user's account. This might not * cause an unbind of the identifier from the identity server. + * + * Unlike other endpoints, this endpoint does not take an ``id_access_token`` + * parameter because the homeserver is expected to sign the request to the + * identity server instead. */ class Delete3pidFromAccountJob : public BaseJob { public: /*! \brief Deletes a third party identifier from the user's account * + * * \param medium * The medium of the third party identifier being removed. + * * \param address * The third party address being removed. + * + * \param idServer + * The identity server to unbind from. If not provided, the homeserver + * MUST use the ``id_server`` the identifier was added through. If the + * homeserver does not know the original ``id_server``, it MUST return + * a ``id_server_unbind_result`` of ``no-support``. */ explicit Delete3pidFromAccountJob(const QString& medium, - const QString& address); + const QString& address, + const QString& idServer = {}); + + // Result properties + + /// An indicator as to whether or not the homeserver was able to unbind + /// the 3PID from the identity server. ``success`` indicates that the + /// indentity server has unbound the identifier whereas ``no-support`` + /// indicates that the identity server refuses to support the request + /// or the homeserver was not able to determine an identity server to + /// unbind from. + QString idServerUnbindResult() const + { + return loadFromJson<QString>("id_server_unbind_result"_ls); + } +}; + +/*! \brief Removes a user's third party identifier from an identity server. + * + * Removes a user's third party identifier from the provided identity server + * without removing it from the homeserver. + * + * Unlike other endpoints, this endpoint does not take an ``id_access_token`` + * parameter because the homeserver is expected to sign the request to the + * identity server instead. + */ +class Unbind3pidFromAccountJob : public BaseJob { +public: + /*! \brief Removes a user's third party identifier from an identity server. + * + * + * \param medium + * The medium of the third party identifier being removed. + * + * \param address + * The third party address being removed. + * + * \param idServer + * The identity server to unbind from. If not provided, the homeserver + * MUST use the ``id_server`` the identifier was added through. If the + * homeserver does not know the original ``id_server``, it MUST return + * a ``id_server_unbind_result`` of ``no-support``. + */ + explicit Unbind3pidFromAccountJob(const QString& medium, + const QString& address, + const QString& idServer = {}); + + // Result properties + + /// An indicator as to whether or not the identity server was able to unbind + /// the 3PID. ``success`` indicates that the identity server has unbound the + /// identifier whereas ``no-support`` indicates that the identity server + /// refuses to support the request or the homeserver was not able to + /// determine an identity server to unbind from. + QString idServerUnbindResult() const + { + return loadFromJson<QString>("id_server_unbind_result"_ls); + } }; /*! \brief Begins the validation process for an email address for association * with the user's account. * - * Proxies the Identity Service API ``validate/email/requestToken``, but - * first checks that the given email address is **not** already associated - * with an account on this homeserver. This API should be used to request - * validation tokens when adding an email address to an account. This API's - * parameters and response are identical to that of the - * |/register/email/requestToken|_ endpoint. + * The homeserver must check that the given email address is **not** + * already associated with an account on this homeserver. This API should + * be used to request validation tokens when adding an email address to an + * account. This API's parameters and response are identical to that of + * the |/register/email/requestToken|_ endpoint. The homeserver should validate + * the email itself, either by sending a validation email itself or by using + * a service it has control over. */ class RequestTokenTo3PIDEmailJob : public BaseJob { public: /*! \brief Begins the validation process for an email address for * association with the user's account. * - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it - * must not be empty. - * \param email - * The email address to validate. - * \param sendAttempt - * The server will only send an email if the ``send_attempt`` - * is a number greater than the most recent one which it has seen, - * scoped to that ``email`` + ``client_secret`` pair. This is to - * avoid repeatedly sending the same email in the case of request - * retries between the POSTing user and the identity server. - * The client should increment this value if they desire a new - * email (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. + * + * \param body + * The homeserver must check that the given email address is **not** + * already associated with an account on this homeserver. This API should + * be used to request validation tokens when adding an email address to an + * account. This API's parameters and response are identical to that of + * the |/register/email/requestToken|_ endpoint. The homeserver should + * validate the email itself, either by sending a validation email itself or + * by using a service it has control over. */ - explicit RequestTokenTo3PIDEmailJob(const QString& clientSecret, - const QString& email, int sendAttempt, - const QString& idServer, - const QString& nextLink = {}); - - ~RequestTokenTo3PIDEmailJob() override; + explicit RequestTokenTo3PIDEmailJob(const EmailValidationData& body); // Result properties - /// An email was sent to the given address. - const Sid& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + /// An email was sent to the given address. Note that this may be an + /// email containing the validation token or it may be informing the + /// user of an error. + RequestTokenResponse data() const + { + return fromJson<RequestTokenResponse>(jsonData()); + } }; /*! \brief Begins the validation process for a phone number for association with * the user's account. * - * Proxies the Identity Service API ``validate/msisdn/requestToken``, but - * first checks that the given phone number is **not** already associated - * with an account on this homeserver. This API should be used to request - * validation tokens when adding a phone number to an account. This API's - * parameters and response are identical to that of the - * |/register/msisdn/requestToken|_ endpoint. + * The homeserver must check that the given phone number is **not** + * already associated with an account on this homeserver. This API should + * be used to request validation tokens when adding a phone number to an + * account. This API's parameters and response are identical to that of + * the |/register/msisdn/requestToken|_ endpoint. The homeserver should validate + * the phone number itself, either by sending a validation message itself or by + * using a service it has control over. */ class RequestTokenTo3PIDMSISDNJob : public BaseJob { public: /*! \brief Begins the validation process for a phone number for association * with the user's account. * - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it - * must not be empty. - * \param country - * The two-letter uppercase ISO country code that the number in - * ``phone_number`` should be parsed as if it were dialled from. - * \param phoneNumber - * The phone number to validate. - * \param sendAttempt - * The server will only send an SMS if the ``send_attempt`` is a - * number greater than the most recent one which it has seen, - * scoped to that ``country`` + ``phone_number`` + ``client_secret`` - * triple. This is to avoid repeatedly sending the same SMS in - * the case of request retries between the POSTing user and the - * identity server. The client should increment this value if - * they desire a new SMS (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. + * + * \param body + * The homeserver must check that the given phone number is **not** + * already associated with an account on this homeserver. This API should + * be used to request validation tokens when adding a phone number to an + * account. This API's parameters and response are identical to that of + * the |/register/msisdn/requestToken|_ endpoint. The homeserver should + * validate the phone number itself, either by sending a validation message + * itself or by using a service it has control over. */ - explicit RequestTokenTo3PIDMSISDNJob(const QString& clientSecret, - const QString& country, - const QString& phoneNumber, - int sendAttempt, - const QString& idServer, - const QString& nextLink = {}); - - ~RequestTokenTo3PIDMSISDNJob() override; + explicit RequestTokenTo3PIDMSISDNJob(const MsisdnValidationData& body); // Result properties /// An SMS message was sent to the given phone number. - const Sid& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + RequestTokenResponse data() const + { + return fromJson<RequestTokenResponse>(jsonData()); + } }; } // namespace Quotient diff --git a/lib/csapi/appservice_room_directory.cpp b/lib/csapi/appservice_room_directory.cpp index bfcab2a7..e8ec55bf 100644 --- a/lib/csapi/appservice_room_directory.cpp +++ b/lib/csapi/appservice_room_directory.cpp @@ -4,21 +4,18 @@ #include "appservice_room_directory.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - UpdateAppserviceRoomDirectoryVsibilityJob::UpdateAppserviceRoomDirectoryVsibilityJob( const QString& networkId, const QString& roomId, const QString& visibility) : BaseJob(HttpVerb::Put, QStringLiteral("UpdateAppserviceRoomDirectoryVsibilityJob"), - basePath % "/directory/list/appservice/" % networkId % "/" % roomId) + QStringLiteral("/_matrix/client/r0") + % "/directory/list/appservice/" % networkId % "/" % roomId) { QJsonObject _data; addParam<>(_data, QStringLiteral("visibility"), visibility); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/appservice_room_directory.h b/lib/csapi/appservice_room_directory.h index f0ad78b0..d103177c 100644 --- a/lib/csapi/appservice_room_directory.h +++ b/lib/csapi/appservice_room_directory.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Updates a room's visibility in the application service's room * directory. * @@ -28,12 +26,15 @@ public: /*! \brief Updates a room's visibility in the application service's room * directory. * + * * \param networkId * The protocol (network) ID to update the room list for. This would * have been provided by the application service as being listed as * a supported protocol. + * * \param roomId * The room ID to add to the directory. + * * \param visibility * Whether the room should be visible (public) in the directory * or not (private). diff --git a/lib/csapi/banning.cpp b/lib/csapi/banning.cpp index 6ac9b3ba..8a8ec664 100644 --- a/lib/csapi/banning.cpp +++ b/lib/csapi/banning.cpp @@ -4,30 +4,27 @@ #include "banning.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - BanJob::BanJob(const QString& roomId, const QString& userId, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("BanJob"), - basePath % "/rooms/" % roomId % "/ban") + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/ban") { QJsonObject _data; addParam<>(_data, QStringLiteral("user_id"), userId); addParam<IfNotEmpty>(_data, QStringLiteral("reason"), reason); - setRequestData(_data); + setRequestData(std::move(_data)); } UnbanJob::UnbanJob(const QString& roomId, const QString& userId) : BaseJob(HttpVerb::Post, QStringLiteral("UnbanJob"), - basePath % "/rooms/" % roomId % "/unban") + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/unban") { QJsonObject _data; addParam<>(_data, QStringLiteral("user_id"), userId); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/banning.h b/lib/csapi/banning.h index 6bf782e7..4faaaa81 100644 --- a/lib/csapi/banning.h +++ b/lib/csapi/banning.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Ban a user in the room. * * Ban a user in the room. If the user is currently in the room, also kick them. @@ -24,10 +22,13 @@ class BanJob : public BaseJob { public: /*! \brief Ban a user in the room. * + * * \param roomId * The room identifier (not alias) from which the user should be banned. + * * \param userId * The fully qualified user ID of the user being banned. + * * \param reason * The reason the user has been banned. This will be supplied as the * ``reason`` on the target's updated `m.room.member`_ event. @@ -49,8 +50,10 @@ class UnbanJob : public BaseJob { public: /*! \brief Unban a user from the room. * + * * \param roomId * The room identifier (not alias) from which the user should be unbanned. + * * \param userId * The fully qualified user ID of the user being unbanned. */ diff --git a/lib/csapi/capabilities.cpp b/lib/csapi/capabilities.cpp index 8c7ed141..33a53cad 100644 --- a/lib/csapi/capabilities.cpp +++ b/lib/csapi/capabilities.cpp @@ -4,80 +4,20 @@ #include "capabilities.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<GetCapabilitiesJob::ChangePasswordCapability> { - static void fillFrom(const QJsonObject& jo, - GetCapabilitiesJob::ChangePasswordCapability& result) - { - fromJson(jo.value("enabled"_ls), result.enabled); - } -}; - -template <> -struct JsonObjectConverter<GetCapabilitiesJob::RoomVersionsCapability> { - static void fillFrom(const QJsonObject& jo, - GetCapabilitiesJob::RoomVersionsCapability& result) - { - fromJson(jo.value("default"_ls), result.defaultVersion); - fromJson(jo.value("available"_ls), result.available); - } -}; - -template <> -struct JsonObjectConverter<GetCapabilitiesJob::Capabilities> { - static void fillFrom(QJsonObject jo, - GetCapabilitiesJob::Capabilities& result) - { - fromJson(jo.take("m.change_password"_ls), result.changePassword); - fromJson(jo.take("m.room_versions"_ls), result.roomVersions); - fromJson(jo, result.additionalProperties); - } -}; - -} // namespace Quotient - -class GetCapabilitiesJob::Private { -public: - Capabilities capabilities; -}; - QUrl GetCapabilitiesJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/capabilities"); + QStringLiteral("/_matrix/client/r0") + % "/capabilities"); } GetCapabilitiesJob::GetCapabilitiesJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetCapabilitiesJob"), - basePath % "/capabilities") - , d(new Private) -{} - -GetCapabilitiesJob::~GetCapabilitiesJob() = default; - -const GetCapabilitiesJob::Capabilities& GetCapabilitiesJob::capabilities() const + QStringLiteral("/_matrix/client/r0") % "/capabilities") { - return d->capabilities; -} - -BaseJob::Status GetCapabilitiesJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("capabilities"_ls)) - return { IncorrectResponse, - "The key 'capabilities' not found in the response" }; - fromJson(json.value("capabilities"_ls), d->capabilities); - - return Success; + addExpectedKey("capabilities"); } diff --git a/lib/csapi/capabilities.h b/lib/csapi/capabilities.h index 9f46ab2e..da50c8c1 100644 --- a/lib/csapi/capabilities.h +++ b/lib/csapi/capabilities.h @@ -4,17 +4,10 @@ #pragma once -#include "converters.h" - #include "jobs/basejob.h" -#include <QtCore/QHash> -#include <QtCore/QJsonObject> - namespace Quotient { -// Operations - /*! \brief Gets information about the server's capabilities. * * Gets information about the server's supported feature set @@ -61,20 +54,45 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetCapabilitiesJob() override; // Result properties /// The custom capabilities the server supports, using the /// Java package naming convention. - const Capabilities& capabilities() const; + Capabilities capabilities() const + { + return loadFromJson<Capabilities>("capabilities"_ls); + } +}; -protected: - Status parseJson(const QJsonDocument& data) override; +template <> +struct JsonObjectConverter<GetCapabilitiesJob::ChangePasswordCapability> { + static void fillFrom(const QJsonObject& jo, + GetCapabilitiesJob::ChangePasswordCapability& result) + { + fromJson(jo.value("enabled"_ls), result.enabled); + } +}; + +template <> +struct JsonObjectConverter<GetCapabilitiesJob::RoomVersionsCapability> { + static void fillFrom(const QJsonObject& jo, + GetCapabilitiesJob::RoomVersionsCapability& result) + { + fromJson(jo.value("default"_ls), result.defaultVersion); + fromJson(jo.value("available"_ls), result.available); + } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter<GetCapabilitiesJob::Capabilities> { + static void fillFrom(QJsonObject jo, + GetCapabilitiesJob::Capabilities& result) + { + fromJson(jo.take("m.change_password"_ls), result.changePassword); + fromJson(jo.take("m.room_versions"_ls), result.roomVersions); + fromJson(jo, result.additionalProperties); + } }; } // namespace Quotient diff --git a/lib/csapi/content-repo.cpp b/lib/csapi/content-repo.cpp index d811426d..7ae89739 100644 --- a/lib/csapi/content-repo.cpp +++ b/lib/csapi/content-repo.cpp @@ -4,21 +4,11 @@ #include "content-repo.h" -#include "converters.h" - #include <QtCore/QStringBuilder> -#include <QtNetwork/QNetworkReply> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/media/r0"); - -class UploadContentJob::Private { -public: - QString contentUri; -}; - -BaseJob::Query queryToUploadContent(const QString& filename) +auto queryToUploadContent(const QString& filename) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("filename"), filename); @@ -28,36 +18,15 @@ BaseJob::Query queryToUploadContent(const QString& filename) UploadContentJob::UploadContentJob(QIODevice* content, const QString& filename, const QString& contentType) : BaseJob(HttpVerb::Post, QStringLiteral("UploadContentJob"), - basePath % "/upload", queryToUploadContent(filename)) - , d(new Private) + QStringLiteral("/_matrix/media/r0") % "/upload", + queryToUploadContent(filename)) { setRequestHeader("Content-Type", contentType.toLatin1()); setRequestData(Data(content)); + addExpectedKey("content_uri"); } -UploadContentJob::~UploadContentJob() = default; - -const QString& UploadContentJob::contentUri() const { return d->contentUri; } - -BaseJob::Status UploadContentJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("content_uri"_ls)) - return { IncorrectResponse, - "The key 'content_uri' not found in the response" }; - fromJson(json.value("content_uri"_ls), d->contentUri); - - return Success; -} - -class GetContentJob::Private { -public: - QString contentType; - QString contentDisposition; - QIODevice* data; -}; - -BaseJob::Query queryToGetContent(bool allowRemote) +auto queryToGetContent(bool allowRemote) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("allow_remote"), allowRemote); @@ -68,7 +37,8 @@ QUrl GetContentJob::makeRequestUrl(QUrl baseUrl, const QString& serverName, const QString& mediaId, bool allowRemote) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/download/" % serverName % "/" + QStringLiteral("/_matrix/media/r0") + % "/download/" % serverName % "/" % mediaId, queryToGetContent(allowRemote)); } @@ -76,40 +46,14 @@ QUrl GetContentJob::makeRequestUrl(QUrl baseUrl, const QString& serverName, GetContentJob::GetContentJob(const QString& serverName, const QString& mediaId, bool allowRemote) : BaseJob(HttpVerb::Get, QStringLiteral("GetContentJob"), - basePath % "/download/" % serverName % "/" % mediaId, + QStringLiteral("/_matrix/media/r0") % "/download/" % serverName + % "/" % mediaId, queryToGetContent(allowRemote), {}, false) - , d(new Private) { setExpectedContentTypes({ "*/*" }); } -GetContentJob::~GetContentJob() = default; - -const QString& GetContentJob::contentType() const { return d->contentType; } - -const QString& GetContentJob::contentDisposition() const -{ - return d->contentDisposition; -} - -QIODevice* GetContentJob::data() const { return d->data; } - -BaseJob::Status GetContentJob::parseReply(QNetworkReply* reply) -{ - d->contentType = reply->rawHeader("Content-Type"); - d->contentDisposition = reply->rawHeader("Content-Disposition"); - d->data = reply; - return Success; -} - -class GetContentOverrideNameJob::Private { -public: - QString contentType; - QString contentDisposition; - QIODevice* data; -}; - -BaseJob::Query queryToGetContentOverrideName(bool allowRemote) +auto queryToGetContentOverrideName(bool allowRemote) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("allow_remote"), allowRemote); @@ -123,7 +67,8 @@ QUrl GetContentOverrideNameJob::makeRequestUrl(QUrl baseUrl, bool allowRemote) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/download/" % serverName % "/" + QStringLiteral("/_matrix/media/r0") + % "/download/" % serverName % "/" % mediaId % "/" % fileName, queryToGetContentOverrideName(allowRemote)); } @@ -133,45 +78,15 @@ GetContentOverrideNameJob::GetContentOverrideNameJob(const QString& serverName, const QString& fileName, bool allowRemote) : BaseJob(HttpVerb::Get, QStringLiteral("GetContentOverrideNameJob"), - basePath % "/download/" % serverName % "/" % mediaId % "/" - % fileName, + QStringLiteral("/_matrix/media/r0") % "/download/" % serverName + % "/" % mediaId % "/" % fileName, queryToGetContentOverrideName(allowRemote), {}, false) - , d(new Private) { setExpectedContentTypes({ "*/*" }); } -GetContentOverrideNameJob::~GetContentOverrideNameJob() = default; - -const QString& GetContentOverrideNameJob::contentType() const -{ - return d->contentType; -} - -const QString& GetContentOverrideNameJob::contentDisposition() const -{ - return d->contentDisposition; -} - -QIODevice* GetContentOverrideNameJob::data() const { return d->data; } - -BaseJob::Status GetContentOverrideNameJob::parseReply(QNetworkReply* reply) -{ - d->contentType = reply->rawHeader("Content-Type"); - d->contentDisposition = reply->rawHeader("Content-Disposition"); - d->data = reply; - return Success; -} - -class GetContentThumbnailJob::Private { -public: - QString contentType; - QIODevice* data; -}; - -BaseJob::Query queryToGetContentThumbnail(int width, int height, - const QString& method, - bool allowRemote) +auto queryToGetContentThumbnail(int width, int height, const QString& method, + bool allowRemote) { BaseJob::Query _q; addParam<>(_q, QStringLiteral("width"), width); @@ -189,7 +104,8 @@ QUrl GetContentThumbnailJob::makeRequestUrl(QUrl baseUrl, { return BaseJob::makeRequestUrl( std::move(baseUrl), - basePath % "/thumbnail/" % serverName % "/" % mediaId, + QStringLiteral("/_matrix/media/r0") % "/thumbnail/" % serverName % "/" + % mediaId, queryToGetContentThumbnail(width, height, method, allowRemote)); } @@ -198,37 +114,15 @@ GetContentThumbnailJob::GetContentThumbnailJob(const QString& serverName, int height, const QString& method, bool allowRemote) : BaseJob(HttpVerb::Get, QStringLiteral("GetContentThumbnailJob"), - basePath % "/thumbnail/" % serverName % "/" % mediaId, + QStringLiteral("/_matrix/media/r0") % "/thumbnail/" % serverName + % "/" % mediaId, queryToGetContentThumbnail(width, height, method, allowRemote), {}, false) - , d(new Private) { setExpectedContentTypes({ "image/jpeg", "image/png" }); } -GetContentThumbnailJob::~GetContentThumbnailJob() = default; - -const QString& GetContentThumbnailJob::contentType() const -{ - return d->contentType; -} - -QIODevice* GetContentThumbnailJob::data() const { return d->data; } - -BaseJob::Status GetContentThumbnailJob::parseReply(QNetworkReply* reply) -{ - d->contentType = reply->rawHeader("Content-Type"); - d->data = reply; - return Success; -} - -class GetUrlPreviewJob::Private { -public: - Omittable<qint64> matrixImageSize; - QString ogImage; -}; - -BaseJob::Query queryToGetUrlPreview(const QString& url, Omittable<qint64> ts) +auto queryToGetUrlPreview(const QString& url, Omittable<qint64> ts) { BaseJob::Query _q; addParam<>(_q, QStringLiteral("url"), url); @@ -239,58 +133,26 @@ BaseJob::Query queryToGetUrlPreview(const QString& url, Omittable<qint64> ts) QUrl GetUrlPreviewJob::makeRequestUrl(QUrl baseUrl, const QString& url, Omittable<qint64> ts) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/preview_url", + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/media/r0") + % "/preview_url", queryToGetUrlPreview(url, ts)); } GetUrlPreviewJob::GetUrlPreviewJob(const QString& url, Omittable<qint64> ts) : BaseJob(HttpVerb::Get, QStringLiteral("GetUrlPreviewJob"), - basePath % "/preview_url", queryToGetUrlPreview(url, ts)) - , d(new Private) + QStringLiteral("/_matrix/media/r0") % "/preview_url", + queryToGetUrlPreview(url, ts)) {} -GetUrlPreviewJob::~GetUrlPreviewJob() = default; - -Omittable<qint64> GetUrlPreviewJob::matrixImageSize() const -{ - return d->matrixImageSize; -} - -const QString& GetUrlPreviewJob::ogImage() const { return d->ogImage; } - -BaseJob::Status GetUrlPreviewJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("matrix:image:size"_ls), d->matrixImageSize); - fromJson(json.value("og:image"_ls), d->ogImage); - - return Success; -} - -class GetConfigJob::Private { -public: - Omittable<qint64> uploadSize; -}; - QUrl GetConfigJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/config"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/media/r0") + % "/config"); } GetConfigJob::GetConfigJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetConfigJob"), - basePath % "/config") - , d(new Private) + QStringLiteral("/_matrix/media/r0") % "/config") {} - -GetConfigJob::~GetConfigJob() = default; - -Omittable<qint64> GetConfigJob::uploadSize() const { return d->uploadSize; } - -BaseJob::Status GetConfigJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("m.upload.size"_ls), d->uploadSize); - - return Success; -} diff --git a/lib/csapi/content-repo.h b/lib/csapi/content-repo.h index 6263ef0b..0eb273bc 100644 --- a/lib/csapi/content-repo.h +++ b/lib/csapi/content-repo.h @@ -4,16 +4,13 @@ #pragma once -#include "converters.h" - #include "jobs/basejob.h" #include <QtCore/QIODevice> +#include <QtNetwork/QNetworkReply> namespace Quotient { -// Operations - /*! \brief Upload some content to the content repository. * */ @@ -21,28 +18,26 @@ class UploadContentJob : public BaseJob { public: /*! \brief Upload some content to the content repository. * + * * \param content + * The content to be uploaded. + * * \param filename * The name of the file being uploaded + * * \param contentType * The content type of the file being uploaded */ explicit UploadContentJob(QIODevice* content, const QString& filename = {}, const QString& contentType = {}); - ~UploadContentJob() override; - // Result properties - /// The MXC URI to the uploaded content. - const QString& contentUri() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + /// The `MXC URI`_ to the uploaded content. + QString contentUri() const + { + return loadFromJson<QString>("content_uri"_ls); + } }; /*! \brief Download content from the content repository. @@ -52,10 +47,13 @@ class GetContentJob : public BaseJob { public: /*! \brief Download content from the content repository. * + * * \param serverName * The server name from the ``mxc://`` URI (the authoritory component) + * * \param mediaId * The media ID from the ``mxc://`` URI (the path component) + * * \param allowRemote * Indicates to the server that it should not attempt to fetch the media * if it is deemed remote. This is to prevent routing loops where the server @@ -71,44 +69,47 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& serverName, const QString& mediaId, bool allowRemote = true); - ~GetContentJob() override; // Result properties /// The content type of the file that was previously uploaded. - const QString& contentType() const; + QString contentType() const { return reply()->rawHeader("Content-Type"); } /// The name of the file that was previously uploaded, if set. - const QString& contentDisposition() const; + QString contentDisposition() const + { + return reply()->rawHeader("Content-Disposition"); + } /// The content that was previously uploaded. - QIODevice* data() const; - -protected: - Status parseReply(QNetworkReply* reply) override; - -private: - class Private; - QScopedPointer<Private> d; + QIODevice* data() { return reply(); } }; -/*! \brief Download content from the content repository as a given filename. +/*! \brief Download content from the content repository. This is the same as +the download endpoint above, except permitting a desired file name. * */ class GetContentOverrideNameJob : public BaseJob { public: - /*! \brief Download content from the content repository as a given filename. + /*! \brief Download content from the content repository. This is the same as +the download endpoint above, except permitting a desired file name. + * * * \param serverName * The server name from the ``mxc://`` URI (the authoritory component) + * * \param mediaId * The media ID from the ``mxc://`` URI (the path component) + * * \param fileName - * The filename to give in the Content-Disposition + * A filename to give in the ``Content-Disposition`` header. + * * \param allowRemote * Indicates to the server that it should not attempt to fetch the media - * if it is deemed remote. This is to prevent routing loops where the server - * contacts itself. Defaults to true if not provided. +if it is deemed + * remote. This is to prevent routing loops where the server contacts +itself. Defaults to + * true if not provided. */ explicit GetContentOverrideNameJob(const QString& serverName, const QString& mediaId, @@ -123,50 +124,57 @@ public: static QUrl makeRequestUrl(QUrl baseUrl, const QString& serverName, const QString& mediaId, const QString& fileName, bool allowRemote = true); - ~GetContentOverrideNameJob() override; // Result properties /// The content type of the file that was previously uploaded. - const QString& contentType() const; + QString contentType() const { return reply()->rawHeader("Content-Type"); } - /// The name of file given in the request - const QString& contentDisposition() const; + /// The ``fileName`` requested or the name of the file that was previously + /// uploaded, if set. + QString contentDisposition() const + { + return reply()->rawHeader("Content-Disposition"); + } /// The content that was previously uploaded. - QIODevice* data() const; - -protected: - Status parseReply(QNetworkReply* reply) override; - -private: - class Private; - QScopedPointer<Private> d; + QIODevice* data() { return reply(); } }; -/*! \brief Download a thumbnail of the content from the content repository. +/*! \brief Download a thumbnail of content from the content repository. See the +`thumbnailing <#thumbnails>`_ section for more information. * */ class GetContentThumbnailJob : public BaseJob { public: - /*! \brief Download a thumbnail of the content from the content repository. + /*! \brief Download a thumbnail of content from the content repository. See +the `thumbnailing <#thumbnails>`_ section for more information. + * * * \param serverName * The server name from the ``mxc://`` URI (the authoritory component) + * * \param mediaId * The media ID from the ``mxc://`` URI (the path component) + * * \param width - * The *desired* width of the thumbnail. The actual thumbnail may not - * match the size specified. + * The *desired* width of the thumbnail. The actual thumbnail may be + * larger than the size specified. + * * \param height - * The *desired* height of the thumbnail. The actual thumbnail may not - * match the size specified. + * The *desired* height of the thumbnail. The actual thumbnail may be + * larger than the size specified. + * * \param method - * The desired resizing method. + * The desired resizing method. See the `thumbnailing <#thumbnails>`_ + * section for more information. + * * \param allowRemote * Indicates to the server that it should not attempt to fetch the media - * if it is deemed remote. This is to prevent routing loops where the server - * contacts itself. Defaults to true if not provided. +if it is deemed + * remote. This is to prevent routing loops where the server contacts +itself. Defaults to + * true if not provided. */ explicit GetContentThumbnailJob(const QString& serverName, const QString& mediaId, int width, @@ -182,33 +190,35 @@ public: const QString& mediaId, int width, int height, const QString& method = {}, bool allowRemote = true); - ~GetContentThumbnailJob() override; // Result properties /// The content type of the thumbnail. - const QString& contentType() const; + QString contentType() const { return reply()->rawHeader("Content-Type"); } /// A thumbnail of the requested content. - QIODevice* data() const; - -protected: - Status parseReply(QNetworkReply* reply) override; - -private: - class Private; - QScopedPointer<Private> d; + QIODevice* data() { return reply(); } }; /*! \brief Get information about a URL for a client * + * Get information about a URL for the client. Typically this is called when a + * client sees a URL in a message and wants to render a preview for the user. + * + * .. Note:: + * Clients should consider avoiding this endpoint for URLs posted in encrypted + * rooms. Encrypted rooms often contain more sensitive information the users + * do not want to share with the homeserver, and this can mean that the URLs + * being shared should also not be shared with the homeserver. */ class GetUrlPreviewJob : public BaseJob { public: /*! \brief Get information about a URL for a client * + * * \param url - * The URL to get a preview of + * The URL to get a preview of. + * * \param ts * The preferred point in time to return a preview for. The server may * return a newer version if it does not have the requested version @@ -223,22 +233,17 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& url, Omittable<qint64> ts = none); - ~GetUrlPreviewJob() override; // Result properties /// The byte-size of the image. Omitted if there is no image attached. - Omittable<qint64> matrixImageSize() const; + Omittable<qint64> matrixImageSize() const + { + return loadFromJson<Omittable<qint64>>("matrix:image:size"_ls); + } - /// An MXC URI to the image. Omitted if there is no image. - const QString& ogImage() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + /// An `MXC URI`_ to the image. Omitted if there is no image. + QString ogImage() const { return loadFromJson<QString>("og:image"_ls); } }; /*! \brief Get the configuration for the content repository. @@ -265,21 +270,16 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetConfigJob() override; // Result properties /// The maximum size an upload can be in bytes. /// Clients SHOULD use this as a guide when uploading content. /// If not listed or null, the size limit should be treated as unknown. - Omittable<qint64> uploadSize() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + Omittable<qint64> uploadSize() const + { + return loadFromJson<Omittable<qint64>>("m.upload.size"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/create_room.cpp b/lib/csapi/create_room.cpp index ec2a1855..a94f9951 100644 --- a/lib/csapi/create_room.cpp +++ b/lib/csapi/create_room.cpp @@ -4,44 +4,10 @@ #include "create_room.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<CreateRoomJob::Invite3pid> { - static void dumpTo(QJsonObject& jo, const CreateRoomJob::Invite3pid& pod) - { - addParam<>(jo, QStringLiteral("id_server"), pod.idServer); - addParam<>(jo, QStringLiteral("medium"), pod.medium); - addParam<>(jo, QStringLiteral("address"), pod.address); - } -}; - -template <> -struct JsonObjectConverter<CreateRoomJob::StateEvent> { - static void dumpTo(QJsonObject& jo, const CreateRoomJob::StateEvent& pod) - { - addParam<>(jo, QStringLiteral("type"), pod.type); - addParam<IfNotEmpty>(jo, QStringLiteral("state_key"), pod.stateKey); - addParam<>(jo, QStringLiteral("content"), pod.content); - } -}; - -} // namespace Quotient - -class CreateRoomJob::Private { -public: - QString roomId; -}; - CreateRoomJob::CreateRoomJob(const QString& visibility, const QString& roomAliasName, const QString& name, const QString& topic, const QStringList& invite, @@ -52,8 +18,7 @@ CreateRoomJob::CreateRoomJob(const QString& visibility, const QString& preset, Omittable<bool> isDirect, const QJsonObject& powerLevelContentOverride) : BaseJob(HttpVerb::Post, QStringLiteral("CreateRoomJob"), - basePath % "/createRoom") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/createRoom") { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("visibility"), visibility); @@ -71,20 +36,6 @@ CreateRoomJob::CreateRoomJob(const QString& visibility, addParam<IfNotEmpty>(_data, QStringLiteral("is_direct"), isDirect); addParam<IfNotEmpty>(_data, QStringLiteral("power_level_content_override"), powerLevelContentOverride); - setRequestData(_data); -} - -CreateRoomJob::~CreateRoomJob() = default; - -const QString& CreateRoomJob::roomId() const { return d->roomId; } - -BaseJob::Status CreateRoomJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("room_id"_ls)) - return { IncorrectResponse, - "The key 'room_id' not found in the response" }; - fromJson(json.value("room_id"_ls), d->roomId); - - return Success; + setRequestData(std::move(_data)); + addExpectedKey("room_id"); } diff --git a/lib/csapi/create_room.h b/lib/csapi/create_room.h index f2bd9333..82db9e59 100644 --- a/lib/csapi/create_room.h +++ b/lib/csapi/create_room.h @@ -4,17 +4,10 @@ #pragma once -#include "converters.h" - #include "jobs/basejob.h" -#include <QtCore/QJsonObject> -#include <QtCore/QVector> - namespace Quotient { -// Operations - /*! \brief Create a new room * * Create a new room with various configuration options. @@ -23,21 +16,27 @@ namespace Quotient { * the new room, including checking power levels for each event. It MUST * apply the events implied by the request in the following order: * - * 0. A default ``m.room.power_levels`` event, giving the room creator + * 1. The ``m.room.create`` event itself. Must be the first event in the + * room. + * + * 2. An ``m.room.member`` event for the creator to join the room. This is + * needed so the remaining events can be sent. + * + * 3. A default ``m.room.power_levels`` event, giving the room creator * (and not other members) permission to send state events. Overridden * by the ``power_level_content_override`` parameter. * - * 1. Events set by the ``preset``. Currently these are the + * 4. Events set by the ``preset``. Currently these are the * ``m.room.join_rules``, * ``m.room.history_visibility``, and ``m.room.guest_access`` state events. * - * 2. Events listed in ``initial_state``, in the order that they are + * 5. Events listed in ``initial_state``, in the order that they are * listed. * - * 3. Events implied by ``name`` and ``topic`` (``m.room.name`` and + * 6. Events implied by ``name`` and ``topic`` (``m.room.name`` and * ``m.room.topic`` state events). * - * 4. Invite events implied by ``invite`` and ``invite_3pid`` (``m.room.member`` + * 7. Invite events implied by ``invite`` and ``invite_3pid`` (``m.room.member`` * with * ``membership: invite`` and ``m.room.third_party_invite``). * @@ -69,23 +68,29 @@ public: /// the new room, including checking power levels for each event. It MUST /// apply the events implied by the request in the following order: /// - /// 0. A default ``m.room.power_levels`` event, giving the room creator + /// 1. The ``m.room.create`` event itself. Must be the first event in the + /// room. + /// + /// 2. An ``m.room.member`` event for the creator to join the room. This is + /// needed so the remaining events can be sent. + /// + /// 3. A default ``m.room.power_levels`` event, giving the room creator /// (and not other members) permission to send state events. Overridden /// by the ``power_level_content_override`` parameter. /// - /// 1. Events set by the ``preset``. Currently these are the + /// 4. Events set by the ``preset``. Currently these are the /// ``m.room.join_rules``, /// ``m.room.history_visibility``, and ``m.room.guest_access`` state /// events. /// - /// 2. Events listed in ``initial_state``, in the order that they are + /// 5. Events listed in ``initial_state``, in the order that they are /// listed. /// - /// 3. Events implied by ``name`` and ``topic`` (``m.room.name`` and + /// 6. Events implied by ``name`` and ``topic`` (``m.room.name`` and /// ``m.room.topic`` /// state events). /// - /// 4. Invite events implied by ``invite`` and ``invite_3pid`` + /// 7. Invite events implied by ``invite`` and ``invite_3pid`` /// (``m.room.member`` with /// ``membership: invite`` and ``m.room.third_party_invite``). /// @@ -111,6 +116,10 @@ public: /// The hostname+port of the identity server which should be used for /// third party identifier lookups. QString idServer; + /// An access token previously registered with the identity server. + /// Servers can treat this as optional to distinguish between + /// r0.5-compatible clients and this specification version. + QString idAccessToken; /// The kind of address being passed in the address field, for example /// ``email``. QString medium; @@ -124,23 +133,29 @@ public: /// the new room, including checking power levels for each event. It MUST /// apply the events implied by the request in the following order: /// - /// 0. A default ``m.room.power_levels`` event, giving the room creator + /// 1. The ``m.room.create`` event itself. Must be the first event in the + /// room. + /// + /// 2. An ``m.room.member`` event for the creator to join the room. This is + /// needed so the remaining events can be sent. + /// + /// 3. A default ``m.room.power_levels`` event, giving the room creator /// (and not other members) permission to send state events. Overridden /// by the ``power_level_content_override`` parameter. /// - /// 1. Events set by the ``preset``. Currently these are the + /// 4. Events set by the ``preset``. Currently these are the /// ``m.room.join_rules``, /// ``m.room.history_visibility``, and ``m.room.guest_access`` state /// events. /// - /// 2. Events listed in ``initial_state``, in the order that they are + /// 5. Events listed in ``initial_state``, in the order that they are /// listed. /// - /// 3. Events implied by ``name`` and ``topic`` (``m.room.name`` and + /// 6. Events implied by ``name`` and ``topic`` (``m.room.name`` and /// ``m.room.topic`` /// state events). /// - /// 4. Invite events implied by ``invite`` and ``invite_3pid`` + /// 7. Invite events implied by ``invite`` and ``invite_3pid`` /// (``m.room.member`` with /// ``membership: invite`` and ``m.room.third_party_invite``). /// @@ -175,6 +190,7 @@ public: /*! \brief Create a new room * + * * \param visibility * A ``public`` visibility indicates that the room will be shown * in the published room list. A ``private`` visibility will hide @@ -182,6 +198,7 @@ public: * ``private`` visibility if this key is not included. NB: This * should not be confused with ``join_rules`` which also uses the * word ``public``. + * * \param roomAliasName * The desired room alias **local part**. If this is included, a * room alias will be created and mapped to the newly created @@ -192,35 +209,46 @@ public: * * The complete room alias will become the canonical alias for * the room. + * * \param name * If this is included, an ``m.room.name`` event will be sent * into the room to indicate the name of the room. See Room * Events for more information on ``m.room.name``. + * * \param topic * If this is included, an ``m.room.topic`` event will be sent * into the room to indicate the topic for the room. See Room * Events for more information on ``m.room.topic``. + * * \param invite * A list of user IDs to invite to the room. This will tell the * server to invite everyone in the list to the newly created room. + * * \param invite3pid * A list of objects representing third party IDs to invite into * the room. + * * \param roomVersion * The room version to set for the room. If not provided, the homeserver * is to use its configured default. If provided, the homeserver will return * a 400 error with the errcode ``M_UNSUPPORTED_ROOM_VERSION`` if it does - * not support the room version. \param creationContent Extra keys, such as - * ``m.federate``, to be added to the content of the `m.room.create`_ event. - * The server will clobber the following keys: ``creator``, - * ``room_version``. Future versions of the specification may allow the - * server to clobber other keys. \param initialState A list of state events - * to set in the new room. This allows the user to override the default - * state events set in the new room. The expected format of the state events - * are an object with type, state_key and content keys set. + * not support the room version. + * + * \param creationContent + * Extra keys, such as ``m.federate``, to be added to the content + * of the `m.room.create`_ event. The server will clobber the following + * keys: ``creator``, ``room_version``. Future versions of the + * specification may allow the server to clobber other keys. + * + * \param initialState + * A list of state events to set in the new room. This allows + * the user to override the default state events set in the new + * room. The expected format of the state events are an object + * with type, state_key and content keys set. * * Takes precedence over events set by ``preset``, but gets * overriden by ``name`` and ``topic`` keys. + * * \param preset * Convenience parameter for setting various default state events * based on a preset. @@ -229,10 +257,12 @@ public: * which preset to use. A visbility of ``public`` equates to a preset of * ``public_chat`` and ``private`` visibility equates to a preset of * ``private_chat``. + * * \param isDirect * This flag makes the server set the ``is_direct`` flag on the * ``m.room.member`` events sent to the users in ``invite`` and * ``invite_3pid``. See `Direct Messaging`_ for more information. + * * \param powerLevelContentOverride * The power level content to override in the default power level * event. This object is applied on top of the generated @@ -251,19 +281,31 @@ public: Omittable<bool> isDirect = none, const QJsonObject& powerLevelContentOverride = {}); - ~CreateRoomJob() override; - // Result properties /// The created room's ID. - const QString& roomId() const; + QString roomId() const { return loadFromJson<QString>("room_id"_ls); } +}; -protected: - Status parseJson(const QJsonDocument& data) override; +template <> +struct JsonObjectConverter<CreateRoomJob::Invite3pid> { + static void dumpTo(QJsonObject& jo, const CreateRoomJob::Invite3pid& pod) + { + addParam<>(jo, QStringLiteral("id_server"), pod.idServer); + addParam<>(jo, QStringLiteral("id_access_token"), pod.idAccessToken); + addParam<>(jo, QStringLiteral("medium"), pod.medium); + addParam<>(jo, QStringLiteral("address"), pod.address); + } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter<CreateRoomJob::StateEvent> { + static void dumpTo(QJsonObject& jo, const CreateRoomJob::StateEvent& pod) + { + addParam<>(jo, QStringLiteral("type"), pod.type); + addParam<IfNotEmpty>(jo, QStringLiteral("state_key"), pod.stateKey); + addParam<>(jo, QStringLiteral("content"), pod.content); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/auth_data.cpp b/lib/csapi/definitions/auth_data.cpp deleted file mode 100644 index edeb7111..00000000 --- a/lib/csapi/definitions/auth_data.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "auth_data.h" - -using namespace Quotient; - -void JsonObjectConverter<AuthenticationData>::dumpTo( - QJsonObject& jo, const AuthenticationData& pod) -{ - fillJson(jo, pod.authInfo); - addParam<>(jo, QStringLiteral("type"), pod.type); - addParam<IfNotEmpty>(jo, QStringLiteral("session"), pod.session); -} - -void JsonObjectConverter<AuthenticationData>::fillFrom(QJsonObject jo, - AuthenticationData& result) -{ - fromJson(jo.take("type"_ls), result.type); - fromJson(jo.take("session"_ls), result.session); - fromJson(jo, result.authInfo); -} diff --git a/lib/csapi/definitions/auth_data.h b/lib/csapi/definitions/auth_data.h index e564f7f3..e92596d0 100644 --- a/lib/csapi/definitions/auth_data.h +++ b/lib/csapi/definitions/auth_data.h @@ -6,13 +6,7 @@ #include "converters.h" -#include <QtCore/QHash> -#include <QtCore/QJsonObject> - namespace Quotient { - -// Data structures - /// Used by clients to submit authentication information to the /// interactive-authentication API struct AuthenticationData { @@ -28,8 +22,18 @@ struct AuthenticationData { template <> struct JsonObjectConverter<AuthenticationData> { - static void dumpTo(QJsonObject& jo, const AuthenticationData& pod); - static void fillFrom(QJsonObject jo, AuthenticationData& pod); + static void dumpTo(QJsonObject& jo, const AuthenticationData& pod) + { + fillJson(jo, pod.authInfo); + addParam<>(jo, QStringLiteral("type"), pod.type); + addParam<IfNotEmpty>(jo, QStringLiteral("session"), pod.session); + } + static void fillFrom(QJsonObject jo, AuthenticationData& pod) + { + fromJson(jo.take("type"_ls), pod.type); + fromJson(jo.take("session"_ls), pod.session); + fromJson(jo, pod.authInfo); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/client_device.cpp b/lib/csapi/definitions/client_device.cpp deleted file mode 100644 index 09544138..00000000 --- a/lib/csapi/definitions/client_device.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "client_device.h" - -using namespace Quotient; - -void JsonObjectConverter<Device>::dumpTo(QJsonObject& jo, const Device& pod) -{ - addParam<>(jo, QStringLiteral("device_id"), pod.deviceId); - addParam<IfNotEmpty>(jo, QStringLiteral("display_name"), pod.displayName); - addParam<IfNotEmpty>(jo, QStringLiteral("last_seen_ip"), pod.lastSeenIp); - addParam<IfNotEmpty>(jo, QStringLiteral("last_seen_ts"), pod.lastSeenTs); -} - -void JsonObjectConverter<Device>::fillFrom(const QJsonObject& jo, Device& result) -{ - fromJson(jo.value("device_id"_ls), result.deviceId); - fromJson(jo.value("display_name"_ls), result.displayName); - fromJson(jo.value("last_seen_ip"_ls), result.lastSeenIp); - fromJson(jo.value("last_seen_ts"_ls), result.lastSeenTs); -} diff --git a/lib/csapi/definitions/client_device.h b/lib/csapi/definitions/client_device.h index 2cf75950..a5ab1bfc 100644 --- a/lib/csapi/definitions/client_device.h +++ b/lib/csapi/definitions/client_device.h @@ -7,9 +7,6 @@ #include "converters.h" namespace Quotient { - -// Data structures - /// A client device struct Device { /// Identifier of this device. @@ -31,8 +28,21 @@ struct Device { template <> struct JsonObjectConverter<Device> { - static void dumpTo(QJsonObject& jo, const Device& pod); - static void fillFrom(const QJsonObject& jo, Device& pod); + static void dumpTo(QJsonObject& jo, const Device& pod) + { + addParam<>(jo, QStringLiteral("device_id"), pod.deviceId); + addParam<IfNotEmpty>(jo, QStringLiteral("display_name"), + pod.displayName); + addParam<IfNotEmpty>(jo, QStringLiteral("last_seen_ip"), pod.lastSeenIp); + addParam<IfNotEmpty>(jo, QStringLiteral("last_seen_ts"), pod.lastSeenTs); + } + static void fillFrom(const QJsonObject& jo, Device& pod) + { + fromJson(jo.value("device_id"_ls), pod.deviceId); + fromJson(jo.value("display_name"_ls), pod.displayName); + fromJson(jo.value("last_seen_ip"_ls), pod.lastSeenIp); + fromJson(jo.value("last_seen_ts"_ls), pod.lastSeenTs); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/device_keys.cpp b/lib/csapi/definitions/device_keys.cpp deleted file mode 100644 index 0583840d..00000000 --- a/lib/csapi/definitions/device_keys.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "device_keys.h" - -using namespace Quotient; - -void JsonObjectConverter<DeviceKeys>::dumpTo(QJsonObject& jo, - const DeviceKeys& pod) -{ - addParam<>(jo, QStringLiteral("user_id"), pod.userId); - addParam<>(jo, QStringLiteral("device_id"), pod.deviceId); - addParam<>(jo, QStringLiteral("algorithms"), pod.algorithms); - addParam<>(jo, QStringLiteral("keys"), pod.keys); - addParam<>(jo, QStringLiteral("signatures"), pod.signatures); -} - -void JsonObjectConverter<DeviceKeys>::fillFrom(const QJsonObject& jo, - DeviceKeys& result) -{ - fromJson(jo.value("user_id"_ls), result.userId); - fromJson(jo.value("device_id"_ls), result.deviceId); - fromJson(jo.value("algorithms"_ls), result.algorithms); - fromJson(jo.value("keys"_ls), result.keys); - fromJson(jo.value("signatures"_ls), result.signatures); -} diff --git a/lib/csapi/definitions/device_keys.h b/lib/csapi/definitions/device_keys.h index a067b4f3..3065f218 100644 --- a/lib/csapi/definitions/device_keys.h +++ b/lib/csapi/definitions/device_keys.h @@ -6,12 +6,7 @@ #include "converters.h" -#include <QtCore/QHash> - namespace Quotient { - -// Data structures - /// Device identity keys struct DeviceKeys { /// The ID of the user the device belongs to. Must match the user ID used @@ -40,8 +35,22 @@ struct DeviceKeys { template <> struct JsonObjectConverter<DeviceKeys> { - static void dumpTo(QJsonObject& jo, const DeviceKeys& pod); - static void fillFrom(const QJsonObject& jo, DeviceKeys& pod); + static void dumpTo(QJsonObject& jo, const DeviceKeys& pod) + { + addParam<>(jo, QStringLiteral("user_id"), pod.userId); + addParam<>(jo, QStringLiteral("device_id"), pod.deviceId); + addParam<>(jo, QStringLiteral("algorithms"), pod.algorithms); + addParam<>(jo, QStringLiteral("keys"), pod.keys); + addParam<>(jo, QStringLiteral("signatures"), pod.signatures); + } + static void fillFrom(const QJsonObject& jo, DeviceKeys& pod) + { + fromJson(jo.value("user_id"_ls), pod.userId); + fromJson(jo.value("device_id"_ls), pod.deviceId); + fromJson(jo.value("algorithms"_ls), pod.algorithms); + fromJson(jo.value("keys"_ls), pod.keys); + fromJson(jo.value("signatures"_ls), pod.signatures); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/event_filter.cpp b/lib/csapi/definitions/event_filter.cpp deleted file mode 100644 index b21e08b5..00000000 --- a/lib/csapi/definitions/event_filter.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "event_filter.h" - -using namespace Quotient; - -void JsonObjectConverter<EventFilter>::dumpTo(QJsonObject& jo, - const EventFilter& pod) -{ - addParam<IfNotEmpty>(jo, QStringLiteral("limit"), pod.limit); - addParam<IfNotEmpty>(jo, QStringLiteral("not_senders"), pod.notSenders); - addParam<IfNotEmpty>(jo, QStringLiteral("not_types"), pod.notTypes); - addParam<IfNotEmpty>(jo, QStringLiteral("senders"), pod.senders); - addParam<IfNotEmpty>(jo, QStringLiteral("types"), pod.types); -} - -void JsonObjectConverter<EventFilter>::fillFrom(const QJsonObject& jo, - EventFilter& result) -{ - fromJson(jo.value("limit"_ls), result.limit); - fromJson(jo.value("not_senders"_ls), result.notSenders); - fromJson(jo.value("not_types"_ls), result.notTypes); - fromJson(jo.value("senders"_ls), result.senders); - fromJson(jo.value("types"_ls), result.types); -} diff --git a/lib/csapi/definitions/event_filter.h b/lib/csapi/definitions/event_filter.h index 3958b125..67497412 100644 --- a/lib/csapi/definitions/event_filter.h +++ b/lib/csapi/definitions/event_filter.h @@ -8,8 +8,6 @@ namespace Quotient { -// Data structures - struct EventFilter { /// The maximum number of events to return. Omittable<int> limit; @@ -37,8 +35,22 @@ struct EventFilter { template <> struct JsonObjectConverter<EventFilter> { - static void dumpTo(QJsonObject& jo, const EventFilter& pod); - static void fillFrom(const QJsonObject& jo, EventFilter& pod); + static void dumpTo(QJsonObject& jo, const EventFilter& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("limit"), pod.limit); + addParam<IfNotEmpty>(jo, QStringLiteral("not_senders"), pod.notSenders); + addParam<IfNotEmpty>(jo, QStringLiteral("not_types"), pod.notTypes); + addParam<IfNotEmpty>(jo, QStringLiteral("senders"), pod.senders); + addParam<IfNotEmpty>(jo, QStringLiteral("types"), pod.types); + } + static void fillFrom(const QJsonObject& jo, EventFilter& pod) + { + fromJson(jo.value("limit"_ls), pod.limit); + fromJson(jo.value("not_senders"_ls), pod.notSenders); + fromJson(jo.value("not_types"_ls), pod.notTypes); + fromJson(jo.value("senders"_ls), pod.senders); + fromJson(jo.value("types"_ls), pod.types); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/openid_token.h b/lib/csapi/definitions/openid_token.h new file mode 100644 index 00000000..5e68c376 --- /dev/null +++ b/lib/csapi/definitions/openid_token.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "converters.h" + +namespace Quotient { + +struct OpenidToken { + /// An access token the consumer may use to verify the identity of + /// the person who generated the token. This is given to the federation + /// API ``GET /openid/userinfo`` to verify the user's identity. + QString accessToken; + + /// The string ``Bearer``. + QString tokenType; + + /// The homeserver domain the consumer should use when attempting to + /// verify the user's identity. + QString matrixServerName; + + /// The number of seconds before this token expires and a new one must + /// be generated. + int expiresIn; +}; + +template <> +struct JsonObjectConverter<OpenidToken> { + static void dumpTo(QJsonObject& jo, const OpenidToken& pod) + { + addParam<>(jo, QStringLiteral("access_token"), pod.accessToken); + addParam<>(jo, QStringLiteral("token_type"), pod.tokenType); + addParam<>(jo, QStringLiteral("matrix_server_name"), + pod.matrixServerName); + addParam<>(jo, QStringLiteral("expires_in"), pod.expiresIn); + } + static void fillFrom(const QJsonObject& jo, OpenidToken& pod) + { + fromJson(jo.value("access_token"_ls), pod.accessToken); + fromJson(jo.value("token_type"_ls), pod.tokenType); + fromJson(jo.value("matrix_server_name"_ls), pod.matrixServerName); + fromJson(jo.value("expires_in"_ls), pod.expiresIn); + } +}; + +} // namespace Quotient diff --git a/lib/csapi/definitions/public_rooms_response.cpp b/lib/csapi/definitions/public_rooms_response.cpp deleted file mode 100644 index b6009718..00000000 --- a/lib/csapi/definitions/public_rooms_response.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "public_rooms_response.h" - -using namespace Quotient; - -void JsonObjectConverter<PublicRoomsChunk>::dumpTo(QJsonObject& jo, - const PublicRoomsChunk& pod) -{ - addParam<IfNotEmpty>(jo, QStringLiteral("aliases"), pod.aliases); - addParam<IfNotEmpty>(jo, QStringLiteral("canonical_alias"), - pod.canonicalAlias); - addParam<IfNotEmpty>(jo, QStringLiteral("name"), pod.name); - addParam<>(jo, QStringLiteral("num_joined_members"), pod.numJoinedMembers); - addParam<>(jo, QStringLiteral("room_id"), pod.roomId); - addParam<IfNotEmpty>(jo, QStringLiteral("topic"), pod.topic); - addParam<>(jo, QStringLiteral("world_readable"), pod.worldReadable); - addParam<>(jo, QStringLiteral("guest_can_join"), pod.guestCanJoin); - addParam<IfNotEmpty>(jo, QStringLiteral("avatar_url"), pod.avatarUrl); -} - -void JsonObjectConverter<PublicRoomsChunk>::fillFrom(const QJsonObject& jo, - PublicRoomsChunk& result) -{ - fromJson(jo.value("aliases"_ls), result.aliases); - fromJson(jo.value("canonical_alias"_ls), result.canonicalAlias); - fromJson(jo.value("name"_ls), result.name); - fromJson(jo.value("num_joined_members"_ls), result.numJoinedMembers); - fromJson(jo.value("room_id"_ls), result.roomId); - fromJson(jo.value("topic"_ls), result.topic); - fromJson(jo.value("world_readable"_ls), result.worldReadable); - fromJson(jo.value("guest_can_join"_ls), result.guestCanJoin); - fromJson(jo.value("avatar_url"_ls), result.avatarUrl); -} - -void JsonObjectConverter<PublicRoomsResponse>::dumpTo( - QJsonObject& jo, const PublicRoomsResponse& pod) -{ - addParam<>(jo, QStringLiteral("chunk"), pod.chunk); - addParam<IfNotEmpty>(jo, QStringLiteral("next_batch"), pod.nextBatch); - addParam<IfNotEmpty>(jo, QStringLiteral("prev_batch"), pod.prevBatch); - addParam<IfNotEmpty>(jo, QStringLiteral("total_room_count_estimate"), - pod.totalRoomCountEstimate); -} - -void JsonObjectConverter<PublicRoomsResponse>::fillFrom( - const QJsonObject& jo, PublicRoomsResponse& result) -{ - fromJson(jo.value("chunk"_ls), result.chunk); - fromJson(jo.value("next_batch"_ls), result.nextBatch); - fromJson(jo.value("prev_batch"_ls), result.prevBatch); - fromJson(jo.value("total_room_count_estimate"_ls), - result.totalRoomCountEstimate); -} diff --git a/lib/csapi/definitions/public_rooms_response.h b/lib/csapi/definitions/public_rooms_response.h index 36aa52b9..8f30e607 100644 --- a/lib/csapi/definitions/public_rooms_response.h +++ b/lib/csapi/definitions/public_rooms_response.h @@ -6,12 +6,8 @@ #include "converters.h" -#include <QtCore/QVector> - namespace Quotient { -// Data structures - struct PublicRoomsChunk { /// Aliases of the room. May be empty. QStringList aliases; @@ -45,9 +41,34 @@ struct PublicRoomsChunk { template <> struct JsonObjectConverter<PublicRoomsChunk> { - static void dumpTo(QJsonObject& jo, const PublicRoomsChunk& pod); - static void fillFrom(const QJsonObject& jo, PublicRoomsChunk& pod); + static void dumpTo(QJsonObject& jo, const PublicRoomsChunk& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("aliases"), pod.aliases); + addParam<IfNotEmpty>(jo, QStringLiteral("canonical_alias"), + pod.canonicalAlias); + addParam<IfNotEmpty>(jo, QStringLiteral("name"), pod.name); + addParam<>(jo, QStringLiteral("num_joined_members"), + pod.numJoinedMembers); + addParam<>(jo, QStringLiteral("room_id"), pod.roomId); + addParam<IfNotEmpty>(jo, QStringLiteral("topic"), pod.topic); + addParam<>(jo, QStringLiteral("world_readable"), pod.worldReadable); + addParam<>(jo, QStringLiteral("guest_can_join"), pod.guestCanJoin); + addParam<IfNotEmpty>(jo, QStringLiteral("avatar_url"), pod.avatarUrl); + } + static void fillFrom(const QJsonObject& jo, PublicRoomsChunk& pod) + { + fromJson(jo.value("aliases"_ls), pod.aliases); + fromJson(jo.value("canonical_alias"_ls), pod.canonicalAlias); + fromJson(jo.value("name"_ls), pod.name); + fromJson(jo.value("num_joined_members"_ls), pod.numJoinedMembers); + fromJson(jo.value("room_id"_ls), pod.roomId); + fromJson(jo.value("topic"_ls), pod.topic); + fromJson(jo.value("world_readable"_ls), pod.worldReadable); + fromJson(jo.value("guest_can_join"_ls), pod.guestCanJoin); + fromJson(jo.value("avatar_url"_ls), pod.avatarUrl); + } }; + /// A list of the rooms on the server. struct PublicRoomsResponse { /// A paginated chunk of public rooms. @@ -70,8 +91,22 @@ struct PublicRoomsResponse { template <> struct JsonObjectConverter<PublicRoomsResponse> { - static void dumpTo(QJsonObject& jo, const PublicRoomsResponse& pod); - static void fillFrom(const QJsonObject& jo, PublicRoomsResponse& pod); + static void dumpTo(QJsonObject& jo, const PublicRoomsResponse& pod) + { + addParam<>(jo, QStringLiteral("chunk"), pod.chunk); + addParam<IfNotEmpty>(jo, QStringLiteral("next_batch"), pod.nextBatch); + addParam<IfNotEmpty>(jo, QStringLiteral("prev_batch"), pod.prevBatch); + addParam<IfNotEmpty>(jo, QStringLiteral("total_room_count_estimate"), + pod.totalRoomCountEstimate); + } + static void fillFrom(const QJsonObject& jo, PublicRoomsResponse& pod) + { + fromJson(jo.value("chunk"_ls), pod.chunk); + fromJson(jo.value("next_batch"_ls), pod.nextBatch); + fromJson(jo.value("prev_batch"_ls), pod.prevBatch); + fromJson(jo.value("total_room_count_estimate"_ls), + pod.totalRoomCountEstimate); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/push_condition.cpp b/lib/csapi/definitions/push_condition.cpp deleted file mode 100644 index 343b4f1a..00000000 --- a/lib/csapi/definitions/push_condition.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "push_condition.h" - -using namespace Quotient; - -void JsonObjectConverter<PushCondition>::dumpTo(QJsonObject& jo, - const PushCondition& pod) -{ - addParam<>(jo, QStringLiteral("kind"), pod.kind); - addParam<IfNotEmpty>(jo, QStringLiteral("key"), pod.key); - addParam<IfNotEmpty>(jo, QStringLiteral("pattern"), pod.pattern); - addParam<IfNotEmpty>(jo, QStringLiteral("is"), pod.is); -} - -void JsonObjectConverter<PushCondition>::fillFrom(const QJsonObject& jo, - PushCondition& result) -{ - fromJson(jo.value("kind"_ls), result.kind); - fromJson(jo.value("key"_ls), result.key); - fromJson(jo.value("pattern"_ls), result.pattern); - fromJson(jo.value("is"_ls), result.is); -} diff --git a/lib/csapi/definitions/push_condition.h b/lib/csapi/definitions/push_condition.h index 189153b3..a6decf1b 100644 --- a/lib/csapi/definitions/push_condition.h +++ b/lib/csapi/definitions/push_condition.h @@ -8,13 +8,18 @@ namespace Quotient { -// Data structures - struct PushCondition { + /// The kind of condition to apply. See `conditions <#conditions>`_ for + /// more information on the allowed kinds and how they work. QString kind; /// Required for ``event_match`` conditions. The dot-separated field of the /// event to match. + /// + /// Required for ``sender_notification_permission`` conditions. The field in + /// the power level event the user needs a minimum power level for. Fields + /// must be specified under the ``notifications`` property in the power + /// level event's ``content``. QString key; /// Required for ``event_match`` conditions. The glob-style pattern to @@ -32,8 +37,20 @@ struct PushCondition { template <> struct JsonObjectConverter<PushCondition> { - static void dumpTo(QJsonObject& jo, const PushCondition& pod); - static void fillFrom(const QJsonObject& jo, PushCondition& pod); + static void dumpTo(QJsonObject& jo, const PushCondition& pod) + { + addParam<>(jo, QStringLiteral("kind"), pod.kind); + addParam<IfNotEmpty>(jo, QStringLiteral("key"), pod.key); + addParam<IfNotEmpty>(jo, QStringLiteral("pattern"), pod.pattern); + addParam<IfNotEmpty>(jo, QStringLiteral("is"), pod.is); + } + static void fillFrom(const QJsonObject& jo, PushCondition& pod) + { + fromJson(jo.value("kind"_ls), pod.kind); + fromJson(jo.value("key"_ls), pod.key); + fromJson(jo.value("pattern"_ls), pod.pattern); + fromJson(jo.value("is"_ls), pod.is); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/push_rule.cpp b/lib/csapi/definitions/push_rule.cpp deleted file mode 100644 index eae7e446..00000000 --- a/lib/csapi/definitions/push_rule.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "push_rule.h" - -using namespace Quotient; - -void JsonObjectConverter<PushRule>::dumpTo(QJsonObject& jo, const PushRule& pod) -{ - addParam<>(jo, QStringLiteral("actions"), pod.actions); - addParam<>(jo, QStringLiteral("default"), pod.isDefault); - addParam<>(jo, QStringLiteral("enabled"), pod.enabled); - addParam<>(jo, QStringLiteral("rule_id"), pod.ruleId); - addParam<IfNotEmpty>(jo, QStringLiteral("conditions"), pod.conditions); - addParam<IfNotEmpty>(jo, QStringLiteral("pattern"), pod.pattern); -} - -void JsonObjectConverter<PushRule>::fillFrom(const QJsonObject& jo, - PushRule& result) -{ - fromJson(jo.value("actions"_ls), result.actions); - fromJson(jo.value("default"_ls), result.isDefault); - fromJson(jo.value("enabled"_ls), result.enabled); - fromJson(jo.value("rule_id"_ls), result.ruleId); - fromJson(jo.value("conditions"_ls), result.conditions); - fromJson(jo.value("pattern"_ls), result.pattern); -} diff --git a/lib/csapi/definitions/push_rule.h b/lib/csapi/definitions/push_rule.h index c09d063f..43749bae 100644 --- a/lib/csapi/definitions/push_rule.h +++ b/lib/csapi/definitions/push_rule.h @@ -8,14 +8,8 @@ #include "csapi/definitions/push_condition.h" -#include <QtCore/QJsonObject> -#include <QtCore/QVariant> -#include <QtCore/QVector> - namespace Quotient { -// Data structures - struct PushRule { /// The actions to perform when this rule is matched. QVector<QVariant> actions; @@ -41,8 +35,24 @@ struct PushRule { template <> struct JsonObjectConverter<PushRule> { - static void dumpTo(QJsonObject& jo, const PushRule& pod); - static void fillFrom(const QJsonObject& jo, PushRule& pod); + static void dumpTo(QJsonObject& jo, const PushRule& pod) + { + addParam<>(jo, QStringLiteral("actions"), pod.actions); + addParam<>(jo, QStringLiteral("default"), pod.isDefault); + addParam<>(jo, QStringLiteral("enabled"), pod.enabled); + addParam<>(jo, QStringLiteral("rule_id"), pod.ruleId); + addParam<IfNotEmpty>(jo, QStringLiteral("conditions"), pod.conditions); + addParam<IfNotEmpty>(jo, QStringLiteral("pattern"), pod.pattern); + } + static void fillFrom(const QJsonObject& jo, PushRule& pod) + { + fromJson(jo.value("actions"_ls), pod.actions); + fromJson(jo.value("default"_ls), pod.isDefault); + fromJson(jo.value("enabled"_ls), pod.enabled); + fromJson(jo.value("rule_id"_ls), pod.ruleId); + fromJson(jo.value("conditions"_ls), pod.conditions); + fromJson(jo.value("pattern"_ls), pod.pattern); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/push_ruleset.cpp b/lib/csapi/definitions/push_ruleset.cpp deleted file mode 100644 index a2db35d9..00000000 --- a/lib/csapi/definitions/push_ruleset.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "push_ruleset.h" - -using namespace Quotient; - -void JsonObjectConverter<PushRuleset>::dumpTo(QJsonObject& jo, - const PushRuleset& pod) -{ - addParam<IfNotEmpty>(jo, QStringLiteral("content"), pod.content); - addParam<IfNotEmpty>(jo, QStringLiteral("override"), pod.override); - addParam<IfNotEmpty>(jo, QStringLiteral("room"), pod.room); - addParam<IfNotEmpty>(jo, QStringLiteral("sender"), pod.sender); - addParam<IfNotEmpty>(jo, QStringLiteral("underride"), pod.underride); -} - -void JsonObjectConverter<PushRuleset>::fillFrom(const QJsonObject& jo, - PushRuleset& result) -{ - fromJson(jo.value("content"_ls), result.content); - fromJson(jo.value("override"_ls), result.override); - fromJson(jo.value("room"_ls), result.room); - fromJson(jo.value("sender"_ls), result.sender); - fromJson(jo.value("underride"_ls), result.underride); -} diff --git a/lib/csapi/definitions/push_ruleset.h b/lib/csapi/definitions/push_ruleset.h index 98a21408..ba780a33 100644 --- a/lib/csapi/definitions/push_ruleset.h +++ b/lib/csapi/definitions/push_ruleset.h @@ -8,12 +8,8 @@ #include "csapi/definitions/push_rule.h" -#include <QtCore/QVector> - namespace Quotient { -// Data structures - struct PushRuleset { QVector<PushRule> content; @@ -28,8 +24,22 @@ struct PushRuleset { template <> struct JsonObjectConverter<PushRuleset> { - static void dumpTo(QJsonObject& jo, const PushRuleset& pod); - static void fillFrom(const QJsonObject& jo, PushRuleset& pod); + static void dumpTo(QJsonObject& jo, const PushRuleset& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("content"), pod.content); + addParam<IfNotEmpty>(jo, QStringLiteral("override"), pod.override); + addParam<IfNotEmpty>(jo, QStringLiteral("room"), pod.room); + addParam<IfNotEmpty>(jo, QStringLiteral("sender"), pod.sender); + addParam<IfNotEmpty>(jo, QStringLiteral("underride"), pod.underride); + } + static void fillFrom(const QJsonObject& jo, PushRuleset& pod) + { + fromJson(jo.value("content"_ls), pod.content); + fromJson(jo.value("override"_ls), pod.override); + fromJson(jo.value("room"_ls), pod.room); + fromJson(jo.value("sender"_ls), pod.sender); + fromJson(jo.value("underride"_ls), pod.underride); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/request_email_validation.h b/lib/csapi/definitions/request_email_validation.h new file mode 100644 index 00000000..e6ab261d --- /dev/null +++ b/lib/csapi/definitions/request_email_validation.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "converters.h" + +#include "csapi/./definitions/../../identity/definitions/request_email_validation.h" + +namespace Quotient { + +struct EmailValidationData : RequestEmailValidation { + /// The hostname of the identity server to communicate with. May optionally + /// include a port. This parameter is ignored when the homeserver handles + /// 3PID verification. + /// + /// This parameter is deprecated with a plan to be removed in a future + /// specification version for ``/account/password`` and ``/register`` + /// requests. + QString idServer; + + /// An access token previously registered with the identity server. Servers + /// can treat this as optional to distinguish between r0.5-compatible + /// clients and this specification version. + /// + /// Required if an ``id_server`` is supplied. + QString idAccessToken; +}; + +template <> +struct JsonObjectConverter<EmailValidationData> { + static void dumpTo(QJsonObject& jo, const EmailValidationData& pod) + { + fillJson<RequestEmailValidation>(jo, pod); + addParam<IfNotEmpty>(jo, QStringLiteral("id_server"), pod.idServer); + addParam<IfNotEmpty>(jo, QStringLiteral("id_access_token"), + pod.idAccessToken); + } + static void fillFrom(const QJsonObject& jo, EmailValidationData& pod) + { + fillFromJson<RequestEmailValidation>(jo, pod); + fromJson(jo.value("id_server"_ls), pod.idServer); + fromJson(jo.value("id_access_token"_ls), pod.idAccessToken); + } +}; + +} // namespace Quotient diff --git a/lib/csapi/definitions/request_msisdn_validation.h b/lib/csapi/definitions/request_msisdn_validation.h new file mode 100644 index 00000000..6f161f00 --- /dev/null +++ b/lib/csapi/definitions/request_msisdn_validation.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "converters.h" + +#include "csapi/./definitions/../../identity/definitions/request_msisdn_validation.h" + +namespace Quotient { + +struct MsisdnValidationData : RequestMsisdnValidation { + /// The hostname of the identity server to communicate with. May optionally + /// include a port. This parameter is ignored when the homeserver handles + /// 3PID verification. + /// + /// This parameter is deprecated with a plan to be removed in a future + /// specification version for ``/account/password`` and ``/register`` + /// requests. + QString idServer; + + /// An access token previously registered with the identity server. Servers + /// can treat this as optional to distinguish between r0.5-compatible + /// clients and this specification version. + /// + /// Required if an ``id_server`` is supplied. + QString idAccessToken; +}; + +template <> +struct JsonObjectConverter<MsisdnValidationData> { + static void dumpTo(QJsonObject& jo, const MsisdnValidationData& pod) + { + fillJson<RequestMsisdnValidation>(jo, pod); + addParam<IfNotEmpty>(jo, QStringLiteral("id_server"), pod.idServer); + addParam<IfNotEmpty>(jo, QStringLiteral("id_access_token"), + pod.idAccessToken); + } + static void fillFrom(const QJsonObject& jo, MsisdnValidationData& pod) + { + fillFromJson<RequestMsisdnValidation>(jo, pod); + fromJson(jo.value("id_server"_ls), pod.idServer); + fromJson(jo.value("id_access_token"_ls), pod.idAccessToken); + } +}; + +} // namespace Quotient diff --git a/lib/csapi/definitions/request_token_response.h b/lib/csapi/definitions/request_token_response.h new file mode 100644 index 00000000..00222839 --- /dev/null +++ b/lib/csapi/definitions/request_token_response.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "converters.h" + +namespace Quotient { + +struct RequestTokenResponse { + /// The session ID. Session IDs are opaque strings that must consist + /// entirely of the characters ``[0-9a-zA-Z.=_-]``. Their length must not + /// exceed 255 characters and they must not be empty. + QString sid; + + /// An optional field containing a URL where the client must submit the + /// validation token to, with identical parameters to the Identity Service + /// API's ``POST /validate/email/submitToken`` endpoint (without the + /// requirement for an access token). The homeserver must send this token to + /// the user (if applicable), who should then be prompted to provide it to + /// the client. + /// + /// If this field is not present, the client can assume that verification + /// will happen without the client's involvement provided the homeserver + /// advertises this specification version in the ``/versions`` response + /// (ie: r0.5.0). + QString submitUrl; +}; + +template <> +struct JsonObjectConverter<RequestTokenResponse> { + static void dumpTo(QJsonObject& jo, const RequestTokenResponse& pod) + { + addParam<>(jo, QStringLiteral("sid"), pod.sid); + addParam<IfNotEmpty>(jo, QStringLiteral("submit_url"), pod.submitUrl); + } + static void fillFrom(const QJsonObject& jo, RequestTokenResponse& pod) + { + fromJson(jo.value("sid"_ls), pod.sid); + fromJson(jo.value("submit_url"_ls), pod.submitUrl); + } +}; + +} // namespace Quotient diff --git a/lib/csapi/definitions/room_event_filter.cpp b/lib/csapi/definitions/room_event_filter.cpp deleted file mode 100644 index 5613d8d2..00000000 --- a/lib/csapi/definitions/room_event_filter.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "room_event_filter.h" - -using namespace Quotient; - -void JsonObjectConverter<RoomEventFilter>::dumpTo(QJsonObject& jo, - const RoomEventFilter& pod) -{ - fillJson<EventFilter>(jo, pod); - addParam<IfNotEmpty>(jo, QStringLiteral("not_rooms"), pod.notRooms); - addParam<IfNotEmpty>(jo, QStringLiteral("rooms"), pod.rooms); - addParam<IfNotEmpty>(jo, QStringLiteral("contains_url"), pod.containsUrl); -} - -void JsonObjectConverter<RoomEventFilter>::fillFrom(const QJsonObject& jo, - RoomEventFilter& result) -{ - fillFromJson<EventFilter>(jo, result); - fromJson(jo.value("not_rooms"_ls), result.notRooms); - fromJson(jo.value("rooms"_ls), result.rooms); - fromJson(jo.value("contains_url"_ls), result.containsUrl); -} diff --git a/lib/csapi/definitions/room_event_filter.h b/lib/csapi/definitions/room_event_filter.h index 756f9ada..11e87fde 100644 --- a/lib/csapi/definitions/room_event_filter.h +++ b/lib/csapi/definitions/room_event_filter.h @@ -10,9 +10,19 @@ namespace Quotient { -// Data structures - struct RoomEventFilter : EventFilter { + /// If ``true``, enables lazy-loading of membership events. See + /// `Lazy-loading room members <#lazy-loading-room-members>`_ + /// for more information. Defaults to ``false``. + Omittable<bool> lazyLoadMembers; + + /// If ``true``, sends all membership events for all events, even if they + /// have already been sent to the client. Does not apply unless + /// ``lazy_load_members`` is ``true``. See `Lazy-loading room members + /// <#lazy-loading-room-members>`_ for more information. Defaults to + /// ``false``. + Omittable<bool> includeRedundantMembers; + /// A list of room IDs to exclude. If this list is absent then no rooms are /// excluded. A matching room will be excluded even if it is listed in the /// ``'rooms'`` filter. @@ -30,8 +40,28 @@ struct RoomEventFilter : EventFilter { template <> struct JsonObjectConverter<RoomEventFilter> { - static void dumpTo(QJsonObject& jo, const RoomEventFilter& pod); - static void fillFrom(const QJsonObject& jo, RoomEventFilter& pod); + static void dumpTo(QJsonObject& jo, const RoomEventFilter& pod) + { + fillJson<EventFilter>(jo, pod); + addParam<IfNotEmpty>(jo, QStringLiteral("lazy_load_members"), + pod.lazyLoadMembers); + addParam<IfNotEmpty>(jo, QStringLiteral("include_redundant_members"), + pod.includeRedundantMembers); + addParam<IfNotEmpty>(jo, QStringLiteral("not_rooms"), pod.notRooms); + addParam<IfNotEmpty>(jo, QStringLiteral("rooms"), pod.rooms); + addParam<IfNotEmpty>(jo, QStringLiteral("contains_url"), + pod.containsUrl); + } + static void fillFrom(const QJsonObject& jo, RoomEventFilter& pod) + { + fillFromJson<EventFilter>(jo, pod); + fromJson(jo.value("lazy_load_members"_ls), pod.lazyLoadMembers); + fromJson(jo.value("include_redundant_members"_ls), + pod.includeRedundantMembers); + fromJson(jo.value("not_rooms"_ls), pod.notRooms); + fromJson(jo.value("rooms"_ls), pod.rooms); + fromJson(jo.value("contains_url"_ls), pod.containsUrl); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/sync_filter.cpp b/lib/csapi/definitions/sync_filter.cpp deleted file mode 100644 index 15c4bdc1..00000000 --- a/lib/csapi/definitions/sync_filter.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "sync_filter.h" - -using namespace Quotient; - -void JsonObjectConverter<StateFilter>::dumpTo(QJsonObject& jo, - const StateFilter& pod) -{ - fillJson<RoomEventFilter>(jo, pod); - addParam<IfNotEmpty>(jo, QStringLiteral("lazy_load_members"), - pod.lazyLoadMembers); - addParam<IfNotEmpty>(jo, QStringLiteral("include_redundant_members"), - pod.includeRedundantMembers); -} - -void JsonObjectConverter<StateFilter>::fillFrom(const QJsonObject& jo, - StateFilter& result) -{ - fillFromJson<RoomEventFilter>(jo, result); - fromJson(jo.value("lazy_load_members"_ls), result.lazyLoadMembers); - fromJson(jo.value("include_redundant_members"_ls), - result.includeRedundantMembers); -} - -void JsonObjectConverter<RoomFilter>::dumpTo(QJsonObject& jo, - const RoomFilter& pod) -{ - addParam<IfNotEmpty>(jo, QStringLiteral("not_rooms"), pod.notRooms); - addParam<IfNotEmpty>(jo, QStringLiteral("rooms"), pod.rooms); - addParam<IfNotEmpty>(jo, QStringLiteral("ephemeral"), pod.ephemeral); - addParam<IfNotEmpty>(jo, QStringLiteral("include_leave"), pod.includeLeave); - addParam<IfNotEmpty>(jo, QStringLiteral("state"), pod.state); - addParam<IfNotEmpty>(jo, QStringLiteral("timeline"), pod.timeline); - addParam<IfNotEmpty>(jo, QStringLiteral("account_data"), pod.accountData); -} - -void JsonObjectConverter<RoomFilter>::fillFrom(const QJsonObject& jo, - RoomFilter& result) -{ - fromJson(jo.value("not_rooms"_ls), result.notRooms); - fromJson(jo.value("rooms"_ls), result.rooms); - fromJson(jo.value("ephemeral"_ls), result.ephemeral); - fromJson(jo.value("include_leave"_ls), result.includeLeave); - fromJson(jo.value("state"_ls), result.state); - fromJson(jo.value("timeline"_ls), result.timeline); - fromJson(jo.value("account_data"_ls), result.accountData); -} - -void JsonObjectConverter<Filter>::dumpTo(QJsonObject& jo, const Filter& pod) -{ - addParam<IfNotEmpty>(jo, QStringLiteral("event_fields"), pod.eventFields); - addParam<IfNotEmpty>(jo, QStringLiteral("event_format"), pod.eventFormat); - addParam<IfNotEmpty>(jo, QStringLiteral("presence"), pod.presence); - addParam<IfNotEmpty>(jo, QStringLiteral("account_data"), pod.accountData); - addParam<IfNotEmpty>(jo, QStringLiteral("room"), pod.room); -} - -void JsonObjectConverter<Filter>::fillFrom(const QJsonObject& jo, Filter& result) -{ - fromJson(jo.value("event_fields"_ls), result.eventFields); - fromJson(jo.value("event_format"_ls), result.eventFormat); - fromJson(jo.value("presence"_ls), result.presence); - fromJson(jo.value("account_data"_ls), result.accountData); - fromJson(jo.value("room"_ls), result.room); -} diff --git a/lib/csapi/definitions/sync_filter.h b/lib/csapi/definitions/sync_filter.h index ad8d055d..9c8f08d5 100644 --- a/lib/csapi/definitions/sync_filter.h +++ b/lib/csapi/definitions/sync_filter.h @@ -10,40 +10,6 @@ #include "csapi/definitions/room_event_filter.h" namespace Quotient { - -// Data structures - -/// The state events to include for rooms. -struct StateFilter : RoomEventFilter { - /// If ``true``, the only ``m.room.member`` events returned in - /// the ``state`` section of the ``/sync`` response are those - /// which are definitely necessary for a client to display - /// the ``sender`` of the timeline events in that response. - /// If ``false``, ``m.room.member`` events are not filtered. - /// By default, servers should suppress duplicate redundant - /// lazy-loaded ``m.room.member`` events from being sent to a given - /// client across multiple calls to ``/sync``, given that most clients - /// cache membership events (see ``include_redundant_members`` - /// to change this behaviour). - Omittable<bool> lazyLoadMembers; - - /// If ``true``, the ``state`` section of the ``/sync`` response will - /// always contain the ``m.room.member`` events required to display - /// the ``sender`` of the timeline events in that response, assuming - /// ``lazy_load_members`` is enabled. This means that redundant - /// duplicate member events may be returned across multiple calls to - /// ``/sync``. This is useful for naive clients who never track - /// membership data. If ``false``, duplicate ``m.room.member`` events - /// may be suppressed by the server across multiple calls to ``/sync``. - /// If ``lazy_load_members`` is ``false`` this field is ignored. - Omittable<bool> includeRedundantMembers; -}; - -template <> -struct JsonObjectConverter<StateFilter> { - static void dumpTo(QJsonObject& jo, const StateFilter& pod); - static void fillFrom(const QJsonObject& jo, StateFilter& pod); -}; /// Filters to be applied to room data. struct RoomFilter { /// A list of room IDs to exclude. If this list is absent then no rooms are @@ -59,30 +25,50 @@ struct RoomFilter { /// The events that aren't recorded in the room history, e.g. typing and /// receipts, to include for rooms. - Omittable<RoomEventFilter> ephemeral; + RoomEventFilter ephemeral; /// Include rooms that the user has left in the sync, default false Omittable<bool> includeLeave; /// The state events to include for rooms. - Omittable<StateFilter> state; + RoomEventFilter state; /// The message and state update events to include for rooms. - Omittable<RoomEventFilter> timeline; + RoomEventFilter timeline; /// The per user account data to include for rooms. - Omittable<RoomEventFilter> accountData; + RoomEventFilter accountData; }; template <> struct JsonObjectConverter<RoomFilter> { - static void dumpTo(QJsonObject& jo, const RoomFilter& pod); - static void fillFrom(const QJsonObject& jo, RoomFilter& pod); + static void dumpTo(QJsonObject& jo, const RoomFilter& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("not_rooms"), pod.notRooms); + addParam<IfNotEmpty>(jo, QStringLiteral("rooms"), pod.rooms); + addParam<IfNotEmpty>(jo, QStringLiteral("ephemeral"), pod.ephemeral); + addParam<IfNotEmpty>(jo, QStringLiteral("include_leave"), + pod.includeLeave); + addParam<IfNotEmpty>(jo, QStringLiteral("state"), pod.state); + addParam<IfNotEmpty>(jo, QStringLiteral("timeline"), pod.timeline); + addParam<IfNotEmpty>(jo, QStringLiteral("account_data"), + pod.accountData); + } + static void fillFrom(const QJsonObject& jo, RoomFilter& pod) + { + fromJson(jo.value("not_rooms"_ls), pod.notRooms); + fromJson(jo.value("rooms"_ls), pod.rooms); + fromJson(jo.value("ephemeral"_ls), pod.ephemeral); + fromJson(jo.value("include_leave"_ls), pod.includeLeave); + fromJson(jo.value("state"_ls), pod.state); + fromJson(jo.value("timeline"_ls), pod.timeline); + fromJson(jo.value("account_data"_ls), pod.accountData); + } }; struct Filter { /// List of event fields to include. If this list is absent then all fields - /// are included. The entries may include '.' charaters to indicate + /// are included. The entries may include '.' characters to indicate /// sub-fields. So ['content.body'] will include the 'body' field of the /// 'content' object. A literal '.' character in a field name may be escaped /// using a '\\'. A server may include more fields than were requested. @@ -90,23 +76,40 @@ struct Filter { /// The format to use for events. 'client' will return the events in a /// format suitable for clients. 'federation' will return the raw event as - /// receieved over federation. The default is 'client'. + /// received over federation. The default is 'client'. QString eventFormat; /// The presence updates to include. - Omittable<EventFilter> presence; + EventFilter presence; /// The user account data that isn't associated with rooms to include. - Omittable<EventFilter> accountData; + EventFilter accountData; /// Filters to be applied to room data. - Omittable<RoomFilter> room; + RoomFilter room; }; template <> struct JsonObjectConverter<Filter> { - static void dumpTo(QJsonObject& jo, const Filter& pod); - static void fillFrom(const QJsonObject& jo, Filter& pod); + static void dumpTo(QJsonObject& jo, const Filter& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("event_fields"), + pod.eventFields); + addParam<IfNotEmpty>(jo, QStringLiteral("event_format"), + pod.eventFormat); + addParam<IfNotEmpty>(jo, QStringLiteral("presence"), pod.presence); + addParam<IfNotEmpty>(jo, QStringLiteral("account_data"), + pod.accountData); + addParam<IfNotEmpty>(jo, QStringLiteral("room"), pod.room); + } + static void fillFrom(const QJsonObject& jo, Filter& pod) + { + fromJson(jo.value("event_fields"_ls), pod.eventFields); + fromJson(jo.value("event_format"_ls), pod.eventFormat); + fromJson(jo.value("presence"_ls), pod.presence); + fromJson(jo.value("account_data"_ls), pod.accountData); + fromJson(jo.value("room"_ls), pod.room); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/third_party_signed.h b/lib/csapi/definitions/third_party_signed.h new file mode 100644 index 00000000..526545d0 --- /dev/null +++ b/lib/csapi/definitions/third_party_signed.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "converters.h" + +namespace Quotient { +/// A signature of an ``m.third_party_invite`` token to prove that this user +/// owns a third party identity which has been invited to the room. +struct ThirdPartySigned { + /// The Matrix ID of the user who issued the invite. + QString sender; + + /// The Matrix ID of the invitee. + QString mxid; + + /// The state key of the m.third_party_invite event. + QString token; + + /// A signatures object containing a signature of the entire signed object. + QHash<QString, QHash<QString, QString>> signatures; +}; + +template <> +struct JsonObjectConverter<ThirdPartySigned> { + static void dumpTo(QJsonObject& jo, const ThirdPartySigned& pod) + { + addParam<>(jo, QStringLiteral("sender"), pod.sender); + addParam<>(jo, QStringLiteral("mxid"), pod.mxid); + addParam<>(jo, QStringLiteral("token"), pod.token); + addParam<>(jo, QStringLiteral("signatures"), pod.signatures); + } + static void fillFrom(const QJsonObject& jo, ThirdPartySigned& pod) + { + fromJson(jo.value("sender"_ls), pod.sender); + fromJson(jo.value("mxid"_ls), pod.mxid); + fromJson(jo.value("token"_ls), pod.token); + fromJson(jo.value("signatures"_ls), pod.signatures); + } +}; + +} // namespace Quotient diff --git a/lib/csapi/definitions/user_identifier.cpp b/lib/csapi/definitions/user_identifier.cpp deleted file mode 100644 index 9e9b4fe9..00000000 --- a/lib/csapi/definitions/user_identifier.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "user_identifier.h" - -using namespace Quotient; - -void JsonObjectConverter<UserIdentifier>::dumpTo(QJsonObject& jo, - const UserIdentifier& pod) -{ - fillJson(jo, pod.additionalProperties); - addParam<>(jo, QStringLiteral("type"), pod.type); -} - -void JsonObjectConverter<UserIdentifier>::fillFrom(QJsonObject jo, - UserIdentifier& result) -{ - fromJson(jo.take("type"_ls), result.type); - fromJson(jo, result.additionalProperties); -} diff --git a/lib/csapi/definitions/user_identifier.h b/lib/csapi/definitions/user_identifier.h index 72a81677..dadf6f97 100644 --- a/lib/csapi/definitions/user_identifier.h +++ b/lib/csapi/definitions/user_identifier.h @@ -6,12 +6,7 @@ #include "converters.h" -#include <QtCore/QVariant> - namespace Quotient { - -// Data structures - /// Identification information for a user struct UserIdentifier { /// The type of identification. See `Identifier types`_ for supported @@ -24,8 +19,16 @@ struct UserIdentifier { template <> struct JsonObjectConverter<UserIdentifier> { - static void dumpTo(QJsonObject& jo, const UserIdentifier& pod); - static void fillFrom(QJsonObject jo, UserIdentifier& pod); + static void dumpTo(QJsonObject& jo, const UserIdentifier& pod) + { + fillJson(jo, pod.additionalProperties); + addParam<>(jo, QStringLiteral("type"), pod.type); + } + static void fillFrom(QJsonObject jo, UserIdentifier& pod) + { + fromJson(jo.take("type"_ls), pod.type); + fromJson(jo, pod.additionalProperties); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/wellknown/full.cpp b/lib/csapi/definitions/wellknown/full.cpp deleted file mode 100644 index 595db0e5..00000000 --- a/lib/csapi/definitions/wellknown/full.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "full.h" - -using namespace Quotient; - -void JsonObjectConverter<DiscoveryInformation>::dumpTo( - QJsonObject& jo, const DiscoveryInformation& pod) -{ - fillJson(jo, pod.additionalProperties); - addParam<>(jo, QStringLiteral("m.homeserver"), pod.homeserver); - addParam<IfNotEmpty>(jo, QStringLiteral("m.identity_server"), - pod.identityServer); -} - -void JsonObjectConverter<DiscoveryInformation>::fillFrom( - QJsonObject jo, DiscoveryInformation& result) -{ - fromJson(jo.take("m.homeserver"_ls), result.homeserver); - fromJson(jo.take("m.identity_server"_ls), result.identityServer); - fromJson(jo, result.additionalProperties); -} diff --git a/lib/csapi/definitions/wellknown/full.h b/lib/csapi/definitions/wellknown/full.h index 88d7da79..a0ef2076 100644 --- a/lib/csapi/definitions/wellknown/full.h +++ b/lib/csapi/definitions/wellknown/full.h @@ -9,13 +9,7 @@ #include "csapi/definitions/wellknown/homeserver.h" #include "csapi/definitions/wellknown/identity_server.h" -#include <QtCore/QHash> -#include <QtCore/QJsonObject> - namespace Quotient { - -// Data structures - /// Used by clients to determine the homeserver, identity server, and other /// optional components they should be interacting with. struct DiscoveryInformation { @@ -33,8 +27,19 @@ struct DiscoveryInformation { template <> struct JsonObjectConverter<DiscoveryInformation> { - static void dumpTo(QJsonObject& jo, const DiscoveryInformation& pod); - static void fillFrom(QJsonObject jo, DiscoveryInformation& pod); + static void dumpTo(QJsonObject& jo, const DiscoveryInformation& pod) + { + fillJson(jo, pod.additionalProperties); + addParam<>(jo, QStringLiteral("m.homeserver"), pod.homeserver); + addParam<IfNotEmpty>(jo, QStringLiteral("m.identity_server"), + pod.identityServer); + } + static void fillFrom(QJsonObject jo, DiscoveryInformation& pod) + { + fromJson(jo.take("m.homeserver"_ls), pod.homeserver); + fromJson(jo.take("m.identity_server"_ls), pod.identityServer); + fromJson(jo, pod.additionalProperties); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/wellknown/homeserver.cpp b/lib/csapi/definitions/wellknown/homeserver.cpp deleted file mode 100644 index 7b87aa95..00000000 --- a/lib/csapi/definitions/wellknown/homeserver.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "homeserver.h" - -using namespace Quotient; - -void JsonObjectConverter<HomeserverInformation>::dumpTo( - QJsonObject& jo, const HomeserverInformation& pod) -{ - addParam<>(jo, QStringLiteral("base_url"), pod.baseUrl); -} - -void JsonObjectConverter<HomeserverInformation>::fillFrom( - const QJsonObject& jo, HomeserverInformation& result) -{ - fromJson(jo.value("base_url"_ls), result.baseUrl); -} diff --git a/lib/csapi/definitions/wellknown/homeserver.h b/lib/csapi/definitions/wellknown/homeserver.h index 7607c8b5..5cfaca24 100644 --- a/lib/csapi/definitions/wellknown/homeserver.h +++ b/lib/csapi/definitions/wellknown/homeserver.h @@ -7,9 +7,6 @@ #include "converters.h" namespace Quotient { - -// Data structures - /// Used by clients to discover homeserver information. struct HomeserverInformation { /// The base URL for the homeserver for client-server connections. @@ -18,8 +15,14 @@ struct HomeserverInformation { template <> struct JsonObjectConverter<HomeserverInformation> { - static void dumpTo(QJsonObject& jo, const HomeserverInformation& pod); - static void fillFrom(const QJsonObject& jo, HomeserverInformation& pod); + static void dumpTo(QJsonObject& jo, const HomeserverInformation& pod) + { + addParam<>(jo, QStringLiteral("base_url"), pod.baseUrl); + } + static void fillFrom(const QJsonObject& jo, HomeserverInformation& pod) + { + fromJson(jo.value("base_url"_ls), pod.baseUrl); + } }; } // namespace Quotient diff --git a/lib/csapi/definitions/wellknown/identity_server.cpp b/lib/csapi/definitions/wellknown/identity_server.cpp deleted file mode 100644 index 3b2a5720..00000000 --- a/lib/csapi/definitions/wellknown/identity_server.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "identity_server.h" - -using namespace Quotient; - -void JsonObjectConverter<IdentityServerInformation>::dumpTo( - QJsonObject& jo, const IdentityServerInformation& pod) -{ - addParam<>(jo, QStringLiteral("base_url"), pod.baseUrl); -} - -void JsonObjectConverter<IdentityServerInformation>::fillFrom( - const QJsonObject& jo, IdentityServerInformation& result) -{ - fromJson(jo.value("base_url"_ls), result.baseUrl); -} diff --git a/lib/csapi/definitions/wellknown/identity_server.h b/lib/csapi/definitions/wellknown/identity_server.h index d272e527..3bd07bd1 100644 --- a/lib/csapi/definitions/wellknown/identity_server.h +++ b/lib/csapi/definitions/wellknown/identity_server.h @@ -7,9 +7,6 @@ #include "converters.h" namespace Quotient { - -// Data structures - /// Used by clients to discover identity server information. struct IdentityServerInformation { /// The base URL for the identity server for client-server connections. @@ -18,8 +15,14 @@ struct IdentityServerInformation { template <> struct JsonObjectConverter<IdentityServerInformation> { - static void dumpTo(QJsonObject& jo, const IdentityServerInformation& pod); - static void fillFrom(const QJsonObject& jo, IdentityServerInformation& pod); + static void dumpTo(QJsonObject& jo, const IdentityServerInformation& pod) + { + addParam<>(jo, QStringLiteral("base_url"), pod.baseUrl); + } + static void fillFrom(const QJsonObject& jo, IdentityServerInformation& pod) + { + fromJson(jo.value("base_url"_ls), pod.baseUrl); + } }; } // namespace Quotient diff --git a/lib/csapi/device_management.cpp b/lib/csapi/device_management.cpp index ba7570f7..eac9a545 100644 --- a/lib/csapi/device_management.cpp +++ b/lib/csapi/device_management.cpp @@ -4,97 +4,61 @@ #include "device_management.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class GetDevicesJob::Private { -public: - QVector<Device> devices; -}; - QUrl GetDevicesJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/devices"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/devices"); } GetDevicesJob::GetDevicesJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetDevicesJob"), - basePath % "/devices") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/devices") {} -GetDevicesJob::~GetDevicesJob() = default; - -const QVector<Device>& GetDevicesJob::devices() const { return d->devices; } - -BaseJob::Status GetDevicesJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("devices"_ls), d->devices); - - return Success; -} - -class GetDeviceJob::Private { -public: - Device data; -}; - QUrl GetDeviceJob::makeRequestUrl(QUrl baseUrl, const QString& deviceId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/devices/" % deviceId); + QStringLiteral("/_matrix/client/r0") + % "/devices/" % deviceId); } GetDeviceJob::GetDeviceJob(const QString& deviceId) : BaseJob(HttpVerb::Get, QStringLiteral("GetDeviceJob"), - basePath % "/devices/" % deviceId) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/devices/" % deviceId) {} -GetDeviceJob::~GetDeviceJob() = default; - -const Device& GetDeviceJob::data() const { return d->data; } - -BaseJob::Status GetDeviceJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} - UpdateDeviceJob::UpdateDeviceJob(const QString& deviceId, const QString& displayName) : BaseJob(HttpVerb::Put, QStringLiteral("UpdateDeviceJob"), - basePath % "/devices/" % deviceId) + QStringLiteral("/_matrix/client/r0") % "/devices/" % deviceId) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("display_name"), displayName); - setRequestData(_data); + setRequestData(std::move(_data)); } DeleteDeviceJob::DeleteDeviceJob(const QString& deviceId, const Omittable<AuthenticationData>& auth) : BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), - basePath % "/devices/" % deviceId) + QStringLiteral("/_matrix/client/r0") % "/devices/" % deviceId) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth); - setRequestData(_data); + setRequestData(std::move(_data)); } DeleteDevicesJob::DeleteDevicesJob(const QStringList& devices, const Omittable<AuthenticationData>& auth) : BaseJob(HttpVerb::Post, QStringLiteral("DeleteDevicesJob"), - basePath % "/delete_devices") + QStringLiteral("/_matrix/client/r0") % "/delete_devices") { QJsonObject _data; addParam<>(_data, QStringLiteral("devices"), devices); addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/device_management.h b/lib/csapi/device_management.h index 1c57db84..0abd5b84 100644 --- a/lib/csapi/device_management.h +++ b/lib/csapi/device_management.h @@ -4,19 +4,13 @@ #pragma once -#include "converters.h" - #include "csapi/definitions/auth_data.h" #include "csapi/definitions/client_device.h" #include "jobs/basejob.h" -#include <QtCore/QVector> - namespace Quotient { -// Operations - /*! \brief List registered devices for the current user * * Gets information about all devices for the current user. @@ -32,19 +26,14 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetDevicesJob() override; // Result properties /// A list of all registered devices for this user. - const QVector<Device>& devices() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QVector<Device> devices() const + { + return loadFromJson<QVector<Device>>("devices"_ls); + } }; /*! \brief Get a single device @@ -55,6 +44,7 @@ class GetDeviceJob : public BaseJob { public: /*! \brief Get a single device * + * * \param deviceId * The device to retrieve. */ @@ -66,19 +56,11 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& deviceId); - ~GetDeviceJob() override; // Result properties /// Device information - const Device& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + Device data() const { return fromJson<Device>(jsonData()); } }; /*! \brief Update a device @@ -89,8 +71,10 @@ class UpdateDeviceJob : public BaseJob { public: /*! \brief Update a device * + * * \param deviceId * The device to update. + * * \param displayName * The new display name for this device. If not given, the * display name is unchanged. @@ -109,8 +93,10 @@ class DeleteDeviceJob : public BaseJob { public: /*! \brief Delete a device * + * * \param deviceId * The device to delete. + * * \param auth * Additional authentication information for the * user-interactive authentication API. @@ -130,8 +116,10 @@ class DeleteDevicesJob : public BaseJob { public: /*! \brief Bulk deletion of devices * + * * \param devices * The list of device IDs to delete. + * * \param auth * Additional authentication information for the * user-interactive authentication API. diff --git a/lib/csapi/directory.cpp b/lib/csapi/directory.cpp index 0d4029bd..25ea82e2 100644 --- a/lib/csapi/directory.cpp +++ b/lib/csapi/directory.cpp @@ -4,63 +4,58 @@ #include "directory.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0/directory"); - SetRoomAliasJob::SetRoomAliasJob(const QString& roomAlias, const QString& roomId) : BaseJob(HttpVerb::Put, QStringLiteral("SetRoomAliasJob"), - basePath % "/room/" % roomAlias) + QStringLiteral("/_matrix/client/r0") % "/directory/room/" + % roomAlias) { QJsonObject _data; addParam<>(_data, QStringLiteral("room_id"), roomId); - setRequestData(_data); + setRequestData(std::move(_data)); } -class GetRoomIdByAliasJob::Private { -public: - QString roomId; - QStringList servers; -}; - QUrl GetRoomIdByAliasJob::makeRequestUrl(QUrl baseUrl, const QString& roomAlias) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/room/" % roomAlias); + QStringLiteral("/_matrix/client/r0") + % "/directory/room/" % roomAlias); } GetRoomIdByAliasJob::GetRoomIdByAliasJob(const QString& roomAlias) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomIdByAliasJob"), - basePath % "/room/" % roomAlias, false) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/directory/room/" + % roomAlias, + false) {} -GetRoomIdByAliasJob::~GetRoomIdByAliasJob() = default; - -const QString& GetRoomIdByAliasJob::roomId() const { return d->roomId; } - -const QStringList& GetRoomIdByAliasJob::servers() const { return d->servers; } - -BaseJob::Status GetRoomIdByAliasJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("room_id"_ls), d->roomId); - fromJson(json.value("servers"_ls), d->servers); - - return Success; -} - QUrl DeleteRoomAliasJob::makeRequestUrl(QUrl baseUrl, const QString& roomAlias) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/room/" % roomAlias); + QStringLiteral("/_matrix/client/r0") + % "/directory/room/" % roomAlias); } DeleteRoomAliasJob::DeleteRoomAliasJob(const QString& roomAlias) : BaseJob(HttpVerb::Delete, QStringLiteral("DeleteRoomAliasJob"), - basePath % "/room/" % roomAlias) + QStringLiteral("/_matrix/client/r0") % "/directory/room/" + % roomAlias) {} + +QUrl GetLocalAliasesJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) +{ + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/rooms/" % roomId % "/aliases"); +} + +GetLocalAliasesJob::GetLocalAliasesJob(const QString& roomId) + : BaseJob(HttpVerb::Get, QStringLiteral("GetLocalAliasesJob"), + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/aliases") +{ + addExpectedKey("aliases"); +} diff --git a/lib/csapi/directory.h b/lib/csapi/directory.h index c13ca20a..7ae44d1d 100644 --- a/lib/csapi/directory.h +++ b/lib/csapi/directory.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Create a new mapping from room alias to room ID. * */ @@ -17,8 +15,10 @@ class SetRoomAliasJob : public BaseJob { public: /*! \brief Create a new mapping from room alias to room ID. * + * * \param roomAlias * The room alias to set. + * * \param roomId * The room ID to set. */ @@ -37,6 +37,7 @@ class GetRoomIdByAliasJob : public BaseJob { public: /*! \brief Get the room ID corresponding to this room alias. * + * * \param roomAlias * The room alias. */ @@ -48,22 +49,17 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomAlias); - ~GetRoomIdByAliasJob() override; // Result properties /// The room ID for this room alias. - const QString& roomId() const; + QString roomId() const { return loadFromJson<QString>("room_id"_ls); } /// A list of servers that are aware of this room alias. - const QStringList& servers() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QStringList servers() const + { + return loadFromJson<QStringList>("servers"_ls); + } }; /*! \brief Remove a mapping of room alias to room ID. @@ -73,11 +69,20 @@ private: * Servers may choose to implement additional access control checks here, for * instance that room aliases can only be deleted by their creator or a server * administrator. + * + * .. Note:: + * Servers may choose to update the ``alt_aliases`` for the + * ``m.room.canonical_alias`` state event in the room when an alias is removed. + * Servers which choose to update the canonical alias event are recommended to, + * in addition to their other relevant permission checks, delete the alias and + * return a successful response even if the user does not have permission to + * update the ``m.room.canonical_alias`` event. */ class DeleteRoomAliasJob : public BaseJob { public: /*! \brief Remove a mapping of room alias to room ID. * + * * \param roomAlias * The room alias to remove. */ @@ -91,4 +96,49 @@ public: static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomAlias); }; +/*! \brief Get a list of local aliases on a given room. + * + * Get a list of aliases maintained by the local server for the + * given room. + * + * This endpoint can be called by users who are in the room (external + * users receive an ``M_FORBIDDEN`` error response). If the room's + * ``m.room.history_visibility`` maps to ``world_readable``, any + * user can call this endpoint. + * + * Servers may choose to implement additional access control checks here, + * such as allowing server administrators to view aliases regardless of + * membership. + * + * .. Note:: + * Clients are recommended not to display this list of aliases prominently + * as they are not curated, unlike those listed in the + * ``m.room.canonical_alias`` state event. + */ +class GetLocalAliasesJob : public BaseJob { +public: + /*! \brief Get a list of local aliases on a given room. + * + * + * \param roomId + * The room ID to find local aliases of. + */ + explicit GetLocalAliasesJob(const QString& roomId); + + /*! \brief Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for GetLocalAliasesJob + * is necessary but the job itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); + + // Result properties + + /// The server's local aliases on the room. Can be empty. + QStringList aliases() const + { + return loadFromJson<QStringList>("aliases"_ls); + } +}; + } // namespace Quotient diff --git a/lib/csapi/event_context.cpp b/lib/csapi/event_context.cpp index 5bb2222e..d2a5f522 100644 --- a/lib/csapi/event_context.cpp +++ b/lib/csapi/event_context.cpp @@ -4,79 +4,36 @@ #include "event_context.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class GetEventContextJob::Private { -public: - QString begin; - QString end; - RoomEvents eventsBefore; - RoomEventPtr event; - RoomEvents eventsAfter; - StateEvents state; -}; - -BaseJob::Query queryToGetEventContext(Omittable<int> limit) +auto queryToGetEventContext(Omittable<int> limit, const QString& filter) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("limit"), limit); + addParam<IfNotEmpty>(_q, QStringLiteral("filter"), filter); return _q; } QUrl GetEventContextJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& eventId, - Omittable<int> limit) + Omittable<int> limit, + const QString& filter) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/rooms/" % roomId % "/context/" + QStringLiteral("/_matrix/client/r0") + % "/rooms/" % roomId % "/context/" % eventId, - queryToGetEventContext(limit)); + queryToGetEventContext(limit, filter)); } GetEventContextJob::GetEventContextJob(const QString& roomId, const QString& eventId, - Omittable<int> limit) + Omittable<int> limit, + const QString& filter) : BaseJob(HttpVerb::Get, QStringLiteral("GetEventContextJob"), - basePath % "/rooms/" % roomId % "/context/" % eventId, - queryToGetEventContext(limit)) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/context/" % eventId, + queryToGetEventContext(limit, filter)) {} - -GetEventContextJob::~GetEventContextJob() = default; - -const QString& GetEventContextJob::begin() const { return d->begin; } - -const QString& GetEventContextJob::end() const { return d->end; } - -RoomEvents&& GetEventContextJob::eventsBefore() -{ - return std::move(d->eventsBefore); -} - -RoomEventPtr&& GetEventContextJob::event() { return std::move(d->event); } - -RoomEvents&& GetEventContextJob::eventsAfter() -{ - return std::move(d->eventsAfter); -} - -StateEvents&& GetEventContextJob::state() { return std::move(d->state); } - -BaseJob::Status GetEventContextJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("start"_ls), d->begin); - fromJson(json.value("end"_ls), d->end); - fromJson(json.value("events_before"_ls), d->eventsBefore); - fromJson(json.value("event"_ls), d->event); - fromJson(json.value("events_after"_ls), d->eventsAfter); - fromJson(json.value("state"_ls), d->state); - - return Success; -} diff --git a/lib/csapi/event_context.h b/lib/csapi/event_context.h index 54441617..2f9c66d8 100644 --- a/lib/csapi/event_context.h +++ b/lib/csapi/event_context.h @@ -4,34 +4,47 @@ #pragma once -#include "converters.h" - #include "events/eventloader.h" #include "jobs/basejob.h" namespace Quotient { -// Operations - /*! \brief Get events and state around the specified event. * * This API returns a number of events that happened just before and * after the specified event. This allows clients to get the context * surrounding an event. + * + * *Note*: This endpoint supports lazy-loading of room member events. See + * `Lazy-loading room members <#lazy-loading-room-members>`_ for more + * information. */ class GetEventContextJob : public BaseJob { public: /*! \brief Get events and state around the specified event. * + * * \param roomId * The room to get events from. + * * \param eventId * The event to get context around. + * * \param limit * The maximum number of events to return. Default: 10. + * + * \param filter + * A JSON ``RoomEventFilter`` to filter the returned events with. The + * filter is only applied to ``events_before``, ``events_after``, and + * ``state``. It is not applied to the ``event`` itself. The filter may + * be applied before or/and after the ``limit`` parameter - whichever the + * homeserver prefers. + * + * See `Filtering <#filtering>`_ for more information. */ explicit GetEventContextJob(const QString& roomId, const QString& eventId, - Omittable<int> limit = none); + Omittable<int> limit = none, + const QString& filter = {}); /*! \brief Construct a URL without creating a full-fledged job object * @@ -40,37 +53,36 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& eventId, - Omittable<int> limit = none); - ~GetEventContextJob() override; + Omittable<int> limit = none, + const QString& filter = {}); // Result properties /// A token that can be used to paginate backwards with. - const QString& begin() const; + QString begin() const { return loadFromJson<QString>("start"_ls); } /// A token that can be used to paginate forwards with. - const QString& end() const; + QString end() const { return loadFromJson<QString>("end"_ls); } /// A list of room events that happened just before the /// requested event, in reverse-chronological order. - RoomEvents&& eventsBefore(); + RoomEvents eventsBefore() + { + return takeFromJson<RoomEvents>("events_before"_ls); + } /// Details of the requested event. - RoomEventPtr&& event(); + RoomEventPtr event() { return takeFromJson<RoomEventPtr>("event"_ls); } /// A list of room events that happened just after the /// requested event, in chronological order. - RoomEvents&& eventsAfter(); + RoomEvents eventsAfter() + { + return takeFromJson<RoomEvents>("events_after"_ls); + } /// The state of the room at the last event returned. - StateEvents&& state(); - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + StateEvents state() { return takeFromJson<StateEvents>("state"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/filter.cpp b/lib/csapi/filter.cpp index 98b85f83..bb3a893f 100644 --- a/lib/csapi/filter.cpp +++ b/lib/csapi/filter.cpp @@ -4,68 +4,29 @@ #include "filter.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class DefineFilterJob::Private { -public: - QString filterId; -}; - DefineFilterJob::DefineFilterJob(const QString& userId, const Filter& filter) : BaseJob(HttpVerb::Post, QStringLiteral("DefineFilterJob"), - basePath % "/user/" % userId % "/filter") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/user/" % userId + % "/filter") { setRequestData(Data(toJson(filter))); + addExpectedKey("filter_id"); } -DefineFilterJob::~DefineFilterJob() = default; - -const QString& DefineFilterJob::filterId() const { return d->filterId; } - -BaseJob::Status DefineFilterJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("filter_id"_ls)) - return { IncorrectResponse, - "The key 'filter_id' not found in the response" }; - fromJson(json.value("filter_id"_ls), d->filterId); - - return Success; -} - -class GetFilterJob::Private { -public: - Filter data; -}; - QUrl GetFilterJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& filterId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/user/" - % userId % "/filter/" - % filterId); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") % "/user/" + % userId % "/filter/" % filterId); } GetFilterJob::GetFilterJob(const QString& userId, const QString& filterId) : BaseJob(HttpVerb::Get, QStringLiteral("GetFilterJob"), - basePath % "/user/" % userId % "/filter/" % filterId) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/user/" % userId + % "/filter/" % filterId) {} - -GetFilterJob::~GetFilterJob() = default; - -const Filter& GetFilterJob::data() const { return d->data; } - -BaseJob::Status GetFilterJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} diff --git a/lib/csapi/filter.h b/lib/csapi/filter.h index 7a0f8958..9aa7dc79 100644 --- a/lib/csapi/filter.h +++ b/lib/csapi/filter.h @@ -4,16 +4,12 @@ #pragma once -#include "converters.h" - #include "csapi/definitions/sync_filter.h" #include "jobs/basejob.h" namespace Quotient { -// Operations - /*! \brief Upload a new filter. * * Uploads a new filter definition to the homeserver. @@ -24,30 +20,23 @@ class DefineFilterJob : public BaseJob { public: /*! \brief Upload a new filter. * + * * \param userId * The id of the user uploading the filter. The access token must be - * authorized to make requests for this user id. \param filter Uploads a new - * filter definition to the homeserver. Returns a filter ID that may be used - * in future requests to restrict which events are returned to the client. + * authorized to make requests for this user id. + * + * \param filter + * The filter to upload. */ explicit DefineFilterJob(const QString& userId, const Filter& filter); - ~DefineFilterJob() override; - // Result properties /// The ID of the filter that was created. Cannot start /// with a ``{`` as this character is used to determine /// if the filter provided is inline JSON or a previously /// declared filter by homeservers on some APIs. - const QString& filterId() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString filterId() const { return loadFromJson<QString>("filter_id"_ls); } }; /*! \brief Download a filter @@ -57,8 +46,10 @@ class GetFilterJob : public BaseJob { public: /*! \brief Download a filter * + * * \param userId * The user ID to download a filter for. + * * \param filterId * The filter ID to download. */ @@ -71,19 +62,11 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& filterId); - ~GetFilterJob() override; // Result properties /// "The filter defintion" - const Filter& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + Filter data() const { return fromJson<Filter>(jsonData()); } }; } // namespace Quotient diff --git a/lib/csapi/inviting.cpp b/lib/csapi/inviting.cpp index f070c3ce..01620f9e 100644 --- a/lib/csapi/inviting.cpp +++ b/lib/csapi/inviting.cpp @@ -4,19 +4,16 @@ #include "inviting.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - InviteUserJob::InviteUserJob(const QString& roomId, const QString& userId) : BaseJob(HttpVerb::Post, QStringLiteral("InviteUserJob"), - basePath % "/rooms/" % roomId % "/invite") + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/invite") { QJsonObject _data; addParam<>(_data, QStringLiteral("user_id"), userId); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/inviting.h b/lib/csapi/inviting.h index 41d78ae6..a7ec6093 100644 --- a/lib/csapi/inviting.h +++ b/lib/csapi/inviting.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Invite a user to participate in a particular room. * * .. _invite-by-user-id-endpoint: @@ -35,8 +33,10 @@ class InviteUserJob : public BaseJob { public: /*! \brief Invite a user to participate in a particular room. * + * * \param roomId * The room identifier (not alias) to which to invite the user. + * * \param userId * The fully qualified user ID of the invitee. */ diff --git a/lib/csapi/joining.cpp b/lib/csapi/joining.cpp index cde179e0..4761e949 100644 --- a/lib/csapi/joining.cpp +++ b/lib/csapi/joining.cpp @@ -4,93 +4,23 @@ #include "joining.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<JoinRoomByIdJob::ThirdPartySigned> { - static void dumpTo(QJsonObject& jo, - const JoinRoomByIdJob::ThirdPartySigned& pod) - { - addParam<>(jo, QStringLiteral("sender"), pod.sender); - addParam<>(jo, QStringLiteral("mxid"), pod.mxid); - addParam<>(jo, QStringLiteral("token"), pod.token); - addParam<>(jo, QStringLiteral("signatures"), pod.signatures); - } -}; - -} // namespace Quotient - -class JoinRoomByIdJob::Private { -public: - QString roomId; -}; - JoinRoomByIdJob::JoinRoomByIdJob( const QString& roomId, const Omittable<ThirdPartySigned>& thirdPartySigned) : BaseJob(HttpVerb::Post, QStringLiteral("JoinRoomByIdJob"), - basePath % "/rooms/" % roomId % "/join") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/join") { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("third_party_signed"), thirdPartySigned); - setRequestData(_data); + setRequestData(std::move(_data)); + addExpectedKey("room_id"); } -JoinRoomByIdJob::~JoinRoomByIdJob() = default; - -const QString& JoinRoomByIdJob::roomId() const { return d->roomId; } - -BaseJob::Status JoinRoomByIdJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("room_id"_ls)) - return { IncorrectResponse, - "The key 'room_id' not found in the response" }; - fromJson(json.value("room_id"_ls), d->roomId); - - return Success; -} - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<JoinRoomJob::Signed> { - static void dumpTo(QJsonObject& jo, const JoinRoomJob::Signed& pod) - { - addParam<>(jo, QStringLiteral("sender"), pod.sender); - addParam<>(jo, QStringLiteral("mxid"), pod.mxid); - addParam<>(jo, QStringLiteral("token"), pod.token); - addParam<>(jo, QStringLiteral("signatures"), pod.signatures); - } -}; - -template <> -struct JsonObjectConverter<JoinRoomJob::ThirdPartySigned> { - static void dumpTo(QJsonObject& jo, const JoinRoomJob::ThirdPartySigned& pod) - { - addParam<>(jo, QStringLiteral("signed"), pod.signedData); - } -}; - -} // namespace Quotient - -class JoinRoomJob::Private { -public: - QString roomId; -}; - -BaseJob::Query queryToJoinRoom(const QStringList& serverName) +auto queryToJoinRoom(const QStringList& serverName) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("server_name"), serverName); @@ -101,26 +31,12 @@ JoinRoomJob::JoinRoomJob(const QString& roomIdOrAlias, const QStringList& serverName, const Omittable<ThirdPartySigned>& thirdPartySigned) : BaseJob(HttpVerb::Post, QStringLiteral("JoinRoomJob"), - basePath % "/join/" % roomIdOrAlias, queryToJoinRoom(serverName)) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/join/" % roomIdOrAlias, + queryToJoinRoom(serverName)) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("third_party_signed"), thirdPartySigned); - setRequestData(_data); -} - -JoinRoomJob::~JoinRoomJob() = default; - -const QString& JoinRoomJob::roomId() const { return d->roomId; } - -BaseJob::Status JoinRoomJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("room_id"_ls)) - return { IncorrectResponse, - "The key 'room_id' not found in the response" }; - fromJson(json.value("room_id"_ls), d->roomId); - - return Success; + setRequestData(std::move(_data)); + addExpectedKey("room_id"); } diff --git a/lib/csapi/joining.h b/lib/csapi/joining.h index 6d93bcb1..6822fbdf 100644 --- a/lib/csapi/joining.h +++ b/lib/csapi/joining.h @@ -4,16 +4,12 @@ #pragma once -#include "converters.h" +#include "csapi/definitions/third_party_signed.h" #include "jobs/basejob.h" -#include <QtCore/QJsonObject> - namespace Quotient { -// Operations - /*! \brief Start the requesting user participating in a particular room. * * *Note that this API requires a room ID, not alias.* ``/join/{roomIdOrAlias}`` @@ -33,49 +29,36 @@ namespace Quotient { */ class JoinRoomByIdJob : public BaseJob { public: - // Inner data structures - - /// A signature of an ``m.third_party_invite`` token to prove that this user - /// owns a third party identity which has been invited to the room. - struct ThirdPartySigned { - /// The Matrix ID of the user who issued the invite. - QString sender; - /// The Matrix ID of the invitee. - QString mxid; - /// The state key of the m.third_party_invite event. - QString token; - /// A signatures object containing a signature of the entire signed - /// object. - QJsonObject signatures; - }; - - // Construction/destruction - /*! \brief Start the requesting user participating in a particular room. * + * * \param roomId * The room identifier (not alias) to join. + * * \param thirdPartySigned - * A signature of an ``m.third_party_invite`` token to prove that this - * user owns a third party identity which has been invited to the room. + * *Note that this API requires a room ID, not alias.* + * ``/join/{roomIdOrAlias}`` *exists if you have a room alias.* + * + * This API starts a user participating in a particular room, if that user + * is allowed to participate in that room. After this call, the client is + * allowed to see all current state events in the room, and all subsequent + * events associated with the room until the user leaves the room. + * + * After a user has joined a room, the room will appear as an entry in the + * response of the |/initialSync|_ and |/sync|_ APIs. + * + * If a ``third_party_signed`` was supplied, the homeserver must verify + * that it matches a pending ``m.room.third_party_invite`` event in the + * room, and perform key validity checking if required by the event. */ explicit JoinRoomByIdJob( const QString& roomId, const Omittable<ThirdPartySigned>& thirdPartySigned = none); - ~JoinRoomByIdJob() override; - // Result properties /// The joined room ID. - const QString& roomId() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString roomId() const { return loadFromJson<QString>("room_id"_ls); } }; /*! \brief Start the requesting user participating in a particular room. @@ -97,72 +80,40 @@ private: */ class JoinRoomJob : public BaseJob { public: - // Inner data structures - - /// *Note that this API takes either a room ID or alias, unlike* - /// ``/room/{roomId}/join``. - /// - /// This API starts a user participating in a particular room, if that user - /// is allowed to participate in that room. After this call, the client is - /// allowed to see all current state events in the room, and all subsequent - /// events associated with the room until the user leaves the room. - /// - /// After a user has joined a room, the room will appear as an entry in the - /// response of the |/initialSync|_ and |/sync|_ APIs. - /// - /// If a ``third_party_signed`` was supplied, the homeserver must verify - /// that it matches a pending ``m.room.third_party_invite`` event in the - /// room, and perform key validity checking if required by the event. - struct Signed { - /// The Matrix ID of the user who issued the invite. - QString sender; - /// The Matrix ID of the invitee. - QString mxid; - /// The state key of the m.third_party_invite event. - QString token; - /// A signatures object containing a signature of the entire signed - /// object. - QJsonObject signatures; - }; - - /// A signature of an ``m.third_party_invite`` token to prove that this user - /// owns a third party identity which has been invited to the room. - struct ThirdPartySigned { - /// A signature of an ``m.third_party_invite`` token to prove that this - /// user owns a third party identity which has been invited to the room. - Signed signedData; - }; - - // Construction/destruction - /*! \brief Start the requesting user participating in a particular room. * + * * \param roomIdOrAlias * The room identifier or alias to join. + * * \param serverName * The servers to attempt to join the room through. One of the servers * must be participating in the room. + * * \param thirdPartySigned - * A signature of an ``m.third_party_invite`` token to prove that this - * user owns a third party identity which has been invited to the room. + * *Note that this API takes either a room ID or alias, unlike* + * ``/room/{roomId}/join``. + * + * This API starts a user participating in a particular room, if that user + * is allowed to participate in that room. After this call, the client is + * allowed to see all current state events in the room, and all subsequent + * events associated with the room until the user leaves the room. + * + * After a user has joined a room, the room will appear as an entry in the + * response of the |/initialSync|_ and |/sync|_ APIs. + * + * If a ``third_party_signed`` was supplied, the homeserver must verify + * that it matches a pending ``m.room.third_party_invite`` event in the + * room, and perform key validity checking if required by the event. */ explicit JoinRoomJob( const QString& roomIdOrAlias, const QStringList& serverName = {}, const Omittable<ThirdPartySigned>& thirdPartySigned = none); - ~JoinRoomJob() override; - // Result properties /// The joined room ID. - const QString& roomId() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString roomId() const { return loadFromJson<QString>("room_id"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/keys.cpp b/lib/csapi/keys.cpp index b1a947b3..34ab47c9 100644 --- a/lib/csapi/keys.cpp +++ b/lib/csapi/keys.cpp @@ -4,161 +4,48 @@ #include "keys.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class UploadKeysJob::Private { -public: - QHash<QString, int> oneTimeKeyCounts; -}; - UploadKeysJob::UploadKeysJob(const Omittable<DeviceKeys>& deviceKeys, const QHash<QString, QVariant>& oneTimeKeys) : BaseJob(HttpVerb::Post, QStringLiteral("UploadKeysJob"), - basePath % "/keys/upload") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/keys/upload") { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("device_keys"), deviceKeys); addParam<IfNotEmpty>(_data, QStringLiteral("one_time_keys"), oneTimeKeys); - setRequestData(_data); -} - -UploadKeysJob::~UploadKeysJob() = default; - -const QHash<QString, int>& UploadKeysJob::oneTimeKeyCounts() const -{ - return d->oneTimeKeyCounts; -} - -BaseJob::Status UploadKeysJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("one_time_key_counts"_ls)) - return { IncorrectResponse, - "The key 'one_time_key_counts' not found in the response" }; - fromJson(json.value("one_time_key_counts"_ls), d->oneTimeKeyCounts); - - return Success; + setRequestData(std::move(_data)); + addExpectedKey("one_time_key_counts"); } -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<QueryKeysJob::UnsignedDeviceInfo> { - static void fillFrom(const QJsonObject& jo, - QueryKeysJob::UnsignedDeviceInfo& result) - { - fromJson(jo.value("device_display_name"_ls), result.deviceDisplayName); - } -}; - -template <> -struct JsonObjectConverter<QueryKeysJob::DeviceInformation> { - static void fillFrom(const QJsonObject& jo, - QueryKeysJob::DeviceInformation& result) - { - fillFromJson<DeviceKeys>(jo, result); - fromJson(jo.value("unsigned"_ls), result.unsignedData); - } -}; - -} // namespace Quotient - -class QueryKeysJob::Private { -public: - QHash<QString, QJsonObject> failures; - QHash<QString, QHash<QString, DeviceInformation>> deviceKeys; -}; - QueryKeysJob::QueryKeysJob(const QHash<QString, QStringList>& deviceKeys, Omittable<int> timeout, const QString& token) : BaseJob(HttpVerb::Post, QStringLiteral("QueryKeysJob"), - basePath % "/keys/query") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/keys/query") { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("timeout"), timeout); addParam<>(_data, QStringLiteral("device_keys"), deviceKeys); addParam<IfNotEmpty>(_data, QStringLiteral("token"), token); - setRequestData(_data); -} - -QueryKeysJob::~QueryKeysJob() = default; - -const QHash<QString, QJsonObject>& QueryKeysJob::failures() const -{ - return d->failures; -} - -const QHash<QString, QHash<QString, QueryKeysJob::DeviceInformation>>& -QueryKeysJob::deviceKeys() const -{ - return d->deviceKeys; + setRequestData(std::move(_data)); } -BaseJob::Status QueryKeysJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("failures"_ls), d->failures); - fromJson(json.value("device_keys"_ls), d->deviceKeys); - - return Success; -} - -class ClaimKeysJob::Private { -public: - QHash<QString, QJsonObject> failures; - QHash<QString, QHash<QString, QVariant>> oneTimeKeys; -}; - ClaimKeysJob::ClaimKeysJob( const QHash<QString, QHash<QString, QString>>& oneTimeKeys, Omittable<int> timeout) : BaseJob(HttpVerb::Post, QStringLiteral("ClaimKeysJob"), - basePath % "/keys/claim") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/keys/claim") { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("timeout"), timeout); addParam<>(_data, QStringLiteral("one_time_keys"), oneTimeKeys); - setRequestData(_data); -} - -ClaimKeysJob::~ClaimKeysJob() = default; - -const QHash<QString, QJsonObject>& ClaimKeysJob::failures() const -{ - return d->failures; -} - -const QHash<QString, QHash<QString, QVariant>>& ClaimKeysJob::oneTimeKeys() const -{ - return d->oneTimeKeys; + setRequestData(std::move(_data)); + addExpectedKey("one_time_keys"); } -BaseJob::Status ClaimKeysJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("failures"_ls), d->failures); - fromJson(json.value("one_time_keys"_ls), d->oneTimeKeys); - - return Success; -} - -class GetKeysChangesJob::Private { -public: - QStringList changed; - QStringList left; -}; - -BaseJob::Query queryToGetKeysChanges(const QString& from, const QString& to) +auto queryToGetKeysChanges(const QString& from, const QString& to) { BaseJob::Query _q; addParam<>(_q, QStringLiteral("from"), from); @@ -170,27 +57,13 @@ QUrl GetKeysChangesJob::makeRequestUrl(QUrl baseUrl, const QString& from, const QString& to) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/keys/changes", + QStringLiteral("/_matrix/client/r0") + % "/keys/changes", queryToGetKeysChanges(from, to)); } GetKeysChangesJob::GetKeysChangesJob(const QString& from, const QString& to) : BaseJob(HttpVerb::Get, QStringLiteral("GetKeysChangesJob"), - basePath % "/keys/changes", queryToGetKeysChanges(from, to)) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/keys/changes", + queryToGetKeysChanges(from, to)) {} - -GetKeysChangesJob::~GetKeysChangesJob() = default; - -const QStringList& GetKeysChangesJob::changed() const { return d->changed; } - -const QStringList& GetKeysChangesJob::left() const { return d->left; } - -BaseJob::Status GetKeysChangesJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("changed"_ls), d->changed); - fromJson(json.value("left"_ls), d->left); - - return Success; -} diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h index 2673acc5..b7a8dc71 100644 --- a/lib/csapi/keys.h +++ b/lib/csapi/keys.h @@ -4,20 +4,12 @@ #pragma once -#include "converters.h" - #include "csapi/definitions/device_keys.h" #include "jobs/basejob.h" -#include <QtCore/QHash> -#include <QtCore/QJsonObject> -#include <QtCore/QVariant> - namespace Quotient { -// Operations - /*! \brief Upload end-to-end encryption keys. * * Publishes end-to-end encryption keys for the device. @@ -26,34 +18,30 @@ class UploadKeysJob : public BaseJob { public: /*! \brief Upload end-to-end encryption keys. * + * * \param deviceKeys * Identity keys for the device. May be absent if no new * identity keys are required. + * * \param oneTimeKeys * One-time public keys for "pre-key" messages. The names of * the properties should be in the format * ``<algorithm>:<key_id>``. The format of the key is determined - * by the key algorithm. + * by the `key algorithm <#key-algorithms>`_. * * May be absent if no new one-time keys are required. */ explicit UploadKeysJob(const Omittable<DeviceKeys>& deviceKeys = none, const QHash<QString, QVariant>& oneTimeKeys = {}); - ~UploadKeysJob() override; - // Result properties /// For each key algorithm, the number of unclaimed one-time keys /// of that type currently held on the server for this device. - const QHash<QString, int>& oneTimeKeyCounts() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QHash<QString, int> oneTimeKeyCounts() const + { + return loadFromJson<QHash<QString, int>>("one_time_key_counts"_ls); + } }; /*! \brief Download device identity keys. @@ -73,7 +61,9 @@ public: }; /// Returns the current devices and identity keys for the given users. - struct DeviceInformation : DeviceKeys { + struct DeviceKeys : + + Quotient::DeviceKeys { /// Additional data added to the device key information /// by intermediate servers, and not covered by the /// signatures. @@ -84,13 +74,16 @@ public: /*! \brief Download device identity keys. * + * * \param deviceKeys * The keys to be downloaded. A map from user ID, to a list of * device IDs, or to an empty list to indicate all devices for the * corresponding user. + * * \param timeout * The time (in milliseconds) to wait when downloading keys from * remote servers. 10 seconds is the recommended default. + * * \param token * If the client is fetching keys as a result of a device update received * in a sync request, this should be the 'since' token of that sync @@ -101,8 +94,6 @@ public: Omittable<int> timeout = none, const QString& token = {}); - ~QueryKeysJob() override; - // Result properties /// If any remote homeservers could not be reached, they are @@ -112,21 +103,39 @@ public: /// If the homeserver could be reached, but the user or device /// was unknown, no failure is recorded. Instead, the corresponding /// user or device is missing from the ``device_keys`` result. - const QHash<QString, QJsonObject>& failures() const; + QHash<QString, QJsonObject> failures() const + { + return loadFromJson<QHash<QString, QJsonObject>>("failures"_ls); + } /// Information on the queried devices. A map from user ID, to a /// map from device ID to device information. For each device, /// the information returned will be the same as uploaded via /// ``/keys/upload``, with the addition of an ``unsigned`` /// property. - const QHash<QString, QHash<QString, DeviceInformation>>& deviceKeys() const; + QHash<QString, QHash<QString, DeviceKeys>> deviceKeys() const + { + return loadFromJson<QHash<QString, QHash<QString, DeviceKeys>>>( + "device_keys"_ls); + } +}; -protected: - Status parseJson(const QJsonDocument& data) override; +template <> +struct JsonObjectConverter<QueryKeysJob::UnsignedDeviceInfo> { + static void fillFrom(const QJsonObject& jo, + QueryKeysJob::UnsignedDeviceInfo& result) + { + fromJson(jo.value("device_display_name"_ls), result.deviceDisplayName); + } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter<QueryKeysJob::DeviceKeys> { + static void fillFrom(const QJsonObject& jo, QueryKeysJob::DeviceKeys& result) + { + fillFromJson<DeviceKeys>(jo, result); + fromJson(jo.value("unsigned"_ls), result.unsignedData); + } }; /*! \brief Claim one-time encryption keys. @@ -137,9 +146,11 @@ class ClaimKeysJob : public BaseJob { public: /*! \brief Claim one-time encryption keys. * + * * \param oneTimeKeys * The keys to be claimed. A map from user ID, to a map from * device ID to algorithm name. + * * \param timeout * The time (in milliseconds) to wait when downloading keys from * remote servers. 10 seconds is the recommended default. @@ -148,8 +159,6 @@ public: const QHash<QString, QHash<QString, QString>>& oneTimeKeys, Omittable<int> timeout = none); - ~ClaimKeysJob() override; - // Result properties /// If any remote homeservers could not be reached, they are @@ -159,18 +168,22 @@ public: /// If the homeserver could be reached, but the user or device /// was unknown, no failure is recorded. Instead, the corresponding /// user or device is missing from the ``one_time_keys`` result. - const QHash<QString, QJsonObject>& failures() const; + QHash<QString, QJsonObject> failures() const + { + return loadFromJson<QHash<QString, QJsonObject>>("failures"_ls); + } /// One-time keys for the queried devices. A map from user ID, to a - /// map from devices to a map from ``<algorithm>:<key_id>`` to the key object. - const QHash<QString, QHash<QString, QVariant>>& oneTimeKeys() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + /// map from devices to a map from ``<algorithm>:<key_id>`` to the key + /// object. + /// + /// See the `key algorithms <#key-algorithms>`_ section for information + /// on the Key Object format. + QHash<QString, QHash<QString, QVariant>> oneTimeKeys() const + { + return loadFromJson<QHash<QString, QHash<QString, QVariant>>>( + "one_time_keys"_ls); + } }; /*! \brief Query users with recent device key updates. @@ -189,12 +202,14 @@ class GetKeysChangesJob : public BaseJob { public: /*! \brief Query users with recent device key updates. * + * * \param from * The desired start point of the list. Should be the ``next_batch`` field * from a response to an earlier call to |/sync|. Users who have not * uploaded new device identity keys since this point, nor deleted * existing devices with identity keys since then, will be excluded * from the results. + * * \param to * The desired end point of the list. Should be the ``next_batch`` * field from a recent call to |/sync| - typically the most recent @@ -210,25 +225,20 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& from, const QString& to); - ~GetKeysChangesJob() override; // Result properties /// The Matrix User IDs of all users who updated their device /// identity keys. - const QStringList& changed() const; + QStringList changed() const + { + return loadFromJson<QStringList>("changed"_ls); + } /// The Matrix User IDs of all users who may have left all /// the end-to-end encrypted rooms they previously shared /// with the user. - const QStringList& left() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QStringList left() const { return loadFromJson<QStringList>("left"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/kicking.cpp b/lib/csapi/kicking.cpp index 39125f42..7de5ce01 100644 --- a/lib/csapi/kicking.cpp +++ b/lib/csapi/kicking.cpp @@ -4,21 +4,17 @@ #include "kicking.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - KickJob::KickJob(const QString& roomId, const QString& userId, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("KickJob"), - basePath % "/rooms/" % roomId % "/kick") + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/kick") { QJsonObject _data; addParam<>(_data, QStringLiteral("user_id"), userId); addParam<IfNotEmpty>(_data, QStringLiteral("reason"), reason); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/kicking.h b/lib/csapi/kicking.h index 0633a7f6..594f5845 100644 --- a/lib/csapi/kicking.h +++ b/lib/csapi/kicking.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Kick a user from the room. * * Kick a user from the room. @@ -26,10 +24,13 @@ class KickJob : public BaseJob { public: /*! \brief Kick a user from the room. * + * * \param roomId * The room identifier (not alias) from which the user should be kicked. + * * \param userId * The fully qualified user ID of the user being kicked. + * * \param reason * The reason the user has been kicked. This will be supplied as the * ``reason`` on the target's updated `m.room.member`_ event. diff --git a/lib/csapi/leaving.cpp b/lib/csapi/leaving.cpp index 2fa1da56..8bd170bf 100644 --- a/lib/csapi/leaving.cpp +++ b/lib/csapi/leaving.cpp @@ -4,32 +4,32 @@ #include "leaving.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - QUrl LeaveRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/rooms/" % roomId % "/leave"); + QStringLiteral("/_matrix/client/r0") + % "/rooms/" % roomId % "/leave"); } LeaveRoomJob::LeaveRoomJob(const QString& roomId) : BaseJob(HttpVerb::Post, QStringLiteral("LeaveRoomJob"), - basePath % "/rooms/" % roomId % "/leave") + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/leave") {} QUrl ForgetRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/rooms/" % roomId % "/forget"); + QStringLiteral("/_matrix/client/r0") + % "/rooms/" % roomId % "/forget"); } ForgetRoomJob::ForgetRoomJob(const QString& roomId) : BaseJob(HttpVerb::Post, QStringLiteral("ForgetRoomJob"), - basePath % "/rooms/" % roomId % "/forget") + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/forget") {} diff --git a/lib/csapi/leaving.h b/lib/csapi/leaving.h index 45b983f3..0a4c401c 100644 --- a/lib/csapi/leaving.h +++ b/lib/csapi/leaving.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Stop the requesting user participating in a particular room. * * This API stops a user participating in a particular room. @@ -28,6 +26,7 @@ class LeaveRoomJob : public BaseJob { public: /*! \brief Stop the requesting user participating in a particular room. * + * * \param roomId * The room identifier to leave. */ @@ -57,6 +56,7 @@ class ForgetRoomJob : public BaseJob { public: /*! \brief Stop the requesting user remembering about a particular room. * + * * \param roomId * The room identifier to forget. */ diff --git a/lib/csapi/list_joined_rooms.cpp b/lib/csapi/list_joined_rooms.cpp index 34eb0d42..8d7e267f 100644 --- a/lib/csapi/list_joined_rooms.cpp +++ b/lib/csapi/list_joined_rooms.cpp @@ -4,45 +4,20 @@ #include "list_joined_rooms.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class GetJoinedRoomsJob::Private { -public: - QStringList joinedRooms; -}; - QUrl GetJoinedRoomsJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/joined_rooms"); + QStringLiteral("/_matrix/client/r0") + % "/joined_rooms"); } GetJoinedRoomsJob::GetJoinedRoomsJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetJoinedRoomsJob"), - basePath % "/joined_rooms") - , d(new Private) -{} - -GetJoinedRoomsJob::~GetJoinedRoomsJob() = default; - -const QStringList& GetJoinedRoomsJob::joinedRooms() const + QStringLiteral("/_matrix/client/r0") % "/joined_rooms") { - return d->joinedRooms; -} - -BaseJob::Status GetJoinedRoomsJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("joined_rooms"_ls)) - return { IncorrectResponse, - "The key 'joined_rooms' not found in the response" }; - fromJson(json.value("joined_rooms"_ls), d->joinedRooms); - - return Success; + addExpectedKey("joined_rooms"); } diff --git a/lib/csapi/list_joined_rooms.h b/lib/csapi/list_joined_rooms.h index a170d623..1034aa7b 100644 --- a/lib/csapi/list_joined_rooms.h +++ b/lib/csapi/list_joined_rooms.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Lists the user's current rooms. * * This API returns a list of the user's current rooms. @@ -25,19 +23,14 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetJoinedRoomsJob() override; // Result properties /// The ID of each room in which the user has ``joined`` membership. - const QStringList& joinedRooms() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QStringList joinedRooms() const + { + return loadFromJson<QStringList>("joined_rooms"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/list_public_rooms.cpp b/lib/csapi/list_public_rooms.cpp index 83320ec0..cdc5c716 100644 --- a/lib/csapi/list_public_rooms.cpp +++ b/lib/csapi/list_public_rooms.cpp @@ -4,66 +4,39 @@ #include "list_public_rooms.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class GetRoomVisibilityOnDirectoryJob::Private { -public: - QString visibility; -}; - QUrl GetRoomVisibilityOnDirectoryJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/directory/list/room/" % roomId); + QStringLiteral("/_matrix/client/r0") + % "/directory/list/room/" % roomId); } GetRoomVisibilityOnDirectoryJob::GetRoomVisibilityOnDirectoryJob( const QString& roomId) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomVisibilityOnDirectoryJob"), - basePath % "/directory/list/room/" % roomId, false) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/directory/list/room/" + % roomId, + false) {} -GetRoomVisibilityOnDirectoryJob::~GetRoomVisibilityOnDirectoryJob() = default; - -const QString& GetRoomVisibilityOnDirectoryJob::visibility() const -{ - return d->visibility; -} - -BaseJob::Status -GetRoomVisibilityOnDirectoryJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("visibility"_ls), d->visibility); - - return Success; -} - SetRoomVisibilityOnDirectoryJob::SetRoomVisibilityOnDirectoryJob( const QString& roomId, const QString& visibility) : BaseJob(HttpVerb::Put, QStringLiteral("SetRoomVisibilityOnDirectoryJob"), - basePath % "/directory/list/room/" % roomId) + QStringLiteral("/_matrix/client/r0") % "/directory/list/room/" + % roomId) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("visibility"), visibility); - setRequestData(_data); + setRequestData(std::move(_data)); } -class GetPublicRoomsJob::Private { -public: - PublicRoomsResponse data; -}; - -BaseJob::Query queryToGetPublicRooms(Omittable<int> limit, const QString& since, - const QString& server) +auto queryToGetPublicRooms(Omittable<int> limit, const QString& since, + const QString& server) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("limit"), limit); @@ -76,49 +49,20 @@ QUrl GetPublicRoomsJob::makeRequestUrl(QUrl baseUrl, Omittable<int> limit, const QString& since, const QString& server) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/publicRooms", + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/publicRooms", queryToGetPublicRooms(limit, since, server)); } GetPublicRoomsJob::GetPublicRoomsJob(Omittable<int> limit, const QString& since, const QString& server) : BaseJob(HttpVerb::Get, QStringLiteral("GetPublicRoomsJob"), - basePath % "/publicRooms", + QStringLiteral("/_matrix/client/r0") % "/publicRooms", queryToGetPublicRooms(limit, since, server), {}, false) - , d(new Private) {} -GetPublicRoomsJob::~GetPublicRoomsJob() = default; - -const PublicRoomsResponse& GetPublicRoomsJob::data() const { return d->data; } - -BaseJob::Status GetPublicRoomsJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<QueryPublicRoomsJob::Filter> { - static void dumpTo(QJsonObject& jo, const QueryPublicRoomsJob::Filter& pod) - { - addParam<IfNotEmpty>(jo, QStringLiteral("generic_search_term"), - pod.genericSearchTerm); - } -}; - -} // namespace Quotient - -class QueryPublicRoomsJob::Private { -public: - PublicRoomsResponse data; -}; - -BaseJob::Query queryToQueryPublicRooms(const QString& server) +auto queryToQueryPublicRooms(const QString& server) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("server"), server); @@ -132,8 +76,8 @@ QueryPublicRoomsJob::QueryPublicRoomsJob(const QString& server, Omittable<bool> includeAllNetworks, const QString& thirdPartyInstanceId) : BaseJob(HttpVerb::Post, QStringLiteral("QueryPublicRoomsJob"), - basePath % "/publicRooms", queryToQueryPublicRooms(server)) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/publicRooms", + queryToQueryPublicRooms(server)) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("limit"), limit); @@ -143,16 +87,6 @@ QueryPublicRoomsJob::QueryPublicRoomsJob(const QString& server, includeAllNetworks); addParam<IfNotEmpty>(_data, QStringLiteral("third_party_instance_id"), thirdPartyInstanceId); - setRequestData(_data); -} - -QueryPublicRoomsJob::~QueryPublicRoomsJob() = default; - -const PublicRoomsResponse& QueryPublicRoomsJob::data() const { return d->data; } - -BaseJob::Status QueryPublicRoomsJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; + setRequestData(std::move(_data)); + addExpectedKey("chunk"); } diff --git a/lib/csapi/list_public_rooms.h b/lib/csapi/list_public_rooms.h index 0c9a2620..5ef7c23e 100644 --- a/lib/csapi/list_public_rooms.h +++ b/lib/csapi/list_public_rooms.h @@ -4,16 +4,12 @@ #pragma once -#include "converters.h" - #include "csapi/definitions/public_rooms_response.h" #include "jobs/basejob.h" namespace Quotient { -// Operations - /*! \brief Gets the visibility of a room in the directory * * Gets the visibility of a given room on the server's public room directory. @@ -22,6 +18,7 @@ class GetRoomVisibilityOnDirectoryJob : public BaseJob { public: /*! \brief Gets the visibility of a room in the directory * + * * \param roomId * The room ID. */ @@ -33,19 +30,14 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); - ~GetRoomVisibilityOnDirectoryJob() override; // Result properties /// The visibility of the room in the directory. - const QString& visibility() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString visibility() const + { + return loadFromJson<QString>("visibility"_ls); + } }; /*! \brief Sets the visibility of a room in the room directory @@ -61,8 +53,10 @@ class SetRoomVisibilityOnDirectoryJob : public BaseJob { public: /*! \brief Sets the visibility of a room in the room directory * + * * \param roomId * The room ID. + * * \param visibility * The new visibility setting for the room. * Defaults to 'public'. @@ -82,13 +76,16 @@ class GetPublicRoomsJob : public BaseJob { public: /*! \brief Lists the public rooms on the server. * + * * \param limit * Limit the number of results returned. + * * \param since * A pagination token from a previous request, allowing clients to * get the next (or previous) batch of rooms. * The direction of pagination is specified solely by which token * is supplied, rather than via an explicit flag. + * * \param server * The server to fetch the public room lists from. Defaults to the * local server. @@ -105,19 +102,14 @@ public: static QUrl makeRequestUrl(QUrl baseUrl, Omittable<int> limit = none, const QString& since = {}, const QString& server = {}); - ~GetPublicRoomsJob() override; // Result properties /// A list of the rooms on the server. - const PublicRoomsResponse& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + PublicRoomsResponse data() const + { + return fromJson<PublicRoomsResponse>(jsonData()); + } }; /*! \brief Lists the public rooms on the server with optional filter. @@ -138,25 +130,58 @@ public: QString genericSearchTerm; }; + /// Lists the public rooms on the server, with optional filter. + /// + /// This API returns paginated responses. The rooms are ordered by the + /// number of joined members, with the largest rooms first. + struct PublicRoomsChunk { + /// Aliases of the room. May be empty. + QStringList aliases; + /// The canonical alias of the room, if any. + QString canonicalAlias; + /// The name of the room, if any. + QString name; + /// The number of members joined to the room. + int numJoinedMembers; + /// The ID of the room. + QString roomId; + /// The topic of the room, if any. + QString topic; + /// Whether the room may be viewed by guest users without joining. + bool worldReadable; + /// Whether guest users may join the room and participate in it. + /// If they can, they will be subject to ordinary power level + /// rules like any other user. + bool guestCanJoin; + /// The URL for the room's avatar, if one is set. + QString avatarUrl; + }; + // Construction/destruction /*! \brief Lists the public rooms on the server with optional filter. * + * * \param server * The server to fetch the public room lists from. Defaults to the * local server. + * * \param limit * Limit the number of results returned. + * * \param since * A pagination token from a previous request, allowing clients * to get the next (or previous) batch of rooms. The direction * of pagination is specified solely by which token is supplied, * rather than via an explicit flag. + * * \param filter * Filter to apply to the results. + * * \param includeAllNetworks * Whether or not to include all known networks/protocols from * application services on the homeserver. Defaults to false. + * * \param thirdPartyInstanceId * The specific third party network/protocol to request from the * homeserver. Can only be used if ``include_all_networks`` is false. @@ -168,19 +193,56 @@ public: Omittable<bool> includeAllNetworks = none, const QString& thirdPartyInstanceId = {}); - ~QueryPublicRoomsJob() override; - // Result properties - /// A list of the rooms on the server. - const PublicRoomsResponse& data() const; + /// A paginated chunk of public rooms. + QVector<PublicRoomsChunk> chunk() const + { + return loadFromJson<QVector<PublicRoomsChunk>>("chunk"_ls); + } + + /// A pagination token for the response. The absence of this token + /// means there are no more results to fetch and the client should + /// stop paginating. + QString nextBatch() const { return loadFromJson<QString>("next_batch"_ls); } + + /// A pagination token that allows fetching previous results. The + /// absence of this token means there are no results before this + /// batch, i.e. this is the first batch. + QString prevBatch() const { return loadFromJson<QString>("prev_batch"_ls); } + + /// An estimate on the total number of public rooms, if the + /// server has an estimate. + Omittable<int> totalRoomCountEstimate() const + { + return loadFromJson<Omittable<int>>("total_room_count_estimate"_ls); + } +}; -protected: - Status parseJson(const QJsonDocument& data) override; +template <> +struct JsonObjectConverter<QueryPublicRoomsJob::Filter> { + static void dumpTo(QJsonObject& jo, const QueryPublicRoomsJob::Filter& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("generic_search_term"), + pod.genericSearchTerm); + } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter<QueryPublicRoomsJob::PublicRoomsChunk> { + static void fillFrom(const QJsonObject& jo, + QueryPublicRoomsJob::PublicRoomsChunk& result) + { + fromJson(jo.value("aliases"_ls), result.aliases); + fromJson(jo.value("canonical_alias"_ls), result.canonicalAlias); + fromJson(jo.value("name"_ls), result.name); + fromJson(jo.value("num_joined_members"_ls), result.numJoinedMembers); + fromJson(jo.value("room_id"_ls), result.roomId); + fromJson(jo.value("topic"_ls), result.topic); + fromJson(jo.value("world_readable"_ls), result.worldReadable); + fromJson(jo.value("guest_can_join"_ls), result.guestCanJoin); + fromJson(jo.value("avatar_url"_ls), result.avatarUrl); + } }; } // namespace Quotient diff --git a/lib/csapi/login.cpp b/lib/csapi/login.cpp index 3e98f56b..a5bac9ea 100644 --- a/lib/csapi/login.cpp +++ b/lib/csapi/login.cpp @@ -4,77 +4,29 @@ #include "login.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<GetLoginFlowsJob::LoginFlow> { - static void fillFrom(const QJsonObject& jo, - GetLoginFlowsJob::LoginFlow& result) - { - fromJson(jo.value("type"_ls), result.type); - } -}; - -} // namespace Quotient - -class GetLoginFlowsJob::Private { -public: - QVector<LoginFlow> flows; -}; - QUrl GetLoginFlowsJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/login"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/login"); } GetLoginFlowsJob::GetLoginFlowsJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetLoginFlowsJob"), - basePath % "/login", false) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/login", false) {} -GetLoginFlowsJob::~GetLoginFlowsJob() = default; - -const QVector<GetLoginFlowsJob::LoginFlow>& GetLoginFlowsJob::flows() const -{ - return d->flows; -} - -BaseJob::Status GetLoginFlowsJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("flows"_ls), d->flows); - - return Success; -} - -class LoginJob::Private { -public: - QString userId; - QString accessToken; - QString homeServer; - QString deviceId; - Omittable<DiscoveryInformation> wellKnown; -}; - LoginJob::LoginJob(const QString& type, const Omittable<UserIdentifier>& identifier, const QString& password, const QString& token, const QString& deviceId, - const QString& initialDeviceDisplayName, const QString& user, - const QString& medium, const QString& address) - : BaseJob(HttpVerb::Post, QStringLiteral("LoginJob"), basePath % "/login", - false) - , d(new Private) + const QString& initialDeviceDisplayName) + : BaseJob(HttpVerb::Post, QStringLiteral("LoginJob"), + QStringLiteral("/_matrix/client/r0") % "/login", false) { QJsonObject _data; addParam<>(_data, QStringLiteral("type"), type); @@ -84,35 +36,5 @@ LoginJob::LoginJob(const QString& type, addParam<IfNotEmpty>(_data, QStringLiteral("device_id"), deviceId); addParam<IfNotEmpty>(_data, QStringLiteral("initial_device_display_name"), initialDeviceDisplayName); - addParam<IfNotEmpty>(_data, QStringLiteral("user"), user); - addParam<IfNotEmpty>(_data, QStringLiteral("medium"), medium); - addParam<IfNotEmpty>(_data, QStringLiteral("address"), address); - setRequestData(_data); -} - -LoginJob::~LoginJob() = default; - -const QString& LoginJob::userId() const { return d->userId; } - -const QString& LoginJob::accessToken() const { return d->accessToken; } - -const QString& LoginJob::homeServer() const { return d->homeServer; } - -const QString& LoginJob::deviceId() const { return d->deviceId; } - -const Omittable<DiscoveryInformation>& LoginJob::wellKnown() const -{ - return d->wellKnown; -} - -BaseJob::Status LoginJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("user_id"_ls), d->userId); - fromJson(json.value("access_token"_ls), d->accessToken); - fromJson(json.value("home_server"_ls), d->homeServer); - fromJson(json.value("device_id"_ls), d->deviceId); - fromJson(json.value("well_known"_ls), d->wellKnown); - - return Success; + setRequestData(std::move(_data)); } diff --git a/lib/csapi/login.h b/lib/csapi/login.h index adecca6f..0483b77c 100644 --- a/lib/csapi/login.h +++ b/lib/csapi/login.h @@ -4,19 +4,13 @@ #pragma once -#include "converters.h" - #include "csapi/definitions/user_identifier.h" #include "csapi/definitions/wellknown/full.h" #include "jobs/basejob.h" -#include <QtCore/QVector> - namespace Quotient { -// Operations - /*! \brief Get the supported login types to authenticate users * * Gets the homeserver's supported login types to authenticate users. Clients @@ -46,19 +40,23 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetLoginFlowsJob() override; // Result properties /// The homeserver's supported login types - const QVector<LoginFlow>& flows() const; - -protected: - Status parseJson(const QJsonDocument& data) override; + QVector<LoginFlow> flows() const + { + return loadFromJson<QVector<LoginFlow>>("flows"_ls); + } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter<GetLoginFlowsJob::LoginFlow> { + static void fillFrom(const QJsonObject& jo, + GetLoginFlowsJob::LoginFlow& result) + { + fromJson(jo.value("type"_ls), result.type); + } }; /*! \brief Authenticates the user. @@ -78,45 +76,47 @@ class LoginJob : public BaseJob { public: /*! \brief Authenticates the user. * + * * \param type * The login type being used. + * * \param identifier * Identification information for the user. + * * \param password * Required when ``type`` is ``m.login.password``. The user's * password. + * * \param token * Required when ``type`` is ``m.login.token``. Part of `Token-based`_ - * login. \param deviceId ID of the client device. If this does not - * correspond to a known client device, a new device will be created. The - * server will auto-generate a device_id if this is not specified. \param - * initialDeviceDisplayName A display name to assign to the newly-created - * device. Ignored if ``device_id`` corresponds to a known device. \param - * user The fully qualified user ID or just local part of the user ID, to - * log in. Deprecated in favour of ``identifier``. \param medium When - * logging in using a third party identifier, the medium of the identifier. - * Must be 'email'. Deprecated in favour of ``identifier``. \param address - * Third party identifier for the user. Deprecated in favour of - * ``identifier``. + * login. + * + * \param deviceId + * ID of the client device. If this does not correspond to a + * known client device, a new device will be created. The server + * will auto-generate a device_id if this is not specified. + * + * \param initialDeviceDisplayName + * A display name to assign to the newly-created device. Ignored + * if ``device_id`` corresponds to a known device. */ explicit LoginJob(const QString& type, const Omittable<UserIdentifier>& identifier = none, const QString& password = {}, const QString& token = {}, const QString& deviceId = {}, - const QString& initialDeviceDisplayName = {}, - const QString& user = {}, const QString& medium = {}, - const QString& address = {}); - - ~LoginJob() override; + const QString& initialDeviceDisplayName = {}); // Result properties /// The fully-qualified Matrix ID that has been registered. - const QString& userId() const; + QString userId() const { return loadFromJson<QString>("user_id"_ls); } /// An access token for the account. /// This access token can then be used to authorize other requests. - const QString& accessToken() const; + QString accessToken() const + { + return loadFromJson<QString>("access_token"_ls); + } /// The server_name of the homeserver on which the account has /// been registered. @@ -124,24 +124,23 @@ public: /// **Deprecated**. Clients should extract the server_name from /// ``user_id`` (by splitting at the first colon) if they require /// it. Note also that ``homeserver`` is not spelt this way. - const QString& homeServer() const; + QString homeServer() const + { + return loadFromJson<QString>("home_server"_ls); + } /// ID of the logged-in device. Will be the same as the /// corresponding parameter in the request, if one was specified. - const QString& deviceId() const; + QString deviceId() const { return loadFromJson<QString>("device_id"_ls); } /// Optional client configuration provided by the server. If present, /// clients SHOULD use the provided object to reconfigure themselves, /// optionally validating the URLs within. This object takes the same /// form as the one returned from .well-known autodiscovery. - const Omittable<DiscoveryInformation>& wellKnown() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + Omittable<DiscoveryInformation> wellKnown() const + { + return loadFromJson<Omittable<DiscoveryInformation>>("well_known"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/logout.cpp b/lib/csapi/logout.cpp index 36281b79..9583b8ec 100644 --- a/lib/csapi/logout.cpp +++ b/lib/csapi/logout.cpp @@ -4,29 +4,30 @@ #include "logout.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - QUrl LogoutJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/logout"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/logout"); } LogoutJob::LogoutJob() - : BaseJob(HttpVerb::Post, QStringLiteral("LogoutJob"), basePath % "/logout") + : BaseJob(HttpVerb::Post, QStringLiteral("LogoutJob"), + QStringLiteral("/_matrix/client/r0") % "/logout") {} QUrl LogoutAllJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/logout/all"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/logout/all"); } LogoutAllJob::LogoutAllJob() : BaseJob(HttpVerb::Post, QStringLiteral("LogoutAllJob"), - basePath % "/logout/all") + QStringLiteral("/_matrix/client/r0") % "/logout/all") {} diff --git a/lib/csapi/logout.h b/lib/csapi/logout.h index 2a705620..e9a7ef89 100644 --- a/lib/csapi/logout.h +++ b/lib/csapi/logout.h @@ -8,12 +8,11 @@ namespace Quotient { -// Operations - /*! \brief Invalidates a user access token * * Invalidates an existing access token, so that it can no longer be used for - * authorization. + * authorization. The device associated with the access token is also deleted. + * `Device keys <#device-keys>`_ for the device are deleted alongside the device. */ class LogoutJob : public BaseJob { public: @@ -31,7 +30,9 @@ public: /*! \brief Invalidates all access tokens for a user * * Invalidates all access tokens for a user, so that they can no longer be used - * for authorization. This includes the access token that made this request. + * for authorization. This includes the access token that made this request. All + * devices for the user are also deleted. `Device keys <#device-keys>`_ for the + * device are deleted alongside the device. * * This endpoint does not require UI authorization because UI authorization is * designed to protect against attacks where the someone gets hold of a single diff --git a/lib/csapi/message_pagination.cpp b/lib/csapi/message_pagination.cpp index ba982748..855c051f 100644 --- a/lib/csapi/message_pagination.cpp +++ b/lib/csapi/message_pagination.cpp @@ -4,24 +4,13 @@ #include "message_pagination.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class GetRoomEventsJob::Private { -public: - QString begin; - QString end; - RoomEvents chunk; -}; - -BaseJob::Query queryToGetRoomEvents(const QString& from, const QString& to, - const QString& dir, Omittable<int> limit, - const QString& filter) +auto queryToGetRoomEvents(const QString& from, const QString& to, + const QString& dir, Omittable<int> limit, + const QString& filter) { BaseJob::Query _q; addParam<>(_q, QStringLiteral("from"), from); @@ -37,35 +26,17 @@ QUrl GetRoomEventsJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& to, Omittable<int> limit, const QString& filter) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/rooms/" % roomId % "/messages", - queryToGetRoomEvents(from, to, dir, limit, - filter)); + return BaseJob::makeRequestUrl( + std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/messages", + queryToGetRoomEvents(from, to, dir, limit, filter)); } GetRoomEventsJob::GetRoomEventsJob(const QString& roomId, const QString& from, const QString& dir, const QString& to, Omittable<int> limit, const QString& filter) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomEventsJob"), - basePath % "/rooms/" % roomId % "/messages", + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/messages", queryToGetRoomEvents(from, to, dir, limit, filter)) - , d(new Private) {} - -GetRoomEventsJob::~GetRoomEventsJob() = default; - -const QString& GetRoomEventsJob::begin() const { return d->begin; } - -const QString& GetRoomEventsJob::end() const { return d->end; } - -RoomEvents&& GetRoomEventsJob::chunk() { return std::move(d->chunk); } - -BaseJob::Status GetRoomEventsJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("start"_ls), d->begin); - fromJson(json.value("end"_ls), d->end); - fromJson(json.value("chunk"_ls), d->chunk); - - return Success; -} diff --git a/lib/csapi/message_pagination.h b/lib/csapi/message_pagination.h index b0d95bad..e9f9e6d4 100644 --- a/lib/csapi/message_pagination.h +++ b/lib/csapi/message_pagination.h @@ -4,40 +4,46 @@ #pragma once -#include "converters.h" - #include "events/eventloader.h" #include "jobs/basejob.h" namespace Quotient { -// Operations - /*! \brief Get a list of events for this room * * This API returns a list of message and state events for a room. It uses * pagination query parameters to paginate history in the room. + * + * *Note*: This endpoint supports lazy-loading of room member events. See + * `Lazy-loading room members <#lazy-loading-room-members>`_ for more + * information. */ class GetRoomEventsJob : public BaseJob { public: /*! \brief Get a list of events for this room * + * * \param roomId * The room to get events from. + * * \param from * The token to start returning events from. This token can be obtained * from a ``prev_batch`` token returned for each room by the sync API, * or from a ``start`` or ``end`` token returned by a previous request * to this endpoint. + * * \param dir * The direction to return events from. + * * \param to * The token to stop returning events at. This token can be obtained from * a ``prev_batch`` token returned for each room by the sync endpoint, * or from a ``start`` or ``end`` token returned by a previous request to * this endpoint. + * * \param limit * The maximum number of events to return. Default: 10. + * * \param filter * A JSON RoomEventFilter to filter returned events with. */ @@ -56,27 +62,32 @@ public: const QString& to = {}, Omittable<int> limit = none, const QString& filter = {}); - ~GetRoomEventsJob() override; // Result properties /// The token the pagination starts from. If ``dir=b`` this will be /// the token supplied in ``from``. - const QString& begin() const; + QString begin() const { return loadFromJson<QString>("start"_ls); } /// The token the pagination ends at. If ``dir=b`` this token should /// be used again to request even earlier events. - const QString& end() const; - - /// A list of room events. - RoomEvents&& chunk(); + QString end() const { return loadFromJson<QString>("end"_ls); } -protected: - Status parseJson(const QJsonDocument& data) override; + /// A list of room events. The order depends on the ``dir`` parameter. + /// For ``dir=b`` events will be in reverse-chronological order, + /// for ``dir=f`` in chronological order, so that events start + /// at the ``from`` point. + RoomEvents chunk() { return takeFromJson<RoomEvents>("chunk"_ls); } -private: - class Private; - QScopedPointer<Private> d; + /// A list of state events relevant to showing the ``chunk``. For example, if + /// ``lazy_load_members`` is enabled in the filter then this may contain + /// the membership events for the senders of events in the ``chunk``. + /// + /// Unless ``include_redundant_members`` is ``true``, the server + /// may remove membership events which would have already been + /// sent to the client in prior calls to this endpoint, assuming + /// the membership of those members has not changed. + StateEvents state() { return takeFromJson<StateEvents>("state"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/notifications.cpp b/lib/csapi/notifications.cpp index d00d5425..a479d500 100644 --- a/lib/csapi/notifications.cpp +++ b/lib/csapi/notifications.cpp @@ -4,41 +4,12 @@ #include "notifications.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<GetNotificationsJob::Notification> { - static void fillFrom(const QJsonObject& jo, - GetNotificationsJob::Notification& result) - { - fromJson(jo.value("actions"_ls), result.actions); - fromJson(jo.value("event"_ls), result.event); - fromJson(jo.value("profile_tag"_ls), result.profileTag); - fromJson(jo.value("read"_ls), result.read); - fromJson(jo.value("room_id"_ls), result.roomId); - fromJson(jo.value("ts"_ls), result.ts); - } -}; - -} // namespace Quotient - -class GetNotificationsJob::Private { -public: - QString nextToken; - std::vector<Notification> notifications; -}; - -BaseJob::Query queryToGetNotifications(const QString& from, - Omittable<int> limit, const QString& only) +auto queryToGetNotifications(const QString& from, Omittable<int> limit, + const QString& only) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("from"), from); @@ -52,7 +23,8 @@ QUrl GetNotificationsJob::makeRequestUrl(QUrl baseUrl, const QString& from, const QString& only) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/notifications", + QStringLiteral("/_matrix/client/r0") + % "/notifications", queryToGetNotifications(from, limit, only)); } @@ -60,29 +32,8 @@ GetNotificationsJob::GetNotificationsJob(const QString& from, Omittable<int> limit, const QString& only) : BaseJob(HttpVerb::Get, QStringLiteral("GetNotificationsJob"), - basePath % "/notifications", + QStringLiteral("/_matrix/client/r0") % "/notifications", queryToGetNotifications(from, limit, only)) - , d(new Private) -{} - -GetNotificationsJob::~GetNotificationsJob() = default; - -const QString& GetNotificationsJob::nextToken() const { return d->nextToken; } - -std::vector<GetNotificationsJob::Notification>&& -GetNotificationsJob::notifications() -{ - return std::move(d->notifications); -} - -BaseJob::Status GetNotificationsJob::parseJson(const QJsonDocument& data) { - auto json = data.object(); - fromJson(json.value("next_token"_ls), d->nextToken); - if (!json.contains("notifications"_ls)) - return { IncorrectResponse, - "The key 'notifications' not found in the response" }; - fromJson(json.value("notifications"_ls), d->notifications); - - return Success; + addExpectedKey("notifications"); } diff --git a/lib/csapi/notifications.h b/lib/csapi/notifications.h index 0e86e424..095e9325 100644 --- a/lib/csapi/notifications.h +++ b/lib/csapi/notifications.h @@ -4,19 +4,11 @@ #pragma once -#include "converters.h" - #include "events/eventloader.h" #include "jobs/basejob.h" -#include <QtCore/QJsonObject> -#include <QtCore/QVariant> -#include <QtCore/QVector> - namespace Quotient { -// Operations - /*! \brief Gets a list of events that the user has been notified about * * This API is used to paginate through the list of events that the @@ -50,10 +42,13 @@ public: /*! \brief Gets a list of events that the user has been notified about * + * * \param from * Pagination token given to retrieve the next set of events. + * * \param limit * Limit on the number of events to return in this request. + * * \param only * Allows basic filtering of events returned. Supply ``highlight`` * to return only events where the notification had the highlight @@ -71,24 +66,33 @@ public: static QUrl makeRequestUrl(QUrl baseUrl, const QString& from = {}, Omittable<int> limit = none, const QString& only = {}); - ~GetNotificationsJob() override; // Result properties /// The token to supply in the ``from`` param of the next /// ``/notifications`` request in order to request more /// events. If this is absent, there are no more results. - const QString& nextToken() const; + QString nextToken() const { return loadFromJson<QString>("next_token"_ls); } /// The list of events that triggered notifications. - std::vector<Notification>&& notifications(); - -protected: - Status parseJson(const QJsonDocument& data) override; + std::vector<Notification> notifications() + { + return takeFromJson<std::vector<Notification>>("notifications"_ls); + } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter<GetNotificationsJob::Notification> { + static void fillFrom(const QJsonObject& jo, + GetNotificationsJob::Notification& result) + { + fromJson(jo.value("actions"_ls), result.actions); + fromJson(jo.value("event"_ls), result.event); + fromJson(jo.value("profile_tag"_ls), result.profileTag); + fromJson(jo.value("read"_ls), result.read); + fromJson(jo.value("room_id"_ls), result.roomId); + fromJson(jo.value("ts"_ls), result.ts); + } }; } // namespace Quotient diff --git a/lib/csapi/openid.cpp b/lib/csapi/openid.cpp index 9def2377..3941e9c0 100644 --- a/lib/csapi/openid.cpp +++ b/lib/csapi/openid.cpp @@ -4,66 +4,15 @@ #include "openid.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class RequestOpenIdTokenJob::Private { -public: - QString accessToken; - QString tokenType; - QString matrixServerName; - int expiresIn; -}; - RequestOpenIdTokenJob::RequestOpenIdTokenJob(const QString& userId, const QJsonObject& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestOpenIdTokenJob"), - basePath % "/user/" % userId % "/openid/request_token") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/user/" % userId + % "/openid/request_token") { setRequestData(Data(toJson(body))); } - -RequestOpenIdTokenJob::~RequestOpenIdTokenJob() = default; - -const QString& RequestOpenIdTokenJob::accessToken() const -{ - return d->accessToken; -} - -const QString& RequestOpenIdTokenJob::tokenType() const { return d->tokenType; } - -const QString& RequestOpenIdTokenJob::matrixServerName() const -{ - return d->matrixServerName; -} - -int RequestOpenIdTokenJob::expiresIn() const { return d->expiresIn; } - -BaseJob::Status RequestOpenIdTokenJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("access_token"_ls)) - return { IncorrectResponse, - "The key 'access_token' not found in the response" }; - fromJson(json.value("access_token"_ls), d->accessToken); - if (!json.contains("token_type"_ls)) - return { IncorrectResponse, - "The key 'token_type' not found in the response" }; - fromJson(json.value("token_type"_ls), d->tokenType); - if (!json.contains("matrix_server_name"_ls)) - return { IncorrectResponse, - "The key 'matrix_server_name' not found in the response" }; - fromJson(json.value("matrix_server_name"_ls), d->matrixServerName); - if (!json.contains("expires_in"_ls)) - return { IncorrectResponse, - "The key 'expires_in' not found in the response" }; - fromJson(json.value("expires_in"_ls), d->expiresIn); - - return Success; -} diff --git a/lib/csapi/openid.h b/lib/csapi/openid.h index bda4ce74..e69f9ad0 100644 --- a/lib/csapi/openid.h +++ b/lib/csapi/openid.h @@ -4,16 +4,12 @@ #pragma once -#include "converters.h" +#include "csapi/definitions/openid_token.h" #include "jobs/basejob.h" -#include <QtCore/QJsonObject> - namespace Quotient { -// Operations - /*! \brief Get an OpenID token object to verify the requester's identity. * * Gets an OpenID token object that the requester may supply to another @@ -29,41 +25,25 @@ class RequestOpenIdTokenJob : public BaseJob { public: /*! \brief Get an OpenID token object to verify the requester's identity. * + * * \param userId * The user to request and OpenID token for. Should be the user who * is authenticated for the request. + * * \param body * An empty object. Reserved for future expansion. */ explicit RequestOpenIdTokenJob(const QString& userId, const QJsonObject& body = {}); - ~RequestOpenIdTokenJob() override; - // Result properties - /// An access token the consumer may use to verify the identity of - /// the person who generated the token. This is given to the federation - /// API ``GET /openid/userinfo``. - const QString& accessToken() const; - - /// The string ``Bearer``. - const QString& tokenType() const; - - /// The homeserver domain the consumer should use when attempting to - /// verify the user's identity. - const QString& matrixServerName() const; - - /// The number of seconds before this token expires and a new one must - /// be generated. - int expiresIn() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + /// OpenID token information. This response is nearly compatible with the + /// response documented in the `OpenID 1.0 Specification + /// <http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse>`_ + /// with the only difference being the lack of an ``id_token``. Instead, + /// the Matrix homeserver's name is provided. + OpenidToken data() const { return fromJson<OpenidToken>(jsonData()); } }; } // namespace Quotient diff --git a/lib/csapi/peeking_events.cpp b/lib/csapi/peeking_events.cpp index 4d886812..70a5b6f3 100644 --- a/lib/csapi/peeking_events.cpp +++ b/lib/csapi/peeking_events.cpp @@ -4,23 +4,12 @@ #include "peeking_events.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class PeekEventsJob::Private { -public: - QString begin; - QString end; - RoomEvents chunk; -}; - -BaseJob::Query queryToPeekEvents(const QString& from, Omittable<int> timeout, - const QString& roomId) +auto queryToPeekEvents(const QString& from, Omittable<int> timeout, + const QString& roomId) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("from"), from); @@ -32,31 +21,15 @@ BaseJob::Query queryToPeekEvents(const QString& from, Omittable<int> timeout, QUrl PeekEventsJob::makeRequestUrl(QUrl baseUrl, const QString& from, Omittable<int> timeout, const QString& roomId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/events", + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/events", queryToPeekEvents(from, timeout, roomId)); } PeekEventsJob::PeekEventsJob(const QString& from, Omittable<int> timeout, const QString& roomId) : BaseJob(HttpVerb::Get, QStringLiteral("PeekEventsJob"), - basePath % "/events", queryToPeekEvents(from, timeout, roomId)) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/events", + queryToPeekEvents(from, timeout, roomId)) {} - -PeekEventsJob::~PeekEventsJob() = default; - -const QString& PeekEventsJob::begin() const { return d->begin; } - -const QString& PeekEventsJob::end() const { return d->end; } - -RoomEvents&& PeekEventsJob::chunk() { return std::move(d->chunk); } - -BaseJob::Status PeekEventsJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("start"_ls), d->begin); - fromJson(json.value("end"_ls), d->end); - fromJson(json.value("chunk"_ls), d->chunk); - - return Success; -} diff --git a/lib/csapi/peeking_events.h b/lib/csapi/peeking_events.h index 12a66a02..bfb9e24a 100644 --- a/lib/csapi/peeking_events.h +++ b/lib/csapi/peeking_events.h @@ -4,15 +4,11 @@ #pragma once -#include "converters.h" - #include "events/eventloader.h" #include "jobs/basejob.h" namespace Quotient { -// Operations - /*! \brief Listen on the event stream. * * This will listen for new events related to a particular room and return @@ -30,11 +26,14 @@ class PeekEventsJob : public BaseJob { public: /*! \brief Listen on the event stream. * + * * \param from * The token to stream from. This token is either from a previous * request to this API or from the initial sync API. + * * \param timeout * The maximum time in milliseconds to wait for an event. + * * \param roomId * The room ID for which events should be returned. */ @@ -50,27 +49,19 @@ public: static QUrl makeRequestUrl(QUrl baseUrl, const QString& from = {}, Omittable<int> timeout = none, const QString& roomId = {}); - ~PeekEventsJob() override; // Result properties /// A token which correlates to the first value in ``chunk``. This /// is usually the same token supplied to ``from=``. - const QString& begin() const; + QString begin() const { return loadFromJson<QString>("start"_ls); } /// A token which correlates to the last value in ``chunk``. This /// token should be used in the next request to ``/events``. - const QString& end() const; + QString end() const { return loadFromJson<QString>("end"_ls); } /// An array of events. - RoomEvents&& chunk(); - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + RoomEvents chunk() { return takeFromJson<RoomEvents>("chunk"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/presence.cpp b/lib/csapi/presence.cpp index 932ccc6e..58d0d157 100644 --- a/lib/csapi/presence.cpp +++ b/lib/csapi/presence.cpp @@ -4,71 +4,33 @@ #include "presence.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - SetPresenceJob::SetPresenceJob(const QString& userId, const QString& presence, const QString& statusMsg) : BaseJob(HttpVerb::Put, QStringLiteral("SetPresenceJob"), - basePath % "/presence/" % userId % "/status") + QStringLiteral("/_matrix/client/r0") % "/presence/" % userId + % "/status") { QJsonObject _data; addParam<>(_data, QStringLiteral("presence"), presence); addParam<IfNotEmpty>(_data, QStringLiteral("status_msg"), statusMsg); - setRequestData(_data); + setRequestData(std::move(_data)); } -class GetPresenceJob::Private { -public: - QString presence; - Omittable<int> lastActiveAgo; - QString statusMsg; - Omittable<bool> currentlyActive; -}; - QUrl GetPresenceJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/presence/" % userId % "/status"); + QStringLiteral("/_matrix/client/r0") + % "/presence/" % userId % "/status"); } GetPresenceJob::GetPresenceJob(const QString& userId) : BaseJob(HttpVerb::Get, QStringLiteral("GetPresenceJob"), - basePath % "/presence/" % userId % "/status") - , d(new Private) -{} - -GetPresenceJob::~GetPresenceJob() = default; - -const QString& GetPresenceJob::presence() const { return d->presence; } - -Omittable<int> GetPresenceJob::lastActiveAgo() const -{ - return d->lastActiveAgo; -} - -const QString& GetPresenceJob::statusMsg() const { return d->statusMsg; } - -Omittable<bool> GetPresenceJob::currentlyActive() const + QStringLiteral("/_matrix/client/r0") % "/presence/" % userId + % "/status") { - return d->currentlyActive; -} - -BaseJob::Status GetPresenceJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("presence"_ls)) - return { IncorrectResponse, - "The key 'presence' not found in the response" }; - fromJson(json.value("presence"_ls), d->presence); - fromJson(json.value("last_active_ago"_ls), d->lastActiveAgo); - fromJson(json.value("status_msg"_ls), d->statusMsg); - fromJson(json.value("currently_active"_ls), d->currentlyActive); - - return Success; + addExpectedKey("presence"); } diff --git a/lib/csapi/presence.h b/lib/csapi/presence.h index 21e57603..b34028e7 100644 --- a/lib/csapi/presence.h +++ b/lib/csapi/presence.h @@ -4,14 +4,10 @@ #pragma once -#include "converters.h" - #include "jobs/basejob.h" namespace Quotient { -// Operations - /*! \brief Update this user's presence state. * * This API sets the given user's presence state. When setting the status, @@ -23,10 +19,13 @@ class SetPresenceJob : public BaseJob { public: /*! \brief Update this user's presence state. * + * * \param userId * The user whose presence state to update. + * * \param presence * The new presence state. + * * \param statusMsg * The status message to attach to this state. */ @@ -42,6 +41,7 @@ class GetPresenceJob : public BaseJob { public: /*! \brief Get this user's presence state. * + * * \param userId * The user whose presence state to get. */ @@ -53,29 +53,27 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); - ~GetPresenceJob() override; // Result properties /// This user's presence. - const QString& presence() const; + QString presence() const { return loadFromJson<QString>("presence"_ls); } /// The length of time in milliseconds since an action was performed /// by this user. - Omittable<int> lastActiveAgo() const; + Omittable<int> lastActiveAgo() const + { + return loadFromJson<Omittable<int>>("last_active_ago"_ls); + } /// The state message for this user if one was set. - const QString& statusMsg() const; + QString statusMsg() const { return loadFromJson<QString>("status_msg"_ls); } /// Whether the user is currently active - Omittable<bool> currentlyActive() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + Omittable<bool> currentlyActive() const + { + return loadFromJson<Omittable<bool>>("currently_active"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/profile.cpp b/lib/csapi/profile.cpp index d86e3bdc..cb8f72be 100644 --- a/lib/csapi/profile.cpp +++ b/lib/csapi/profile.cpp @@ -4,122 +4,67 @@ #include "profile.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - SetDisplayNameJob::SetDisplayNameJob(const QString& userId, const QString& displayname) : BaseJob(HttpVerb::Put, QStringLiteral("SetDisplayNameJob"), - basePath % "/profile/" % userId % "/displayname") + QStringLiteral("/_matrix/client/r0") % "/profile/" % userId + % "/displayname") { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("displayname"), displayname); - setRequestData(_data); + setRequestData(std::move(_data)); } -class GetDisplayNameJob::Private { -public: - QString displayname; -}; - QUrl GetDisplayNameJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/profile/" - % userId - % "/displayname"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/profile/" % userId % "/displayname"); } GetDisplayNameJob::GetDisplayNameJob(const QString& userId) : BaseJob(HttpVerb::Get, QStringLiteral("GetDisplayNameJob"), - basePath % "/profile/" % userId % "/displayname", false) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/profile/" % userId + % "/displayname", + false) {} -GetDisplayNameJob::~GetDisplayNameJob() = default; - -const QString& GetDisplayNameJob::displayname() const { return d->displayname; } - -BaseJob::Status GetDisplayNameJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("displayname"_ls), d->displayname); - - return Success; -} - SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, const QString& avatarUrl) : BaseJob(HttpVerb::Put, QStringLiteral("SetAvatarUrlJob"), - basePath % "/profile/" % userId % "/avatar_url") + QStringLiteral("/_matrix/client/r0") % "/profile/" % userId + % "/avatar_url") { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("avatar_url"), avatarUrl); - setRequestData(_data); + setRequestData(std::move(_data)); } -class GetAvatarUrlJob::Private { -public: - QString avatarUrl; -}; - QUrl GetAvatarUrlJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/profile/" - % userId - % "/avatar_url"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/profile/" % userId % "/avatar_url"); } GetAvatarUrlJob::GetAvatarUrlJob(const QString& userId) : BaseJob(HttpVerb::Get, QStringLiteral("GetAvatarUrlJob"), - basePath % "/profile/" % userId % "/avatar_url", false) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/profile/" % userId + % "/avatar_url", + false) {} -GetAvatarUrlJob::~GetAvatarUrlJob() = default; - -const QString& GetAvatarUrlJob::avatarUrl() const { return d->avatarUrl; } - -BaseJob::Status GetAvatarUrlJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("avatar_url"_ls), d->avatarUrl); - - return Success; -} - -class GetUserProfileJob::Private { -public: - QString avatarUrl; - QString displayname; -}; - QUrl GetUserProfileJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/profile/" % userId); + QStringLiteral("/_matrix/client/r0") + % "/profile/" % userId); } GetUserProfileJob::GetUserProfileJob(const QString& userId) : BaseJob(HttpVerb::Get, QStringLiteral("GetUserProfileJob"), - basePath % "/profile/" % userId, false) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/profile/" % userId, false) {} - -GetUserProfileJob::~GetUserProfileJob() = default; - -const QString& GetUserProfileJob::avatarUrl() const { return d->avatarUrl; } - -const QString& GetUserProfileJob::displayname() const { return d->displayname; } - -BaseJob::Status GetUserProfileJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("avatar_url"_ls), d->avatarUrl); - fromJson(json.value("displayname"_ls), d->displayname); - - return Success; -} diff --git a/lib/csapi/profile.h b/lib/csapi/profile.h index 8279fe20..5b0d06d9 100644 --- a/lib/csapi/profile.h +++ b/lib/csapi/profile.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Set the user's display name. * * This API sets the given user's display name. You must have permission to @@ -19,8 +17,10 @@ class SetDisplayNameJob : public BaseJob { public: /*! \brief Set the user's display name. * + * * \param userId * The user whose display name to set. + * * \param displayname * The new display name for this user. */ @@ -38,6 +38,7 @@ class GetDisplayNameJob : public BaseJob { public: /*! \brief Get the user's display name. * + * * \param userId * The user whose display name to get. */ @@ -49,19 +50,14 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); - ~GetDisplayNameJob() override; // Result properties /// The user's display name if they have set one, otherwise not present. - const QString& displayname() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString displayname() const + { + return loadFromJson<QString>("displayname"_ls); + } }; /*! \brief Set the user's avatar URL. @@ -73,8 +69,10 @@ class SetAvatarUrlJob : public BaseJob { public: /*! \brief Set the user's avatar URL. * + * * \param userId * The user whose avatar URL to set. + * * \param avatarUrl * The new avatar URL for this user. */ @@ -92,6 +90,7 @@ class GetAvatarUrlJob : public BaseJob { public: /*! \brief Get the user's avatar URL. * + * * \param userId * The user whose avatar URL to get. */ @@ -103,19 +102,11 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); - ~GetAvatarUrlJob() override; // Result properties /// The user's avatar URL if they have set one, otherwise not present. - const QString& avatarUrl() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString avatarUrl() const { return loadFromJson<QString>("avatar_url"_ls); } }; /*! \brief Get this user's profile information. @@ -129,6 +120,7 @@ class GetUserProfileJob : public BaseJob { public: /*! \brief Get this user's profile information. * + * * \param userId * The user whose profile information to get. */ @@ -140,22 +132,17 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); - ~GetUserProfileJob() override; // Result properties /// The user's avatar URL if they have set one, otherwise not present. - const QString& avatarUrl() const; + QString avatarUrl() const { return loadFromJson<QString>("avatar_url"_ls); } /// The user's display name if they have set one, otherwise not present. - const QString& displayname() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString displayname() const + { + return loadFromJson<QString>("displayname"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/pusher.cpp b/lib/csapi/pusher.cpp index ad51b901..028022c5 100644 --- a/lib/csapi/pusher.cpp +++ b/lib/csapi/pusher.cpp @@ -4,96 +4,29 @@ #include "pusher.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<GetPushersJob::PusherData> { - static void fillFrom(const QJsonObject& jo, - GetPushersJob::PusherData& result) - { - fromJson(jo.value("url"_ls), result.url); - fromJson(jo.value("format"_ls), result.format); - } -}; - -template <> -struct JsonObjectConverter<GetPushersJob::Pusher> { - static void fillFrom(const QJsonObject& jo, GetPushersJob::Pusher& result) - { - fromJson(jo.value("pushkey"_ls), result.pushkey); - fromJson(jo.value("kind"_ls), result.kind); - fromJson(jo.value("app_id"_ls), result.appId); - fromJson(jo.value("app_display_name"_ls), result.appDisplayName); - fromJson(jo.value("device_display_name"_ls), result.deviceDisplayName); - fromJson(jo.value("profile_tag"_ls), result.profileTag); - fromJson(jo.value("lang"_ls), result.lang); - fromJson(jo.value("data"_ls), result.data); - } -}; - -} // namespace Quotient - -class GetPushersJob::Private { -public: - QVector<Pusher> pushers; -}; - QUrl GetPushersJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/pushers"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/pushers"); } GetPushersJob::GetPushersJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetPushersJob"), - basePath % "/pushers") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/pushers") {} -GetPushersJob::~GetPushersJob() = default; - -const QVector<GetPushersJob::Pusher>& GetPushersJob::pushers() const -{ - return d->pushers; -} - -BaseJob::Status GetPushersJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("pushers"_ls), d->pushers); - - return Success; -} - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<PostPusherJob::PusherData> { - static void dumpTo(QJsonObject& jo, const PostPusherJob::PusherData& pod) - { - addParam<IfNotEmpty>(jo, QStringLiteral("url"), pod.url); - addParam<IfNotEmpty>(jo, QStringLiteral("format"), pod.format); - } -}; - -} // namespace Quotient - PostPusherJob::PostPusherJob(const QString& pushkey, const QString& kind, const QString& appId, const QString& appDisplayName, const QString& deviceDisplayName, const QString& lang, const PusherData& data, const QString& profileTag, Omittable<bool> append) : BaseJob(HttpVerb::Post, QStringLiteral("PostPusherJob"), - basePath % "/pushers/set") + QStringLiteral("/_matrix/client/r0") % "/pushers/set") { QJsonObject _data; addParam<>(_data, QStringLiteral("pushkey"), pushkey); @@ -105,5 +38,5 @@ PostPusherJob::PostPusherJob(const QString& pushkey, const QString& kind, addParam<>(_data, QStringLiteral("lang"), lang); addParam<>(_data, QStringLiteral("data"), data); addParam<IfNotEmpty>(_data, QStringLiteral("append"), append); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/pusher.h b/lib/csapi/pusher.h index d60a2b56..40cd5796 100644 --- a/lib/csapi/pusher.h +++ b/lib/csapi/pusher.h @@ -4,16 +4,10 @@ #pragma once -#include "converters.h" - #include "jobs/basejob.h" -#include <QtCore/QVector> - namespace Quotient { -// Operations - /*! \brief Gets the current pushers for the authenticated user * * Gets all currently active pushers for the authenticated user. @@ -73,19 +67,39 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetPushersJob() override; // Result properties /// An array containing the current pushers for the user - const QVector<Pusher>& pushers() const; + QVector<Pusher> pushers() const + { + return loadFromJson<QVector<Pusher>>("pushers"_ls); + } +}; -protected: - Status parseJson(const QJsonDocument& data) override; +template <> +struct JsonObjectConverter<GetPushersJob::PusherData> { + static void fillFrom(const QJsonObject& jo, + GetPushersJob::PusherData& result) + { + fromJson(jo.value("url"_ls), result.url); + fromJson(jo.value("format"_ls), result.format); + } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter<GetPushersJob::Pusher> { + static void fillFrom(const QJsonObject& jo, GetPushersJob::Pusher& result) + { + fromJson(jo.value("pushkey"_ls), result.pushkey); + fromJson(jo.value("kind"_ls), result.kind); + fromJson(jo.value("app_id"_ls), result.appId); + fromJson(jo.value("app_display_name"_ls), result.appDisplayName); + fromJson(jo.value("device_display_name"_ls), result.deviceDisplayName); + fromJson(jo.value("profile_tag"_ls), result.profileTag); + fromJson(jo.value("lang"_ls), result.lang); + fromJson(jo.value("data"_ls), result.data); + } }; /*! \brief Modify a pusher for this user on the homeserver. @@ -118,6 +132,7 @@ public: /*! \brief Modify a pusher for this user on the homeserver. * + * * \param pushkey * This is a unique identifier for this pusher. The value you * should use for this is the routing or destination address @@ -128,10 +143,12 @@ public: * * If the ``kind`` is ``"email"``, this is the email address to * send notifications to. + * * \param kind * The kind of pusher to configure. ``"http"`` makes a pusher that * sends HTTP pokes. ``"email"`` makes a pusher that emails the * user with unread notifications. ``null`` deletes the pusher. + * * \param appId * This is a reverse-DNS style identifier for the application. * It is recommended that this end with the platform, such that @@ -139,22 +156,28 @@ public: * Max length, 64 chars. * * If the ``kind`` is ``"email"``, this is ``"m.email"``. + * * \param appDisplayName * A string that will allow the user to identify what application * owns this pusher. + * * \param deviceDisplayName * A string that will allow the user to identify what device owns * this pusher. + * * \param lang * The preferred language for receiving notifications (e.g. 'en' * or 'en-US'). + * * \param data * A dictionary of information for the pusher implementation * itself. If ``kind`` is ``http``, this should contain ``url`` * which is the URL to use to send notifications to. + * * \param profileTag * This string determines which set of device specific rules this * pusher executes. + * * \param append * If true, the homeserver should add another pusher with the * given pushkey and App ID in addition to any others with @@ -170,4 +193,13 @@ public: Omittable<bool> append = none); }; +template <> +struct JsonObjectConverter<PostPusherJob::PusherData> { + static void dumpTo(QJsonObject& jo, const PostPusherJob::PusherData& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("url"), pod.url); + addParam<IfNotEmpty>(jo, QStringLiteral("format"), pod.format); + } +}; + } // namespace Quotient diff --git a/lib/csapi/pushrules.cpp b/lib/csapi/pushrules.cpp index cd0fb02d..f2a16f1b 100644 --- a/lib/csapi/pushrules.cpp +++ b/lib/csapi/pushrules.cpp @@ -4,92 +4,58 @@ #include "pushrules.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class GetPushRulesJob::Private { -public: - PushRuleset global; -}; - QUrl GetPushRulesJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/pushrules"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/pushrules"); } GetPushRulesJob::GetPushRulesJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetPushRulesJob"), - basePath % "/pushrules") - , d(new Private) -{} - -GetPushRulesJob::~GetPushRulesJob() = default; - -const PushRuleset& GetPushRulesJob::global() const { return d->global; } - -BaseJob::Status GetPushRulesJob::parseJson(const QJsonDocument& data) + QStringLiteral("/_matrix/client/r0") % "/pushrules") { - auto json = data.object(); - if (!json.contains("global"_ls)) - return { IncorrectResponse, - "The key 'global' not found in the response" }; - fromJson(json.value("global"_ls), d->global); - - return Success; + addExpectedKey("global"); } -class GetPushRuleJob::Private { -public: - PushRule data; -}; - QUrl GetPushRuleJob::makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& kind, const QString& ruleId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/pushrules/" - % scope % "/" % kind - % "/" % ruleId); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/pushrules/" % scope % "/" % kind + % "/" % ruleId); } GetPushRuleJob::GetPushRuleJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, QStringLiteral("GetPushRuleJob"), - basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" + % kind % "/" % ruleId) {} -GetPushRuleJob::~GetPushRuleJob() = default; - -const PushRule& GetPushRuleJob::data() const { return d->data; } - -BaseJob::Status GetPushRuleJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} - QUrl DeletePushRuleJob::makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& kind, const QString& ruleId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/pushrules/" - % scope % "/" % kind - % "/" % ruleId); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/pushrules/" % scope % "/" % kind + % "/" % ruleId); } DeletePushRuleJob::DeletePushRuleJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Delete, QStringLiteral("DeletePushRuleJob"), - basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId) + QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" + % kind % "/" % ruleId) {} -BaseJob::Query queryToSetPushRule(const QString& before, const QString& after) +auto queryToSetPushRule(const QString& before, const QString& after) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("before"), before); @@ -103,27 +69,24 @@ SetPushRuleJob::SetPushRuleJob(const QString& scope, const QString& kind, const QVector<PushCondition>& conditions, const QString& pattern) : BaseJob(HttpVerb::Put, QStringLiteral("SetPushRuleJob"), - basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId, + QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" + % kind % "/" % ruleId, queryToSetPushRule(before, after)) { QJsonObject _data; addParam<>(_data, QStringLiteral("actions"), actions); addParam<IfNotEmpty>(_data, QStringLiteral("conditions"), conditions); addParam<IfNotEmpty>(_data, QStringLiteral("pattern"), pattern); - setRequestData(_data); + setRequestData(std::move(_data)); } -class IsPushRuleEnabledJob::Private { -public: - bool enabled; -}; - QUrl IsPushRuleEnabledJob::makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& kind, const QString& ruleId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/pushrules/" % scope % "/" % kind + QStringLiteral("/_matrix/client/r0") + % "/pushrules/" % scope % "/" % kind % "/" % ruleId % "/enabled"); } @@ -131,49 +94,31 @@ IsPushRuleEnabledJob::IsPushRuleEnabledJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, QStringLiteral("IsPushRuleEnabledJob"), - basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId - % "/enabled") - , d(new Private) -{} - -IsPushRuleEnabledJob::~IsPushRuleEnabledJob() = default; - -bool IsPushRuleEnabledJob::enabled() const { return d->enabled; } - -BaseJob::Status IsPushRuleEnabledJob::parseJson(const QJsonDocument& data) + QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" + % kind % "/" % ruleId % "/enabled") { - auto json = data.object(); - if (!json.contains("enabled"_ls)) - return { IncorrectResponse, - "The key 'enabled' not found in the response" }; - fromJson(json.value("enabled"_ls), d->enabled); - - return Success; + addExpectedKey("enabled"); } SetPushRuleEnabledJob::SetPushRuleEnabledJob(const QString& scope, const QString& kind, const QString& ruleId, bool enabled) : BaseJob(HttpVerb::Put, QStringLiteral("SetPushRuleEnabledJob"), - basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId - % "/enabled") + QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" + % kind % "/" % ruleId % "/enabled") { QJsonObject _data; addParam<>(_data, QStringLiteral("enabled"), enabled); - setRequestData(_data); + setRequestData(std::move(_data)); } -class GetPushRuleActionsJob::Private { -public: - QStringList actions; -}; - QUrl GetPushRuleActionsJob::makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& kind, const QString& ruleId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/pushrules/" % scope % "/" % kind + QStringLiteral("/_matrix/client/r0") + % "/pushrules/" % scope % "/" % kind % "/" % ruleId % "/actions"); } @@ -181,24 +126,10 @@ GetPushRuleActionsJob::GetPushRuleActionsJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, QStringLiteral("GetPushRuleActionsJob"), - basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId - % "/actions") - , d(new Private) -{} - -GetPushRuleActionsJob::~GetPushRuleActionsJob() = default; - -const QStringList& GetPushRuleActionsJob::actions() const { return d->actions; } - -BaseJob::Status GetPushRuleActionsJob::parseJson(const QJsonDocument& data) + QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" + % kind % "/" % ruleId % "/actions") { - auto json = data.object(); - if (!json.contains("actions"_ls)) - return { IncorrectResponse, - "The key 'actions' not found in the response" }; - fromJson(json.value("actions"_ls), d->actions); - - return Success; + addExpectedKey("actions"); } SetPushRuleActionsJob::SetPushRuleActionsJob(const QString& scope, @@ -206,10 +137,10 @@ SetPushRuleActionsJob::SetPushRuleActionsJob(const QString& scope, const QString& ruleId, const QStringList& actions) : BaseJob(HttpVerb::Put, QStringLiteral("SetPushRuleActionsJob"), - basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId - % "/actions") + QStringLiteral("/_matrix/client/r0") % "/pushrules/" % scope % "/" + % kind % "/" % ruleId % "/actions") { QJsonObject _data; addParam<>(_data, QStringLiteral("actions"), actions); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/pushrules.h b/lib/csapi/pushrules.h index a9169151..0971dc6b 100644 --- a/lib/csapi/pushrules.h +++ b/lib/csapi/pushrules.h @@ -4,20 +4,14 @@ #pragma once -#include "converters.h" - #include "csapi/definitions/push_condition.h" #include "csapi/definitions/push_rule.h" #include "csapi/definitions/push_ruleset.h" #include "jobs/basejob.h" -#include <QtCore/QVector> - namespace Quotient { -// Operations - /*! \brief Retrieve all push rulesets. * * Retrieve all push rulesets for this user. Clients can "drill-down" on @@ -36,19 +30,14 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetPushRulesJob() override; // Result properties /// The global ruleset. - const PushRuleset& global() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + PushRuleset global() const + { + return loadFromJson<PushRuleset>("global"_ls); + } }; /*! \brief Retrieve a push rule. @@ -59,10 +48,13 @@ class GetPushRuleJob : public BaseJob { public: /*! \brief Retrieve a push rule. * + * * \param scope * ``global`` to specify global rules. + * * \param kind * The kind of rule + * * \param ruleId * The identifier for the rule. */ @@ -76,19 +68,12 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& kind, const QString& ruleId); - ~GetPushRuleJob() override; // Result properties - /// The push rule. - const PushRule& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + /// The specific push rule. This will also include keys specific to the + /// rule itself such as the rule's ``actions`` and ``conditions`` if set. + PushRule data() const { return fromJson<PushRule>(jsonData()); } }; /*! \brief Delete a push rule. @@ -99,10 +84,13 @@ class DeletePushRuleJob : public BaseJob { public: /*! \brief Delete a push rule. * + * * \param scope * ``global`` to specify global rules. + * * \param kind * The kind of rule + * * \param ruleId * The identifier for the rule. */ @@ -130,28 +118,37 @@ class SetPushRuleJob : public BaseJob { public: /*! \brief Add or change a push rule. * + * * \param scope * ``global`` to specify global rules. + * * \param kind * The kind of rule + * * \param ruleId * The identifier for the rule. + * * \param actions * The action(s) to perform when the conditions for this rule are met. + * * \param before * Use 'before' with a ``rule_id`` as its value to make the new rule the * next-most important rule with respect to the given user defined rule. * It is not possible to add a rule relative to a predefined server rule. + * * \param after * This makes the new rule the next-less important rule relative to the * given user defined rule. It is not possible to add a rule relative * to a predefined server rule. + * * \param conditions * The conditions that must hold true for an event in order for a * rule to be applied to an event. A rule with no conditions - * always matches. Only applicable to ``underride`` and ``override`` - * rules. \param pattern Only applicable to ``content`` rules. The - * glob-style pattern to match against. + * always matches. Only applicable to ``underride`` and ``override`` rules. + * + * \param pattern + * Only applicable to ``content`` rules. The glob-style pattern to match + * against. */ explicit SetPushRuleJob(const QString& scope, const QString& kind, const QString& ruleId, const QStringList& actions, @@ -169,11 +166,14 @@ class IsPushRuleEnabledJob : public BaseJob { public: /*! \brief Get whether a push rule is enabled * + * * \param scope * Either ``global`` or ``device/<profile_tag>`` to specify global * rules or device rules for the given ``profile_tag``. + * * \param kind * The kind of rule + * * \param ruleId * The identifier for the rule. */ @@ -187,19 +187,11 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& kind, const QString& ruleId); - ~IsPushRuleEnabledJob() override; // Result properties /// Whether the push rule is enabled or not. - bool enabled() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + bool enabled() const { return loadFromJson<bool>("enabled"_ls); } }; /*! \brief Enable or disable a push rule. @@ -210,12 +202,16 @@ class SetPushRuleEnabledJob : public BaseJob { public: /*! \brief Enable or disable a push rule. * + * * \param scope * ``global`` to specify global rules. + * * \param kind * The kind of rule + * * \param ruleId * The identifier for the rule. + * * \param enabled * Whether the push rule is enabled or not. */ @@ -231,11 +227,14 @@ class GetPushRuleActionsJob : public BaseJob { public: /*! \brief The actions for a push rule * + * * \param scope * Either ``global`` or ``device/<profile_tag>`` to specify global * rules or device rules for the given ``profile_tag``. + * * \param kind * The kind of rule + * * \param ruleId * The identifier for the rule. */ @@ -249,19 +248,14 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& kind, const QString& ruleId); - ~GetPushRuleActionsJob() override; // Result properties /// The action(s) to perform for this rule. - const QStringList& actions() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QStringList actions() const + { + return loadFromJson<QStringList>("actions"_ls); + } }; /*! \brief Set the actions for a push rule. @@ -273,12 +267,16 @@ class SetPushRuleActionsJob : public BaseJob { public: /*! \brief Set the actions for a push rule. * + * * \param scope * ``global`` to specify global rules. + * * \param kind * The kind of rule + * * \param ruleId * The identifier for the rule. + * * \param actions * The action(s) to perform for this rule. */ diff --git a/lib/csapi/read_markers.cpp b/lib/csapi/read_markers.cpp index 76145808..39e4d148 100644 --- a/lib/csapi/read_markers.cpp +++ b/lib/csapi/read_markers.cpp @@ -4,22 +4,19 @@ #include "read_markers.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - SetReadMarkerJob::SetReadMarkerJob(const QString& roomId, const QString& mFullyRead, const QString& mRead) : BaseJob(HttpVerb::Post, QStringLiteral("SetReadMarkerJob"), - basePath % "/rooms/" % roomId % "/read_markers") + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/read_markers") { QJsonObject _data; addParam<>(_data, QStringLiteral("m.fully_read"), mFullyRead); addParam<IfNotEmpty>(_data, QStringLiteral("m.read"), mRead); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/read_markers.h b/lib/csapi/read_markers.h index 539aa5e4..addb9123 100644 --- a/lib/csapi/read_markers.h +++ b/lib/csapi/read_markers.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Set the position of the read marker for a room. * * Sets the position of the read marker for a given room, and optionally @@ -19,11 +17,14 @@ class SetReadMarkerJob : public BaseJob { public: /*! \brief Set the position of the read marker for a room. * + * * \param roomId * The room ID to set the read marker in for the user. + * * \param mFullyRead * The event ID the read marker should be located at. The * event MUST belong to the room. + * * \param mRead * The event ID to set the read receipt location at. This is * equivalent to calling ``/receipt/m.read/$elsewhere:example.org`` diff --git a/lib/csapi/receipts.cpp b/lib/csapi/receipts.cpp index 87264074..00d1c28a 100644 --- a/lib/csapi/receipts.cpp +++ b/lib/csapi/receipts.cpp @@ -4,20 +4,16 @@ #include "receipts.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - PostReceiptJob::PostReceiptJob(const QString& roomId, const QString& receiptType, const QString& eventId, const QJsonObject& receipt) : BaseJob(HttpVerb::Post, QStringLiteral("PostReceiptJob"), - basePath % "/rooms/" % roomId % "/receipt/" % receiptType % "/" - % eventId) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/receipt/" % receiptType % "/" % eventId) { setRequestData(Data(toJson(receipt))); } diff --git a/lib/csapi/receipts.h b/lib/csapi/receipts.h index eb82fc28..fdec3b88 100644 --- a/lib/csapi/receipts.h +++ b/lib/csapi/receipts.h @@ -6,12 +6,8 @@ #include "jobs/basejob.h" -#include <QtCore/QJsonObject> - namespace Quotient { -// Operations - /*! \brief Send a receipt for the given event ID. * * This API updates the marker for the given receipt type to the event ID @@ -21,12 +17,16 @@ class PostReceiptJob : public BaseJob { public: /*! \brief Send a receipt for the given event ID. * + * * \param roomId * The room in which to send the event. + * * \param receiptType * The type of receipt to send. + * * \param eventId * The event ID to acknowledge up to. + * * \param receipt * Extra receipt information to attach to ``content`` if any. The * server will automatically set the ``ts`` field. diff --git a/lib/csapi/redaction.cpp b/lib/csapi/redaction.cpp index 2b6417ea..91497064 100644 --- a/lib/csapi/redaction.cpp +++ b/lib/csapi/redaction.cpp @@ -4,38 +4,17 @@ #include "redaction.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class RedactEventJob::Private { -public: - QString eventId; -}; - RedactEventJob::RedactEventJob(const QString& roomId, const QString& eventId, const QString& txnId, const QString& reason) : BaseJob(HttpVerb::Put, QStringLiteral("RedactEventJob"), - basePath % "/rooms/" % roomId % "/redact/" % eventId % "/" % txnId) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/redact/" % eventId % "/" % txnId) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("reason"), reason); - setRequestData(_data); -} - -RedactEventJob::~RedactEventJob() = default; - -const QString& RedactEventJob::eventId() const { return d->eventId; } - -BaseJob::Status RedactEventJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("event_id"_ls), d->eventId); - - return Success; + setRequestData(std::move(_data)); } diff --git a/lib/csapi/redaction.h b/lib/csapi/redaction.h index 0e9095d1..c737de41 100644 --- a/lib/csapi/redaction.h +++ b/lib/csapi/redaction.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Strips all non-integrity-critical information out of an event. * * Strips all information out of an event which isn't critical to the @@ -18,38 +16,35 @@ namespace Quotient { * This cannot be undone. * * Users may redact their own events, and any user with a power level - * greater than or equal to the `redact` power level of the room may + * greater than or equal to the ``redact`` power level of the room may * redact events there. */ class RedactEventJob : public BaseJob { public: /*! \brief Strips all non-integrity-critical information out of an event. * + * * \param roomId * The room from which to redact the event. + * * \param eventId * The ID of the event to redact + * * \param txnId * The transaction ID for this event. Clients should generate a * unique ID; it will be used by the server to ensure idempotency of - * requests. \param reason The reason for the event being redacted. + * requests. + * + * \param reason + * The reason for the event being redacted. */ explicit RedactEventJob(const QString& roomId, const QString& eventId, const QString& txnId, const QString& reason = {}); - ~RedactEventJob() override; - // Result properties /// A unique identifier for the event. - const QString& eventId() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString eventId() const { return loadFromJson<QString>("event_id"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/registration.cpp b/lib/csapi/registration.cpp index 768617cc..9f88ef28 100644 --- a/lib/csapi/registration.cpp +++ b/lib/csapi/registration.cpp @@ -4,23 +4,11 @@ #include "registration.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class RegisterJob::Private { -public: - QString userId; - QString accessToken; - QString homeServer; - QString deviceId; -}; - -BaseJob::Query queryToRegister(const QString& kind) +auto queryToRegister(const QString& kind) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("kind"), kind); @@ -29,220 +17,94 @@ BaseJob::Query queryToRegister(const QString& kind) RegisterJob::RegisterJob(const QString& kind, const Omittable<AuthenticationData>& auth, - Omittable<bool> bindEmail, const QString& username, - const QString& password, const QString& deviceId, + const QString& username, const QString& password, + const QString& deviceId, const QString& initialDeviceDisplayName, Omittable<bool> inhibitLogin) : BaseJob(HttpVerb::Post, QStringLiteral("RegisterJob"), - basePath % "/register", queryToRegister(kind), {}, false) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/register", + queryToRegister(kind), {}, false) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth); - addParam<IfNotEmpty>(_data, QStringLiteral("bind_email"), bindEmail); addParam<IfNotEmpty>(_data, QStringLiteral("username"), username); addParam<IfNotEmpty>(_data, QStringLiteral("password"), password); addParam<IfNotEmpty>(_data, QStringLiteral("device_id"), deviceId); addParam<IfNotEmpty>(_data, QStringLiteral("initial_device_display_name"), initialDeviceDisplayName); addParam<IfNotEmpty>(_data, QStringLiteral("inhibit_login"), inhibitLogin); - setRequestData(_data); -} - -RegisterJob::~RegisterJob() = default; - -const QString& RegisterJob::userId() const { return d->userId; } - -const QString& RegisterJob::accessToken() const { return d->accessToken; } - -const QString& RegisterJob::homeServer() const { return d->homeServer; } - -const QString& RegisterJob::deviceId() const { return d->deviceId; } - -BaseJob::Status RegisterJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("user_id"_ls)) - return { IncorrectResponse, - "The key 'user_id' not found in the response" }; - fromJson(json.value("user_id"_ls), d->userId); - fromJson(json.value("access_token"_ls), d->accessToken); - fromJson(json.value("home_server"_ls), d->homeServer); - fromJson(json.value("device_id"_ls), d->deviceId); - - return Success; + setRequestData(std::move(_data)); + addExpectedKey("user_id"); } -class RequestTokenToRegisterEmailJob::Private { -public: - Sid data; -}; - RequestTokenToRegisterEmailJob::RequestTokenToRegisterEmailJob( - const QString& clientSecret, const QString& email, int sendAttempt, - const QString& idServer, const QString& nextLink) + const EmailValidationData& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenToRegisterEmailJob"), - basePath % "/register/email/requestToken", false) - , d(new Private) -{ - QJsonObject _data; - addParam<>(_data, QStringLiteral("client_secret"), clientSecret); - addParam<>(_data, QStringLiteral("email"), email); - addParam<>(_data, QStringLiteral("send_attempt"), sendAttempt); - addParam<IfNotEmpty>(_data, QStringLiteral("next_link"), nextLink); - addParam<>(_data, QStringLiteral("id_server"), idServer); - setRequestData(_data); -} - -RequestTokenToRegisterEmailJob::~RequestTokenToRegisterEmailJob() = default; - -const Sid& RequestTokenToRegisterEmailJob::data() const { return d->data; } - -BaseJob::Status -RequestTokenToRegisterEmailJob::parseJson(const QJsonDocument& data) + QStringLiteral("/_matrix/client/r0") + % "/register/email/requestToken", + false) { - fromJson(data, d->data); - - return Success; + setRequestData(Data(toJson(body))); } -class RequestTokenToRegisterMSISDNJob::Private { -public: - Sid data; -}; - RequestTokenToRegisterMSISDNJob::RequestTokenToRegisterMSISDNJob( - const QString& clientSecret, const QString& country, - const QString& phoneNumber, int sendAttempt, const QString& idServer, - const QString& nextLink) + const MsisdnValidationData& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenToRegisterMSISDNJob"), - basePath % "/register/msisdn/requestToken", false) - , d(new Private) + QStringLiteral("/_matrix/client/r0") + % "/register/msisdn/requestToken", + false) { - QJsonObject _data; - addParam<>(_data, QStringLiteral("client_secret"), clientSecret); - addParam<>(_data, QStringLiteral("country"), country); - addParam<>(_data, QStringLiteral("phone_number"), phoneNumber); - addParam<>(_data, QStringLiteral("send_attempt"), sendAttempt); - addParam<IfNotEmpty>(_data, QStringLiteral("next_link"), nextLink); - addParam<>(_data, QStringLiteral("id_server"), idServer); - setRequestData(_data); -} - -RequestTokenToRegisterMSISDNJob::~RequestTokenToRegisterMSISDNJob() = default; - -const Sid& RequestTokenToRegisterMSISDNJob::data() const { return d->data; } - -BaseJob::Status -RequestTokenToRegisterMSISDNJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; + setRequestData(Data(toJson(body))); } ChangePasswordJob::ChangePasswordJob(const QString& newPassword, + Omittable<bool> logoutDevices, const Omittable<AuthenticationData>& auth) : BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), - basePath % "/account/password") + QStringLiteral("/_matrix/client/r0") % "/account/password") { QJsonObject _data; addParam<>(_data, QStringLiteral("new_password"), newPassword); + addParam<IfNotEmpty>(_data, QStringLiteral("logout_devices"), logoutDevices); addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth); - setRequestData(_data); + setRequestData(std::move(_data)); } -class RequestTokenToResetPasswordEmailJob::Private { -public: - Sid data; -}; - RequestTokenToResetPasswordEmailJob::RequestTokenToResetPasswordEmailJob( - const QString& clientSecret, const QString& email, int sendAttempt, - const QString& idServer, const QString& nextLink) + const EmailValidationData& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenToResetPasswordEmailJob"), - basePath % "/account/password/email/requestToken", false) - , d(new Private) -{ - QJsonObject _data; - addParam<>(_data, QStringLiteral("client_secret"), clientSecret); - addParam<>(_data, QStringLiteral("email"), email); - addParam<>(_data, QStringLiteral("send_attempt"), sendAttempt); - addParam<IfNotEmpty>(_data, QStringLiteral("next_link"), nextLink); - addParam<>(_data, QStringLiteral("id_server"), idServer); - setRequestData(_data); -} - -RequestTokenToResetPasswordEmailJob::~RequestTokenToResetPasswordEmailJob() = - default; - -const Sid& RequestTokenToResetPasswordEmailJob::data() const { return d->data; } - -BaseJob::Status -RequestTokenToResetPasswordEmailJob::parseJson(const QJsonDocument& data) + QStringLiteral("/_matrix/client/r0") + % "/account/password/email/requestToken", + false) { - fromJson(data, d->data); - - return Success; + setRequestData(Data(toJson(body))); } -class RequestTokenToResetPasswordMSISDNJob::Private { -public: - Sid data; -}; - RequestTokenToResetPasswordMSISDNJob::RequestTokenToResetPasswordMSISDNJob( - const QString& clientSecret, const QString& country, - const QString& phoneNumber, int sendAttempt, const QString& idServer, - const QString& nextLink) + const RequestMsisdnValidation& body) : BaseJob(HttpVerb::Post, QStringLiteral("RequestTokenToResetPasswordMSISDNJob"), - basePath % "/account/password/msisdn/requestToken", false) - , d(new Private) -{ - QJsonObject _data; - addParam<>(_data, QStringLiteral("client_secret"), clientSecret); - addParam<>(_data, QStringLiteral("country"), country); - addParam<>(_data, QStringLiteral("phone_number"), phoneNumber); - addParam<>(_data, QStringLiteral("send_attempt"), sendAttempt); - addParam<IfNotEmpty>(_data, QStringLiteral("next_link"), nextLink); - addParam<>(_data, QStringLiteral("id_server"), idServer); - setRequestData(_data); -} - -RequestTokenToResetPasswordMSISDNJob::~RequestTokenToResetPasswordMSISDNJob() = - default; - -const Sid& RequestTokenToResetPasswordMSISDNJob::data() const + QStringLiteral("/_matrix/client/r0") + % "/account/password/msisdn/requestToken", + false) { - return d->data; -} - -BaseJob::Status -RequestTokenToResetPasswordMSISDNJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; + setRequestData(Data(toJson(body))); } DeactivateAccountJob::DeactivateAccountJob( - const Omittable<AuthenticationData>& auth) + const Omittable<AuthenticationData>& auth, const QString& idServer) : BaseJob(HttpVerb::Post, QStringLiteral("DeactivateAccountJob"), - basePath % "/account/deactivate") + QStringLiteral("/_matrix/client/r0") % "/account/deactivate") { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth); - setRequestData(_data); + addParam<IfNotEmpty>(_data, QStringLiteral("id_server"), idServer); + setRequestData(std::move(_data)); + addExpectedKey("id_server_unbind_result"); } -class CheckUsernameAvailabilityJob::Private { -public: - Omittable<bool> available; -}; - -BaseJob::Query queryToCheckUsernameAvailability(const QString& username) +auto queryToCheckUsernameAvailability(const QString& username) { BaseJob::Query _q; addParam<>(_q, QStringLiteral("username"), username); @@ -253,28 +115,13 @@ QUrl CheckUsernameAvailabilityJob::makeRequestUrl(QUrl baseUrl, const QString& username) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/register/available", + QStringLiteral("/_matrix/client/r0") + % "/register/available", queryToCheckUsernameAvailability(username)); } CheckUsernameAvailabilityJob::CheckUsernameAvailabilityJob(const QString& username) : BaseJob(HttpVerb::Get, QStringLiteral("CheckUsernameAvailabilityJob"), - basePath % "/register/available", + QStringLiteral("/_matrix/client/r0") % "/register/available", queryToCheckUsernameAvailability(username), {}, false) - , d(new Private) {} - -CheckUsernameAvailabilityJob::~CheckUsernameAvailabilityJob() = default; - -Omittable<bool> CheckUsernameAvailabilityJob::available() const -{ - return d->available; -} - -BaseJob::Status CheckUsernameAvailabilityJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("available"_ls), d->available); - - return Success; -} diff --git a/lib/csapi/registration.h b/lib/csapi/registration.h index 2619dd87..9d96db32 100644 --- a/lib/csapi/registration.h +++ b/lib/csapi/registration.h @@ -4,20 +4,20 @@ #pragma once -#include "converters.h" - -#include "csapi/../identity/definitions/sid.h" +#include "csapi/../identity/definitions/request_msisdn_validation.h" +#include "csapi/./definitions/request_email_validation.h" +#include "csapi/./definitions/request_msisdn_validation.h" #include "csapi/definitions/auth_data.h" +#include "csapi/definitions/request_token_response.h" #include "jobs/basejob.h" namespace Quotient { -// Operations - /*! \brief Register for an account on this homeserver. * - * This API endpoint uses the `User-Interactive Authentication API`_. + * This API endpoint uses the `User-Interactive Authentication API`_, except in + * the cases where a guest account is being registered. * * Register for an account on this homeserver. * @@ -49,36 +49,46 @@ namespace Quotient { * supplied by the client or generated by the server. The server may * invalidate any access token previously associated with that device. See * `Relationship between access tokens and devices`_. + * + * When registering a guest account, all parameters in the request body + * with the exception of ``initial_device_display_name`` MUST BE ignored + * by the server. The server MUST pick a ``device_id`` for the account + * regardless of input. + * + * Any user ID returned by this API must conform to the grammar given in the + * `Matrix specification <../appendices.html#user-identifiers>`_. */ class RegisterJob : public BaseJob { public: /*! \brief Register for an account on this homeserver. * + * * \param kind - * The kind of account to register. Defaults to `user`. + * The kind of account to register. Defaults to ``user``. + * * \param auth * Additional authentication information for the * user-interactive authentication API. Note that this * information is *not* used to define how the registered user * should be authenticated, but is instead used to - * authenticate the ``register`` call itself. It should be - * left empty, or omitted, unless an earlier call returned an - * response with status code 401. - * \param bindEmail - * If true, the server binds the email used for authentication to - * the Matrix ID with the identity server. + * authenticate the ``register`` call itself. + * * \param username * The basis for the localpart of the desired Matrix ID. If omitted, * the homeserver MUST generate a Matrix ID local part. + * * \param password * The desired password for the account. + * * \param deviceId * ID of the client device. If this does not correspond to a * known client device, a new device will be created. The server * will auto-generate a device_id if this is not specified. + * * \param initialDeviceDisplayName * A display name to assign to the newly-created device. Ignored * if ``device_id`` corresponds to a known device. + * * \param inhibitLogin * If true, an ``access_token`` and ``device_id`` should not be * returned from this call, therefore preventing an automatic @@ -86,28 +96,27 @@ public: */ explicit RegisterJob(const QString& kind = QStringLiteral("user"), const Omittable<AuthenticationData>& auth = none, - Omittable<bool> bindEmail = none, const QString& username = {}, const QString& password = {}, const QString& deviceId = {}, const QString& initialDeviceDisplayName = {}, Omittable<bool> inhibitLogin = none); - ~RegisterJob() override; - // Result properties /// The fully-qualified Matrix user ID (MXID) that has been registered. /// /// Any user ID returned by this API must conform to the grammar given in - /// the `Matrix specification - /// <https://matrix.org/docs/spec/appendices.html#user-identifiers>`_. - const QString& userId() const; + /// the `Matrix specification <../appendices.html#user-identifiers>`_. + QString userId() const { return loadFromJson<QString>("user_id"_ls); } /// An access token for the account. /// This access token can then be used to authorize other requests. /// Required if the ``inhibit_login`` option is false. - const QString& accessToken() const; + QString accessToken() const + { + return loadFromJson<QString>("access_token"_ls); + } /// The server_name of the homeserver on which the account has /// been registered. @@ -115,180 +124,141 @@ public: /// **Deprecated**. Clients should extract the server_name from /// ``user_id`` (by splitting at the first colon) if they require /// it. Note also that ``homeserver`` is not spelt this way. - const QString& homeServer() const; + QString homeServer() const + { + return loadFromJson<QString>("home_server"_ls); + } /// ID of the registered device. Will be the same as the /// corresponding parameter in the request, if one was specified. /// Required if the ``inhibit_login`` option is false. - const QString& deviceId() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString deviceId() const { return loadFromJson<QString>("device_id"_ls); } }; /*! \brief Begins the validation process for an email to be used during * registration. * - * Proxies the Identity Service API ``validate/email/requestToken``, but - * first checks that the given email address is not already associated - * with an account on this homeserver. See the Identity Service API for - * further information. + * The homeserver must check that the given email address is **not** + * already associated with an account on this homeserver. The homeserver + * should validate the email itself, either by sending a validation email + * itself or by using a service it has control over. */ class RequestTokenToRegisterEmailJob : public BaseJob { public: /*! \brief Begins the validation process for an email to be used during * registration. * - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it - * must not be empty. - * \param email - * The email address to validate. - * \param sendAttempt - * The server will only send an email if the ``send_attempt`` - * is a number greater than the most recent one which it has seen, - * scoped to that ``email`` + ``client_secret`` pair. This is to - * avoid repeatedly sending the same email in the case of request - * retries between the POSTing user and the identity server. - * The client should increment this value if they desire a new - * email (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. + * + * \param body + * The homeserver must check that the given email address is **not** + * already associated with an account on this homeserver. The homeserver + * should validate the email itself, either by sending a validation email + * itself or by using a service it has control over. */ - explicit RequestTokenToRegisterEmailJob(const QString& clientSecret, - const QString& email, - int sendAttempt, - const QString& idServer, - const QString& nextLink = {}); - - ~RequestTokenToRegisterEmailJob() override; + explicit RequestTokenToRegisterEmailJob(const EmailValidationData& body); // Result properties - /// An email has been sent to the specified address. - /// Note that this may be an email containing the validation token or it may - /// be informing the user of an error. - const Sid& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + /// An email has been sent to the specified address. Note that this + /// may be an email containing the validation token or it may be + /// informing the user of an error. + RequestTokenResponse data() const + { + return fromJson<RequestTokenResponse>(jsonData()); + } }; /*! \brief Requests a validation token be sent to the given phone number for the * purpose of registering an account * - * Proxies the Identity Service API ``validate/msisdn/requestToken``, but - * first checks that the given phone number is not already associated - * with an account on this homeserver. See the Identity Service API for - * further information. + * The homeserver must check that the given phone number is **not** + * already associated with an account on this homeserver. The homeserver + * should validate the phone number itself, either by sending a validation + * message itself or by using a service it has control over. */ class RequestTokenToRegisterMSISDNJob : public BaseJob { public: /*! \brief Requests a validation token be sent to the given phone number for * the purpose of registering an account * - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it - * must not be empty. - * \param country - * The two-letter uppercase ISO country code that the number in - * ``phone_number`` should be parsed as if it were dialled from. - * \param phoneNumber - * The phone number to validate. - * \param sendAttempt - * The server will only send an SMS if the ``send_attempt`` is a - * number greater than the most recent one which it has seen, - * scoped to that ``country`` + ``phone_number`` + ``client_secret`` - * triple. This is to avoid repeatedly sending the same SMS in - * the case of request retries between the POSTing user and the - * identity server. The client should increment this value if - * they desire a new SMS (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. + * + * \param body + * The homeserver must check that the given phone number is **not** + * already associated with an account on this homeserver. The homeserver + * should validate the phone number itself, either by sending a validation + * message itself or by using a service it has control over. */ - explicit RequestTokenToRegisterMSISDNJob(const QString& clientSecret, - const QString& country, - const QString& phoneNumber, - int sendAttempt, - const QString& idServer, - const QString& nextLink = {}); - - ~RequestTokenToRegisterMSISDNJob() override; + explicit RequestTokenToRegisterMSISDNJob(const MsisdnValidationData& body); // Result properties - /// An SMS message has been sent to the specified phone number. - /// Note that this may be an SMS message containing the validation token or + /// An SMS message has been sent to the specified phone number. Note + /// that this may be an SMS message containing the validation token or /// it may be informing the user of an error. - const Sid& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + RequestTokenResponse data() const + { + return fromJson<RequestTokenResponse>(jsonData()); + } }; /*! \brief Changes a user's password. * * Changes the password for an account on this homeserver. * - * This API endpoint uses the `User-Interactive Authentication API`_. + * This API endpoint uses the `User-Interactive Authentication API`_ to + * ensure the user changing the password is actually the owner of the + * account. * * An access token should be submitted to this endpoint if the client has * an active session. * * The homeserver may change the flows available depending on whether a - * valid access token is provided. + * valid access token is provided. The homeserver SHOULD NOT revoke the + * access token provided in the request. Whether other access tokens for + * the user are revoked depends on the request parameters. */ class ChangePasswordJob : public BaseJob { public: /*! \brief Changes a user's password. * + * * \param newPassword * The new password for the account. + * + * \param logoutDevices + * Whether the user's other access tokens, and their associated devices, + * should be revoked if the request succeeds. Defaults to true. + * + * When ``false``, the server can still take advantage of `the soft logout + * method <#soft-logout>`_ for the user's remaining devices. + * * \param auth * Additional authentication information for the user-interactive * authentication API. */ explicit ChangePasswordJob(const QString& newPassword, + Omittable<bool> logoutDevices = none, const Omittable<AuthenticationData>& auth = none); }; /*! \brief Requests a validation token be sent to the given email address for * the purpose of resetting a user's password * - * Proxies the Identity Service API ``validate/email/requestToken``, but - * first checks that the given email address **is** associated with an account - * on this homeserver. This API should be used to request - * validation tokens when authenticating for the - * `account/password` endpoint. This API's parameters and response are - * identical to that of the HS API |/register/email/requestToken|_ except that - * `M_THREEPID_NOT_FOUND` may be returned if no account matching the + * The homeserver must check that the given email address **is + * associated** with an account on this homeserver. This API should be + * used to request validation tokens when authenticating for the + * ``/account/password`` endpoint. + * + * This API's parameters and response are identical to that of the + * |/register/email/requestToken|_ endpoint, except that + * ``M_THREEPID_NOT_FOUND`` may be returned if no account matching the * given email address could be found. The server may instead send an * email to the given address prompting the user to create an account. - * `M_THREEPID_IN_USE` may not be returned. + * ``M_THREEPID_IN_USE`` may not be returned. + * + * The homeserver should validate the email itself, either by sending a + * validation email itself or by using a service it has control over. + * * * .. |/register/email/requestToken| replace:: ``/register/email/requestToken`` * @@ -300,62 +270,58 @@ public: /*! \brief Requests a validation token be sent to the given email address * for the purpose of resetting a user's password * - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it - * must not be empty. - * \param email - * The email address to validate. - * \param sendAttempt - * The server will only send an email if the ``send_attempt`` - * is a number greater than the most recent one which it has seen, - * scoped to that ``email`` + ``client_secret`` pair. This is to - * avoid repeatedly sending the same email in the case of request - * retries between the POSTing user and the identity server. - * The client should increment this value if they desire a new - * email (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. + * + * \param body + * The homeserver must check that the given email address **is + * associated** with an account on this homeserver. This API should be + * used to request validation tokens when authenticating for the + * ``/account/password`` endpoint. + * + * This API's parameters and response are identical to that of the + * |/register/email/requestToken|_ endpoint, except that + * ``M_THREEPID_NOT_FOUND`` may be returned if no account matching the + * given email address could be found. The server may instead send an + * email to the given address prompting the user to create an account. + * ``M_THREEPID_IN_USE`` may not be returned. + * + * The homeserver should validate the email itself, either by sending a + * validation email itself or by using a service it has control over. + * + * + * .. |/register/email/requestToken| replace:: + * ``/register/email/requestToken`` + * + * .. _/register/email/requestToken: + * #post-matrix-client-r0-register-email-requesttoken */ - explicit RequestTokenToResetPasswordEmailJob(const QString& clientSecret, - const QString& email, - int sendAttempt, - const QString& idServer, - const QString& nextLink = {}); - - ~RequestTokenToResetPasswordEmailJob() override; + explicit RequestTokenToResetPasswordEmailJob(const EmailValidationData& body); // Result properties /// An email was sent to the given address. - const Sid& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + RequestTokenResponse data() const + { + return fromJson<RequestTokenResponse>(jsonData()); + } }; /*! \brief Requests a validation token be sent to the given phone number for the * purpose of resetting a user's password. * - * Proxies the Identity Service API ``validate/msisdn/requestToken``, but - * first checks that the given phone number **is** associated with an account - * on this homeserver. This API should be used to request - * validation tokens when authenticating for the - * `account/password` endpoint. This API's parameters and response are - * identical to that of the HS API |/register/msisdn/requestToken|_ except that - * `M_THREEPID_NOT_FOUND` may be returned if no account matching the - * given phone number could be found. The server may instead send an - * SMS message to the given address prompting the user to create an account. - * `M_THREEPID_IN_USE` may not be returned. + * The homeserver must check that the given phone number **is + * associated** with an account on this homeserver. This API should be + * used to request validation tokens when authenticating for the + * ``/account/password`` endpoint. + * + * This API's parameters and response are identical to that of the + * |/register/msisdn/requestToken|_ endpoint, except that + * ``M_THREEPID_NOT_FOUND`` may be returned if no account matching the + * given phone number could be found. The server may instead send the SMS + * to the given phone number prompting the user to create an account. + * ``M_THREEPID_IN_USE`` may not be returned. + * + * The homeserver should validate the phone number itself, either by sending a + * validation message itself or by using a service it has control over. * * .. |/register/msisdn/requestToken| replace:: ``/register/msisdn/requestToken`` * @@ -367,51 +333,39 @@ public: /*! \brief Requests a validation token be sent to the given phone number for * the purpose of resetting a user's password. * - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it - * must not be empty. - * \param country - * The two-letter uppercase ISO country code that the number in - * ``phone_number`` should be parsed as if it were dialled from. - * \param phoneNumber - * The phone number to validate. - * \param sendAttempt - * The server will only send an SMS if the ``send_attempt`` is a - * number greater than the most recent one which it has seen, - * scoped to that ``country`` + ``phone_number`` + ``client_secret`` - * triple. This is to avoid repeatedly sending the same SMS in - * the case of request retries between the POSTing user and the - * identity server. The client should increment this value if - * they desire a new SMS (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. + * + * \param body + * The homeserver must check that the given phone number **is + * associated** with an account on this homeserver. This API should be + * used to request validation tokens when authenticating for the + * ``/account/password`` endpoint. + * + * This API's parameters and response are identical to that of the + * |/register/msisdn/requestToken|_ endpoint, except that + * ``M_THREEPID_NOT_FOUND`` may be returned if no account matching the + * given phone number could be found. The server may instead send the SMS + * to the given phone number prompting the user to create an account. + * ``M_THREEPID_IN_USE`` may not be returned. + * + * The homeserver should validate the phone number itself, either by sending + * a validation message itself or by using a service it has control over. + * + * .. |/register/msisdn/requestToken| replace:: + * ``/register/msisdn/requestToken`` + * + * .. _/register/msisdn/requestToken: + * #post-matrix-client-r0-register-email-requesttoken */ - explicit RequestTokenToResetPasswordMSISDNJob(const QString& clientSecret, - const QString& country, - const QString& phoneNumber, - int sendAttempt, - const QString& idServer, - const QString& nextLink = {}); - - ~RequestTokenToResetPasswordMSISDNJob() override; + explicit RequestTokenToResetPasswordMSISDNJob( + const RequestMsisdnValidation& body); // Result properties /// An SMS message was sent to the given phone number. - const Sid& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + RequestTokenResponse data() const + { + return fromJson<RequestTokenResponse>(jsonData()); + } }; /*! \brief Deactivate a user's account. @@ -426,17 +380,45 @@ private: * * The homeserver may change the flows available depending on whether a * valid access token is provided. + * + * Unlike other endpoints, this endpoint does not take an ``id_access_token`` + * parameter because the homeserver is expected to sign the request to the + * identity server instead. */ class DeactivateAccountJob : public BaseJob { public: /*! \brief Deactivate a user's account. * + * * \param auth * Additional authentication information for the user-interactive * authentication API. + * + * \param idServer + * The identity server to unbind all of the user's 3PIDs from. + * If not provided, the homeserver MUST use the ``id_server`` + * that was originally use to bind each identifier. If the + * homeserver does not know which ``id_server`` that was, + * it must return an ``id_server_unbind_result`` of + * ``no-support``. */ - explicit DeactivateAccountJob( - const Omittable<AuthenticationData>& auth = none); + explicit DeactivateAccountJob(const Omittable<AuthenticationData>& auth = none, + const QString& idServer = {}); + + // Result properties + + /// An indicator as to whether or not the homeserver was able to unbind + /// the user's 3PIDs from the identity server(s). ``success`` indicates + /// that all identifiers have been unbound from the identity server while + /// ``no-support`` indicates that one or more identifiers failed to unbind + /// due to the identity server refusing the request or the homeserver + /// being unable to determine an identity server to unbind from. This + /// must be ``success`` if the homeserver has no identifiers to unbind + /// for the user. + QString idServerUnbindResult() const + { + return loadFromJson<QString>("id_server_unbind_result"_ls); + } }; /*! \brief Checks to see if a username is available on the server. @@ -458,6 +440,7 @@ class CheckUsernameAvailabilityJob : public BaseJob { public: /*! \brief Checks to see if a username is available on the server. * + * * \param username * The username to check the availability of. */ @@ -469,20 +452,15 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& username); - ~CheckUsernameAvailabilityJob() override; // Result properties /// A flag to indicate that the username is available. This should always /// be ``true`` when the server replies with 200 OK. - Omittable<bool> available() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + Omittable<bool> available() const + { + return loadFromJson<Omittable<bool>>("available"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/report_content.cpp b/lib/csapi/report_content.cpp index f38b7a9a..0a41625f 100644 --- a/lib/csapi/report_content.cpp +++ b/lib/csapi/report_content.cpp @@ -4,21 +4,18 @@ #include "report_content.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - ReportContentJob::ReportContentJob(const QString& roomId, const QString& eventId, int score, const QString& reason) : BaseJob(HttpVerb::Post, QStringLiteral("ReportContentJob"), - basePath % "/rooms/" % roomId % "/report/" % eventId) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/report/" % eventId) { QJsonObject _data; addParam<>(_data, QStringLiteral("score"), score); addParam<>(_data, QStringLiteral("reason"), reason); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/report_content.h b/lib/csapi/report_content.h index 0f3cc3d5..13dc9138 100644 --- a/lib/csapi/report_content.h +++ b/lib/csapi/report_content.h @@ -4,14 +4,10 @@ #pragma once -#include "converters.h" - #include "jobs/basejob.h" namespace Quotient { -// Operations - /*! \brief Reports an event as inappropriate. * * Reports an event as inappropriate to the server, which may then notify @@ -21,13 +17,17 @@ class ReportContentJob : public BaseJob { public: /*! \brief Reports an event as inappropriate. * + * * \param roomId * The room in which the event being reported is located. + * * \param eventId * The event to report. + * * \param score * The score to rate this content as where -100 is most offensive * and 0 is inoffensive. + * * \param reason * The reason the content is being reported. May be blank. */ diff --git a/lib/csapi/room_send.cpp b/lib/csapi/room_send.cpp index d278433b..63986c56 100644 --- a/lib/csapi/room_send.cpp +++ b/lib/csapi/room_send.cpp @@ -4,36 +4,16 @@ #include "room_send.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class SendMessageJob::Private { -public: - QString eventId; -}; - SendMessageJob::SendMessageJob(const QString& roomId, const QString& eventType, const QString& txnId, const QJsonObject& body) : BaseJob(HttpVerb::Put, QStringLiteral("SendMessageJob"), - basePath % "/rooms/" % roomId % "/send/" % eventType % "/" % txnId) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/send/" % eventType % "/" % txnId) { setRequestData(Data(toJson(body))); -} - -SendMessageJob::~SendMessageJob() = default; - -const QString& SendMessageJob::eventId() const { return d->eventId; } - -BaseJob::Status SendMessageJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("event_id"_ls), d->eventId); - - return Success; + addExpectedKey("event_id"); } diff --git a/lib/csapi/room_send.h b/lib/csapi/room_send.h index 96b111ff..b1905e53 100644 --- a/lib/csapi/room_send.h +++ b/lib/csapi/room_send.h @@ -6,12 +6,8 @@ #include "jobs/basejob.h" -#include <QtCore/QJsonObject> - namespace Quotient { -// Operations - /*! \brief Send a message event to the given room. * * This endpoint is used to send a message event to a room. Message events @@ -26,14 +22,18 @@ class SendMessageJob : public BaseJob { public: /*! \brief Send a message event to the given room. * + * * \param roomId * The room to send the event to. + * * \param eventType * The type of event to send. + * * \param txnId * The transaction ID for this event. Clients should generate an * ID unique across requests with the same access token; it will be * used by the server to ensure idempotency of requests. + * * \param body * This endpoint is used to send a message event to a room. Message events * allow access to historical events and pagination, making them suited @@ -46,19 +46,10 @@ public: explicit SendMessageJob(const QString& roomId, const QString& eventType, const QString& txnId, const QJsonObject& body = {}); - ~SendMessageJob() override; - // Result properties /// A unique identifier for the event. - const QString& eventId() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString eventId() const { return loadFromJson<QString>("event_id"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/room_state.cpp b/lib/csapi/room_state.cpp index 5973b06b..e18108ac 100644 --- a/lib/csapi/room_state.cpp +++ b/lib/csapi/room_state.cpp @@ -4,65 +4,18 @@ #include "room_state.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class SetRoomStateWithKeyJob::Private { -public: - QString eventId; -}; - SetRoomStateWithKeyJob::SetRoomStateWithKeyJob(const QString& roomId, const QString& eventType, const QString& stateKey, const QJsonObject& body) : BaseJob(HttpVerb::Put, QStringLiteral("SetRoomStateWithKeyJob"), - basePath % "/rooms/" % roomId % "/state/" % eventType % "/" - % stateKey) - , d(new Private) -{ - setRequestData(Data(toJson(body))); -} - -SetRoomStateWithKeyJob::~SetRoomStateWithKeyJob() = default; - -const QString& SetRoomStateWithKeyJob::eventId() const { return d->eventId; } - -BaseJob::Status SetRoomStateWithKeyJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("event_id"_ls), d->eventId); - - return Success; -} - -class SetRoomStateJob::Private { -public: - QString eventId; -}; - -SetRoomStateJob::SetRoomStateJob(const QString& roomId, const QString& eventType, - const QJsonObject& body) - : BaseJob(HttpVerb::Put, QStringLiteral("SetRoomStateJob"), - basePath % "/rooms/" % roomId % "/state/" % eventType) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/state/" % eventType % "/" % stateKey) { setRequestData(Data(toJson(body))); -} - -SetRoomStateJob::~SetRoomStateJob() = default; - -const QString& SetRoomStateJob::eventId() const { return d->eventId; } - -BaseJob::Status SetRoomStateJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("event_id"_ls), d->eventId); - - return Success; + addExpectedKey("event_id"); } diff --git a/lib/csapi/room_state.h b/lib/csapi/room_state.h index 7843cff4..eeb2b33c 100644 --- a/lib/csapi/room_state.h +++ b/lib/csapi/room_state.h @@ -6,14 +6,13 @@ #include "jobs/basejob.h" -#include <QtCore/QJsonObject> - namespace Quotient { -// Operations - /*! \brief Send a state event to the given room. * + * .. For backwards compatibility with older links... + * .. _`put-matrix-client-r0-rooms-roomid-state-eventtype`: + * * State events can be sent using this endpoint. These events will be * overwritten if ``<room id>``, ``<event type>`` and ``<state key>`` all * match. @@ -25,18 +24,32 @@ namespace Quotient { * The body of the request should be the content object of the event; the * fields in this object will vary depending on the type of event. See * `Room Events`_ for the ``m.`` event specification. + * + * If the event type being sent is ``m.room.canonical_alias`` servers + * SHOULD ensure that any new aliases being listed in the event are valid + * per their grammar/syntax and that they point to the room ID where the + * state event is to be sent. Servers do not validate aliases which are + * being removed or are already present in the state event. */ class SetRoomStateWithKeyJob : public BaseJob { public: /*! \brief Send a state event to the given room. * + * * \param roomId * The room to set the state in + * * \param eventType * The type of event to send. + * * \param stateKey - * The state_key for the state to send. Defaults to the empty string. + * The state_key for the state to send. Defaults to the empty string. When + * an empty string, the trailing slash on this endpoint is optional. + * * \param body + * .. For backwards compatibility with older links... + * .. _`put-matrix-client-r0-rooms-roomid-state-eventtype`: + * * State events can be sent using this endpoint. These events will be * overwritten if ``<room id>``, ``<event type>`` and ``<state key>`` all * match. @@ -48,81 +61,22 @@ public: * The body of the request should be the content object of the event; the * fields in this object will vary depending on the type of event. See * `Room Events`_ for the ``m.`` event specification. + * + * If the event type being sent is ``m.room.canonical_alias`` servers + * SHOULD ensure that any new aliases being listed in the event are valid + * per their grammar/syntax and that they point to the room ID where the + * state event is to be sent. Servers do not validate aliases which are + * being removed or are already present in the state event. */ explicit SetRoomStateWithKeyJob(const QString& roomId, const QString& eventType, const QString& stateKey, const QJsonObject& body = {}); - ~SetRoomStateWithKeyJob() override; - - // Result properties - - /// A unique identifier for the event. - const QString& eventId() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; -}; - -/*! \brief Send a state event to the given room. - * - * State events can be sent using this endpoint. This endpoint is - * equivalent to calling `/rooms/{roomId}/state/{eventType}/{stateKey}` - * with an empty `stateKey`. Previous state events with matching - * `<roomId>` and `<eventType>`, and empty `<stateKey>`, will be overwritten. - * - * Requests to this endpoint **cannot use transaction IDs** - * like other ``PUT`` paths because they cannot be differentiated from the - * ``state_key``. Furthermore, ``POST`` is unsupported on state paths. - * - * The body of the request should be the content object of the event; the - * fields in this object will vary depending on the type of event. See - * `Room Events`_ for the ``m.`` event specification. - */ -class SetRoomStateJob : public BaseJob { -public: - /*! \brief Send a state event to the given room. - * - * \param roomId - * The room to set the state in - * \param eventType - * The type of event to send. - * \param body - * State events can be sent using this endpoint. This endpoint is - * equivalent to calling `/rooms/{roomId}/state/{eventType}/{stateKey}` - * with an empty `stateKey`. Previous state events with matching - * `<roomId>` and `<eventType>`, and empty `<stateKey>`, will be - * overwritten. - * - * Requests to this endpoint **cannot use transaction IDs** - * like other ``PUT`` paths because they cannot be differentiated from the - * ``state_key``. Furthermore, ``POST`` is unsupported on state paths. - * - * The body of the request should be the content object of the event; the - * fields in this object will vary depending on the type of event. See - * `Room Events`_ for the ``m.`` event specification. - */ - explicit SetRoomStateJob(const QString& roomId, const QString& eventType, - const QJsonObject& body = {}); - - ~SetRoomStateJob() override; - // Result properties /// A unique identifier for the event. - const QString& eventId() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString eventId() const { return loadFromJson<QString>("event_id"_ls); } }; } // namespace Quotient diff --git a/lib/csapi/room_upgrades.cpp b/lib/csapi/room_upgrades.cpp index 244f5b74..e3791b08 100644 --- a/lib/csapi/room_upgrades.cpp +++ b/lib/csapi/room_upgrades.cpp @@ -4,43 +4,17 @@ #include "room_upgrades.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class UpgradeRoomJob::Private { -public: - QString replacementRoom; -}; - UpgradeRoomJob::UpgradeRoomJob(const QString& roomId, const QString& newVersion) : BaseJob(HttpVerb::Post, QStringLiteral("UpgradeRoomJob"), - basePath % "/rooms/" % roomId % "/upgrade") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/upgrade") { QJsonObject _data; addParam<>(_data, QStringLiteral("new_version"), newVersion); - setRequestData(_data); -} - -UpgradeRoomJob::~UpgradeRoomJob() = default; - -const QString& UpgradeRoomJob::replacementRoom() const -{ - return d->replacementRoom; -} - -BaseJob::Status UpgradeRoomJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("replacement_room"_ls)) - return { IncorrectResponse, - "The key 'replacement_room' not found in the response" }; - fromJson(json.value("replacement_room"_ls), d->replacementRoom); - - return Success; + setRequestData(std::move(_data)); + addExpectedKey("replacement_room"); } diff --git a/lib/csapi/room_upgrades.h b/lib/csapi/room_upgrades.h index f13a9af4..53601816 100644 --- a/lib/csapi/room_upgrades.h +++ b/lib/csapi/room_upgrades.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Upgrades a room to a new room version. * * Upgrades the given room to a particular room version. @@ -18,26 +16,22 @@ class UpgradeRoomJob : public BaseJob { public: /*! \brief Upgrades a room to a new room version. * + * * \param roomId * The ID of the room to upgrade. + * * \param newVersion * The new version for the room. */ explicit UpgradeRoomJob(const QString& roomId, const QString& newVersion); - ~UpgradeRoomJob() override; - // Result properties /// The ID of the new room. - const QString& replacementRoom() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString replacementRoom() const + { + return loadFromJson<QString>("replacement_room"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/rooms.cpp b/lib/csapi/rooms.cpp index 234e33df..724d941f 100644 --- a/lib/csapi/rooms.cpp +++ b/lib/csapi/rooms.cpp @@ -4,51 +4,33 @@ #include "rooms.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class GetOneRoomEventJob::Private { -public: - EventPtr data; -}; - QUrl GetOneRoomEventJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& eventId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/rooms/" - % roomId % "/event/" - % eventId); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/rooms/" % roomId % "/event/" + % eventId); } GetOneRoomEventJob::GetOneRoomEventJob(const QString& roomId, const QString& eventId) : BaseJob(HttpVerb::Get, QStringLiteral("GetOneRoomEventJob"), - basePath % "/rooms/" % roomId % "/event/" % eventId) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/event/" % eventId) {} -GetOneRoomEventJob::~GetOneRoomEventJob() = default; - -EventPtr&& GetOneRoomEventJob::data() { return std::move(d->data); } - -BaseJob::Status GetOneRoomEventJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} - QUrl GetRoomStateWithKeyJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& eventType, const QString& stateKey) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/rooms/" % roomId % "/state/" + QStringLiteral("/_matrix/client/r0") + % "/rooms/" % roomId % "/state/" % eventType % "/" % stateKey); } @@ -56,60 +38,25 @@ GetRoomStateWithKeyJob::GetRoomStateWithKeyJob(const QString& roomId, const QString& eventType, const QString& stateKey) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomStateWithKeyJob"), - basePath % "/rooms/" % roomId % "/state/" % eventType % "/" - % stateKey) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/state/" % eventType % "/" % stateKey) {} -QUrl GetRoomStateByTypeJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, - const QString& eventType) -{ - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/rooms/" - % roomId % "/state/" - % eventType); -} - -GetRoomStateByTypeJob::GetRoomStateByTypeJob(const QString& roomId, - const QString& eventType) - : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomStateByTypeJob"), - basePath % "/rooms/" % roomId % "/state/" % eventType) -{} - -class GetRoomStateJob::Private { -public: - StateEvents data; -}; - QUrl GetRoomStateJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/rooms/" % roomId % "/state"); + QStringLiteral("/_matrix/client/r0") + % "/rooms/" % roomId % "/state"); } GetRoomStateJob::GetRoomStateJob(const QString& roomId) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomStateJob"), - basePath % "/rooms/" % roomId % "/state") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/state") {} -GetRoomStateJob::~GetRoomStateJob() = default; - -StateEvents&& GetRoomStateJob::data() { return std::move(d->data); } - -BaseJob::Status GetRoomStateJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} - -class GetMembersByRoomJob::Private { -public: - EventsArray<RoomMemberEvent> chunk; -}; - -BaseJob::Query queryToGetMembersByRoom(const QString& at, - const QString& membership, - const QString& notMembership) +auto queryToGetMembersByRoom(const QString& at, const QString& membership, + const QString& notMembership) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("at"), at); @@ -123,10 +70,10 @@ QUrl GetMembersByRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& membership, const QString& notMembership) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/rooms/" % roomId % "/members", - queryToGetMembersByRoom(at, membership, - notMembership)); + return BaseJob::makeRequestUrl( + std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId % "/members", + queryToGetMembersByRoom(at, membership, notMembership)); } GetMembersByRoomJob::GetMembersByRoomJob(const QString& roomId, @@ -134,72 +81,21 @@ GetMembersByRoomJob::GetMembersByRoomJob(const QString& roomId, const QString& membership, const QString& notMembership) : BaseJob(HttpVerb::Get, QStringLiteral("GetMembersByRoomJob"), - basePath % "/rooms/" % roomId % "/members", + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/members", queryToGetMembersByRoom(at, membership, notMembership)) - , d(new Private) {} -GetMembersByRoomJob::~GetMembersByRoomJob() = default; - -EventsArray<RoomMemberEvent>&& GetMembersByRoomJob::chunk() -{ - return std::move(d->chunk); -} - -BaseJob::Status GetMembersByRoomJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("chunk"_ls), d->chunk); - - return Success; -} - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<GetJoinedMembersByRoomJob::RoomMember> { - static void fillFrom(const QJsonObject& jo, - GetJoinedMembersByRoomJob::RoomMember& result) - { - fromJson(jo.value("display_name"_ls), result.displayName); - fromJson(jo.value("avatar_url"_ls), result.avatarUrl); - } -}; - -} // namespace Quotient - -class GetJoinedMembersByRoomJob::Private { -public: - QHash<QString, RoomMember> joined; -}; - QUrl GetJoinedMembersByRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/rooms/" - % roomId - % "/joined_members"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") + % "/rooms/" % roomId % "/joined_members"); } GetJoinedMembersByRoomJob::GetJoinedMembersByRoomJob(const QString& roomId) : BaseJob(HttpVerb::Get, QStringLiteral("GetJoinedMembersByRoomJob"), - basePath % "/rooms/" % roomId % "/joined_members") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/joined_members") {} - -GetJoinedMembersByRoomJob::~GetJoinedMembersByRoomJob() = default; - -const QHash<QString, GetJoinedMembersByRoomJob::RoomMember>& -GetJoinedMembersByRoomJob::joined() const -{ - return d->joined; -} - -BaseJob::Status GetJoinedMembersByRoomJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("joined"_ls), d->joined); - - return Success; -} diff --git a/lib/csapi/rooms.h b/lib/csapi/rooms.h index 05c5b82a..6137bcbd 100644 --- a/lib/csapi/rooms.h +++ b/lib/csapi/rooms.h @@ -4,18 +4,12 @@ #pragma once -#include "converters.h" - #include "events/eventloader.h" #include "events/roommemberevent.h" #include "jobs/basejob.h" -#include <QtCore/QHash> - namespace Quotient { -// Operations - /*! \brief Get a single event by event ID. * * Get a single event based on ``roomId/eventId``. You must have permission to @@ -25,8 +19,10 @@ class GetOneRoomEventJob : public BaseJob { public: /*! \brief Get a single event by event ID. * + * * \param roomId * The ID of the room the event is in. + * * \param eventId * The event ID to get. */ @@ -39,23 +35,18 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& eventId); - ~GetOneRoomEventJob() override; // Result properties /// The full event. - EventPtr&& data(); - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + EventPtr data() { return fromJson<EventPtr>(jsonData()); } }; /*! \brief Get the state identified by the type and key. * + * .. For backwards compatibility with older links... + * .. _`get-matrix-client-r0-rooms-roomid-state-eventtype`: + * * Looks up the contents of a state event in a room. If the user is * joined to the room then the state is taken from the current * state of the room. If the user has left the room then the state is @@ -65,12 +56,16 @@ class GetRoomStateWithKeyJob : public BaseJob { public: /*! \brief Get the state identified by the type and key. * + * * \param roomId * The room to look up the state in. + * * \param eventType * The type of state to look up. + * * \param stateKey - * The key of the state to look up. + * The key of the state to look up. Defaults to an empty string. When + * an empty string, the trailing slash on this endpoint is optional. */ explicit GetRoomStateWithKeyJob(const QString& roomId, const QString& eventType, @@ -86,36 +81,6 @@ public: const QString& stateKey); }; -/*! \brief Get the state identified by the type, with the empty state key. - * - * Looks up the contents of a state event in a room. If the user is - * joined to the room then the state is taken from the current - * state of the room. If the user has left the room then the state is - * taken from the state of the room when they left. - * - * This looks up the state event with the empty state key. - */ -class GetRoomStateByTypeJob : public BaseJob { -public: - /*! \brief Get the state identified by the type, with the empty state key. - * - * \param roomId - * The room to look up the state in. - * \param eventType - * The type of state to look up. - */ - explicit GetRoomStateByTypeJob(const QString& roomId, - const QString& eventType); - - /*! \brief Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for GetRoomStateByTypeJob - * is necessary but the job itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, - const QString& eventType); -}; - /*! \brief Get all state events in the current state of a room. * * Get the state events for the current state of a room. @@ -124,6 +89,7 @@ class GetRoomStateJob : public BaseJob { public: /*! \brief Get all state events in the current state of a room. * + * * \param roomId * The room to look up the state for. */ @@ -135,22 +101,11 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); - ~GetRoomStateJob() override; // Result properties - /// If the user is a member of the room this will be the - /// current state of the room as a list of events. If the user - /// has left the room then this will be the state of the room - /// when they left as a list of events. - StateEvents&& data(); - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + /// The current state of the room + StateEvents data() { return fromJson<StateEvents>(jsonData()); } }; /*! \brief Get the m.room.member events for the room. @@ -161,17 +116,26 @@ class GetMembersByRoomJob : public BaseJob { public: /*! \brief Get the m.room.member events for the room. * + * * \param roomId * The room to get the member events for. + * * \param at - * The token defining the timeline position as-of which to return - * the list of members. This token can be obtained from a batch token - * returned for each room by the sync API, or from - * a ``start``/``end`` token returned by a ``/messages`` request. + * The point in time (pagination token) to return members for in the room. + * This token can be obtained from a ``prev_batch`` token returned for + * each room by the sync API. Defaults to the current state of the room, + * as determined by the server. + * * \param membership - * Only return users with the specified membership + * The kind of membership to filter for. Defaults to no filtering if + * unspecified. When specified alongside ``not_membership``, the two + * parameters create an 'or' condition: either the membership *is* + * the same as ``membership`` **or** *is not* the same as + * ``not_membership``. + * * \param notMembership - * Only return users with membership state other than specified + * The kind of membership to exclude from the results. Defaults to no + * filtering if unspecified. */ explicit GetMembersByRoomJob(const QString& roomId, const QString& at = {}, const QString& membership = {}, @@ -186,19 +150,14 @@ public: const QString& at = {}, const QString& membership = {}, const QString& notMembership = {}); - ~GetMembersByRoomJob() override; // Result properties /// Get the list of members for this room. - EventsArray<RoomMemberEvent>&& chunk(); - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + EventsArray<RoomMemberEvent> chunk() + { + return takeFromJson<EventsArray<RoomMemberEvent>>("chunk"_ls); + } }; /*! \brief Gets the list of currently joined users and their profile data. @@ -231,6 +190,7 @@ public: /*! \brief Gets the list of currently joined users and their profile data. * + * * \param roomId * The room to get the members of. */ @@ -242,19 +202,24 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); - ~GetJoinedMembersByRoomJob() override; // Result properties /// A map from user ID to a RoomMember object. - const QHash<QString, RoomMember>& joined() const; - -protected: - Status parseJson(const QJsonDocument& data) override; + QHash<QString, RoomMember> joined() const + { + return loadFromJson<QHash<QString, RoomMember>>("joined"_ls); + } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter<GetJoinedMembersByRoomJob::RoomMember> { + static void fillFrom(const QJsonObject& jo, + GetJoinedMembersByRoomJob::RoomMember& result) + { + fromJson(jo.value("display_name"_ls), result.displayName); + fromJson(jo.value("avatar_url"_ls), result.avatarUrl); + } }; } // namespace Quotient diff --git a/lib/csapi/sso_login_redirect.cpp b/lib/csapi/sso_login_redirect.cpp index 21fe646e..85a18560 100644 --- a/lib/csapi/sso_login_redirect.cpp +++ b/lib/csapi/sso_login_redirect.cpp @@ -4,15 +4,11 @@ #include "sso_login_redirect.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -BaseJob::Query queryToRedirectToSSO(const QString& redirectUrl) +auto queryToRedirectToSSO(const QString& redirectUrl) { BaseJob::Query _q; addParam<>(_q, QStringLiteral("redirectUrl"), redirectUrl); @@ -22,13 +18,13 @@ BaseJob::Query queryToRedirectToSSO(const QString& redirectUrl) QUrl RedirectToSSOJob::makeRequestUrl(QUrl baseUrl, const QString& redirectUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/login/sso/redirect", + QStringLiteral("/_matrix/client/r0") + % "/login/sso/redirect", queryToRedirectToSSO(redirectUrl)); } RedirectToSSOJob::RedirectToSSOJob(const QString& redirectUrl) : BaseJob(HttpVerb::Get, QStringLiteral("RedirectToSSOJob"), - basePath % "/login/sso/redirect", + QStringLiteral("/_matrix/client/r0") % "/login/sso/redirect", queryToRedirectToSSO(redirectUrl), {}, false) - {} diff --git a/lib/csapi/sso_login_redirect.h b/lib/csapi/sso_login_redirect.h index 8978c7f7..20f33da2 100644 --- a/lib/csapi/sso_login_redirect.h +++ b/lib/csapi/sso_login_redirect.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Redirect the user's browser to the SSO interface. * * A web-based Matrix client should instruct the user's browser to @@ -21,6 +19,7 @@ class RedirectToSSOJob : public BaseJob { public: /*! \brief Redirect the user's browser to the SSO interface. * + * * \param redirectUrl * URI to which the user will be redirected after the homeserver has * authenticated the user with SSO. diff --git a/lib/csapi/tags.cpp b/lib/csapi/tags.cpp index 0e2d122a..dc22dc18 100644 --- a/lib/csapi/tags.cpp +++ b/lib/csapi/tags.cpp @@ -4,82 +4,49 @@ #include "tags.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<GetRoomTagsJob::Tag> { - static void fillFrom(QJsonObject jo, GetRoomTagsJob::Tag& result) - { - fromJson(jo.take("order"_ls), result.order); - fromJson(jo, result.additionalProperties); - } -}; - -} // namespace Quotient - -class GetRoomTagsJob::Private { -public: - QHash<QString, Tag> tags; -}; - QUrl GetRoomTagsJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& roomId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/user/" - % userId % "/rooms/" - % roomId % "/tags"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client/r0") % "/user/" + % userId % "/rooms/" % roomId % "/tags"); } GetRoomTagsJob::GetRoomTagsJob(const QString& userId, const QString& roomId) : BaseJob(HttpVerb::Get, QStringLiteral("GetRoomTagsJob"), - basePath % "/user/" % userId % "/rooms/" % roomId % "/tags") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/user/" % userId + % "/rooms/" % roomId % "/tags") {} -GetRoomTagsJob::~GetRoomTagsJob() = default; - -const QHash<QString, GetRoomTagsJob::Tag>& GetRoomTagsJob::tags() const -{ - return d->tags; -} - -BaseJob::Status GetRoomTagsJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - fromJson(json.value("tags"_ls), d->tags); - - return Success; -} - SetRoomTagJob::SetRoomTagJob(const QString& userId, const QString& roomId, - const QString& tag, Omittable<float> order) + const QString& tag, Omittable<float> order, + const QVariantHash& additionalProperties) : BaseJob(HttpVerb::Put, QStringLiteral("SetRoomTagJob"), - basePath % "/user/" % userId % "/rooms/" % roomId % "/tags/" % tag) + QStringLiteral("/_matrix/client/r0") % "/user/" % userId + % "/rooms/" % roomId % "/tags/" % tag) { QJsonObject _data; + fillJson(_data, additionalProperties); addParam<IfNotEmpty>(_data, QStringLiteral("order"), order); - setRequestData(_data); + setRequestData(std::move(_data)); } QUrl DeleteRoomTagJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& roomId, const QString& tag) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/user/" % userId % "/rooms/" - % roomId % "/tags/" % tag); + QStringLiteral("/_matrix/client/r0") + % "/user/" % userId % "/rooms/" % roomId + % "/tags/" % tag); } DeleteRoomTagJob::DeleteRoomTagJob(const QString& userId, const QString& roomId, const QString& tag) : BaseJob(HttpVerb::Delete, QStringLiteral("DeleteRoomTagJob"), - basePath % "/user/" % userId % "/rooms/" % roomId % "/tags/" % tag) + QStringLiteral("/_matrix/client/r0") % "/user/" % userId + % "/rooms/" % roomId % "/tags/" % tag) {} diff --git a/lib/csapi/tags.h b/lib/csapi/tags.h index 1ffb0f81..e6eb9dda 100644 --- a/lib/csapi/tags.h +++ b/lib/csapi/tags.h @@ -4,17 +4,10 @@ #pragma once -#include "converters.h" - #include "jobs/basejob.h" -#include <QtCore/QHash> -#include <QtCore/QVariant> - namespace Quotient { -// Operations - /*! \brief List the tags for a room. * * List the tags set by a user on a room. @@ -36,9 +29,11 @@ public: /*! \brief List the tags for a room. * + * * \param userId * The id of the user to get tags for. The access token must be * authorized to make requests for this user ID. + * * \param roomId * The ID of the room to get tags for. */ @@ -51,19 +46,23 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& roomId); - ~GetRoomTagsJob() override; // Result properties /// List the tags set by a user on a room. - const QHash<QString, Tag>& tags() const; - -protected: - Status parseJson(const QJsonDocument& data) override; + QHash<QString, Tag> tags() const + { + return loadFromJson<QHash<QString, Tag>>("tags"_ls); + } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter<GetRoomTagsJob::Tag> { + static void fillFrom(QJsonObject jo, GetRoomTagsJob::Tag& result) + { + fromJson(jo.take("order"_ls), result.order); + fromJson(jo, result.additionalProperties); + } }; /*! \brief Add a tag to a room. @@ -74,19 +73,27 @@ class SetRoomTagJob : public BaseJob { public: /*! \brief Add a tag to a room. * + * * \param userId * The id of the user to add a tag for. The access token must be * authorized to make requests for this user ID. + * * \param roomId * The ID of the room to add a tag to. + * * \param tag * The tag to add. + * * \param order * A number in a range ``[0,1]`` describing a relative * position of the room under the given tag. + * + * \param additionalProperties + * Add a tag to the room. */ explicit SetRoomTagJob(const QString& userId, const QString& roomId, - const QString& tag, Omittable<float> order = none); + const QString& tag, Omittable<float> order = none, + const QVariantHash& additionalProperties = {}); }; /*! \brief Remove a tag from the room. @@ -97,11 +104,14 @@ class DeleteRoomTagJob : public BaseJob { public: /*! \brief Remove a tag from the room. * + * * \param userId * The id of the user to remove a tag for. The access token must be * authorized to make requests for this user ID. + * * \param roomId * The ID of the room to remove a tag from. + * * \param tag * The tag to remove. */ diff --git a/lib/csapi/third_party_lookup.cpp b/lib/csapi/third_party_lookup.cpp index 9d92e407..baf1fab5 100644 --- a/lib/csapi/third_party_lookup.cpp +++ b/lib/csapi/third_party_lookup.cpp @@ -4,83 +4,37 @@ #include "third_party_lookup.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class GetProtocolsJob::Private { -public: - QHash<QString, ThirdPartyProtocol> data; -}; - QUrl GetProtocolsJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/thirdparty/protocols"); + QStringLiteral("/_matrix/client/r0") + % "/thirdparty/protocols"); } GetProtocolsJob::GetProtocolsJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetProtocolsJob"), - basePath % "/thirdparty/protocols") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/thirdparty/protocols") {} -GetProtocolsJob::~GetProtocolsJob() = default; - -const QHash<QString, ThirdPartyProtocol>& GetProtocolsJob::data() const -{ - return d->data; -} - -BaseJob::Status GetProtocolsJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} - -class GetProtocolMetadataJob::Private { -public: - ThirdPartyProtocol data; -}; - QUrl GetProtocolMetadataJob::makeRequestUrl(QUrl baseUrl, const QString& protocol) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/thirdparty/protocol/" % protocol); + QStringLiteral("/_matrix/client/r0") + % "/thirdparty/protocol/" % protocol); } GetProtocolMetadataJob::GetProtocolMetadataJob(const QString& protocol) : BaseJob(HttpVerb::Get, QStringLiteral("GetProtocolMetadataJob"), - basePath % "/thirdparty/protocol/" % protocol) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/thirdparty/protocol/" + % protocol) {} -GetProtocolMetadataJob::~GetProtocolMetadataJob() = default; - -const ThirdPartyProtocol& GetProtocolMetadataJob::data() const -{ - return d->data; -} - -BaseJob::Status GetProtocolMetadataJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} - -class QueryLocationByProtocolJob::Private { -public: - QVector<ThirdPartyLocation> data; -}; - -BaseJob::Query queryToQueryLocationByProtocol(const QString& searchFields) +auto queryToQueryLocationByProtocol(const QString& searchFields) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("searchFields"), searchFields); @@ -92,38 +46,20 @@ QUrl QueryLocationByProtocolJob::makeRequestUrl(QUrl baseUrl, const QString& searchFields) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/thirdparty/location/" % protocol, + QStringLiteral("/_matrix/client/r0") + % "/thirdparty/location/" % protocol, queryToQueryLocationByProtocol(searchFields)); } QueryLocationByProtocolJob::QueryLocationByProtocolJob( const QString& protocol, const QString& searchFields) : BaseJob(HttpVerb::Get, QStringLiteral("QueryLocationByProtocolJob"), - basePath % "/thirdparty/location/" % protocol, + QStringLiteral("/_matrix/client/r0") % "/thirdparty/location/" + % protocol, queryToQueryLocationByProtocol(searchFields)) - , d(new Private) {} -QueryLocationByProtocolJob::~QueryLocationByProtocolJob() = default; - -const QVector<ThirdPartyLocation>& QueryLocationByProtocolJob::data() const -{ - return d->data; -} - -BaseJob::Status QueryLocationByProtocolJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} - -class QueryUserByProtocolJob::Private { -public: - QVector<ThirdPartyUser> data; -}; - -BaseJob::Query queryToQueryUserByProtocol(const QString& fields) +auto queryToQueryUserByProtocol(const QString& fields) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("fields..."), fields); @@ -135,38 +71,20 @@ QUrl QueryUserByProtocolJob::makeRequestUrl(QUrl baseUrl, const QString& fields) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/thirdparty/user/" % protocol, + QStringLiteral("/_matrix/client/r0") + % "/thirdparty/user/" % protocol, queryToQueryUserByProtocol(fields)); } QueryUserByProtocolJob::QueryUserByProtocolJob(const QString& protocol, const QString& fields) : BaseJob(HttpVerb::Get, QStringLiteral("QueryUserByProtocolJob"), - basePath % "/thirdparty/user/" % protocol, + QStringLiteral("/_matrix/client/r0") % "/thirdparty/user/" + % protocol, queryToQueryUserByProtocol(fields)) - , d(new Private) {} -QueryUserByProtocolJob::~QueryUserByProtocolJob() = default; - -const QVector<ThirdPartyUser>& QueryUserByProtocolJob::data() const -{ - return d->data; -} - -BaseJob::Status QueryUserByProtocolJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} - -class QueryLocationByAliasJob::Private { -public: - QVector<ThirdPartyLocation> data; -}; - -BaseJob::Query queryToQueryLocationByAlias(const QString& alias) +auto queryToQueryLocationByAlias(const QString& alias) { BaseJob::Query _q; addParam<>(_q, QStringLiteral("alias"), alias); @@ -176,37 +94,18 @@ BaseJob::Query queryToQueryLocationByAlias(const QString& alias) QUrl QueryLocationByAliasJob::makeRequestUrl(QUrl baseUrl, const QString& alias) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/thirdparty/location", + QStringLiteral("/_matrix/client/r0") + % "/thirdparty/location", queryToQueryLocationByAlias(alias)); } QueryLocationByAliasJob::QueryLocationByAliasJob(const QString& alias) : BaseJob(HttpVerb::Get, QStringLiteral("QueryLocationByAliasJob"), - basePath % "/thirdparty/location", + QStringLiteral("/_matrix/client/r0") % "/thirdparty/location", queryToQueryLocationByAlias(alias)) - , d(new Private) {} -QueryLocationByAliasJob::~QueryLocationByAliasJob() = default; - -const QVector<ThirdPartyLocation>& QueryLocationByAliasJob::data() const -{ - return d->data; -} - -BaseJob::Status QueryLocationByAliasJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} - -class QueryUserByIDJob::Private { -public: - QVector<ThirdPartyUser> data; -}; - -BaseJob::Query queryToQueryUserByID(const QString& userid) +auto queryToQueryUserByID(const QString& userid) { BaseJob::Query _q; addParam<>(_q, QStringLiteral("userid"), userid); @@ -216,26 +115,13 @@ BaseJob::Query queryToQueryUserByID(const QString& userid) QUrl QueryUserByIDJob::makeRequestUrl(QUrl baseUrl, const QString& userid) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/thirdparty/user", + QStringLiteral("/_matrix/client/r0") + % "/thirdparty/user", queryToQueryUserByID(userid)); } QueryUserByIDJob::QueryUserByIDJob(const QString& userid) : BaseJob(HttpVerb::Get, QStringLiteral("QueryUserByIDJob"), - basePath % "/thirdparty/user", queryToQueryUserByID(userid)) - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/thirdparty/user", + queryToQueryUserByID(userid)) {} - -QueryUserByIDJob::~QueryUserByIDJob() = default; - -const QVector<ThirdPartyUser>& QueryUserByIDJob::data() const -{ - return d->data; -} - -BaseJob::Status QueryUserByIDJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} diff --git a/lib/csapi/third_party_lookup.h b/lib/csapi/third_party_lookup.h index c8ca8cbb..9329b7a7 100644 --- a/lib/csapi/third_party_lookup.h +++ b/lib/csapi/third_party_lookup.h @@ -4,21 +4,14 @@ #pragma once -#include "converters.h" - #include "csapi/../application-service/definitions/location.h" #include "csapi/../application-service/definitions/protocol.h" #include "csapi/../application-service/definitions/user.h" #include "jobs/basejob.h" -#include <QtCore/QHash> -#include <QtCore/QVector> - namespace Quotient { -// Operations - /*! \brief Retrieve metadata about all protocols that a homeserver supports. * * Fetches the overall metadata about protocols supported by the @@ -36,19 +29,14 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetProtocolsJob() override; // Result properties /// The protocols supported by the homeserver. - const QHash<QString, ThirdPartyProtocol>& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QHash<QString, ThirdPartyProtocol> data() const + { + return fromJson<QHash<QString, ThirdPartyProtocol>>(jsonData()); + } }; /*! \brief Retrieve metadata about a specific protocol that the homeserver @@ -62,6 +50,7 @@ public: /*! \brief Retrieve metadata about a specific protocol that the homeserver * supports. * + * * \param protocol * The name of the protocol. */ @@ -73,19 +62,14 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& protocol); - ~GetProtocolMetadataJob() override; // Result properties /// The protocol was found and metadata returned. - const ThirdPartyProtocol& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + ThirdPartyProtocol data() const + { + return fromJson<ThirdPartyProtocol>(jsonData()); + } }; /*! \brief Retrieve Matrix-side portals rooms leading to a third party location. @@ -104,8 +88,10 @@ public: /*! \brief Retrieve Matrix-side portals rooms leading to a third party * location. * + * * \param protocol * The protocol used to communicate to the third party network. + * * \param searchFields * One or more custom fields to help identify the third party * location. @@ -120,19 +106,14 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& protocol, const QString& searchFields = {}); - ~QueryLocationByProtocolJob() override; // Result properties /// At least one portal room was found. - const QVector<ThirdPartyLocation>& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QVector<ThirdPartyLocation> data() const + { + return fromJson<QVector<ThirdPartyLocation>>(jsonData()); + } }; /*! \brief Retrieve the Matrix User ID of a corresponding third party user. @@ -144,8 +125,10 @@ class QueryUserByProtocolJob : public BaseJob { public: /*! \brief Retrieve the Matrix User ID of a corresponding third party user. * + * * \param protocol * The name of the protocol. + * * \param fields * One or more custom fields that are passed to the AS to help identify * the user. @@ -160,19 +143,14 @@ public: */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& protocol, const QString& fields = {}); - ~QueryUserByProtocolJob() override; // Result properties /// The Matrix User IDs found with the given parameters. - const QVector<ThirdPartyUser>& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QVector<ThirdPartyUser> data() const + { + return fromJson<QVector<ThirdPartyUser>>(jsonData()); + } }; /*! \brief Reverse-lookup third party locations given a Matrix room alias. @@ -184,6 +162,7 @@ class QueryLocationByAliasJob : public BaseJob { public: /*! \brief Reverse-lookup third party locations given a Matrix room alias. * + * * \param alias * The Matrix room alias to look up. */ @@ -195,19 +174,14 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& alias); - ~QueryLocationByAliasJob() override; // Result properties /// All found third party locations. - const QVector<ThirdPartyLocation>& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QVector<ThirdPartyLocation> data() const + { + return fromJson<QVector<ThirdPartyLocation>>(jsonData()); + } }; /*! \brief Reverse-lookup third party users given a Matrix User ID. @@ -218,6 +192,7 @@ class QueryUserByIDJob : public BaseJob { public: /*! \brief Reverse-lookup third party users given a Matrix User ID. * + * * \param userid * The Matrix User ID to look up. */ @@ -229,19 +204,14 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl, const QString& userid); - ~QueryUserByIDJob() override; // Result properties /// An array of third party users. - const QVector<ThirdPartyUser>& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QVector<ThirdPartyUser> data() const + { + return fromJson<QVector<ThirdPartyUser>>(jsonData()); + } }; } // namespace Quotient diff --git a/lib/csapi/third_party_membership.cpp b/lib/csapi/third_party_membership.cpp index 15da6b3c..fda772d2 100644 --- a/lib/csapi/third_party_membership.cpp +++ b/lib/csapi/third_party_membership.cpp @@ -4,22 +4,21 @@ #include "third_party_membership.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - InviteBy3PIDJob::InviteBy3PIDJob(const QString& roomId, const QString& idServer, + const QString& idAccessToken, const QString& medium, const QString& address) : BaseJob(HttpVerb::Post, QStringLiteral("InviteBy3PIDJob"), - basePath % "/rooms/" % roomId % "/invite") + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/invite") { QJsonObject _data; addParam<>(_data, QStringLiteral("id_server"), idServer); + addParam<>(_data, QStringLiteral("id_access_token"), idAccessToken); addParam<>(_data, QStringLiteral("medium"), medium); addParam<>(_data, QStringLiteral("address"), address); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/third_party_membership.h b/lib/csapi/third_party_membership.h index c85e8ab4..f8196f6f 100644 --- a/lib/csapi/third_party_membership.h +++ b/lib/csapi/third_party_membership.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Invite a user to participate in a particular room. * * .. _invite-by-third-party-id-endpoint: @@ -61,15 +59,28 @@ class InviteBy3PIDJob : public BaseJob { public: /*! \brief Invite a user to participate in a particular room. * + * * \param roomId * The room identifier (not alias) to which to invite the user. + * * \param idServer * The hostname+port of the identity server which should be used for third - * party identifier lookups. \param medium The kind of address being passed - * in the address field, for example ``email``. \param address The invitee's - * third party identifier. + * party identifier lookups. + * + * \param idAccessToken + * An access token previously registered with the identity server. Servers + * can treat this as optional to distinguish between r0.5-compatible + * clients and this specification version. + * + * \param medium + * The kind of address being passed in the address field, for example + * ``email``. + * + * \param address + * The invitee's third party identifier. */ explicit InviteBy3PIDJob(const QString& roomId, const QString& idServer, + const QString& idAccessToken, const QString& medium, const QString& address); }; diff --git a/lib/csapi/to_device.cpp b/lib/csapi/to_device.cpp index e277503c..28c4115a 100644 --- a/lib/csapi/to_device.cpp +++ b/lib/csapi/to_device.cpp @@ -4,21 +4,18 @@ #include "to_device.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - SendToDeviceJob::SendToDeviceJob( const QString& eventType, const QString& txnId, const QHash<QString, QHash<QString, QJsonObject>>& messages) : BaseJob(HttpVerb::Put, QStringLiteral("SendToDeviceJob"), - basePath % "/sendToDevice/" % eventType % "/" % txnId) + QStringLiteral("/_matrix/client/r0") % "/sendToDevice/" + % eventType % "/" % txnId) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("messages"), messages); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/to_device.h b/lib/csapi/to_device.h index 48f0ec83..8c82af45 100644 --- a/lib/csapi/to_device.h +++ b/lib/csapi/to_device.h @@ -6,13 +6,8 @@ #include "jobs/basejob.h" -#include <QtCore/QHash> -#include <QtCore/QJsonObject> - namespace Quotient { -// Operations - /*! \brief Send an event to a given set of devices. * * This endpoint is used to send send-to-device events to a set of @@ -22,12 +17,15 @@ class SendToDeviceJob : public BaseJob { public: /*! \brief Send an event to a given set of devices. * + * * \param eventType * The type of event to send. + * * \param txnId * The transaction ID for this event. Clients should generate an * ID unique across requests with the same access token; it will be * used by the server to ensure idempotency of requests. + * * \param messages * The messages to send. A map from user ID, to a map from * device ID to message body. The device ID may also be `*`, diff --git a/lib/csapi/typing.cpp b/lib/csapi/typing.cpp index 261622c4..8e214053 100644 --- a/lib/csapi/typing.cpp +++ b/lib/csapi/typing.cpp @@ -4,21 +4,18 @@ #include "typing.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - SetTypingJob::SetTypingJob(const QString& userId, const QString& roomId, bool typing, Omittable<int> timeout) : BaseJob(HttpVerb::Put, QStringLiteral("SetTypingJob"), - basePath % "/rooms/" % roomId % "/typing/" % userId) + QStringLiteral("/_matrix/client/r0") % "/rooms/" % roomId + % "/typing/" % userId) { QJsonObject _data; addParam<>(_data, QStringLiteral("typing"), typing); addParam<IfNotEmpty>(_data, QStringLiteral("timeout"), timeout); - setRequestData(_data); + setRequestData(std::move(_data)); } diff --git a/lib/csapi/typing.h b/lib/csapi/typing.h index dbf17a3e..f5b67baf 100644 --- a/lib/csapi/typing.h +++ b/lib/csapi/typing.h @@ -4,14 +4,10 @@ #pragma once -#include "converters.h" - #include "jobs/basejob.h" namespace Quotient { -// Operations - /*! \brief Informs the server that the user has started or stopped typing. * * This tells the server that the user is typing for the next N @@ -23,13 +19,17 @@ class SetTypingJob : public BaseJob { public: /*! \brief Informs the server that the user has started or stopped typing. * + * * \param userId * The user who has started to type. + * * \param roomId * The room in which the user is typing. + * * \param typing * Whether the user is typing or not. If ``false``, the ``timeout`` * key can be omitted. + * * \param timeout * The length of time in milliseconds to mark this user as typing. */ diff --git a/lib/csapi/users.cpp b/lib/csapi/users.cpp index 5d2a6133..a0279d7e 100644 --- a/lib/csapi/users.cpp +++ b/lib/csapi/users.cpp @@ -4,68 +4,19 @@ #include "users.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -// Converters -namespace Quotient { - -template <> -struct JsonObjectConverter<SearchUserDirectoryJob::User> { - static void fillFrom(const QJsonObject& jo, - SearchUserDirectoryJob::User& result) - { - fromJson(jo.value("user_id"_ls), result.userId); - fromJson(jo.value("display_name"_ls), result.displayName); - fromJson(jo.value("avatar_url"_ls), result.avatarUrl); - } -}; - -} // namespace Quotient - -class SearchUserDirectoryJob::Private { -public: - QVector<User> results; - bool limited; -}; - SearchUserDirectoryJob::SearchUserDirectoryJob(const QString& searchTerm, Omittable<int> limit) : BaseJob(HttpVerb::Post, QStringLiteral("SearchUserDirectoryJob"), - basePath % "/user_directory/search") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/user_directory/search") { QJsonObject _data; addParam<>(_data, QStringLiteral("search_term"), searchTerm); addParam<IfNotEmpty>(_data, QStringLiteral("limit"), limit); - setRequestData(_data); -} - -SearchUserDirectoryJob::~SearchUserDirectoryJob() = default; - -const QVector<SearchUserDirectoryJob::User>& SearchUserDirectoryJob::results() const -{ - return d->results; -} - -bool SearchUserDirectoryJob::limited() const { return d->limited; } - -BaseJob::Status SearchUserDirectoryJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("results"_ls)) - return { IncorrectResponse, - "The key 'results' not found in the response" }; - fromJson(json.value("results"_ls), d->results); - if (!json.contains("limited"_ls)) - return { IncorrectResponse, - "The key 'limited' not found in the response" }; - fromJson(json.value("limited"_ls), d->limited); - - return Success; + setRequestData(std::move(_data)); + addExpectedKey("results"); + addExpectedKey("limited"); } diff --git a/lib/csapi/users.h b/lib/csapi/users.h index de4eb529..adb2a33d 100644 --- a/lib/csapi/users.h +++ b/lib/csapi/users.h @@ -4,19 +4,13 @@ #pragma once -#include "converters.h" - #include "jobs/basejob.h" -#include <QtCore/QVector> - namespace Quotient { -// Operations - /*! \brief Searches the user directory. * - * Performs a search for users on the homeserver. The homeserver may + * Performs a search for users. The homeserver may * determine which subset of users are searched, however the homeserver * MUST at a minimum consider the users the requesting user shares a * room with and those who reside in public rooms (known to the homeserver). @@ -31,7 +25,7 @@ class SearchUserDirectoryJob : public BaseJob { public: // Inner data structures - /// Performs a search for users on the homeserver. The homeserver may + /// Performs a search for users. The homeserver may /// determine which subset of users are searched, however the homeserver /// MUST at a minimum consider the users the requesting user shares a /// room with and those who reside in public rooms (known to the @@ -41,7 +35,7 @@ public: /// The search is performed case-insensitively on user IDs and display /// names preferably using a collation determined based upon the /// ``Accept-Language`` header provided in the request, if present. - struct User { + struct SearchUserDirectory200ThirdPartyUser { /// The user's matrix user ID. QString userId; /// The display name of the user, if one exists. @@ -54,30 +48,40 @@ public: /*! \brief Searches the user directory. * + * * \param searchTerm * The term to search for + * * \param limit * The maximum number of results to return. Defaults to 10. */ explicit SearchUserDirectoryJob(const QString& searchTerm, Omittable<int> limit = none); - ~SearchUserDirectoryJob() override; - // Result properties /// Ordered by rank and then whether or not profile info is available. - const QVector<User>& results() const; + QVector<SearchUserDirectory200ThirdPartyUser> results() const + { + return loadFromJson<QVector<SearchUserDirectory200ThirdPartyUser>>( + "results"_ls); + } /// Indicates if the result list has been truncated by the limit. - bool limited() const; - -protected: - Status parseJson(const QJsonDocument& data) override; + bool limited() const { return loadFromJson<bool>("limited"_ls); } +}; -private: - class Private; - QScopedPointer<Private> d; +template <> +struct JsonObjectConverter< + SearchUserDirectoryJob::SearchUserDirectory200ThirdPartyUser> { + static void + fillFrom(const QJsonObject& jo, + SearchUserDirectoryJob::SearchUserDirectory200ThirdPartyUser& result) + { + fromJson(jo.value("user_id"_ls), result.userId); + fromJson(jo.value("display_name"_ls), result.displayName); + fromJson(jo.value("avatar_url"_ls), result.avatarUrl); + } }; } // namespace Quotient diff --git a/lib/csapi/versions.cpp b/lib/csapi/versions.cpp index 8cce03b9..9003e27f 100644 --- a/lib/csapi/versions.cpp +++ b/lib/csapi/versions.cpp @@ -4,48 +4,20 @@ #include "versions.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client"); - -class GetVersionsJob::Private { -public: - QStringList versions; - QHash<QString, bool> unstableFeatures; -}; - QUrl GetVersionsJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/versions"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + QStringLiteral("/_matrix/client") + % "/versions"); } GetVersionsJob::GetVersionsJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetVersionsJob"), - basePath % "/versions", false) - , d(new Private) -{} - -GetVersionsJob::~GetVersionsJob() = default; - -const QStringList& GetVersionsJob::versions() const { return d->versions; } - -const QHash<QString, bool>& GetVersionsJob::unstableFeatures() const + QStringLiteral("/_matrix/client") % "/versions", false) { - return d->unstableFeatures; -} - -BaseJob::Status GetVersionsJob::parseJson(const QJsonDocument& data) -{ - auto json = data.object(); - if (!json.contains("versions"_ls)) - return { IncorrectResponse, - "The key 'versions' not found in the response" }; - fromJson(json.value("versions"_ls), d->versions); - fromJson(json.value("unstable_features"_ls), d->unstableFeatures); - - return Success; + addExpectedKey("versions"); } diff --git a/lib/csapi/versions.h b/lib/csapi/versions.h index 4c4f8109..828a7eb9 100644 --- a/lib/csapi/versions.h +++ b/lib/csapi/versions.h @@ -4,16 +4,10 @@ #pragma once -#include "converters.h" - #include "jobs/basejob.h" -#include <QtCore/QHash> - namespace Quotient { -// Operations - /*! \brief Gets the versions of the specification supported by the server. * * Gets the versions of the specification supported by the server. @@ -48,24 +42,22 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetVersionsJob() override; // Result properties /// The supported versions. - const QStringList& versions() const; + QStringList versions() const + { + return loadFromJson<QStringList>("versions"_ls); + } /// Experimental features the server supports. Features not listed here, /// or the lack of this property all together, indicate that a feature is /// not supported. - const QHash<QString, bool>& unstableFeatures() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QHash<QString, bool> unstableFeatures() const + { + return loadFromJson<QHash<QString, bool>>("unstable_features"_ls); + } }; } // namespace Quotient diff --git a/lib/csapi/voip.cpp b/lib/csapi/voip.cpp index c98824b3..43170057 100644 --- a/lib/csapi/voip.cpp +++ b/lib/csapi/voip.cpp @@ -4,38 +4,18 @@ #include "voip.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class GetTurnServerJob::Private { -public: - QJsonObject data; -}; - QUrl GetTurnServerJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/voip/turnServer"); + QStringLiteral("/_matrix/client/r0") + % "/voip/turnServer"); } GetTurnServerJob::GetTurnServerJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetTurnServerJob"), - basePath % "/voip/turnServer") - , d(new Private) + QStringLiteral("/_matrix/client/r0") % "/voip/turnServer") {} - -GetTurnServerJob::~GetTurnServerJob() = default; - -const QJsonObject& GetTurnServerJob::data() const { return d->data; } - -BaseJob::Status GetTurnServerJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} diff --git a/lib/csapi/voip.h b/lib/csapi/voip.h index d3eb3c4b..087ebbbd 100644 --- a/lib/csapi/voip.h +++ b/lib/csapi/voip.h @@ -6,12 +6,8 @@ #include "jobs/basejob.h" -#include <QtCore/QJsonObject> - namespace Quotient { -// Operations - /*! \brief Obtain TURN server credentials. * * This API provides credentials for the client to use when initiating @@ -28,19 +24,11 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetTurnServerJob() override; // Result properties /// The TURN server credentials. - const QJsonObject& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QJsonObject data() const { return fromJson<QJsonObject>(jsonData()); } }; } // namespace Quotient diff --git a/lib/csapi/wellknown.cpp b/lib/csapi/wellknown.cpp index 464068e7..1aa0a90b 100644 --- a/lib/csapi/wellknown.cpp +++ b/lib/csapi/wellknown.cpp @@ -4,38 +4,18 @@ #include "wellknown.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/.well-known"); - -class GetWellknownJob::Private { -public: - DiscoveryInformation data; -}; - QUrl GetWellknownJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/matrix/client"); + QStringLiteral("/.well-known") + % "/matrix/client"); } GetWellknownJob::GetWellknownJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetWellknownJob"), - basePath % "/matrix/client", false) - , d(new Private) + QStringLiteral("/.well-known") % "/matrix/client", false) {} - -GetWellknownJob::~GetWellknownJob() = default; - -const DiscoveryInformation& GetWellknownJob::data() const { return d->data; } - -BaseJob::Status GetWellknownJob::parseJson(const QJsonDocument& data) -{ - fromJson(data, d->data); - - return Success; -} diff --git a/lib/csapi/wellknown.h b/lib/csapi/wellknown.h index 94d3631c..b21d9fc7 100644 --- a/lib/csapi/wellknown.h +++ b/lib/csapi/wellknown.h @@ -4,16 +4,12 @@ #pragma once -#include "converters.h" - #include "csapi/definitions/wellknown/full.h" #include "jobs/basejob.h" namespace Quotient { -// Operations - /*! \brief Gets Matrix server discovery information about the domain. * * Gets discovery information about the domain. The file may include @@ -36,19 +32,14 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetWellknownJob() override; // Result properties /// Server discovery information. - const DiscoveryInformation& data() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + DiscoveryInformation data() const + { + return fromJson<DiscoveryInformation>(jsonData()); + } }; } // namespace Quotient diff --git a/lib/csapi/whoami.cpp b/lib/csapi/whoami.cpp index 8d4681e4..73f0298e 100644 --- a/lib/csapi/whoami.cpp +++ b/lib/csapi/whoami.cpp @@ -4,42 +4,20 @@ #include "whoami.h" -#include "converters.h" - #include <QtCore/QStringBuilder> using namespace Quotient; -static const auto basePath = QStringLiteral("/_matrix/client/r0"); - -class GetTokenOwnerJob::Private { -public: - QString userId; -}; - QUrl GetTokenOwnerJob::makeRequestUrl(QUrl baseUrl) { return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/account/whoami"); + QStringLiteral("/_matrix/client/r0") + % "/account/whoami"); } GetTokenOwnerJob::GetTokenOwnerJob() : BaseJob(HttpVerb::Get, QStringLiteral("GetTokenOwnerJob"), - basePath % "/account/whoami") - , d(new Private) -{} - -GetTokenOwnerJob::~GetTokenOwnerJob() = default; - -const QString& GetTokenOwnerJob::userId() const { return d->userId; } - -BaseJob::Status GetTokenOwnerJob::parseJson(const QJsonDocument& data) + QStringLiteral("/_matrix/client/r0") % "/account/whoami") { - auto json = data.object(); - if (!json.contains("user_id"_ls)) - return { IncorrectResponse, - "The key 'user_id' not found in the response" }; - fromJson(json.value("user_id"_ls), d->userId); - - return Success; + addExpectedKey("user_id"); } diff --git a/lib/csapi/whoami.h b/lib/csapi/whoami.h index dda8d8c8..af8f1e8a 100644 --- a/lib/csapi/whoami.h +++ b/lib/csapi/whoami.h @@ -8,8 +8,6 @@ namespace Quotient { -// Operations - /*! \brief Gets information about the owner of an access token. * * Gets information about the owner of a given access token. @@ -32,19 +30,11 @@ public: * is necessary but the job itself isn't. */ static QUrl makeRequestUrl(QUrl baseUrl); - ~GetTokenOwnerJob() override; // Result properties /// The user id that owns the access token. - const QString& userId() const; - -protected: - Status parseJson(const QJsonDocument& data) override; - -private: - class Private; - QScopedPointer<Private> d; + QString userId() const { return loadFromJson<QString>("user_id"_ls); } }; } // namespace Quotient diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 0895fae9..c50459f3 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -89,13 +89,11 @@ public: // A map from senderKey to InboundSession QMap<QString, InboundSession*> sessions; // TODO: cache void updateDeviceKeys( - const QHash<QString, QHash<QString, QueryKeysJob::DeviceInformation>>& - deviceKeys) + const QHash<QString, QHash<QString, QueryKeysJob::DeviceKeys>>& deviceKeys) { for (auto userId : deviceKeys.keys()) { for (auto deviceId : deviceKeys.value(userId).keys()) { - QueryKeysJob::DeviceInformation info = - deviceKeys.value(userId).value(deviceId); + auto info = deviceKeys.value(userId).value(deviceId); // TODO: ed25519Verify, etc } } diff --git a/lib/identity/definitions/request_email_validation.cpp b/lib/identity/definitions/request_email_validation.cpp deleted file mode 100644 index 22cb0072..00000000 --- a/lib/identity/definitions/request_email_validation.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "request_email_validation.h" - -using namespace Quotient; - -void JsonObjectConverter<RequestEmailValidation>::dumpTo( - QJsonObject& jo, const RequestEmailValidation& pod) -{ - addParam<>(jo, QStringLiteral("client_secret"), pod.clientSecret); - addParam<>(jo, QStringLiteral("email"), pod.email); - addParam<>(jo, QStringLiteral("send_attempt"), pod.sendAttempt); - addParam<IfNotEmpty>(jo, QStringLiteral("next_link"), pod.nextLink); -} - -void JsonObjectConverter<RequestEmailValidation>::fillFrom( - const QJsonObject& jo, RequestEmailValidation& result) -{ - fromJson(jo.value("client_secret"_ls), result.clientSecret); - fromJson(jo.value("email"_ls), result.email); - fromJson(jo.value("send_attempt"_ls), result.sendAttempt); - fromJson(jo.value("next_link"_ls), result.nextLink); -} diff --git a/lib/identity/definitions/request_email_validation.h b/lib/identity/definitions/request_email_validation.h index 32c6eaaf..079da953 100644 --- a/lib/identity/definitions/request_email_validation.h +++ b/lib/identity/definitions/request_email_validation.h @@ -8,8 +8,6 @@ namespace Quotient { -// Data structures - struct RequestEmailValidation { /// A unique string generated by the client, and used to identify the /// validation attempt. It must be a string consisting of the characters @@ -26,18 +24,32 @@ struct RequestEmailValidation { /// avoid repeatedly sending the same email in the case of request /// retries between the POSTing user and the identity server. /// The client should increment this value if they desire a new - /// email (e.g. a reminder) to be sent. + /// email (e.g. a reminder) to be sent. If they do not, the server + /// should respond with success but not resend the email. int sendAttempt; - /// Optional. When the validation is completed, the identity - /// server will redirect the user to this URL. + /// Optional. When the validation is completed, the identity server will + /// redirect the user to this URL. This option is ignored when submitting + /// 3PID validation information through a POST request. QString nextLink; }; template <> struct JsonObjectConverter<RequestEmailValidation> { - static void dumpTo(QJsonObject& jo, const RequestEmailValidation& pod); - static void fillFrom(const QJsonObject& jo, RequestEmailValidation& pod); + static void dumpTo(QJsonObject& jo, const RequestEmailValidation& pod) + { + addParam<>(jo, QStringLiteral("client_secret"), pod.clientSecret); + addParam<>(jo, QStringLiteral("email"), pod.email); + addParam<>(jo, QStringLiteral("send_attempt"), pod.sendAttempt); + addParam<IfNotEmpty>(jo, QStringLiteral("next_link"), pod.nextLink); + } + static void fillFrom(const QJsonObject& jo, RequestEmailValidation& pod) + { + fromJson(jo.value("client_secret"_ls), pod.clientSecret); + fromJson(jo.value("email"_ls), pod.email); + fromJson(jo.value("send_attempt"_ls), pod.sendAttempt); + fromJson(jo.value("next_link"_ls), pod.nextLink); + } }; } // namespace Quotient diff --git a/lib/identity/definitions/request_msisdn_validation.cpp b/lib/identity/definitions/request_msisdn_validation.cpp deleted file mode 100644 index 6024bf61..00000000 --- a/lib/identity/definitions/request_msisdn_validation.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "request_msisdn_validation.h" - -using namespace Quotient; - -void JsonObjectConverter<RequestMsisdnValidation>::dumpTo( - QJsonObject& jo, const RequestMsisdnValidation& pod) -{ - addParam<>(jo, QStringLiteral("client_secret"), pod.clientSecret); - addParam<>(jo, QStringLiteral("country"), pod.country); - addParam<>(jo, QStringLiteral("phone_number"), pod.phoneNumber); - addParam<>(jo, QStringLiteral("send_attempt"), pod.sendAttempt); - addParam<IfNotEmpty>(jo, QStringLiteral("next_link"), pod.nextLink); -} - -void JsonObjectConverter<RequestMsisdnValidation>::fillFrom( - const QJsonObject& jo, RequestMsisdnValidation& result) -{ - fromJson(jo.value("client_secret"_ls), result.clientSecret); - fromJson(jo.value("country"_ls), result.country); - fromJson(jo.value("phone_number"_ls), result.phoneNumber); - fromJson(jo.value("send_attempt"_ls), result.sendAttempt); - fromJson(jo.value("next_link"_ls), result.nextLink); -} diff --git a/lib/identity/definitions/request_msisdn_validation.h b/lib/identity/definitions/request_msisdn_validation.h index 90aed7b8..a29fd0de 100644 --- a/lib/identity/definitions/request_msisdn_validation.h +++ b/lib/identity/definitions/request_msisdn_validation.h @@ -8,8 +8,6 @@ namespace Quotient { -// Data structures - struct RequestMsisdnValidation { /// A unique string generated by the client, and used to identify the /// validation attempt. It must be a string consisting of the characters @@ -17,8 +15,8 @@ struct RequestMsisdnValidation { /// must not be empty. QString clientSecret; - /// The two-letter uppercase ISO country code that the number in - /// ``phone_number`` should be parsed as if it were dialled from. + /// The two-letter uppercase ISO-3166-1 alpha-2 country code that the + /// number in ``phone_number`` should be parsed as if it were dialled from. QString country; /// The phone number to validate. @@ -33,15 +31,30 @@ struct RequestMsisdnValidation { /// they desire a new SMS (e.g. a reminder) to be sent. int sendAttempt; - /// Optional. When the validation is completed, the identity - /// server will redirect the user to this URL. + /// Optional. When the validation is completed, the identity server will + /// redirect the user to this URL. This option is ignored when submitting + /// 3PID validation information through a POST request. QString nextLink; }; template <> struct JsonObjectConverter<RequestMsisdnValidation> { - static void dumpTo(QJsonObject& jo, const RequestMsisdnValidation& pod); - static void fillFrom(const QJsonObject& jo, RequestMsisdnValidation& pod); + static void dumpTo(QJsonObject& jo, const RequestMsisdnValidation& pod) + { + addParam<>(jo, QStringLiteral("client_secret"), pod.clientSecret); + addParam<>(jo, QStringLiteral("country"), pod.country); + addParam<>(jo, QStringLiteral("phone_number"), pod.phoneNumber); + addParam<>(jo, QStringLiteral("send_attempt"), pod.sendAttempt); + addParam<IfNotEmpty>(jo, QStringLiteral("next_link"), pod.nextLink); + } + static void fillFrom(const QJsonObject& jo, RequestMsisdnValidation& pod) + { + fromJson(jo.value("client_secret"_ls), pod.clientSecret); + fromJson(jo.value("country"_ls), pod.country); + fromJson(jo.value("phone_number"_ls), pod.phoneNumber); + fromJson(jo.value("send_attempt"_ls), pod.sendAttempt); + fromJson(jo.value("next_link"_ls), pod.nextLink); + } }; } // namespace Quotient diff --git a/lib/identity/definitions/sid.cpp b/lib/identity/definitions/sid.cpp deleted file mode 100644 index 99fe9b59..00000000 --- a/lib/identity/definitions/sid.cpp +++ /dev/null @@ -1,17 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#include "sid.h" - -using namespace Quotient; - -void JsonObjectConverter<Sid>::dumpTo(QJsonObject& jo, const Sid& pod) -{ - addParam<>(jo, QStringLiteral("sid"), pod.sid); -} - -void JsonObjectConverter<Sid>::fillFrom(const QJsonObject& jo, Sid& result) -{ - fromJson(jo.value("sid"_ls), result.sid); -} diff --git a/lib/identity/definitions/sid.h b/lib/identity/definitions/sid.h deleted file mode 100644 index 981826f6..00000000 --- a/lib/identity/definitions/sid.h +++ /dev/null @@ -1,27 +0,0 @@ -/****************************************************************************** - * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN - */ - -#pragma once - -#include "converters.h" - -namespace Quotient { - -// Data structures - -struct Sid { - /// The session ID. Session IDs are opaque strings generated by the identity - /// server. They must consist entirely of the characters - /// ``[0-9a-zA-Z.=_-]``. Their length must not exceed 255 characters and - /// they must not be empty. - QString sid; -}; - -template <> -struct JsonObjectConverter<Sid> { - static void dumpTo(QJsonObject& jo, const Sid& pod); - static void fillFrom(const QJsonObject& jo, Sid& pod); -}; - -} // namespace Quotient diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 2519713e..3978dbcb 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -19,12 +19,11 @@ #include "basejob.h" #include "connectiondata.h" -#include "util.h" -#include <QtCore/QJsonObject> #include <QtCore/QRegularExpression> #include <QtCore/QTimer> #include <QtCore/QStringBuilder> +#include <QtCore/QMetaEnum> #include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkRequest> @@ -37,6 +36,7 @@ using namespace std::chrono_literals; BaseJob::StatusCode BaseJob::Status::fromHttpCode(int httpCode) { + // Based on https://en.wikipedia.org/wiki/List_of_HTTP_status_codes if (httpCode / 10 == 41) // 41x errors return httpCode == 410 ? IncorrectRequestError : NotFoundError; switch (httpCode) { @@ -113,6 +113,13 @@ public: } void sendRequest(); + /*! \brief Parse the response byte array into JSON + * + * This calls QJsonDocument::fromJson() on rawResponse, converts + * the QJsonParseError result to BaseJob::Status and stores the resulting + * JSON in jsonResponse. + */ + Status parseJson(); ConnectionData* connection = nullptr; @@ -131,9 +138,17 @@ public: // type QMimeType is of little help with MIME type globs (`text/*` etc.) QByteArrayList expectedContentTypes { "application/json" }; + QByteArrayList expectedKeys; + QScopedPointer<QNetworkReply, NetworkReplyDeleter> reply; Status status = Unprepared; QByteArray rawResponse; + /// Contains a null document in case of non-JSON body (for a successful + /// or unsuccessful response); a document with QJsonObject or QJsonArray + /// in case of a successful response with JSON payload, as per the API + /// definition (including an empty JSON object - QJsonObject{}); + /// and QJsonObject in case of an API error. + QJsonDocument jsonResponse; QUrl errorUrl; //< May contain a URL to help with some errors LoggingCategory logCat = JOBS; @@ -243,6 +258,19 @@ void BaseJob::setExpectedContentTypes(const QByteArrayList& contentTypes) d->expectedContentTypes = contentTypes; } +const QByteArrayList BaseJob::expectedKeys() const { return d->expectedKeys; } + +void BaseJob::addExpectedKey(const QByteArray& key) { d->expectedKeys << key; } + +void BaseJob::setExpectedKeys(const QByteArrayList& keys) +{ + d->expectedKeys = keys; +} + +const QNetworkReply* BaseJob::reply() const { return d->reply.data(); } + +QNetworkReply* BaseJob::reply() { return d->reply.data(); } + QUrl BaseJob::makeRequestUrl(QUrl baseUrl, const QString& path, const QUrlQuery& query) { @@ -304,7 +332,7 @@ void BaseJob::doPrepare() { } void BaseJob::onSentRequest(QNetworkReply*) { } -void BaseJob::beforeAbandon(QNetworkReply*) { } +void BaseJob::beforeAbandon() { } void BaseJob::initiate(ConnectionData* connData, bool inBackground) { @@ -346,40 +374,74 @@ void BaseJob::sendRequest() emit aboutToSendRequest(); d->sendRequest(); Q_ASSERT(d->reply); - connect(d->reply.data(), &QNetworkReply::finished, this, &BaseJob::gotReply); + connect(reply(), &QNetworkReply::finished, this, [this] { + gotReply(); + finishJob(); + }); if (d->reply->isRunning()) { - connect(d->reply.data(), &QNetworkReply::metaDataChanged, this, - &BaseJob::checkReply); - connect(d->reply.data(), &QNetworkReply::uploadProgress, this, + connect(reply(), &QNetworkReply::metaDataChanged, this, + [this] { checkReply(reply()); }); + connect(reply(), &QNetworkReply::uploadProgress, this, &BaseJob::uploadProgress); - connect(d->reply.data(), &QNetworkReply::downloadProgress, this, + connect(reply(), &QNetworkReply::downloadProgress, this, &BaseJob::downloadProgress); d->timer.start(getCurrentTimeout()); qCInfo(d->logCat).noquote() << "Sent" << d->dumpRequest(); - onSentRequest(d->reply.data()); + onSentRequest(reply()); emit sentRequest(); } else qCCritical(d->logCat).noquote() << "Request could not start:" << d->dumpRequest(); } +BaseJob::Status BaseJob::Private::parseJson() +{ + QJsonParseError error { 0, QJsonParseError::MissingObject }; + jsonResponse = QJsonDocument::fromJson(rawResponse, &error); + return { error.error == QJsonParseError::NoError + ? BaseJob::NoError + : BaseJob::IncorrectResponse, + error.errorString() }; +} + void BaseJob::gotReply() { - checkReply(); + setStatus(checkReply(reply())); + + if (status().good() + && d->expectedContentTypes == QByteArrayList { "application/json" }) { + d->rawResponse = reply()->readAll(); + setStatus(d->parseJson()); + if (status().good() && !expectedKeys().empty()) { + const auto& responseObject = jsonData(); + QByteArrayList missingKeys; + for (const auto& k: expectedKeys()) + if (!responseObject.contains(k)) + missingKeys.push_back(k); + if (!missingKeys.empty()) + setStatus(IncorrectResponse, tr("Required JSON keys missing: ") + + missingKeys.join()); + } + if (!status().good()) // Bad JSON in a "good" reply: bail out + return; + } // else { + // If the endpoint expects anything else than just (API-related) JSON + // reply()->readAll() is not performed and the whole reply processing + // is left to derived job classes: they may read it piecemeal or customise + // per content type in prepareResult(), or even have read it already + // (see, e.g., DownloadFileJob). + // } + if (status().good()) - setStatus(parseReply(d->reply.data())); + setStatus(prepareResult()); else { - d->rawResponse = d->reply->readAll(); - const auto jsonBody = d->reply->rawHeader("Content-Type") - == "application/json"; + d->rawResponse = reply()->readAll(); qCDebug(d->logCat).noquote() - << "Error body (truncated if long):" << d->rawResponse.left(500); - if (jsonBody) - setStatus( - parseError(d->reply.data(), - QJsonDocument::fromJson(d->rawResponse).object())); + << "Error body (truncated if long):" << rawDataSample(500); + // Parse the error payload and update the status if needed + if (const auto newStatus = prepareError(); !newStatus.good()) + setStatus(newStatus); } - finishJob(); } bool checkContentType(const QByteArray& type, const QByteArrayList& patterns) @@ -408,12 +470,10 @@ bool checkContentType(const QByteArray& type, const QByteArrayList& patterns) return false; } -BaseJob::Status BaseJob::doCheckReply(QNetworkReply* reply) const +BaseJob::Status BaseJob::checkReply(const QNetworkReply* reply) const { - // QNetworkReply error codes seem to be flawed when it comes to HTTP; - // see, e.g., https://github.com/quotient-im/libQuotient/issues/200 - // so check genuine HTTP codes. The below processing is based on - // https://en.wikipedia.org/wiki/List_of_HTTP_status_codes + // QNetworkReply error codes are insufficient for our purposes (e.g. they + // don't allow to discern HTTP code 429) so check the original code instead const auto httpCodeHeader = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); if (!httpCodeHeader.isValid()) { @@ -444,24 +504,23 @@ BaseJob::Status BaseJob::doCheckReply(QNetworkReply* reply) const return Status::fromHttpCode(httpCode, message); } -void BaseJob::checkReply() { setStatus(doCheckReply(d->reply.data())); } +BaseJob::Status BaseJob::prepareResult() { return Success; } -BaseJob::Status BaseJob::parseReply(QNetworkReply* reply) +BaseJob::Status BaseJob::prepareError() { - d->rawResponse = reply->readAll(); - QJsonParseError error { 0, QJsonParseError::MissingObject }; - const auto& json = QJsonDocument::fromJson(d->rawResponse, &error); - if (error.error == QJsonParseError::NoError) - return parseJson(json); + // Since it's an error, the expected content type is of no help; + // check the actually advertised content type instead + if (reply()->rawHeader("Content-Type") != "application/json") + return NoError; // Retain the status if the error payload is not JSON - return { IncorrectResponseError, error.errorString() }; -} + if (const auto status = d->parseJson(); !status.good()) + return status; -BaseJob::Status BaseJob::parseJson(const QJsonDocument&) { return Success; } + if (d->jsonResponse.isArray()) + return { IncorrectResponse, + tr("Malformed error JSON: an array instead of an object") }; -BaseJob::Status BaseJob::parseError(QNetworkReply* /*reply*/, - const QJsonObject& errorJson) -{ + const auto& errorJson = jsonData(); const auto errCode = errorJson.value("errcode"_ls).toString(); if (error() == TooManyRequestsError || errCode == "M_LIMIT_EXCEEDED") { QString msg = tr("Too many requests"); @@ -499,6 +558,16 @@ BaseJob::Status BaseJob::parseError(QNetworkReply* /*reply*/, return d->status; } +QJsonValue BaseJob::takeValueFromJson(const QString& key) +{ + if (!d->jsonResponse.isObject()) + return QJsonValue::Undefined; + auto o = d->jsonResponse.object(); + auto v = o.take(key); + d->jsonResponse.setObject(o); + return v; +} + void BaseJob::stop() { // This method is (also) used to semi-finalise the job before retrying; so @@ -616,10 +685,19 @@ QString BaseJob::rawDataSample(int bytesAtMost) const Q_ASSERT(data.size() <= d->rawResponse.size()); return data.size() == d->rawResponse.size() ? data - : data - + tr("...(truncated, %Ln bytes in total)", - "Comes after trimmed raw network response", - d->rawResponse.size()); + : data + tr("...(truncated, %Ln bytes in total)", + "Comes after trimmed raw network response", + d->rawResponse.size()); +} + +QJsonObject BaseJob::jsonData() const +{ + return d->jsonResponse.object(); +} + +QJsonArray BaseJob::jsonItems() const +{ + return d->jsonResponse.array(); } QString BaseJob::statusCaption() const @@ -704,7 +782,7 @@ void BaseJob::setStatus(int code, QString message) void BaseJob::abandon() { - beforeAbandon(d->reply ? d->reply.data() : nullptr); + beforeAbandon(); d->timer.stop(); d->retryTimer.stop(); // In case abandon() was called between retries setStatus(Abandoned); diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 6920ebc1..be2926be 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -18,13 +18,11 @@ #pragma once -#include "../logging.h" #include "requestdata.h" +#include "../logging.h" +#include "../converters.h" -#include <QtCore/QJsonDocument> #include <QtCore/QObject> -#include <QtCore/QUrlQuery> -#include <QtCore/QMetaEnum> class QNetworkReply; class QSslError; @@ -181,6 +179,49 @@ public: */ QString rawDataSample(int bytesAtMost = 65535) const; + /** Get the response body as a JSON object + * + * If the job's returned content type is not `application/json` + * or if the top-level JSON entity is not an object, an empty object + * is returned. + */ + QJsonObject jsonData() const; + + /** Get the response body as a JSON array + * + * If the job's returned content type is not `application/json` + * or if the top-level JSON entity is not an array, an empty array + * is returned. + */ + QJsonArray jsonItems() const; + + /** Load the property from the JSON response assuming a given C++ type + * + * If there's no top-level JSON object in the response or if there's + * no node with the key \p keyName, \p defaultValue is returned. + */ + template <typename T, typename StrT> // Waiting for QStringViews... + T loadFromJson(const StrT& keyName, T&& defaultValue = {}) const + { + const auto& jv = jsonData().value(keyName); + return jv.isUndefined() ? std::forward<T>(defaultValue) + : fromJson<T>(jv); + } + + /** Load the property from the JSON response and delete it from JSON + * + * If there's no top-level JSON object in the response or if there's + * no node with the key \p keyName, \p defaultValue is returned. + */ + template <typename T> + T takeFromJson(const QString& key, T&& defaultValue = {}) + { + if (const auto& jv = takeValueFromJson(key); !jv.isUndefined()) + return fromJson<T>(jv); + + return std::forward<T>(defaultValue); + } + /** Error (more generally, status) code * Equivalent to status().code * \sa status @@ -314,6 +355,12 @@ protected: const QByteArrayList& expectedContentTypes() const; void addExpectedContentType(const QByteArray& contentType); void setExpectedContentTypes(const QByteArrayList& contentTypes); + const QByteArrayList expectedKeys() const; + void addExpectedKey(const QByteArray &key); + void setExpectedKeys(const QByteArrayList &keys); + + const QNetworkReply* reply() const; + QNetworkReply* reply(); /** Construct a URL out of baseUrl, path and query * @@ -338,50 +385,42 @@ protected: * successfully sending a network request (including retries). */ virtual void onSentRequest(QNetworkReply*); - virtual void beforeAbandon(QNetworkReply*); + virtual void beforeAbandon(); - /*! \brief Check the pending or received reply for upfront issues + /*! \brief An extension point for additional reply processing. * - * This is invoked when headers are first received and also once - * the complete reply is obtained; the base implementation checks the HTTP - * headers to detect general issues such as network errors or access denial. - * It cannot read the response body (use parseReply/parseError to check - * for problems in the body). Returning anything except NoError/Success - * prevents further processing of the reply. - * - * @return the result of checking the reply + * The base implementation does nothing and returns Success. * - * @see gotReply + * \sa gotReply */ - virtual Status doCheckReply(QNetworkReply* reply) const; + virtual Status prepareResult(); - /** - * Processes the reply. By default, parses the reply into - * a QJsonDocument and calls parseJson() if it's a valid JSON. - * - * @param reply raw contents of a HTTP reply from the server + /*! \brief Process details of the error * - * @see gotReply, parseJson + * The function processes the reply in case when status from checkReply() + * was not good (usually because of an unsuccessful HTTP code). + * The base implementation assumes Matrix JSON error object in the body; + * overrides are strongly recommended to call it for all stock Matrix + * responses as early as possible but in addition can process custom errors, + * with JSON or non-JSON payload. */ - virtual Status parseReply(QNetworkReply* reply); + virtual Status prepareError(); - /** - * Processes the JSON document received from the Matrix server. - * By default returns successful status without analysing the JSON. + /*! \brief Get direct access to the JSON response object in the job * - * @param json valid JSON document received from the server + * This allows to implement deserialisation with "move" semantics for parts + * of the response. Assuming that the response body is a valid JSON object, + * the function calls QJsonObject::take(key) on it and returns the result. * - * @see parseReply - */ - virtual Status parseJson(const QJsonDocument&); - - /** - * Processes the reply in case of unsuccessful HTTP code. - * The body is already loaded from the reply object to errorJson. - * @param reply the HTTP reply from the server - * @param errorJson the JSON payload describing the error + * \return QJsonValue::Null, if the response content type is not + * advertised as `application/json`; + * QJsonValue::Undefined, if the response is a JSON object but + * doesn't have \p key; + * the value for \p key otherwise. + * + * \sa takeFromJson */ - virtual Status parseError(QNetworkReply*, const QJsonObject& errorJson); + QJsonValue takeValueFromJson(const QString& key); void setStatus(Status s); void setStatus(int code, QString message); @@ -397,9 +436,28 @@ protected: protected slots: void timeout(); + /*! \brief Check the pending or received reply for upfront issues + * + * This is invoked when headers are first received and also once + * the complete reply is obtained; the base implementation checks the HTTP + * headers to detect general issues such as network errors or access denial + * and it's strongly recommended to call it from overrides, + * as early as possible. + * This slot is const and cannot read the response body. If you need to read + * the body on the fly, override onSentRequest() and connect in it + * to reply->readyRead(); and if you only need to validate the body after + * it fully arrived, use prepareResult() for that). Returning anything + * except NoError/Success switches further processing from prepareResult() + * to prepareError(). + * + * @return the result of checking the reply + * + * @see gotReply + */ + virtual Status checkReply(const QNetworkReply *reply) const; + private slots: void sendRequest(); - void checkReply(); void gotReply(); friend class ConnectionData; // to provide access to sendRequest() diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 3e037680..7b4cf690 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -86,14 +86,14 @@ void DownloadFileJob::onSentRequest(QNetworkReply* reply) }); } -void DownloadFileJob::beforeAbandon(QNetworkReply*) +void DownloadFileJob::beforeAbandon() { if (d->targetFile) d->targetFile->remove(); d->tempFile->remove(); } -BaseJob::Status DownloadFileJob::parseReply(QNetworkReply*) +BaseJob::Status DownloadFileJob::prepareResult() { if (d->targetFile) { d->targetFile->close(); diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index 06dc145c..e00fd9e4 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -19,7 +19,7 @@ private: void doPrepare() override; void onSentRequest(QNetworkReply* reply) override; - void beforeAbandon(QNetworkReply*) override; - Status parseReply(QNetworkReply*) override; + void beforeAbandon() override; + Status prepareResult() override; }; } // namespace Quotient diff --git a/lib/jobs/mediathumbnailjob.cpp b/lib/jobs/mediathumbnailjob.cpp index df3763b2..33f4236c 100644 --- a/lib/jobs/mediathumbnailjob.cpp +++ b/lib/jobs/mediathumbnailjob.cpp @@ -48,12 +48,8 @@ QImage MediaThumbnailJob::scaledThumbnail(QSize toSize) const Qt::SmoothTransformation); } -BaseJob::Status MediaThumbnailJob::parseReply(QNetworkReply* reply) +BaseJob::Status MediaThumbnailJob::prepareResult() { - auto result = GetContentThumbnailJob::parseReply(reply); - if (!result.good()) - return result; - if (_thumbnail.loadFromData(data()->readAll())) return Success; diff --git a/lib/jobs/mediathumbnailjob.h b/lib/jobs/mediathumbnailjob.h index 75e2e55a..e6d39085 100644 --- a/lib/jobs/mediathumbnailjob.h +++ b/lib/jobs/mediathumbnailjob.h @@ -37,7 +37,7 @@ public: QImage scaledThumbnail(QSize toSize) const; protected: - Status parseReply(QNetworkReply* reply) override; + Status prepareResult() override; private: QImage _thumbnail; diff --git a/lib/jobs/syncjob.cpp b/lib/jobs/syncjob.cpp index cd7709e1..6b8cfe4b 100644 --- a/lib/jobs/syncjob.cpp +++ b/lib/jobs/syncjob.cpp @@ -49,9 +49,9 @@ SyncJob::SyncJob(const QString& since, const Filter& filter, int timeout, timeout, presence) {} -BaseJob::Status SyncJob::parseJson(const QJsonDocument& data) +BaseJob::Status SyncJob::prepareResult() { - d.parseJson(data.object()); + d.parseJson(jsonData()); if (d.unresolvedRooms().isEmpty()) return BaseJob::Success; diff --git a/lib/jobs/syncjob.h b/lib/jobs/syncjob.h index df419ba8..bf139a7b 100644 --- a/lib/jobs/syncjob.h +++ b/lib/jobs/syncjob.h @@ -33,7 +33,7 @@ public: SyncData&& takeData() { return std::move(d); } protected: - Status parseJson(const QJsonDocument& data) override; + Status prepareResult() override; private: SyncData d; |