#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"
namespace remoting {
namespace gvariant {
template <Type C = Type("*")>
class GVariantRef;
template <typename T>
struct Mapping;
class GVariantBase {
public:
explicit operator GVariantRef<>() const;
GVariant* raw() const;
[[nodiscard]] GVariant* release() &&;
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;
GVariantRef() = default;
GVariantRef(const GVariantRef& other) = default;
GVariantRef(GVariantRef&& other) = default;
GVariantRef& operator=(const GVariantRef& other) = default;
GVariantRef& operator=(GVariantRef&& other) = default;
~GVariantRef() = default;
template <Type D>
GVariantRef(const GVariantRef<D>& other)
requires(D.IsSubtypeOf(C));
template <Type D>
GVariantRef(GVariantRef<D>&& other)
requires(D.IsSubtypeOf(C));
template <typename T>
static GVariantRef From(const T& value)
requires(Mapping<T>::kType.IsSubtypeOf(C) &&
requires { Mapping<T>::From(value); });
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); }));
template <typename T>
T Into() const
requires(C.IsSubtypeOf(Mapping<T>::kType) &&
requires { Mapping<T>::Into(*this); });
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);
}));
template <typename... Types>
void Destructure(Types&&... refs) const;
template <typename... Types>
base::expected<void, Loggable> TryDestructure(Types&&... refs) const;
auto begin() const
requires(C.IsContainer());
auto end() const
requires(C.IsContainer());
std::size_t size() const
requires(C.IsContainer());
template <std::size_t I>
auto get() const
requires(C.IsFixedSizeContainer());
template <typename T>
auto LookUp(const T& key)
requires(C.IsSubtypeOf(Type("a{?*}")) && requires {
decltype((*begin()).template get<0>())::TryFrom(key);
});
std::string_view string_view() const
requires(C.IsStringType());
const char* c_string() const
requires(C.IsStringType());
static GVariantRef Take(GVariant* variant)
requires(C == Type("*"));
static GVariantRef Ref(GVariant* variant)
requires(C == Type("*"));
static GVariantRef RefSink(GVariant* variant)
requires(C == Type("*"));
static GVariantRef TakeUnchecked(GVariant* variant);
static GVariantRef RefUnchecked(GVariant* variant);
static GVariantRef RefSinkUnchecked(GVariant* variant);
private:
using GVariantBase::GVariantBase;
};
template <typename T>
static GVariantRef<Mapping<T>::kType> GVariantFrom(const T& value) {
return GVariantRef<Mapping<T>::kType>::From(value);
}
struct Ignored {};
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;
}
template <typename T>
Boxed<const T&> BoxedRef(const T& value LIFETIME_BOUND) {
return {value};
}
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;
}
template <typename T>
FilledMaybe<const T&> FilledMaybeRef(const T& value LIFETIME_BOUND) {
return {value};
}
template <Type C>
struct EmptyArrayOf {};
class ObjectPath;
class ObjectPathCStr {
public:
consteval ObjectPathCStr(const char* path);
ObjectPathCStr(const ObjectPath& path LIFETIME_BOUND);
static base::expected<ObjectPathCStr, Loggable> TryFrom(
const char* path LIFETIME_BOUND);
constexpr const char* c_str() const LIFETIME_BOUND;
friend constexpr bool operator==(const ObjectPathCStr& lhs,
const ObjectPathCStr& rhs);
private:
struct Checked {};
ObjectPathCStr(const char* path LIFETIME_BOUND, Checked);
const char* path_;
};
constexpr bool operator==(const ObjectPathCStr& lhs, const ObjectPathCStr& rhs);
class ObjectPath {
public:
ObjectPath();
explicit ObjectPath(ObjectPathCStr path);
static base::expected<ObjectPath, Loggable> TryFrom(std::string path);
const std::string& value() const LIFETIME_BOUND;
const char* c_str() const LIFETIME_BOUND;
private:
explicit ObjectPath(std::string path);
std::string path_;
friend struct Mapping<ObjectPath>;
};
class TypeSignature;
class TypeSignatureCStr {
public:
consteval TypeSignatureCStr(const char* signature);
TypeSignatureCStr(const TypeSignature& signature LIFETIME_BOUND);
static base::expected<TypeSignatureCStr, Loggable> TryFrom(
const char* signature LIFETIME_BOUND);
constexpr const char* c_str() const LIFETIME_BOUND;
friend constexpr bool operator==(const TypeSignatureCStr& lhs,
const TypeSignatureCStr& rhs);
private:
struct Checked {};
TypeSignatureCStr(const char* signature LIFETIME_BOUND, Checked);
const char* signature_;
};
constexpr bool operator==(const TypeSignatureCStr& lhs,
const TypeSignatureCStr& rhs);
class TypeSignature {
public:
TypeSignature();
explicit TypeSignature(TypeSignatureCStr signature);
static base::expected<TypeSignature, Loggable> TryFrom(std::string signature);
const std::string& value() const LIFETIME_BOUND;
const char* c_str() const LIFETIME_BOUND;
private:
explicit TypeSignature(std::string);
std::string signature_;
friend struct Mapping<TypeSignature>;
};
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;
Iterator(const Iterator& other) = default;
Iterator(Iterator&& other) = default;
Iterator& operator=(const Iterator& other) = default;
Iterator& operator=(Iterator&& other) = default;
using iterator_category = std::input_iterator_tag;
using iterator_concept = std::random_access_iterator_tag;
using value_type = GVariantRef<C>;
using reference_type = GVariantRef<C>;
using difference_type = std::ptrdiff_t;
value_type operator*() const;
Iterator& operator++();
Iterator operator++(int);
bool operator==(const Iterator& other) const;
Iterator& operator--();
Iterator operator--(int);
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;
};
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)) {}
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);
}
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));
}
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()) {
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);
}
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));
}
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));
}
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));
}
consteval ObjectPathCStr::ObjectPathCStr(const char* path) {
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) {
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;
}
}
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) {}
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)
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);
}
};
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);
};
template <>
struct Mapping<std::string> {
static constexpr Type kType{"s"};
static GVariantRef<kType> From(const std::string& value);
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"};
static GVariantRef<kType> From(std::string_view value);
static base::expected<GVariantRef<kType>, Loggable> TryFrom(
std::string_view value);
};
template <>
struct Mapping<const char*> {
static constexpr Type kType{"s"};
static GVariantRef<kType> From(const char* value);
static base::expected<GVariantRef<kType>, Loggable> TryFrom(
const char* value);
};
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)));
}
}
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>
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());
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:
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());
}
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)
requires(... || requires { variant.template Into<Types>(); })
{
auto result = VariantTryInto<0, Types...>(variant);
if (result.has_value()) {
return std::move(result).value();
}
return VariantInto<C, 0, Types...>(variant);
}
static base::expected<std::variant<Types...>, Loggable> TryInto(
const GVariantRef<kType>& variant)
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);
}
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);
}
}
};
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);
};
}
using gvariant::GVariantFrom;
using gvariant::GVariantRef;
}
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