diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api-fwd.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api-fwd.hpp index 77f72a7310..546f3aa270 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api-fwd.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api-fwd.hpp @@ -14,6 +14,8 @@ #pragma once +#include + #include namespace mongocxx { @@ -29,7 +31,7 @@ class server_api; namespace mongocxx { namespace options { -using ::mongocxx::v_noabi::options::server_api; +using v_noabi::options::server_api; } // namespace options } // namespace mongocxx @@ -40,3 +42,6 @@ using ::mongocxx::v_noabi::options::server_api; /// @file /// Declares @ref mongocxx::v_noabi::options::server_api. /// +/// @par Includes +/// - @ref mongocxx/v1/server_api-fwd.hpp +/// diff --git a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api.hpp b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api.hpp index 54cb7439ba..58603670d4 100644 --- a/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api.hpp +++ b/src/mongocxx/include/mongocxx/v_noabi/mongocxx/options/server_api.hpp @@ -14,11 +14,16 @@ #pragma once +#include // IWYU pragma: export + +// + +#include // IWYU pragma: export + #include -#include -#include // IWYU pragma: export -#include +#include // IWYU pragma: keep: backward compatibility, to be removed. +#include // IWYU pragma: keep: backward compatibility, to be removed. #include #include @@ -40,9 +45,7 @@ class server_api { /// /// Enum representing the possible values for server API version. /// - enum class version { - k_version_1, ///< Stable API Version 1. - }; + using version = v1::server_api::version; /// /// Constructs a new server_api object. @@ -55,7 +58,19 @@ class server_api { /// @param version /// The server api version to send to the server. /// - MONGOCXX_ABI_EXPORT_CDECL() server_api(version version); + /* explicit(false) */ server_api(version version) : _version{version} {} + + /// + /// Construct with the @ref mongocxx::v1 equivalent. + /// + /* explicit(false) */ server_api(v1::server_api const& opts) : server_api{opts.get_version()} {} + + /// + /// Convert to the @ref mongocxx::v1 equivalent. + /// + explicit operator v1::server_api() const { + return v1::server_api{_version}; + } /// /// Converts a version enum value to its string value. @@ -93,7 +108,10 @@ class server_api { /// @return /// A reference to this object to facilitate method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(server_api&) strict(bool strict); + server_api& strict(bool strict) { + _strict = strict; + return *this; + } /// /// Gets the current value of the strict option. @@ -101,7 +119,9 @@ class server_api { /// @return /// The optional value of the strict option. /// - MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::optional const&) strict() const; + bsoncxx::v_noabi::stdx::optional const& strict() const { + return _strict; + } /// /// Sets the deprecation errors option, specifying whether the server should @@ -113,7 +133,10 @@ class server_api { /// @return /// A reference to this object to facilitate method chaining. /// - MONGOCXX_ABI_EXPORT_CDECL(server_api&) deprecation_errors(bool deprecation_errors); + server_api& deprecation_errors(bool deprecation_errors) { + _deprecation_errors = deprecation_errors; + return *this; + } /// /// Gets the current value of the deprecation errors option. @@ -121,8 +144,9 @@ class server_api { /// @return /// The optional value of the deprecation errors option. /// - MONGOCXX_ABI_EXPORT_CDECL(bsoncxx::v_noabi::stdx::optional const&) - deprecation_errors() const; + bsoncxx::v_noabi::stdx::optional const& deprecation_errors() const { + return _deprecation_errors; + } /// /// Gets the declared server api version. @@ -130,12 +154,11 @@ class server_api { /// @return /// The version enum value specifying the declared server api version. /// - MONGOCXX_ABI_EXPORT_CDECL(version) get_version() const; + version get_version() const { + return _version; + } private: - friend ::mongocxx::v_noabi::client; - friend ::mongocxx::v_noabi::pool; - version _version; bsoncxx::v_noabi::stdx::optional _strict; bsoncxx::v_noabi::stdx::optional _deprecation_errors; @@ -145,9 +168,32 @@ class server_api { } // namespace v_noabi } // namespace mongocxx +namespace mongocxx { +namespace v_noabi { + +/// +/// Convert to the @ref mongocxx::v_noabi equivalent of `v`. +/// +inline v_noabi::options::server_api from_v1(v1::server_api const& v) { + return {v}; +} + +/// +/// Convert to the @ref mongocxx::v1 equivalent of `v`. +/// +inline v1::server_api to_v1(v_noabi::options::server_api const& v) { + return v1::server_api{v}; +} + +} // namespace v_noabi +} // namespace mongocxx + #include /// /// @file /// Provides @ref mongocxx::v_noabi::options::server_api. /// +/// @par Includes +/// - @ref mongocxx/v1/server_api.hpp +/// diff --git a/src/mongocxx/lib/mongocxx/v1/server_api.cpp b/src/mongocxx/lib/mongocxx/v1/server_api.cpp index db7813ec12..913344db79 100644 --- a/src/mongocxx/lib/mongocxx/v1/server_api.cpp +++ b/src/mongocxx/lib/mongocxx/v1/server_api.cpp @@ -13,3 +13,196 @@ // limitations under the License. #include + +// + +#include +#include + +#include + +#include +#include + +#include + +#include +#include + +namespace mongocxx { +namespace v1 { + +using code = server_api::errc; + +namespace { + +static_assert( + static_cast(server_api::version::k_version_1) == + static_cast(mongoc_server_api_version_t::MONGOC_SERVER_API_V1), + ""); + +server_api::version from_mongoc(mongoc_server_api_version_t v) { + return static_cast(v); +} + +} // namespace + +class server_api::impl { + public: + version _version; + bsoncxx::v1::stdx::optional _strict; + bsoncxx::v1::stdx::optional _deprecation_errors; + + explicit impl(version v) : _version{v} {} + + static impl const& with(server_api const& self) { + return *static_cast(self._impl); + } + + static impl const* with(server_api const* self) { + return static_cast(self->_impl); + } + + static impl& with(server_api& self) { + return *static_cast(self._impl); + } + + static impl* with(server_api* self) { + return static_cast(self->_impl); + } + + static impl* with(void* ptr) { + return static_cast(ptr); + } +}; + +// NOLINTBEGIN(cppcoreguidelines-owning-memory): owning void* for ABI stability. + +server_api::~server_api() { + delete impl::with(this); +} + +server_api::server_api(server_api&& other) noexcept : _impl{exchange(other._impl, nullptr)} {} + +server_api& server_api::operator=(server_api&& other) noexcept { + if (this != &other) { + delete impl::with(exchange(_impl, exchange(other._impl, nullptr))); + } + + return *this; +} + +server_api::server_api(server_api const& other) : _impl{new impl{impl::with(other)}} {} + +server_api& server_api::operator=(server_api const& other) { + if (this != &other) { + delete impl::with(exchange(_impl, new impl{impl::with(other)})); + } + + return *this; +} + +server_api::server_api(version v) : _impl{new impl{v}} {} + +// NOLINTEND(cppcoreguidelines-owning-memory) + +std::string server_api::version_to_string(version v) { + switch (v) { + case version::k_version_1: + return "1"; + default: + throw v1::exception::internal::make(code::invalid_version); + } +} + +server_api::version server_api::version_from_string(bsoncxx::v1::stdx::string_view v) { + mongoc_server_api_version_t ver = {}; + + if (libmongoc::server_api_version_from_string(std::string{v}.c_str(), &ver)) { + return from_mongoc(ver); + } + + throw v1::exception::internal::make(code::invalid_version); +} + +server_api& server_api::strict(bool strict) { + impl::with(this)->_strict = strict; + return *this; +} + +bsoncxx::v1::stdx::optional server_api::strict() const { + return impl::with(this)->_strict; +} + +server_api& server_api::deprecation_errors(bool v) { + impl::with(this)->_deprecation_errors = v; + return *this; +} + +bsoncxx::v1::stdx::optional server_api::deprecation_errors() const { + return impl::with(this)->_deprecation_errors; +} + +server_api::version server_api::get_version() const { + return impl::with(this)->_version; +} + +std::error_category const& server_api::error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "mongocxx::v1::server_api"; + } + + std::string message(int v) const noexcept override { + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::invalid_version: + return "invalid server API version"; + default: + return std::string(this->name()) + ':' + std::to_string(v); + } + } + + bool equivalent(int v, std::error_condition const& ec) const noexcept override { + if (ec.category() == v1::source_error_category()) { + using condition = v1::source_errc; + + auto const source = static_cast(ec.value()); + + switch (static_cast(v)) { + case code::invalid_version: + return source == condition::mongocxx; + + case code::zero: + default: + return false; + } + } + + if (ec.category() == v1::type_error_category()) { + using condition = v1::type_errc; + + auto const type = static_cast(ec.value()); + + switch (static_cast(v)) { + case code::invalid_version: + return type == condition::invalid_argument; + + case code::zero: + default: + return false; + } + } + + return false; + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +} // namespace v1 +} // namespace mongocxx diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.cpp index 934723033e..17d319bd98 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.cpp @@ -16,6 +16,7 @@ // +#include #include #include @@ -23,52 +24,24 @@ #include #include -#include - namespace mongocxx { namespace v_noabi { namespace options { std::string server_api::version_to_string(server_api::version version) { switch (version) { - case server_api::version::k_version_1: + case version::k_version_1: return "1"; default: - throw mongocxx::v_noabi::logic_error{ - mongocxx::v_noabi::error_code::k_invalid_parameter, "invalid server API version"}; + throw v_noabi::logic_error{v_noabi::error_code::k_invalid_parameter, "invalid server API version"}; } } server_api::version server_api::version_from_string(bsoncxx::v_noabi::stdx::string_view version) { if (version == "1") { - return server_api::version::k_version_1; + return version::k_version_1; } - throw mongocxx::v_noabi::logic_error{ - mongocxx::v_noabi::error_code::k_invalid_parameter, "invalid server API version"}; -} - -server_api::server_api(server_api::version version) : _version(std::move(version)) {} - -server_api& server_api::strict(bool strict) { - _strict = strict; - return *this; -} - -bsoncxx::v_noabi::stdx::optional const& server_api::strict() const { - return _strict; -} - -server_api& server_api::deprecation_errors(bool deprecation_errors) { - _deprecation_errors = deprecation_errors; - return *this; -} - -bsoncxx::v_noabi::stdx::optional const& server_api::deprecation_errors() const { - return _deprecation_errors; -} - -server_api::version server_api::get_version() const { - return _version; + throw v_noabi::logic_error{v_noabi::error_code::k_invalid_parameter, "invalid server API version"}; } } // namespace options diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.hh index 457706882e..dade61da37 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/options/server_api.hh @@ -18,9 +18,10 @@ // +#include + #include #include -#include #include @@ -31,7 +32,7 @@ namespace options { using unique_server_api = std::unique_ptr; inline unique_server_api make_server_api(server_api const& opts) { - mongoc_server_api_version_t mongoc_api_version; + mongoc_server_api_version_t mongoc_api_version = {}; // Convert version enum value to std::string then to c_str to create mongoc api version. auto result = libmongoc::server_api_version_from_string( diff --git a/src/mongocxx/test/CMakeLists.txt b/src/mongocxx/test/CMakeLists.txt index e566e4ba61..2b474cc68b 100644 --- a/src/mongocxx/test/CMakeLists.txt +++ b/src/mongocxx/test/CMakeLists.txt @@ -133,6 +133,7 @@ set(mongocxx_test_sources_v1 v1/read_preference.cpp v1/replace_one_options.cpp v1/rewrap_many_datakey_options.cpp + v1/server_api.cpp v1/server_error.cpp v1/tls.cpp v1/transaction_options.cpp diff --git a/src/mongocxx/test/v1/server_api.cpp b/src/mongocxx/test/v1/server_api.cpp new file mode 100644 index 0000000000..2e824a37b7 --- /dev/null +++ b/src/mongocxx/test/v1/server_api.cpp @@ -0,0 +1,219 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +// + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace mongocxx { +namespace v1 { + +using code = server_api::errc; + +TEST_CASE("error code", "[bsoncxx][v1][server_api][error]") { + using mongocxx::v1::source_errc; + using mongocxx::v1::type_errc; + + auto const& category = mongocxx::v1::server_api::error_category(); + CHECK_THAT(category.name(), Catch::Matchers::Equals("mongocxx::v1::server_api")); + + auto const zero_errc = make_error_condition(static_cast(0)); + + SECTION("unknown") { + std::error_code const ec = static_cast(-1); + + CHECK(ec.category() == category); + CHECK(ec.value() == -1); + CHECK(ec); + CHECK(ec.message() == std::string(category.name()) + ":-1"); + } + + SECTION("zero") { + std::error_code const ec = code::zero; + + CHECK(ec.category() == category); + CHECK(ec.value() == 0); + CHECK_FALSE(ec); + CHECK(ec.message() == "zero"); + + CHECK(ec != zero_errc); + CHECK(ec != source_errc::zero); + CHECK(ec != type_errc::zero); + } + + SECTION("non-zero") { + std::error_code const ec = code::invalid_version; + + CHECK(ec.category() == category); + CHECK(ec.value() != static_cast(code::zero)); + CHECK(ec); + CHECK(ec.message() != "zero"); + + CHECK(ec != zero_errc); + CHECK(ec != source_errc::zero); + CHECK(ec != type_errc::zero); + } + + SECTION("source") { + CHECK(make_error_code(code::invalid_version) == source_errc::mongocxx); + } + + SECTION("type") { + CHECK(make_error_code(code::invalid_version) == type_errc::invalid_argument); + } +} + +TEST_CASE("exceptions", "[bsoncxx][v1][server_api]") { + SECTION("version_to_string") { + CHECK_THROWS_WITH_CODE( + server_api::version_to_string(static_cast(1)), code::invalid_version); + } + + SECTION("version_from_string") { + CHECK_THROWS_WITH_CODE(server_api::version_from_string("invalid"), code::invalid_version); + } +} + +TEST_CASE("ownership", "[mongocxx][v1][server_api]") { + server_api source{server_api::version::k_version_1}; + server_api target{static_cast(1)}; + + CHECK(source.get_version() == server_api::version::k_version_1); + CHECK(target.get_version() == static_cast(1)); + + auto const source_value = source.get_version(); + + SECTION("move") { + auto move = std::move(source); + + // source is in an assign-or-move-only state. + + CHECK(move.get_version() == source_value); + + target = std::move(move); + + // source is in an assign-or-move-only state. + + CHECK(target.get_version() == source_value); + } + + SECTION("copy") { + auto copy = source; + + CHECK(source.get_version() == source_value); + CHECK(copy.get_version() == source_value); + + target = copy; + + CHECK(copy.get_version() == source_value); + CHECK(target.get_version() == source_value); + } +} + +TEST_CASE("default", "[mongocxx][v1][server_api]") { + server_api const api{server_api::version::k_version_1}; + + CHECK_FALSE(api.strict().has_value()); + CHECK_FALSE(api.deprecation_errors().has_value()); + CHECK(api.get_version() == server_api::version::k_version_1); +} + +TEST_CASE("version_to_string", "[mongocxx][v1][server_api]") { + using T = server_api::version; + + SECTION("invalid") { + auto const v = GENERATE(values({ + static_cast(INT_MIN), + static_cast(-1), + static_cast(1), + static_cast(INT_MAX), + })); + CAPTURE(v); + + CHECK_THROWS_WITH_CODE(server_api::version_to_string(v), code::invalid_version); + } + + SECTION("valid") { + CHECK(server_api::version_to_string(T::k_version_1) == "1"); + } +} + +TEST_CASE("version_from_string", "[mongocxx][v1][server_api]") { + using T = server_api::version; + + SECTION("invalid") { + auto const v = GENERATE(values({ + "", + "x", + "abc", + "-1", + "0", + "2", + })); + CAPTURE(v); + + CHECK_THROWS_WITH_CODE(server_api::version_from_string(v), code::invalid_version); + } + + SECTION("valid") { + CHECK(server_api::version_from_string("1") == T::k_version_1); + } +} + +TEST_CASE("strict", "[mongocxx][v1][server_api]") { + auto const v = GENERATE(false, true); + server_api api{server_api::version::k_version_1}; + CHECK(api.strict(v).strict() == v); +} + +TEST_CASE("deprecation_errors", "[mongocxx][v1][server_api]") { + auto const v = GENERATE(false, true); + server_api api{server_api::version::k_version_1}; + CHECK(api.deprecation_errors(v).deprecation_errors() == v); +} + +TEST_CASE("get_version", "[mongocxx][v1][server_api]") { + using T = int; + + auto const v = GENERATE(values({ + T{INT_MIN}, + T{-1}, + T{0}, + T{1}, + T{INT_MAX}, + })); + CAPTURE(v); + + auto const version = static_cast(v); + + CHECK(server_api{version}.get_version() == version); +} + +} // namespace v1 +} // namespace mongocxx