Skip to content

Commit

Permalink
Merge pull request #126 from nathanhourt/api-optionals
Browse files Browse the repository at this point in the history
Add API support for optional arguments
  • Loading branch information
nathanielhourt authored May 14, 2019
2 parents 7e612be + 6b7874e commit 6bee7ff
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 257 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ script:
- 'which build-wrapper-linux-x86-64 && build-wrapper-linux-x86-64 --out-dir bw-output make -j 2 || make -j 2'
- set -o pipefail
- tests/run-parallel-tests.sh tests/all_tests
- "tests/api 2>&1 | grep -vE 'callback result 9|remote_calc->add. 4, 5 .: 9|set callback|] \\.$'"
- tests/hmac_test 2>&1 | cat
- tests/ecc_test README.md 2>&1 | cat
- 'find CMakeFiles/fc.dir -type d | while read d; do gcov -o "$d" "${d/CMakeFiles*.dir/./}"/*.cpp; done >/dev/null'
Expand Down
90 changes: 84 additions & 6 deletions include/fc/api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,91 @@
#endif

namespace fc {
struct identity_member {
namespace detail {
/// This metafunction determines whether its template argument is an instantiation of fc::optional
template<typename T> struct is_optional : public std::false_type {};
template<typename T> struct is_optional<fc::optional<T>> : public std::true_type {};
/// This metafunction determines whether all of its template arguments are instantiations of fc::optional
template<typename... Ts> struct all_optionals;
template<> struct all_optionals<> : public std::true_type {};
template<typename T, typename... Ts> struct all_optionals<T, Ts...> : public std::false_type {};
template<typename T, typename... Ts> struct all_optionals<fc::optional<T>, Ts...> : public all_optionals<Ts...> {};

/// A wrapper of std::function allowing callers to omit the last several arguments if those arguments are
/// fc::optional types. i.e. given a function taking (int, double, bool, fc::optional<string>, fc::optional<char>),
/// whereas normally the last two arguments must be provided, this template allows them to be omitted.
/// Note that this only applies to trailing optional arguments, i.e. given a callable taking
/// (fc::optional<int>, int, fc::optional<int>), only the last argument can be omitted.
///
/// A discussion of how exactly this works is available here:
/// https://github.com/bitshares/bitshares-fc/pull/126#issuecomment-490566060
template<typename R, typename... Parameters>
struct optionals_callable : public std::function<R(Parameters...)> {
using std::function<R(Parameters...)>::operator();

template<typename... CutList>
struct short_pack {};
/// This metafunction removes the first several types from a variadic parameter pack of types.
/// The first parameter is the number of arguments to remove from the beginning of the pack.
/// All subsequent parameters are types in the list to be cut
/// The result pack_cutter<...>::type is a short_pack<RemainingTypes>
template<unsigned RemoveCount, typename... Types>
struct pack_cutter;
template<unsigned RemoveCount, typename, typename... Types>
struct pack_cutter_impl;
template<typename... Types>
struct pack_cutter_impl<0, void, Types...> {
static_assert(all_optionals<Types...>::value, "All omitted arguments must correspond to optional parameters.");
using type = short_pack<Types...>;
};
template<unsigned RemoveCount, typename T, typename... Types>
struct pack_cutter_impl<RemoveCount, std::enable_if_t<RemoveCount != 0>, T, Types...>
: public pack_cutter_impl<RemoveCount - 1, void, Types...> {};
template<unsigned RemoveCount, typename... Types>
struct pack_cutter : public pack_cutter_impl<RemoveCount, void, Types...> {};
template<unsigned RemoveCount, typename... Types>
using pack_cutter_t = typename pack_cutter<RemoveCount, Types...>::type;

template<typename F, typename... OptionalTypes>
R call_function(F&& f, short_pack<OptionalTypes...>) {
return f(OptionalTypes()...);
}

/// Overload the function call operator, enabled if the caller provides fewer arguments than there are parameters.
/// Pads out the provided arguments with default-constructed optionals, checking that they are indeed optional types
template<class... Args>
std::enable_if_t<sizeof...(Args) < sizeof...(Parameters), R> operator()(Args... args) {
// Partially apply with the arguments provided
auto partial_function = [this, &args...](auto&&... rest) {
return (*this)(std::forward<decltype(args)>(args)..., std::move(rest)...);
};
// Cut the provided arguments' types out of the Parameters list, and store the rest in a dummy type
pack_cutter_t<sizeof...(Args), std::decay_t<Parameters>...> dummy;
// Pass the partially applied function and the dummy type to another function which can deduce the optional
// types and call the function with default instantiations of those types
return call_function(std::move(partial_function), dummy);
}
};
}

// This is no longer used and probably no longer can be used without generalizing the infrastructure around it, but I
// kept it because it is informative.
// struct identity_member {
// template<typename R, typename C, typename P, typename... Args>
// static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...) );
// template<typename R, typename C, typename P, typename... Args>
// static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...)const );
// };
/// Used as the Transform template parameter for APIs, this type has two main purposes: first, it reads the argument
/// list and return type of a method into template parameters; and second, it uses those types in conjunction with the
/// optionals_callable template above to create a function pointer which supports optional arguments.
struct identity_member_with_optionals {
template<typename R, typename C, typename P, typename... Args>
static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...) );
static detail::optionals_callable<R, Args...> functor( P&& p, R (C::*mem_func)(Args...) );
template<typename R, typename C, typename P, typename... Args>
static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...)const );
static detail::optionals_callable<R, Args...> functor( P&& p, R (C::*mem_func)(Args...)const );
};

template< typename Interface, typename Transform >
struct vtable : public std::enable_shared_from_this<vtable<Interface,Transform>>
{ private: vtable(); };
Expand Down Expand Up @@ -57,13 +135,13 @@ namespace fc {

// defined in api_connection.hpp
template< typename T >
api<T, identity_member> as();
api<T, identity_member_with_optionals> as();
};
typedef std::shared_ptr< api_base > api_ptr;

class api_connection;

template<typename Interface, typename Transform = identity_member >
template<typename Interface, typename Transform = identity_member_with_optionals >
class api : public api_base {
public:
typedef vtable<Interface,Transform> vtable_type;
Expand Down
7 changes: 7 additions & 0 deletions include/fc/network/http/websocket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ namespace fc { namespace http {
void on_connection( const on_connection_handler& handler);
void listen( uint16_t port );
void listen( const fc::ip::endpoint& ep );
uint16_t get_listening_port();
void start_accept();

void stop_listening();
void close();

private:
friend class detail::websocket_server_impl;
std::unique_ptr<detail::websocket_server_impl> my;
Expand Down Expand Up @@ -83,6 +87,9 @@ namespace fc { namespace http {

websocket_connection_ptr connect( const std::string& uri );
websocket_connection_ptr secure_connect( const std::string& uri );

void close();
void synchronous_close();
private:
std::unique_ptr<detail::websocket_client_impl> my;
std::unique_ptr<detail::websocket_tls_client_impl> smy;
Expand Down
20 changes: 14 additions & 6 deletions include/fc/rpc/api_connection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ namespace fc {
template<typename R, typename Arg0, typename ... Args>
std::function<R(Args...)> bind_first_arg( const std::function<R(Arg0,Args...)>& f, Arg0 a0 )
{
return [=]( Args... args ) { return f( a0, args... ); };
// Capture a0 this way because of a {compiler,fc,???} bug that causes optional<bool>() to be incorrectly
// captured as optional<bool>(false).
return [f, a0 = std::decay_t<Arg0>(a0)]( Args... args ) { return f( a0, args... ); };
}
template<typename R>
R call_generic( const std::function<R()>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth = 1 )
Expand All @@ -44,9 +46,11 @@ namespace fc {
template<typename R, typename Arg0, typename ... Args>
R call_generic( const std::function<R(Arg0,Args...)>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth )
{
FC_ASSERT( a0 != e );
bool optional_args = all_optionals<std::decay_t<Arg0>, std::decay_t<Args>...>::value;
FC_ASSERT( a0 != e || optional_args );
FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" );
return call_generic<R,Args...>( bind_first_arg<R,Arg0,Args...>( f, a0->as< typename std::decay<Arg0>::type >( max_depth - 1 ) ), a0+1, e, max_depth - 1 );
auto arg = (a0 == e)? std::decay_t<Arg0>() : a0->as<std::decay_t<Arg0>>(max_depth - 1);
return call_generic<R,Args...>( bind_first_arg<R,Arg0,Args...>( f, arg ), a0+1, e, max_depth - 1 );
}

template<typename R, typename ... Args>
Expand Down Expand Up @@ -137,7 +141,9 @@ namespace fc {
template<typename R, typename Arg0, typename ... Args>
std::function<R(Args...)> bind_first_arg( const std::function<R(Arg0,Args...)>& f, Arg0 a0 )const
{
return [=]( Args... args ) { return f( a0, args... ); };
// Capture a0 this way because of a {compiler,fc,???} bug that causes optional<bool>() to be incorrectly
// captured as optional<bool>(false).
return [f, a0 = std::decay_t<Arg0>(a0)]( Args... args ) { return f( a0, args... ); };
}

template<typename R>
Expand Down Expand Up @@ -172,9 +178,11 @@ namespace fc {
template<typename R, typename Arg0, typename ... Args>
R call_generic( const std::function<R(Arg0,Args...)>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth )
{
FC_ASSERT( a0 != e, "too few arguments passed to method" );
bool optional_args = detail::all_optionals<std::decay_t<Arg0>, std::decay_t<Args>...>::value;
FC_ASSERT( a0 != e || optional_args, "too few arguments passed to method" );
FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" );
return call_generic<R,Args...>( this->bind_first_arg<R,Arg0,Args...>( f, a0->as< typename std::decay<Arg0>::type >( max_depth - 1 ) ), a0+1, e, max_depth - 1 );
auto arg = (a0 == e)? std::decay_t<Arg0>() : a0->as<std::decay_t<Arg0>>(max_depth - 1);
return call_generic<R,Args...>( this->bind_first_arg<R,Arg0,Args...>( f, arg ), a0+1, e, max_depth - 1 );
}

struct api_visitor
Expand Down
41 changes: 36 additions & 5 deletions src/network/http/websocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,6 @@ namespace fc { namespace http {
auto current_con = _connections.find(hdl);
assert( current_con != _connections.end() );
wdump(("server")(msg->get_payload()));
//std::cerr<<"recv: "<<msg->get_payload()<<"\n";
auto payload = msg->get_payload();
std::shared_ptr<websocket_connection> con = current_con->second;
++_pending_messages;
Expand Down Expand Up @@ -290,7 +289,7 @@ namespace fc { namespace http {
if( _server.is_listening() )
_server.stop_listening();

if( _connections.size() )
if ( _connections.size() )
_closed = new fc::promise<void>();

auto cpy_con = _connections;
Expand Down Expand Up @@ -431,6 +430,7 @@ namespace fc { namespace http {

typedef websocket_client_type::connection_ptr websocket_client_connection_type;
typedef websocket_tls_client_type::connection_ptr websocket_tls_client_connection_type;
using websocketpp::connection_hdl;

class websocket_client_impl
{
Expand Down Expand Up @@ -484,6 +484,7 @@ namespace fc { namespace http {
websocket_client_type _client;
websocket_connection_ptr _connection;
std::string _uri;
fc::optional<connection_hdl> _hdl;
};


Expand Down Expand Up @@ -625,13 +626,29 @@ namespace fc { namespace http {
}
void websocket_server::listen( const fc::ip::endpoint& ep )
{
my->_server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) );
my->_server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) );
}

uint16_t websocket_server::get_listening_port()
{
websocketpp::lib::asio::error_code ec;
return my->_server.get_local_endpoint(ec).port();
}

void websocket_server::start_accept() {
my->_server.start_accept();
my->_server.start_accept();
}

void websocket_server::stop_listening()
{
my->_server.stop_listening();
}

void websocket_server::close()
{
for (auto& connection : my->_connections)
my->_server.close(connection.first, websocketpp::close::status::normal, "Goodbye");
}



Expand Down Expand Up @@ -678,6 +695,7 @@ namespace fc { namespace http {
my->_connected = fc::promise<void>::ptr( new fc::promise<void>("websocket::connect") );

my->_client.set_open_handler( [=]( websocketpp::connection_hdl hdl ){
my->_hdl = hdl;
auto con = my->_client.get_con_from_hdl(hdl);
my->_connection = std::make_shared<detail::websocket_connection_impl<detail::websocket_client_connection_type>>( con );
my->_closed = fc::promise<void>::ptr( new fc::promise<void>("websocket::closed") );
Expand Down Expand Up @@ -717,7 +735,20 @@ namespace fc { namespace http {
smy->_client.connect(con);
smy->_connected->wait();
return smy->_connection;
} FC_CAPTURE_AND_RETHROW( (uri) ) }
} FC_CAPTURE_AND_RETHROW( (uri) ) }

void websocket_client::close()
{
if (my->_hdl)
my->_client.close(*my->_hdl, websocketpp::close::status::normal, "Goodbye");
}

void websocket_client::synchronous_close()
{
close();
if (my->_closed)
my->_closed->wait();
}

websocket_connection_ptr websocket_tls_client::connect( const std::string& uri )
{ try {
Expand Down
5 changes: 1 addition & 4 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@

add_executable( api api.cpp )
target_link_libraries( api fc )

if( ECC_IMPL STREQUAL secp256k1 )
add_executable( blind all_tests.cpp crypto/blind.cpp )
target_link_libraries( blind fc )
Expand Down Expand Up @@ -55,5 +51,6 @@ add_executable( all_tests all_tests.cpp
utf8_test.cpp
variant_test.cpp
logging_tests.cpp
api_tests.cpp
)
target_link_libraries( all_tests fc )
Loading

0 comments on commit 6bee7ff

Please sign in to comment.