libQuotient
A Qt library for building matrix clients
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 
13 namespace Quotient {
14 // === event_ptr_tt<> and basic type casting facilities ===
15 
16 template <typename EventT>
17 using event_ptr_tt = std::unique_ptr<EventT>;
18 
19 // === Standard Matrix key names ===
20 
21 constexpr inline auto TypeKey = "type"_L1;
22 constexpr inline auto ContentKey = "content"_L1;
23 constexpr inline auto SenderKey = "sender"_L1;
24 constexpr inline auto UnsignedKey = "unsigned"_L1;
25 
26 using event_type_t = QLatin1String;
27 
28 // === EventMetaType ===
29 
30 class Event;
31 
32 template <typename EventT, typename BaseEventT = Event>
33 concept EventClass = std::derived_from<EventT, BaseEventT>;
34 
35 template <EventClass EventT>
36 bool 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.
42 class QUOTIENT_API AbstractEventMetaType {
43 public:
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
47  const AbstractEventMetaType* const baseType;
48  const event_type_t matrixId;
49  // NOLINTEND(misc-non-private-member-variables-in-classes)
50 
51  explicit AbstractEventMetaType(const char* className,
52  AbstractEventMetaType* nearestBase = nullptr,
53  const char* matrixId = nullptr)
54  : className(className), baseType(nearestBase), matrixId(matrixId)
55  {
56  if (nearestBase)
57  nearestBase->addDerived(this);
58  }
59 
60  void addDerived(const AbstractEventMetaType* newType);
61  auto derivedTypes() const { return std::span(_derivedTypes); }
62 
63  virtual ~AbstractEventMetaType() = default;
64 
65 protected:
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 
75 private:
76  std::vector<const AbstractEventMetaType*> _derivedTypes{};
77  Q_DISABLE_COPY_MOVE(AbstractEventMetaType)
78 };
79 
80 // Any event metatype is unique (note Q_DISABLE_COPY_MOVE above) so can be
81 // identified by its address
82 inline 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
98 template <class EventT>
99 class QUOTIENT_API EventMetaType : public AbstractEventMetaType {
100  // Above: can't constrain EventT to be EventClass because it's incomplete
101  // at the point of EventMetaType<EventT> instantiation.
102 public:
103  using AbstractEventMetaType::AbstractEventMetaType;
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.
135  event_ptr_tt<EventT> loadFrom(const QJsonObject& fullJson,
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 
145 private:
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) {
154  p->doLoadFrom(fullJson, type, event);
155  if (event) {
156  Q_ASSERT(is<EventT>(*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
177 template <EventClass EventT, typename... ArgTs>
178 inline event_ptr_tt<EventT> makeEvent(ArgTs&&... args)
179 {
180  return std::make_unique<EventT>(std::forward<ArgTs>(args)...);
181 }
182 
183 template <EventClass EventT>
184 constexpr 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.
197 template <EventClass EventT>
198 inline 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().
209 template <EventClass EventT>
210 inline 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 
217 template <EventClass EventT>
218 struct JsonConverter<event_ptr_tt<EventT>>
219  : JsonObjectUnpacker<event_ptr_tt<EventT>> {
220  // No dump() to avoid any ambiguity on whether a given export to JSON uses
221  // fullJson() or only contentJson()
222  using JsonObjectUnpacker<event_ptr_tt<EventT>>::load;
223  static auto load(const QJsonObject& jo)
224  {
225  return loadEvent<EventT>(jo);
226  }
227 };
228 
229 // === Event ===
230 
231 class QUOTIENT_API Event {
232 public:
233  static inline EventMetaType<Event> BaseMetaType { "Event" };
234  virtual const AbstractEventMetaType& metaType() const
235  {
236  return BaseMetaType;
237  }
238 
239  Q_DISABLE_COPY(Event)
240  Event(Event&&) noexcept = default;
241  Event& operator=(Event&&) = delete;
242  virtual ~Event();
243 
244  /// Make a minimal correct Matrix event JSON
245  static QJsonObject basicJson(const QString& matrixType,
246  const QJsonObject& content)
247  {
248  return { { TypeKey, matrixType }, { ContentKey, content } };
249  }
250 
251  //! \brief Exact Matrix type stored in JSON
252  QString matrixType() const;
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 
300  const QJsonObject contentJson() const;
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 
313  const QJsonObject unsignedJson() const;
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 
326  friend QUOTIENT_API QDebug operator<<(QDebug dbg, const Event& e)
327  {
328  const QDebugStateSaver _dss { dbg };
329  dbg.noquote().nospace()
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 
339 protected:
340  friend class EventMetaType<Event>; // To access the below constructor
341 
342  explicit Event(const QJsonObject& json);
343 
344  QJsonObject& editJson() { return _json; }
345  virtual void dumpTo(QDebug dbg) const;
346 
347 private:
348  QJsonObject _json;
349 };
350 using EventPtr = event_ptr_tt<Event>;
351 
352 template <EventClass EventT>
353 using EventsArray = std::vector<event_ptr_tt<EventT>>;
354 using Events = EventsArray<Event>;
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
375 template <typename EventT, EventClass BaseEventT, typename ContentT = void>
376 class EventTemplate : public BaseEventT {
377  // Above: can't constrain EventT to be EventClass because it's incomplete
378  // by CRTP definition.
379 public:
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)
390  : BaseEventT(json)
391  {}
392  explicit EventTemplate(const ContentT& c)
393  : BaseEventT(EventT::basicJson(EventT::TypeId, toJson(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 
491 template <EventClass EventT>
492 inline 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.
517 template <EventClass EventT, typename BasePtrT>
518 inline 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.
540 template <EventClass EventT, typename BaseEventT>
541 inline 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 
548 namespace _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 
554 template <EventClass BaseT, typename TailT>
555 inline 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 
569 template <typename FnT1, typename... FnTs>
570 inline 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 
578 template <typename... VisitorTs>
579 inline auto Event::switchOnType(VisitorTs&&... visitors) const
580 {
581  return Quotient::switchOnType(*this,
582  std::forward<VisitorTs>(visitors)...);
583 }
584 
585 } // namespace Quotient
586 Q_DECLARE_METATYPE(Quotient::Event*)
587 Q_DECLARE_METATYPE(const Quotient::Event*)