diff --git a/CHANGELOG.md b/CHANGELOG.md index dd5fa2f2f8f..b381b8ccf4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ ### Breaking changes * The `WebSocketObserver` interface in the sync `SocketProvider` API now takes a `WebSocketError` enum/`std::string_view` for the `websocket_closed_handler()` instead of a `Status`. Implementers of platform networking should make sure all their error handling is expressed in terms of the WebSocketError enum. ([PR #6859](https://github.com/realm/realm-core/pull/6859)) +* `Status` no longer holds a `std::error_code` for `SystemError`'s ([PR #6869](https://github.com/realm/realm-core/pull/6869)) +* C API no longer has a special type for sync error codes. Instead sync errors codes are converted to `realm_error_t` ([PR #6869](https://github.com/realm/realm-core/pull/6869)) +* WebSocket specific error codes are no longer in the ErrorCodes enum or C API. ([PR #6869](https://github.com/realm/realm-core/pull/6869)) +* `ProtocolError` is no longer a `std::error_code` enum and is no longer directly exposed by the sync error API ([PR #6869](https://github.com/realm/realm-core/pull/6869)) +* The ClientError enum/`std::error_code` in the sync client has been removed in favor of a simplified error set using Status/ErrorCodes ([PR #6846](https://github.com/realm/realm-core/pull/6846)). +* SyncError now contains a Status to hold the error information from the sync client instead of a `std::error_code`/`std::string` ([PR #6824](https://github.com/realm/realm-core/pull/6824)). ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. @@ -18,8 +24,6 @@ ### Internals * Timestamp objects can now only be created from a system clock timepoint. ([#6112](https://github.com/realm/realm-core/issues/6112)) -* SyncError now contains a Status to hold the error information from the sync client instead of a `std::error_code`/`std::string` ([PR #6824](https://github.com/realm/realm-core/pull/6824)). -* The ClientError enum/`std::error_code` in the sync client has been removed in favor of a simplified error set using Status/ErrorCodes ([PR #6846](https://github.com/realm/realm-core/pull/6846)). ---------------------------------------------- diff --git a/src/realm.h b/src/realm.h index 3966683bafa..12a18b1647a 100644 --- a/src/realm.h +++ b/src/realm.h @@ -3334,26 +3334,6 @@ typedef enum realm_sync_progress_direction { RLM_SYNC_PROGRESS_DIRECTION_DOWNLOAD, } realm_sync_progress_direction_e; -/** - * Possible error categories realm_sync_error_code_t can fall in. - */ -typedef enum realm_sync_error_category { - RLM_SYNC_ERROR_CATEGORY_CLIENT, - RLM_SYNC_ERROR_CATEGORY_CONNECTION, - RLM_SYNC_ERROR_CATEGORY_SESSION, - RLM_SYNC_ERROR_CATEGORY_WEBSOCKET, - - /** - * System error - POSIX errno, Win32 HRESULT, etc. - */ - RLM_SYNC_ERROR_CATEGORY_SYSTEM, - - /** - * Unknown source of error. - */ - RLM_SYNC_ERROR_CATEGORY_UNKNOWN, -} realm_sync_error_category_e; - typedef enum realm_sync_error_action { RLM_SYNC_ERROR_ACTION_NO_ACTION, RLM_SYNC_ERROR_ACTION_PROTOCOL_VIOLATION, @@ -3370,17 +3350,6 @@ typedef enum realm_sync_error_action { typedef struct realm_sync_session realm_sync_session_t; typedef struct realm_async_open_task realm_async_open_task_t; -// This type should never be returned from a function. -// It's only meant as an asynchronous callback argument. -// Pointers to this struct and its pointer members are only valid inside the scope -// of the callback they were passed to. -typedef struct realm_sync_error_code { - realm_sync_error_category_e category; - int value; - const char* message; - const char* category_name; -} realm_sync_error_code_t; - typedef struct realm_sync_error_user_info { const char* key; const char* value; @@ -3397,8 +3366,7 @@ typedef struct realm_sync_error_compensating_write_info { // Pointers to this struct and its pointer members are only valid inside the scope // of the callback they were passed to. typedef struct realm_sync_error { - realm_sync_error_code_t error_code; - const char* detailed_message; + realm_error_t status; const char* c_original_file_path_key; const char* c_recovery_file_path_key; bool is_fatal; @@ -3422,7 +3390,7 @@ typedef struct realm_sync_error { * * @param error Null, if the operation completed successfully. */ -typedef void (*realm_sync_wait_for_completion_func_t)(realm_userdata_t userdata, realm_sync_error_code_t* error); +typedef void (*realm_sync_wait_for_completion_func_t)(realm_userdata_t userdata, realm_error_t* error); typedef void (*realm_sync_connection_state_changed_func_t)(realm_userdata_t userdata, realm_sync_connection_state_e old_state, realm_sync_connection_state_e new_state); @@ -3864,13 +3832,13 @@ RLM_API void realm_sync_session_wait_for_upload_completion(realm_sync_session_t* /** * Wrapper for SyncSession::OnlyForTesting::handle_error. This routine should be used only for testing. * @param session ptr to a valid sync session - * @param error_code error code to simulate - * @param category category of the error to simulate - * @param error_message string representing the error + * @param error_code realm_errno_e representing the error to simulate + * @param error_str error message to be included with Status * @param is_fatal boolean to signal if the error is fatal or not */ -RLM_API void realm_sync_session_handle_error_for_testing(const realm_sync_session_t* session, int error_code, - int category, const char* error_message, bool is_fatal); +RLM_API void realm_sync_session_handle_error_for_testing(const realm_sync_session_t* session, + realm_errno_e error_code, const char* error_str, + bool is_fatal); /** * In case of exception thrown in user code callbacks, this api will allow the sdk to store the user code exception diff --git a/src/realm/error_codes.cpp b/src/realm/error_codes.cpp index fbdfc2e89b8..0b13d381879 100644 --- a/src/realm/error_codes.cpp +++ b/src/realm/error_codes.cpp @@ -225,11 +225,6 @@ ErrorCategory ErrorCodes::error_categories(Error code) .set(ErrorCategory::app_error) .set(ErrorCategory::service_error); - case WebSocketResolveFailedError: - case WebSocketConnectionClosedClientError: - case WebSocketConnectionClosedServerError: - return ErrorCategory().set(ErrorCategory::runtime_error).set(ErrorCategory::websocket_error); - case UnknownError: break; } @@ -400,9 +395,6 @@ static const MapElem string_to_error_code[] = { {"ValueAlreadyExists", ErrorCodes::ValueAlreadyExists}, {"ValueDuplicateName", ErrorCodes::ValueDuplicateName}, {"ValueNotFound", ErrorCodes::ValueNotFound}, - {"WebSocketConnectionClosedClientError", ErrorCodes::WebSocketConnectionClosedClientError}, - {"WebSocketConnectionClosedServerError", ErrorCodes::WebSocketConnectionClosedServerError}, - {"WebSocketResolveFailedError", ErrorCodes::WebSocketResolveFailedError}, {"WrongSyncType", ErrorCodes::WrongSyncType}, {"WrongThread", ErrorCodes::WrongThread}, {"WrongTransactionState", ErrorCodes::WrongTransactionState}, diff --git a/src/realm/error_codes.h b/src/realm/error_codes.h index 6269fda0a1f..955c74cf411 100644 --- a/src/realm/error_codes.h +++ b/src/realm/error_codes.h @@ -202,10 +202,6 @@ typedef enum realm_errno { RLM_ERR_INVALID_SERVER_RESPONSE = 4354, RLM_ERR_APP_SERVER_ERROR = 4355, - RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR = 4400, - RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR = 4401, - RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR = 4402, - RLM_ERR_CALLBACK = 1000000, /**< A user-provided callback failed. */ RLM_ERR_UNKNOWN = 2000000 /* Should not be used in code */ } realm_errno_e; diff --git a/src/realm/error_codes.hpp b/src/realm/error_codes.hpp index 0733ed3452b..17ad2c58586 100644 --- a/src/realm/error_codes.hpp +++ b/src/realm/error_codes.hpp @@ -246,10 +246,6 @@ class ErrorCodes { InvalidServerResponse = RLM_ERR_INVALID_SERVER_RESPONSE, AppServerError = RLM_ERR_APP_SERVER_ERROR, - WebSocketResolveFailedError = RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR, - WebSocketConnectionClosedClientError = RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR, - WebSocketConnectionClosedServerError = RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR, - CallbackFailed = RLM_ERR_CALLBACK, UnknownError = RLM_ERR_UNKNOWN, }; diff --git a/src/realm/exceptions.hpp b/src/realm/exceptions.hpp index f5dbeffa361..7521c40e77c 100644 --- a/src/realm/exceptions.hpp +++ b/src/realm/exceptions.hpp @@ -367,20 +367,11 @@ struct SystemError : RuntimeError { ~SystemError() noexcept override; - std::error_code get_system_error() const - { - return to_status().get_std_error_code(); - } - - const std::error_category& get_category() const - { - return get_system_error().category(); - } - private: static Status make_status(std::error_code err, std::string_view msg, bool msg_is_prefix) { - return Status(err, msg_is_prefix ? util::format("%1: %2 (%3)", msg, err.message(), err.value()) : msg); + return Status(ErrorCodes::SystemError, + msg_is_prefix ? util::format("%1: %2 (%3)", msg, err.message(), err.value()) : msg); } }; diff --git a/src/realm/impl/simulated_failure.hpp b/src/realm/impl/simulated_failure.hpp index ab65f01d77b..0638a557011 100644 --- a/src/realm/impl/simulated_failure.hpp +++ b/src/realm/impl/simulated_failure.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #ifdef REALM_DEBUG @@ -31,7 +32,7 @@ namespace realm { namespace _impl { -class SimulatedFailure : public std::system_error { +class SimulatedFailure : public RuntimeError { public: enum FailureType { generic, @@ -64,11 +65,6 @@ class SimulatedFailure : public std::system_error { /// not defined, this function always return false. static bool check_trigger(FailureType) noexcept; - /// The specified error code is set to `make_error_code(failure_type)` if - /// check_trigger() returns true. Otherwise it is set to - /// `std::error_code()`. Returns a copy of the updated error code. - static std::error_code trigger(FailureType failure_type, std::error_code&) noexcept; - /// Throws SimulatedFailure if check_trigger() returns true. The exception /// will be constructed with an error code equal to /// `make_error_code(failure_type)`. @@ -90,7 +86,7 @@ class SimulatedFailure : public std::system_error { /// when turning this off. static void set_thread_local(bool); - SimulatedFailure(std::error_code); + SimulatedFailure(FailureType); private: #ifdef REALM_ENABLE_SIMULATED_FAILURE @@ -132,7 +128,8 @@ std::error_code make_error_code(SimulatedFailure::FailureType) noexcept; namespace std { -template<> struct is_error_code_enum { +template <> +struct is_error_code_enum { static const bool value = true; }; @@ -184,21 +181,10 @@ inline bool SimulatedFailure::check_trigger(FailureType failure_type) noexcept #endif } -inline std::error_code SimulatedFailure::trigger(FailureType failure_type, std::error_code& ec) noexcept -{ - if (check_trigger(failure_type)) { - ec = make_error_code(failure_type); - } - else { - ec = std::error_code(); - } - return ec; -} - inline void SimulatedFailure::trigger(FailureType failure_type) { if (check_trigger(failure_type)) - throw SimulatedFailure(make_error_code(failure_type)); + throw SimulatedFailure(failure_type); } inline constexpr bool SimulatedFailure::is_enabled() @@ -219,8 +205,8 @@ inline void SimulatedFailure::set_thread_local(bool tl) #endif } -inline SimulatedFailure::SimulatedFailure(std::error_code ec) - : std::system_error(ec) +inline SimulatedFailure::SimulatedFailure(FailureType) + : RuntimeError(Status{ErrorCodes::RuntimeError, "SimulatedFailure"}) { } diff --git a/src/realm/object-store/audit.mm b/src/realm/object-store/audit.mm index 0cd7647ac10..2412d7b9c37 100644 --- a/src/realm/object-store/audit.mm +++ b/src/realm/object-store/audit.mm @@ -783,8 +783,8 @@ bool write_event(Timestamp timestamp, StringData activity, StringData event_type std::string path = session->path(); session->close(); m_open_paths.erase(path); - if (status.get_std_error_code()) { - m_logger->error("Events: Upload on '%1' failed with error '%2'.", path, status.reason()); + if (!status.is_ok()) { + m_logger->error("Events: Upload on '%1' failed with error '%2'.", path, status); if (m_error_handler) { m_error_handler(SyncError(std::move(status), false)); } diff --git a/src/realm/object-store/c_api/conversion.hpp b/src/realm/object-store/c_api/conversion.hpp index 28147d1383b..ac4bcd354f4 100644 --- a/src/realm/object-store/c_api/conversion.hpp +++ b/src/realm/object-store/c_api/conversion.hpp @@ -485,8 +485,14 @@ static inline realm_version_id_t to_capi(const VersionID& v) return version_id; } -realm_sync_error_code_t to_capi(const Status& status, std::string& message); -void sync_error_to_error_code(const realm_sync_error_code_t& sync_error_code, std::error_code* error_code_out); +static inline realm_error_t to_capi(const Status& s) +{ + realm_error_t err; + err.error = static_cast(s.code()); + err.categories = static_cast(ErrorCodes::error_categories(s.code()).value()); + err.message = s.reason().c_str(); + return err; +} } // namespace realm::c_api diff --git a/src/realm/object-store/c_api/sync.cpp b/src/realm/object-store/c_api/sync.cpp index 0ab903dd1c3..bc862097794 100644 --- a/src/realm/object-store/c_api/sync.cpp +++ b/src/realm/object-store/c_api/sync.cpp @@ -122,64 +122,6 @@ static_assert(realm_flx_sync_subscription_set_state_e(SubscriptionSet::State::Un } // namespace -realm_sync_error_code_t to_capi(const Status& status, std::string& message) -{ - auto ret = realm_sync_error_code_t(); - - auto error_code = status.get_std_error_code(); - const std::error_category& category = error_code.category(); - if (status != ErrorCodes::SystemError) { - // TODO this will all go away when the unify error handling project is done, but for now just treat the - // ErrorCodes enum as client errors. - ret.category = RLM_SYNC_ERROR_CATEGORY_CLIENT; - } - else if (category == realm::sync::protocol_error_category()) { - if (realm::sync::is_session_level_error(realm::sync::ProtocolError(error_code.value()))) { - ret.category = RLM_SYNC_ERROR_CATEGORY_SESSION; - } - else { - ret.category = RLM_SYNC_ERROR_CATEGORY_CONNECTION; - } - } - else if (category == std::system_category() || category == realm::util::error::basic_system_error_category()) { - ret.category = RLM_SYNC_ERROR_CATEGORY_SYSTEM; - } - else { - ret.category = RLM_SYNC_ERROR_CATEGORY_UNKNOWN; - } - - if (status == ErrorCodes::SystemError) { - ret.value = error_code.value(); - message = error_code.message(); - } - else { - ret.value = status.code(); - message = status.reason(); - } - ret.message = message.c_str(); - ret.category_name = category.name(); - - return ret; -} - -void sync_error_to_error_code(const realm_sync_error_code_t& sync_error_code, std::error_code* error_code_out) -{ - if (error_code_out) { - const realm_sync_error_category_e category = sync_error_code.category; - if (category == RLM_SYNC_ERROR_CATEGORY_CLIENT) { - error_code_out->assign(sync_error_code.value, std::generic_category()); - } - else if (category == RLM_SYNC_ERROR_CATEGORY_SESSION || category == RLM_SYNC_ERROR_CATEGORY_CONNECTION) { - error_code_out->assign(sync_error_code.value, realm::sync::protocol_error_category()); - } - else if (category == RLM_SYNC_ERROR_CATEGORY_SYSTEM) { - error_code_out->assign(sync_error_code.value, std::system_category()); - } - else if (category == RLM_SYNC_ERROR_CATEGORY_UNKNOWN) { - error_code_out->assign(sync_error_code.value, realm::util::error::basic_system_error_category()); - } - } -} static Query add_ordering_to_realm_query(Query realm_query, const DescriptorOrdering& ordering) { @@ -315,8 +257,7 @@ RLM_API void realm_sync_config_set_error_handler(realm_sync_config_t* config, re auto c_error = realm_sync_error_t(); std::string error_code_message; - c_error.error_code = to_capi(error.status, error_code_message); - c_error.detailed_message = error.status.reason().c_str(); + c_error.status = to_capi(error.status); c_error.is_fatal = error.is_fatal; c_error.is_unrecognized_by_client = error.is_unrecognized_by_client; c_error.is_client_reset_requested = error.is_client_reset_requested(); @@ -856,9 +797,8 @@ RLM_API void realm_sync_session_wait_for_download_completion(realm_sync_session_ { util::UniqueFunction cb = [done, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](Status s) { - if (s.get_std_error_code()) { - std::string error_code_message; - realm_sync_error_code_t error = to_capi(s, error_code_message); + if (!s.is_ok()) { + realm_error_t error = to_capi(s); done(userdata.get(), &error); } else { @@ -875,9 +815,8 @@ RLM_API void realm_sync_session_wait_for_upload_completion(realm_sync_session_t* { util::UniqueFunction cb = [done, userdata = SharedUserdata(userdata, FreeUserdata(userdata_free))](Status s) { - if (s.get_std_error_code()) { - std::string error_code_message; - realm_sync_error_code_t error = to_capi(s, error_code_message); + if (!s.is_ok()) { + realm_error_t error = to_capi(s); done(userdata.get(), &error); } else { @@ -887,16 +826,14 @@ RLM_API void realm_sync_session_wait_for_upload_completion(realm_sync_session_t* (*session)->wait_for_upload_completion(std::move(cb)); } -RLM_API void realm_sync_session_handle_error_for_testing(const realm_sync_session_t* session, int error_code, - int error_category, const char* error_message, bool is_fatal) +RLM_API void realm_sync_session_handle_error_for_testing(const realm_sync_session_t* session, + realm_errno_e error_code, const char* error_str, + bool is_fatal) { REALM_ASSERT(session); - realm_sync_error_code_t sync_error{static_cast(error_category), error_code, - error_message}; - std::error_code err; - sync_error_to_error_code(sync_error, &err); - SyncSession::OnlyForTesting::handle_error(*session->get(), - sync::SessionErrorInfo{Status{err, error_message}, IsFatal{is_fatal}}); + SyncSession::OnlyForTesting::handle_error( + *session->get(), + sync::SessionErrorInfo{Status{static_cast(error_code), error_str}, !is_fatal}); } } // namespace realm::c_api diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index 2b79f34501a..7152bda9b2d 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include #include @@ -237,21 +237,19 @@ void SyncSession::become_waiting_for_access_token() m_state = State::WaitingForAccessToken; } -void SyncSession::handle_bad_auth(const std::shared_ptr& user, Status error_code, - std::string_view context_message) +void SyncSession::handle_bad_auth(const std::shared_ptr& user, Status status) { // TODO: ideally this would write to the logs as well in case users didn't set up their error handler. { util::CheckedUniqueLock lock(m_state_mutex); - cancel_pending_waits(std::move(lock), error_code); + cancel_pending_waits(std::move(lock), status); } if (user) { user->log_out(); } if (auto error_handler = config(&SyncConfig::error_handler)) { - auto user_facing_error = - SyncError(Status{realm::sync::ProtocolError::bad_authentication, context_message}, true); + auto user_facing_error = SyncError({ErrorCodes::AuthError, status.reason()}, true); error_handler(shared_from_this(), std::move(user_facing_error)); } } @@ -301,21 +299,24 @@ SyncSession::handle_refresh(const std::shared_ptr& session, bool re // there was a problem locally before even sending the request to the server // eg. ClientErrorCode::user_not_found, ClientErrorCode::user_not_logged_in, // ClientErrorCode::too_many_redirects - session->handle_bad_auth(session_user, error->to_status(), error->reason()); + session->handle_bad_auth(session_user, error->to_status()); } else if (check_for_auth_failure(*error)) { // A 401 response on a refresh request means that the token cannot be refreshed and we should not // retry. This can be because an admin has revoked this user's sessions, the user has been disabled, // or the refresh token has expired according to the server's clock. - session->handle_bad_auth(session_user, error->to_status(), - "Unable to refresh the user access token."); + session->handle_bad_auth( + session_user, + {error->code(), util::format("Unable to refresh the user access token: %1", error->reason())}); } else if (check_for_redirect_response(*error)) { // A 301 or 308 response is an unhandled permanent redirect response (which should not happen) - if // this is received, fail the request with an appropriate error message. // Temporary redirect responses (302, 307) are not supported - session->handle_bad_auth(session_user, error->to_status(), - "Unhandled redirect response when trying to reach the server."); + session->handle_bad_auth( + session_user, + {error->code(), util::format("Unhandled redirect response when trying to reach the server: %1", + error->reason())}); } else { // A refresh request has failed. This is an unexpected non-fatal error and we would @@ -633,7 +634,6 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) { enum class NextStateAfterError { none, inactive, error }; auto next_state = error.is_fatal ? NextStateAfterError::error : NextStateAfterError::none; - auto error_code = error.status.get_std_error_code(); util::Optional delete_file; bool log_out_user = false; bool unrecognized_by_client = false; @@ -644,10 +644,6 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) next_state = NextStateAfterError::inactive; delete_file = ShouldBackup::yes; } - else if (error_code == realm::sync::ProtocolError::bad_authentication) { - next_state = NextStateAfterError::inactive; - log_out_user = true; - } else if (error.server_requests_action != sync::ProtocolErrorInfo::Action::NoAction) { switch (error.server_requests_action) { case sync::ProtocolErrorInfo::Action::NoAction: @@ -719,6 +715,10 @@ void SyncSession::handle_error(sync::SessionErrorInfo error) return; } break; + case sync::ProtocolErrorInfo::Action::LogOutUser: + next_state = NextStateAfterError::inactive; + log_out_user = true; + break; } } else { diff --git a/src/realm/object-store/sync/sync_session.hpp b/src/realm/object-store/sync/sync_session.hpp index 423898de5a1..ad4a599f703 100644 --- a/src/realm/object-store/sync/sync_session.hpp +++ b/src/realm/object-store/sync/sync_session.hpp @@ -383,7 +383,7 @@ class SyncSession : public std::enable_shared_from_this { sync::ProtocolErrorInfo::Action server_requests_action) REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex); void handle_error(sync::SessionErrorInfo) REQUIRES(!m_state_mutex, !m_config_mutex, !m_connection_state_mutex); - void handle_bad_auth(const std::shared_ptr& user, Status error_code, std::string_view context_message) + void handle_bad_auth(const std::shared_ptr& user, Status status) REQUIRES(!m_state_mutex, !m_config_mutex); // If sub_notify_error is set (including Status::OK()), then the pending subscription waiters will // also be called with the sub_notify_error status value. diff --git a/src/realm/status.cpp b/src/realm/status.cpp index 0f097e254d8..f8e860d6840 100644 --- a/src/realm/status.cpp +++ b/src/realm/status.cpp @@ -22,14 +22,14 @@ namespace realm { -Status::ErrorInfo::ErrorInfo(ErrorCodes::Error code, std::string_view reason) +Status::ErrorInfo::ErrorInfo(ErrorCodes::Error code, std::string&& reason) : m_refs(0) , m_code(code) - , m_reason(reason) + , m_reason(std::move(reason)) { } -util::bind_ptr Status::ErrorInfo::create(ErrorCodes::Error code, std::string_view reason) +util::bind_ptr Status::ErrorInfo::create(ErrorCodes::Error code, std::string&& reason) { // OK status should be created by calling Status::OK() - which is a special case that doesn't allocate // anything. diff --git a/src/realm/status.hpp b/src/realm/status.hpp index d8e50bea1ef..adea6364038 100644 --- a/src/realm/status.hpp +++ b/src/realm/status.hpp @@ -41,22 +41,13 @@ class REALM_NODISCARD Status { */ template , int> = 0> Status(ErrorCodes::Error code, Reason&& reason) - : m_error(ErrorInfo::create(code, std::string_view{reason})) + : m_error(ErrorInfo::create(code, std::string{std::string_view{reason}})) { } - template , int> = 0> - Status(std::error_code code, Reason&& reason) - { - if (code) { - m_error = ErrorInfo::create(ErrorCodes::SystemError, std::string_view{reason}); - m_error->m_std_error_code = code; - } - } - - std::error_code get_std_error_code() const + Status(ErrorCodes::Error code, std::string&& reason) + : m_error(ErrorInfo::create(code, std::move(reason))) { - return m_error ? m_error->m_std_error_code : std::error_code{}; } /* @@ -80,23 +71,14 @@ class REALM_NODISCARD Status { void ignore() const noexcept {} private: - friend struct SystemError; - void set_std_error_code(std::error_code code) - { - m_error->m_std_error_code = code; - } - Status() = default; struct ErrorInfo { mutable std::atomic m_refs; const ErrorCodes::Error m_code; const std::string m_reason; - // The addition of this system error code may be temporary if we ever get all use of - // std::error_code migrated to the unified exception system - std::error_code m_std_error_code; - static util::bind_ptr create(ErrorCodes::Error code, std::string_view reason); + static util::bind_ptr create(ErrorCodes::Error code, std::string&& reason); protected: template @@ -115,7 +97,7 @@ class REALM_NODISCARD Status { } private: - ErrorInfo(ErrorCodes::Error code, std::string_view reason); + ErrorInfo(ErrorCodes::Error code, std::string&& reason); }; util::bind_ptr m_error = {}; diff --git a/src/realm/sync/client_base.hpp b/src/realm/sync/client_base.hpp index e72d1d36a8c..a2ca58136e7 100644 --- a/src/realm/sync/client_base.hpp +++ b/src/realm/sync/client_base.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace realm::sync { @@ -245,6 +246,12 @@ struct ClientConfig { /// /// \sa set_connection_state_change_listener(). struct SessionErrorInfo : public ProtocolErrorInfo { + SessionErrorInfo(const ProtocolErrorInfo& info) + : ProtocolErrorInfo(info) + , status(protocol_error_to_status(static_cast(info.raw_error_code), message)) + { + } + SessionErrorInfo(const ProtocolErrorInfo& info, Status status) : ProtocolErrorInfo(info) , status(std::move(status)) @@ -252,7 +259,7 @@ struct SessionErrorInfo : public ProtocolErrorInfo { } SessionErrorInfo(Status status, IsFatal is_fatal) - : ProtocolErrorInfo(status.get_std_error_code().value(), status.reason(), is_fatal) + : ProtocolErrorInfo(0, {}, is_fatal) , status(std::move(status)) { } diff --git a/src/realm/sync/config.cpp b/src/realm/sync/config.cpp index b287312ca37..52b12609801 100644 --- a/src/realm/sync/config.cpp +++ b/src/realm/sync/config.cpp @@ -37,15 +37,6 @@ std::string format_sync_error_message(const Status& status, std::optional log_url) -{ - if (status.get_std_error_code()) { - return Status(status.get_std_error_code(), format_sync_error_message(status, log_url)); - } - return Status(status.code(), format_sync_error_message(status, log_url)); -} - } // namespace // sync defines its own copy of port_type to avoid depending on network.hpp, but they should be the same. static_assert(std::is_same_v); @@ -54,7 +45,7 @@ using ProtocolError = realm::sync::ProtocolError; SyncError::SyncError(Status orig_status, bool is_fatal, std::optional server_log, std::vector compensating_writes) - : status(fixup_sync_error_status(orig_status, server_log)) + : status(orig_status.code(), format_sync_error_message(orig_status, server_log)) , is_fatal(is_fatal) , simple_message(std::string_view(status.reason()).substr(0, orig_status.reason().size())) , compensating_writes_info(std::move(compensating_writes)) diff --git a/src/realm/sync/config.hpp b/src/realm/sync/config.hpp index 5c88a6aec44..525e951bc77 100644 --- a/src/realm/sync/config.hpp +++ b/src/realm/sync/config.hpp @@ -82,13 +82,6 @@ struct SyncError { /// The error indicates a client reset situation. bool is_client_reset_requested() const; - - // TODO Remove this temporary function after we've fully converted the sync codebase to using - // Status without std::error_code. - std::error_code get_system_error() const noexcept - { - return status.get_std_error_code(); - } }; using SyncSessionErrorHandler = void(std::shared_ptr, SyncError); diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 3c41a0504af..a9af5ebf2a0 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -465,14 +466,15 @@ void Connection::websocket_connected_handler(const std::string& protocol) } } } - logger.error("Bad protocol info from server: '%1'", protocol); // Throws + close_due_to_client_side_error({ErrorCodes::SyncProtocolNegotiationFailed, + util::format("Bad protocol info from server: '%1'", protocol)}, + IsFatal{true}, ConnectionTerminationReason::bad_headers_in_http_response); } else { - logger.error("Missing protocol info from server"); // Throws + close_due_to_client_side_error( + {ErrorCodes::SyncProtocolNegotiationFailed, "Missing protocol info from server"}, IsFatal{true}, + ConnectionTerminationReason::bad_headers_in_http_response); } - close_due_to_client_side_error( - {ErrorCodes::SyncProtocolNegotiationFailed, "Failed to negotiate websocket protocol"}, IsFatal{true}, - ConnectionTerminationReason::bad_headers_in_http_response); // Throws } @@ -482,10 +484,12 @@ bool Connection::websocket_binary_message_received(util::Span data) logger.debug("Received binary message after connection was force closed"); return false; } - std::error_code ec; + using sf = SimulatedFailure; - if (sf::trigger(sf::sync_client__read_head, ec)) { - read_or_write_error(ec, "simulated read error"); + if (sf::check_trigger(sf::sync_client__read_head)) { + close_due_to_client_side_error( + {ErrorCodes::RuntimeError, "Simulated failure during sync client websocket read"}, IsFatal{false}, + ConnectionTerminationReason::read_or_write_error); return bool(m_websocket); } @@ -573,7 +577,9 @@ bool Connection::websocket_closed_handler(bool was_clean, WebSocketError error_c break; } case WebSocketError::websocket_forbidden: { - involuntary_disconnect(SessionErrorInfo({ErrorCodes::AuthError, msg}, IsFatal{true}), + SessionErrorInfo error_info({ErrorCodes::AuthError, msg}, IsFatal{true}); + error_info.server_requests_action = ProtocolErrorInfo::Action::LogOutUser; + involuntary_disconnect(std::move(error_info), ConnectionTerminationReason::http_response_says_fatal_error); break; } @@ -1098,13 +1104,6 @@ void Connection::handle_disconnect_wait(Status status) } -void Connection::read_or_write_error(std::error_code ec, std::string_view msg) -{ - close_due_to_client_side_error(ec, msg, IsFatal{false}, - ConnectionTerminationReason::read_or_write_error); // Throws -} - - void Connection::close_due_to_protocol_error(Status status) { close_due_to_client_side_error(std::move(status), IsFatal{true}, @@ -1112,18 +1111,6 @@ void Connection::close_due_to_protocol_error(Status status) } -// Close connection due to error discovered on the client-side. -void Connection::close_due_to_client_side_error(std::error_code ec, std::optional msg, - IsFatal is_fatal, ConnectionTerminationReason reason) -{ - std::string message = ec.message(); - if (msg) { - message += ": "; - message += *msg; - } - close_due_to_client_side_error(Status{ec, std::move(message)}, is_fatal, reason); -} - void Connection::close_due_to_client_side_error(Status status, IsFatal is_fatal, ConnectionTerminationReason reason) { logger.info("Connection closed due to error: %1", status); // Throws @@ -1223,7 +1210,7 @@ void Connection::receive_pong(milliseconds_type timestamp) if (REALM_UNLIKELY(timestamp != m_last_ping_sent_at)) { close_due_to_protocol_error( {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received PONG message with an invalid timestamp (expected %1, received %2", + util::format("Received PONG message with an invalid timestamp (expected %1, received %2)", m_last_ping_sent_at, timestamp)}); // Throws return; } @@ -1415,30 +1402,9 @@ void Connection::receive_test_command_response(session_ident_type session_ident, } -void Connection::handle_protocol_error(ClientProtocol::Error error, std::string msg) +void Connection::handle_protocol_error(Status status) { - switch (error) { - case ClientProtocol::Error::unknown_message: - [[fallthrough]]; - case ClientProtocol::Error::bad_syntax: - [[fallthrough]]; - case ClientProtocol::Error::bad_changeset_header_syntax: - [[fallthrough]]; - case ClientProtocol::Error::bad_changeset_size: - [[fallthrough]]; - case ClientProtocol::Error::bad_server_version: - close_due_to_protocol_error({ErrorCodes::SyncProtocolInvariantFailed, std::move(msg)}); // Throws - break; - case ClientProtocol::Error::limits_exceeded: - close_due_to_protocol_error({ErrorCodes::LimitExceeded, std::move(msg)}); // Throws - break; - case ClientProtocol::Error::bad_decompression: - close_due_to_protocol_error({ErrorCodes::RuntimeError, std::move(msg)}); // Throws - break; - case ClientProtocol::Error::bad_error_code: - close_due_to_protocol_error({ErrorCodes::UnknownError, std::move(msg)}); // Throws - break; - } + close_due_to_protocol_error(std::move(status)); } @@ -1569,7 +1535,7 @@ void Session::on_integration_failure(const IntegrationException& error) { REALM_ASSERT_EX(m_state == Active, m_state); REALM_ASSERT(!m_client_error && !m_error_to_send); - logger.error("Failed to integrate downloaded changesets: %1", error.what()); + logger.error("Failed to integrate downloaded changesets: %1", error.to_status()); m_client_error = util::make_optional(error); m_error_to_send = true; @@ -2516,18 +2482,19 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) return {ErrorCodes::SyncProtocolInvariantFailed, "Received ERROR message when it was not legal"}; } - bool known_error_code = bool(get_protocol_error_message(info.raw_error_code)); - if (REALM_UNLIKELY(!known_error_code)) { + auto status = protocol_error_to_status(static_cast(info.raw_error_code), info.message); + if (status == ErrorCodes::UnknownError) { return {ErrorCodes::SyncProtocolInvariantFailed, util::format("Received ERROR message with unknown error code %1", info.raw_error_code)}; } - ProtocolError error_code = ProtocolError(info.raw_error_code); - if (REALM_UNLIKELY(!is_session_level_error(error_code))) { + else if (auto error_code = ProtocolError(info.raw_error_code); + REALM_UNLIKELY(!is_session_level_error(error_code))) { return {ErrorCodes::SyncProtocolInvariantFailed, util::format("Received ERROR message for session with non-session-level error code %1", info.raw_error_code)}; } + // Can't process debug hook actions once the Session is undergoing deactivation, since // the SessionWrapper may not be available if (m_state == Active) { @@ -2539,7 +2506,7 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) // For compensating write errors, we need to defer raising them to the SDK until after the server version // containing the compensating write has appeared in a download message. - if (error_code == ProtocolError::compensating_write) { + if (status == ErrorCodes::SyncCompensatingWrite) { // If the client is not active, the compensating writes will not be processed now, but will be // sent again the next time the client connects if (m_state == Active) { @@ -2549,7 +2516,7 @@ Status Session::receive_error_message(const ProtocolErrorInfo& info) } m_error_message_received = true; - suspend(SessionErrorInfo{info, protocol_error_to_status(error_code, info.message)}); + suspend(SessionErrorInfo{info, std::move(status)}); return Status::OK(); } @@ -2599,7 +2566,7 @@ Status Session::receive_test_command_response(request_ident_type ident, std::str }); if (it == m_pending_test_commands.end()) { return {ErrorCodes::SyncProtocolInvariantFailed, - util::format("Received TEST_COMMAND for request ident %1 which does not exist", ident)}; + util::format("Received test command response for a non-existent ident %1", ident)}; } it->promise.emplace_value(std::string{body}); diff --git a/src/realm/sync/noinst/client_impl_base.hpp b/src/realm/sync/noinst/client_impl_base.hpp index b92f04932b9..07aa1541f77 100644 --- a/src/realm/sync/noinst/client_impl_base.hpp +++ b/src/realm/sync/noinst/client_impl_base.hpp @@ -560,13 +560,9 @@ class ClientImpl::Connection { void handle_message_received(util::Span data); void initiate_disconnect_wait(); void handle_disconnect_wait(Status status); - void read_or_write_error(std::error_code ec, std::string_view msg); void close_due_to_protocol_error(Status status); - + void close_due_to_client_side_error(Status, IsFatal is_fatal, ConnectionTerminationReason reason); void close_due_to_transient_error(Status status, ConnectionTerminationReason reason); - void close_due_to_client_side_error(Status status, IsFatal is_fatal, ConnectionTerminationReason reason); - void close_due_to_client_side_error(std::error_code, std::optional msg, IsFatal is_fatal, - ConnectionTerminationReason reason); void close_due_to_server_side_error(ProtocolError, const ProtocolErrorInfo& info); void involuntary_disconnect(const SessionErrorInfo& info, ConnectionTerminationReason reason); void disconnect(const SessionErrorInfo& info); @@ -582,7 +578,7 @@ class ClientImpl::Connection { void receive_mark_message(session_ident_type, request_ident_type); void receive_unbound_message(session_ident_type); void receive_test_command_response(session_ident_type, request_ident_type, std::string_view body); - void handle_protocol_error(ClientProtocol::Error, std::string message); + void handle_protocol_error(Status status); // These are only called from Session class. void enlist_to_send(Session*); diff --git a/src/realm/sync/noinst/protocol_codec.hpp b/src/realm/sync/noinst/protocol_codec.hpp index d398bb43276..6d8a6d4c85e 100644 --- a/src/realm/sync/noinst/protocol_codec.hpp +++ b/src/realm/sync/noinst/protocol_codec.hpp @@ -147,21 +147,6 @@ class ClientProtocol { using RemoteChangeset = sync::Transformer::RemoteChangeset; using ReceivedChangesets = std::vector; - // FIXME: No need to explicitly assign numbers to these - enum class Error { - // clang-format off - unknown_message = 101, // Unknown type of input message - bad_syntax = 102, // Bad syntax in input message head - limits_exceeded = 103, // Limits exceeded in input message - bad_changeset_header_syntax = 108, // Bad syntax in changeset header (DOWNLOAD) - bad_changeset_size = 109, // Bad changeset size in changeset header (DOWNLOAD) - bad_server_version = 111, // Bad server version in changeset header (DOWNLOAD) - bad_error_code = 114, ///< Bad error code (ERROR) - bad_decompression = 115, // Error in decompression (DOWNLOAD) - // clang-format on - }; - - /// Messages sent by the client. void make_pbs_bind_message(int protocol_version, OutputBuffer&, session_ident_type session_ident, @@ -223,9 +208,9 @@ class ClientProtocol { void parse_message_received(Connection& connection, std::string_view msg_data) { util::Logger& logger = connection.logger; - auto report_error = [&](Error err, const auto fmt, auto&&... args) { + auto report_error = [&](const auto fmt, auto&&... args) { auto msg = util::format(fmt, std::forward(args)...); - connection.handle_protocol_error(err, std::move(msg)); + connection.handle_protocol_error(Status{ErrorCodes::SyncProtocolInvariantFailed, std::move(msg)}); }; HeaderLineParser msg(msg_data); @@ -234,7 +219,7 @@ class ClientProtocol { message_type = msg.read_next(); } catch (const ProtocolCodecException& e) { - return report_error(Error::bad_syntax, "Could not find message type in message: %1", e.what()); + return report_error("Could not find message type in message: %1", e.what()); } try { @@ -254,12 +239,6 @@ class ClientProtocol { auto message_size = msg.read_next(); auto is_fatal = sync::IsFatal{!msg.read_next()}; auto session_ident = msg.read_next('\n'); - - bool unknown_error = !sync::get_protocol_error_message(error_code); - if (unknown_error) { - return report_error(Error::bad_error_code, "Bad error code"); - } - auto message = msg.read_sized_data(message_size); connection.receive_error_message(sync::ProtocolErrorInfo{error_code, message, is_fatal}, @@ -296,7 +275,6 @@ class ClientProtocol { if (query_string == json.end() || !query_string->is_string() || query_string->get().empty()) { return report_error( - Error::bad_syntax, "Missing/invalid partition query string in migrate to flexible sync error response"); } @@ -306,14 +284,12 @@ class ClientProtocol { if (auto rejected_updates = json.find("rejectedUpdates"); rejected_updates != json.end()) { if (!rejected_updates->is_array()) { return report_error( - Error::bad_syntax, "Compensating writes error list is not stored in an array as expected"); } for (const auto& rejected_update : *rejected_updates) { if (!rejected_update.is_object()) { return report_error( - Error::bad_syntax, "Compensating write error information is not stored in an object as expected"); } @@ -334,8 +310,8 @@ class ClientProtocol { catch (const nlohmann::json::exception& e) { // If any of the above json fields are not present, this is a fatal error // however, additional optional fields may be added in the future. - return report_error(Error::bad_syntax, "Failed to parse 'json_error' with error_code %1: '%2'", - info.raw_error_code, e.what()); + return report_error("Failed to parse 'json_error' with error_code %1: '%2'", info.raw_error_code, + e.what()); } connection.receive_error_message(info, session_ident); // Throws } @@ -372,14 +348,14 @@ class ClientProtocol { connection.receive_test_command_response(session_ident, request_ident, body); } else { - return report_error(Error::unknown_message, "Unknown input message type '%1'", msg_data); + return report_error("Unknown input message type '%1'", msg_data); } } catch (const ProtocolCodecException& e) { - return report_error(Error::bad_syntax, "Bad syntax in %1 message: %2", message_type, e.what()); + return report_error("Bad syntax in %1 message: %2", message_type, e.what()); } if (!msg.at_end()) { - return report_error(Error::bad_syntax, "wire protocol message had leftover data after being parsed"); + return report_error("wire protocol message had leftover data after being parsed"); } } @@ -388,9 +364,9 @@ class ClientProtocol { void parse_download_message(Connection& connection, HeaderLineParser& msg) { util::Logger& logger = connection.logger; - auto report_error = [&](Error err, const auto fmt, auto&&... args) { + auto report_error = [&](ErrorCodes::Error code, const auto fmt, auto&&... args) { auto msg = util::format(fmt, std::forward(args)...); - connection.handle_protocol_error(err, std::move(msg)); + connection.handle_protocol_error(Status{code, std::move(msg)}); }; auto msg_with_header = msg.remaining(); @@ -413,7 +389,7 @@ class ClientProtocol { if (uncompressed_body_size > s_max_body_size) { auto header = msg_with_header.substr(0, msg_with_header.size() - msg.remaining().size()); - return report_error(Error::limits_exceeded, "Limits exceeded in input message '%1'", header); + return report_error(ErrorCodes::LimitExceeded, "Limits exceeded in input message '%1'", header); } std::unique_ptr uncompressed_body_buffer; @@ -425,7 +401,7 @@ class ClientProtocol { {uncompressed_body_buffer.get(), uncompressed_body_size}); if (ec) { - return report_error(Error::bad_decompression, "compression::inflate: %1", ec.message()); + return report_error(ErrorCodes::RuntimeError, "compression::inflate: %1", ec.message()); } msg = HeaderLineParser(std::string_view(uncompressed_body_buffer.get(), uncompressed_body_size)); @@ -448,11 +424,11 @@ class ClientProtocol { auto changeset_size = msg.read_next(); if (changeset_size > msg.bytes_remaining()) { - return report_error(Error::bad_changeset_size, "Bad changeset size %1 > %2", changeset_size, - msg.bytes_remaining()); + return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Bad changeset size %1 > %2", + changeset_size, msg.bytes_remaining()); } if (cur_changeset.remote_version == 0) { - return report_error(Error::bad_server_version, + return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Server version in downloaded changeset cannot be zero"); } auto changeset_data = msg.read_sized_data(changeset_size); @@ -506,6 +482,7 @@ class ClientProtocol { {"RevertToPBS", action::RevertToPBS}, {"RefreshUser", action::RefreshUser}, {"RefreshLocation", action::RefreshLocation}, + {"LogOutUser", action::LogOutUser}, }; if (auto action_it = mapping.find(action_string); action_it != mapping.end()) { @@ -543,18 +520,6 @@ class ServerProtocol { using OutputBuffer = util::ResettableExpandableBufferOutputStream; - // FIXME: No need to explicitly assign numbers to these - enum class Error { - // clang-format off - unknown_message = 101, // Unknown type of input message - bad_syntax = 102, // Bad syntax in input message head - limits_exceeded = 103, // Limits exceeded in input message - bad_decompression = 104, // Error in decompression (UPLOAD) - bad_changeset_header_syntax = 105, // Bad syntax in changeset header (UPLOAD) - bad_changeset_size = 106, // Changeset size doesn't fit in message (UPLOAD) - // clang-format on - }; - // Messages sent by the server to the client void make_ident_message(int protocol_version, OutputBuffer&, session_ident_type session_ident, @@ -603,8 +568,8 @@ class ServerProtocol { connection.receive_ping(timestamp, rtt); } catch (const ProtocolCodecException& e) { - connection.handle_protocol_error(Error::bad_syntax, - util::format("Bad syntax in PING message: %1", e.what())); + connection.handle_protocol_error(Status{ErrorCodes::SyncProtocolInvariantFailed, + util::format("Bad syntax in PING message: %1", e.what())}); } } @@ -625,9 +590,9 @@ class ServerProtocol { { auto& logger = connection.logger; - auto report_error = [&](Error err, const auto fmt, auto&&... args) { + auto report_error = [&](ErrorCodes::Error err, const auto fmt, auto&&... args) { auto msg = util::format(fmt, std::forward(args)...); - connection.handle_protocol_error(err, std::move(msg)); + connection.handle_protocol_error(Status{err, std::move(msg)}); }; HeaderLineParser msg(msg_data); @@ -636,7 +601,8 @@ class ServerProtocol { message_type = msg.read_next(); } catch (const ProtocolCodecException& e) { - return report_error(Error::bad_syntax, "Could not find message type in message: %1", e.what()); + return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Could not find message type in message: %1", + e.what()); } try { @@ -653,7 +619,8 @@ class ServerProtocol { std::size_t body_size = (is_body_compressed ? compressed_body_size : uncompressed_body_size); if (body_size > s_max_body_size) { auto header = msg_with_header.substr(0, msg_with_header.size() - msg.bytes_remaining()); - return report_error(Error::limits_exceeded, + + return report_error(ErrorCodes::LimitExceeded, "Body size of upload message is too large. Raw header: %1", header); } @@ -668,7 +635,7 @@ class ServerProtocol { compressed_body, {uncompressed_body_buffer.get(), uncompressed_body_size}); if (ec) { - return report_error(Error::bad_decompression, "compression::inflate: %1", ec.message()); + return report_error(ErrorCodes::RuntimeError, "compression::inflate: %1", ec.message()); } msg = HeaderLineParser(std::string_view(uncompressed_body_buffer.get(), uncompressed_body_size)); @@ -696,12 +663,12 @@ class ServerProtocol { changeset_size = msg.read_next(); } catch (const ProtocolCodecException& e) { - return report_error(Error::bad_changeset_header_syntax, "Bad changeset header syntax: %1", - e.what()); + return report_error(ErrorCodes::SyncProtocolInvariantFailed, + "Bad changeset header syntax: %1", e.what()); } if (changeset_size > msg.bytes_remaining()) { - return report_error(Error::bad_changeset_size, "Bad changeset size"); + return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Bad changeset size"); } upload_changeset.changeset = msg.read_sized_data(changeset_size); @@ -743,13 +710,14 @@ class ServerProtocol { auto is_subserver = msg.read_next('\n'); if (path_size == 0) { - return report_error(Error::bad_syntax, "Path size in BIND message is zero"); + return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Path size in BIND message is zero"); } if (path_size > s_max_path_size) { - return report_error(Error::limits_exceeded, "Path size in BIND message is too large"); + return report_error(ErrorCodes::SyncProtocolInvariantFailed, + "Path size in BIND message is too large"); } if (signed_user_token_size > s_max_signed_user_token_size) { - return report_error(Error::limits_exceeded, + return report_error(ErrorCodes::SyncProtocolInvariantFailed, "Signed user token size in BIND message is too large"); } @@ -786,11 +754,12 @@ class ServerProtocol { connection.receive_error_message(session_ident, error_code, json_raw); } else { - return report_error(Error::unknown_message, "unknown message type %1", message_type); + return report_error(ErrorCodes::SyncProtocolInvariantFailed, "unknown message type %1", message_type); } } catch (const ProtocolCodecException& e) { - return report_error(Error::bad_syntax, "bad syntax in %1 message: %2", message_type, e.what()); + return report_error(ErrorCodes::SyncProtocolInvariantFailed, "bad syntax in %1 message: %2", message_type, + e.what()); } } diff --git a/src/realm/sync/noinst/server/server.cpp b/src/realm/sync/noinst/server/server.cpp index 942e2049c1c..7b40431f6d2 100644 --- a/src/realm/sync/noinst/server/server.cpp +++ b/src/realm/sync/noinst/server/server.cpp @@ -1148,11 +1148,10 @@ class SyncConnection : public websocket::Config { bool websocket_binary_message_received(const char* data, size_t size) final override { - std::error_code ec; using sf = _impl::SimulatedFailure; - if (sf::trigger(sf::sync_server__read_head, ec)) { + if (sf::check_trigger(sf::sync_server__read_head)) { // Suicide - read_error(ec); + read_error(sf::sync_server__read_head); return false; } // After a connection level error has occurred, all incoming messages @@ -1275,7 +1274,7 @@ class SyncConnection : public websocket::Config { void initiate_pong_output_buffer(); - void handle_protocol_error(ServerProtocol::Error error, std::string msg); + void handle_protocol_error(Status status); void receive_bind_message(session_ident_type, std::string path, std::string signed_user_token, bool need_client_file_ident, bool is_subserver); @@ -4247,32 +4246,22 @@ void SyncConnection::enlist_to_send(Session* sess) noexcept } -void SyncConnection::handle_protocol_error(ServerProtocol::Error error, std::string msg) +void SyncConnection::handle_protocol_error(Status status) { - logger.error("%1", msg); - switch (error) { - case ServerProtocol::Error::unknown_message: - protocol_error(ProtocolError::unknown_message); // Throws - break; - case ServerProtocol::Error::bad_syntax: + logger.error("%1", status); + switch (status.code()) { + case ErrorCodes::SyncProtocolInvariantFailed: protocol_error(ProtocolError::bad_syntax); // Throws break; - case ServerProtocol::Error::limits_exceeded: + case ErrorCodes::LimitExceeded: protocol_error(ProtocolError::limits_exceeded); // Throws break; - case ServerProtocol::Error::bad_decompression: - protocol_error(ProtocolError::bad_decompression); // Throws - break; - case ServerProtocol::Error::bad_changeset_header_syntax: - protocol_error(ProtocolError::bad_changeset_header_syntax); // Throws - break; - case ServerProtocol::Error::bad_changeset_size: - protocol_error(ProtocolError::bad_changeset_size); // Throws + default: + protocol_error(ProtocolError::other_error); break; } } - void SyncConnection::receive_bind_message(session_ident_type session_ident, std::string path, std::string signed_user_token, bool need_client_file_ident, bool is_subserver) diff --git a/src/realm/sync/protocol.cpp b/src/realm/sync/protocol.cpp index 4e6e20ba6c0..b1c214fe784 100644 --- a/src/realm/sync/protocol.cpp +++ b/src/realm/sync/protocol.cpp @@ -1,36 +1,7 @@ #include -namespace { - -class ErrorCategoryImpl : public std::error_category { -public: - const char* name() const noexcept override final - { - return "realm::sync::ProtocolError"; - } - std::string message(int error_code) const override final - { - const char* msg = realm::sync::get_protocol_error_message(error_code); - if (!msg) - msg = "Unknown error"; - std::string msg_2{msg}; // Throws (copy) - return msg_2; - } -}; - -ErrorCategoryImpl g_error_category; - -} // unnamed namespace - - -namespace realm { -namespace sync { - -Status protocol_error_to_status(ProtocolError error_code, std::string_view msg) -{ - return SystemError(make_error_code(error_code), msg).to_status(); -} +namespace realm::sync { const char* get_protocol_error_message(int error_code) noexcept { @@ -156,15 +127,127 @@ const char* get_protocol_error_message(int error_code) noexcept return nullptr; } -const std::error_category& protocol_error_category() noexcept +std::ostream& operator<<(std::ostream& os, ProtocolError error) { - return g_error_category; + if (auto str = get_protocol_error_message(static_cast(error))) { + return os << str; + } + return os << "Unknown protocol error " << static_cast(error); } -std::error_code make_error_code(ProtocolError error_code) noexcept +Status protocol_error_to_status(ProtocolError error_code, std::string_view msg) { - return std::error_code(int(error_code), g_error_category); + auto translated_error_code = [&] { + switch (error_code) { + case ProtocolError::connection_closed: + return ErrorCodes::ConnectionClosed; + case ProtocolError::other_error: + return ErrorCodes::RuntimeError; + case ProtocolError::unknown_message: + [[fallthrough]]; + case ProtocolError::bad_syntax: + [[fallthrough]]; + case ProtocolError::wrong_protocol_version: + [[fallthrough]]; + case ProtocolError::bad_session_ident: + [[fallthrough]]; + case ProtocolError::reuse_of_session_ident: + [[fallthrough]]; + case ProtocolError::bound_in_other_session: + [[fallthrough]]; + case ProtocolError::bad_changeset_header_syntax: + [[fallthrough]]; + case ProtocolError::bad_changeset_size: + [[fallthrough]]; + case ProtocolError::bad_message_order: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::bad_decompression: + return ErrorCodes::RuntimeError; + case ProtocolError::switch_to_flx_sync: + [[fallthrough]]; + case ProtocolError::switch_to_pbs: + return ErrorCodes::WrongSyncType; + + case ProtocolError::session_closed: + return ErrorCodes::ConnectionClosed; + case ProtocolError::other_session_error: + return ErrorCodes::RuntimeError; + case ProtocolError::illegal_realm_path: + return ErrorCodes::BadSyncPartitionValue; + case ProtocolError::permission_denied: + return ErrorCodes::SyncPermissionDenied; + case ProtocolError::bad_client_file_ident: + [[fallthrough]]; + case ProtocolError::bad_server_version: + [[fallthrough]]; + case ProtocolError::bad_client_version: + [[fallthrough]]; + case ProtocolError::diverging_histories: + [[fallthrough]]; + case ProtocolError::client_file_expired: + [[fallthrough]]; + case ProtocolError::bad_client_file: + return ErrorCodes::SyncClientResetRequired; + case ProtocolError::bad_changeset: + return ErrorCodes::BadChangeset; + case ProtocolError::bad_origin_file_ident: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::user_mismatch: + return ErrorCodes::SyncUserMismatch; + case ProtocolError::invalid_schema_change: + return ErrorCodes::InvalidSchemaChange; + case ProtocolError::bad_query: + return ErrorCodes::InvalidSubscriptionQuery; + case ProtocolError::object_already_exists: + return ErrorCodes::ObjectAlreadyExists; + case ProtocolError::server_permissions_changed: + return ErrorCodes::SyncServerPermissionsChanged; + case ProtocolError::initial_sync_not_completed: + return ErrorCodes::ConnectionClosed; + case ProtocolError::write_not_allowed: + return ErrorCodes::SyncWriteNotAllowed; + case ProtocolError::compensating_write: + return ErrorCodes::SyncCompensatingWrite; + case ProtocolError::bad_progress: + return ErrorCodes::SyncProtocolInvariantFailed; + case ProtocolError::migrate_to_flx: + [[fallthrough]]; + case ProtocolError::revert_to_pbs: + return ErrorCodes::WrongSyncType; + + case ProtocolError::limits_exceeded: + [[fallthrough]]; + case ProtocolError::token_expired: + [[fallthrough]]; + case ProtocolError::bad_authentication: + [[fallthrough]]; + case ProtocolError::no_such_realm: + [[fallthrough]]; + case ProtocolError::bad_server_file_ident: + [[fallthrough]]; + case ProtocolError::partial_sync_disabled: + [[fallthrough]]; + case ProtocolError::unsupported_session_feature: + [[fallthrough]]; + case ProtocolError::too_many_sessions: + [[fallthrough]]; + case ProtocolError::server_file_deleted: + [[fallthrough]]; + case ProtocolError::client_file_blacklisted: + [[fallthrough]]; + case ProtocolError::user_blacklisted: + [[fallthrough]]; + case ProtocolError::transact_before_upload: + REALM_UNREACHABLE(); + } + return ErrorCodes::UnknownError; + }(); + + if (translated_error_code == ErrorCodes::UnknownError) { + return {ErrorCodes::UnknownError, + util::format("Unknown sync protocol error code %1: %2", static_cast(error_code), msg)}; + } + return {translated_error_code, msg}; } -} // namespace sync -} // namespace realm +} // namespace realm::sync diff --git a/src/realm/sync/protocol.hpp b/src/realm/sync/protocol.hpp index 6d0e25b2b12..0df1a5cbf1d 100644 --- a/src/realm/sync/protocol.hpp +++ b/src/realm/sync/protocol.hpp @@ -263,10 +263,11 @@ struct ProtocolErrorInfo { ClientResetNoRecovery, MigrateToFLX, RevertToPBS, - // The RefreshUser/RefreshLocation actions are currently generated internally when the + // The RefreshUser/RefreshLocation/LogOutUser actions are currently generated internally when the // sync websocket is closed with specific error codes. RefreshUser, RefreshLocation, + LogOutUser, }; ProtocolErrorInfo() = default; @@ -364,33 +365,14 @@ enum class ProtocolError { // clang-format on }; +Status protocol_error_to_status(ProtocolError raw_error_code, std::string_view msg); + constexpr bool is_session_level_error(ProtocolError); /// Returns null if the specified protocol error code is not defined by /// ProtocolError. const char* get_protocol_error_message(int error_code) noexcept; - -Status protocol_error_to_status(ProtocolError error_code, std::string_view msg); - -const std::error_category& protocol_error_category() noexcept; - -std::error_code make_error_code(ProtocolError) noexcept; - -} // namespace sync -} // namespace realm - -namespace std { - -template <> -struct is_error_code_enum { - static const bool value = true; -}; - -} // namespace std - -namespace realm { -namespace sync { - +std::ostream& operator<<(std::ostream&, ProtocolError protocol_error); // Implementation @@ -454,6 +436,8 @@ inline std::ostream& operator<<(std::ostream& o, ProtocolErrorInfo::Action actio return o << "RefreshUser"; case ProtocolErrorInfo::Action::RefreshLocation: return o << "RefreshLocation"; + case ProtocolErrorInfo::Action::LogOutUser: + return o << "LogOutUser"; } return o << "Invalid error action: " << int64_t(action); } diff --git a/src/realm/sync/socket_provider.hpp b/src/realm/sync/socket_provider.hpp index f2c6b3be62f..dcc3f44778b 100644 --- a/src/realm/sync/socket_provider.hpp +++ b/src/realm/sync/socket_provider.hpp @@ -32,6 +32,9 @@ #include namespace realm::sync { +namespace websocket { +enum class WebSocketError; +} struct WebSocketEndpoint; struct WebSocketInterface; diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index e68a9ea9e34..d617b08bc4f 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -700,56 +700,6 @@ TEST_CASE("C API (non-database)", "[c_api]") { }); } - SECTION("realm_sync_error_code") { - using namespace realm::sync; - std::string message; - - Status fake_status(ErrorCodes::SyncProtocolInvariantFailed, "fake sync error"); - realm_sync_error_code_t error = c_api::to_capi(fake_status, message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_CLIENT); - CHECK(error.value == int(ErrorCodes::SyncProtocolInvariantFailed)); - CHECK(fake_status.reason() == error.message); - CHECK(message == error.message); - - std::error_code ec_check; - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == std::generic_category()); - CHECK(ec_check.value() == int(fake_status.code())); - - std::error_code error_code = make_error_code(sync::ProtocolError::connection_closed); - error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_CONNECTION); - - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == realm::sync::protocol_error_category()); - CHECK(ec_check.value() == int(error_code.value())); - - error_code = make_error_code(sync::ProtocolError::session_closed); - error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_SESSION); - - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == realm::sync::protocol_error_category()); - CHECK(ec_check.value() == int(error_code.value())); - - error_code = make_error_code(realm::util::error::basic_system_errors::invalid_argument); - error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_SYSTEM); - - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == std::system_category()); - CHECK(ec_check.value() == int(error_code.value())); - - error_code = make_error_code(util::error::misc_errors::unknown); - error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_UNKNOWN); - - c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == realm::util::error::basic_system_error_category()); - CHECK(ec_check.value() == int(error_code.value())); - } - - #endif // REALM_ENABLE_AUTH_TESTS } @@ -5084,7 +5034,6 @@ struct Userdata { realm_error_t error; realm_thread_safe_reference_t* realm_ref = nullptr; std::string error_message; - std::string error_catagory; }; #if REALM_ENABLE_SYNC @@ -5110,8 +5059,10 @@ static void sync_error_handler(void* p, realm_sync_session_t*, const realm_sync_ { auto userdata_p = static_cast(p); userdata_p->has_error = true; - userdata_p->error_message = error.error_code.message; - userdata_p->error_catagory = error.error_code.category_name; + userdata_p->error_message = error.status.message; + userdata_p->error.error = error.status.error; + userdata_p->error.categories = error.status.categories; + userdata_p->error.message = userdata_p->error_message.c_str(); } TEST_CASE("C API - async_open", "[sync][pbs][c_api]") { @@ -5191,8 +5142,9 @@ TEST_CASE("C API - async_open", "[sync][pbs][c_api]") { }); REQUIRE(userdata.called); REQUIRE(!userdata.realm_ref); - REQUIRE(userdata.error_message == "Bad user authentication (BIND)"); - REQUIRE(userdata.error_catagory == "realm::sync::ProtocolError"); + REQUIRE(userdata.error.error == RLM_ERR_AUTH_ERROR); + REQUIRE(userdata.error_message == + "Unable to refresh the user access token: http error code considered fatal. Client Error: 403"); realm_release(task); realm_release(config); realm_release(sync_config); @@ -5735,9 +5687,7 @@ TEST_CASE("app: flx-sync compensating writes C API support", "[sync][flx][c_api] sync_config, [](realm_userdata_t user_data, realm_sync_session_t*, const realm_sync_error_t error) { auto state = reinterpret_cast(user_data); - REQUIRE(error.error_code.category == RLM_SYNC_ERROR_CATEGORY_SESSION); - REQUIRE(error.error_code.value == RLM_SYNC_ERR_SESSION_COMPENSATING_WRITE); - + REQUIRE(error.status.error == RLM_ERR_SYNC_COMPENSATING_WRITE); REQUIRE(error.compensating_writes_length > 0); std::lock_guard lk(state->mutex); diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 90a0f690fc4..f9134748056 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1193,8 +1193,9 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { task->start([&](auto ref, auto error) { std::lock_guard lock(mutex); REQUIRE(error); - REQUIRE_EXCEPTION(std::rethrow_exception(error), HTTPError, - "http error code considered fatal. Client Error: 403"); + REQUIRE_EXCEPTION( + std::rethrow_exception(error), HTTPError, + "Unable to refresh the user access token: http error code considered fatal. Client Error: 403"); REQUIRE(!ref); called = true; }); diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index b26a0cfe42f..741c39815ab 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -2661,7 +2661,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { SyncTestFile r_config(user1, partition, schema); // Override the default r_config.sync_config->error_handler = [&](std::shared_ptr, SyncError error) { - if (error.get_system_error() == sync::make_error_code(realm::sync::ProtocolError::bad_authentication)) { + if (error.status == ErrorCodes::AuthError) { util::format(std::cerr, "Websocket redirect test: User logged out\n"); std::unique_lock lk(logout_mutex); logged_out = true; @@ -2958,9 +2958,9 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { std::atomic sync_error_handler_called{false}; config.sync_config->error_handler = [&](std::shared_ptr, SyncError error) { sync_error_handler_called.store(true); - REQUIRE(error.get_system_error() == - sync::make_error_code(realm::sync::ProtocolError::bad_authentication)); - REQUIRE(error.status.reason() == "Unable to refresh the user access token."); + REQUIRE(error.status.code() == ErrorCodes::AuthError); + REQUIRE_THAT(std::string{error.status.reason()}, + Catch::Matchers::StartsWith("Unable to refresh the user access token")); }; auto r = Realm::get_shared_realm(config); timed_wait_for([&] { @@ -3054,21 +3054,19 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { user->update_access_token(encode_fake_jwt("fake_access_token")); REQUIRE(!app_session.admin_api.verify_access_token(user->access_token(), app_session.server_app_id)); - std::atomic sync_error_handler_called{false}; - config.sync_config->error_handler = [&](std::shared_ptr, SyncError error) { - std::lock_guard lock(mtx); - sync_error_handler_called.store(true); - REQUIRE(error.get_system_error() == - sync::make_error_code(realm::sync::ProtocolError::bad_authentication)); - REQUIRE(error.status.reason() == "Unable to refresh the user access token."); - }; + auto [sync_error_promise, sync_error] = util::make_promise_future(); + config.sync_config->error_handler = + [promise = util::CopyablePromiseHolder(std::move(sync_error_promise))](std::shared_ptr, + SyncError error) mutable { + promise.get_promise().emplace_value(std::move(error)); + }; auto transport = static_cast(session.transport()); transport->block(); // don't let the token refresh happen until we're ready for it auto r = Realm::get_shared_realm(config); auto session = user->session_for_on_disk_path(config.path); REQUIRE(user->is_logged_in()); - REQUIRE(!sync_error_handler_called.load()); + REQUIRE(!sync_error.is_ready()); { std::atomic called{false}; session->wait_for_upload_completion([&](Status stat) { @@ -3083,9 +3081,11 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { std::lock_guard lock(mtx); REQUIRE(called); } - timed_wait_for([&] { - return sync_error_handler_called.load(); - }); + + auto sync_error_res = wait_for_future(std::move(sync_error)).get(); + REQUIRE(sync_error_res.status == ErrorCodes::AuthError); + REQUIRE_THAT(std::string{sync_error_res.status.reason()}, + Catch::Matchers::StartsWith("Unable to refresh the user access token")); // the failed refresh logs out the user std::lock_guard lock(mtx); @@ -3248,9 +3248,9 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { CreatePolicy::ForceCreate); r->commit_transaction(); } - r->sync_session()->wait_for_upload_completion([&](Status ec) { + r->sync_session()->wait_for_upload_completion([&](Status status) { std::lock_guard lk(mutex); - REQUIRE(!ec.get_std_error_code()); + REQUIRE(status.is_ok()); done = true; }); r->sync_session()->resume(); diff --git a/test/object-store/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index a972b836196..1fcf09c5de4 100644 --- a/test/object-store/sync/client_reset.cpp +++ b/test/object-store/sync/client_reset.cpp @@ -80,8 +80,8 @@ struct StringMaker { if (!value) { return "No SyncError"; } - return realm::util::format("SyncError(%1), is_fatal: %2, with message: '%3'", value->get_system_error(), - value->is_fatal, value->status); + return realm::util::format("SyncError(%1), is_fatal: %2, with message: '%3'", value->status.code_string(), + value->is_fatal, value->status.reason()); } }; } // namespace Catch @@ -133,7 +133,7 @@ TEST_CASE("sync: large reset with recovery is restartable", "[sync][pbs][client SyncTestFile realm_config(app->current_user(), partition.value, schema); realm_config.sync_config->client_resync_mode = ClientResyncMode::Recover; realm_config.sync_config->error_handler = [&](std::shared_ptr, SyncError err) { - if (err.get_system_error() == util::make_error_code(util::MiscExtErrors::end_of_input)) { + if (err.status == ErrorCodes::ConnectionClosed) { return; } @@ -142,7 +142,7 @@ TEST_CASE("sync: large reset with recovery is restartable", "[sync][pbs][client return; } - FAIL(util::format("got error from server: %1: %2", err.get_system_error().value(), err.status)); + FAIL(util::format("got error from server: %1", err.status)); }; auto realm = Realm::get_shared_realm(realm_config); @@ -233,7 +233,7 @@ TEST_CASE("sync: pending client resets are cleared when downloads are complete", return; } - FAIL(util::format("got error from server: %1: %2", err.get_system_error().value(), err.status)); + FAIL(util::format("got error from server: %1", err.status)); }; auto realm = Realm::get_shared_realm(realm_config); @@ -2409,8 +2409,7 @@ struct Remove { util::Optional pk; }; -struct Clear { -}; +struct Clear {}; struct RemoveObject { RemoveObject(std::string_view name, util::Optional key) diff --git a/test/object-store/sync/flx_migration.cpp b/test/object-store/sync/flx_migration.cpp index e5b0f3d5630..c08c694dcff 100644 --- a/test/object-store/sync/flx_migration.cpp +++ b/test/object-store/sync/flx_migration.cpp @@ -239,7 +239,7 @@ TEST_CASE("Test server migration and rollback", "[sync][flx][flx migration][baas }; auto flx_realm = Realm::get_shared_realm(flx_config); auto err = wait_for_future(std::move(err_future), std::chrono::seconds(30)).get(); - REQUIRE(err.get_system_error() == make_error_code(sync::ProtocolError::switch_to_pbs)); + REQUIRE(err.status == ErrorCodes::WrongSyncType); REQUIRE(err.server_requests_action == sync::ProtocolErrorInfo::Action::ApplicationBug); } @@ -699,7 +699,7 @@ TEST_CASE("Update to native FLX after migration", "[sync][flx][flx migration][ba }; auto flx_realm = Realm::get_shared_realm(flx_config); auto err = wait_for_future(std::move(err_future), std::chrono::seconds(30)).get(); - REQUIRE(err.get_system_error() == make_error_code(sync::ProtocolError::switch_to_pbs)); + REQUIRE(err.status == ErrorCodes::WrongSyncType); REQUIRE(err.server_requests_action == sync::ProtocolErrorInfo::Action::ApplicationBug); } } diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index 69310c0f92d..96a3d60d358 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -885,7 +885,7 @@ TEST_CASE("flx: client reset", "[sync][flx][client reset][baas]") { // Client reset fails due to sync client not being able to create the fresh realm. auto sync_error = wait_for_future(std::move(error_future)).get(); - CHECK(sync_error.status == ErrorCodes::AutoClientResetFailed); + REQUIRE(sync_error.status == ErrorCodes::AutoClientResetFailed); // Open the realm again. This should not crash. { @@ -895,7 +895,7 @@ TEST_CASE("flx: client reset", "[sync][flx][client reset][baas]") { auto realm_post_reset = Realm::get_shared_realm(config_copy); auto sync_error = wait_for_future(std::move(err_future)).get(); - CHECK(sync_error.status == ErrorCodes::AutoClientResetFailed); + REQUIRE(sync_error.status == ErrorCodes::AutoClientResetFailed); } } @@ -1302,7 +1302,7 @@ TEST_CASE("flx: uploading an object that is out-of-view results in compensating auto validate_sync_error = [&](const SyncError& sync_error, Mixed expected_pk, const char* expected_object_name, const std::string& error_msg_fragment) { - CHECK(sync_error.get_system_error() == sync::make_error_code(sync::ProtocolError::compensating_write)); + CHECK(sync_error.status == ErrorCodes::SyncCompensatingWrite); CHECK(!sync_error.is_client_reset_requested()); CHECK(sync_error.compensating_writes_info.size() == 1); CHECK(sync_error.server_requests_action == sync::ProtocolErrorInfo::Action::Warning); @@ -2298,7 +2298,7 @@ TEST_CASE("flx: connect to FLX as PBS returns an error", "[sync][flx][baas]") { return static_cast(sync_error); }); - CHECK(sync_error->get_system_error() == make_error_code(sync::ProtocolError::switch_to_flx_sync)); + CHECK(sync_error->status == ErrorCodes::WrongSyncType); CHECK(sync_error->server_requests_action == sync::ProtocolErrorInfo::Action::ApplicationBug); } @@ -2340,8 +2340,7 @@ TEST_CASE("flx: connect to PBS as FLX returns an error", "[sync][flx][protocol][ return static_cast(sync_error); }); - - CHECK(sync_error->get_system_error() == make_error_code(sync::ProtocolError::switch_to_pbs)); + CHECK(sync_error->status == ErrorCodes::WrongSyncType); CHECK(sync_error->server_requests_action == sync::ProtocolErrorInfo::Action::ApplicationBug); } @@ -2824,7 +2823,7 @@ TEST_CASE("flx: data ingest", "[sync][flx][data ingest][baas]") { } else if (error_count == 2) { // Server asking for a client reset. - CHECK(err.get_system_error() == sync::make_error_code(sync::ProtocolError::bad_client_file)); + CHECK(err.status == ErrorCodes::SyncClientResetRequired); CHECK(err.is_client_reset_requested()); promise.get_promise().emplace_value(std::move(err)); } @@ -2966,7 +2965,7 @@ TEST_CASE("flx: send client error", "[sync][flx][baas]") { } else if (error_count == 2) { // Server asking for a client reset. - CHECK(err.get_system_error() == sync::make_error_code(sync::ProtocolError::bad_client_file)); + CHECK(err.status == ErrorCodes::SyncClientResetRequired); CHECK(err.is_client_reset_requested()); promise.get_promise().emplace_value(std::move(err)); } @@ -3484,7 +3483,7 @@ TEST_CASE("flx: compensating write errors get re-sent across sessions", "[sync][ config.sync_config->error_handler = [&](std::shared_ptr, SyncError error) { std::unique_lock lk(errors_mutex); - REQUIRE(error.get_system_error() == make_error_code(sync::ProtocolError::compensating_write)); + REQUIRE(error.status == ErrorCodes::SyncCompensatingWrite); for (const auto& compensating_write : error.compensating_writes_info) { auto tracked_error = std::find_if(error_to_download_version.begin(), error_to_download_version.end(), [&](const auto& pair) { diff --git a/test/object-store/sync/session/session.cpp b/test/object-store/sync/session/session.cpp index d33aa24c8b0..818ca84fe25 100644 --- a/test/object-store/sync/session/session.cpp +++ b/test/object-store/sync/session/session.cpp @@ -400,39 +400,22 @@ TEST_CASE("sync: error handling", "[sync][session]") { }); SECTION("Doesn't treat unknown system errors as being fatal") { - std::error_code code = std::error_code{EBADF, std::generic_category()}; - sync::SessionErrorInfo err{Status{code, "Not a real error message"}, sync::IsFatal{false}}; + sync::SessionErrorInfo err{Status{ErrorCodes::UnknownError, "unknown error"}, true}; err.server_requests_action = ProtocolErrorInfo::Action::Transient; SyncSession::OnlyForTesting::handle_error(*session, std::move(err)); CHECK(!sessions_are_inactive(*session)); } SECTION("Properly handles a client reset error") { - int code = 0; util::Optional final_error; error_handler = [&](auto, SyncError error) { final_error = std::move(error); }; - SECTION("for bad_server_file_ident") { - code = static_cast(ProtocolError::bad_server_file_ident); - } - - SECTION("for bad_client_file_ident") { - code = static_cast(ProtocolError::bad_client_file_ident); - } - - SECTION("for bad_server_version") { - code = static_cast(ProtocolError::bad_server_version); - } - - SECTION("for diverging_histories") { - code = static_cast(ProtocolError::diverging_histories); - } + auto code = GENERATE(ProtocolError::bad_client_file_ident, ProtocolError::bad_server_version, + ProtocolError::diverging_histories); - sync::SessionErrorInfo initial_error{ - Status{std::error_code{code, realm::sync::protocol_error_category()}, "Something bad happened"}, - sync::IsFatal{false}}; + sync::SessionErrorInfo initial_error{sync::protocol_error_to_status(code, "Something bad happened"), true}; initial_error.server_requests_action = ProtocolErrorInfo::Action::ClientReset; std::time_t just_before_raw = std::time(nullptr); SyncSession::OnlyForTesting::handle_error(*session, std::move(initial_error)); diff --git a/test/object-store/sync/session/wait_for_completion.cpp b/test/object-store/sync/session/wait_for_completion.cpp index 1ad20b72e0a..701c6df37b1 100644 --- a/test/object-store/sync/session/wait_for_completion.cpp +++ b/test/object-store/sync/session/wait_for_completion.cpp @@ -99,15 +99,15 @@ TEST_CASE("SyncSession: wait_for_download_completion() API", "[sync][pbs][sessio std::shared_ptr session = sync_session(user, "/async-wait-download-4", [&](auto, auto) { ++error_count; }); + Status err_status(ErrorCodes::SyncProtocolInvariantFailed, "Not a real error message"); // Register the download-completion notification session->wait_for_download_completion([&](Status status) { - REQUIRE(status == ErrorCodes::SyncProtocolInvariantFailed); + REQUIRE(status == err_status); handler_called = true; }); REQUIRE(handler_called == false); // Now trigger an error - sync::SessionErrorInfo err{Status{ErrorCodes::SyncProtocolInvariantFailed, "Not a real error message"}, - sync::IsFatal{false}}; + sync::SessionErrorInfo err{err_status, sync::IsFatal{false}}; err.server_requests_action = sync::ProtocolErrorInfo::Action::ProtocolViolation; SyncSession::OnlyForTesting::handle_error(*session, std::move(err)); EventLoop::main().run_until([&] { diff --git a/test/sync_fixtures.hpp b/test/sync_fixtures.hpp index e1fb6910ad7..2c2919f68f9 100644 --- a/test/sync_fixtures.hpp +++ b/test/sync_fixtures.hpp @@ -581,7 +581,7 @@ class MultiClientServerFixture { } } - using ErrorHandler = void(Status status, bool is_fatal, const std::string& detailed_message); + using ErrorHandler = void(Status status, bool is_fatal); // Set an error handler to be used for all sessions of the specified client // (\a handler will be copied for each session). Must be called before @@ -593,7 +593,7 @@ class MultiClientServerFixture { if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - handler(error_info->status, error_info->is_fatal, error_info->message); + handler(error_info->status, error_info->is_fatal); }; m_connection_state_change_listeners[client_index] = std::move(handler_wrapped); } @@ -712,8 +712,7 @@ class MultiClientServerFixture { return; REALM_ASSERT(error); unit_test::TestContext& test_context = m_test_context; - test_context.logger->error("Client disconnect: %1: %2 (is_fatal=%3)", - error->status.get_std_error_code(), error->message, error->is_fatal); + test_context.logger->error("Client disconnect: %1 (is_fatal=%3)", error->status, error->is_fatal); bool client_error_occurred = true; CHECK_NOT(client_error_occurred); stop(); @@ -1078,7 +1077,7 @@ inline void RealmFixture::setup_error_handler(util::UniqueFunction if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - handler(error_info->status, error_info->is_fatal, error_info->message); + handler(error_info->status, error_info->is_fatal); }; m_session.set_connection_state_change_listener(std::move(listener)); } diff --git a/test/test_client_reset.cpp b/test/test_client_reset.cpp index 34ae8f20210..7619afdbf04 100644 --- a/test/test_client_reset.cpp +++ b/test/test_client_reset.cpp @@ -152,8 +152,9 @@ TEST(ClientReset_NoLocalChanges) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->status.get_std_error_code(); - CHECK_EQUAL(ec, sync::ProtocolError::bad_server_version); + CHECK_EQUAL(error_info->status, ErrorCodes::SyncClientResetRequired); + CHECK_EQUAL(static_cast(error_info->raw_error_code), + ProtocolError::bad_server_version); bowl.add_stone(); }; @@ -601,8 +602,9 @@ TEST(ClientReset_ThreeClients) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->status.get_std_error_code(); - CHECK_EQUAL(ec, sync::ProtocolError::bad_server_version); + CHECK_EQUAL(error_info->status, ErrorCodes::SyncClientResetRequired); + CHECK_EQUAL(static_cast(error_info->raw_error_code), + ProtocolError::bad_server_version); bowl.add_stone(); }; diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 109eb7ef082..ed4dc67c035 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -146,8 +146,7 @@ TEST(Sync_BadVirtualPath) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->status.get_std_error_code(); - CHECK_EQUAL(sync::ProtocolError::illegal_realm_path, ec); + CHECK_EQUAL(error_info->status, ErrorCodes::BadSyncPartitionValue); CHECK(error_info->is_fatal); ++nerrors; if (nerrors == 3) @@ -571,9 +570,7 @@ TEST(Sync_TokenWithoutExpirationAllowed) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->status.get_std_error_code(); - CHECK(ec == sync::ProtocolError::token_expired || ec == sync::ProtocolError::bad_authentication || - ec == sync::ProtocolError::permission_denied); + CHECK_EQUAL(error_info->status, ErrorCodes::SyncPermissionDenied); did_fail = true; fixture.stop(); }; @@ -602,7 +599,7 @@ TEST(Sync_TokenWithNullExpirationAllowed) TEST_DIR(dir); TEST_CLIENT_DB(db); ClientServerFixture fixture(dir, test_context); - auto error_handler = [&](Status, bool, const std::string&) { + auto error_handler = [&](Status, bool) { did_fail = true; fixture.stop(); }; @@ -786,7 +783,7 @@ struct ExpectChangesetError { REALM_ASSERT(error_info); CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset); CHECK(!error_info->is_fatal); - CHECK_EQUAL(error_info->message, + CHECK_EQUAL(error_info->status.reason(), "Failed to transform received changeset: Schema mismatch: " + expected_error); fixture.stop(); } @@ -1461,7 +1458,6 @@ TEST(Sync_Randomized) } } - #ifdef REALM_DEBUG // Failure simulation only works in debug mode TEST(Sync_ReadFailureSimulation) @@ -1475,9 +1471,9 @@ TEST(Sync_ReadFailureSimulation) { ClientServerFixture fixture(server_dir, test_context); fixture.set_client_side_error_rate(1, 1); // 100% chance of failure - auto error_handler = [&](Status status, bool is_fatal, const std::string&) { - auto ec = status.get_std_error_code(); - CHECK_EQUAL(_impl::SimulatedFailure::sync_client__read_head, ec); + auto error_handler = [&](Status status, bool is_fatal) { + CHECK_EQUAL(status, ErrorCodes::RuntimeError); + CHECK_EQUAL(status.reason(), "Simulated failure during sync client websocket read"); CHECK_NOT(is_fatal); client_side_read_did_fail = true; fixture.stop(); @@ -1495,8 +1491,6 @@ TEST(Sync_ReadFailureSimulation) } #endif // REALM_DEBUG - - TEST(Sync_FailingReadsOnClientSide) { TEST_CLIENT_DB(db_1); @@ -1506,10 +1500,12 @@ TEST(Sync_FailingReadsOnClientSide) TEST_DIR(dir); ClientServerFixture fixture{dir, test_context}; fixture.set_client_side_error_rate(5, 100); // 5% chance of failure - auto error_handler = [&](Status status, bool, const std::string&) { - auto ec = status.get_std_error_code(); - if (CHECK_EQUAL(_impl::SimulatedFailure::sync_client__read_head, ec)) + auto error_handler = [&](Status status, bool is_fatal) { + if (CHECK_EQUAL(status.reason(), "Simulated failure during sync client websocket read")) { + CHECK_EQUAL(status, ErrorCodes::RuntimeError); + CHECK_NOT(is_fatal); fixture.cancel_reconnect_delay(); + } }; fixture.set_client_side_error_handler(error_handler); fixture.start(); @@ -1567,7 +1563,7 @@ TEST(Sync_FailingReadsOnServerSide) TEST_DIR(dir); ClientServerFixture fixture{dir, test_context}; fixture.set_server_side_error_rate(5, 100); // 5% chance of failure - auto error_handler = [&](Status, bool is_fatal, const std::string&) { + auto error_handler = [&](Status, bool is_fatal) { CHECK_NOT(is_fatal); fixture.cancel_reconnect_delay(); }; @@ -1646,8 +1642,8 @@ TEST(Sync_ErrorAfterServerRestore_BadClientFileIdent) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](Status status, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::bad_server_version, status.get_std_error_code()); + auto error_handler = [&](Status status, bool is_fatal) { + CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -1856,8 +1852,8 @@ TEST(Sync_ErrorAfterServerRestore_BadServerVersion) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](Status status, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::bad_server_version, status.get_std_error_code()); + auto error_handler = [&](Status status, bool is_fatal) { + CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -1934,8 +1930,8 @@ TEST(Sync_ErrorAfterServerRestore_BadClientVersion) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](Status status, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::bad_client_version, status.get_std_error_code()); + auto error_handler = [&](Status status, bool is_fatal) { + CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -2001,8 +1997,8 @@ TEST(Sync_ErrorAfterServerRestore_BadClientFileIdentSalt) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](Status status, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::diverging_histories, status.get_std_error_code()); + auto error_handler = [&](Status status, bool is_fatal) { + CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -2085,8 +2081,8 @@ TEST(Sync_ErrorAfterServerRestore_BadServerVersionSalt) bool did_fail = false; { ClientServerFixture fixture(server_dir, test_context); - auto error_handler = [&](Status status, bool is_fatal, const std::string&) { - CHECK_EQUAL(ProtocolError::diverging_histories, status.get_std_error_code()); + auto error_handler = [&](Status status, bool is_fatal) { + CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired); CHECK(is_fatal); did_fail = true; fixture.stop(); @@ -2270,8 +2266,8 @@ TEST_IF(Sync_ReadOnlyClient, false) TEST_DIR(server_dir); MultiClientServerFixture fixture(2, 1, server_dir, test_context); bool did_get_permission_denied = false; - fixture.set_client_side_error_handler(1, [&](Status status, bool, const std::string&) { - CHECK_EQUAL(ProtocolError::permission_denied, status.get_std_error_code()); + fixture.set_client_side_error_handler(1, [&](Status status, bool) { + CHECK_EQUAL(status, ErrorCodes::SyncPermissionDenied); did_get_permission_denied = true; fixture.get_client(1).shutdown(); }); @@ -2664,8 +2660,8 @@ TEST(Sync_Permissions) TEST_DIR(server_dir); ClientServerFixture fixture{server_dir, test_context}; - fixture.set_client_side_error_handler([&](Status, bool, const std::string& message) { - CHECK_EQUAL("", message); + fixture.set_client_side_error_handler([&](Status status, bool) { + CHECK_EQUAL("", status.reason()); did_see_error_for_valid = true; }); fixture.start(); @@ -2734,7 +2730,7 @@ TEST(Sync_SSL_Certificate_2) session_config.verify_servers_ssl_certificate = true; session_config.ssl_trust_certificate_path = ca_dir + "dns-chain.crt.pem"; - auto error_handler = [&](Status status, bool, const std::string&) { + auto error_handler = [&](Status status, bool) { CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed); did_fail = true; fixture.stop(); @@ -2885,7 +2881,7 @@ TEST(Sync_SSL_Certificate_Verify_Callback_2) ClientServerFixture fixture{server_dir, test_context, config}; - auto error_handler = [&](Status status, bool, const std::string&) { + auto error_handler = [&](Status status, bool) { CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed); did_fail = true; fixture.stop(); @@ -3882,7 +3878,7 @@ TEST(Sync_CancelReconnectDelay) BowlOfStonesSemaphore bowl; auto handler = [&](const SessionErrorInfo& info) { - if (CHECK_EQUAL(info.status.get_std_error_code(), ProtocolError::connection_closed)) + if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed)) bowl.add_stone(); }; Session session = fixture.make_session(db, "/test"); @@ -3904,7 +3900,7 @@ TEST(Sync_CancelReconnectDelay) BowlOfStonesSemaphore bowl; auto handler = [&](const SessionErrorInfo& info) { - if (CHECK_EQUAL(info.status.get_std_error_code(), ProtocolError::connection_closed)) + if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed)) bowl.add_stone(); }; Session session = fixture.make_session(db, "/test"); @@ -3927,7 +3923,7 @@ TEST(Sync_CancelReconnectDelay) { BowlOfStonesSemaphore bowl; auto handler = [&](const SessionErrorInfo& info) { - if (CHECK_EQUAL(info.status.get_std_error_code(), ProtocolError::connection_closed)) + if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed)) bowl.add_stone(); }; Session session = fixture.make_session(db, "/test"); @@ -3963,7 +3959,7 @@ TEST(Sync_CancelReconnectDelay) BowlOfStonesSemaphore bowl; auto handler = [&](const SessionErrorInfo& info) { - if (CHECK_EQUAL(info.status.get_std_error_code(), ProtocolError::illegal_realm_path)) + if (CHECK_EQUAL(info.status, ErrorCodes::BadSyncPartitionValue)) bowl.add_stone(); }; Session session = fixture.make_session(db, "/.."); @@ -3986,7 +3982,7 @@ TEST(Sync_CancelReconnectDelay) BowlOfStonesSemaphore bowl; auto handler = [&](const SessionErrorInfo& info) { - if (CHECK_EQUAL(info.status.get_std_error_code(), ProtocolError::illegal_realm_path)) + if (CHECK_EQUAL(info.status, ErrorCodes::BadSyncPartitionValue)) bowl.add_stone(); }; Session session = fixture.make_session(db, "/.."); @@ -4491,8 +4487,9 @@ TEST(Sync_PingTimesOut) config.client_pong_timeout = 0; // time out immediately ClientServerFixture fixture(dir, test_context, std::move(config)); - auto error_handler = [&](Status status, bool, const std::string&) { + auto error_handler = [&](Status status, bool) { CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); + CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server"); did_fail = true; fixture.stop(); }; @@ -4519,9 +4516,11 @@ TEST(Sync_ReconnectAfterPingTimeout) ClientServerFixture fixture(dir, test_context, std::move(config)); BowlOfStonesSemaphore bowl; - auto error_handler = [&](Status status, bool, const std::string&) { - if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) + auto error_handler = [&](Status status, bool) { + if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) { + CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server"); bowl.add_stone(); + } }; fixture.set_client_side_error_handler(std::move(error_handler)); fixture.start(); @@ -4543,8 +4542,9 @@ TEST(Sync_UrgentPingIsSent) ClientServerFixture fixture(dir, test_context, std::move(config)); - auto error_handler = [&](Status status, bool, const std::string&) { + auto error_handler = [&](Status status, bool) { CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); + CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server"); did_fail = true; fixture.stop(); }; @@ -4572,7 +4572,7 @@ TEST(Sync_ServerDiscardDeadConnections) ClientServerFixture fixture(dir, test_context, std::move(config)); BowlOfStonesSemaphore bowl; - auto error_handler = [&](Status status, bool, const std::string&) { + auto error_handler = [&](Status status, bool) { CHECK_EQUAL(status, ErrorCodes::ConnectionClosed); bowl.add_stone(); }; @@ -5167,9 +5167,8 @@ TEST_IF(Sync_SSL_Certificates, false) auto listener = [&](ConnectionState state, const util::Optional& error_info) { if (state == ConnectionState::disconnected) { CHECK(error_info); - client_logger->debug( - "State change: disconnected, error_code = %1, is_fatal = %2, detailed_message = %3", - error_info->status.get_std_error_code(), error_info->is_fatal, error_info->message); + client_logger->debug("State change: disconnected, error_code = %1, is_fatal = %2", error_info->status, + error_info->is_fatal); // We expect to get through the SSL handshake but will hit an error due to the wrong token. CHECK_NOT_EQUAL(error_info->status, ErrorCodes::TlsHandshakeFailed); client.shutdown(); @@ -5245,10 +5244,8 @@ TEST(Sync_BadChangeset) if (state != ConnectionState::disconnected) return; REALM_ASSERT(error_info); - std::error_code ec = error_info->status.get_std_error_code(); - bool is_fatal = error_info->is_fatal; - CHECK_EQUAL(sync::ProtocolError::bad_changeset, ec); - CHECK(is_fatal); + CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset); + CHECK(error_info->is_fatal); did_fail = true; fixture.stop(); };