diff --git a/include/signalrclient/connection.h b/include/signalrclient/connection.h index aa77bc2..b798358 100644 --- a/include/signalrclient/connection.h +++ b/include/signalrclient/connection.h @@ -11,6 +11,7 @@ #include "trace_level.h" #include "log_writer.h" #include "signalr_client_config.h" +#include "transfer_format.h" namespace signalr { @@ -31,7 +32,7 @@ namespace signalr SIGNALRCLIENT_API void __cdecl start(std::function callback) noexcept; - SIGNALRCLIENT_API void __cdecl send(const std::string& data, std::function callback) noexcept; + SIGNALRCLIENT_API void __cdecl send(const std::string& data, transfer_format transfer_format, std::function callback) noexcept; SIGNALRCLIENT_API void __cdecl set_message_received(const message_received_handler& message_received_callback); SIGNALRCLIENT_API void __cdecl set_disconnected(const std::function& disconnected_callback); diff --git a/include/signalrclient/signalr_value.h b/include/signalrclient/signalr_value.h index d017e96..0ebdbb9 100644 --- a/include/signalrclient/signalr_value.h +++ b/include/signalrclient/signalr_value.h @@ -7,6 +7,7 @@ #include #include #include +#include namespace signalr { @@ -34,6 +35,11 @@ namespace signalr */ value(); + /** + * Create an object representing a value_type::null value. + */ + value(std::nullptr_t); + /** * Create an object representing a default value for the given value_type. */ @@ -64,6 +70,11 @@ namespace signalr */ value(const char* val); + /** + * Create an object representing a value_type::string with the given string value. + */ + value(const char* val, size_t length); + /** * Create an object representing a value_type::array with the given vector of value's. */ diff --git a/include/signalrclient/websocket_client.h b/include/signalrclient/websocket_client.h index a360e0f..aff83aa 100644 --- a/include/signalrclient/websocket_client.h +++ b/include/signalrclient/websocket_client.h @@ -14,11 +14,11 @@ namespace signalr public: virtual ~websocket_client() {}; - virtual void start(const std::string& url, transfer_format format, std::function callback) = 0; + virtual void start(const std::string& url, std::function callback) = 0; virtual void stop(std::function callback) = 0; - virtual void send(const std::string& payload, std::function callback) = 0; + virtual void send(const std::string& payload, transfer_format transfer_format, std::function callback) = 0; virtual void receive(std::function callback) = 0; }; diff --git a/src/signalrclient/callback_manager.cpp b/src/signalrclient/callback_manager.cpp index 4c084e3..1dc2876 100644 --- a/src/signalrclient/callback_manager.cpp +++ b/src/signalrclient/callback_manager.cpp @@ -10,17 +10,17 @@ namespace signalr { // dtor_clear_arguments will be passed when closing any pending callbacks when the `callback_manager` is // destroyed (i.e. in the dtor) - callback_manager::callback_manager(const signalr::value& dtor_clear_arguments) + callback_manager::callback_manager(const char* dtor_clear_arguments) : m_dtor_clear_arguments(dtor_clear_arguments) { } callback_manager::~callback_manager() { - clear(m_dtor_clear_arguments); + clear(m_dtor_clear_arguments.data()); } // note: callback must not throw except for the `on_progress` callback which will never be invoked from the dtor - std::string callback_manager::register_callback(const std::function& callback) + std::string callback_manager::register_callback(const std::function& callback) { auto callback_id = get_callback_id(); @@ -35,9 +35,9 @@ namespace signalr // invokes a callback and stops tracking it if remove callback set to true - bool callback_manager::invoke_callback(const std::string& callback_id, const signalr::value& arguments, bool remove_callback) + bool callback_manager::invoke_callback(const std::string& callback_id, const char* error, const signalr::value& arguments, bool remove_callback) { - std::function callback; + std::function callback; { std::lock_guard lock(m_map_lock); @@ -56,7 +56,7 @@ namespace signalr } } - callback(arguments); + callback(error, arguments); return true; } @@ -69,14 +69,14 @@ namespace signalr } } - void callback_manager::clear(const signalr::value& arguments) + void callback_manager::clear(const char* error) { { std::lock_guard lock(m_map_lock); for (auto& kvp : m_callbacks) { - kvp.second(arguments); + kvp.second(error, signalr::value()); } m_callbacks.clear(); diff --git a/src/signalrclient/callback_manager.h b/src/signalrclient/callback_manager.h index d6b5380..3879cfb 100644 --- a/src/signalrclient/callback_manager.h +++ b/src/signalrclient/callback_manager.h @@ -15,22 +15,22 @@ namespace signalr class callback_manager { public: - explicit callback_manager(const signalr::value& dtor_error); + explicit callback_manager(const char* dtor_error); ~callback_manager(); callback_manager(const callback_manager&) = delete; callback_manager& operator=(const callback_manager&) = delete; - std::string register_callback(const std::function& callback); - bool invoke_callback(const std::string& callback_id, const signalr::value& arguments, bool remove_callback); + std::string register_callback(const std::function& callback); + bool invoke_callback(const std::string& callback_id, const char* error, const signalr::value& arguments, bool remove_callback); bool remove_callback(const std::string& callback_id); - void clear(const signalr::value& arguments); + void clear(const char* error); private: std::atomic m_id { 0 }; - std::unordered_map> m_callbacks; + std::unordered_map> m_callbacks; std::mutex m_map_lock; - const signalr::value m_dtor_clear_arguments; + std::string m_dtor_clear_arguments; std::string get_callback_id(); }; diff --git a/src/signalrclient/connection.cpp b/src/signalrclient/connection.cpp index 605305e..5a35e76 100644 --- a/src/signalrclient/connection.cpp +++ b/src/signalrclient/connection.cpp @@ -22,9 +22,9 @@ namespace signalr m_pImpl->start(callback); } - void connection::send(const std::string& data, std::function callback) noexcept + void connection::send(const std::string& data, transfer_format transfer_format, std::function callback) noexcept { - m_pImpl->send(data, callback); + m_pImpl->send(data, transfer_format, callback); } void connection::set_message_received(const message_received_handler& message_received_callback) diff --git a/src/signalrclient/connection_impl.cpp b/src/signalrclient/connection_impl.cpp index 73572cf..aac7da8 100644 --- a/src/signalrclient/connection_impl.cpp +++ b/src/signalrclient/connection_impl.cpp @@ -455,7 +455,7 @@ namespace signalr auto query_string = "id=" + m_connection_token; auto connect_url = url_builder::build_connect(url, transport->get_transport_type(), query_string); - transport->start(connect_url, transfer_format::text, [callback, logger](std::exception_ptr exception) + transport->start(connect_url, [callback, logger](std::exception_ptr exception) mutable { try { @@ -479,6 +479,7 @@ namespace signalr void connection_impl::process_response(std::string&& response) { + // TODO: log binary data better m_logger.log(trace_level::messages, std::string("processing message: ").append(response)); @@ -504,7 +505,7 @@ namespace signalr } } - void connection_impl::send(const std::string& data, std::function callback) noexcept + void connection_impl::send(const std::string& data, transfer_format transfer_format, std::function callback) noexcept { // To prevent an (unlikely) condition where the transport is nulled out after we checked the connection_state // and before sending data we store the pointer in the local variable. In this case `send()` will throw but @@ -524,7 +525,7 @@ namespace signalr logger.log(trace_level::info, std::string("sending data: ").append(data)); - transport->send(data, [logger, callback](std::exception_ptr exception) + transport->send(data, transfer_format, [logger, callback](std::exception_ptr exception) mutable { try { diff --git a/src/signalrclient/connection_impl.h b/src/signalrclient/connection_impl.h index c9ae235..338a994 100644 --- a/src/signalrclient/connection_impl.h +++ b/src/signalrclient/connection_impl.h @@ -39,7 +39,7 @@ namespace signalr ~connection_impl(); void start(std::function callback) noexcept; - void send(const std::string &data, std::function callback) noexcept; + void send(const std::string &data, transfer_format transfer_format, std::function callback) noexcept; void stop(std::function callback) noexcept; connection_state get_connection_state() const noexcept; diff --git a/src/signalrclient/default_websocket_client.cpp b/src/signalrclient/default_websocket_client.cpp index f7c83dc..a6df921 100644 --- a/src/signalrclient/default_websocket_client.cpp +++ b/src/signalrclient/default_websocket_client.cpp @@ -6,6 +6,7 @@ #ifdef USE_CPPRESTSDK #include "default_websocket_client.h" +#include namespace signalr { @@ -28,7 +29,7 @@ namespace signalr : m_underlying_client(create_client_config(signalr_client_config)) { } - void default_websocket_client::start(const std::string& url, transfer_format, std::function callback) + void default_websocket_client::start(const std::string& url, std::function callback) { m_underlying_client.connect(utility::conversions::to_string_t(url)) .then([callback](pplx::task task) @@ -62,10 +63,18 @@ namespace signalr }); } - void default_websocket_client::send(const std::string& payload, std::function callback) + void default_websocket_client::send(const std::string& payload, signalr::transfer_format transfer_format, std::function callback) { web::websockets::client::websocket_outgoing_message msg; - msg.set_utf8_message(payload); + + if (transfer_format == signalr::transfer_format::binary) + { + throw signalr_exception("binary isn't supported currently"); + } + else + { + msg.set_utf8_message(payload); + } m_underlying_client.send(msg) .then([callback](pplx::task task) { @@ -88,8 +97,15 @@ namespace signalr { try { + + std::string msg; auto response = task.get(); - auto msg = response.extract_string().get(); + if (response.message_type() == web::websockets::client::websocket_message_type::binary_message) + { + throw signalr_exception("binary isn't supported currently"); + } + msg = response.extract_string().get(); + callback(msg, nullptr); } catch (...) diff --git a/src/signalrclient/default_websocket_client.h b/src/signalrclient/default_websocket_client.h index 45e849b..9172832 100644 --- a/src/signalrclient/default_websocket_client.h +++ b/src/signalrclient/default_websocket_client.h @@ -20,9 +20,9 @@ namespace signalr public: explicit default_websocket_client(const signalr_client_config& signalr_client_config = {}) noexcept; - void start(const std::string& url, transfer_format format, std::function callback); + void start(const std::string& url, std::function callback); void stop(std::function callback); - void send(const std::string& payload, std::function callback); + void send(const std::string& payload, transfer_format transfer_format, std::function callback); void receive(std::function callback); private: web::websockets::client::websocket_client m_underlying_client; diff --git a/src/signalrclient/handshake_protocol.cpp b/src/signalrclient/handshake_protocol.cpp index f4b9cb3..fb8542c 100644 --- a/src/signalrclient/handshake_protocol.cpp +++ b/src/signalrclient/handshake_protocol.cpp @@ -11,7 +11,7 @@ namespace signalr { namespace handshake { - std::string write_handshake(std::shared_ptr& protocol) + std::string write_handshake(const std::unique_ptr& protocol) { auto map = std::map { diff --git a/src/signalrclient/handshake_protocol.h b/src/signalrclient/handshake_protocol.h index 3a58174..4a0cb33 100644 --- a/src/signalrclient/handshake_protocol.h +++ b/src/signalrclient/handshake_protocol.h @@ -12,7 +12,7 @@ namespace signalr { namespace handshake { - std::string write_handshake(std::shared_ptr&); + std::string write_handshake(const std::unique_ptr&); std::tuple parse_handshake(const std::string&); } } \ No newline at end of file diff --git a/src/signalrclient/hub_connection_impl.cpp b/src/signalrclient/hub_connection_impl.cpp index 9317f03..4add7f7 100644 --- a/src/signalrclient/hub_connection_impl.cpp +++ b/src/signalrclient/hub_connection_impl.cpp @@ -17,16 +17,17 @@ namespace signalr // unnamed namespace makes it invisble outside this translation unit namespace { - static std::function create_hub_invocation_callback(const logger& logger, + static std::function create_hub_invocation_callback(const logger& logger, const std::function& set_result, const std::function& set_exception); } - std::shared_ptr hub_connection_impl::create(const std::string& url, trace_level trace_level, - const std::shared_ptr& log_writer, std::shared_ptr http_client, + std::shared_ptr hub_connection_impl::create(const std::string& url, + trace_level trace_level, const std::shared_ptr& log_writer, std::shared_ptr http_client, std::function(const signalr_client_config&)> websocket_factory, const bool skip_negotiation) { - auto connection = std::shared_ptr(new hub_connection_impl(url, trace_level, log_writer, http_client, websocket_factory, skip_negotiation)); + auto connection = std::shared_ptr(new hub_connection_impl(url, + trace_level, log_writer, http_client, websocket_factory, skip_negotiation)); connection->initialize(); @@ -38,8 +39,8 @@ namespace signalr std::function(const signalr_client_config&)> websocket_factory, const bool skip_negotiation) : m_connection(connection_impl::create(url, trace_level, log_writer, http_client, websocket_factory, skip_negotiation)), m_logger(log_writer, trace_level), - m_callback_manager(signalr::value(std::map { { std::string("error"), std::string("connection went out of scope before invocation result was received") } })), - m_handshakeReceived(false), m_disconnected([]() noexcept {}), m_protocol(std::make_shared()) + m_callback_manager("connection went out of scope before invocation result was received"), + m_handshakeReceived(false), m_disconnected([]() noexcept {}), m_protocol(std::unique_ptr(new json_hub_protocol())) {} void hub_connection_impl::initialize() @@ -64,7 +65,7 @@ namespace signalr // start may be waiting on the handshake response so we complete it here, this no-ops if already set connection->m_handshakeTask->set(std::make_exception_ptr(signalr_exception("connection closed while handshake was in progress."))); - connection->m_callback_manager.clear(signalr::value(std::map { { std::string("error"), std::string("connection was stopped before invocation result was received") } })); + connection->m_callback_manager.clear("connection was stopped before invocation result was received"); connection->m_disconnected(); } @@ -139,7 +140,7 @@ namespace signalr auto handshake_request = handshake::write_handshake(connection->m_protocol); - connection->m_connection->send(handshake_request, [weak_connection, callback](std::exception_ptr exception) + connection->m_connection->send(handshake_request, connection->m_protocol->transfer_format(), [weak_connection, callback](std::exception_ptr exception) { auto connection = weak_connection.lock(); if (!connection) @@ -262,25 +263,15 @@ namespace signalr for (const auto& val : messages) { - if (!val.is_map()) - { - m_logger.log(trace_level::info, std::string("unexpected response received from the server: ") - .append(response)); - - return; - } - - const auto &obj = val.as_map(); - - switch ((int)obj.at("type").as_double()) + switch (val->message_type) { case message_type::invocation: { - auto method = obj.at("target").as_string(); - auto event = m_subscriptions.find(method); + auto invocation = static_cast(val.get()); + auto event = m_subscriptions.find(invocation->target); if (event != m_subscriptions.end()) { - const auto& args = obj.at("arguments"); + const auto& args = invocation->arguments; event->second(args); } else @@ -297,13 +288,8 @@ namespace signalr break; case message_type::completion: { - auto error = obj.find("error"); - auto result = obj.find("result"); - if (error != obj.end() && result != obj.end()) - { - // TODO: error - } - invoke_callback(obj); + auto completion = static_cast(val.get()); + invoke_callback(completion); break; } case message_type::cancel_invocation: @@ -330,23 +316,19 @@ namespace signalr } } - bool hub_connection_impl::invoke_callback(const signalr::value& message) + bool hub_connection_impl::invoke_callback(completion_message* completion) { - if (!message.is_map()) + const char* error = nullptr; + if (!completion->error.empty()) { - throw signalr_exception("expected object"); + error = completion->error.data(); } - auto& invocationId = message.as_map().at("invocationId"); - if (!invocationId.is_string()) + // TODO: consider transferring ownership of 'result' so that if we run user callbacks on a different thread we don't need to + // worry about object lifetime + if (!m_callback_manager.invoke_callback(completion->invocation_id, error, completion->result, true)) { - throw signalr_exception("invocationId is not a string"); - } - - auto& id = invocationId.as_string(); - if (!m_callback_manager.invoke_callback(id, message, true)) - { - m_logger.log(trace_level::info, std::string("no callback found for id: ").append(id)); + m_logger.log(trace_level::info, std::string("no callback found for id: ").append(completion->invocation_id)); return false; } @@ -385,24 +367,13 @@ namespace signalr void hub_connection_impl::invoke_hub_method(const std::string& method_name, const signalr::value& arguments, const std::string& callback_id, std::function set_completion, std::function set_exception) noexcept { - auto map = std::map - { - { "type", signalr::value((double)message_type::invocation) }, - { "target", signalr::value(method_name) }, - { "arguments", arguments } - }; - - if (!callback_id.empty()) - { - map["invocationId"] = signalr::value(callback_id); - } - - auto message = m_protocol->write_message(signalr::value(std::move(map))); + invocation_message invocation(callback_id, method_name, arguments); + auto message = m_protocol->write_message(&invocation); // weak_ptr prevents a circular dependency leading to memory leak and other problems auto weak_hub_connection = std::weak_ptr(shared_from_this()); - m_connection->send(message, [set_completion, set_exception, weak_hub_connection, callback_id](std::exception_ptr exception) + m_connection->send(message, m_protocol->transfer_format(), [set_completion, set_exception, weak_hub_connection, callback_id](std::exception_ptr exception) { if (exception) { @@ -448,29 +419,21 @@ namespace signalr // unnamed namespace makes it invisble outside this translation unit namespace { - static std::function create_hub_invocation_callback(const logger& logger, + static std::function create_hub_invocation_callback(const logger& logger, const std::function& set_result, const std::function& set_exception) { - return [logger, set_result, set_exception](const signalr::value& message) + return [logger, set_result, set_exception](const char* error, const signalr::value& message) { - assert(message.is_map()); - const auto& map = message.as_map(); - auto found = map.find("result"); - if (found != map.end()) + if (error != nullptr) { - set_result(found->second); - } - else if ((found = map.find("error")) != map.end()) - { - assert(found->second.is_string()); set_exception( std::make_exception_ptr( - hub_exception(found->second.as_string()))); + hub_exception(error))); } else { - set_result(signalr::value()); + set_result(message); } }; } diff --git a/src/signalrclient/hub_connection_impl.h b/src/signalrclient/hub_connection_impl.h index 0428023..3899383 100644 --- a/src/signalrclient/hub_connection_impl.h +++ b/src/signalrclient/hub_connection_impl.h @@ -24,8 +24,8 @@ namespace signalr class hub_connection_impl : public std::enable_shared_from_this { public: - static std::shared_ptr create(const std::string& url, trace_level trace_level, - const std::shared_ptr& log_writer, std::shared_ptr http_client, + static std::shared_ptr create(const std::string& url, + trace_level trace_level, const std::shared_ptr& log_writer, std::shared_ptr http_client, std::function(const signalr_client_config&)> websocket_factory, bool skip_negotiation = false); hub_connection_impl(const hub_connection_impl&) = delete; @@ -59,7 +59,7 @@ namespace signalr std::shared_ptr m_handshakeTask; std::function m_disconnected; signalr_client_config m_signalr_client_config; - std::shared_ptr m_protocol; + std::unique_ptr m_protocol; std::mutex m_stop_callback_lock; std::vector> m_stop_callbacks; @@ -70,6 +70,6 @@ namespace signalr void invoke_hub_method(const std::string& method_name, const signalr::value& arguments, const std::string& callback_id, std::function set_completion, std::function set_exception) noexcept; - bool invoke_callback(const signalr::value& message); + bool invoke_callback(completion_message* completion); }; } diff --git a/src/signalrclient/hub_protocol.h b/src/signalrclient/hub_protocol.h index 430d2fd..c8f811f 100644 --- a/src/signalrclient/hub_protocol.h +++ b/src/signalrclient/hub_protocol.h @@ -5,16 +5,74 @@ #pragma once #include "signalrclient/signalr_value.h" +#include "signalrclient/transfer_format.h" +#include "message_type.h" +#include namespace signalr { + struct hub_message + { + hub_message(signalr::message_type message_type) : message_type(message_type) {} + + virtual ~hub_message() {} + + signalr::message_type message_type; + }; + + struct hub_invocation_message : hub_message + { + hub_invocation_message(const std::string& invocation_id, signalr::message_type message_type) + : hub_message(message_type), invocation_id(invocation_id) + { } + + std::string invocation_id; + }; + + struct invocation_message : hub_invocation_message + { + invocation_message(const std::string& invocation_id, const std::string& target, + const signalr::value& args, const std::vector& stream_ids = std::vector()) + : hub_invocation_message(invocation_id, signalr::message_type::invocation), target(target), arguments(args), stream_ids(stream_ids) + { } + + invocation_message(std::string&& invocation_id, std::string&& target, + signalr::value&& args, std::vector&& stream_ids = std::vector()) + : hub_invocation_message(invocation_id, signalr::message_type::invocation), target(target), arguments(args), stream_ids(stream_ids) + { } + + std::string target; + signalr::value arguments; + std::vector stream_ids; + }; + + struct completion_message : hub_invocation_message + { + completion_message(const std::string& invocation_id, const std::string& error, const signalr::value& result) + : hub_invocation_message(invocation_id, signalr::message_type::completion), error(error), result(result) + { } + + completion_message(std::string&& invocation_id, std::string&& error, signalr::value&& result) + : hub_invocation_message(invocation_id, signalr::message_type::completion), error(error), result(result) + { } + + std::string error; + signalr::value result; + }; + + struct ping_message : hub_message + { + ping_message() : hub_message(signalr::message_type::ping) {} + }; + class hub_protocol { public: - virtual std::string write_message(const signalr::value&) const = 0; - virtual std::vector parse_messages(const std::string&) const = 0; + virtual std::string write_message(const hub_message*) const = 0; + virtual std::vector> parse_messages(const std::string&) const = 0; virtual const std::string& name() const = 0; virtual int version() const = 0; + virtual signalr::transfer_format transfer_format() const = 0; virtual ~hub_protocol() {} }; } \ No newline at end of file diff --git a/src/signalrclient/json_hub_protocol.cpp b/src/signalrclient/json_hub_protocol.cpp index e3ee8de..5df350d 100644 --- a/src/signalrclient/json_hub_protocol.cpp +++ b/src/signalrclient/json_hub_protocol.cpp @@ -10,20 +10,67 @@ namespace signalr { - std::string signalr::json_hub_protocol::write_message(const signalr::value& hub_message) const + std::string signalr::json_hub_protocol::write_message(const hub_message* hub_message) const { - return Json::writeString(getJsonWriter(), createJson(hub_message)) + record_separator; + Json::Value object(Json::ValueType::objectValue); + +#pragma warning (push) +#pragma warning (disable: 4061) + switch (hub_message->message_type) + { + case message_type::invocation: + { + auto invocation = static_cast(hub_message); + object["type"] = (int)invocation->message_type; + if (!invocation->invocation_id.empty()) + { + object["invocationId"] = invocation->invocation_id; + } + object["target"] = invocation->target; + object["arguments"] = createJson(invocation->arguments); + // TODO: streamIds + + break; + } + case message_type::completion: + { + auto completion = static_cast(hub_message); + object["type"] = (int)completion->message_type; + object["invocationId"] = completion->invocation_id; + if (!completion->error.empty()) + { + object["error"] = completion->error; + } + else + { + object["result"] = createJson(completion->result); + } + break; + } + case message_type::ping: + { + auto ping = static_cast(hub_message); + object["type"] = (int)ping->message_type; + break; + } + // TODO: other message types + default: + break; + } +#pragma warning (pop) + + return Json::writeString(getJsonWriter(), object) + record_separator; } - std::vector json_hub_protocol::parse_messages(const std::string& message) const + std::vector> json_hub_protocol::parse_messages(const std::string& message) const { - std::vector vec; + std::vector> vec; size_t offset = 0; auto pos = message.find(record_separator, offset); while (pos != std::string::npos) { auto hub_message = parse_message(message.c_str() + offset, pos - offset); - vec.push_back(std::move(hub_message)); + vec.emplace_back(std::move(hub_message)); offset = pos + 1; pos = message.find(record_separator, offset); @@ -33,7 +80,7 @@ namespace signalr return vec; } - signalr::value json_hub_protocol::parse_message(const char* begin, size_t length) const + std::unique_ptr json_hub_protocol::parse_message(const char* begin, size_t length) const { Json::Value root; auto reader = getJsonReader(); @@ -44,6 +91,7 @@ namespace signalr throw signalr_exception(errors); } + // TODO: manually go through the json object to avoid short-lived allocations auto value = createValue(root); if (!value.is_map()) @@ -59,6 +107,8 @@ namespace signalr throw signalr_exception("Field 'type' not found"); } + std::unique_ptr hub_message; + switch ((int)found->second.as_double()) { case message_type::invocation: @@ -68,12 +118,97 @@ namespace signalr { throw signalr_exception("Field 'target' not found for 'invocation' message"); } + if (!found->second.is_string()) + { + throw signalr_exception("Expected 'target' to be of type 'string'"); + } + + found = obj.find("arguments"); if (found == obj.end()) { throw signalr_exception("Field 'arguments' not found for 'invocation' message"); } + if (!found->second.is_array()) + { + throw signalr_exception("Expected 'arguments' to be of type 'array'"); + } + std::string invocation_id; + found = obj.find("invocationId"); + if (found == obj.end()) + { + invocation_id = ""; + } + else + { + if (!found->second.is_string()) + { + throw signalr_exception("Expected 'invocationId' to be of type 'string'"); + } + invocation_id = found->second.as_string(); + } + + hub_message = std::unique_ptr(new invocation_message(invocation_id, + obj.find("target")->second.as_string(), obj.find("arguments")->second.as_array())); + + break; + } + case message_type::completion: + { + bool has_result = false; + signalr::value result; + found = obj.find("result"); + if (found != obj.end()) + { + has_result = true; + result = found->second; + } + + std::string error; + found = obj.find("error"); + if (found == obj.end()) + { + error = ""; + } + else + { + if (found->second.is_string()) + { + error = found->second.as_string(); + } + else + { + throw signalr_exception("Expected 'error' to be of type 'string'"); + } + } + + found = obj.find("invocationId"); + if (found == obj.end()) + { + throw signalr_exception("Field 'invocationId' not found for 'completion' message"); + } + else + { + if (!found->second.is_string()) + { + throw signalr_exception("Expected 'invocationId' to be of type 'string'"); + } + } + + if (!error.empty() && has_result) + { + throw signalr_exception("The 'error' and 'result' properties are mutually exclusive."); + } + + hub_message = std::unique_ptr(new completion_message(obj.find("invocationId")->second.as_string(), + error, result)); + + break; + } + case message_type::ping: + { + hub_message = std::unique_ptr(new ping_message()); break; } // TODO: other message types @@ -83,6 +218,6 @@ namespace signalr break; } - return value; + return hub_message; } } \ No newline at end of file diff --git a/src/signalrclient/json_hub_protocol.h b/src/signalrclient/json_hub_protocol.h index d06fff0..7f36086 100644 --- a/src/signalrclient/json_hub_protocol.h +++ b/src/signalrclient/json_hub_protocol.h @@ -12,20 +12,27 @@ namespace signalr class json_hub_protocol : public hub_protocol { public: - std::string write_message(const signalr::value&) const; - std::vector parse_messages(const std::string&) const; + std::string write_message(const hub_message*) const; + std::vector> parse_messages(const std::string&) const; + const std::string& name() const { return m_protocol_name; } + int version() const { return 1; } + signalr::transfer_format transfer_format() const + { + return signalr::transfer_format::text; + } + ~json_hub_protocol() {} private: - signalr::value parse_message(const char* begin, size_t length) const; + std::unique_ptr parse_message(const char* begin, size_t length) const; std::string m_protocol_name = "json"; }; diff --git a/src/signalrclient/signalr_value.cpp b/src/signalrclient/signalr_value.cpp index a01e9c3..bf19a4c 100644 --- a/src/signalrclient/signalr_value.cpp +++ b/src/signalrclient/signalr_value.cpp @@ -32,6 +32,8 @@ namespace signalr value::value() : mType(value_type::null) {} + value::value(std::nullptr_t) : mType(value_type::null) {} + value::value(value_type t) : mType(t) { switch (mType) @@ -82,6 +84,11 @@ namespace signalr new (&mStorage.string) std::string(val); } + value::value(const char* val, size_t length) : mType(value_type::string) + { + new (&mStorage.string) std::string(val, length); + } + value::value(const std::vector& val) : mType(value_type::array) { new (&mStorage.array) std::vector(val); diff --git a/src/signalrclient/transport.h b/src/signalrclient/transport.h index 7cf145c..a385e8b 100644 --- a/src/signalrclient/transport.h +++ b/src/signalrclient/transport.h @@ -17,11 +17,11 @@ namespace signalr virtual ~transport(); - virtual void start(const std::string& url, transfer_format format, std::function callback) noexcept = 0; + virtual void start(const std::string& url, std::function callback) noexcept = 0; virtual void stop(std::function callback) noexcept = 0; virtual void on_close(std::function callback) = 0; - virtual void send(const std::string& payload, std::function callback) noexcept = 0; + virtual void send(const std::string& payload, signalr::transfer_format transfer_format, std::function callback) noexcept = 0; virtual void on_receive(std::function callback) = 0; diff --git a/src/signalrclient/websocket_transport.cpp b/src/signalrclient/websocket_transport.cpp index e1d84d2..3050d63 100644 --- a/src/signalrclient/websocket_transport.cpp +++ b/src/signalrclient/websocket_transport.cpp @@ -149,7 +149,7 @@ namespace signalr } } - void websocket_transport::start(const std::string& url, transfer_format format, std::function callback) noexcept + void websocket_transport::start(const std::string& url, std::function callback) noexcept { signalr::uri uri(url); assert(uri.scheme() == "ws" || uri.scheme() == "wss"); @@ -178,7 +178,7 @@ namespace signalr auto transport = shared_from_this(); - websocket_client->start(url, format, [transport, receive_loop_cts, callback](std::exception_ptr exception) + websocket_client->start(url, [transport, receive_loop_cts, callback](std::exception_ptr exception) { try { @@ -261,9 +261,9 @@ namespace signalr m_process_response_callback = callback; } - void websocket_transport::send(const std::string& payload, std::function callback) noexcept + void websocket_transport::send(const std::string& payload, transfer_format transfer_format, std::function callback) noexcept { - safe_get_websocket_client()->send(payload, [callback](std::exception_ptr exception) + safe_get_websocket_client()->send(payload, transfer_format, [callback](std::exception_ptr exception) { if (exception != nullptr) { diff --git a/src/signalrclient/websocket_transport.h b/src/signalrclient/websocket_transport.h index 76f87d8..07975c1 100644 --- a/src/signalrclient/websocket_transport.h +++ b/src/signalrclient/websocket_transport.h @@ -25,11 +25,11 @@ namespace signalr transport_type get_transport_type() const noexcept override; - void start(const std::string& url, transfer_format format, std::function callback) noexcept override; + void start(const std::string& url, std::function callback) noexcept override; void stop(std::function callback) noexcept override; void on_close(std::function callback) override; - void send(const std::string& payload, std::function callback) noexcept override; + void send(const std::string& payload, transfer_format transfer_format, std::function callback) noexcept override; void on_receive(std::function) override; diff --git a/test/signalrclienttests/CMakeLists.txt b/test/signalrclienttests/CMakeLists.txt index f130010..cdca9cf 100644 --- a/test/signalrclienttests/CMakeLists.txt +++ b/test/signalrclienttests/CMakeLists.txt @@ -7,6 +7,7 @@ set (SOURCES handshake_tests.cpp hub_connection_tests.cpp hub_exception_tests.cpp + json_hub_protocol_tests.cpp logger_tests.cpp memory_log_writer.cpp negotiate_tests.cpp diff --git a/test/signalrclienttests/callback_manager_tests.cpp b/test/signalrclienttests/callback_manager_tests.cpp index 2cfef74..cddb05d 100644 --- a/test/signalrclienttests/callback_manager_tests.cpp +++ b/test/signalrclienttests/callback_manager_tests.cpp @@ -9,26 +9,27 @@ using namespace signalr; TEST(callback_manager_register_callback, register_returns_unique_callback_ids) { - callback_manager callback_mgr{ signalr::value() }; - auto callback_id1 = callback_mgr.register_callback([](const signalr::value&){}); - auto callback_id2 = callback_mgr.register_callback([](const signalr::value&){}); + callback_manager callback_mgr{ "" }; + auto callback_id1 = callback_mgr.register_callback([](const char*, const signalr::value&){}); + auto callback_id2 = callback_mgr.register_callback([](const char*, const signalr::value&){}); ASSERT_NE(callback_id1, callback_id2); } TEST(callback_manager_invoke_callback, invoke_callback_invokes_and_removes_callback_if_remove_callback_true) { - callback_manager callback_mgr{ signalr::value() }; + callback_manager callback_mgr{ "" }; int callback_argument; auto callback_id = callback_mgr.register_callback( - [&callback_argument](const signalr::value& argument) + [&callback_argument](const char* error, const signalr::value& argument) { + ASSERT_EQ(nullptr, error); callback_argument = (int)argument.as_double(); }); - auto callback_found = callback_mgr.invoke_callback(callback_id, signalr::value(42.0), true); + auto callback_found = callback_mgr.invoke_callback(callback_id, nullptr, signalr::value(42.0), true); ASSERT_TRUE(callback_found); ASSERT_EQ(42, callback_argument); @@ -37,17 +38,18 @@ TEST(callback_manager_invoke_callback, invoke_callback_invokes_and_removes_callb TEST(callback_manager_invoke_callback, invoke_callback_invokes_and_does_not_remove_callback_if_remove_callback_false) { - callback_manager callback_mgr{ signalr::value() }; + callback_manager callback_mgr{ "" }; int callback_argument; auto callback_id = callback_mgr.register_callback( - [&callback_argument](const signalr::value& argument) - { - callback_argument = (int)argument.as_double(); - }); + [&callback_argument](const char* error, const signalr::value& argument) + { + ASSERT_EQ(nullptr, error); + callback_argument = (int)argument.as_double(); + }); - auto callback_found = callback_mgr.invoke_callback(callback_id, signalr::value(42.0), false); + auto callback_found = callback_mgr.invoke_callback(callback_id, nullptr, signalr::value(42.0), false); ASSERT_TRUE(callback_found); ASSERT_EQ(42, callback_argument); @@ -56,8 +58,8 @@ TEST(callback_manager_invoke_callback, invoke_callback_invokes_and_does_not_remo TEST(callback_manager_invoke_callback, invoke_callback_returns_false_for_invalid_callback_id) { - callback_manager callback_mgr{ signalr::value() }; - auto callback_found = callback_mgr.invoke_callback("42", signalr::value(), true); + callback_manager callback_mgr{ "" }; + auto callback_found = callback_mgr.invoke_callback("42", nullptr, signalr::value(), true); ASSERT_FALSE(callback_found); } @@ -67,10 +69,10 @@ TEST(callback_manager_remove, remove_removes_callback_and_returns_true_for_valid auto callback_called = false; { - callback_manager callback_mgr{ signalr::value() }; + callback_manager callback_mgr{ "" }; auto callback_id = callback_mgr.register_callback( - [&callback_called](const signalr::value&) + [&callback_called](const char*, const signalr::value&) { callback_called = true; }); @@ -83,26 +85,27 @@ TEST(callback_manager_remove, remove_removes_callback_and_returns_true_for_valid TEST(callback_manager_remove, remove_returns_false_for_invalid_callback_id) { - callback_manager callback_mgr{ signalr::value() }; + callback_manager callback_mgr{ "" }; ASSERT_FALSE(callback_mgr.remove_callback("42")); } TEST(callback_manager_clear, clear_invokes_all_callbacks) { - callback_manager callback_mgr{ signalr::value() }; + callback_manager callback_mgr{ "" }; auto invocation_count = 0; for (auto i = 0; i < 10; i++) { callback_mgr.register_callback( - [&invocation_count](const signalr::value& argument) - { - invocation_count++; - ASSERT_EQ(42, argument.as_double()); - }); + [&invocation_count](const char* error, const signalr::value& argument) + { + invocation_count++; + ASSERT_STREQ("clearing callback", error); + ASSERT_TRUE(argument.is_null()); + }); } - callback_mgr.clear(signalr::value(42.0)); + callback_mgr.clear("clearing callback"); ASSERT_EQ(10, invocation_count); } @@ -110,21 +113,20 @@ TEST(callback_manager_clear, clear_invokes_all_callbacks) TEST(callback_manager_dtor, clear_invokes_all_callbacks) { auto invocation_count = 0; - bool parameter_correct = true; { - callback_manager callback_mgr{ signalr::value(42.0) }; + callback_manager callback_mgr{ "error" }; for (auto i = 0; i < 10; i++) { callback_mgr.register_callback( - [&invocation_count, ¶meter_correct](const signalr::value& argument) + [&invocation_count](const char* error, const signalr::value& argument) { invocation_count++; - parameter_correct &= argument.as_double() == 42; + ASSERT_STREQ("error", error); + ASSERT_TRUE(argument.is_null()); }); } } ASSERT_EQ(10, invocation_count); - ASSERT_TRUE(parameter_correct); } diff --git a/test/signalrclienttests/connection_impl_tests.cpp b/test/signalrclienttests/connection_impl_tests.cpp index 133ace9..6f7a439 100644 --- a/test/signalrclienttests/connection_impl_tests.cpp +++ b/test/signalrclienttests/connection_impl_tests.cpp @@ -348,7 +348,7 @@ TEST(connection_impl_send, send_fails_if_transport_fails_when_receiving_messages mre.get(); - connection->send("message", [&mre](std::exception_ptr exception) + connection->send("message", transfer_format::text, [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -1055,7 +1055,7 @@ TEST(connection_impl_send, message_sent) mre.get(); - connection->send(message, [&mre](std::exception_ptr exception) + connection->send(message, transfer_format::text, [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -1071,7 +1071,7 @@ TEST(connection_impl_send, send_throws_if_connection_not_connected) connection_impl::create(create_uri(), trace_level::none, std::make_shared()); auto mre = manual_reset_event(); - connection->send("whatever", [&mre](std::exception_ptr exception) + connection->send("whatever", transfer_format::text, [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -1106,7 +1106,7 @@ TEST(connection_impl_send, exceptions_from_send_logged_and_propagated) mre.get(); - connection->send("Test message", [&mre](std::exception_ptr exception) + connection->send("Test message", transfer_format::text, [&mre](std::exception_ptr exception) { mre.set(exception); }); diff --git a/test/signalrclienttests/handshake_tests.cpp b/test/signalrclienttests/handshake_tests.cpp index 97e4276..6483e35 100644 --- a/test/signalrclienttests/handshake_tests.cpp +++ b/test/signalrclienttests/handshake_tests.cpp @@ -70,7 +70,7 @@ TEST(handshake, extra_fields_are_fine) TEST(handshake, writes_protocol_and_version) { - auto protocol = std::shared_ptr(new json_hub_protocol()); + auto protocol = std::unique_ptr(new json_hub_protocol()); auto message = handshake::write_handshake(protocol); ASSERT_STREQ("{\"protocol\":\"json\",\"version\":1}\x1e", message.c_str()); diff --git a/test/signalrclienttests/json_hub_protocol_tests.cpp b/test/signalrclienttests/json_hub_protocol_tests.cpp new file mode 100644 index 0000000..0d061e5 --- /dev/null +++ b/test/signalrclienttests/json_hub_protocol_tests.cpp @@ -0,0 +1,232 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "stdafx.h" +#include "json_hub_protocol.h" + +using namespace signalr; + +void assert_signalr_value_equality(const signalr::value& expected, const signalr::value& actual) +{ + ASSERT_EQ(expected.type(), actual.type()); + switch (expected.type()) + { + case value_type::string: + ASSERT_STREQ(expected.as_string().data(), actual.as_string().data()); + break; + case value_type::boolean: + ASSERT_EQ(expected.as_bool(), actual.as_bool()); + break; + case value_type::float64: + ASSERT_DOUBLE_EQ(expected.as_double(), actual.as_double()); + break; + case value_type::map: + { + auto& expected_map = expected.as_map(); + auto& actual_map = actual.as_map(); + ASSERT_EQ(expected_map.size(), actual_map.size()); + for (auto& pair : expected_map) + { + const auto& actual_found = actual_map.find(pair.first); + ASSERT_FALSE(actual_found == actual_map.end()); + assert_signalr_value_equality(pair.second, actual_found->second); + } + break; + } + case value_type::array: + { + auto& expected_array = expected.as_array(); + auto& actual_array = actual.as_array(); + ASSERT_EQ(expected_array.size(), actual_array.size()); + for (auto i = 0; i < expected_array.size(); ++i) + { + assert_signalr_value_equality(expected_array[i], actual_array[i]); + } + break; + } + case value_type::null: + break; + default: + ASSERT_TRUE(false); + break; + } +} + +void assert_hub_message_equality(hub_message* expected, hub_message* actual) +{ + ASSERT_EQ(expected->message_type, actual->message_type); + switch (expected->message_type) + { + case message_type::invocation: + { + auto expected_message = reinterpret_cast(expected); + auto actual_message = reinterpret_cast(actual); + + ASSERT_STREQ(expected_message->invocation_id.data(), actual_message->invocation_id.data()); + ASSERT_STREQ(expected_message->target.data(), actual_message->target.data()); + assert_signalr_value_equality(expected_message->arguments, actual_message->arguments); + //stream_ids + break; + } + case message_type::completion: + { + auto expected_message = reinterpret_cast(expected); + auto actual_message = reinterpret_cast(actual); + + ASSERT_STREQ(expected_message->invocation_id.data(), actual_message->invocation_id.data()); + ASSERT_STREQ(expected_message->error.data(), actual_message->error.data()); + assert_signalr_value_equality(expected_message->result, actual_message->result); + + break; + } + case message_type::ping: + { + // No fields on ping messages currently + break; + } + default: + ASSERT_TRUE(false); + break; + } +} + +std::vector>> protocol_test_data +{ + // invocation message without invocation id + { "{\"arguments\":[1,\"Foo\"],\"target\":\"Target\",\"type\":1}\x1e", + std::shared_ptr(new invocation_message("", "Target", std::vector{ value(1.f), value("Foo") })) }, + + // invocation message with multiple arguments + { "{\"arguments\":[1,\"Foo\"],\"invocationId\":\"123\",\"target\":\"Target\",\"type\":1}\x1e", + std::shared_ptr(new invocation_message("123", "Target", std::vector{ value(1.f), value("Foo") })) }, + + // invocation message with bool argument + { "{\"arguments\":[true],\"target\":\"Target\",\"type\":1}\x1e", + std::shared_ptr(new invocation_message("", "Target", std::vector{ value(true) })) }, + + // invocation message with null argument + { "{\"arguments\":[null],\"target\":\"Target\",\"type\":1}\x1e", + std::shared_ptr(new invocation_message("", "Target", std::vector{ value(nullptr) })) }, + + // invocation message with no arguments + { "{\"arguments\":[],\"target\":\"Target\",\"type\":1}\x1e", + std::shared_ptr(new invocation_message("", "Target", std::vector{})) }, + + // invocation message with non-ascii string argument + /*{ "{\"arguments\":[\"\xD7\x9E\xD7\x97\xD7\xA8\xD7\x95\xD7\x96\xD7\xAA\x20\xD7\x9B\xD7\x9C\xD7\xA9\xD7\x94\xD7\x99\"],\"target\":\"Target\",\"type\":1}\x1e", + std::shared_ptr(new invocation_message("", "Target", std::vector{ value("\xD7\x9E\xD7\x97\xD7\xA8\xD7\x95\xD7\x96\xD7\xAA\x20\xD7\x9B\xD7\x9C\xD7\xA9\xD7\x94\xD7\x99") })) },*/ + + // invocation message with object argument + { "{\"arguments\":[{\"property\":5}],\"target\":\"Target\",\"type\":1}\x1e", + std::shared_ptr(new invocation_message("", "Target", std::vector{ value(std::map{ {"property", value(5.f)} }) })) }, + + // invocation message with array argument + { "{\"arguments\":[[1,5]],\"target\":\"Target\",\"type\":1}\x1e", + std::shared_ptr(new invocation_message("", "Target", std::vector{ value(std::vector{value(1.f), value(5.f)}) })) }, + + // ping message + { "{\"type\":6}\x1e", + std::shared_ptr(new ping_message()) }, + + // completion message with error + { "{\"error\":\"error\",\"invocationId\":\"1\",\"type\":3}\x1e", + std::shared_ptr(new completion_message("1", "error", value())) }, + + // completion message with result + { "{\"invocationId\":\"1\",\"result\":42,\"type\":3}\x1e", + std::shared_ptr(new completion_message("1", "", value(42.f))) }, +}; + +TEST(json_hub_protocol, write_message) +{ + for (auto& data : protocol_test_data) + { + auto output = json_hub_protocol().write_message(data.second.get()); + ASSERT_STREQ(data.first.data(), output.data()); + } +} + +TEST(json_hub_protocol, parse_message) +{ + for (auto& data : protocol_test_data) + { + auto output = json_hub_protocol().parse_messages(data.first); + ASSERT_EQ(1, output.size()); + assert_hub_message_equality(data.second.get(), output[0].get()); + } +} + +TEST(json_hub_protocol, parsing_field_order_does_not_matter) +{ + invocation_message message = invocation_message("123", "Target", std::vector{value(true)}); + auto output = json_hub_protocol().parse_messages("{\"type\":1,\"invocationId\":\"123\",\"arguments\":[true],\"target\":\"Target\"}\x1e"); + ASSERT_EQ(1, output.size()); + assert_hub_message_equality(&message, output[0].get()); + + output = json_hub_protocol().parse_messages("{\"target\":\"Target\",\"invocationId\":\"123\",\"type\":1,\"arguments\":[true]}\x1e"); + ASSERT_EQ(1, output.size()); + assert_hub_message_equality(&message, output[0].get()); + + output = json_hub_protocol().parse_messages("{\"target\":\"Target\",\"arguments\":[true],\"type\":1,\"invocationId\":\"123\"}\x1e"); + ASSERT_EQ(1, output.size()); + assert_hub_message_equality(&message, output[0].get()); +} + +TEST(json_hub_protocol, can_parse_multiple_messages) +{ + auto output = json_hub_protocol().parse_messages(std::string("{\"arguments\":[],\"target\":\"Target\",\"type\":1}\x1e") + + "{\"invocationId\":\"1\",\"result\":42,\"type\":3}\x1e"); + ASSERT_EQ(2, output.size()); + + invocation_message invocation = invocation_message("", "Target", std::vector{}); + assert_hub_message_equality(&invocation, output[0].get()); + + completion_message completion = completion_message("1", "", value(42.f)); + assert_hub_message_equality(&completion, output[1].get()); +} + +TEST(json_hub_protocol, extra_items_ignored_when_parsing) +{ + invocation_message message = invocation_message("", "Target", std::vector{value(true)}); + auto output = json_hub_protocol().parse_messages("{\"type\":1,\"arguments\":[true],\"target\":\"Target\",\"extra\":\"ignored\"}\x1e"); + ASSERT_EQ(1, output.size()); + assert_hub_message_equality(&message, output[0].get()); +} + +std::vector> invalid_messages +{ + { "\x1e", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n* Line 1, Column 1\n A valid JSON document must be either an array or an object value.\n" }, + { "foo\x1e", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n* Line 1, Column 2\n Extra non-whitespace after JSON value.\n" }, + { "[42]\x1e", "Message was not a 'map' type" }, + { "{\x1e", "* Line 1, Column 2\n Missing '}' or object member name\n" }, + + { "{\"arguments\":[],\"target\":\"send\",\"invocationId\":42}\x1e", "Field 'type' not found" }, + + { "{\"type\":1}\x1e", "Field 'target' not found for 'invocation' message" }, + { "{\"type\":1,\"target\":\"send\",\"invocationId\":42}\x1e", "Field 'arguments' not found for 'invocation' message" }, + { "{\"type\":1,\"target\":\"send\",\"arguments\":[],\"invocationId\":42}\x1e", "Expected 'invocationId' to be of type 'string'" }, + { "{\"type\":1,\"target\":\"send\",\"arguments\":42,\"invocationId\":\"42\"}\x1e", "Expected 'arguments' to be of type 'array'" }, + { "{\"type\":1,\"target\":true,\"arguments\":[],\"invocationId\":\"42\"}\x1e", "Expected 'target' to be of type 'string'" }, + + { "{\"type\":3}\x1e", "Field 'invocationId' not found for 'completion' message" }, + { "{\"type\":3,\"invocationId\":42}\x1e", "Expected 'invocationId' to be of type 'string'" }, + { "{\"type\":3,\"invocationId\":\"42\",\"error\":[]}\x1e", "Expected 'error' to be of type 'string'" }, + { "{\"type\":3,\"invocationId\":\"42\",\"error\":\"foo\",\"result\":true}\x1e", "The 'error' and 'result' properties are mutually exclusive." }, +}; + +TEST(json_hub_protocol, invalid_messages_throw) +{ + for (auto& pair : invalid_messages) + { + try + { + json_hub_protocol().parse_messages(pair.first); + ASSERT_TRUE(false); + } + catch (const std::exception& exception) + { + ASSERT_STREQ(pair.second.data(), exception.what()); + } + } +} \ No newline at end of file diff --git a/test/signalrclienttests/test_websocket_client.cpp b/test/signalrclienttests/test_websocket_client.cpp index d817b06..5320492 100644 --- a/test/signalrclienttests/test_websocket_client.cpp +++ b/test/signalrclienttests/test_websocket_client.cpp @@ -51,7 +51,7 @@ test_websocket_client::~test_websocket_client() } } -void test_websocket_client::start(const std::string& url, transfer_format, std::function callback) +void test_websocket_client::start(const std::string& url, std::function callback) { std::lock_guard lock(m_receive_lock); m_stopped = false; @@ -99,7 +99,7 @@ void test_websocket_client::stop(std::function callbac }).detach(); } -void test_websocket_client::send(const std::string& payload, std::function callback) +void test_websocket_client::send(const std::string& payload, signalr::transfer_format, std::function callback) { handshake_sent.cancel(); auto local_copy = m_send_function; diff --git a/test/signalrclienttests/test_websocket_client.h b/test/signalrclienttests/test_websocket_client.h index 0709b91..3900a29 100644 --- a/test/signalrclienttests/test_websocket_client.h +++ b/test/signalrclienttests/test_websocket_client.h @@ -18,11 +18,11 @@ public: test_websocket_client(); ~test_websocket_client(); - void start(const std::string& url, transfer_format format, std::function callback); + void start(const std::string& url, std::function callback); void stop(std::function callback); - void send(const std::string& payload, std::function callback); + void send(const std::string& payload, signalr::transfer_format transfer_format, std::function callback); void receive(std::function callback); diff --git a/test/signalrclienttests/websocket_transport_tests.cpp b/test/signalrclienttests/websocket_transport_tests.cpp index 16452a6..b918fc1 100644 --- a/test/signalrclienttests/websocket_transport_tests.cpp +++ b/test/signalrclienttests/websocket_transport_tests.cpp @@ -29,7 +29,7 @@ TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop) auto ws_transport = websocket_transport::create([&](const signalr_client_config&) { return client; }, signalr_client_config{}, logger(writer, trace_level::info)); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org/connect?param=42", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://fakeuri.org/connect?param=42", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -59,7 +59,7 @@ TEST(websocket_transport_connect, connect_propagates_exceptions) try { auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -84,7 +84,7 @@ TEST(websocket_transport_connect, connect_logs_exceptions) auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(writer, trace_level::errors)); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -111,7 +111,7 @@ TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_trans auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -119,7 +119,7 @@ TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_trans try { - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -138,7 +138,7 @@ TEST(websocket_transport_connect, can_connect_after_disconnecting) auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -150,7 +150,7 @@ TEST(websocket_transport_connect, can_connect_after_disconnecting) }); mre.get(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -172,13 +172,13 @@ TEST(websocket_transport_send, send_creates_and_sends_websocket_messages) auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://url", [&mre](std::exception_ptr exception) { mre.set(exception); }); mre.get(); - ws_transport->send("ABC", [&mre](std::exception_ptr exception) + ws_transport->send("ABC", transfer_format::text, [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -202,7 +202,7 @@ TEST(websocket_transport_disconnect, disconnect_closes_websocket) auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://url", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -231,7 +231,7 @@ TEST(websocket_transport_stop, propogates_exception_from_close) auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://url", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -264,7 +264,7 @@ TEST(websocket_transport_disconnect, disconnect_logs_exceptions) auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(writer, trace_level::errors)); auto mre = manual_reset_event(); - ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://url", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -309,7 +309,7 @@ TEST(websocket_transport_disconnect, receive_not_called_after_disconnect) auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared(), trace_level::none)); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) + ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr) { mre.set(); }); @@ -323,7 +323,7 @@ TEST(websocket_transport_disconnect, receive_not_called_after_disconnect) }); mre.get(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -383,7 +383,7 @@ void receive_loop_logs_exception_runner(const T& e, const std::string& expected_ auto ws_transport = websocket_transport::create([&](const signalr_client_config&) { return client; }, signalr_client_config{}, logger(writer, trace_level)); auto mre = manual_reset_event(); - ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://url", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -434,7 +434,7 @@ TEST(websocket_transport_receive_loop, process_response_callback_called_when_mes ws_transport->on_receive(process_response); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception) { mre.set(exception); }); @@ -478,7 +478,7 @@ TEST(websocket_transport_receive_loop, error_callback_called_when_exception_thro ws_transport->on_close(error_callback); auto mre = manual_reset_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) + ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception) { mre.set(exception); });