Merge pull request #126 from nathanhourt/api-optionals
Add API support for optional arguments
This commit is contained in:
commit
6bee7ff30b
9 changed files with 289 additions and 257 deletions
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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(); };
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -484,6 +484,7 @@ namespace fc { namespace http {
|
|||
websocket_client_type _client;
|
||||
websocket_connection_ptr _connection;
|
||||
std::string _uri;
|
||||
fc::optional<connection_hdl> _hdl;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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") );
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
@ -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 )
|
||||
|
|
|
|||
191
tests/api.cpp
191
tests/api.cpp
|
|
@ -1,191 +0,0 @@
|
|||
#include <fc/api.hpp>
|
||||
#include <fc/log/logger.hpp>
|
||||
#include <fc/rpc/api_connection.hpp>
|
||||
#include <fc/rpc/websocket_api.hpp>
|
||||
|
||||
class calculator
|
||||
{
|
||||
public:
|
||||
int32_t add( int32_t a, int32_t b ); // not implemented
|
||||
int32_t sub( int32_t a, int32_t b ); // not implemented
|
||||
void on_result( const std::function<void(int32_t)>& cb );
|
||||
void on_result2( const std::function<void(int32_t)>& cb, int test );
|
||||
};
|
||||
|
||||
FC_API( calculator, (add)(sub)(on_result)(on_result2) )
|
||||
|
||||
|
||||
class login_api
|
||||
{
|
||||
public:
|
||||
fc::api<calculator> get_calc()const
|
||||
{
|
||||
FC_ASSERT( calc );
|
||||
return *calc;
|
||||
}
|
||||
fc::optional<fc::api<calculator>> calc;
|
||||
std::set<std::string> test( const std::string&, const std::string& ) { return std::set<std::string>(); }
|
||||
};
|
||||
FC_API( login_api, (get_calc)(test) );
|
||||
|
||||
using namespace fc;
|
||||
|
||||
class some_calculator
|
||||
{
|
||||
public:
|
||||
int32_t add( int32_t a, int32_t b ) { wlog("."); if( _cb ) _cb(a+b); return a+b; }
|
||||
int32_t sub( int32_t a, int32_t b ) { wlog(".");if( _cb ) _cb(a-b); return a-b; }
|
||||
void on_result( const std::function<void(int32_t)>& cb ) { wlog( "set callback" ); _cb = cb; return ; }
|
||||
void on_result2( const std::function<void(int32_t)>& cb, int test ){}
|
||||
std::function<void(int32_t)> _cb;
|
||||
};
|
||||
class variant_calculator
|
||||
{
|
||||
public:
|
||||
double add( fc::variant a, fc::variant b ) { return a.as_double()+b.as_double(); }
|
||||
double sub( fc::variant a, fc::variant b ) { return a.as_double()-b.as_double(); }
|
||||
void on_result( const std::function<void(int32_t)>& cb ) { wlog("set callback"); _cb = cb; return ; }
|
||||
void on_result2( const std::function<void(int32_t)>& cb, int test ){}
|
||||
std::function<void(int32_t)> _cb;
|
||||
};
|
||||
|
||||
using namespace fc::http;
|
||||
using namespace fc::rpc;
|
||||
|
||||
#define MAX_DEPTH 10
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
try {
|
||||
fc::api<calculator> calc_api( std::make_shared<some_calculator>() );
|
||||
|
||||
fc::http::websocket_server server;
|
||||
server.on_connection([&]( const websocket_connection_ptr& c ){
|
||||
auto wsc = std::make_shared<websocket_api_connection>(c, MAX_DEPTH);
|
||||
auto login = std::make_shared<login_api>();
|
||||
login->calc = calc_api;
|
||||
wsc->register_api(fc::api<login_api>(login));
|
||||
c->set_session_data( wsc );
|
||||
});
|
||||
|
||||
server.listen( 8090 );
|
||||
server.start_accept();
|
||||
|
||||
for( uint32_t i = 0; i < 5000; ++i )
|
||||
{
|
||||
try {
|
||||
fc::http::websocket_client client;
|
||||
auto con = client.connect( "ws://localhost:8090" );
|
||||
auto apic = std::make_shared<websocket_api_connection>(con, MAX_DEPTH);
|
||||
auto remote_login_api = apic->get_remote_api<login_api>();
|
||||
auto remote_calc = remote_login_api->get_calc();
|
||||
remote_calc->on_result( []( uint32_t r ) { elog( "callback result ${r}", ("r",r) ); } );
|
||||
wdump((remote_calc->add( 4, 5 )));
|
||||
} catch ( const fc::exception& e )
|
||||
{
|
||||
edump((e.to_detail_string()));
|
||||
}
|
||||
}
|
||||
wlog( "exit scope" );
|
||||
}
|
||||
catch( const fc::exception& e )
|
||||
{
|
||||
edump((e.to_detail_string()));
|
||||
}
|
||||
wlog( "returning now..." );
|
||||
|
||||
return 0;
|
||||
|
||||
some_calculator calc;
|
||||
variant_calculator vcalc;
|
||||
|
||||
fc::api<calculator> api_calc( &calc );
|
||||
fc::api<calculator> api_vcalc( &vcalc );
|
||||
fc::api<calculator> api_nested_calc( api_calc );
|
||||
|
||||
wdump( (api_calc->add(5,4)) );
|
||||
wdump( (api_calc->sub(5,4)) );
|
||||
wdump( (api_vcalc->add(5,4)) );
|
||||
wdump( (api_vcalc->sub(5,4)) );
|
||||
wdump( (api_nested_calc->sub(5,4)) );
|
||||
wdump( (api_nested_calc->sub(5,4)) );
|
||||
|
||||
/*
|
||||
variants v = { 4, 5 };
|
||||
auto g = to_generic( api_calc->add );
|
||||
auto r = call_generic( api_calc->add, v.begin(), v.end() );
|
||||
wdump((r));
|
||||
wdump( (g(v)) );
|
||||
*/
|
||||
|
||||
/*
|
||||
try {
|
||||
fc::api_server server;
|
||||
auto api_id = server.register_api( api_calc );
|
||||
wdump( (api_id) );
|
||||
auto result = server.call( api_id, "add", {4, 5} );
|
||||
wdump( (result) );
|
||||
} catch ( const fc::exception& e )
|
||||
{
|
||||
elog( "${e}", ("e",e.to_detail_string() ) );
|
||||
}
|
||||
|
||||
ilog( "------------------ NESTED TEST --------------" );
|
||||
try {
|
||||
login_api napi_impl;
|
||||
napi_impl.calc = api_calc;
|
||||
fc::api<login_api> napi(&napi_impl);
|
||||
|
||||
fc::api_server server;
|
||||
auto api_id = server.register_api( napi );
|
||||
wdump( (api_id) );
|
||||
auto result = server.call( api_id, "get_calc" );
|
||||
wdump( (result) );
|
||||
result = server.call( result.as_uint64(), "add", {4,5} );
|
||||
wdump( (result) );
|
||||
|
||||
|
||||
fc::api<api_server> serv( &server );
|
||||
|
||||
fc::api_client<login_api> apic( serv );
|
||||
|
||||
fc::api<login_api> remote_api = apic;
|
||||
|
||||
|
||||
auto remote_calc = remote_api->get_calc();
|
||||
int r = remote_calc->add( 4, 5 );
|
||||
idump( (r) );
|
||||
|
||||
} catch ( const fc::exception& e )
|
||||
{
|
||||
elog( "${e}", ("e",e.to_detail_string() ) );
|
||||
}
|
||||
*/
|
||||
|
||||
ilog( "------------------ NESTED TEST --------------" );
|
||||
try {
|
||||
login_api napi_impl;
|
||||
napi_impl.calc = api_calc;
|
||||
fc::api<login_api> napi(&napi_impl);
|
||||
|
||||
|
||||
auto client_side = std::make_shared<local_api_connection>(MAX_DEPTH);
|
||||
auto server_side = std::make_shared<local_api_connection>(MAX_DEPTH);
|
||||
server_side->set_remote_connection( client_side );
|
||||
client_side->set_remote_connection( server_side );
|
||||
|
||||
server_side->register_api( napi );
|
||||
|
||||
fc::api<login_api> remote_api = client_side->get_remote_api<login_api>();
|
||||
|
||||
auto remote_calc = remote_api->get_calc();
|
||||
int r = remote_calc->add( 4, 5 );
|
||||
idump( (r) );
|
||||
|
||||
} catch ( const fc::exception& e )
|
||||
{
|
||||
elog( "${e}", ("e",e.to_detail_string() ) );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
138
tests/api_tests.cpp
Normal file
138
tests/api_tests.cpp
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <fc/api.hpp>
|
||||
#include <fc/log/logger.hpp>
|
||||
#include <fc/rpc/api_connection.hpp>
|
||||
#include <fc/rpc/websocket_api.hpp>
|
||||
|
||||
class calculator
|
||||
{
|
||||
public:
|
||||
int32_t add( int32_t a, int32_t b ); // not implemented
|
||||
int32_t sub( int32_t a, int32_t b ); // not implemented
|
||||
void on_result( const std::function<void(int32_t)>& cb );
|
||||
void on_result2( const std::function<void(int32_t)>& cb, int test );
|
||||
};
|
||||
|
||||
FC_API( calculator, (add)(sub)(on_result)(on_result2) )
|
||||
|
||||
|
||||
class login_api
|
||||
{
|
||||
public:
|
||||
fc::api<calculator> get_calc()const
|
||||
{
|
||||
FC_ASSERT( calc );
|
||||
return *calc;
|
||||
}
|
||||
fc::optional<fc::api<calculator>> calc;
|
||||
std::set<std::string> test( const std::string&, const std::string& ) { return std::set<std::string>(); }
|
||||
};
|
||||
FC_API( login_api, (get_calc)(test) );
|
||||
|
||||
|
||||
class optionals_api
|
||||
{
|
||||
public:
|
||||
std::string foo( const std::string& first, const fc::optional<std::string>& second,
|
||||
const fc::optional<std::string>& third ) {
|
||||
return fc::json::to_string(fc::variants{first, {second, 2}, {third, 2}});
|
||||
}
|
||||
};
|
||||
FC_API( optionals_api, (foo) );
|
||||
|
||||
using namespace fc;
|
||||
|
||||
class some_calculator
|
||||
{
|
||||
public:
|
||||
int32_t add( int32_t a, int32_t b ) { wlog("."); if( _cb ) _cb(a+b); return a+b; }
|
||||
int32_t sub( int32_t a, int32_t b ) { wlog(".");if( _cb ) _cb(a-b); return a-b; }
|
||||
void on_result( const std::function<void(int32_t)>& cb ) { wlog( "set callback" ); _cb = cb; return ; }
|
||||
void on_result2( const std::function<void(int32_t)>& cb, int test ){}
|
||||
std::function<void(int32_t)> _cb;
|
||||
};
|
||||
|
||||
using namespace fc::http;
|
||||
using namespace fc::rpc;
|
||||
|
||||
#define MAX_DEPTH 10
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(api_tests)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(login_test) {
|
||||
try {
|
||||
fc::api<calculator> calc_api( std::make_shared<some_calculator>() );
|
||||
|
||||
auto server = std::make_shared<fc::http::websocket_server>();
|
||||
server->on_connection([&]( const websocket_connection_ptr& c ){
|
||||
auto wsc = std::make_shared<websocket_api_connection>(c, MAX_DEPTH);
|
||||
auto login = std::make_shared<login_api>();
|
||||
login->calc = calc_api;
|
||||
wsc->register_api(fc::api<login_api>(login));
|
||||
c->set_session_data( wsc );
|
||||
});
|
||||
|
||||
server->listen( 0 );
|
||||
auto listen_port = server->get_listening_port();
|
||||
server->start_accept();
|
||||
|
||||
auto client = std::make_shared<fc::http::websocket_client>();
|
||||
auto con = client->connect( "ws://localhost:" + std::to_string(listen_port) );
|
||||
server->stop_listening();
|
||||
auto apic = std::make_shared<websocket_api_connection>(con, MAX_DEPTH);
|
||||
auto remote_login_api = apic->get_remote_api<login_api>();
|
||||
auto remote_calc = remote_login_api->get_calc();
|
||||
bool remote_triggered = false;
|
||||
remote_calc->on_result( [&remote_triggered]( uint32_t r ) { remote_triggered = true; } );
|
||||
BOOST_CHECK_EQUAL(remote_calc->add( 4, 5 ), 9);
|
||||
BOOST_CHECK(remote_triggered);
|
||||
|
||||
client->synchronous_close();
|
||||
server->close();
|
||||
fc::usleep(fc::milliseconds(50));
|
||||
client.reset();
|
||||
server.reset();
|
||||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(optionals_test) {
|
||||
try {
|
||||
auto optionals = std::make_shared<optionals_api>();
|
||||
fc::api<optionals_api> oapi(optionals);
|
||||
BOOST_CHECK_EQUAL(oapi->foo("a"), "[\"a\",null,null]");
|
||||
BOOST_CHECK_EQUAL(oapi->foo("a", "b"), "[\"a\",\"b\",null]");
|
||||
BOOST_CHECK_EQUAL(oapi->foo("a", "b", "c"), "[\"a\",\"b\",\"c\"]");
|
||||
BOOST_CHECK_EQUAL(oapi->foo("a", {}, "c"), "[\"a\",null,\"c\"]");
|
||||
|
||||
auto server = std::make_shared<fc::http::websocket_server>();
|
||||
server->on_connection([&]( const websocket_connection_ptr& c ){
|
||||
auto wsc = std::make_shared<websocket_api_connection>(c, MAX_DEPTH);
|
||||
wsc->register_api(fc::api<optionals_api>(optionals));
|
||||
c->set_session_data( wsc );
|
||||
});
|
||||
|
||||
server->listen( 0 );
|
||||
auto listen_port = server->get_listening_port();
|
||||
server->start_accept();
|
||||
|
||||
auto client = std::make_shared<fc::http::websocket_client>();
|
||||
auto con = client->connect( "ws://localhost:" + std::to_string(listen_port) );
|
||||
server->stop_listening();
|
||||
auto apic = std::make_shared<websocket_api_connection>(con, MAX_DEPTH);
|
||||
auto remote_optionals = apic->get_remote_api<optionals_api>();
|
||||
|
||||
BOOST_CHECK_EQUAL(remote_optionals->foo("a"), "[\"a\",null,null]");
|
||||
BOOST_CHECK_EQUAL(remote_optionals->foo("a", "b"), "[\"a\",\"b\",null]");
|
||||
BOOST_CHECK_EQUAL(remote_optionals->foo("a", "b", "c"), "[\"a\",\"b\",\"c\"]");
|
||||
BOOST_CHECK_EQUAL(remote_optionals->foo("a", {}, "c"), "[\"a\",null,\"c\"]");
|
||||
|
||||
client->synchronous_close();
|
||||
server->close();
|
||||
fc::usleep(fc::milliseconds(50));
|
||||
client.reset();
|
||||
server.reset();
|
||||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
@ -20,21 +20,8 @@ BOOST_AUTO_TEST_CASE(websocket_test)
|
|||
});
|
||||
});
|
||||
|
||||
bool listen_ok = false;
|
||||
for( int i = 0; !listen_ok && i < 5; ++i )
|
||||
{
|
||||
port = std::rand() % 50000 + 10000;
|
||||
try
|
||||
{
|
||||
server.listen( port );
|
||||
listen_ok = true;
|
||||
}
|
||||
catch( std::exception& ignore )
|
||||
{
|
||||
// if the port is busy, listen() will throw a std::exception, do nothing here.
|
||||
}
|
||||
}
|
||||
BOOST_REQUIRE( listen_ok );
|
||||
server.listen( 0 );
|
||||
port = server.get_listening_port();
|
||||
|
||||
server.start_accept();
|
||||
|
||||
|
|
@ -44,49 +31,27 @@ BOOST_AUTO_TEST_CASE(websocket_test)
|
|||
echo = s;
|
||||
});
|
||||
c_conn->send_message( "hello world" );
|
||||
fc::usleep( fc::seconds(1) );
|
||||
fc::usleep( fc::milliseconds(100) );
|
||||
BOOST_CHECK_EQUAL("echo: hello world", echo);
|
||||
c_conn->send_message( "again" );
|
||||
fc::usleep( fc::seconds(1) );
|
||||
fc::usleep( fc::milliseconds(100) );
|
||||
BOOST_CHECK_EQUAL("echo: again", echo);
|
||||
|
||||
s_conn->close(0, "test");
|
||||
fc::usleep( fc::seconds(1) );
|
||||
try {
|
||||
c_conn->send_message( "again" );
|
||||
BOOST_FAIL("expected assertion failure");
|
||||
} catch (const fc::exception& e) {
|
||||
//std::cerr << e.to_string() << "\n";
|
||||
}
|
||||
fc::usleep( fc::milliseconds(100) );
|
||||
BOOST_CHECK_THROW(c_conn->send_message( "again" ), fc::exception);
|
||||
|
||||
c_conn = client.connect( "ws://localhost:" + fc::to_string(port) );
|
||||
c_conn->on_message_handler([&](const std::string& s){
|
||||
echo = s;
|
||||
});
|
||||
c_conn->send_message( "hello world" );
|
||||
fc::usleep( fc::seconds(1) );
|
||||
fc::usleep( fc::milliseconds(100) );
|
||||
BOOST_CHECK_EQUAL("echo: hello world", echo);
|
||||
}
|
||||
|
||||
try {
|
||||
c_conn->send_message( "again" );
|
||||
BOOST_FAIL("expected assertion failure");
|
||||
} catch (const fc::assert_exception& e) {
|
||||
std::cerr << "Expected assertion failure : " << e.to_string() << "\n";
|
||||
} catch (const fc::exception& e) {
|
||||
BOOST_FAIL("Unexpected exception : " + e.to_string());
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_FAIL("Unexpected exception : " + std::string(e.what()));
|
||||
}
|
||||
|
||||
try {
|
||||
c_conn = client.connect( "ws://localhost:" + fc::to_string(port) );
|
||||
BOOST_FAIL("expected fc::exception (fail to connect)");
|
||||
} catch (const fc::exception& e) {
|
||||
std::cerr << "Excepted fc::exception : " << e.to_string() << "\n";
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_FAIL("Unexpected exception : " + std::string(e.what()));
|
||||
}
|
||||
BOOST_CHECK_THROW(c_conn->send_message( "again" ), fc::assert_exception);
|
||||
BOOST_CHECK_THROW(client.connect( "ws://localhost:" + fc::to_string(port) ), fc::exception);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
|||
Loading…
Reference in a new issue