diff --git a/test/test.hpp b/test/test.hpp index b3c99fa..0f69ac0 100644 --- a/test/test.hpp +++ b/test/test.hpp @@ -157,6 +157,12 @@ std::ostream& operator<<(std::ostream& os, Enum value) return os << static_cast>(value); } +template +std::ostream& operator<<(std::ostream& os, std::optional const& optional) +{ + return optional ? os << *optional : os << "nullopt"; +} + template std::ostream& operator<<(std::ostream& os, std::tuple const& tuple) { diff --git a/test/test_convert.cpp b/test/test_convert.cpp index ca0b80e..b2affb6 100644 --- a/test/test_convert.cpp +++ b/test/test_convert.cpp @@ -61,25 +61,88 @@ void test_string_conv(v8::Isolate* isolate, Char const (&str)[N]) v8pp::from_v8(isolate, v8pp::to_v8(isolate, empty, 0)), empty); } +struct address +{ + std::string zip; + std::string city; + std::string street; + std::string house; + std::optional flat; + + //for test framework + bool operator==(address const& other) const = default; + + friend std::ostream& operator<<(std::ostream& os, address const& a) + { + return os << "address: " << a.zip << " " << a.city << " " << a.street << " " << a.house << " " << a.flat; + } +}; + struct person { std::string name; int age; + std::optional
home; //for test framework - bool operator!=(person const& other) const - { - return name != other.name || age != other.age; - } + bool operator==(person const& other) const = default; friend std::ostream& operator<<(std::ostream& os, person const& p) { - return os << "person: " << p.name << " age: " << p.age; + return os << "person: " << p.name << " age: " << p.age << " home: " << p.home; } }; namespace v8pp { +template<> +struct convert
+{ + using from_type = address; + using to_type = v8::Local; + + static bool is_valid(v8::Isolate*, v8::Local value) + { + return !value.IsEmpty() && value->IsObject(); + } + + static to_type to_v8(v8::Isolate* isolate, address const& a) + { + v8::EscapableHandleScope scope(isolate); + v8::Local obj = v8::Object::New(isolate); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "zip"), v8pp::to_v8(isolate, a.zip)).FromJust(); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "city"), v8pp::to_v8(isolate, a.city)).FromJust(); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "street"), v8pp::to_v8(isolate, a.street)).FromJust(); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "house"), v8pp::to_v8(isolate, a.house)).FromJust(); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "flat"), v8pp::to_v8(isolate, a.flat)).FromJust(); + return scope.Escape(obj); + } + + static from_type from_v8(v8::Isolate* isolate, v8::Local value) + { + if (!is_valid(isolate, value)) + { + throw std::runtime_error("expected object"); + } + + v8::HandleScope scope(isolate); + v8::Local obj = value.As(); + + address result; + result.zip = v8pp::from_v8(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "zip")).ToLocalChecked()); + result.city = v8pp::from_v8(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "city")).ToLocalChecked()); + result.street = v8pp::from_v8(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "street")).ToLocalChecked()); + result.house = v8pp::from_v8(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "house")).ToLocalChecked()); + result.flat = v8pp::from_v8(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "flat")).ToLocalChecked()); + return result; + } +}; + template<> struct convert { @@ -97,9 +160,11 @@ struct convert v8::Local obj = v8::Object::New(isolate); obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "name"), v8pp::to_v8(isolate, p.name)).FromJust(); obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "age"), v8pp::to_v8(isolate, p.age)).FromJust(); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "home"), v8pp::to_v8(isolate, p.home)).FromJust(); /* Simpler after #include set_option(isolate, obj, "name", p.name); set_option(isolate, obj, "age", p.age); + set_option(isolate, obj, "home", p.home); */ return scope.Escape(obj); } @@ -119,10 +184,13 @@ struct convert obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "name")).ToLocalChecked()); result.age = v8pp::from_v8(isolate, obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "age")).ToLocalChecked()); + result.home = v8pp::from_v8>(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "home")).ToLocalChecked()); /* Simpler after #include get_option(isolate, obj, "name", result.name); get_option(isolate, obj, "age", result.age); + get_option(isolate, obj, "home", result.home); */ return result; } @@ -135,6 +203,25 @@ void test_convert_user_type(v8::Isolate* isolate) person p; p.name = "Al"; p.age = 33; test_conv(isolate, p); + p.home = { .zip = "90210", .city = "Beverly Hills", .street = "Main St", .house = "123", .flat = "B2" }; + test_conv(isolate, p); +} + +void test_convert_optional(v8::Isolate* isolate) +{ + test_conv(isolate, std::optional{42}); + test_conv(isolate, std::optional{std::nullopt}); + + check("null", v8pp::from_v8>(isolate, v8::Null(isolate)) == std::nullopt); + check("undefined", v8pp::from_v8>(isolate, v8::Undefined(isolate)) == std::nullopt); + + check("nullopt", v8pp::to_v8(isolate, std::nullopt)->IsNull()); + check("monostate", v8pp::to_v8(isolate, std::monostate{})->IsUndefined()); + + check_ex("wrong optional type", [isolate]() + { + v8pp::from_v8>(isolate, v8pp::to_v8(isolate, std::optional{"aa"})); + }); } void test_convert_tuple(v8::Isolate* isolate) @@ -148,6 +235,9 @@ void test_convert_tuple(v8::Isolate* isolate) std::tuple const tuple_3{ 1, 2, 3 }; test_conv(isolate, tuple_3); + std::tuple, int, std::optional> const tuple_4{ 1, 2, 3, std::nullopt }; + test_conv(isolate, tuple_4); + check_ex("Tuple", [isolate, &tuple_1]() { // incorrect number of elements @@ -316,8 +406,9 @@ void test_convert_variant(v8::Isolate* isolate) check_arithmetic_reversed(2, 5.5f, true); check_arithmetic_reversed(-2, 2.2f, false); - variant_check, float, std::string> check_vector{ isolate }; - check_vector({1.f, 2.f, 3.f}, 4.f, "testing"); + variant_check, float, std::optional> check_vector{ isolate }; + check_vector({1.f, 2.f, 3.f}, 4.f, std::optional("testing")); + check_vector(std::vector{}, 0.f, std::optional{}); // The order here matters variant_check order_check{ isolate }; @@ -353,6 +444,10 @@ void test_convert_variant(v8::Isolate* isolate) variant_check> unordered_multimap_check{ isolate }; unordered_multimap_check(U2{3.0}, std::unordered_multimap{ { 'a', U{1} }, { 'b', U{2} } }); + + variant_check, bool> optional_check{ isolate }; + optional_check(true, "test", 1); + optional_check(0, std::optional{}, false); } void test_convert() @@ -405,6 +500,7 @@ void test_convert() v8pp::from_v8>(isolate, v8pp::to_v8(isolate, list.begin(), list.end())), vector); test_convert_user_type(isolate); + test_convert_optional(isolate); test_convert_tuple(isolate); test_convert_variant(isolate); } diff --git a/v8pp/call_from_v8.hpp b/v8pp/call_from_v8.hpp index 771bbc9..939d174 100644 --- a/v8pp/call_from_v8.hpp +++ b/v8pp/call_from_v8.hpp @@ -10,12 +10,22 @@ namespace v8pp::detail { +template +struct optional_count; + +template +struct optional_count> +{ + static constexpr size_t value = (0 + ... + is_optional::value); +}; + template struct call_from_v8_traits { static constexpr size_t offset = Offset; static constexpr bool is_mem_fun = std::is_member_function_pointer_v; using arguments = typename function_traits::arguments; + static constexpr size_t optional_arg_count = optional_count::value; static constexpr size_t arg_count = std::tuple_size_v - is_mem_fun - offset; @@ -89,7 +99,8 @@ decltype(auto) call_from_v8(F&& func, v8::FunctionCallbackInfo const& using call_traits = call_from_v8_traits; using indices = std::make_index_sequence; - if (args.Length() != call_traits::arg_count) + size_t const arg_count = args.Length(); + if (arg_count > call_traits::arg_count || arg_count < call_traits::arg_count - call_traits::optional_arg_count) { throw std::runtime_error( "Argument count does not match function definition. Expected " + diff --git a/v8pp/convert.hpp b/v8pp/convert.hpp index db04168..7c25ff9 100644 --- a/v8pp/convert.hpp +++ b/v8pp/convert.hpp @@ -148,6 +148,60 @@ struct convert : convert> #endif // converter specializations for primitive types +template<> +struct convert +{ + using from_type = std::monostate; + using to_type = v8::Local; + + static bool is_valid(v8::Isolate*, v8::Local value) + { + return value.IsEmpty() || value->IsUndefined(); + } + + static std::monostate from_v8(v8::Isolate* isolate, v8::Local value) + { + if (!is_valid(isolate, value)) + { + throw invalid_argument(isolate, value, "Undefined"); + } + + return std::monostate{}; + } + + static v8::Local to_v8(v8::Isolate* isolate, std::monostate) + { + return v8::Undefined(isolate); + } +}; + +template<> +struct convert +{ + using from_type = std::nullopt_t; + using to_type = v8::Local; + + static bool is_valid(v8::Isolate*, v8::Local value) + { + return value.IsEmpty() || value->IsNull(); + } + + static std::nullopt_t from_v8(v8::Isolate* isolate, v8::Local value) + { + if (!is_valid(isolate, value)) + { + throw invalid_argument(isolate, value, "Null"); + } + + return std::nullopt; + } + + static v8::Local to_v8(v8::Isolate* isolate, std::nullopt_t) + { + return v8::Null(isolate); + } +}; + template<> struct convert { @@ -288,6 +342,47 @@ struct convert::value>::typ } }; +// convert std::optional <-> value or undefined +template +struct convert> +{ + using from_type = std::optional; + using to_type = v8::Local; + + static bool is_valid(v8::Isolate* isolate, v8::Local value) + { + return value.IsEmpty() || value->IsNullOrUndefined() || convert::is_valid(isolate, value); + } + + static from_type from_v8(v8::Isolate* isolate, v8::Local value) + { + if (value.IsEmpty() || value->IsNullOrUndefined()) + { + return std::nullopt; + } + else if (convert::is_valid(isolate, value)) + { + return convert::from_v8(isolate, value); + } + else + { + throw invalid_argument(isolate, value, "Optional"); + } + } + + static to_type to_v8(v8::Isolate* isolate, std::optional const& value) + { + if (value) + { + return convert::to_v8(isolate, *value); + } + else + { + return v8::Undefined(isolate); + } + } +}; + // convert std::tuple <-> Array template struct convert> @@ -364,36 +459,44 @@ struct convert> v8::HandleScope scope(isolate); + if (value.IsEmpty() || value->IsNull()) + { + return alternate(isolate, value); + } + else if (value->IsUndefined()) + { + return alternate(isolate, value); + } if (value->IsBoolean()) { - return alternate(isolate, value); + return alternate(isolate, value); } else if (value->IsInt32() || value->IsUint32()) { - return alternate(isolate, value); + return alternate(isolate, value); } else if (value->IsNumber()) { //TODO: 64-bit integers - return alternate(isolate, value); + return alternate(isolate, value); } else if (value->IsString()) { - return alternate(isolate, value); + return alternate(isolate, value); } else if (value->IsArray()) { - return alternate(isolate, value); + return alternate(isolate, value); } else if (value->IsObject()) { if (is_map_object(isolate, value.As())) { - return alternate(isolate, value); + return alternate(isolate, value); } else { - return alternate(isolate, value); + return alternate(isolate, value); } } else @@ -415,6 +518,12 @@ struct convert> template using is_bool = std::is_same; + template + using is_monostate = std::is_same; + + template + using is_nullopt = std::is_same; + template using is_integral_not_bool = std::bool_constant::value && !is_bool::value>; @@ -450,7 +559,21 @@ struct convert> template static bool try_as(v8::Isolate* isolate, v8::Local value, std::optional& result) { - if constexpr (detail::is_shared_ptr::value) + if constexpr (std::is_same::value) + { + if (v8pp::convert::is_valid(isolate, value)) + { + result = std::monostate{}; + } + } + else if constexpr (std::is_same::value) + { + if (v8pp::convert::is_valid(isolate, value)) + { + result = std::nullopt; + } + } + else if constexpr (detail::is_shared_ptr::value) { using U = typename T::element_type; if (auto obj = v8pp::class_::unwrap_object(isolate, value)) @@ -658,7 +781,8 @@ struct is_wrapped_class : std::conjunction< std::negation>, std::negation>, std::negation>, - std::negation>> + std::negation>, + std::negation>> { }; @@ -880,6 +1004,19 @@ v8::Local to_v8(v8::Isolate* isolate, } #endif +template +v8::Local to_v8(v8::Isolate* isolate, std::optional const& value) +{ + if (value) + { + return convert::to_v8(isolate, *value); + } + else + { + return v8::Undefined(isolate); + } +} + template auto to_v8(v8::Isolate* isolate, T const& value) { diff --git a/v8pp/utility.hpp b/v8pp/utility.hpp index 1fcee57..b2351a9 100644 --- a/v8pp/utility.hpp +++ b/v8pp/utility.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace v8pp::detail { @@ -162,6 +163,20 @@ struct is_shared_ptr> : std::true_type { }; +///////////////////////////////////////////////////////////////////////////// +// +// is_optional +// +template +struct is_optional : std::false_type +{ +}; + +template +struct is_optional> : std::true_type +{ +}; + ///////////////////////////////////////////////////////////////////////////// // // Function traits