/****************************************************************************** * Copyright (C) 2017 Kitsune Ral * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once #include "util.h" #include #include // Includes #include #include #include #include #include #include #include #if 0 // Waiting for C++17 # include template using optional = std::experimental::optional; #endif // Enable std::unordered_map namespace std { template <> struct hash { size_t operator()(const QString& s) const Q_DECL_NOEXCEPT { return qHash(s #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) , uint(qGlobalQHashSeed()) #endif ); } }; } // namespace std class QVariant; namespace QMatrixClient { template struct JsonObjectConverter { static void dumpTo(QJsonObject& jo, const T& pod) { jo = pod.toJson(); } static void fillFrom(const QJsonObject& jo, T& pod) { pod = T(jo); } }; template struct JsonConverter { static QJsonObject dump(const T& pod) { QJsonObject jo; JsonObjectConverter::dumpTo(jo, pod); return jo; } static T doLoad(const QJsonObject& jo) { T pod; JsonObjectConverter::fillFrom(jo, pod); return pod; } static T load(const QJsonValue& jv) { return doLoad(jv.toObject()); } static T load(const QJsonDocument& jd) { return doLoad(jd.object()); } }; template inline auto toJson(const T& pod) { return JsonConverter::dump(pod); } inline auto toJson(const QJsonObject& jo) { return jo; } template inline void fillJson(QJsonObject& json, const T& data) { JsonObjectConverter::dumpTo(json, data); } template inline T fromJson(const QJsonValue& jv) { return JsonConverter::load(jv); } template inline T fromJson(const QJsonDocument& jd) { return JsonConverter::load(jd); } // Convenience fromJson() overloads that deduce T instead of requiring // the coder to explicitly type it. They still enforce the // overwrite-everything semantics of fromJson(), unlike fillFromJson() template inline void fromJson(const QJsonValue& jv, T& pod) { pod = jv.isUndefined() ? T() : fromJson(jv); } template inline void fromJson(const QJsonDocument& jd, T& pod) { pod = fromJson(jd); } template inline void fillFromJson(const QJsonValue& jv, T& pod) { if (jv.isObject()) JsonObjectConverter::fillFrom(jv.toObject(), pod); else if (!jv.isUndefined()) pod = fromJson(jv); } // JsonConverter<> specialisations template struct TrivialJsonDumper { // Works for: QJsonValue (and all things it can consume), // QJsonObject, QJsonArray static auto dump(const T& val) { return val; } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return jv.toBool(); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return jv.toInt(); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return jv.toDouble(); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return float(jv.toDouble()); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return qint64(jv.toDouble()); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return jv.toString(); } }; template <> struct JsonConverter { static auto dump(const QDateTime& val) = delete; // not provided yet static auto load(const QJsonValue& jv) { return QDateTime::fromMSecsSinceEpoch(fromJson(jv), Qt::UTC); } }; template <> struct JsonConverter { static auto dump(const QDate& val) = delete; // not provided yet static auto load(const QJsonValue& jv) { return fromJson(jv).date(); } }; template <> struct JsonConverter : public TrivialJsonDumper { static auto load(const QJsonValue& jv) { return jv.toArray(); } }; template <> struct JsonConverter { static QString dump(const QByteArray& ba) { return ba.constData(); } static auto load(const QJsonValue& jv) { return fromJson(jv).toLatin1(); } }; template <> struct JsonConverter { static QJsonValue dump(const QVariant& v); static QVariant load(const QJsonValue& jv); }; template struct JsonConverter> { static QJsonValue dump(const Omittable& from) { return from.omitted() ? QJsonValue() : toJson(from.value()); } static Omittable load(const QJsonValue& jv) { if (jv.isUndefined()) return none; return fromJson(jv); } }; template struct JsonArrayConverter { static void dumpTo(QJsonArray& ar, const VectorT& vals) { for (const auto& v : vals) ar.push_back(toJson(v)); } static auto dump(const VectorT& vals) { QJsonArray ja; dumpTo(ja, vals); return ja; } static auto load(const QJsonArray& ja) { VectorT vect; vect.reserve(typename VectorT::size_type(ja.size())); for (const auto& i : ja) vect.push_back(fromJson(i)); return vect; } static auto load(const QJsonValue& jv) { return load(jv.toArray()); } static auto load(const QJsonDocument& jd) { return load(jd.array()); } }; template struct JsonConverter> : public JsonArrayConverter> {}; template struct JsonConverter> : public JsonArrayConverter> {}; template struct JsonConverter> : public JsonArrayConverter> {}; template <> struct JsonConverter : public JsonConverter> { static auto dump(const QStringList& sl) { return QJsonArray::fromStringList(sl); } }; template <> struct JsonObjectConverter> { static void dumpTo(QJsonObject& json, const QSet& s) { for (const auto& e : s) json.insert(toJson(e), QJsonObject {}); } static auto fillFrom(const QJsonObject& json, QSet& s) { s.reserve(s.size() + json.size()); for (auto it = json.begin(); it != json.end(); ++it) s.insert(it.key()); return s; } }; template struct HashMapFromJson { static void dumpTo(QJsonObject& json, const HashMapT& hashMap) { for (auto it = hashMap.begin(); it != hashMap.end(); ++it) json.insert(it.key(), toJson(it.value())); } static void fillFrom(const QJsonObject& jo, HashMapT& h) { h.reserve(jo.size()); for (auto it = jo.begin(); it != jo.end(); ++it) h[it.key()] = fromJson(it.value()); } }; template struct JsonObjectConverter> : public HashMapFromJson> {}; template struct JsonObjectConverter> : public HashMapFromJson> {}; // We could use std::conditional<> below but QT_VERSION* macros in C++ code // cause (kinda valid but useless and noisy) compiler warnings about // bitwise operations on signed integers; so use the preprocessor for now. using variant_map_t = #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) QVariantHash; #else QVariantMap; #endif template <> struct JsonConverter { static QJsonObject dump(const variant_map_t& vh); static QVariantHash load(const QJsonValue& jv); }; // Conditional insertion into a QJsonObject namespace _impl { template inline void addTo(QJsonObject& o, const QString& k, ValT&& v) { o.insert(k, toJson(v)); } template inline void addTo(QUrlQuery& q, const QString& k, ValT&& v) { q.addQueryItem(k, QStringLiteral("%1").arg(v)); } // OpenAPI is entirely JSON-based, which means representing bools as // textual true/false, rather than 1/0. inline void addTo(QUrlQuery& q, const QString& k, bool v) { q.addQueryItem(k, v ? QStringLiteral("true") : QStringLiteral("false")); } inline void addTo(QUrlQuery& q, const QString& k, const QStringList& vals) { for (const auto& v : vals) q.addQueryItem(k, v); } inline void addTo(QUrlQuery& q, const QString&, const QJsonObject& vals) { for (auto it = vals.begin(); it != vals.end(); ++it) q.addQueryItem(it.key(), it.value().toString()); } // This one is for types that don't have isEmpty() and for all types // when Force is true template struct AddNode { template static void impl(ContT& container, const QString& key, ForwardedT&& value) { addTo(container, key, std::forward(value)); } }; // This one is for types that have isEmpty() when Force is false template struct AddNode().isEmpty())> { template static void impl(ContT& container, const QString& key, ForwardedT&& value) { if (!value.isEmpty()) addTo(container, key, std::forward(value)); } }; // This one unfolds Omittable<> (also only when Force is false) template struct AddNode, false> { template static void impl(ContT& container, const QString& key, const OmittableT& value) { if (!value.omitted()) addTo(container, key, value.value()); } }; #if 0 // This is a special one that unfolds optional<> template struct AddNode, Force> { template static void impl(ContT& container, const QString& key, const OptionalT& value) { if (value) AddNode::impl(container, key, value.value()); else if (Force) // Edge case, no value but must put something AddNode::impl(container, key, QString{}); } }; #endif } // namespace _impl static constexpr bool IfNotEmpty = false; /*! Add a key-value pair to QJsonObject or QUrlQuery * * Adds a key-value pair(s) specified by \p key and \p value to * \p container, optionally (in case IfNotEmpty is passed for the first * template parameter) taking into account the value "emptiness". * With IfNotEmpty, \p value is NOT added to the container if and only if: * - it has a method `isEmpty()` and `value.isEmpty() == true`, or * - it's an `Omittable<>` and `value.omitted() == true`. * * If \p container is a QUrlQuery, an attempt to fit \p value into it is * made as follows: * - if \p value is a QJsonObject, \p key is ignored and pairs from \p value * are copied to \p container, assuming that the value in each pair * is a string; * - if \p value is a QStringList, it is "exploded" into a list of key-value * pairs with key equal to \p key and value taken from each list item; * - if \p value is a bool, its OpenAPI (i.e. JSON) representation is added * to the query (`true` or `false`, respectively). * * \tparam Force add the pair even if the value is empty. This is true * by default; passing IfNotEmpty or false for this parameter * enables emptiness checks as described above */ template inline void addParam(ContT& container, const QString& key, ValT&& value) { _impl::AddNode, Force>::impl(container, key, std::forward(value)); } } // namespace QMatrixClient