| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef REMOTING_HOST_LINUX_GVARIANT_REF_H_ |
| #define REMOTING_HOST_LINUX_GVARIANT_REF_H_ |
| |
| #include <glib-object.h> |
| #include <glib.h> |
| #include <glibconfig.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <compare> |
| #include <concepts> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstring> |
| #include <iterator> |
| #include <map> |
| #include <optional> |
| #include <ranges> |
| #include <string> |
| #include <string_view> |
| #include <tuple> |
| #include <type_traits> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/compiler_specific.h" |
| #include "base/location.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/types/expected.h" |
| #include "remoting/host/base/loggable.h" |
| #include "remoting/host/base/pointer_utils.h" |
| #include "remoting/host/linux/gvariant_type.h" |
| |
| // Provides a scoped wrapper, GVariantRef, around a GLib Variant to handle |
| // reference counting, conversion to and from other C++ types, and additional |
| // convenience methods when the GVariant's type is statically known. |
| // |
| // A good familiarity with the GVariant type system is recommended to use or |
| // read this code: https://docs.gtk.org/glib/struct.VariantType.html |
| |
| namespace remoting { |
| namespace gvariant { |
| |
| // A GVariantRef is a wrapper around a GLib Variant that handles scoped |
| // reference counting and convenient, typesafe conversion between GVariants and |
| // standard types. |
| // |
| // GVariants are logically immutable once created, and this class's only data |
| // member is a reference-counted pointer. Thus, instances can be copied and |
| // passed around cheaply. |
| // |
| // Sample usage: |
| // |
| // Creating a GVariantRef: |
| // |
| // // Creates a GVariantRef with a compile-time type of "*" (can hold any |
| // // value) and initialize it with a value of type "as" (array of strings) |
| // // constructed from the provided span. |
| // GVariantRef<> variant = |
| // GVariantRef<>::From(base::span{{"Bob", "Sally", "Alice"}}); |
| // |
| // If a type string is provided as a template argument, the GVariantRef may only |
| // hold values that match that type string, which may be indefinite. E.g., a |
| // GVariantRef<"s"> may only strings, while a GVariantRef<"{?*}"> may hold any |
| // dictionary entry. The type may be narrowed using TryFrom: |
| // |
| // GVariantRef<> variant = ...; |
| // // expectedVariant will be a base::expected with either the requested |
| // // GVariantRef or an error message suitable for logging. |
| // auto expectedVariant = GVariantRef<"as">::TryFrom(variant); |
| // if (expectedVariant.has_value()) { |
| // // expectedVariant.value() is guaranteed to be an array of strings |
| // // and can be iterated through, infalibly converted to a |
| // // std::vector<std::string>, passed to functions expected GVariantRef |
| // // arrays, et cetera. |
| // } |
| // |
| // Conversion can combine narrowing and converting from C++ types: |
| // |
| // std::tuple<GVariantRef<>> tuple = ...; |
| // auto expectedVariant = GVariantRef<"(s)">::TryFrom(tuple); |
| // |
| // Getting values from a GVariantRef: |
| // |
| // GVariantRef<> variant = ...; |
| // // Returns a base::expected with the value or an error message. |
| // auto value = result.TryInto<std::vector<std::string>>(); |
| // |
| // If the requested conversion is valid for all values of the GVariantRef's |
| // static type, you can use Into instead to get the value directly: |
| // |
| // GVariantRef<"s"> variant = ...; |
| // std::string value = variant.Into<std::string>(); |
| // |
| // Like TryFrom, TryInto can also be used to attempt to narrow a GVariantRef's |
| // static type, and can be combined with conversions to C++ types: |
| // |
| // GVariantRef<> variant = ...; // Compile-time type string is "*"; |
| // // Value will be returned if variant's run-time type is a subtype of |
| // // "a(s*)". Otherwise, an error string will be returned. |
| // auto result2 = variant.TryInto<std::vector<GVariantRef<"(s*)">>>(); |
| // |
| // If the GVariantRef is known to be a container (that is, it's static type is |
| // a boxed value, an array, a maybe value, a tuple, or a dictionary entry), it |
| // can be used with range-based for: |
| // |
| // GVariantRef<"a(iu)"> variant1 = ...; |
| // for (auto item : variant1) { /* item is a GVariantRef<"(iu)"> */} |
| // GVariantRef<"r"> variant2 = ...; |
| // for (auto item : variant1) { /* item is a GVariantRef<"*"> */} |
| // |
| // If it is known to be a container of a fixed size (boxed value, fixed-size |
| // tuple (not "r"), or dictionary entry), it can be used with structured |
| // binding: |
| // |
| // GVariantRef<"(uba{sv})"> variant = ...; |
| // // Yields GVariantRef<"u">, GVariantRef<"b">, and GVariantRef<"a{sv}"> |
| // auto [first, second, third] = variant; |
| // |
| // For convenience, GVariantRefs known to be a dictionary type (the compile-time |
| // type string is a subtype of "a{?*}") provide a LookUp() method to find the |
| // first value with a matching key. |
| // |
| // GVariantRef<"a{is}"> variant = ...; |
| // std::optional<GVariantRef<"s">> value = variant.LookUp(5); |
| // |
| // Finally, values can be extracted from containers using Destructure() and |
| // TryDestructure(). Destructure requires a known fixed-size container, while |
| // TryDestructure checks if the GVariantRef holds a matching container at |
| // runtime. |
| // |
| // GVariantRef<"(ii(ssssv))"> variant = ...; |
| // std::uint32_t i1, i2; |
| // std::string s1, s2, s3, s4; |
| // GVariantRef<> v; |
| // // Inner containers can be unpacked by passing (possibly nested) tuples |
| // // of lvalue references. |
| // variant.Destructure(i1, i2, std::forward_as_tuple(s1, s2, s3, s4, |
| // std::tie(v))); |
| // |
| // GVariantRef<"mb"> variant = ...; |
| // bool b; |
| // // result will contain an error if the maybe value is unexpectedly empty. |
| // base::expected<void, Loggable> result = variant.TryDestructure(b); |
| // |
| // Access to the underlying GVariant pointer can be obtained via raw() and |
| // release(). A GVariantRef can be created from a raw GVariant pointer via |
| // Ref(), RefSink(), and Take(). |
| // |
| // For convenience, the GVariantRef class is exported to the remoting namespace. |
| // |
| // This header provides conversions to and from the following types. Additional |
| // support for additional types can be added by providing an appropriate |
| // specialization of gvariant::Mapping<T>. |
| // |
| // Basic fixed types: |
| // |
| // bool (type code "b") |
| // std::uint8_t (type code "y") |
| // std::int16_t (type code "n") |
| // std::uint16_t (type code "q") |
| // std::int32_t (type code "i") |
| // std::uint32_t (type code "u") |
| // std::int64_t (type code "x") |
| // std::uint64_t (type code "t") |
| // double (type code "d") |
| // |
| // Strings: |
| // |
| // std::string (type code "s") |
| // const char * ([Try]From() only, type code "s") |
| // std::string_view ([Try]From() only, type code "s") |
| // |
| // Note: Strings must be valid UTF-8. For convenience, both From() and TryFrom() |
| // are provided. If the string (or all strings in an aggregate type) are |
| // known to be valid UTF-8, From() may be used. Otherwise TryFrom() should be |
| // used. Calling From() with a string containing invalid UTF-8 will |
| // result in a crash. |
| // |
| // Containers: |
| // |
| // std::optional<T> (type code "mT") - To use with From(), the type string of T |
| // must be definite. E.g., it may not contain an unboxed heterogeneous |
| // std::variant or unconstrained GVariantRef. Otherwise, TryFrom() must be |
| // used. Alternatively, GVariantRef::FilledMaybe can be used for indefinite |
| // types that will never actually be absent. Note that maybe values are not |
| // currently supported by D-Bus. |
| // std::vector<T> (type code "aT") - To use with From(), the type string of T |
| // must be definite. Otherwise, TryFrom() must be used. |
| // std::map<K, T> (type code "a{KT}") - To use with From(), the type string of K |
| // and T must be definite. Otherwise, TryFrom() must be used. |
| // std::pair<K, T> (type code "{KT}") |
| // std::ranges::range<T> ([Try]From() only, type code "aE" where |
| // E = std::ranges::range_value_t<T>) - The type string of T must be |
| // definite to use with From(). Otherwise, TryFrom() must be used. |
| // std::tuple<...> (type code "(...)") |
| // std::variant<...> (type code "S", where S is the narrowest common supertype |
| // of all of the variant alternatives) - Despite the name, when used with |
| // From(), the type will be the type of the active variant, not "v". If the |
| // type should be "v", use a gvariant::Boxed<std::variant<...>>. When used |
| // with [Try]Into(), each variant alternative will be |
| // tried in turn until one succeeds or all are exhausted. Similarly to |
| // From(), if the type in the GVariant is expected to be "v" rather than a |
| // concrete type, use with gvariant::Boxed. From() and TryFrom() are |
| // provided if all of the alternatives provide the respected method. |
| // TryInto() is provided if provided by any of the alternatives. Into() is |
| // provided if the GVariantRef at hand can be infallibly converted to any |
| // of the variant's alternatives. E.g., the following is valid: |
| // |
| // GVariantRef<"i"> gvariant = ...; |
| // auto v = gvariant.Into<std::variant<std::int32, bool>>(); |
| // |
| // Special types: |
| // |
| // GVariantRef<C> (type code "C") - GVariantRef can itself be used with |
| // [Try]From() or [Try]Into(). This can be used to widen or narrow the |
| // type or to hold inner values as nested GVariants. Like with |
| // std::variant, the type of the GVariantRef is used directly. If the |
| // expected/desired type is "v", wrap it in a gvariant::Boxed. |
| // gvariant::Ignored ([Try]Into() only, type code "*") - Discard the value at |
| // this position instead of decoding it. |
| // decltype(std::ignore) - Same as gvariant::Ignored. Allows passing std::ignore |
| // to [Try]Destructure(). |
| // gvariant::Boxed<T> (type code "v") - Wrapper for a value that appears boxed |
| // inside a nested variant, rather than directly. A Boxed<T> is definite |
| // regardless of whether the contained type is. Provides [Try]From() and |
| // [Try]Into() if the inner type provides them for GVariantRef<"*">. |
| // gvariant::FilledMaybe<T> (type code "mT") - Wrapper for a value that |
| // appears inside a maybe, but will always be present. Presence of |
| // [Try]From() and TryInto() mirror the inner type. Into() is never |
| // provided. |
| // gvariant::EmptyArrayOf<C> (type code "aC") - Represents an empty array of |
| // the type C, which should be an instance of GVariantRef::Type. C must be |
| // definite to use with [Try]From(). Into() is never provided; TryInto() |
| // must be used to check if the array is, in fact, empty. |
| // gvariant::ObjectPath (type code "o") - Wrapper around a std::string that |
| // contains a DBus object path. |
| // gvariant::ObjectPathCStr ([Try]From() only, type code "o") - An owned object |
| // path that can be used as a function parameter (somewhat analogous to a |
| // string_view) or to hold a compile-time-verified object path constant. |
| // gvariant::TypeSignature (type code "g") - Wrapper around a std::string |
| // that contains a DBus type signature. |
| // gvariant::TypeSignatureCStr ([Try]From() only, type code "g") - An owned type |
| // signature that can be used as a function parameter (somewhat analogous to |
| // a string_view) or to hold a compile-time-verified type signature |
| // constant. |
| // |
| // Convenience functions: |
| // |
| // GVariantFrom(value) - Like GVariantRef<C>::From(), but infers C from value's |
| // type. |
| // gvariant::BoxedRef(value) - Returns a gvariant::Boxed that holds a const |
| // reference to value. (Whereas Boxed{value} would hold a copy of value.) |
| // gvariant::FilledMaybeRef(value) - Returns a gvariant::FilledMaybe that holds |
| // a const reference to value. (Whereas FilledMaybe{value} would hold a copy |
| // of value.) |
| template <Type C = Type("*")> |
| class GVariantRef; |
| |
| // A struct specialized for each type supporting conversion to or from a |
| // GVariant. |
| // |
| // All specializations must provide a kType field, which should be in the form |
| // of a static constexpr Type. If the exact type can only be determined at |
| // runtime, an indefinite type can be used (e.g., "*"). |
| // |
| // static constexpr Type kType{"i"}; |
| // |
| // A given type may support conversion to a GVariant, from a GVariant, or |
| // both. An example of a type that only supports conversion to a GVariant is |
| // a C-style string (const char *). An example of a type that only supports |
| // conversion from a GVariant is GVariantRef::Ignored. |
| // |
| // To support conversion to a GVariant, the specialization should provide a |
| // static From or TryFrom method that takes a value of the type and returns a |
| // GVariantRef<kType>, e.g., |
| // |
| // static GVariantRef<kType> From(const T&); |
| // static base::expected<GVariantRef<kType>, Loggable> TryFrom(const T&); |
| // |
| // Usually a specialization will provide one or the other. |
| // |
| // To support conversion from a GVariant, the specialization should provide a |
| // static Into or TryInto method that takes a GVariantRef<kType> and returns a |
| // value of the type, e.g., |
| // |
| // static T Into(const GVariantRef<kType>&); |
| // static base::expected<T, Loggable> TryInto(const GVariantRef<kType>&); |
| // |
| // Usually a specialization will provide one or the other, with one exception: |
| // In the event that infallible conversion is possible for only some subtypes of |
| // kType, Into may be specified only for those subtypes. In that case, TryInto |
| // must also be specified. E.g., if a conversion can take an int or a string, |
| // the definition might look like this: |
| // |
| // static constexpr Type kType{"?"}; |
| // static base::expected<T, Loggable> TryInto(const GVariantRef<kType>&); |
| // static T Into(const GVariantRef<"i">&); |
| // static T Into(const GVariantRef<"s">&); |
| // |
| // In the event a Try* conversion fails, the method should return an error |
| // string suitable for inclusion in a log message. |
| template <typename T> |
| struct Mapping; |
| |
| // Common members of all GVariantRefs. |
| class GVariantBase { |
| public: |
| // Allows creating an owned reference given a reference to GVariantBase. |
| explicit operator GVariantRef<>() const; |
| |
| // Returns the inner GVariant reference without incrementing the count. It is |
| // the caller's responsibility to increment the ref count if it is to be held |
| // longer than the containing remoting::GVariant instance. Otherwise, the |
| // caller should not call unref on it. |
| GVariant* raw() const; |
| |
| // Takes owneriship of the inner GVariant, leaving this in a moved-from state. |
| // It is the caller's responsibility to call unref when they are done with it. |
| [[nodiscard]] GVariant* release() &&; |
| |
| // Returns the type of the value currently held in the GVariant. Will always |
| // be definite. |
| Type<> GetType() const; |
| |
| friend bool operator==(const GVariantBase& lhs, const GVariantBase& rhs); |
| |
| protected: |
| using GVariantPtr = CRefCounted<GVariant, |
| g_variant_ref, |
| g_variant_unref, |
| g_variant_take_ref, |
| g_variant_ref_sink>; |
| |
| GVariantBase(); |
| explicit GVariantBase(GVariantPtr variant); |
| GVariantBase(const GVariantBase& other); |
| GVariantBase(GVariantBase&& other); |
| GVariantBase& operator=(const GVariantBase& other); |
| GVariantBase& operator=(GVariantBase&& other); |
| ~GVariantBase(); |
| |
| private: |
| GVariantPtr variant_; |
| }; |
| |
| bool operator==(const GVariantBase& lhs, const GVariantBase& rhs); |
| |
| template <Type C> |
| class GVariantRef : public GVariantBase { |
| public: |
| static constexpr auto kType = C; |
| |
| // Default constructor constructs an empty GVariantRef in the same state as |
| // one that has been moved from. No operations are valid until a value has |
| // been assigned. |
| GVariantRef() = default; |
| |
| // Copyable and movable. Copies are cheap, only bumping the reference count. |
| // The only valid operations for a moved-from instance are dropping it and |
| // assigning a new value. |
| GVariantRef(const GVariantRef& other) = default; |
| GVariantRef(GVariantRef&& other) = default; |
| GVariantRef& operator=(const GVariantRef& other) = default; |
| GVariantRef& operator=(GVariantRef&& other) = default; |
| ~GVariantRef() = default; |
| |
| // Allow implicit conversion from subtype to supertype. |
| template <Type D> |
| // NOLINTNEXTLINE(google-explicit-constructor) |
| GVariantRef(const GVariantRef<D>& other) |
| requires(D.IsSubtypeOf(C)); |
| |
| template <Type D> |
| // NOLINTNEXTLINE(google-explicit-constructor) |
| GVariantRef(GVariantRef<D>&& other) |
| requires(D.IsSubtypeOf(C)); |
| |
| // Type conversions |
| |
| // Constructs a new GVariantRef from the provided value. |
| template <typename T> |
| static GVariantRef From(const T& value) |
| requires(Mapping<T>::kType.IsSubtypeOf(C) && |
| requires { Mapping<T>::From(value); }); |
| |
| // Constructs a new GVariantRef from the provided value, if possible. |
| template <typename T> |
| static base::expected<GVariantRef, Loggable> TryFrom(const T& value) |
| requires(Mapping<T>::kType.HasCommonTypeWith(C) && |
| (requires { Mapping<T>::TryFrom(value); } || |
| requires { Mapping<T>::From(value); })); |
| |
| // Builds a value of the provided type from the contents of the GVariant. |
| // Calls will only compile if they are guaranteed to succeed at runtime. |
| template <typename T> |
| T Into() const |
| requires(C.IsSubtypeOf(Mapping<T>::kType) && |
| requires { Mapping<T>::Into(*this); }); |
| |
| // Builds a value of the provided type from the contents of the GVariant, if |
| // possible. Fails to compile if the conversion can statically be determined |
| // never to succeed. |
| template <typename T> |
| base::expected<T, Loggable> TryInto() const |
| requires(C.HasCommonTypeWith(Mapping<T>::kType) && |
| (requires(GVariantRef<Mapping<T>::kType> v) { |
| Mapping<T>::TryInto(v); |
| } || |
| requires(GVariantRef<Mapping<T>::kType> v) { |
| Mapping<T>::Into(v); |
| })); |
| |
| // Unpacks a container GVariant into lvalue references. The GVariant must be a |
| // boxed value, a tuple, or a dictionary entry. Each argument must be a lvalue |
| // reference or a tuple to unpack a nested container. Each tuple should |
| // similarly consist of lvalue references and tuples. std::tie and |
| // std::forward_as_tuple can be useful for constructing such tuples. Calls to |
| // Destructure will only compile if it can be guaranteed at compile time that |
| // the operation will succeed. |
| template <typename... Types> |
| void Destructure(Types&&... refs) const; |
| |
| // As above, but allows conversions that could fail at runtime. The GVariant |
| // must be a boxed value, a maybe value, a tuple, an array, or a dictionary |
| // entry. If the types or number of values don't match at runtime, an error |
| // message will be returned. In this case, the reference lvalues will be in an |
| // indeterminate state, as some values may have been read prior to the error |
| // occurring. |
| template <typename... Types> |
| base::expected<void, Loggable> TryDestructure(Types&&... refs) const; |
| |
| // Iterate through the values of a container GVariant. The value type will be |
| // GVariant<TypeBase::ContainedType(C)>. |
| auto begin() const |
| requires(C.IsContainer()); |
| auto end() const |
| requires(C.IsContainer()); |
| std::size_t size() const |
| requires(C.IsContainer()); |
| |
| // Get the Ith element from a fixed-size container GVariant. Specializations |
| // of std::tuple_size and std::tuple_element are also provided to make fixed- |
| // size container GVariants tuple-like. |
| template <std::size_t I> |
| auto get() const |
| requires(C.IsFixedSizeContainer()); |
| |
| // Look up the provided key in a dictionary GVariant. The provided argument |
| // must be convertible to the key type via TryFrom. Returns a |
| // std::optional<GVariantRef<[value type]>> containing the found value, or |
| // nullopt if the key was not found. Note that this performs a linear search |
| // through the dictionary. If the dictionary is large and many lookups will be |
| // performed, it might be more efficient to convert to a std::map or another |
| // datastructure first. |
| template <typename T> |
| auto LookUp(const T& key) |
| requires(C.IsSubtypeOf(Type("a{?*}")) && requires { |
| decltype((*begin()).template get<0>())::TryFrom(key); |
| }); |
| |
| // Access the contained string without copying. C must be "s", "o", or "g". |
| std::string_view string_view() const |
| requires(C.IsStringType()); |
| |
| // Access the contained string without copying. C must be "s", "o", or "g". |
| const char* c_string() const |
| requires(C.IsStringType()); |
| |
| // Create from a raw GVariant*. Can only be used with GVariantRef<Type("*")>. |
| // To get a narrower GVariantRef, first create one with "*" and then use |
| // TryInto (or TryFrom) to convert to the narrower type. |
| |
| // Takes ownership of the reference. If it is floating, it will be sunk. May |
| // not be null. |
| // |
| // This is the right method to call with a floating or caller-owned |
| // GVariant* when one wants to pass ownership to the new GVariantRef. Most |
| // GLib functions returning a GVariant* will return either a floating |
| // reference (e.g., `g_variant_new`) or a caller owned reference (e.g., |
| // `get_child_value`), making their return value suitable to be passed to this |
| // method (after checking for null, if the function is falible). |
| static GVariantRef Take(GVariant* variant) |
| requires(C == Type("*")); |
| |
| // Takes a new reference unconditionally. If object is floating, it will |
| // remain so. May not be null. |
| // |
| // This is the right method to call with a borrowed GVariant* (such as is |
| // returned by `raw()` or might be owned by a different class/struct) or a |
| // caller-owned GVariant* of which the caller wants to maintain ownership. |
| static GVariantRef Ref(GVariant* variant) |
| requires(C == Type("*")); |
| |
| // If the reference count is floating, takes ownership and sinks it. |
| // Otherwise, takes a new reference. May not be null. |
| // |
| // This is usually the right method to call with a GVariant* that was passed |
| // into the caller as an argument. That gives the caller's caller the |
| // flexibility to pass in either a newly-constructed GVariant (in which case |
| // the reference will be floating, and GVariantRef will take ownership) or an |
| // existing GVariant* (in which case GVariantRef will take its own reference). |
| static GVariantRef RefSink(GVariant* variant) |
| requires(C == Type("*")); |
| |
| // Same as above, but can be used with any GVariantRef type. The caller must |
| // ensure the passed pointer is actually of the appropriate type. Passing a |
| // pointer that does not match C can result in undefined behavior. |
| static GVariantRef TakeUnchecked(GVariant* variant); |
| static GVariantRef RefUnchecked(GVariant* variant); |
| static GVariantRef RefSinkUnchecked(GVariant* variant); |
| |
| private: |
| using GVariantBase::GVariantBase; |
| }; |
| |
| // Constructs a new GVariantRef from the provided value, inferring the type |
| // string. |
| template <typename T> |
| static GVariantRef<Mapping<T>::kType> GVariantFrom(const T& value) { |
| return GVariantRef<Mapping<T>::kType>::From(value); |
| } |
| |
| // Wrapper types and special types |
| |
| // Can be used as a nested type in a call to [Try]Into() as a placeholder for a |
| // value the caller isn't interested in. |
| struct Ignored {}; |
| |
| // Wrapper for a value to specify that it will appear in the GVariant "boxed". |
| // That is, as a nested variant (type "v") holding the value, rather than the |
| // value directly. |
| template <typename T> |
| struct Boxed { |
| T value; |
| }; |
| |
| template <typename T, typename U> |
| bool operator==(const Boxed<T>& lhs, const Boxed<U>& rhs) |
| requires requires(T t, U u) { t == u; } |
| { |
| return lhs.value == rhs.value; |
| } |
| |
| // Returns a gvariant::Boxed that holds a const reference to value. (Whereas |
| // Boxed{value} would hold a copy of value.) Useful to avoid making an extra |
| // copy when constructing a GVariantRef. |
| template <typename T> |
| Boxed<const T&> BoxedRef(const T& value LIFETIME_BOUND) { |
| return {value}; |
| } |
| |
| // Wrapper for a value that should appear inside a maybe, but will always be |
| // present. |
| template <typename T> |
| struct FilledMaybe { |
| T value; |
| }; |
| |
| template <typename T, typename U> |
| bool operator==(const FilledMaybe<T>& lhs, const FilledMaybe<U>& rhs) |
| requires requires(T t, U u) { t == u; } |
| { |
| return lhs.value == rhs.value; |
| } |
| |
| // Returns a gvariant::FilledMaybe that holds a const reference to value. |
| // (Whereas FilledMaybe{value} would hold a copy of value.) Useful to avoid |
| // making an extra copy when constructing a GVariantRef. |
| template <typename T> |
| FilledMaybe<const T&> FilledMaybeRef(const T& value LIFETIME_BOUND) { |
| return {value}; |
| } |
| |
| // Represents an empty array of the given type. |
| template <Type C> |
| struct EmptyArrayOf {}; |
| |
| class ObjectPath; |
| |
| // Holds an unowned pointer to a null-terminated string known to be a valid |
| // D-Bus object path. |
| class ObjectPathCStr { |
| public: |
| // Constructs from a compile-time constant. The passed string is checked at |
| // compile time to be a valid object path. |
| // Allows implicit construction so string constants can easily be passed to |
| // ObjectPathCStr parameters. |
| // NOLINTNEXTLINE(google-explicit-constructor) |
| consteval ObjectPathCStr(const char* path); |
| // Constructs from an ObjectPath object. The resulting ObjectPathCStr is only |
| // valid as long as the ObjectPath to which it refers. |
| // Allows explicit construction so ObjectPaths can easily be passed to |
| // ObjectPathCStr parameters. |
| // NOLINTNEXTLINE(google-explicit-constructor) |
| ObjectPathCStr(const ObjectPath& path LIFETIME_BOUND); |
| |
| // Attempts to construct from an existing C string. Returns an error string |
| // if |path| is not a valid object path. |
| static base::expected<ObjectPathCStr, Loggable> TryFrom( |
| const char* path LIFETIME_BOUND); |
| |
| // Gets the object path C string. |
| constexpr const char* c_str() const LIFETIME_BOUND; |
| |
| friend constexpr bool operator==(const ObjectPathCStr& lhs, |
| const ObjectPathCStr& rhs); |
| |
| private: |
| struct Checked {}; |
| // Construct from already-checked path. Extra parameter to avoid conflicting |
| // with consteval constructor. |
| ObjectPathCStr(const char* path LIFETIME_BOUND, Checked); |
| |
| const char* path_; |
| }; |
| |
| constexpr bool operator==(const ObjectPathCStr& lhs, const ObjectPathCStr& rhs); |
| |
| // Represents an owned D-Bus object path. |
| class ObjectPath { |
| public: |
| // Constructs an instance of the root path "/" |
| ObjectPath(); |
| |
| // Constructs an owned copy of |path|. |
| explicit ObjectPath(ObjectPathCStr path); |
| |
| // Attempts to construct from an existing std::string. Returns an error string |
| // if |path| is not a valid object path. |
| static base::expected<ObjectPath, Loggable> TryFrom(std::string path); |
| |
| // Gets the object path. |
| const std::string& value() const LIFETIME_BOUND; |
| |
| // Shorthand for .value().c_str() |
| const char* c_str() const LIFETIME_BOUND; |
| |
| private: |
| // Construct from already-checked path. |
| explicit ObjectPath(std::string path); |
| std::string path_; |
| friend struct Mapping<ObjectPath>; |
| }; |
| |
| class TypeSignature; |
| |
| // Holds an unowned pointer to a null-terminated string known to be a valid |
| // D-Bus type signature. |
| class TypeSignatureCStr { |
| public: |
| // Constructs from a compile-time constant. The passed string is checked at |
| // compile time to be a valid type signature. |
| // Allows implicit construction so string constants can easily be passed to |
| // TypeSignatureCStr parameters. |
| // NOLINTNEXTLINE(google-explicit-constructor) |
| consteval TypeSignatureCStr(const char* signature); |
| |
| // Constructs from a TypeSignature object. The resulting TypeSignatureCStr is |
| // only valid as long as the TypeSignature to which it refers. |
| // Allows implicit construction so TypeSignatures can easily be passed to |
| // TypeSignatureCStr parameters. |
| // NOLINTNEXTLINE(google-explicit-constructor) |
| TypeSignatureCStr(const TypeSignature& signature LIFETIME_BOUND); |
| |
| // Attempts to construct from an existing C string. Returns an error if |
| // |signature| is not a valid type signature. |
| static base::expected<TypeSignatureCStr, Loggable> TryFrom( |
| const char* signature LIFETIME_BOUND); |
| |
| // Gets the type signature C string. |
| constexpr const char* c_str() const LIFETIME_BOUND; |
| |
| friend constexpr bool operator==(const TypeSignatureCStr& lhs, |
| const TypeSignatureCStr& rhs); |
| |
| private: |
| struct Checked {}; |
| // Construct from already-checked path. Extra parameter to avoid conflicting |
| // with consteval constructor. |
| TypeSignatureCStr(const char* signature LIFETIME_BOUND, Checked); |
| |
| const char* signature_; |
| }; |
| |
| constexpr bool operator==(const TypeSignatureCStr& lhs, |
| const TypeSignatureCStr& rhs); |
| |
| // Represents an owned D-Bus type signature. |
| class TypeSignature { |
| public: |
| // Constructs an empty type signature. |
| TypeSignature(); |
| |
| // Constructs an owned copy of |signature|. |
| explicit TypeSignature(TypeSignatureCStr signature); |
| |
| // Attempts to construct from an existing std::string. Returns an error if |
| // |signature| is not a valid type signature. |
| static base::expected<TypeSignature, Loggable> TryFrom(std::string signature); |
| |
| // Gets the type signature. |
| const std::string& value() const LIFETIME_BOUND; |
| |
| // Shorthand for .value().c_str() |
| const char* c_str() const LIFETIME_BOUND; |
| |
| private: |
| // Construct from already-checked signature. |
| explicit TypeSignature(std::string); |
| std::string signature_; |
| friend struct Mapping<TypeSignature>; |
| }; |
| |
| // Iterator type for a container GVariant |
| template <Type C> |
| class Iterator; |
| |
| template <Type C> |
| Iterator<C> operator+(std::ptrdiff_t n, const Iterator<C>& iter); |
| |
| template <Type C> |
| class Iterator { |
| public: |
| Iterator() = default; |
| // Copyable and movable |
| Iterator(const Iterator& other) = default; |
| Iterator(Iterator&& other) = default; |
| Iterator& operator=(const Iterator& other) = default; |
| Iterator& operator=(Iterator&& other) = default; |
| |
| // Iterator interface |
| |
| // LegacyForwardIterator requires reference_type be &value_type or |
| // const &value_type, so this implementation is only a LegacyInputIterator. |
| using iterator_category = std::input_iterator_tag; |
| |
| // The std::forward_iterator and higher concepts impose no such requirement, |
| // so this can be a full random-access iterator. |
| using iterator_concept = std::random_access_iterator_tag; |
| |
| using value_type = GVariantRef<C>; |
| using reference_type = GVariantRef<C>; |
| using difference_type = std::ptrdiff_t; |
| |
| // input iterator |
| value_type operator*() const; |
| Iterator& operator++(); |
| Iterator operator++(int); |
| |
| // forward iterator |
| bool operator==(const Iterator& other) const; |
| |
| // bidirectional iterator |
| Iterator& operator--(); |
| Iterator operator--(int); |
| |
| // random access iterator |
| std::partial_ordering operator<=>(const Iterator& other) const; |
| difference_type operator-(const Iterator& other) const; |
| Iterator operator+(difference_type n) const; |
| friend Iterator operator+ <C>(difference_type n, const Iterator& iter); |
| Iterator operator-(difference_type n) const; |
| Iterator& operator+=(difference_type n); |
| Iterator& operator-=(difference_type n); |
| value_type operator[](difference_type i) const; |
| |
| private: |
| Iterator(GVariantRef<> variant, std::size_t i); |
| std::size_t i_ = 0; |
| GVariantRef<> variant_; |
| |
| template <Type D> |
| friend class GVariantRef; |
| }; |
| |
| // GVariantRef implementation |
| |
| template <Type C> |
| template <Type D> |
| GVariantRef<C>::GVariantRef(const GVariantRef<D>& other) |
| requires(D.IsSubtypeOf(C)) |
| : GVariantBase(other) {} |
| |
| template <Type C> |
| template <Type D> |
| GVariantRef<C>::GVariantRef(GVariantRef<D>&& other) |
| requires(D.IsSubtypeOf(C)) |
| : GVariantBase(std::move(other)) {} |
| |
| // static |
| template <Type C> |
| template <typename T> |
| GVariantRef<C> GVariantRef<C>::From(const T& value) |
| requires(Mapping<T>::kType.IsSubtypeOf(C) && |
| requires { Mapping<T>::From(value); }) |
| { |
| return Mapping<T>::From(value); |
| } |
| |
| // static |
| template <Type C> |
| template <typename T> |
| base::expected<GVariantRef<C>, Loggable> GVariantRef<C>::TryFrom( |
| const T& value) |
| requires(Mapping<T>::kType.HasCommonTypeWith(C) && |
| (requires { Mapping<T>::TryFrom(value); } || |
| requires { Mapping<T>::From(value); })) |
| { |
| if constexpr (requires { Mapping<T>::TryFrom(value); }) { |
| return Mapping<T>::TryFrom(value).and_then( |
| [](auto value) { return value.template TryInto<GVariantRef>(); }); |
| } else { |
| return Mapping<T>::From(value).template TryInto<GVariantRef>(); |
| } |
| } |
| |
| template <Type C> |
| template <typename T> |
| T GVariantRef<C>::Into() const |
| requires(C.IsSubtypeOf(Mapping<T>::kType) && |
| requires { Mapping<T>::Into(*this); }) |
| { |
| return Mapping<T>::Into(*this); |
| } |
| |
| template <Type C> |
| template <typename T> |
| base::expected<T, Loggable> GVariantRef<C>::TryInto() const |
| requires(C.HasCommonTypeWith(Mapping<T>::kType) && |
| (requires(GVariantRef<Mapping<T>::kType> v) { |
| Mapping<T>::TryInto(v); |
| } || |
| requires(GVariantRef<Mapping<T>::kType> v) { |
| Mapping<T>::Into(v); |
| })) |
| { |
| if constexpr (!C.IsSubtypeOf(Mapping<T>::kType)) { |
| if (GetType().IsSubtypeOf(Mapping<T>::kType)) { |
| return GVariantRef<Mapping<T>::kType>::RefUnchecked(raw()) |
| .template TryInto<T>(); |
| } else { |
| return base::unexpected(Loggable( |
| FROM_HERE, |
| base::StrCat({"Expected type: ", Mapping<T>::kType.string_view(), |
| " Found: ", GetType().string_view()}))); |
| } |
| } else if constexpr (requires { Mapping<T>::TryInto(*this); }) { |
| return Mapping<T>::TryInto(*this); |
| } else { |
| return base::ok(Mapping<T>::Into(*this)); |
| } |
| } |
| |
| template <Type C> |
| template <typename... Types> |
| void GVariantRef<C>::Destructure(Types&&... refs) const { |
| static_assert((... && (std::is_lvalue_reference_v<Types> || |
| requires { std::tuple_size_v<Types>; }))); |
| constexpr auto contained_types = TypeBase::Unpack<C>(); |
| static_assert(std::tuple_size_v<decltype(contained_types)> == sizeof...(refs), |
| "Incorrect number of elements."); |
| [&]<std::size_t... Is>(std::index_sequence<Is...>) { |
| ( |
| [&]() { |
| auto inner_variant = |
| GVariantRef<std::get<Is>(contained_types)>::TakeUnchecked( |
| g_variant_get_child_value(raw(), Is)); |
| if constexpr (std::is_lvalue_reference_v<Types>) { |
| refs = |
| inner_variant.template Into<std::remove_reference_t<Types>>(); |
| } else { |
| std::apply( |
| [&]<typename... Ts>(Ts&&... subref) { |
| return inner_variant.Destructure(std::forward<Ts>(subref)...); |
| }, |
| std::forward<Types>(refs)); |
| } |
| }(), |
| ...); |
| }(std::index_sequence_for<Types...>()); |
| } |
| |
| template <Type C> |
| template <typename... Types> |
| base::expected<void, Loggable> GVariantRef<C>::TryDestructure( |
| Types&&... refs) const { |
| static_assert((... && (std::is_lvalue_reference_v<Types> || |
| requires { std::tuple_size_v<Types>; }))); |
| |
| if (!g_variant_is_container(raw())) { |
| return base::unexpected( |
| Loggable(FROM_HERE, "Destructured GVariant is not a container.")); |
| } |
| |
| if (std::size_t size = g_variant_n_children(raw()); size != sizeof...(refs)) { |
| return base::unexpected(Loggable( |
| FROM_HERE, base::StringPrintf( |
| "Incorrect number of elements. Expected: %zd Found: %zd", |
| sizeof...(refs), size))); |
| } |
| |
| base::expected<void, Loggable> result; |
| |
| [&]<std::size_t... Is>(std::index_sequence<Is...>) { |
| ([&]() { |
| GVariantRef<> inner_variant = |
| GVariantRef<>::Take(g_variant_get_child_value(raw(), Is)); |
| if constexpr (std::is_lvalue_reference_v<Types>) { |
| auto value = inner_variant.TryInto<std::remove_reference_t<Types>>(); |
| if (!value.has_value()) { |
| result = std::move(value).error().UnexpectedWithContext( |
| FROM_HERE, "While destructuring container"); |
| return false; |
| } |
| refs = value.value(); |
| } else { |
| auto nested_result = std::apply( |
| [&]<typename... Ts>(Ts&&... subref) { |
| return inner_variant.TryDestructure(std::forward<Ts>(subref)...); |
| }, |
| std::forward<Types>(refs)); |
| if (!nested_result.has_value()) { |
| result = std::move(nested_result); |
| return false; |
| } |
| } |
| return true; |
| }() && |
| ...); |
| }(std::index_sequence_for<Types...>()); |
| |
| return result; |
| } |
| |
| template <Type C> |
| auto GVariantRef<C>::begin() const |
| requires(C.IsContainer()) |
| { |
| return Iterator<TypeBase::ContainedType<C>()>(*this, 0); |
| } |
| |
| template <Type C> |
| auto GVariantRef<C>::end() const |
| requires(C.IsContainer()) |
| { |
| return Iterator<TypeBase::ContainedType<C>()>(*this, |
| g_variant_n_children(raw())); |
| } |
| |
| template <Type C> |
| std::size_t GVariantRef<C>::size() const |
| requires(C.IsContainer()) |
| { |
| return g_variant_n_children(raw()); |
| } |
| |
| template <Type C> |
| template <std::size_t I> |
| auto GVariantRef<C>::get() const |
| requires(C.IsFixedSizeContainer()) |
| { |
| return GVariantRef<std::get<I>(TypeBase::Unpack<C>())>::TakeUnchecked( |
| g_variant_get_child_value(raw(), I)); |
| } |
| |
| // Define free version as well for generic code that calls get(tuple_like) using |
| // argument-dependent lookup. |
| template <std::size_t I, Type C> |
| requires(C.IsFixedSizeContainer()) |
| auto get(const GVariantRef<C>& variant) { |
| return variant.template get<I>(); |
| } |
| |
| template <Type C> |
| template <typename T> |
| auto GVariantRef<C>::LookUp(const T& needle) |
| requires(C.IsSubtypeOf(Type("a{?*}")) && |
| requires { |
| decltype((*begin()).template get<0>())::TryFrom(needle); |
| }) |
| { |
| using KeyType = decltype((*begin()).template get<0>()); |
| using ValueType = decltype((*begin()).template get<1>()); |
| auto needle_variant = KeyType::TryFrom(needle); |
| if (!needle_variant.has_value()) { |
| // If the value is something that can't be converted to a GVariant (e.g., |
| // a non-UTF-8 string), it's probably safe to say it's not in the |
| // dictionary. |
| return std::optional<ValueType>(); |
| } |
| for (auto [key, value] : *this) { |
| if (key == needle_variant.value()) { |
| return std::optional<ValueType>(std::move(value)); |
| } |
| } |
| return std::optional<ValueType>(); |
| } |
| |
| template <Type C> |
| std::string_view GVariantRef<C>::string_view() const |
| requires(C.IsStringType()) |
| { |
| gsize length; |
| const char* string = g_variant_get_string(raw(), &length); |
| return std::string_view(string, length); |
| } |
| |
| template <Type C> |
| const char* GVariantRef<C>::c_string() const |
| requires(C.IsStringType()) |
| { |
| return g_variant_get_string(raw(), nullptr); |
| } |
| |
| // static |
| template <Type C> |
| GVariantRef<C> GVariantRef<C>::TakeUnchecked(GVariant* variant) { |
| DCHECK(g_variant_is_of_type(variant, C.gvariant_type())); |
| return GVariantRef<C>(GVariantPtr::Take(variant)); |
| } |
| |
| // static |
| template <Type C> |
| GVariantRef<C> GVariantRef<C>::RefUnchecked(GVariant* variant) { |
| DCHECK(g_variant_is_of_type(variant, C.gvariant_type())); |
| return GVariantRef<C>(GVariantPtr::Ref(variant)); |
| } |
| |
| // static |
| template <Type C> |
| GVariantRef<C> GVariantRef<C>::RefSinkUnchecked(GVariant* variant) { |
| DCHECK(g_variant_is_of_type(variant, C.gvariant_type())); |
| return GVariantRef<C>(GVariantPtr::RefSink(variant)); |
| } |
| |
| // Wrapper implementation |
| |
| consteval ObjectPathCStr::ObjectPathCStr(const char* path) { |
| // SAFETY: Since this constructor is consteval, it can only execute at compile |
| // time. Thus, a read past the end triggered by a missing null terminator will |
| // result in a compile-time error, with no risk at runtime. |
| |
| // TODO: bug 400761089 - Remove UNSAFE_BUFFERS annotations when Clang no |
| // longer flags consteval code. |
| |
| // CHECKs cannot actually print messages at compile time, but a failed check |
| // will trigger a compiler error pointing to the failed check, allowing the |
| // message to be seen in the source code. |
| CHECK_EQ(*path, '/') << "Path must start with a '/'"; |
| const char* prev_char = path; |
| const char* current_char = UNSAFE_BUFFERS(path + 1); |
| while (*current_char != '\0') { |
| CHECK((*current_char >= 'A' && *current_char <= 'Z') || |
| (*current_char >= 'a' && *current_char <= 'z') || |
| (*current_char >= '0' && *current_char <= '9') || |
| *current_char == '_' || *current_char == '/') |
| << "Path contains invalid character"; |
| CHECK(*prev_char != '/' || *current_char != '/') |
| << "Two '/' characters may not appear in a row"; |
| UNSAFE_BUFFERS(++prev_char); |
| UNSAFE_BUFFERS(++current_char;) |
| } |
| CHECK(*prev_char != '/' || prev_char == path) |
| << "Path may not end in '/' unless the whole path is only a single '/' " |
| "referring to the root path"; |
| path_ = path; |
| } |
| |
| constexpr const char* ObjectPathCStr::c_str() const { |
| return path_; |
| } |
| |
| constexpr bool operator==(const ObjectPathCStr& lhs, |
| const ObjectPathCStr& rhs) { |
| if (std::is_constant_evaluated()) { |
| return std::string_view(lhs.c_str()) == std::string_view(rhs.c_str()); |
| } else { |
| return UNSAFE_TODO(std::strcmp(lhs.c_str(), rhs.c_str())) == 0; |
| } |
| } |
| |
| consteval TypeSignatureCStr::TypeSignatureCStr(const char* signature) { |
| // SAFETY: Since this constructor is consteval, it can only execute at compile |
| // time. Thus, a read past the end triggered by a missing null terminator will |
| // result in a compile-time error, with no risk at runtime. |
| |
| // TODO: bug 400761089 - Remove UNSAFE_BUFFERS annotations when Clang no |
| // longer flags consteval code. |
| |
| // CHECKs cannot actually print messages at compile time, but a failed check |
| // will trigger a compiler error pointing to the failed check, allowing the |
| // message to be seen in the source code. |
| CHECK(Type("(", signature, ")").IsValid()) << "Not a valid signature"; |
| CHECK(Type("(", signature, ")").IsDefinite()) << "Signature must be definite"; |
| char prev_char = '\0'; |
| for (const char* current_char = signature; *current_char != '\0'; |
| UNSAFE_BUFFERS(++current_char)) { |
| CHECK(*current_char != 'm') << "Maybe type not valid in D-Bus signature"; |
| CHECK(*current_char != '{' || prev_char == 'a') |
| << "Dict entry not part of a dictionary."; |
| prev_char = *current_char; |
| } |
| signature_ = signature; |
| } |
| |
| constexpr const char* TypeSignatureCStr::c_str() const { |
| return signature_; |
| } |
| |
| constexpr bool operator==(const TypeSignatureCStr& lhs, |
| const TypeSignatureCStr& rhs) { |
| if (std::is_constant_evaluated()) { |
| return std::string_view(lhs.c_str()) == std::string_view(rhs.c_str()); |
| } else { |
| return UNSAFE_TODO(std::strcmp(lhs.c_str(), rhs.c_str())) == 0; |
| } |
| } |
| |
| // Iterator implementation |
| |
| template <Type C> |
| GVariantRef<C> Iterator<C>::operator*() const { |
| return GVariantRef<C>::TakeUnchecked( |
| g_variant_get_child_value(variant_.raw(), i_)); |
| } |
| |
| template <Type C> |
| Iterator<C>& Iterator<C>::operator++() { |
| ++i_; |
| return *this; |
| } |
| |
| template <Type C> |
| Iterator<C> Iterator<C>::operator++(int) { |
| return Iterator(variant_, i_++); |
| } |
| |
| template <Type C> |
| bool Iterator<C>::operator==(const Iterator& other) const { |
| return variant_.raw() == other.variant_.raw() && i_ == other.i_; |
| } |
| |
| template <Type C> |
| Iterator<C>& Iterator<C>::operator--() { |
| --i_; |
| return *this; |
| } |
| |
| template <Type C> |
| Iterator<C> Iterator<C>::operator--(int) { |
| return Iterator(variant_, i_--); |
| } |
| |
| template <Type C> |
| std::partial_ordering Iterator<C>::operator<=>(const Iterator& other) const { |
| if (variant_.raw() != other.variant_.raw()) { |
| return std::partial_ordering::unordered; |
| } |
| return i_ <=> other.i_; |
| } |
| |
| template <Type C> |
| std::ptrdiff_t Iterator<C>::operator-(const Iterator& other) const { |
| return static_cast<std::ptrdiff_t>(i_) - |
| static_cast<std::ptrdiff_t>(other.i_); |
| } |
| |
| template <Type C> |
| Iterator<C> Iterator<C>::operator+(std::ptrdiff_t n) const { |
| return Iterator(variant_, static_cast<std::ptrdiff_t>(i_) + n); |
| } |
| |
| template <Type C> |
| Iterator<C> operator+(std::ptrdiff_t n, const Iterator<C>& iter) { |
| return Iterator<C>(iter.variant_, n + static_cast<std::ptrdiff_t>(iter.i_)); |
| } |
| |
| template <Type C> |
| Iterator<C> Iterator<C>::operator-(std::ptrdiff_t n) const { |
| return Iterator(variant_, static_cast<std::ptrdiff_t>(i_) - n); |
| } |
| |
| template <Type C> |
| Iterator<C>& Iterator<C>::operator+=(std::ptrdiff_t n) { |
| i_ = static_cast<std::size_t>(static_cast<std::ptrdiff_t>(i_) + n); |
| return *this; |
| } |
| |
| template <Type C> |
| Iterator<C>& Iterator<C>::operator-=(std::ptrdiff_t n) { |
| i_ = static_cast<std::size_t>(static_cast<std::ptrdiff_t>(i_) - n); |
| return *this; |
| } |
| |
| template <Type C> |
| GVariantRef<C> Iterator<C>::operator[](std::ptrdiff_t i) const { |
| return GVariantRef<C>::TakeUnchecked(g_variant_get_child_value( |
| variant_.raw(), |
| static_cast<std::size_t>(static_cast<std::ptrdiff_t>(i_) + i))); |
| } |
| |
| template <Type C> |
| Iterator<C>::Iterator(GVariantRef<> variant, std::size_t i) |
| : i_(i), variant_(variant) {} |
| |
| // Mapping implementations |
| |
| // Possibly cv-qualified reference can be used with *From() but not *Into(). |
| template <typename T> |
| requires(!std::same_as<T, std::decay_t<T>>) |
| struct Mapping<T> { |
| static constexpr Type kType = Mapping<std::decay_t<const T&>>::kType; |
| |
| static auto From(const T& value) |
| // Typically one wouldn't want a requires clause that just mirrors the |
| // function body. Unfortunately, it is necessary to allow other generic |
| // mappings to detect when From is absent, since requires expressions only |
| // care about what is valid according to the declaration, not whether the |
| // resulting instantiation would actually compile. |
| requires(requires { |
| GVariantRef<kType>::template From<std::decay_t<const T&>>(value); |
| }) |
| { |
| return GVariantRef<kType>::template From<std::decay_t<const T&>>(value); |
| } |
| |
| static auto TryFrom(const T& value) |
| requires(requires { |
| GVariantRef<kType>::template TryFrom<std::decay_t<const T&>>(value); |
| }) |
| { |
| return GVariantRef<kType>::template TryFrom<std::decay_t<const T&>>(value); |
| } |
| }; |
| |
| // Basic fixed values. |
| |
| template <> |
| struct Mapping<bool> { |
| static constexpr Type kType{"b"}; |
| static GVariantRef<kType> From(bool value); |
| static bool Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <> |
| struct Mapping<std::uint8_t> { |
| static constexpr Type kType{"y"}; |
| static GVariantRef<kType> From(std::uint8_t value); |
| static std::uint8_t Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <> |
| struct Mapping<std::int16_t> { |
| static constexpr Type kType{"n"}; |
| static GVariantRef<kType> From(std::int16_t value); |
| static std::int16_t Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <> |
| struct Mapping<std::uint16_t> { |
| static constexpr Type kType{"q"}; |
| static GVariantRef<kType> From(std::uint16_t value); |
| static std::uint16_t Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <> |
| struct Mapping<std::int32_t> { |
| static constexpr Type kType{"i"}; |
| static GVariantRef<kType> From(std::int32_t value); |
| static std::int32_t Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <> |
| struct Mapping<std::uint32_t> { |
| static constexpr Type kType{"u"}; |
| static GVariantRef<kType> From(std::uint32_t value); |
| static std::uint32_t Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <> |
| struct Mapping<std::int64_t> { |
| static constexpr Type kType{"x"}; |
| static GVariantRef<kType> From(std::int64_t value); |
| static std::int64_t Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <> |
| struct Mapping<std::uint64_t> { |
| static constexpr Type kType{"t"}; |
| static GVariantRef<kType> From(std::uint64_t value); |
| static std::uint64_t Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <> |
| struct Mapping<double> { |
| static constexpr Type kType{"d"}; |
| static GVariantRef<kType> From(double value); |
| static double Into(const GVariantRef<kType>& variant); |
| }; |
| |
| // Strings. |
| |
| template <> |
| struct Mapping<std::string> { |
| static constexpr Type kType{"s"}; |
| // Crashes if string is not valid UTF-8. |
| static GVariantRef<kType> From(const std::string& value); |
| // Fails if string is not valid UTF-8. |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom( |
| const std::string& value); |
| static std::string Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <> |
| struct Mapping<std::string_view> { |
| static constexpr Type kType{"s"}; |
| // Crashes if string is not valid UTF-8. |
| static GVariantRef<kType> From(std::string_view value); |
| // Fails if string is not valid UTF-8. |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom( |
| std::string_view value); |
| }; |
| |
| template <> |
| struct Mapping<const char*> { |
| static constexpr Type kType{"s"}; |
| // Crashes if string is not valid UTF-8. |
| static GVariantRef<kType> From(const char* value); |
| // Fails if string is not valid UTF-8. |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom( |
| const char* value); |
| }; |
| |
| // Containers |
| |
| template <typename T> |
| struct Mapping<std::optional<T>> { |
| static constexpr Type kInnerType = Mapping<T>::kType; |
| static constexpr Type kType{"m", kInnerType}; |
| |
| static GVariantRef<kType> From(const std::optional<T>& value) |
| requires(kInnerType.IsDefinite() && |
| requires(T v) { GVariantRef<kInnerType>::From(v); }) |
| { |
| std::optional<GVariantRef<kInnerType>> variant; |
| GVariant* child = nullptr; |
| if (value) { |
| variant = GVariantRef<kInnerType>::From(*value); |
| child = variant->raw(); |
| } |
| return GVariantRef<kType>::TakeUnchecked( |
| g_variant_new_maybe(kInnerType.gvariant_type(), child)); |
| } |
| |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom( |
| const std::optional<T>& value) |
| requires(requires(T v) { GVariantRef<kInnerType>::TryFrom(v); }) |
| { |
| if (value.has_value()) { |
| auto result = GVariantRef<kInnerType>::TryFrom(*value); |
| if (!result.has_value()) { |
| return base::unexpected(std::move(result).error()); |
| } |
| return base::ok(GVariantRef<kType>::From(FilledMaybe{result.value()})); |
| } else if constexpr (kInnerType.IsDefinite()) { |
| return base::ok( |
| GVariantRef<kType>::From(std::optional<GVariantRef<kInnerType>>())); |
| } else { |
| return base::unexpected(Loggable( |
| FROM_HERE, "Can't convert indefinite optional with no value.")); |
| } |
| } |
| |
| static std::optional<T> Into(const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<kInnerType> v) { v.template Into<T>(); }) |
| { |
| GVariant* child = g_variant_get_maybe(variant.raw()); |
| if (child) { |
| return GVariantRef<kInnerType>::TakeUnchecked(child).template Into<T>(); |
| } else { |
| return std::nullopt; |
| } |
| } |
| |
| static base::expected<std::optional<T>, Loggable> TryInto( |
| const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<kInnerType> v) { v.template TryInto<T>(); }) |
| { |
| auto optional = |
| variant.template Into<std::optional<GVariantRef<kInnerType>>>(); |
| |
| if (optional.has_value()) { |
| return optional->template TryInto<T>(); |
| } else { |
| return base::ok(std::nullopt); |
| } |
| } |
| }; |
| |
| namespace internal { |
| template <Type kType, Type kInnerType, typename R> |
| GVariantRef<kType> FromRange(const R& value) |
| requires(kInnerType.IsDefinite()) |
| { |
| GVariantBuilder builder; |
| g_variant_builder_init(&builder, kType.gvariant_type()); |
| for (const auto& item : value) { |
| g_variant_builder_add_value(&builder, |
| GVariantRef<kInnerType>::From(item).raw()); |
| } |
| return GVariantRef<kType>::TakeUnchecked(g_variant_builder_end(&builder)); |
| } |
| |
| template <Type kType, Type kInnerType, typename R> |
| static base::expected<GVariantRef<kType>, Loggable> TryFromRange( |
| const R& value) { |
| if (!kInnerType.IsDefinite() && value.empty()) { |
| return base::unexpected( |
| Loggable(FROM_HERE, "Can't convert empty indefinite array")); |
| } |
| |
| std::optional<Type<>> inner_type; |
| GVariantBuilder builder; |
| g_variant_builder_init(&builder, kType.gvariant_type()); |
| for (const auto& item : value) { |
| auto converted = GVariantRef<kInnerType>::TryFrom(item); |
| if (!converted.has_value()) { |
| g_variant_builder_clear(&builder); |
| return base::unexpected(std::move(converted).error()); |
| } |
| if constexpr (!kInnerType.IsDefinite()) { |
| if (!inner_type.has_value()) { |
| inner_type = converted->GetType(); |
| } else if (!converted->GetType().IsSubtypeOf(inner_type.value())) { |
| g_variant_builder_clear(&builder); |
| return base::unexpected( |
| Loggable(FROM_HERE, "Mismatched types in array")); |
| } |
| } |
| g_variant_builder_add_value(&builder, converted->raw()); |
| } |
| return base::ok( |
| GVariantRef<kType>::TakeUnchecked(g_variant_builder_end(&builder))); |
| } |
| } // namespace internal |
| |
| // If needed, a further specialization could be added for vectors and |
| // contiguous ranges of fixed basic types (bools, bytes, ints, and doubles) |
| // to use g_variant_{new,get}_fixed_array() rather than processing each |
| // element individually. This would make handling, e.g., large blobs of binary |
| // data much more efficient. |
| |
| template <typename T> |
| struct Mapping<std::vector<T>> { |
| static constexpr Type kInnerType = Mapping<T>::kType; |
| static constexpr Type kType{"a", kInnerType}; |
| |
| static GVariantRef<kType> From(const std::vector<T>& value) |
| requires(kInnerType.IsDefinite() && |
| requires(T v) { GVariantRef<kInnerType>::From(v); }) |
| { |
| return internal::FromRange<kType, kInnerType>(value); |
| } |
| |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom( |
| const std::vector<T>& value) |
| requires(requires(T v) { GVariantRef<kInnerType>::TryFrom(v); }) |
| { |
| return internal::TryFromRange<kType, kInnerType>(value); |
| } |
| |
| static std::vector<T> Into(const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<kInnerType> v) { v.template Into<T>(); }) |
| { |
| std::vector<T> result; |
| GVariantIter iter; |
| g_variant_iter_init(&iter, variant.raw()); |
| result.reserve(g_variant_iter_n_children(&iter)); |
| while (GVariant* next = g_variant_iter_next_value(&iter)) { |
| auto item_gvariant = GVariantRef<kInnerType>::TakeUnchecked(next); |
| result.push_back(item_gvariant.template Into<T>()); |
| } |
| return result; |
| } |
| |
| static base::expected<std::vector<T>, Loggable> TryInto( |
| const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<kInnerType> v) { v.template TryInto<T>(); }) |
| { |
| std::vector<T> result; |
| GVariantIter iter; |
| g_variant_iter_init(&iter, variant.raw()); |
| result.reserve(g_variant_iter_n_children(&iter)); |
| while (GVariant* next = g_variant_iter_next_value(&iter)) { |
| auto item_gvariant = GVariantRef<>::Take(next); |
| auto item_result = item_gvariant.TryInto<T>(); |
| if (item_result.has_value()) { |
| result.push_back(std::move(item_result).value()); |
| } else { |
| return base::unexpected(std::move(item_result).error()); |
| } |
| } |
| return result; |
| } |
| }; |
| |
| template <typename K, typename T> |
| requires(Mapping<K>::kType.IsBasic()) |
| struct Mapping<std::map<K, T>> { |
| static constexpr Type kKeyType = Mapping<K>::kType; |
| static constexpr Type kValueType = Mapping<T>::kType; |
| static constexpr Type kInnerType{"{", kKeyType, kValueType, "}"}; |
| static constexpr Type kType{"a", kInnerType}; |
| |
| static GVariantRef<kType> From(const std::map<K, T>& value) |
| requires(kInnerType.IsDefinite() && |
| requires(std::pair<K, T> v) { GVariantRef<kInnerType>::From(v); }) |
| { |
| return internal::FromRange<kType, kInnerType>(value); |
| } |
| |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom( |
| const std::map<K, T>& value) |
| requires(requires(std::pair<K, T> v) { |
| GVariantRef<kInnerType>::TryFrom(v); |
| }) |
| { |
| return internal::TryFromRange<kType, kInnerType>(value); |
| } |
| |
| static std::map<K, T> Into(const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<kInnerType> v) { |
| v.template Into<std::pair<K, T>>(); |
| }) |
| { |
| std::map<K, T> result; |
| GVariantIter iter; |
| g_variant_iter_init(&iter, variant.raw()); |
| while (GVariant* next = g_variant_iter_next_value(&iter)) { |
| auto item_gvariant = GVariantRef<kInnerType>::TakeUnchecked(next); |
| result.insert(item_gvariant.template Into<std::pair<K, T>>()); |
| } |
| return result; |
| } |
| |
| static base::expected<std::map<K, T>, Loggable> TryInto( |
| const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<kInnerType> v) { |
| v.template TryInto<std::pair<K, T>>(); |
| }) |
| { |
| std::map<K, T> result; |
| GVariantIter iter; |
| g_variant_iter_init(&iter, variant.raw()); |
| while (GVariant* next = g_variant_iter_next_value(&iter)) { |
| auto item_gvariant = GVariantRef<>::Take(next); |
| auto item_result = item_gvariant.TryInto<std::pair<K, T>>(); |
| if (item_result.has_value()) { |
| result.insert(std::move(item_result).value()); |
| } else { |
| return base::unexpected(std::move(item_result).error()); |
| } |
| } |
| return result; |
| } |
| }; |
| |
| template <typename K, typename T> |
| requires(Mapping<K>::kType.IsBasic()) |
| struct Mapping<std::pair<K, T>> { |
| static constexpr Type kKeyType = Mapping<K>::kType; |
| static constexpr Type kValueType = Mapping<T>::kType; |
| static constexpr Type kType{"{", kKeyType, kValueType, "}"}; |
| |
| static GVariantRef<kType> From(const std::pair<K, T>& pair) |
| requires(requires(K k, T v) { |
| GVariantRef<kKeyType>::From(k); |
| GVariantRef<kValueType>::From(v); |
| }) |
| { |
| auto key = GVariantRef<kKeyType>::From(pair.first); |
| auto value = GVariantRef<kValueType>::From(pair.second); |
| return GVariantRef<kType>::TakeUnchecked( |
| g_variant_new_dict_entry(key.raw(), value.raw())); |
| } |
| |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom( |
| const std::pair<K, T>& pair) |
| requires(requires(K k, T v) { |
| GVariantRef<kKeyType>::TryFrom(k); |
| GVariantRef<kValueType>::TryFrom(v); |
| }) |
| { |
| auto key = GVariantRef<kKeyType>::TryFrom(pair.first); |
| if (!key.has_value()) { |
| return base::unexpected(std::move(key).error()); |
| } |
| auto value = GVariantRef<kValueType>::TryFrom(pair.second); |
| if (!value.has_value()) { |
| return base::unexpected(std::move(value).error()); |
| } |
| return GVariantRef<kType>::From(std::pair(key.value(), value.value())); |
| } |
| |
| static std::pair<K, T> Into(const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<kKeyType> k, GVariantRef<kValueType> v) { |
| k.template Into<K>(); |
| v.template Into<T>(); |
| }) |
| { |
| GVariantIter iter; |
| g_variant_iter_init(&iter, variant.raw()); |
| auto key_gvariant = |
| GVariantRef<kKeyType>::TakeUnchecked(g_variant_iter_next_value(&iter)); |
| auto value_gvariant = GVariantRef<kValueType>::TakeUnchecked( |
| g_variant_iter_next_value(&iter)); |
| return std::pair(key_gvariant.template Into<K>(), |
| value_gvariant.template Into<T>()); |
| } |
| |
| static base::expected<std::pair<K, T>, Loggable> TryInto( |
| const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<kKeyType> k, GVariantRef<kValueType> v) { |
| k.template TryInto<K>(); |
| v.template TryInto<T>(); |
| }) |
| { |
| auto gvariants = variant.template Into< |
| std::pair<GVariantRef<kKeyType>, GVariantRef<kValueType>>>(); |
| auto key_result = gvariants.first.template TryInto<K>(); |
| if (!key_result.has_value()) { |
| return base::unexpected(std::move(key_result).error()); |
| } |
| auto value_result = gvariants.second.template TryInto<T>(); |
| if (!value_result.has_value()) { |
| return base::unexpected(std::move(value_result).error()); |
| } |
| return std::pair(std::move(key_result).value(), |
| std::move(value_result).value()); |
| } |
| }; |
| |
| template <typename R> |
| // If the type can decay, let that happen first to avoid ambiguities. |
| // E.g., a std::vector<double>& could either be used as a range directly or |
| // decayed into a std::vector<double>. Resolving in favor of decay is |
| // desirable so that string constants decay into a const char* and are treated |
| // as a C string rather than a range of chars. |
| requires(std::ranges::range<R> && std::same_as<R, std::decay_t<R>>) |
| struct Mapping<R> { |
| static constexpr Type kInnerType = |
| Mapping<std::ranges::range_value_t<R>>::kType; |
| static constexpr Type kType{"a", kInnerType}; |
| |
| static GVariantRef<kType> From(const R& value) |
| requires(kInnerType.IsDefinite() && |
| requires(std::ranges::range_value_t<R> v) { |
| GVariantRef<kInnerType>::From(v); |
| }) |
| { |
| return internal::FromRange<kType, kInnerType>(value); |
| } |
| |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom(const R& value) |
| requires(requires(std::ranges::range_value_t<R> v) { |
| GVariantRef<kInnerType>::TryFrom(v); |
| }) |
| { |
| return internal::TryFromRange<kType, kInnerType>(value); |
| } |
| }; |
| |
| template <typename... Types> |
| struct Mapping<std::tuple<Types...>> { |
| static constexpr Type kType{"(", Mapping<Types>::kType..., ")"}; |
| |
| static GVariantRef<kType> From(const std::tuple<Types...>& value) |
| requires(requires(Types... v) { |
| (GVariantRef<Mapping<Types>::kType>::From(v), ...); |
| }) |
| { |
| GVariantBuilder builder; |
| g_variant_builder_init(&builder, kType.gvariant_type()); |
| |
| std::apply( |
| [&](const Types&... values) { |
| (g_variant_builder_add_value( |
| &builder, |
| GVariantRef<Mapping<Types>::kType>::From(values).raw()), |
| ...); |
| }, |
| value); |
| |
| return GVariantRef<kType>::TakeUnchecked(g_variant_builder_end(&builder)); |
| } |
| |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom( |
| const std::tuple<Types...>& value) |
| requires(requires(Types... v) { |
| (GVariantRef<Mapping<Types>::kType>::TryFrom(v), ...); |
| }) |
| { |
| auto conversion_result = std::apply( |
| [](const Types&... item) { return TupleTryFrom<Types...>(item...); }, |
| value); |
| if (!conversion_result.has_value()) { |
| return base::unexpected(std::move(conversion_result).error()); |
| } |
| return GVariantRef<kType>::From(conversion_result.value()); |
| } |
| |
| static std::tuple<Types...> Into(const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<Mapping<Types>::kType>... v) { |
| (v.template Into<Types>(), ...); |
| }) |
| { |
| GVariantIter iter; |
| g_variant_iter_init(&iter, variant.raw()); |
| // Must use uniform-initialization syntax since (in contrast to |
| // function-call syntax) it is specified to evalulate values in order. |
| auto gvariant_items = |
| std::tuple{GVariantRef<Mapping<Types>::kType>::TakeUnchecked( |
| g_variant_iter_next_value(&iter))...}; |
| return std::apply( |
| [](const GVariantRef<Mapping<Types>::kType>&... items) { |
| return std::tuple(items.template Into<Types>()...); |
| }, |
| gvariant_items); |
| } |
| |
| static base::expected<std::tuple<Types...>, Loggable> TryInto( |
| const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<Mapping<Types>::kType>... v) { |
| (v.template TryInto<Types>(), ...); |
| }) |
| { |
| auto gvariant_items = |
| variant |
| .template Into<std::tuple<GVariantRef<Mapping<Types>::kType>...>>(); |
| return std::apply( |
| [](const GVariantRef<Mapping<Types>::kType>&... items) { |
| return TupleTryInto<Types...>(items...); |
| }, |
| gvariant_items); |
| } |
| |
| private: |
| // Attempt to turn a tuple of Ts into a tuple of GVariantRefs |
| template <typename T = void> |
| static base::expected<std::tuple<>, Loggable> TupleTryFrom() { |
| return base::ok(std::tuple()); |
| } |
| |
| template <typename T, typename... Ts> |
| static base::expected<std::tuple<GVariantRef<Mapping<T>::kType>, |
| GVariantRef<Mapping<Ts>::kType>...>, |
| Loggable> |
| TupleTryFrom(const T& first, const Ts&... rest) { |
| auto first_result = GVariantRef<Mapping<T>::kType>::TryFrom(first); |
| if (!first_result.has_value()) { |
| return base::unexpected(std::move(first_result).error()); |
| } |
| auto rest_result = TupleTryFrom<Ts...>(rest...); |
| if (!rest_result.has_value()) { |
| return base::unexpected(std::move(first_result).error()); |
| } |
| return std::tuple_cat(std::tuple(first_result.value()), |
| rest_result.value()); |
| } |
| |
| // Attempt to turn a tuple of GVariantRefs into a tuple of Ts |
| template <typename T = void> |
| static base::expected<std::tuple<>, Loggable> TupleTryInto() { |
| return base::ok(std::tuple()); |
| } |
| |
| template <typename T, typename... Ts> |
| static base::expected<std::tuple<T, Ts...>, Loggable> TupleTryInto( |
| const GVariantRef<Mapping<T>::kType>& first, |
| const GVariantRef<Mapping<Ts>::kType>&... rest) { |
| auto first_result = first.template TryInto<T>(); |
| if (!first_result.has_value()) { |
| return base::unexpected(std::move(first_result).error()); |
| } |
| auto rest_result = TupleTryInto<Ts...>(rest...); |
| if (!rest_result.has_value()) { |
| return base::unexpected(std::move(first_result).error()); |
| } |
| return std::tuple_cat(std::tuple(first_result.value()), |
| rest_result.value()); |
| } |
| }; |
| |
| template <typename... Types> |
| requires(sizeof...(Types) > 0) |
| struct Mapping<std::variant<Types...>> { |
| static constexpr Type kType = |
| TypeBase::CommonSuperTypeOf<Mapping<Types>::kType...>(); |
| |
| static GVariantRef<kType> From(const std::variant<Types...>& value) |
| requires(requires(Types... v) { (GVariantRef<kType>::From(v), ...); }) |
| { |
| return [&]<std::size_t... Is>(std::index_sequence<Is...>) { |
| std::optional<GVariantRef<kType>> result; |
| ((std::ignore = |
| value.index() == Is && |
| (result.emplace(GVariantRef<kType>::From(std::get<Is>(value))), |
| false)), |
| ...); |
| return std::move(*result); |
| }(std::index_sequence_for<Types...>()); |
| } |
| |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom( |
| const std::variant<Types...>& value) |
| requires(requires(Types... v) { (GVariantRef<kType>::TryFrom(v), ...); }) |
| { |
| return [&]<std::size_t... Is>(std::index_sequence<Is...>) { |
| std::optional<base::expected<GVariantRef<kType>, Loggable>> result; |
| ((std::ignore = |
| value.index() == Is && |
| (result.emplace(GVariantRef<kType>::TryFrom(std::get<Is>(value))), |
| false)), |
| ...); |
| return std::move(*result); |
| }(std::index_sequence_for<Types...>()); |
| } |
| |
| template <Type C> |
| static std::variant<Types...> Into(const GVariantRef<C>& variant) |
| // Infallible Into is provided if at least one alternative provides an |
| // infallible Into for the provided type. |
| requires(... || requires { variant.template Into<Types>(); }) |
| { |
| // Try TryInto() first so where possible Into and TryInto produce the same |
| // variant. |
| auto result = VariantTryInto<0, Types...>(variant); |
| if (result.has_value()) { |
| return std::move(result).value(); |
| } |
| |
| // To get here, one of the types must provide both a fallible and infallible |
| // conversion, and the fallible version failed. Loop through again and call |
| // the infallible version to get a value to return. |
| return VariantInto<C, 0, Types...>(variant); |
| } |
| |
| static base::expected<std::variant<Types...>, Loggable> TryInto( |
| const GVariantRef<kType>& variant) |
| // TryInto is only provided if it is provided by at least one alternative. |
| requires(... || requires { variant.template TryInto<Types>(); }) |
| { |
| return VariantTryInto<0, Types...>(variant); |
| } |
| |
| private: |
| template <std::size_t I> |
| static base::expected<std::variant<Types...>, Loggable> VariantTryInto( |
| const GVariantRef<kType>& variant) { |
| return base::unexpected( |
| Loggable(FROM_HERE, "No variant alternative could decode value")); |
| } |
| |
| template <std::size_t I, typename T, typename... Ts> |
| static base::expected<std::variant<Types...>, Loggable> VariantTryInto( |
| const GVariantRef<kType>& variant) { |
| if constexpr (requires { variant.template TryInto<T>(); }) { |
| auto alternative_result = variant.template TryInto<T>(); |
| if (alternative_result.has_value()) { |
| return base::ok(std::variant<Types...>( |
| std::in_place_index<I>, std::move(alternative_result).value())); |
| } |
| } |
| return VariantTryInto<I + 1, Ts...>(variant); |
| } |
| |
| // No base case needed, as at least one type is guaranteed to provide an |
| // infallible conversion. |
| template <Type C, std::size_t I, typename T, typename... Ts> |
| static std::variant<Types...> VariantInto(const GVariantRef<C>& variant) { |
| if constexpr (requires { variant.template Into<T>(); }) { |
| return std::variant<Types...>(std::in_place_index<I>, |
| variant.template Into<T>()); |
| } else { |
| return VariantInto<C, I + 1, Ts...>(variant); |
| } |
| } |
| }; |
| |
| // Special Types |
| |
| template <Type C> |
| struct Mapping<GVariantRef<C>> { |
| static constexpr Type kType = C; |
| static GVariantRef<kType> From(GVariantRef<kType> value) { return value; } |
| static GVariantRef<kType> Into(GVariantRef<kType> value) { return value; } |
| }; |
| |
| template <> |
| struct Mapping<Ignored> { |
| static constexpr Type kType{"*"}; |
| static Ignored Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <> |
| struct Mapping<decltype(std::ignore)> { |
| static constexpr Type kType{"*"}; |
| static decltype(std::ignore) Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <typename T> |
| struct Mapping<Boxed<T>> { |
| static constexpr Type kType{"v"}; |
| |
| static GVariantRef<kType> From(const Boxed<T>& value) |
| requires(requires(T v) { GVariantRef<>::From(v); }) |
| { |
| return GVariantRef<kType>::TakeUnchecked( |
| g_variant_new_variant(GVariantRef<>::From(value.value).raw())); |
| } |
| |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom( |
| const Boxed<T>& value) |
| requires(requires(T v) { GVariantRef<>::TryFrom(v); }) |
| { |
| auto result = GVariantRef<>::TryFrom(value.value); |
| if (!result.has_value()) { |
| return base::unexpected(std::move(result).error()); |
| } |
| return base::ok(GVariantRef<kType>::From(Boxed{result.value()})); |
| } |
| |
| static Boxed<T> Into(const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<> v) { v.Into<T>(); }) |
| { |
| return Boxed{ |
| GVariantRef<>::Take(g_variant_get_variant(variant.raw())).Into<T>()}; |
| } |
| |
| static base::expected<Boxed<T>, Loggable> TryInto( |
| const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<> v) { v.TryInto<T>(); }) |
| { |
| return variant.Into<Boxed<GVariantRef<>>>().value.TryInto<T>().transform( |
| [](auto v) { return Boxed{std::move(v)}; }); |
| } |
| }; |
| |
| template <typename T> |
| struct Mapping<FilledMaybe<T>> { |
| static constexpr Type kInnerType = Mapping<T>::kType; |
| static constexpr Type kType{"m", kInnerType}; |
| |
| static GVariantRef<kType> From(const FilledMaybe<T>& value) |
| requires(requires(T v) { GVariantRef<kInnerType>::From(v); }) |
| { |
| return GVariantRef<kType>::TakeUnchecked(g_variant_new_maybe( |
| nullptr, GVariantRef<kInnerType>::From(value.value).raw())); |
| } |
| |
| static base::expected<GVariantRef<kType>, Loggable> TryFrom( |
| const FilledMaybe<T>& value) |
| requires(requires(T v) { GVariantRef<kInnerType>::TryFrom(v); }) |
| { |
| auto result = GVariantRef<kInnerType>::TryFrom(value.value); |
| if (!result.has_value()) { |
| return base::unexpected(std::move(result).error()); |
| } |
| return base::ok(GVariantRef<kType>::From(FilledMaybe{result.value()})); |
| } |
| |
| static base::expected<FilledMaybe<T>, Loggable> TryInto( |
| const GVariantRef<kType>& variant) |
| requires(requires(GVariantRef<kInnerType> v) { v.template TryInto<T>(); }) |
| { |
| GVariant* contents = g_variant_get_maybe(variant.raw()); |
| if (!contents) { |
| return base::unexpected( |
| Loggable(FROM_HERE, "Maybe value unexpectedly empty")); |
| } |
| |
| return GVariantRef<kInnerType>::TakeUnchecked(contents) |
| .template TryInto<T>() |
| .transform([](auto v) { return FilledMaybe{std::move(v)}; }); |
| } |
| }; |
| |
| template <Type C> |
| struct Mapping<EmptyArrayOf<C>> { |
| static constexpr Type kType{"a", C}; |
| |
| static GVariantRef<kType> From(const EmptyArrayOf<C>& value) |
| requires(C.IsDefinite()) |
| { |
| return GVariantRef<kType>::TakeUnchecked( |
| g_variant_new_array(C.gvariant_type(), nullptr, 0)); |
| } |
| |
| static base::expected<EmptyArrayOf<C>, Loggable> TryInto( |
| const GVariantRef<kType>& variant) { |
| if (auto size = g_variant_n_children(variant.raw()); size != 0) { |
| return base::unexpected( |
| Loggable(FROM_HERE, "Array unexpectedly not empty.")); |
| } |
| |
| return EmptyArrayOf<C>{}; |
| } |
| }; |
| |
| template <> |
| struct Mapping<ObjectPathCStr> { |
| static constexpr Type kType{"o"}; |
| static GVariantRef<kType> From(const ObjectPathCStr& value); |
| }; |
| |
| template <> |
| struct Mapping<ObjectPath> { |
| static constexpr Type kType{"o"}; |
| static GVariantRef<kType> From(const ObjectPath& value); |
| static ObjectPath Into(const GVariantRef<kType>& variant); |
| }; |
| |
| template <> |
| struct Mapping<TypeSignatureCStr> { |
| static constexpr Type kType{"g"}; |
| static GVariantRef<kType> From(const TypeSignatureCStr& value); |
| }; |
| |
| template <> |
| struct Mapping<TypeSignature> { |
| static constexpr Type kType{"g"}; |
| static GVariantRef<kType> From(const TypeSignature& value); |
| static TypeSignature Into(const GVariantRef<kType>& variant); |
| }; |
| |
| } // namespace gvariant |
| |
| using gvariant::GVariantFrom; |
| using gvariant::GVariantRef; |
| |
| } // namespace remoting |
| |
| // Make tuple-like |
| template <remoting::gvariant::Type C> |
| requires(C.IsFixedSizeContainer()) |
| struct std::tuple_size<remoting::GVariantRef<C>> |
| : public std::tuple_size< |
| decltype(remoting::gvariant::TypeBase::Unpack<C>())> {}; |
| |
| template <std::size_t I, remoting::gvariant::Type C> |
| requires(C.IsFixedSizeContainer()) |
| struct std::tuple_element<I, remoting::GVariantRef<C>> { |
| using type = remoting::GVariantRef<std::get<I>( |
| remoting::gvariant::TypeBase::Unpack<C>())>; |
| }; |
| |
| #endif // REMOTING_HOST_LINUX_GVARIANT_REF_H_ |