libQuotient
A Qt library for building matrix clients
Loading...
Searching...
No Matches
event.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net>
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#pragma once
5
7
8#include <Quotient/converters.h>
9#include <Quotient/function_traits.h>
10
11#include <span>
12
13namespace Quotient {
14// === event_ptr_tt<> and basic type casting facilities ===
15
16template <typename EventT>
18
19// === Standard Matrix key names ===
20
21constexpr inline auto TypeKey = "type"_L1;
22constexpr inline auto ContentKey = "content"_L1;
23constexpr inline auto SenderKey = "sender"_L1;
24constexpr inline auto UnsignedKey = "unsigned"_L1;
25
27
28// === EventMetaType ===
29
30class Event;
31
32template <typename EventT, typename BaseEventT = Event>
33concept EventClass = std::derived_from<EventT, BaseEventT>;
34
35template <EventClass EventT>
36bool is(const Event& e);
37
38//! \brief The base class for event metatypes
39//!
40//! You should not normally have to use this directly, unless you need to devise
41//! a whole new kind of event metatypes.
43public:
44 // The public fields here are const and are not to be changeable anyway.
45 // NOLINTBEGIN(misc-non-private-member-variables-in-classes)
46 const char* const className; ///< C++ class name this metatype is for
49 // NOLINTEND(misc-non-private-member-variables-in-classes)
50
51 explicit AbstractEventMetaType(const char* className,
53 const char* matrixId = nullptr)
55 {
56 if (nearestBase)
58 }
59
61 auto derivedTypes() const { return std::span(_derivedTypes); }
62
63 virtual ~AbstractEventMetaType() = default;
64
65protected:
66 // Allow template specialisations to call into one another
67 template <class EventT>
68 friend class EventMetaType;
69
70 // The returned value indicates whether a generic object has to be created
71 // on the top level when `event` is empty, instead of returning nullptr
72 virtual bool doLoadFrom(const QJsonObject& fullJson, const QString& type,
73 Event*& event) const = 0;
74
75private:
78};
79
80// Any event metatype is unique (note Q_DISABLE_COPY_MOVE above) so can be
81// identified by its address
82inline bool operator==(const AbstractEventMetaType& lhs,
83 const AbstractEventMetaType& rhs)
84{
85 return &lhs == &rhs;
86}
87
88//! \brief A family of event meta-types to load and match events
89//!
90//! TL;DR for the loadFrom() story:
91//! - for base event types, use QUO_BASE_EVENT and, if you have additional
92//! validation (e.g., JSON has to contain a certain key - see StateEvent
93//! for a real example), define it in the static EventT::isValid() member
94//! function accepting QJsonObject and returning bool.
95//! - for leaf (specific) event types - simply use QUO_EVENT and it will do
96//! everything necessary, including the TypeId definition.
97//! \sa QUO_EVENT, QUO_BASE_EVENT
98template <class EventT>
100 // Above: can't constrain EventT to be EventClass because it's incomplete
101 // at the point of EventMetaType<EventT> instantiation.
102public:
104
105 //! \brief Try to load an event from JSON, with dynamic type resolution
106 //!
107 //! The generic logic defined in this class template and invoked applies to
108 //! all event types defined in the library and boils down to the following:
109 //! 1.
110 //! a. If EventT has TypeId defined (which normally is a case of
111 //! all leaf - specific - event types, via QUO_EVENT macro) and
112 //! \p type doesn't exactly match it, nullptr is immediately returned.
113 //! b. In absence of TypeId, an event type is assumed to be a base;
114 //! its derivedTypes are examined, and this algorithm is applied
115 //! recursively on each.
116 //! 2. Optional validation: if EventT (or, due to the way inheritance works,
117 //! any of its base event types) has a static isValid() predicate and
118 //! the event JSON does not satisfy it, nullptr is immediately returned
119 //! to the upper level or to the loadFrom() caller. This is how existence
120 //! of `state_key` is checked in any type derived from StateEvent.
121 //! 3. If step 1b above returned non-nullptr, immediately return it.
122 //! 4.
123 //! a. If EventT::isValid() or EventT::TypeId (either, or both) exist and
124 //! are satisfied (see steps 1a and 2 above), an object of this type
125 //! is created from the passed JSON and returned. In case of a base
126 //! event type, this will be a generic (aka "unknown") event.
127 //! b. If neither exists, a generic event is only created and returned
128 //! when on the top level (i.e., outside of recursion into
129 //! derivedTypes); lower levels return nullptr instead and the type
130 //! lookup continues. The latter is a case of a derived base event
131 //! metatype (e.g. RoomEvent) called from its base event metatype
132 //! (i.e., Event). If no matching type derived from RoomEvent is found,
133 //! the nested lookup returns nullptr rather than a generic RoomEvent,
134 //! so that other types derived from Event could be examined.
136 const QString& type) const
137 {
138 Event* event = nullptr;
139 const bool goodEnough = doLoadFrom(fullJson, type, event);
140 if (!event && goodEnough)
141 return event_ptr_tt<EventT>{ new EventT(fullJson) };
142 return event_ptr_tt<EventT>{ static_cast<EventT*>(event) };
143 }
144
145private:
146 bool doLoadFrom(const QJsonObject& fullJson, const QString& type,
147 Event*& event) const override
148 {
149 if constexpr (requires { EventT::TypeId; }) {
150 if (EventT::TypeId != type)
151 return false;
152 } else {
153 for (const auto& p : _derivedTypes) {
155 if (event) {
157 return false;
158 }
159 }
160 }
161 if constexpr (requires { EventT::isValid; }) {
162 if (!EventT::isValid(fullJson))
163 return false;
164 } else if constexpr (!requires { EventT::TypeId; })
165 return true; // Create a generic event object if on the top level
166 event = new EventT(fullJson);
167 return false;
168 }
169};
170
171// === Event creation facilities ===
172
173//! \brief Create an event of arbitrary type from its arguments
174//!
175//! This should not be used to load events from JSON - use loadEvent() for that.
176//! \sa loadEvent
177template <EventClass EventT, typename... ArgTs>
178inline event_ptr_tt<EventT> makeEvent(ArgTs&&... args)
179{
180 return std::make_unique<EventT>(std::forward<ArgTs>(args)...);
181}
182
183template <EventClass EventT>
184constexpr const auto& mostSpecificMetaType()
185{
186 if constexpr (requires { EventT::MetaType; })
187 return EventT::MetaType;
188 else
189 return EventT::BaseMetaType;
190}
191
192//! \brief Create an event with proper type from a JSON object
193//!
194//! Use this factory template to detect the type from the JSON object
195//! contents (the detected event type should derive from the template
196//! parameter type) and create an event object of that type.
197template <EventClass EventT>
198inline event_ptr_tt<EventT> loadEvent(const QJsonObject& fullJson)
199{
200 return mostSpecificMetaType<EventT>().loadFrom(
201 fullJson, fullJson[TypeKey].toString());
202}
203
204//! \brief Create an event from a type string and content JSON
205//!
206//! Use this template to resolve the C++ type from the Matrix type string in
207//! \p matrixType and create an event of that type by passing all parameters
208//! to EventT::basicJson().
209template <EventClass EventT>
210inline event_ptr_tt<EventT> loadEvent(const QString& matrixType,
211 const auto&... otherBasicJsonParams)
212{
213 return mostSpecificMetaType<EventT>().loadFrom(
214 EventT::basicJson(matrixType, otherBasicJsonParams...), matrixType);
215}
216
217template <EventClass EventT>
220 // No dump() to avoid any ambiguity on whether a given export to JSON uses
221 // fullJson() or only contentJson()
223 static auto load(const QJsonObject& jo)
224 {
225 return loadEvent<EventT>(jo);
226 }
227};
228
229// === Event ===
230
232public:
233 static inline EventMetaType<Event> BaseMetaType { "Event" };
234 virtual const AbstractEventMetaType& metaType() const
235 {
236 return BaseMetaType;
237 }
238
240 Event(Event&&) noexcept = default;
241 Event& operator=(Event&&) = delete;
242 virtual ~Event();
243
244 /// Make a minimal correct Matrix event JSON
246 const QJsonObject& content)
247 {
248 return { { TypeKey, matrixType }, { ContentKey, content } };
249 }
250
251 //! \brief Exact Matrix type stored in JSON
253
254 template <EventClass EventT>
255 bool is() const
256 {
257 return Quotient::is<EventT>(*this);
258 }
259
260 //! \brief Apply one of the visitors based on the actual event type
261 //!
262 //! This function uses function_traits template and is() to find the first
263 //! of the passed visitor invocables that can be called with this event
264 //! object, downcasting `*this` in a type-safe way to the most specific type
265 //! accepted by the visitor. Without this function, you can still write
266 //! a stack of, for example,
267 //! `(else) if (const auto* evtPtr = eventCast<...>(baseEvtPtr))`
268 //! blocks but switchType() provides a more concise and isolating syntax:
269 //! there's no `else` or trailing `return/break` to forget, for one.
270 //! The visitors have to all return the same type (possibly void).
271 //! Here's how you might use this function:
272 //! \code
273 //! RoomEventPtr eptr = /* get the event pointer from somewhere */;
274 //! const auto result = eptr->switchOnType(
275 //! [](const RoomMemberEvent& memberEvent) {
276 //! // Do what's needed if eptr points to a RoomMemberEvent
277 //! return 1;
278 //! },
279 //! [](const CallEvent& callEvent) {
280 //! // Do what's needed if eptr points to a CallEvent or any
281 //! // class derived from it
282 //! return 2;
283 //! },
284 //! 3); /* the default value to return if nothing above matched */
285 //! \endcode
286 //! As the example shows, the last parameter can optionally be
287 //! a plain returned value instead of a visitor.
288 template <typename... VisitorTs>
289 auto switchOnType(VisitorTs&&... visitors) const;
290
291 const QJsonObject& fullJson() const { return _json; }
292
293 // According to the CS API spec, every event also has
294 // a "content" object; but since its structure is different for
295 // different types, we're implementing it per-event type.
296
297 // NB: const return types below are meant to catch accidental attempts
298 // to change event JSON (e.g., consider contentJson()["inexistentKey"]).
299
301
302 //! \brief Get a part of the content object, assuming a given type
303 //!
304 //! This retrieves the value under `content.<key>` from the event JSON and
305 //! then converts it to \p T using fromJson().
306 //! \sa contentJson, fromJson
307 template <typename T, typename KeyT>
308 const T contentPart(KeyT&& key) const
309 {
310 return fromJson<T>(contentJson()[std::forward<KeyT>(key)]);
311 }
312
314
315 //! \brief Get a part of the unsigned object, assuming a given type
316 //!
317 //! This retrieves the value under `unsigned.<key>` from the event JSON and
318 //! then converts it to \p T using fromJson().
319 //! \sa unsignedJson, fromJson
320 template <typename T, typename KeyT>
321 const T unsignedPart(KeyT&& key) const
322 {
323 return fromJson<T>(unsignedJson()[std::forward<KeyT>(key)]);
324 }
325
327 {
328 const QDebugStateSaver _dss { dbg };
330 << e.matrixType() << '(' << e.metaType().className << "): ";
331 e.dumpTo(dbg);
332 return dbg;
333 }
334
335#if Quotient_VERSION_MAJOR == 0 && Quotient_VERSION_MINOR <= 9
336 [[deprecated("isStateEvent() has moved to RoomEvent")]] bool isStateEvent() const;
337#endif
338
339protected:
340 friend class EventMetaType<Event>; // To access the below constructor
341
342 explicit Event(const QJsonObject& json);
343
345 virtual void dumpTo(QDebug dbg) const;
346
347private:
349};
351
352template <EventClass EventT>
355
356// === Facilities for event class definitions ===
357
358//! \brief A template base class to derive your event type from
359//!
360//! This simple class template generates commonly used event constructor
361//! signatures and the content() method with the appropriate return type.
362//! The generic version here is only used with non-trivial \p ContentT (if you
363//! don't need to create an event from its content structure, just go and derive
364//! straight from the respective \p EventBaseT instead of using EventTemplate);
365//! specialisations may override that and provide useful semantics even without
366//! \p ContentT (see EventTemplate<CallEvent>, e.g.).
367//!
368//! The template uses CRTP to pick the event type id from the actual class;
369//! it will fail to compile if \p EventT doesn't provide TypeId. It also uses
370//! the base event type's basicJson(); if you need extra keys to be inserted
371//! you may want to bypass this template as writing the code to that effect in
372//! your class will likely be clearer and more concise.
373//! \sa https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
374//! \sa DEFINE_SIMPLE_EVENT
375template <typename EventT, EventClass BaseEventT, typename ContentT = void>
376class EventTemplate : public BaseEventT {
377 // Above: can't constrain EventT to be EventClass because it's incomplete
378 // by CRTP definition.
379public:
380 static_assert(
381 !std::is_same_v<ContentT, void>,
382 "If you see this, you tried to use EventTemplate with the default"
383 " ContentT type, which is void. This default is only used with explicit"
384 " specialisations (see CallEvent, e.g.). Otherwise, if you don't intend"
385 " to use the content part of EventTemplate then you don't need"
386 " EventTemplate; just use the base event class directly");
387 using content_type = ContentT;
388
389 explicit EventTemplate(const QJsonObject& json)
391 {}
392 explicit EventTemplate(const ContentT& c)
394 {}
395
396 ContentT content() const { return fromJson<ContentT>(this->contentJson()); }
397};
398
399//! \brief Supply event metatype information in base event types
400//!
401//! Use this macro in a public section of your base event class to provide
402//! type identity and enable dynamic loading of generic events of that type.
403//! Do _not_ add this macro if your class is an intermediate wrapper and is not
404//! supposed to be instantiated on its own. Provides BaseMetaType static field
405//! initialised by parameters passed to the macro, and a metaType() override
406//! pointing to that BaseMetaType.
407//! \sa EventMetaType
408#define QUO_BASE_EVENT(CppType_, BaseCppType_, ...)
409 friend class EventMetaType<CppType_>;
410 static inline EventMetaType<CppType_> BaseMetaType{
411 #CppType_, &BaseCppType_::BaseMetaType __VA_OPT__(, ) __VA_ARGS__
412 };
413 static_assert(&CppType_::BaseMetaType == &BaseMetaType,
414 #CppType_ " is wrong here - check for copy-pasta");
415 const AbstractEventMetaType& metaType() const override
416 {
417 return BaseMetaType;
418 }
419 // End of macro
420
421//! \brief Supply event metatype information in (specific) event types
422//!
423//! Use this macro in a public section of your event class to provide type
424//! identity and enable dynamic loading of generic events of that type.
425//! Do _not_ use this macro if your class is an intermediate wrapper and is not
426//! supposed to be instantiated on its own. Provides MetaType static field
427//! initialised as described below; a metaType() override pointing to it; and
428//! the TypeId static field that is equal to MetaType.matrixId.
429//!
430//! The first two macro parameters are used as the first two EventMetaType
431//! constructor parameters; the third EventMetaType parameter is always
432//! BaseMetaType; and additional base types can be passed in extra macro
433//! parameters if you need to include the same event type in more than one
434//! event factory hierarchy (e.g., EncryptedEvent).
435//! \sa EventMetaType
436#define QUO_EVENT(CppType_, MatrixType_)
437 friend class EventMetaType<CppType_>;
438 static inline const EventMetaType<CppType_> MetaType{ #CppType_,
439 &BaseMetaType,
440 MatrixType_ };
441 static_assert(&CppType_::MetaType == &MetaType,
442 #CppType_ " is wrong here - check for copy-pasta");
443 static inline const auto& TypeId = MetaType.matrixId;
444 const AbstractEventMetaType& metaType() const override
445 {
446 return MetaType;
447 }
448 // End of macro
449
450#define QUO_CONTENT_GETTER_X(PartType_, PartName_, JsonKey_)
451 PartType_ PartName_() const
452 {
453 static const auto PartName_##JsonKey = JsonKey_;
454 return contentPart<PartType_>(PartName_##JsonKey);
455 }
456
457//! \brief Define an inline method obtaining a content part
458//!
459//! This macro adds a const method that extracts a JSON value at the key
460//! <tt>toSnakeCase(PartName_)</tt> (sic) and converts it to the type
461//! \p PartType_. Effectively, the generated method is an equivalent of
462//! \code
463//! contentPart<PartType_>(Quotient::toSnakeCase(#PartName_##_L1));
464//! \endcode
465#define QUO_CONTENT_GETTER(PartType_, PartName_)
466 QUO_CONTENT_GETTER_X(PartType_, PartName_, toSnakeCase(#PartName_##_L1))
467
468/// \brief Define a new event class with a single key-value pair in the content
469///
470/// This macro defines a new event class \p Name_ derived from \p Base_,
471/// with Matrix event type \p TypeId_, providing a getter named \p GetterName_
472/// for a single value of type \p ValueType_ inside the event content.
473/// To retrieve the value the getter uses a JSON key name that corresponds to
474/// its own (getter's) name but written in snake_case. \p GetterName_ must be
475/// in camelCase, no quotes (an identifier, not a literal).
476#define DEFINE_SIMPLE_EVENT(Name_, Base_, TypeId_, ValueType_, GetterName_, JsonKey_)
477 constexpr inline auto Name_##ContentKey = JsonKey_##_L1;
478 class QUOTIENT_API Name_
479 : public ::Quotient::EventTemplate<
480 Name_, Base_, EventContent::SingleKeyValue<ValueType_, Name_##ContentKey>> {
481 public:
482 QUO_EVENT(Name_, TypeId_)
483 using value_type = ValueType_;
484 using EventTemplate::EventTemplate;
485 QUO_CONTENT_GETTER_X(ValueType_, GetterName_, Name_##ContentKey)
486 };
487 // End of macro
488
489// === is<>(), eventCast<>() and switchOnType<>() ===
490
491template <EventClass EventT>
492inline bool is(const Event& e)
493{
494 // Protect against accidental putting QUO_*EVENT to a private section
495 static_assert(requires { &EventT::metaType; },
496 "Event class doesn't have a public metaType() override - "
497 "did you misplace the QUO_*EVENT macro?");
498 if constexpr (requires { EventT::MetaType; }) {
499 return &e.metaType() == &EventT::MetaType;
500 } else {
501 const auto* p = &e.metaType();
502 do {
503 if (p == &EventT::BaseMetaType)
504 return true;
505 } while ((p = p->baseType) != nullptr);
506 return false;
507 }
508}
509
510//! \brief Cast the event pointer down in a type-safe way
511//!
512//! Checks that the event \p eptr points to actually is of the requested type
513//! and returns a (plain) pointer to the event downcast to that type. \p eptr
514//! can be either "dumb" (BaseEventT*) or "smart" (`event_ptr_tt<>`). This
515//! overload doesn't affect the event ownership - if the original pointer owns
516//! the event it must outlive the downcast pointer to keep it from dangling.
517template <EventClass EventT, typename BasePtrT>
518inline auto eventCast(const BasePtrT& eptr)
519 -> decltype(static_cast<EventT*>(std::to_address(eptr)))
520{
521 return eptr && is<std::decay_t<EventT>>(*eptr)
522 ? static_cast<EventT*>(std::to_address(eptr))
523 : nullptr;
524}
525
526//! \brief Cast the event pointer down in a type-safe way, with moving
527//!
528//! Checks that the event \p eptr points to actually is of the requested type;
529//! if (and only if) it is, releases the pointer, downcasts it to the requested
530//! event type and returns a new smart pointer wrapping the downcast one.
531//! Unlike the non-moving eventCast() overload, this one only accepts a smart
532//! pointer, and that smart pointer should be an rvalue (either a temporary,
533//! or as a result of std::move()). The ownership, respectively, is transferred
534//! to the new pointer; the original smart pointer is reset to nullptr, as is
535//! normal for `unique_ptr<>::release()`.
536//! \note If \p eptr's event type does not match \p EventT it retains ownership
537//! after calling this overload; if it is a temporary, this normally
538//! leads to the event getting deleted along with the end of
539//! the temporary's lifetime.
540template <EventClass EventT, typename BaseEventT>
541inline auto eventCast(event_ptr_tt<BaseEventT>&& eptr)
542{
543 return eptr && is<std::decay_t<EventT>>(*eptr)
544 ? event_ptr_tt<EventT>(static_cast<EventT*>(eptr.release()))
545 : nullptr;
546}
547
548namespace _impl {
549 template <typename FnT, typename BaseT>
550 concept Invocable_With_Downcast =
551 EventClass<BaseT> && std::derived_from<std::remove_cvref_t<fn_arg_t<FnT>>, BaseT>;
552}
553
554template <EventClass BaseT, typename TailT>
555inline auto switchOnType(const BaseT& event, TailT&& tail)
556{
557 if constexpr (std::is_invocable_v<TailT, BaseT>) {
558 return tail(event);
559 } else if constexpr (_impl::Invocable_With_Downcast<TailT, BaseT>) {
560 using event_type = fn_arg_t<TailT>;
561 if (is<std::decay_t<event_type>>(event))
562 return tail(static_cast<event_type>(event));
563 return std::invoke_result_t<TailT, event_type>(); // Default-constructed
564 } else { // Treat it as a value to return
565 return std::forward<TailT>(tail);
566 }
567}
568
569template <typename FnT1, typename... FnTs>
570inline auto switchOnType(const EventClass auto& event, FnT1&& fn1, FnTs&&... fns)
571{
572 using event_type1 = fn_arg_t<FnT1>;
573 if (is<std::decay_t<event_type1>>(event))
574 return fn1(static_cast<event_type1>(event));
575 return switchOnType(event, std::forward<FnTs>(fns)...);
576}
577
578template <typename... VisitorTs>
579inline auto Event::switchOnType(VisitorTs&&... visitors) const
580{
581 return Quotient::switchOnType(*this,
583}
584
585} // namespace Quotient
586Q_DECLARE_METATYPE(Quotient::Event*)
587Q_DECLARE_METATYPE(const Quotient::Event*)
The base class for event metatypes.
Definition event.h:42
A family of event meta-types to load and match events.
Definition event.h:99
A template base class to derive your event type from.
Definition event.h:376
EventTemplate(const QJsonObject &json)
Definition event.h:389
ContentT content() const
Definition event.h:396
EventTemplate(const ContentT &c)
Definition event.h:392
#define QUO_CONTENT_GETTER_X(PartType_, PartName_, JsonKey_)
Definition event.h:450
#define QUO_EVENT(CppType_, MatrixType_)
Supply event metatype information in (specific) event types.
Definition event.h:436
auto eventCast(event_ptr_tt< BaseEventT > &&eptr)
Cast the event pointer down in a type-safe way, with moving.
Definition event.h:541
constexpr auto ContentKey
Definition event.h:22
auto eventCast(const BasePtrT &eptr) -> decltype(static_cast< EventT * >(std::to_address(eptr)))
Cast the event pointer down in a type-safe way.
Definition event.h:518
event_ptr_tt< EventT > makeEvent(ArgTs &&... args)
Create an event of arbitrary type from its arguments.
Definition event.h:178
event_ptr_tt< EventT > loadEvent(const QString &matrixType, const auto &... otherBasicJsonParams)
Create an event from a type string and content JSON.
Definition event.h:210
constexpr auto TypeKey
Definition event.h:21
constexpr const auto & mostSpecificMetaType()
Definition event.h:184
auto switchOnType(const EventClass auto &event, FnT1 &&fn1, FnTs &&... fns)
Definition event.h:570
constexpr auto UnsignedKey
Definition event.h:24
auto switchOnType(const BaseT &event, TailT &&tail)
Definition event.h:555
bool operator==(const LoginFlow &lhs, const LoginFlow &rhs)
Definition connection.h:70
bool is(const Event &e)
Definition event.h:492
event_ptr_tt< EventT > loadEvent(const QJsonObject &fullJson)
Create an event with proper type from a JSON object.
Definition event.h:198
constexpr auto SenderKey
Definition event.h:23
#define QUOTIENT_API