Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WORK-IN-PROGRESS] Compile time key value pairs #4

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .nxxm/deps

This file was deleted.

6 changes: 6 additions & 0 deletions .tipi/deps
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"nlohmann/json" : { "@" : "v3.1.2", "x" : ["benchmarks"] }
, "boostorg/pfr" : {}
, "tipi-build/constexpr_all_the_things" : { "@" : "feature/json-substrings-as-types" }
, "platform" : [ "Boost::+boost" ]
}
57 changes: 57 additions & 0 deletions pre/cx/key_value_pair.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#pragma once

#include <cx_json_parser.h>
#include <cx_json_value.h>

#define pre_json_key(type, name) ::pre::cx::key_value_pair<"" # name, type> name

namespace pre::cx {
using ::cx::static_string;
template <cx::static_string str>
struct tstring {
static constexpr cx::static_string value = str;
};

template <cx::static_string key_, class T>
struct key_value_pair {
using key = tstring<key_>;
T value;
friend bool operator<(const key_value_pair& x, const key_value_pair& y) {
return x.value < y.value;
}
template<class U> friend bool operator==(const key_value_pair& lhs, const U& rhs){return lhs.value == rhs.value;}
template<class U> friend bool operator< (const key_value_pair& lhs, const U& rhs){return lhs.value < rhs.value;}
template<class U> friend bool operator!=(const key_value_pair& lhs, const U& rhs){return !operator==(lhs,rhs);}
template<class U> friend bool operator> (const key_value_pair& lhs, const U& rhs){return operator< (rhs,lhs);}
template<class U> friend bool operator<=(const key_value_pair& lhs, const U& rhs){return !operator> (lhs,rhs);}
template<class U> friend bool operator>=(const key_value_pair& lhs, const U& rhs){return !operator< (lhs,rhs);}
};
}

namespace std {
template <cx::static_string key_, class T>
class hash<pre::cx::key_value_pair<key_, T>> {
public:
size_t operator()(const pre::cx::key_value_pair<key_, T> &k) const
{
return std::hash<std::string>{}(key_.c_str());
}
};
}

namespace pre::cx {
template<class T>
auto get_key(const T&) {
return T::key::value.c_str();
};

/*template <cx::static_string key_, class T>
inline bool operator==(key_value_pair<key_, T> const& a, key_value_pair<key_, T> const& b) {
return a.value == b.value;
}
template <cx::static_string key_, class T>
inline bool operator<(key_value_pair<key_, T> const& a, key_value_pair<key_, T> const& b) {
return a.value < b.value;
}*/

}
35 changes: 35 additions & 0 deletions pre/json/detail/dejsonizer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@

#include <pre/variant/for_each_type.hpp>

#include <pre/cx/key_value_pair.hpp>

#include <boost/pfr.hpp>
#include <boost/hana/for_each.hpp>
#include <boost/hana/ext/std/tuple.hpp>

namespace pre { namespace json { namespace detail {

struct dejsonizer {
Expand Down Expand Up @@ -159,6 +165,35 @@ namespace pre { namespace json { namespace detail {
throw std::runtime_error("Expected " + _json_object.dump() + " to be a json object.");
}
}


//! Named fields
template<cx::static_string key_, class T>
void operator()(pre::cx::key_value_pair<key_, T>& value) const {
this->operator()(get_key(value), value.value);
}

//! Non adapted structs with key_value_pair
template<class T,
enable_if_is_struct_non_adapted_t<T>* = nullptr>
void operator()(T& value) const {


auto t = boost::pfr::structure_tie(value);
boost::hana::for_each(t,
[this](auto& x) {
this->operator()(x);
});
}

//! tuples
template<requirements::tuple_like T>
void operator()(T& value) const {
boost::hana::for_each(value,
[this](auto& x) {
this->operator()(x);
});
}

private:
const nlohmann::json& _json_object; // XXX: Invert to be the same as jsonizer
Expand Down
32 changes: 32 additions & 0 deletions pre/json/detail/jsonizer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
#include <pre/enums/to_underlying.hpp>
#include <pre/variant/apply_visitor.hpp>

#include <pre/cx/key_value_pair.hpp>

#include <boost/pfr.hpp>
#include <boost/hana/for_each.hpp>
#include <boost/hana/ext/std/tuple.hpp>

//TODO: What about tuples ?
//TODO: What about normal union?

Expand Down Expand Up @@ -119,6 +125,32 @@ namespace pre { namespace json { namespace detail {
}
}

//! Named fields
template<cx::static_string key_, class T>
void operator()(const pre::cx::key_value_pair<key_, T>& value) const {
this->operator()(get_key(value), value.value);
}

//! Non adapted structs with key_value_pair
template<class T,
enable_if_is_struct_non_adapted_t<T>* = nullptr>
void operator()(const T& value) const {
auto t = boost::pfr::structure_tie(value);
boost::hana::for_each(t,
[this](const auto& x) {
this->operator()(x);
});
}

//! tuples
template<requirements::tuple_like T>
void operator()(const T& value) const {
boost::hana::for_each(value,
[this](const auto& x) {
this->operator()(x);
});
}


private:
nlohmann::json& _json_object;
Expand Down
31 changes: 30 additions & 1 deletion pre/json/detail/sfinae_enabler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ namespace pre { namespace json { namespace detail {
>::value
,T>::type;

template<class T>
using enable_if_is_struct_non_adapted_t = typename std::enable_if<
! std::is_same<
typename boost::fusion::traits::tag_of<T>::type,
boost::fusion::struct_tag
>::value && std::is_aggregate_v<T>
,T>::type;

template<class T>
using enable_if_is_container_t = typename std::enable_if<
Expand All @@ -61,7 +68,29 @@ namespace pre { namespace json { namespace detail {
traits::is_associative_container<T>::value
,T>::type;


namespace requirements {
template<class T, std::size_t N>
concept has_tuple_element =
requires(T t) {
typename std::tuple_element_t<N, std::remove_const_t<T>>;
{ get<N>(t) } -> std::convertible_to<const std::tuple_element_t<N, T>&>;
};

template<class T>
concept tuple_like = !std::is_reference_v<T>
&& requires(T t) {
typename std::tuple_size<T>::type;
requires std::derived_from<
std::tuple_size<T>,
std::integral_constant<std::size_t, std::tuple_size_v<T>>
>;
} && []<std::size_t... N>(std::index_sequence<N...>) {
return (has_tuple_element<T, N> && ...);
}(std::make_index_sequence<std::tuple_size_v<T>>());

template<class T>
concept not_tuple_like = !tuple_like<T>;
}
}}}

#endif
115 changes: 114 additions & 1 deletion test/dejsonize_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include <vector>
#include <list>

#include <boost/fusion/include/equal_to.hpp>
#include <boost/fusion/include/comparison.hpp>
#include <boost/fusion/include/define_struct.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

Expand All @@ -22,6 +22,7 @@
namespace datamodel {

using boost::fusion::operator==;
using boost::fusion::operator<;
using boost::fusion::operator!=;

typedef size_t years;
Expand Down Expand Up @@ -616,3 +617,115 @@ BOOST_AUTO_TEST_CASE (std_variant_complex) {


}

namespace datamodel {
struct BuildRunnerRequestDto {
pre_json_key(std::string, ssh_public_key);
pre_json_key(std::string, environment_name);
pre_json_key(std::string, environment_zip_hash);
pre_json_key(std::size_t, job_size);
pre_json_key(std::string, environment_zip);
pre_json_key(cashier, user);
};
BOOST_PFR_FUNCTIONS_FOR(BuildRunnerRequestDto);
}

BOOST_AUTO_TEST_CASE (compile_time_named_fields_no_adapt_struct) {

{
using datamodel::BuildRunnerRequestDto;
BuildRunnerRequestDto val{ .ssh_public_key = { "that's one key"} };

auto val_json = pre::json::to_json(val);
std::cout << val_json.dump(2) << std::endl;

auto val_deserialized = pre::json::from_json<decltype(val)>(val_json);

auto val_reserialized = pre::json::to_json(val_deserialized);
std::cout << val_reserialized.dump(2) << std::endl;

BOOST_REQUIRE(val == val_deserialized);
}

{
using datamodel::BuildRunnerRequestDto;
std::vector<BuildRunnerRequestDto> vals{
{.ssh_public_key = { "that's one key"}, .job_size = {11}},
{.ssh_public_key = { "that's second key"}, .job_size = {43}},
{.ssh_public_key = { "that's third key"}, .job_size = {13}, .user{{ .section="banananas", .checkout_number=23 }} }
};

auto val_json = pre::json::to_json(vals);
std::cout << val_json.dump(2) << std::endl;

auto val_deserialized = pre::json::from_json<decltype(vals)>(val_json);

auto val_reserialized = pre::json::to_json(val_deserialized);
std::cout << val_reserialized.dump(2) << std::endl;

BOOST_REQUIRE(vals == val_deserialized);
}

{
using pre::cx::key_value_pair;
using namespace datamodel;
using namespace std::literals;

auto val = std::make_tuple(
key_value_pair<"ssh_public_key" , std::string> {"key"s},
key_value_pair<"environment_name" , std::string> {"env"s},
key_value_pair<"environment_zip_hash" , std::string> {"zip_hash"s},
key_value_pair<"job_size" , std::size_t> {434343},
key_value_pair<"environment_zip" , std::string> {"zip"s},
key_value_pair<"user" , cashier> {{ .section="wine & spirits", .checkout_number=27}}
);

auto val_json = pre::json::to_json(val);
std::cout << val_json.dump(2) << std::endl;

auto val_deserialized = pre::json::from_json<decltype(val)>(val_json);

auto val_reserialized = pre::json::to_json(val_deserialized);
std::cout << val_reserialized.dump(2) << std::endl;

BOOST_REQUIRE(val == val_deserialized);
}


{
using pre::cx::key_value_pair;
using namespace datamodel;
using namespace std::literals;

auto val = std::make_tuple(
key_value_pair<"ssh_public_key" , std::string> {"key"s},
key_value_pair<"environment_name" , std::string> {"env"s},
key_value_pair<"environment_zip_hash" , std::string> {"zip_hash"s},
key_value_pair<"job_size" , std::size_t> {434343},
key_value_pair<"environment_zip" , std::string> {"zip"s},
key_value_pair<"user" , cashier> {{ .section="wine & spirits", .checkout_number=27}}
);

std::vector vals { val };
vals.push_back(std::make_tuple(
key_value_pair<"ssh_public_key" , std::string> {"yoooo"s},
key_value_pair<"environment_name" , std::string> {"woui"s},
key_value_pair<"environment_zip_hash" , std::string> {"noooooo"s},
key_value_pair<"job_size" , std::size_t> {540210},
key_value_pair<"environment_zip" , std::string> {"timeeeee"s},
key_value_pair<"user" , cashier> {{ .section="Gemüse", .checkout_number=3486}}
));

auto val_json = pre::json::to_json(vals);
std::cout << val_json.dump(2) << std::endl;

auto val_deserialized = pre::json::from_json<decltype(vals)>(val_json);

auto val_reserialized = pre::json::to_json(val_deserialized);
std::cout << val_reserialized.dump(2) << std::endl;

BOOST_REQUIRE(vals == val_deserialized);
}


}