From 9fc87c6e1e3f6dc586cbf4f1a38cad65093d5cc4 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 28 Jul 2025 14:40:44 -0500 Subject: [PATCH 01/18] v1: impls --- src/bsoncxx/lib/CMakeLists.txt | 4 + src/bsoncxx/lib/bsoncxx/v1/array/view.cpp | 35 ++ src/bsoncxx/lib/bsoncxx/v1/decimal128.cpp | 104 ++++ src/bsoncxx/lib/bsoncxx/v1/document/value.cpp | 2 + src/bsoncxx/lib/bsoncxx/v1/document/view.cpp | 176 ++++++- src/bsoncxx/lib/bsoncxx/v1/document/view.hh | 33 ++ src/bsoncxx/lib/bsoncxx/v1/element/view.cpp | 320 ++++++++++++- src/bsoncxx/lib/bsoncxx/v1/element/view.hh | 53 ++ src/bsoncxx/lib/bsoncxx/v1/exception.cpp | 65 +++ src/bsoncxx/lib/bsoncxx/v1/oid.cpp | 159 ++++++ src/bsoncxx/lib/bsoncxx/v1/types/id.cpp | 44 ++ src/bsoncxx/lib/bsoncxx/v1/types/value.cpp | 453 +++++++++++++++++- src/bsoncxx/lib/bsoncxx/v1/types/value.hh | 46 ++ src/bsoncxx/lib/bsoncxx/v1/types/view.cpp | 210 +++++++- src/bsoncxx/lib/bsoncxx/v1/types/view.hh | 39 ++ 15 files changed, 1739 insertions(+), 4 deletions(-) create mode 100644 src/bsoncxx/lib/bsoncxx/v1/document/view.hh create mode 100644 src/bsoncxx/lib/bsoncxx/v1/element/view.hh create mode 100644 src/bsoncxx/lib/bsoncxx/v1/types/value.hh create mode 100644 src/bsoncxx/lib/bsoncxx/v1/types/view.hh diff --git a/src/bsoncxx/lib/CMakeLists.txt b/src/bsoncxx/lib/CMakeLists.txt index 6d228cdbda..8bb70e33f8 100644 --- a/src/bsoncxx/lib/CMakeLists.txt +++ b/src/bsoncxx/lib/CMakeLists.txt @@ -115,4 +115,8 @@ set_dist_list(src_bsoncxx_lib_DIST bsoncxx/v_noabi/bsoncxx/types/bson_value/value.hh bsoncxx/v1/config/config.hpp.in bsoncxx/v1/config/version.hpp.in + bsoncxx/v1/document/view.hh + bsoncxx/v1/element/view.hh + bsoncxx/v1/types/value.hh + bsoncxx/v1/types/view.hh ) diff --git a/src/bsoncxx/lib/bsoncxx/v1/array/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/array/view.cpp index 898e9e4a02..7a30b0c1ea 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/array/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/array/view.cpp @@ -16,12 +16,24 @@ // +#include + +#include +#include + +#include + +#include +#include +#include #include namespace bsoncxx { namespace v1 { namespace array { +using code = v1::document::view::errc; + static_assert(is_regular::value, "bsoncxx::v1::array::view must be regular"); static_assert(is_semitrivial::value, "bsoncxx::v1::array::view must be semitrivial"); @@ -30,6 +42,29 @@ static_assert( is_nothrow_moveable::value, "bsoncxx::v1::array::view::const_iterator must be nothrow moveable"); +view::const_iterator view::find(std::uint32_t i) const { + if (!_view) { + return this->cend(); + } + + bsoncxx::itoa key{i}; + + bson_t bson; + + if (!bson_init_static(&bson, _view.data(), _view.length())) { + throw v1::exception{code::invalid_data}; + } + + bson_iter_t iter; + + if (!bson_iter_init_find_w_len(&iter, &bson, key.c_str(), static_cast(key.length()))) { + return this->end(); + } + + return const_iterator::internal::make_const_iterator( + _view.data(), _view.length(), bson_iter_offset(&iter), bson_iter_key_len(&iter)); +} + } // namespace array } // namespace v1 } // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/decimal128.cpp b/src/bsoncxx/lib/bsoncxx/v1/decimal128.cpp index 981a0975be..b7acba6ef4 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/decimal128.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/decimal128.cpp @@ -16,13 +16,117 @@ // +#include + +#include +#include +#include +#include + +#include +#include #include namespace bsoncxx { namespace v1 { +using code = v1::decimal128::errc; + static_assert(is_regular::value, "bsoncxx::v1::decimal128 must be regular"); static_assert(is_semitrivial::value, "bsoncxx::v1::decimal128 must be semitrivial"); +decimal128::decimal128(v1::stdx::string_view sv) { + if (sv.empty()) { + throw v1::exception{code::empty_string}; + } + + if (sv.size() > std::size_t{INT_MAX}) { + throw v1::exception{code::invalid_string_length}; + } + + bson_decimal128_t d128; + + if (!bson_decimal128_from_string_w_len(sv.data(), static_cast(sv.size()), &d128)) { + throw v1::exception{code::invalid_string_data}; + } + + _high = d128.high; + _low = d128.low; +} + +std::string decimal128::to_string() const { + bson_decimal128_t d128; + d128.high = _high; + d128.low = _low; + char str[BSON_DECIMAL128_STRING]; + bson_decimal128_to_string(&d128, str); + return {str}; +} + +std::error_category const& decimal128::error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "bsoncxx::v1::decimal128"; + } + + std::string message(int v) const noexcept override { + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::empty_string: + return "string must not be empty"; + case code::invalid_string_length: + return "length of string is too long (exceeds INT_MAX)"; + case code::invalid_string_data: + return "string is not a valid Decimal128 representation"; + default: + return "unknown: " + 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::empty_string: + case code::invalid_string_length: + case code::invalid_string_data: + return source == condition::bsoncxx; + + 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::empty_string: + case code::invalid_string_length: + case code::invalid_string_data: + return type == condition::invalid_argument; + + case code::zero: + default: + return false; + } + } + + return false; + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + } // namespace v1 } // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/document/value.cpp b/src/bsoncxx/lib/bsoncxx/v1/document/value.cpp index 83db595903..f44f5b0e05 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/document/value.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/document/value.cpp @@ -25,6 +25,8 @@ namespace document { static_assert(is_regular::value, "bsoncxx::v1::document::value must be regular"); static_assert(is_nothrow_moveable::value, "bsoncxx::v1::document::value must be nothrow moveable"); +void value::noop_deleter(std::uint8_t*) { /* noop */ } + } // namespace document } // namespace v1 } // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp index a8c878016d..f2a593e78d 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp @@ -12,16 +12,29 @@ // 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 namespace bsoncxx { namespace v1 { namespace document { +using code = v1::document::view::errc; + static_assert(is_regular::value, "bsoncxx::v1::document::view must be regular"); static_assert(is_semitrivial::value, "bsoncxx::v1::document::view must be semitrivial"); @@ -30,6 +43,167 @@ static_assert( is_nothrow_moveable::value, "bsoncxx::v1::document::view::const_iterator must be nothrow moveable"); +namespace { + +constexpr std::uint8_t k_default_view[5] = {5u, 0u, 0u, 0u, 0u}; + +} // namespace + +view::view() : view{k_default_view} {} + +view::view(std::uint8_t const* data, std::size_t length) : view{data} { + if (length < _empty_length || length < this->size()) { + throw v1::exception{v1::document::view::errc::invalid_length}; + } +} + +view::const_iterator view::cbegin() const { + if (!this->operator bool()) { + return this->cend(); + } + + bson_iter_t iter; + + if (!bson_iter_init_from_data(&iter, _data, this->size())) { + throw v1::exception{code::invalid_data}; + } + + if (!bson_iter_next(&iter)) { + return this->cend(); + } + + return const_iterator::internal::make_const_iterator( + _data, this->size(), bson_iter_offset(&iter), bson_iter_key_len(&iter)); +} + +view::const_iterator view::find(v1::stdx::string_view key) const { + if (!this->operator bool()) { + return this->cend(); + } + + if (key.size() >= std::size_t{INT_MAX}) { + return this->cend(); + } + + // Support null as equivalent to empty. + if (!key.data()) { + key = ""; + } + + bson_t bson; + + if (!bson_init_static(&bson, _data, this->size())) { + throw v1::exception{code::invalid_data}; + } + + bson_iter_t iter; + + if (!bson_iter_init_find_w_len(&iter, &bson, key.data(), static_cast(key.size()))) { + return this->end(); + } + + return const_iterator::internal::make_const_iterator( + _data, this->size(), bson_iter_offset(&iter), bson_iter_key_len(&iter)); +} + +std::error_category const& view::error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "bsoncxx::v1::document::view"; + } + + std::string message(int v) const noexcept override { + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::invalid_length: + return "length is invalid"; + case code::invalid_data: + return "data is invalid"; + default: + return "unknown: " + 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_length: + case code::invalid_data: + return source == condition::bsoncxx; + + 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_length: + return type == condition::invalid_argument; + + case code::invalid_data: + return type == condition::runtime_error; + + case code::zero: + default: + return false; + } + } + + return false; + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +view::const_iterator& view::const_iterator::operator++() { + if (!_element) { + return *this; + } + + auto iter_opt = to_bson_iter(_element); + if (!iter_opt) { + throw v1::exception{code::invalid_data}; + } + auto& iter = *iter_opt; + + if (bson_iter_next(&iter)) { + _element = v1::element::view::internal::make( + _element.raw(), _element.length(), bson_iter_offset(&iter), bson_iter_key_len(&iter)); + } else { + _element = {}; + } + + return *this; +} + +view::const_iterator::const_iterator(v1::element::view element) : _element(element) {} + +view::const_iterator view::const_iterator::internal::make_const_iterator( + std::uint8_t const* raw, + std::size_t length, + std::uint32_t offset, + std::uint32_t keylen) { + return const_iterator{v1::element::view::internal::make( + raw, + static_cast(length), // Guarded by `bson_init_static`. + offset, + keylen)}; +} + } // namespace document } // namespace v1 } // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/document/view.hh b/src/bsoncxx/lib/bsoncxx/v1/document/view.hh new file mode 100644 index 0000000000..55554536ae --- /dev/null +++ b/src/bsoncxx/lib/bsoncxx/v1/document/view.hh @@ -0,0 +1,33 @@ +// 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 + +namespace bsoncxx { +namespace v1 { +namespace document { + +class view::const_iterator::internal { + public: + static const_iterator + make_const_iterator(std::uint8_t const* raw, std::size_t length, std::uint32_t offset, std::uint32_t keylen); +}; + +} // namespace document +} // namespace v1 +} // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp index 80d306f98d..b5841b7537 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp @@ -12,19 +12,337 @@ // 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 namespace bsoncxx { namespace v1 { namespace element { +using code = v1::element::view::errc; + static_assert(is_semiregular::value, "bsoncxx::v1::element::view must be semiregular"); static_assert(is_nothrow_moveable::value, "bsoncxx::v1::element::view must be nothrow moveable"); +class alignas(BSONCXX_PRIVATE_MAX_ALIGN_T) view::impl { + private: + enum : std::size_t { + _padding_size = sizeof(view::_storage) // Total reserved. + - sizeof(void*) // _raw + - 3u * sizeof(std::uint32_t) // _length, _offset, and _keylen. + - 1u // _is_valid (final byte). + - 0u, + }; + + // `_padding_size == 3` given `sizeof(void*) == 8`. + static_assert(_padding_size < sizeof(view::_storage), "sizeof(impl) must not exceed reserved storage size"); + + std::uint8_t const* _raw = {}; + std::uint32_t _length = {}; + std::uint32_t _offset = {}; + std::uint32_t _keylen = {}; + + BSONCXX_PRIVATE_WARNINGS_PUSH(); + BSONCXX_PRIVATE_WARNINGS_DISABLE(GNU("-Wunused")); + unsigned char _padding[_padding_size]; // Reserved. + BSONCXX_PRIVATE_WARNINGS_POP(); + + bool _is_valid = {}; // Last byte. + + public: + impl() = default; + + impl( + std::uint8_t const* raw, + std::uint32_t length, + std::uint32_t offset, + std::uint32_t keylen, + bool is_valid = true) + : _raw{raw}, _length{length}, _offset{offset}, _keylen{keylen}, _is_valid{is_valid} {} + + void check() const; + + std::uint8_t const* raw() const { + return _raw; + } + + std::uint32_t length() const { + return _length; + } + + std::uint32_t offset() const { + return _offset; + } + + std::uint32_t keylen() const { + return _keylen; + } + + bool is_valid() const { + return _raw && _is_valid; + } + + impl to_invalid() const { + auto ret = *this; + ret._is_valid = false; + return ret; + } + + v1::types::id type_id() const { + auto const iter = this->iter(); + return static_cast(bson_iter_type(&iter)); + } + + v1::types::id type_id_unchecked() const { + if (auto const iter_opt = this->iter_unchecked()) { + return static_cast(bson_iter_type(&*iter_opt)); + } + return v1::types::id{}; // BSON_TYPE_EOD + } + + v1::stdx::optional iter_unchecked() const { + bson_iter_t iter; + if (bson_iter_init_from_data_at_offset(&iter, _raw, _length, _offset, _keylen)) { + return iter; + } + return v1::stdx::nullopt; + } + + bson_iter_t iter() const { + this->check(); + if (auto iter_opt = this->iter_unchecked()) { + return *iter_opt; + } + throw v1::exception{code::invalid_data}; + } + + v1::types::view type_view() const { + this->check(); + return this->type_view_unchecked(); + } + + v1::types::view type_view_unchecked() const { + if (auto opt = v1::types::view::internal::make(_raw, _length, _offset, _keylen)) { + return *opt; + } + throw v1::exception{code::invalid_data}; + } + + v1::types::value type_value() const { + this->check(); + if (auto opt = v1::types::value::internal::make(_raw, _length, _offset, _keylen)) { + return *opt; + } + throw v1::exception{code::invalid_data}; + } +}; + +void view::impl::check() const { + static_assert(is_semiregular::value, "bsoncxx::v1::element::view::impl must be semiregular"); + static_assert(is_semitrivial::value, "bsoncxx::v1::element::view::impl must be semitrivial"); + + static_assert(sizeof(view::_storage) >= sizeof(view::impl), "insufficient size"); + static_assert(alignof(view) >= alignof(view::impl), "insufficient alignment"); + + if (!this->is_valid()) { + bson_iter_t iter; + if (_raw && bson_iter_init_from_data_at_offset(&iter, _raw, _length, _offset, _keylen)) { + std::string msg; + msg += "last known element key \""; + msg += bson_iter_key(&iter); + msg += '"'; + throw v1::exception{code::invalid_view, msg}; + } else { + throw v1::exception{code::invalid_view}; + } + } +} + +view::~view() = default; + +view::view(view const& other) noexcept { + new (internal::impl(this)) impl{internal::impl(other)}; +} + +view& view::operator=(view const& other) noexcept { + *internal::impl(this) = internal::impl(other); + return *this; +} + +view::view() { + new (_storage.data()) impl{}; +} + +view::view(impl i) { + new (_storage.data()) impl{i}; +} + +view::operator bool() const { + return internal::impl(this)->is_valid(); +} + +std::uint8_t const* view::raw() const { + return internal::impl(this)->raw(); +} + +std::uint32_t view::length() const { + return internal::impl(this)->length(); +} + +std::uint32_t view::offset() const { + return internal::impl(this)->offset(); +} + +std::uint32_t view::keylen() const { + return internal::impl(this)->keylen(); +} + +v1::types::id view::type_id() const { + return internal::impl(this)->type_id(); +} + +v1::stdx::string_view view::key() const { + auto const iter = internal::impl(this)->iter(); + return bson_iter_key(&iter); +} + +#pragma push_macro("X") +#undef X +#define X(_name, _value) \ + v1::types::b_##_name view::get_##_name() const { \ + return internal::impl(this)->type_view().get_##_name(); \ + } +BSONCXX_V1_TYPES_XMACRO(X) +#pragma pop_macro("X") + +v1::types::view view::type_view() const { + return internal::impl(this)->type_view(); +} + +v1::types::value view::type_value() const { + return internal::impl(this)->type_value(); +} + +v1::element::view view::operator[](v1::stdx::string_view key) const { + auto& impl = internal::impl(*this); + if (!impl.is_valid() || impl.type_id_unchecked() != v1::types::id::k_document) { + return v1::element::view{impl.to_invalid()}; + } + return impl.type_view_unchecked().get_document().value[key]; +} + +v1::element::view view::operator[](std::uint32_t idx) const { + auto& impl = internal::impl(*this); + if (!impl.is_valid() || impl.type_id_unchecked() != v1::types::id::k_array) { + return view{impl.to_invalid()}; + } + return impl.type_view_unchecked().get_array().value[idx]; +} + +std::error_category const& view::error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "bsoncxx::v1::element::view"; + } + + std::string message(int v) const noexcept override { + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::invalid_view: + return "view is invalid"; + case code::invalid_data: + return "data is invalid"; + default: + return "unknown: " + 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_view: + case code::invalid_data: + return source == condition::bsoncxx; + + case code::zero: + default: + return false; + } + } + + if (ec.category() == v1::type_error_category()) { + using condition = v1::type_errc; + + auto const source = static_cast(ec.value()); + + switch (static_cast(v)) { + case code::invalid_view: + case code::invalid_data: + return source == condition::runtime_error; + + case code::zero: + default: + return false; + } + } + + return false; + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +view view::internal::make( + std::uint8_t const* raw, + std::uint32_t length, + std::uint32_t offset, + std::uint32_t keylen, + bool is_valid) { + return view{view::impl{raw, length, offset, keylen, is_valid}}; +} + +v1::stdx::optional view::internal::to_bson_iter(view const& v) { + return internal::impl(v).iter_unchecked(); +} + +view::impl const& view::internal::impl(view const& self) { + return *reinterpret_cast(self._storage.data()); +} + +view::impl const* view::internal::impl(view const* self) { + return reinterpret_cast(self->_storage.data()); +} + +view::impl* view::internal::impl(view* self) { + return reinterpret_cast(self->_storage.data()); +} + +v1::stdx::optional to_bson_iter(view const& v) { + return view::internal::to_bson_iter(v); +} + } // namespace element } // namespace v1 } // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/element/view.hh b/src/bsoncxx/lib/bsoncxx/v1/element/view.hh new file mode 100644 index 0000000000..95315a5b5b --- /dev/null +++ b/src/bsoncxx/lib/bsoncxx/v1/element/view.hh @@ -0,0 +1,53 @@ +// 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 + +namespace bsoncxx { +namespace v1 { +namespace element { + +class view::internal { + public: + static view make( + std::uint8_t const* raw, + std::uint32_t length, + std::uint32_t offset, + std::uint32_t keylen, + bool is_valid = true); + + static v1::stdx::optional to_bson_iter(view const& v); + + private: + friend view; + + static view::impl const& impl(view const& v); + + static view::impl const* impl(view const* v); + static view::impl* impl(view* v); +}; + +v1::stdx::optional to_bson_iter(view const& v); + +} // namespace element +} // namespace v1 +} // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/exception.cpp b/src/bsoncxx/lib/bsoncxx/v1/exception.cpp index 07900b8c77..ccc25765f7 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/exception.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/exception.cpp @@ -16,6 +16,71 @@ // +#include + +#include +#include + +namespace bsoncxx { +namespace v1 { + +std::error_category const& source_error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "bsoncxx::v1::source_errc"; + } + + std::string message(int e) const noexcept override { + using code = v1::source_errc; + + switch (static_cast(e)) { + case code::zero: + return "zero"; + case code::bsoncxx: + return "bsoncxx"; + case code::bson: + return "bson"; + default: + return "unknown: " + std::to_string(e); + } + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +std::error_category const& type_error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "bsoncxx::v1::type_errc"; + } + + std::string message(int e) const noexcept override { + using code = v1::type_errc; + + switch (static_cast(e)) { + case code::zero: + return "zero"; + case code::invalid_argument: + return "invalid argument"; + case code::runtime_error: + return "runtime error"; + default: + return "unknown: " + std::to_string(e); + } + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +} // namespace v1 +} // namespace bsoncxx + namespace bsoncxx { namespace v1 { diff --git a/src/bsoncxx/lib/bsoncxx/v1/oid.cpp b/src/bsoncxx/lib/bsoncxx/v1/oid.cpp index 9bf5da8ef0..47e0cd9c0e 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/oid.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/oid.cpp @@ -16,13 +16,172 @@ // +#include + +#include +#include + +#include // via +#include #include namespace bsoncxx { namespace v1 { +using code = v1::oid::errc; + static_assert(is_regular::value, "bsoncxx::v1::oid must be regular"); static_assert(is_semitrivial::value, "bsoncxx::v1::oid must be semitrivial"); +constexpr std::size_t oid::k_oid_length; + +oid::oid() { + // Ensure the Winsock DLL is initialized prior to calling `gethostname` in `bsoncxx::v1::oid::oid()`: + // - bson_oid_init -> bson_context_get_default -> ... -> _bson_context_init_random -> gethostname. + struct WSAGuard { +#if defined(_WIN32) + ~WSAGuard() { + (void)WSACleanup(); + } + + WSAGuard(WSAGuard&&) = delete; + WSAGuard& operator=(WSAGuard&) = delete; + WSAGuard(WSAGuard const&) = delete; + WSAGuard& operator=(WSAGuard const&) = delete; + + WSAGuard() { + WSADATA wsaData; + if (WSAStartup((MAKEWORD(2, 2)), &wsaData) != 0) { + throw v1::exception{ + WSAGetLastError(), std::system_category(), "WSAStartup() failed in bsoncxx::v1::oid::oid()"}; + } + } +#endif + }; + + bson_oid_t oid; + ((void)WSAGuard{}, bson_oid_init(&oid, nullptr)); + std::memcpy(_bytes.data(), oid.bytes, sizeof(oid.bytes)); +} + +oid::oid(std::uint8_t const* bytes, std::size_t len) { + if (!bytes) { + throw v1::exception{code::null_bytes_ptr}; + } + + if (len != this->size()) { + throw v1::exception{code::invalid_length}; + } + + std::memcpy(_bytes.data(), bytes, _bytes.size()); +} + +oid::oid(v1::stdx::string_view str) { + if (str.empty()) { + throw v1::exception{code::empty_string}; + } + + if (!bson_oid_is_valid(str.data(), str.size())) { + throw v1::exception{code::invalid_string}; + } + + bson_oid_t oid; + bson_oid_init_from_string(&oid, str.data()); + std::memcpy(_bytes.data(), oid.bytes, _bytes.size()); +} + +std::string oid::to_string() const { + bson_oid_t oid; + std::memcpy(oid.bytes, _bytes.data(), sizeof(oid.bytes)); + char str[25]; + bson_oid_to_string(&oid, str); + return std::string(str); +} + +std::time_t oid::get_time_t() const { + bson_oid_t oid; + std::memcpy(oid.bytes, _bytes.data(), sizeof(oid.bytes)); + return bson_oid_get_time_t(&oid); +} + +int oid::compare(oid const& other) const { + bson_oid_t lhs_oid; + bson_oid_t rhs_oid; + + std::memcpy(lhs_oid.bytes, this->bytes(), k_oid_length); + std::memcpy(rhs_oid.bytes, other.bytes(), k_oid_length); + + return bson_oid_compare(&lhs_oid, &rhs_oid); +} + +std::error_category const& oid::error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "bsoncxx::v1::oid"; + } + + std::string message(int v) const noexcept override { + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::null_bytes_ptr: + return "bytes pointer must not be null"; + case code::invalid_length: + return "byte length must equal k_oid_length"; + case code::empty_string: + return "string must not be empty"; + case code::invalid_string: + return "string is not a valid ObjectID representation"; + default: + return "unknown: " + 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::null_bytes_ptr: + case code::invalid_length: + case code::empty_string: + case code::invalid_string: + return source == condition::bsoncxx; + + 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::null_bytes_ptr: + case code::invalid_length: + case code::empty_string: + case code::invalid_string: + return type == condition::invalid_argument; + + case code::zero: + default: + return false; + } + } + + return false; + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + } // namespace v1 } // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/id.cpp b/src/bsoncxx/lib/bsoncxx/v1/types/id.cpp index 0f882b4d78..c133cdebd2 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/types/id.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/types/id.cpp @@ -13,3 +13,47 @@ // limitations under the License. #include + +// + +#include + +namespace bsoncxx { +namespace v1 { +namespace types { + +std::string to_string(id rhs) { +#pragma push_macro("X") +#undef X +#define X(_name, _value) \ + case id::k_##_name: \ + return #_name; + + switch (rhs) { + BSONCXX_V1_TYPES_XMACRO(X) + + default: + return "?"; + } +#pragma pop_macro("X") +} + +std::string to_string(binary_subtype rhs) { +#pragma push_macro("X") +#undef X +#define X(_name, _value) \ + case binary_subtype::k_##_name: \ + return #_name; + + switch (rhs) { + BSONCXX_V1_BINARY_SUBTYPES_XMACRO(X) + + default: + return "?"; + } +#pragma pop_macro("X") +} + +} // namespace types +} // namespace v1 +} // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp b/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp index 8dd7dc8c97..a70626b3ef 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp @@ -12,19 +12,470 @@ // 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 +#include +#include + +#include +#include +#include #include namespace bsoncxx { namespace v1 { namespace types { +using code = v1::types::value::errc; + static_assert(is_regular::value, "bsoncxx::v1::types::value must be regular"); static_assert(is_nothrow_moveable::value, "bsoncxx::v1::types::value must be nothrow moveable"); +class alignas(BSONCXX_PRIVATE_MAX_ALIGN_T) value::impl { + friend value::internal; + + public: + enum : std::size_t { + _padding_size = sizeof(value::_storage) // Total reserved. + - sizeof(bson_value_t) // _value. + - 0u, + }; + + // `_padding_size == 0` given `sizeof(void*) == 8`. + static_assert(_padding_size < sizeof(value::_storage), "sizeof(impl) must not exceed reserved storage size"); + + bson_value_t _value = {}; // BSON_TYPE_EOD + + ~impl(); + + impl(impl&& other) noexcept { + _value = other._value; // Ownership transfer. + other._value = {BSON_TYPE_NULL, {}, {}}; + } + + impl& operator=(impl&& other) noexcept { + if (&other != this) { + bson_value_destroy(&_value); + _value = other._value; // Ownership transfer. + other._value = {BSON_TYPE_NULL, {}, {}}; + } + return *this; + } + + impl(impl const& other) { + bson_value_copy(&other._value, &_value); + } + + impl& operator=(impl const& other) { + if (&other != this) { + bson_value_destroy(&_value); + bson_value_copy(&other._value, &_value); + } + return *this; + } + + impl() = default; + + auto t() const -> decltype((_value.value_type)) { + return _value.value_type; + } + + auto t() -> decltype((_value.value_type)) { + return _value.value_type; + } + + auto v() const -> decltype((_value.value)) { + return _value.value; + } + + auto v() -> decltype((_value.value)) { + return _value.value; + } +}; + +value::impl::~impl() { + static_assert(is_semiregular::value, "bsoncxx::v1::types::value::impl must be semiregular"); + static_assert(is_nothrow_moveable::value, "bsoncxx::v1::types::value::impl must be nothrow moveable"); + + static_assert(sizeof(value::_storage) >= sizeof(value::impl), "insufficient size"); + static_assert(alignof(value) >= alignof(value::impl), "insufficient alignment"); + + bson_value_destroy(&_value); +} + +value::~value() { + internal::impl(this)->~impl(); +} + +value::value(value&& other) noexcept { + new (internal::impl(this)) impl{std::move(internal::impl(other))}; +} + +value& value::operator=(value&& other) noexcept { + *internal::impl(this) = std::move(internal::impl(other)); + return *this; +} + +value::value(value const& other) { + new (internal::impl(this)) impl{internal::impl(other)}; +} + +value& value::operator=(value const& other) { + *internal::impl(this) = internal::impl(other); + return *this; +} + +namespace { + +// For backward compatibility, do not prematurely truncate strings in bsoncxx API. Instead, defer handling of potential +// embedded null bytes to the bson library. +char* to_bson_copy(v1::stdx::string_view sv) { + if (sv.empty()) { + return nullptr; + } + + auto ret = static_cast(bson_malloc(sv.size() + 1u)); + std::memcpy(ret, sv.data(), sv.size()); + ret[sv.size()] = '\0'; + return ret; +} + +std::uint8_t* to_bson_copy(void const* data, std::size_t size) { + if (!data) { + return nullptr; + } + + auto ret = static_cast(bson_malloc(size)); + std::memcpy(ret, data, size); + return ret; +} + +} // namespace + +value::value(v1::types::view const& v) : value{} { +#pragma push_macro("X") +#undef X +#define X(_name, _value) \ + case v1::types::id::k_##_name: \ + *this = value{v.get_##_name()}; \ + return; + + switch (v.type_id()) { + BSONCXX_V1_TYPES_XMACRO(X) + + default: + throw v1::exception{code::invalid_type}; + } +#pragma pop_macro("X") +} + +// BSONCXX_V1_TYPES_XMACRO: update below. + +value::value(v1::types::b_double const v) : value{} { + internal::impl(this)->t() = BSON_TYPE_DOUBLE; + internal::impl(this)->v().v_double = v; +} + +value::value(v1::types::b_string const v) : value{} { + if (v.value.size() > UINT32_MAX) { + throw v1::exception{code::invalid_length_u32}; + } + auto const len = static_cast(v.value.size()); + + internal::impl(this)->t() = BSON_TYPE_UTF8; + + auto& v_utf8 = internal::impl(this)->v().v_utf8; + + v_utf8.str = to_bson_copy(v.value); + v_utf8.len = len; +} + +value::value(v1::types::b_document const v) : value{} { + // Range is guaranteed by bsoncxx::v1::document::view::raw_size(). + auto const data_len = static_cast(v.value.size()); + + internal::impl(this)->t() = BSON_TYPE_DOCUMENT; + + auto& v_doc = internal::impl(this)->v().v_doc; + + v_doc.data = to_bson_copy(v.value.data(), v.value.size()); + v_doc.data_len = data_len; +} + +value::value(v1::types::b_array const v) : value{} { + // Range is guaranteed by bsoncxx::v1::document::view::raw_size(). + auto const data_len = static_cast(v.value.size()); + + internal::impl(this)->t() = BSON_TYPE_ARRAY; + + auto& v_doc = internal::impl(this)->v().v_doc; + + v_doc.data = to_bson_copy(v.value.data(), v.value.size()); + v_doc.data_len = data_len; +} + +value::value(v1::types::b_binary const v) : value{} { + internal::impl(this)->t() = BSON_TYPE_BINARY; + + auto& v_binary = internal::impl(this)->v().v_binary; + + v_binary.subtype = static_cast(v.subtype); + v_binary.data = to_bson_copy(v.bytes, v.size); + v_binary.data_len = v.size; +} + +value::value(v1::types::b_undefined) : value{} { + internal::impl(this)->t() = BSON_TYPE_UNDEFINED; +} + +value::value(v1::types::b_oid const v) : value{} { + internal::impl(this)->t() = BSON_TYPE_OID; + std::memcpy(internal::impl(this)->v().v_oid.bytes, v.value.bytes(), v.value.size()); +} + +value::value(v1::types::b_bool const v) : value{} { + internal::impl(this)->t() = BSON_TYPE_BOOL; + internal::impl(this)->v().v_bool = v.value; +} + +value::value(v1::types::b_date const v) : value{} { + internal::impl(this)->t() = BSON_TYPE_DATE_TIME; + internal::impl(this)->v().v_datetime = v.value.count(); +} + +value::value(v1::types::b_null) { + (new (internal::impl(this)) impl{})->t() = BSON_TYPE_NULL; +} + +value::value(v1::types::b_regex const v) : value{} { + internal::impl(this)->t() = BSON_TYPE_REGEX; + + auto& v_regex = internal::impl(this)->v().v_regex; + + v_regex.regex = to_bson_copy(v.regex); + v_regex.options = v.options.empty() ? nullptr : to_bson_copy(v.options); +} + +value::value(v1::types::b_dbpointer const v) : value{} { + if (v.collection.size() > UINT32_MAX) { + throw v1::exception{code::invalid_length_u32}; + } + auto const collection_len = static_cast(v.collection.size()); + + internal::impl(this)->t() = BSON_TYPE_DBPOINTER; + + auto& v_dbpointer = internal::impl(this)->v().v_dbpointer; + + v_dbpointer.collection = to_bson_copy(v.collection); + v_dbpointer.collection_len = collection_len; + std::memcpy(v_dbpointer.oid.bytes, v.value.bytes(), v.value.size()); +} + +value::value(v1::types::b_code const v) : value{} { + if (v.code.size() > UINT32_MAX) { + throw v1::exception{code::invalid_length_u32}; + } + auto const code_len = static_cast(v.code.size()); + + internal::impl(this)->t() = BSON_TYPE_CODE; + + auto& v_code = internal::impl(this)->v().v_code; + + v_code.code = to_bson_copy(v.code); + v_code.code_len = code_len; +} + +value::value(v1::types::b_symbol const v) : value{} { + if (v.symbol.size() > UINT32_MAX) { + throw v1::exception{code::invalid_length_u32}; + } + auto const len = static_cast(v.symbol.size()); + + internal::impl(this)->t() = BSON_TYPE_SYMBOL; + + auto& v_symbol = internal::impl(this)->v().v_symbol; + + v_symbol.symbol = to_bson_copy(v.symbol); + v_symbol.len = len; +} + +value::value(v1::types::b_codewscope const v) : value{} { + if (v.code.size() > UINT32_MAX) { + throw v1::exception{code::invalid_length_u32}; + } + auto const code_len = static_cast(v.code.size()); + + // Range is guaranteed by bsoncxx::v1::document::view::raw_size(). + auto const scope_len = static_cast(v.scope.size()); + + internal::impl(this)->t() = BSON_TYPE_CODEWSCOPE; + + auto& v_codewscope = internal::impl(this)->v().v_codewscope; + + v_codewscope.code = to_bson_copy(v.code); + v_codewscope.code_len = code_len; + v_codewscope.scope_data = to_bson_copy(v.scope.data(), v.scope.size()); + v_codewscope.scope_len = scope_len; +} + +value::value(v1::types::b_int32 const v) : value{} { + internal::impl(this)->t() = BSON_TYPE_INT32; + internal::impl(this)->v().v_int32 = v.value; +} + +value::value(v1::types::b_timestamp const v) : value{} { + internal::impl(this)->t() = BSON_TYPE_TIMESTAMP; + + auto& v_timestamp = internal::impl(this)->v().v_timestamp; + + v_timestamp.timestamp = v.timestamp; + v_timestamp.increment = v.increment; +} + +value::value(v1::types::b_int64 const v) : value{} { + internal::impl(this)->t() = BSON_TYPE_INT64; + internal::impl(this)->v().v_int64 = v.value; +} + +value::value(v1::types::b_decimal128 const v) : value{} { + internal::impl(this)->t() = BSON_TYPE_DECIMAL128; + + auto& v_decimal128 = internal::impl(this)->v().v_decimal128; + + v_decimal128.high = v.value.high(); + v_decimal128.low = v.value.low(); +} + +value::value(v1::types::b_maxkey) : value{} { + internal::impl(this)->t() = BSON_TYPE_MAXKEY; +} + +value::value(v1::types::b_minkey) : value{} { + internal::impl(this)->t() = BSON_TYPE_MINKEY; +} + +// BSONCXX_V1_TYPES_XMACRO: update above. + +value::value(std::uint8_t const* data, std::size_t size, v1::types::binary_subtype const subtype) : value{} { + if (size > UINT32_MAX) { + throw v1::exception{code::invalid_length_u32}; + } + + *this = value{v1::types::b_binary{subtype, static_cast(size), data}}; +} + +v1::types::id value::type_id() const { + return static_cast(internal::impl(this)->t()); +} + +v1::types::view value::view() const { + return v1::types::view::internal::make(internal::impl(this)->_value); +} + +std::error_category const& value::error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "bsoncxx::v1::types::value"; + } + + std::string message(int v) const noexcept override { + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::invalid_type: + return "requested BSON type is not supported"; + case code::invalid_length_u32: + return "length is too long (exceeds UINT32_MAX)"; + default: + return "unknown: " + 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_type: + case code::invalid_length_u32: + return source == condition::bsoncxx; + + case code::zero: + default: + return false; + } + } + + if (ec.category() == v1::type_error_category()) { + using condition = v1::type_errc; + + auto const source = static_cast(ec.value()); + + switch (static_cast(v)) { + case code::invalid_type: + case code::invalid_length_u32: + return source == condition::invalid_argument; + + case code::zero: + default: + return false; + } + } + + return false; + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +v1::stdx::optional +value::internal::make(std::uint8_t const* raw, std::uint32_t length, std::uint32_t offset, std::uint32_t keylen) { + bson_iter_t iter; + + if (!bson_iter_init_from_data_at_offset(&iter, raw, length, offset, keylen)) { + return v1::stdx::nullopt; + } + + value ret; + bson_value_copy(bson_iter_value(&iter), &internal::impl(ret)._value); + return ret; +} + +value::impl const& value::internal::impl(value const& self) { + return *reinterpret_cast(self._storage.data()); +} + +value::impl const* value::internal::impl(value const* self) { + return reinterpret_cast(self->_storage.data()); +} + +value::impl& value::internal::impl(value& self) { + return *reinterpret_cast(self._storage.data()); +} + +value::impl* value::internal::impl(value* self) { + return reinterpret_cast(self->_storage.data()); +} + } // namespace types } // namespace v1 } // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/value.hh b/src/bsoncxx/lib/bsoncxx/v1/types/value.hh new file mode 100644 index 0000000000..a131b43e27 --- /dev/null +++ b/src/bsoncxx/lib/bsoncxx/v1/types/value.hh @@ -0,0 +1,46 @@ +// 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 + +namespace bsoncxx { +namespace v1 { +namespace types { + +class value::internal { + public: + static v1::stdx::optional + make(std::uint8_t const* raw, std::uint32_t length, std::uint32_t offset, std::uint32_t keylen); + + private: + friend value; + + static value::impl const& impl(value const& self); + static value::impl const* impl(value const* self); + + static value::impl& impl(value& self); + static value::impl* impl(value* self); +}; + +} // namespace types +} // namespace v1 +} // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp index cce4161d2f..df70e1cdce 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp @@ -12,16 +12,35 @@ // 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 +#include +#include +#include + +#include +#include #include namespace bsoncxx { namespace v1 { namespace types { +using code = v1::types::view::errc; + #pragma push_macro("X") #undef X #define X(_name, _value) \ @@ -34,6 +53,195 @@ BSONCXX_V1_TYPES_XMACRO(X) static_assert(is_regular::value, "bsoncxx::v1::types::view must be regular"); static_assert(is_semitrivial::value, "bsoncxx::v1::types::view must be semitrivial"); +#pragma push_macro("X") +#undef X +#define X(_name, _value) constexpr id b_##_name::type_id; +BSONCXX_V1_TYPES_XMACRO(X) +#pragma pop_macro("X") + +#pragma push_macro("X") +#undef X +#define X(_name, _value) \ + b_##_name view::get_##_name() const { \ + if (_id != id::k_##_name) { \ + throw v1::exception{code::type_mismatch}; \ + } \ + return _b_##_name; \ + } + +BSONCXX_V1_TYPES_XMACRO(X) +#pragma pop_macro("X") + +std::error_category const& view::error_category() { + class type final : public std::error_category { + char const* name() const noexcept override { + return "bsoncxx::v1::types::view"; + } + + std::string message(int v) const noexcept override { + switch (static_cast(v)) { + case code::zero: + return "zero"; + case code::type_mismatch: + return "requested type does not match the underlying type"; + default: + return "unknown: " + 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::type_mismatch: + return source == condition::bsoncxx; + + case code::zero: + default: + return false; + } + } + + if (ec.category() == v1::type_error_category()) { + using condition = v1::type_errc; + + auto const source = static_cast(ec.value()); + + switch (static_cast(v)) { + case code::type_mismatch: + return source == condition::runtime_error; + + case code::zero: + default: + return false; + } + } + + return false; + } + }; + + static bsoncxx::immortal const instance; + + return instance.value(); +} + +namespace { + +v1::stdx::string_view to_sv(char const* data, std::size_t size) { + if (!data || size == 0u) { + return {}; + } + + return {data, size}; +} + +v1::stdx::string_view to_sv(char const* data) { + if (!data) { + return {}; + } + + return to_sv(data, std::strlen(data)); +} + +} // namespace + +view view::internal::make(bson_value_t const& v) { + // BSONCXX_V1_TYPES_XMACRO: update below. + switch (static_cast(v.value_type)) { + case v1::types::id::k_double: + return v1::types::b_double{v.value.v_double}; + + case v1::types::id::k_string: + return b_string{to_sv(v.value.v_utf8.str, v.value.v_utf8.len)}; + + case v1::types::id::k_document: + return v1::types::b_document{v1::document::view{v.value.v_doc.data}}; + + case v1::types::id::k_array: + return v1::types::b_array{v1::array::view(v.value.v_doc.data)}; + + case v1::types::id::k_binary: + return v1::types::b_binary{ + static_cast(v.value.v_binary.subtype), + v.value.v_binary.data_len, + v.value.v_binary.data}; + + case v1::types::id::k_undefined: + return v1::types::b_undefined{}; + + case v1::types::id::k_oid: + return v1::types::b_oid{v1::oid{v.value.v_oid.bytes, sizeof(v.value.v_oid.bytes)}}; + + case v1::types::id::k_bool: + return v1::types::b_bool{v.value.v_bool}; + + case v1::types::id::k_date: + return v1::types::b_date{std::chrono::milliseconds{v.value.v_datetime}}; + + case v1::types::id::k_null: + return v1::types::b_null{}; + + case v1::types::id::k_regex: + return v1::types::b_regex{to_sv(v.value.v_regex.regex), to_sv(v.value.v_regex.options)}; + + case v1::types::id::k_dbpointer: + return v1::types::b_dbpointer{ + to_sv(v.value.v_dbpointer.collection, v.value.v_dbpointer.collection_len), + v1::oid{v.value.v_dbpointer.oid.bytes, sizeof(v.value.v_dbpointer.oid.bytes)}}; + + case v1::types::id::k_code: + return v1::types::b_code{v1::stdx::string_view{v.value.v_code.code, v.value.v_code.code_len}}; + + case v1::types::id::k_symbol: + return v1::types::b_symbol{v1::stdx::string_view{v.value.v_symbol.symbol, v.value.v_symbol.len}}; + + case v1::types::id::k_codewscope: + return v1::types::b_codewscope{ + to_sv(v.value.v_codewscope.code, v.value.v_codewscope.code_len), + v1::document::view{v.value.v_codewscope.scope_data}}; + + case v1::types::id::k_int32: + return v1::types::b_int32{v.value.v_int32}; + + case v1::types::id::k_timestamp: + return v1::types::b_timestamp{v.value.v_timestamp.increment, v.value.v_timestamp.timestamp}; + + case v1::types::id::k_int64: + return v1::types::b_int64{v.value.v_int64}; + + case v1::types::id::k_decimal128: + return v1::types::b_decimal128{v1::decimal128{v.value.v_decimal128.high, v.value.v_decimal128.low}}; + + case v1::types::id::k_maxkey: + return v1::types::b_maxkey{}; + + case v1::types::id::k_minkey: + return v1::types::b_minkey{}; + } + // BSONCXX_V1_TYPES_XMACRO: update above. + + BSONCXX_PRIVATE_UNREACHABLE; +} + +v1::stdx::optional +view::internal::make(std::uint8_t const* raw, std::uint32_t length, std::uint32_t offset, std::uint32_t keylen) { + bson_iter_t iter; + + if (!bson_iter_init_from_data_at_offset(&iter, raw, length, offset, keylen)) { + return v1::stdx::nullopt; + } + + if (auto const value = bson_iter_value(&iter)) { + return make(*value); + } + + return v1::stdx::nullopt; +} + } // namespace types } // namespace v1 } // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/view.hh b/src/bsoncxx/lib/bsoncxx/v1/types/view.hh new file mode 100644 index 0000000000..4988c9038f --- /dev/null +++ b/src/bsoncxx/lib/bsoncxx/v1/types/view.hh @@ -0,0 +1,39 @@ +// 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 + +namespace bsoncxx { +namespace v1 { +namespace types { + +class view::internal { + public: + static view make(bson_value_t const& v); + + static v1::stdx::optional + make(std::uint8_t const* raw, std::uint32_t length, std::uint32_t offset, std::uint32_t keylen); +}; + +} // namespace types +} // namespace v1 +} // namespace bsoncxx From b034251230e9bed32d8afb85a8368b734c1c9622 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Mon, 28 Jul 2025 14:40:47 -0500 Subject: [PATCH 02/18] v1: tests --- src/bsoncxx/lib/bsoncxx/v1/types/view.cpp | 4 + src/bsoncxx/lib/bsoncxx/v1/types/view.hh | 3 + src/bsoncxx/test/CMakeLists.txt | 11 + src/bsoncxx/test/v1/array/value.cpp | 579 ++++++++++++++ src/bsoncxx/test/v1/array/value.hh | 26 + src/bsoncxx/test/v1/array/view.cpp | 382 +++++++++ src/bsoncxx/test/v1/array/view.hh | 30 + src/bsoncxx/test/v1/decimal128.cpp | 159 ++++ src/bsoncxx/test/v1/decimal128.hh | 39 + src/bsoncxx/test/v1/detail/bit.hh | 27 + src/bsoncxx/test/v1/document/value.cpp | 747 ++++++++++++++++++ src/bsoncxx/test/v1/document/value.hh | 26 + src/bsoncxx/test/v1/document/view.cpp | 452 +++++++++++ src/bsoncxx/test/v1/document/view.hh | 41 + src/bsoncxx/test/v1/element/view.cpp | 308 ++++++++ src/bsoncxx/test/v1/element/view.hh | 26 + src/bsoncxx/test/v1/exception.cpp | 58 ++ src/bsoncxx/test/v1/exception.hh | 33 + src/bsoncxx/test/v1/oid.cpp | 192 +++++ src/bsoncxx/test/v1/oid.hh | 40 + src/bsoncxx/test/v1/stdx/optional.test.cpp | 3 +- src/bsoncxx/test/v1/stdx/string_view.test.cpp | 3 +- src/bsoncxx/test/v1/types/id.cpp | 110 +++ src/bsoncxx/test/v1/types/id.hh | 35 + src/bsoncxx/test/v1/types/value.cpp | 594 ++++++++++++++ src/bsoncxx/test/v1/types/value.hh | 31 + src/bsoncxx/test/v1/types/view.cpp | 520 ++++++++++++ src/bsoncxx/test/v1/types/view.hh | 199 +++++ 28 files changed, 4676 insertions(+), 2 deletions(-) create mode 100644 src/bsoncxx/test/v1/array/value.cpp create mode 100644 src/bsoncxx/test/v1/array/value.hh create mode 100644 src/bsoncxx/test/v1/array/view.cpp create mode 100644 src/bsoncxx/test/v1/array/view.hh create mode 100644 src/bsoncxx/test/v1/decimal128.cpp create mode 100644 src/bsoncxx/test/v1/decimal128.hh create mode 100644 src/bsoncxx/test/v1/detail/bit.hh create mode 100644 src/bsoncxx/test/v1/document/value.cpp create mode 100644 src/bsoncxx/test/v1/document/value.hh create mode 100644 src/bsoncxx/test/v1/document/view.cpp create mode 100644 src/bsoncxx/test/v1/document/view.hh create mode 100644 src/bsoncxx/test/v1/element/view.cpp create mode 100644 src/bsoncxx/test/v1/element/view.hh create mode 100644 src/bsoncxx/test/v1/exception.cpp create mode 100644 src/bsoncxx/test/v1/exception.hh create mode 100644 src/bsoncxx/test/v1/oid.cpp create mode 100644 src/bsoncxx/test/v1/oid.hh create mode 100644 src/bsoncxx/test/v1/types/id.cpp create mode 100644 src/bsoncxx/test/v1/types/id.hh create mode 100644 src/bsoncxx/test/v1/types/value.cpp create mode 100644 src/bsoncxx/test/v1/types/value.hh create mode 100644 src/bsoncxx/test/v1/types/view.cpp create mode 100644 src/bsoncxx/test/v1/types/view.hh diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp index df70e1cdce..41785b42be 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp @@ -242,6 +242,10 @@ view::internal::make(std::uint8_t const* raw, std::uint32_t length, std::uint32_ return v1::stdx::nullopt; } +void view::internal::type_id(view& v, v1::types::id id) { + v._id = id; +} + } // namespace types } // namespace v1 } // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/view.hh b/src/bsoncxx/lib/bsoncxx/v1/types/view.hh index 4988c9038f..aaecd116a2 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/types/view.hh +++ b/src/bsoncxx/lib/bsoncxx/v1/types/view.hh @@ -21,6 +21,7 @@ #include #include +#include namespace bsoncxx { namespace v1 { @@ -32,6 +33,8 @@ class view::internal { static v1::stdx::optional make(std::uint8_t const* raw, std::uint32_t length, std::uint32_t offset, std::uint32_t keylen); + + static BSONCXX_ABI_EXPORT_CDECL_TESTING(void) type_id(view& v, v1::types::id id); }; } // namespace types diff --git a/src/bsoncxx/test/CMakeLists.txt b/src/bsoncxx/test/CMakeLists.txt index d5c0d75852..42e6e9f2d6 100644 --- a/src/bsoncxx/test/CMakeLists.txt +++ b/src/bsoncxx/test/CMakeLists.txt @@ -48,9 +48,20 @@ set(bsoncxx_test_sources_v_noabi ) set(bsoncxx_test_sources_v1 + v1/array/value.cpp + v1/array/view.cpp + v1/decimal128.cpp + v1/document/value.cpp + v1/document/view.cpp + v1/element/view.cpp + v1/exception.cpp + v1/oid.cpp v1/stdx/optional.test.cpp v1/stdx/string_view.test.cpp v1/stdx/type_traits.test.cpp + v1/types/id.cpp + v1/types/value.cpp + v1/types/view.cpp ) set(bsoncxx_test_sources diff --git a/src/bsoncxx/test/v1/array/value.cpp b/src/bsoncxx/test/v1/array/value.cpp new file mode 100644 index 0000000000..8ff6a2ceee --- /dev/null +++ b/src/bsoncxx/test/v1/array/value.cpp @@ -0,0 +1,579 @@ +// 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 + +namespace { + +using bsoncxx::v1::array::value; +using bsoncxx::v1::array::view; + +template +bsoncxx::v1::stdx::optional get_deleter(T const& v) { + if (auto const deleter_ptr = v.get_deleter().template target()) { + return *deleter_ptr; + } + return {}; +} + +TEST_CASE("exceptions", "[bsoncxx][v1][array][value]") { + using code = bsoncxx::v1::document::view::errc; + + std::uint8_t const data[] = {5, 0, 0, 0, 0}; // {} + + REQUIRE(data[0] == sizeof(data)); + + auto const make_ptr = [&data] { + auto res = std::unique_ptr(new std::uint8_t[sizeof(data)]); + std::memcpy(res.get(), data, sizeof(data)); + return res; + }; + + SECTION("invalid_length") { + SECTION("value(std::uint8_t*, std::size_t, Deleter)") { + auto const deleter = [](std::uint8_t* p) { delete[] p; }; + using deleter_type = std::reference_wrapper; + + auto const expr = [&](std::size_t length) { + return value{make_ptr().release(), length, std::ref(deleter)}; + }; + + CHECK_THROWS_WITH_CODE(expr(0u), code::invalid_length); + CHECK_THROWS_WITH_CODE(expr(sizeof(data) - 1u), code::invalid_length); + + CHECK(get_deleter(expr(sizeof(data))).has_value()); + CHECK(get_deleter(expr(sizeof(data) + 1u)).has_value()); + + auto ptr = make_ptr(); + ++ptr[0]; + CHECK_THROWS_WITH_CODE((value{ptr.release(), sizeof(data), std::ref(deleter)}), code::invalid_length); + } + + SECTION("value(std::uint8_t*, std::size_t)") { + auto const expr = [&](std::size_t length) { return value{make_ptr().release(), length}; }; + + CHECK_THROWS_WITH_CODE(expr(0), code::invalid_length); + CHECK_THROWS_WITH_CODE(expr(sizeof(data) - 1u), code::invalid_length); + + CHECK(get_deleter(expr(sizeof(data))).has_value()); + CHECK(get_deleter(expr(sizeof(data) + 1u)).has_value()); + + auto ptr = make_ptr(); + ++ptr[0]; + CHECK_THROWS_WITH_CODE((value{ptr.release(), sizeof(data)}), code::invalid_length); + } + + SECTION("value(unique_ptr_type, std::size_t)") { + auto const expr = [&](std::size_t length) { return value{make_ptr(), length}; }; + + CHECK_THROWS_WITH_CODE(expr(0), code::invalid_length); + CHECK_THROWS_WITH_CODE(expr(sizeof(data) - 1u), code::invalid_length); + + CHECK(get_deleter(expr(sizeof(data))).has_value()); + CHECK(get_deleter(expr(sizeof(data) + 1u)).has_value()); + + auto ptr = make_ptr(); + ++ptr[0]; + CHECK_THROWS_WITH_CODE((value{std::move(ptr), sizeof(data)}), code::invalid_length); + } + } +} + +TEST_CASE("ownership", "[bsoncxx][v1][array][value]") { + using default_deleter_type = value::default_deleter_type; + using noop_deleter_type = bsoncxx::v1::document::value::noop_deleter_type; + + bsoncxx::v1::document::view const empty; + + auto const noop_deleter = &bsoncxx::v1::document::value::noop_deleter; + + std::uint8_t const xdoc[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // { 'x': 1 } + + auto xdoc_owner = std::unique_ptr(new std::uint8_t[sizeof(xdoc)]); + std::memcpy(xdoc_owner.get(), xdoc, sizeof(xdoc)); + + SECTION("invalid") { + value v{nullptr}; + + REQUIRE(v.data() == nullptr); + REQUIRE(get_deleter(v).has_value()); + + SECTION("move") { + auto const move = std::move(v); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(move.data() == nullptr); + CHECK(get_deleter(move).has_value()); + } + + SECTION("copy") { + auto const copy = v; + + CHECK(v.data() == nullptr); + CHECK(get_deleter(v).has_value()); + + CHECK(copy.data() == nullptr); + CHECK(get_deleter(v).has_value()); + } + + SECTION("release") { + auto const ptr = v.release(); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(ptr.get() == nullptr); + REQUIRE(get_deleter(ptr).has_value()); + } + + SECTION("reset with value") { + auto const data_ptr = xdoc_owner.get(); + + v.reset(value{std::move(xdoc_owner)}); + + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + } + + SECTION("reset with view") { + auto const data_ptr = xdoc; + + v.reset(view{xdoc}); + + CHECK(v.data() != nullptr); + CHECK(v.data() != data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == view{xdoc}); + } + } + + SECTION("default") { + value v; + + REQUIRE(v.data() == empty.data()); + REQUIRE(get_deleter(v) == noop_deleter); + + SECTION("release") { + auto const ptr = v.release(); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(ptr.get() == empty.data()); + CHECK(get_deleter(ptr) == noop_deleter); + } + + SECTION("move") { + auto const move = std::move(v); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(move.data() == empty.data()); + CHECK(get_deleter(move) == noop_deleter); + } + + SECTION("copy") { + auto const copy = v; + + CHECK(v.data() == empty.data()); + CHECK(get_deleter(v) == noop_deleter); + + CHECK(copy.data() == empty.data()); + CHECK(get_deleter(copy) == noop_deleter); + } + + SECTION("reset with value") { + auto const data_ptr = xdoc_owner.get(); + + v.reset(value{std::move(xdoc_owner)}); + + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + } + + SECTION("reset with view") { + auto const data_ptr = xdoc; + + v = view{xdoc}; + + CHECK(v.data() != data_ptr); + CHECK(get_deleter(v).has_value()); + } + } + + SECTION("owning") { + auto const owner_ptr = xdoc_owner.get(); + + value v{std::move(xdoc_owner)}; + + REQUIRE(v.data() == owner_ptr); + REQUIRE(get_deleter(v).has_value()); + + SECTION("release") { + auto const ptr = v.release(); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(ptr.get() == owner_ptr); + CHECK(get_deleter(ptr).has_value()); + } + + SECTION("move") { + auto move = std::move(v); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(move.data() == owner_ptr); + CHECK(get_deleter(move).has_value()); + + v = std::move(move); + + CHECK(move.data() == nullptr); + CHECK_NOFAIL(!move.get_deleter()); // Valid but unspecified state. + + CHECK(v.data() == owner_ptr); + CHECK(get_deleter(v).has_value()); + } + + SECTION("copy") { + auto const copy = v; + + CHECK(v.data() == owner_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(copy.data() != nullptr); + CHECK(copy.data() != owner_ptr); + CHECK(get_deleter(copy).has_value()); + + CHECK(copy == view{xdoc}); + + auto const copy_ptr = copy.data(); + + v = copy; + + CHECK(copy.data() == copy_ptr); + CHECK(get_deleter(copy).has_value()); + + CHECK(v.data() != nullptr); + CHECK(v.data() != copy_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == copy); + } + + SECTION("reset with value") { + auto copy = v; + auto const data_ptr = copy.data(); + + REQUIRE(v.data() != data_ptr); + + v.reset(std::move(copy)); + + CHECK(v.data() != owner_ptr); + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == view{xdoc}); + } + + SECTION("reset with view") { + auto const data_ptr = xdoc; + + REQUIRE(v.data() != data_ptr); + + v.reset(view{xdoc}); + + CHECK(v.data() != owner_ptr); + CHECK(v.data() != data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == view{xdoc}); + } + } + + SECTION("custom deleter") { + std::uint8_t bytes[] = {5, 0, 0, 0, 0}; // {} + auto const bytes_ptr = bytes; + auto const deleter = [bytes_ptr](std::uint8_t* p) { CHECK(p == bytes_ptr); }; + using deleter_type = std::reference_wrapper; + + value v{bytes, std::ref(deleter)}; + auto const data_ptr = v.data(); + + REQUIRE(data_ptr == bytes_ptr); + REQUIRE(get_deleter(v).has_value()); + + SECTION("release") { + auto const ptr = v.release(); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(ptr.get() == data_ptr); + CHECK(get_deleter(ptr).has_value()); + } + + SECTION("move") { + auto move = std::move(v); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(move.data() == data_ptr); + CHECK(get_deleter(move).has_value()); + + v = std::move(move); + + CHECK(move.data() == nullptr); + CHECK_NOFAIL(!move.get_deleter()); // Valid but unspecified state. + + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + } + + SECTION("copy") { + auto const copy = v; + + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(copy.data() != nullptr); + CHECK(copy.data() != data_ptr); + CHECK(get_deleter(copy).has_value()); + + CHECK(copy == view{bytes}); + + auto const copy_ptr = copy.data(); + + v = copy; + + CHECK(copy.data() == copy_ptr); + CHECK(get_deleter(copy).has_value()); + + CHECK(v.data() != nullptr); + CHECK(v.data() != copy_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == copy); + } + + SECTION("reset with value") { + auto copy = v; + auto const data_ptr = copy.data(); + + REQUIRE(v.data() != data_ptr); + + v.reset(std::move(copy)); + + CHECK(v.data() != bytes_ptr); + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == view{bytes}); + } + + SECTION("reset with view") { + auto const data_ptr = xdoc; + + REQUIRE(v.data() != data_ptr); + + v.reset(view{xdoc}); + + CHECK(v.data() != bytes_ptr); + CHECK(v.data() != data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == view{xdoc}); + } + } +} + +TEST_CASE("basic", "[bsoncxx][v1][array][value]") { + auto const invalid = value{nullptr}; + + SECTION("invalid") { + REQUIRE_FALSE(invalid); + + CHECK(invalid.data() == nullptr); + CHECK(invalid.size() == 0u); + CHECK_FALSE(invalid.empty()); + + CHECK(invalid.begin() == invalid.begin()); + CHECK(invalid.end() == invalid.end()); + CHECK(invalid.begin() == invalid.end()); + + CHECK(invalid.find(0) == invalid.end()); + CHECK_FALSE(invalid[0]); + + CHECK(invalid == invalid); + CHECK_FALSE(invalid != invalid); + CHECK(invalid != view{}); + CHECK(invalid == value{invalid}); + } + + SECTION("default") { + value const v; + + REQUIRE(v); + + CHECK(v.data() == value{}.data()); + CHECK(v.size() == 5u); + CHECK(v.empty()); + + CHECK(v.begin() == v.begin()); + CHECK(v.end() == v.end()); + CHECK(v.begin() == v.end()); + + CHECK(v.find(0) == v.end()); + CHECK_FALSE(v[0]); + + CHECK(v == v); + CHECK_FALSE(v != v); + CHECK(v == view{}); + CHECK(v == value{v}); + + CHECK_FALSE(v == invalid); + CHECK(v != invalid); + CHECK(v.begin() == invalid.begin()); + CHECK(v.end() == invalid.end()); + } + + SECTION("valid") { + std::uint8_t const xdoc[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // { 'x': 1 } + + auto xdoc_owner = std::unique_ptr(new std::uint8_t[sizeof(xdoc)]); + std::memcpy(xdoc_owner.get(), xdoc, sizeof(xdoc)); + auto const data_ptr = xdoc_owner.get(); + + value const v{std::move(xdoc_owner)}; + + REQUIRE(v); + + CHECK(v != value{}); + + CHECK(v.data() == data_ptr); + CHECK(v.size() == sizeof(xdoc)); + CHECK(v.size() == v.length()); + CHECK_FALSE(v.empty()); + + CHECK(v.begin() == v.begin()); + CHECK(v.begin() == v.cbegin()); + CHECK(v.end() == v.end()); + CHECK(v.end() == v.cend()); + CHECK(v.begin() != v.end()); + + CHECK(v == v); + CHECK_FALSE(v != v); + CHECK(v != view{}); + CHECK(v == value{v}); + + CHECK_FALSE(v == invalid); + CHECK(v != invalid); + CHECK(v.begin() != invalid.begin()); + CHECK(v.end() == invalid.end()); + } +} + +TEST_CASE("StringMaker", "[bsoncxx][test][v1][array][value]") { + SECTION("invalid") { + value doc{nullptr}; + + REQUIRE_FALSE(static_cast(doc)); + + CHECK(bsoncxx::test::stringify(doc) == "invalid"); + } + + SECTION("empty") { + value doc; + + REQUIRE(static_cast(doc)); + + CHECK(bsoncxx::test::stringify(doc) == "[]"); + } + + SECTION("one") { + // {"0": 1} + std::uint8_t const bytes[] = { + // clang-format off + 12, 0, 0, 0, + 16, '0', '\0', 1, 0, 0, 0, // "0": 1 + 0, + // clang-format on + }; + auto ptr = new std::uint8_t[sizeof(bytes)]; + std::memcpy(ptr, bytes, sizeof(bytes)); + + value doc{ptr}; + + REQUIRE(static_cast(doc)); + + CHECK(bsoncxx::test::stringify(doc) == R"([1])"); + } + + SECTION("two") { + // {"x": 1, "y": 2} + std::uint8_t const bytes[] = { + // clang-format off + 19, 0, 0, 0, + 16, '0', '\0', 1, 0, 0, 0, // "0": 1 + 16, '1', '\0', 2, 0, 0, 0, // "1": 2 + 0, + // clang-format on + }; + auto ptr = new std::uint8_t[sizeof(bytes)]; + std::memcpy(ptr, bytes, sizeof(bytes)); + + value doc{ptr}; + + REQUIRE(static_cast(doc)); + + CHECK(bsoncxx::test::stringify(doc) == R"([1, 2])"); + } + + SECTION("three") { + // {"x": 1, "y": 2, "z": 3} + std::uint8_t bytes[] = { + // clang-format off + 26, 0, 0, 0, + 16, '0', '\0', 1, 0, 0, 0, // "0": 1 + 16, '1', '\0', 2, 0, 0, 0, // "1": 2 + 16, '2', '\0', 3, 0, 0, 0, // "2": 3 + 0, + // clang-format on + }; + auto ptr = new std::uint8_t[sizeof(bytes)]; + std::memcpy(ptr, bytes, sizeof(bytes)); + + value doc{ptr}; + + REQUIRE(static_cast(doc)); + + CHECK(bsoncxx::test::stringify(doc) == R"([1, 2, 3])"); + } +} + +} // namespace diff --git a/src/bsoncxx/test/v1/array/value.hh b/src/bsoncxx/test/v1/array/value.hh new file mode 100644 index 0000000000..c2161be241 --- /dev/null +++ b/src/bsoncxx/test/v1/array/value.hh @@ -0,0 +1,26 @@ +// 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. + +#pragma once + +#include + +// + +#include // StringMaker + +#include + +template <> +struct Catch::StringMaker : StringMaker {}; diff --git a/src/bsoncxx/test/v1/array/view.cpp b/src/bsoncxx/test/v1/array/view.cpp new file mode 100644 index 0000000000..d330c66c5f --- /dev/null +++ b/src/bsoncxx/test/v1/array/view.cpp @@ -0,0 +1,382 @@ +// 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 + +namespace { + +using bsoncxx::v1::array::view; +using code = bsoncxx::v1::document::view::errc; + +TEST_CASE("exceptions", "[bsoncxx][v1][array][view]") { + SECTION("invalid_view") { + view v{nullptr}; + + REQUIRE_NOTHROW(v.end()); + REQUIRE_NOTHROW(v[0]); + + CHECK(v.begin() == v.end()); + CHECK(v.find(0) == v.end()); + } + + SECTION("invalid_length") { + std::uint8_t data[] = {5, 0, 0, 0, 0}; // {} + auto const data_ptr = data; + + REQUIRE(data[0] == sizeof(data)); + + auto const expr = [&data](std::size_t length) { return view{data, length}; }; + + CHECK_THROWS_WITH_CODE(expr(0), code::invalid_length); + CHECK_THROWS_WITH_CODE(expr(sizeof(data) - 1u), code::invalid_length); + + CHECK(expr(sizeof(data)).data() == data_ptr); + CHECK(expr(sizeof(data) + 1u).data() == data_ptr); + + ++data[0]; + + CHECK_THROWS_WITH_CODE((view{data, sizeof(data)}), code::invalid_length); + } + + SECTION("invalid_data") { + SECTION("view") { + std::uint8_t const data[] = {5u, 0u, 0u, 0u, 1u}; + view v{data}; + + REQUIRE_NOTHROW(v.end()); + + CHECK_THROWS_WITH_CODE(v.begin(), code::invalid_data); + CHECK_THROWS_WITH_CODE(v.find(0), code::invalid_data); + } + + SECTION("const_iterator") { + std::uint8_t data[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // { 'x': 1 } + view const v{data}; + auto iter = v.begin(); + REQUIRE(iter != v.end()); + + std::memset(data, 0u, sizeof(data)); + + REQUIRE_NOTHROW(v.end()); + + CHECK_THROWS_WITH_CODE(++iter, code::invalid_data); + } + } +} + +TEST_CASE("basic", "[bsoncxx][v1][array][view]") { + auto const invalid = view{nullptr}; + auto const empty_key = bsoncxx::v1::stdx::string_view{}; + + SECTION("invalid") { + REQUIRE_FALSE(invalid); + + CHECK(invalid.data() == nullptr); + CHECK(invalid.size() == 0u); + CHECK_FALSE(invalid.empty()); + + CHECK(invalid.begin() == invalid.begin()); + CHECK(invalid.end() == invalid.end()); + CHECK(invalid.begin() == invalid.end()); + + CHECK(invalid.find(0) == invalid.end()); + CHECK_FALSE(invalid[0]); + + CHECK(invalid == invalid); + CHECK_FALSE(invalid != invalid); + } + + SECTION("default") { + view const v; + + REQUIRE(v); + + CHECK(v == view{}); + CHECK(bsoncxx::v1::document::view{v} == bsoncxx::v1::document::view{}); + + CHECK(v.data() == view{}.data()); + CHECK(v.size() == 5u); + CHECK(v.empty()); + + CHECK(v.begin() == v.begin()); + CHECK(v.end() == v.end()); + CHECK(v.begin() == v.end()); + + CHECK(v.find(0) == v.end()); + CHECK_FALSE(v[0]); + + CHECK(v == v); + CHECK_FALSE(v != v); + + CHECK_FALSE(v == invalid); + CHECK(v != invalid); + CHECK(v.begin() == invalid.begin()); + CHECK(v.end() == invalid.end()); + } + + SECTION("valid") { + // [1, 2] + std::uint8_t const data[] = {19, 0, 0, 0, 16, '0', '\0', 1, 0, 0, 0, 16, '1', '\0', 2, 0, 0, 0, 0}; + view const v{data}; + + REQUIRE(v); + + CHECK(v != view{}); + CHECK(bsoncxx::v1::document::view{v} == bsoncxx::v1::document::view{data}); + + CHECK(v.data() == data); + CHECK(v.size() == sizeof(data)); + CHECK_FALSE(v.empty()); + + CHECK(v.begin() == v.begin()); + CHECK(v.end() == v.end()); + CHECK(v.begin() != v.end()); + + CHECK(v == v); + CHECK_FALSE(v != v); + + CHECK_FALSE(v == invalid); + CHECK(v != invalid); + CHECK(v.begin() != invalid.begin()); + CHECK(v.end() == invalid.end()); + + CHECK(v.find(0) != v.end()); + CHECK(v.find(1) != v.end()); + CHECK(v.find(2) == v.end()); + } + + SECTION("iterator") { + // [1, 2] + std::uint8_t const data[] = {19, 0, 0, 0, 16, '0', '\0', 1, 0, 0, 0, 16, '1', '\0', 2, 0, 0, 0, 0}; + view const v{data}; + + auto iter = v.begin(); // 1 + + { + auto const citer = v.begin(); + + REQUIRE(citer != v.end()); + REQUIRE(*citer); + + CHECK(citer->raw() == data); + CHECK(citer->length() == v.length()); + CHECK(citer->offset() == 4u); + CHECK(citer->keylen() == 1u); + + CHECK(citer->type_id() == bsoncxx::v1::types::id::k_int32); + CHECK(citer->key() == "0"); + + CHECK(citer->get_int32() == bsoncxx::v1::types::b_int32{1}); + CHECK_THROWS_WITH_CODE(citer->get_document(), bsoncxx::v1::types::view::errc::type_mismatch); + CHECK_THROWS_WITH_CODE(citer->get_array(), bsoncxx::v1::types::view::errc::type_mismatch); + + CHECK(citer->type_view() == bsoncxx::v1::types::b_int32{1}); + CHECK(citer->type_value() == citer->type_view()); + + CHECK_FALSE((*citer)[empty_key]); + CHECK_FALSE((*citer)[0]); + } + + { + auto pre = iter; + auto post = iter; + + REQUIRE(pre == iter); + REQUIRE(post == iter); + + CHECK(++pre != iter); + CHECK(post++ == iter); + + CHECK(pre == post); + + CHECK(pre != v.end()); + CHECK(post != v.end()); + + REQUIRE(*pre); + + CHECK(pre->raw() == data); + CHECK(pre->length() == v.length()); + CHECK(pre->offset() == 11u); + CHECK(pre->keylen() == 1u); + + CHECK(pre->type_id() == bsoncxx::v1::types::id::k_int32); + CHECK(pre->key() == "1"); + CHECK(pre->get_int32() == bsoncxx::v1::types::b_int32{2}); + } + + ++iter; // 2 + + { + auto pre = iter; + auto post = iter; + + REQUIRE(pre == iter); + REQUIRE(post == iter); + + CHECK(++pre != iter); + CHECK(post++ == iter); + + CHECK(pre == post); + + CHECK(pre == v.end()); + CHECK(post == v.end()); + + CHECK_FALSE(*pre); + + CHECK(pre->raw() == nullptr); + CHECK(pre->length() == 0u); + CHECK(pre->offset() == 0u); + CHECK(pre->keylen() == 0u); + } + + iter = v.end(); + + { + auto pre = iter; + auto post = iter; + + REQUIRE(pre == iter); + REQUIRE(post == iter); + + CHECK(++pre == iter); + CHECK(post++ == iter); + + CHECK(pre == post); + + CHECK(pre == v.end()); + CHECK(post == v.end()); + + CHECK_FALSE(*pre); + } + } +} + +TEST_CASE("StringMaker", "[bsoncxx][test][v1][array][view]") { + SECTION("invalid") { + view arr{nullptr}; + + REQUIRE_FALSE(static_cast(arr)); + + CHECK(bsoncxx::test::stringify(arr) == "invalid"); + } + + SECTION("empty") { + view arr; + + REQUIRE(static_cast(arr)); + + CHECK(bsoncxx::test::stringify(arr) == R"([])"); + } + + SECTION("one") { + // {"0": 1} + std::uint8_t const bytes[] = { + // clang-format off + 12, 0, 0, 0, + 16, '0', '\0', 1, 0, 0, 0, // "0": 1 + 0, + // clang-format on + }; + + view arr{bytes}; + + REQUIRE(static_cast(arr)); + + CHECK(bsoncxx::test::stringify(arr) == R"([1])"); + } + + SECTION("two") { + // {"0": 1, "1": 2} + std::uint8_t const bytes[] = { + // clang-format off + 19, 0, 0, 0, + 16, '0', '\0', 1, 0, 0, 0, // "0": 1 + 16, '1', '\0', 2, 0, 0, 0, // "1": 2 + 0, + // clang-format on + }; + + view arr{bytes}; + + REQUIRE(static_cast(arr)); + + CHECK(bsoncxx::test::stringify(arr) == R"([1, 2])"); + } + + SECTION("three") { + // {"0": 1, "1": 2, "2": 3} + std::uint8_t const bytes[] = { + // clang-format off + 26, 0, 0, 0, + 16, '0', '\0', 1, 0, 0, 0, // "0": 1 + 16, '1', '\0', 2, 0, 0, 0, // "1": 2 + 16, '2', '\0', 3, 0, 0, 0, // "2": 3 + 0, + // clang-format on + }; + + view arr{bytes}; + + REQUIRE(static_cast(arr)); + + CHECK(bsoncxx::test::stringify(arr) == R"([1, 2, 3])"); + } +} + +} // namespace + +std::string Catch::StringMaker::convert(bsoncxx::v1::array::view const& value) try { + if (!value) { + return "invalid"; + } + + auto const end = value.end(); + auto iter = value.begin(); + + if (iter == end) { + return "[]"; + } + + std::string res; + res += '['; + res += bsoncxx::test::stringify(iter->type_view()); + for (++iter; iter != end; ++iter) { + res += ", "; + res += bsoncxx::test::stringify(iter->type_view()); + } + res += ']'; + return res; +} catch (bsoncxx::v1::exception const& ex) { + WARN("exception during stringification: " << ex.what()); + if (ex.code() == code::invalid_data) { + return "invalid"; + } else { + throw; + } +} diff --git a/src/bsoncxx/test/v1/array/view.hh b/src/bsoncxx/test/v1/array/view.hh new file mode 100644 index 0000000000..2a6034faf2 --- /dev/null +++ b/src/bsoncxx/test/v1/array/view.hh @@ -0,0 +1,30 @@ +// 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. + +#pragma once + +#include + +// + +#include // StringMaker + +#include + +#include + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::array::view const& value); +}; diff --git a/src/bsoncxx/test/v1/decimal128.cpp b/src/bsoncxx/test/v1/decimal128.cpp new file mode 100644 index 0000000000..8e5172496d --- /dev/null +++ b/src/bsoncxx/test/v1/decimal128.cpp @@ -0,0 +1,159 @@ +// 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 + +namespace { + +using bsoncxx::v1::decimal128; +using code = bsoncxx::v1::decimal128::errc; + +TEST_CASE("error code", "[bsoncxx][v1][decimal128][error]") { + using bsoncxx::v1::source_errc; + using bsoncxx::v1::type_errc; + + auto const& category = bsoncxx::v1::decimal128::error_category(); + CHECK_THAT(category.name(), Catch::Matchers::Equals("bsoncxx::v1::decimal128")); + + 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() == "unknown: -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::empty_string; + + 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::empty_string) == source_errc::bsoncxx); + CHECK(make_error_code(code::invalid_string_length) == source_errc::bsoncxx); + CHECK(make_error_code(code::invalid_string_data) == source_errc::bsoncxx); + } + + SECTION("type") { + CHECK(make_error_code(code::empty_string) == type_errc::invalid_argument); + CHECK(make_error_code(code::invalid_string_length) == type_errc::invalid_argument); + CHECK(make_error_code(code::invalid_string_data) == type_errc::invalid_argument); + } +} + +TEST_CASE("exceptions", "[bsoncxx][v1][decimal128]") { + SECTION("empty_string") { + auto const expr = [] { decimal128 d128{bsoncxx::v1::stdx::string_view()}; }; + + CHECK_THROWS_WITH_CODE(expr(), code::empty_string); + } + + SECTION("invalid_string_length") { + try { + auto const size = std::size_t{INT_MAX} + 1u; + std::unique_ptr data{new char[size]}; // make_unique_for_overwrite + auto const big_string = bsoncxx::v1::stdx::string_view{data.get(), size}; + + auto const expr = [&] { decimal128 d128{big_string}; }; + + CHECK_THROWS_WITH_CODE(expr(), code::invalid_string_length); + } catch (std::bad_alloc const& ex) { + WARN("could not allocate big_string: " << ex.what()); + } + } + + SECTION("invalid_string") { + auto const expr = [] { decimal128 d128{"sNaN"}; }; + + CHECK_THROWS_WITH_CODE(expr(), code::invalid_string_data); + } +} + +TEST_CASE("basic", "[bsoncxx][v1][decimal128]") { + SECTION("default") { + decimal128 d128; + + CHECK(d128.high() == 0u); + CHECK(d128.low() == 0u); + + CHECK(d128.to_string() == "0E-6176"); + + CHECK(d128 == d128); + CHECK_FALSE(d128 != d128); + } + + SECTION("values") { + using d128 = decimal128; + + CHECK((d128{0u, 0u}) == d128{}); + CHECK((d128{0u, 0u}) != d128{0u, 1u}); + CHECK((d128{0u, 0u}) != d128{1u, 0u}); + CHECK((d128{1u, 1u}) == d128{1u, 1u}); + + // [0,0,0] + CHECK((d128{0x3040000000000000, 0x0000000000000000}) == d128{"0"}); + + // [0,123,0] + CHECK((d128{0x3040000000000000, 0x000000000000007b}) == d128{"123"}); + + // [0,inf] + CHECK((d128{0x7800000000000000, 0x0000000000000000}) == d128{"Infinity"}); + } +} + +TEST_CASE("stringify", "[bsoncxx][test][v1][decimal128]") { + decimal128 d128; + CHECK(d128.to_string() == "0E-6176"); + CHECK(bsoncxx::test::stringify(d128) == "0E-6176"); +} + +} // namespace diff --git a/src/bsoncxx/test/v1/decimal128.hh b/src/bsoncxx/test/v1/decimal128.hh new file mode 100644 index 0000000000..cd1d72f5bb --- /dev/null +++ b/src/bsoncxx/test/v1/decimal128.hh @@ -0,0 +1,39 @@ +// 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. + +#pragma once + +#include + +// + +#include + +CATCH_REGISTER_ENUM( + bsoncxx::v1::decimal128::errc, + bsoncxx::v1::decimal128::errc::zero, + bsoncxx::v1::decimal128::errc::empty_string, + bsoncxx::v1::decimal128::errc::invalid_string_length, + bsoncxx::v1::decimal128::errc::invalid_string_data) + +namespace Catch { + +template <> +struct StringMaker { + static std::string convert(bsoncxx::v1::decimal128 const& value) { + return value.to_string(); + } +}; + +} // namespace Catch diff --git a/src/bsoncxx/test/v1/detail/bit.hh b/src/bsoncxx/test/v1/detail/bit.hh new file mode 100644 index 0000000000..a69234c7e2 --- /dev/null +++ b/src/bsoncxx/test/v1/detail/bit.hh @@ -0,0 +1,27 @@ +// 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. + +#pragma once + +#include + +// + +#include + +CATCH_REGISTER_ENUM( + bsoncxx::detail::endian, + bsoncxx::detail::endian::little, + bsoncxx::detail::endian::big, + bsoncxx::detail::endian::native) diff --git a/src/bsoncxx/test/v1/document/value.cpp b/src/bsoncxx/test/v1/document/value.cpp new file mode 100644 index 0000000000..af2b982885 --- /dev/null +++ b/src/bsoncxx/test/v1/document/value.cpp @@ -0,0 +1,747 @@ +// 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 { + +using bsoncxx::v1::document::value; +using bsoncxx::v1::document::view; + +template +bsoncxx::v1::stdx::optional get_deleter(T const& v) { + if (auto const deleter_ptr = v.get_deleter().template target()) { + return *deleter_ptr; + } + return {}; +} + +TEST_CASE("exceptions", "[bsoncxx][v1][document][value]") { + using code = bsoncxx::v1::document::view::errc; + + std::uint8_t const data[] = {5, 0, 0, 0, 0}; // {} + + REQUIRE(data[0] == sizeof(data)); + + auto const make_ptr = [&data] { + auto res = std::unique_ptr(new std::uint8_t[sizeof(data)]); + std::memcpy(res.get(), data, sizeof(data)); + return res; + }; + + SECTION("invalid_length") { + SECTION("value(std::uint8_t*, std::size_t, Deleter)") { + auto const deleter = [](std::uint8_t* p) { delete[] p; }; + using deleter_type = std::reference_wrapper; + + auto const expr = [&](std::size_t length) { + return value{make_ptr().release(), length, std::ref(deleter)}; + }; + + CHECK_THROWS_WITH_CODE(expr(0u), code::invalid_length); + CHECK_THROWS_WITH_CODE(expr(sizeof(data) - 1u), code::invalid_length); + + CHECK(get_deleter(expr(sizeof(data))).has_value()); + CHECK(get_deleter(expr(sizeof(data) + 1u)).has_value()); + + auto ptr = make_ptr(); + ++ptr[0]; + CHECK_THROWS_WITH_CODE((value{ptr.release(), sizeof(data), std::ref(deleter)}), code::invalid_length); + } + + SECTION("value(std::uint8_t*, std::size_t)") { + auto const expr = [&](std::size_t length) { return value{make_ptr().release(), length}; }; + + CHECK_THROWS_WITH_CODE(expr(0), code::invalid_length); + CHECK_THROWS_WITH_CODE(expr(sizeof(data) - 1u), code::invalid_length); + + CHECK(get_deleter(expr(sizeof(data))).has_value()); + CHECK(get_deleter(expr(sizeof(data) + 1u)).has_value()); + + auto ptr = make_ptr(); + ++ptr[0]; + CHECK_THROWS_WITH_CODE((value{ptr.release(), sizeof(data)}), code::invalid_length); + } + + SECTION("value(unique_ptr_type, std::size_t)") { + auto const expr = [&](std::size_t length) { return value{make_ptr(), length}; }; + + CHECK_THROWS_WITH_CODE(expr(0), code::invalid_length); + CHECK_THROWS_WITH_CODE(expr(sizeof(data) - 1u), code::invalid_length); + + CHECK(get_deleter(expr(sizeof(data))).has_value()); + CHECK(get_deleter(expr(sizeof(data) + 1u)).has_value()); + + auto ptr = make_ptr(); + ++ptr[0]; + CHECK_THROWS_WITH_CODE((value{std::move(ptr), sizeof(data)}), code::invalid_length); + } + } +} + +TEST_CASE("ownership", "[bsoncxx][v1][document][value]") { + using default_deleter_type = value::default_deleter_type; + using noop_deleter_type = value::noop_deleter_type; + + bsoncxx::v1::document::view const empty; + + auto const noop_deleter = &value::noop_deleter; + + std::uint8_t const xdoc[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // { 'x': 1 } + + auto xdoc_owner = std::unique_ptr(new std::uint8_t[sizeof(xdoc)]); + std::memcpy(xdoc_owner.get(), xdoc, sizeof(xdoc)); + + SECTION("invalid") { + value v{nullptr}; + + REQUIRE(v.data() == nullptr); + REQUIRE(get_deleter(v).has_value()); + + SECTION("move") { + auto const move = std::move(v); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(move.data() == nullptr); + CHECK(get_deleter(move).has_value()); + } + + SECTION("copy") { + auto const copy = v; + + CHECK(v.data() == nullptr); + CHECK(get_deleter(v).has_value()); + + CHECK(copy.data() == nullptr); + CHECK(get_deleter(v).has_value()); + } + + SECTION("release") { + auto const ptr = v.release(); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(ptr.get() == nullptr); + REQUIRE(get_deleter(ptr).has_value()); + } + + SECTION("reset with value") { + auto const data_ptr = xdoc_owner.get(); + + v.reset(value{std::move(xdoc_owner)}); + + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + } + + SECTION("reset with view") { + auto const data_ptr = xdoc; + + v.reset(view{xdoc}); + + CHECK(v.data() != nullptr); + CHECK(v.data() != data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == view{xdoc}); + } + } + + SECTION("default") { + value v; + + REQUIRE(v.data() == empty.data()); + REQUIRE(get_deleter(v) == noop_deleter); + + SECTION("release") { + auto const ptr = v.release(); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(ptr.get() == empty.data()); + CHECK(get_deleter(ptr) == noop_deleter); + } + + SECTION("move") { + auto const move = std::move(v); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(move.data() == empty.data()); + CHECK(get_deleter(move) == noop_deleter); + } + + SECTION("copy") { + auto const copy = v; + + CHECK(v.data() == empty.data()); + CHECK(get_deleter(v) == noop_deleter); + + CHECK(copy.data() == empty.data()); + CHECK(get_deleter(copy) == noop_deleter); + } + + SECTION("reset with value") { + auto const data_ptr = xdoc_owner.get(); + + v.reset(value{std::move(xdoc_owner)}); + + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + } + + SECTION("reset with view") { + auto const data_ptr = xdoc; + + v = view{xdoc}; + + CHECK(v.data() != data_ptr); + CHECK(get_deleter(v).has_value()); + } + } + + SECTION("owning") { + auto const owner_ptr = xdoc_owner.get(); + + value v{std::move(xdoc_owner)}; + + REQUIRE(v.data() == owner_ptr); + REQUIRE(get_deleter(v).has_value()); + + SECTION("release") { + auto const ptr = v.release(); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(ptr.get() == owner_ptr); + CHECK(get_deleter(ptr).has_value()); + } + + SECTION("move") { + auto move = std::move(v); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(move.data() == owner_ptr); + CHECK(get_deleter(move).has_value()); + + v = std::move(move); + + CHECK(move.data() == nullptr); + CHECK_NOFAIL(!move.get_deleter()); // Valid but unspecified state. + + CHECK(v.data() == owner_ptr); + CHECK(get_deleter(v).has_value()); + } + + SECTION("copy") { + auto const copy = v; + + CHECK(v.data() == owner_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(copy.data() != nullptr); + CHECK(copy.data() != owner_ptr); + CHECK(get_deleter(copy).has_value()); + + CHECK(copy == view{xdoc}); + + auto const copy_ptr = copy.data(); + + v = copy; + + CHECK(copy.data() == copy_ptr); + CHECK(get_deleter(copy).has_value()); + + CHECK(v.data() != nullptr); + CHECK(v.data() != copy_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == copy); + } + + SECTION("reset with value") { + auto copy = v; + auto const data_ptr = copy.data(); + + REQUIRE(v.data() != data_ptr); + + v.reset(std::move(copy)); + + CHECK(v.data() != owner_ptr); + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == view{xdoc}); + } + + SECTION("reset with view") { + auto const data_ptr = xdoc; + + REQUIRE(v.data() != data_ptr); + + v.reset(view{xdoc}); + + CHECK(v.data() != owner_ptr); + CHECK(v.data() != data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == view{xdoc}); + } + } + + SECTION("custom deleter") { + std::uint8_t bytes[] = {5, 0, 0, 0, 0}; // {} + auto const bytes_ptr = bytes; + auto const deleter = [bytes_ptr](std::uint8_t* p) { CHECK(p == bytes_ptr); }; + using deleter_type = std::reference_wrapper; + + value v{bytes, std::ref(deleter)}; + auto const data_ptr = v.data(); + + REQUIRE(data_ptr == bytes_ptr); + REQUIRE(get_deleter(v).has_value()); + + SECTION("release") { + auto const ptr = v.release(); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(ptr.get() == data_ptr); + CHECK(get_deleter(ptr).has_value()); + } + + SECTION("move") { + auto move = std::move(v); + + CHECK(v.data() == nullptr); + CHECK_NOFAIL(!v.get_deleter()); // Valid but unspecified state. + + CHECK(move.data() == data_ptr); + CHECK(get_deleter(move).has_value()); + + v = std::move(move); + + CHECK(move.data() == nullptr); + CHECK_NOFAIL(!move.get_deleter()); // Valid but unspecified state. + + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + } + + SECTION("copy") { + auto const copy = v; + + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(copy.data() != nullptr); + CHECK(copy.data() != data_ptr); + CHECK(get_deleter(copy).has_value()); + + CHECK(copy == view{bytes}); + + auto const copy_ptr = copy.data(); + + v = copy; + + CHECK(copy.data() == copy_ptr); + CHECK(get_deleter(copy).has_value()); + + CHECK(v.data() != nullptr); + CHECK(v.data() != copy_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == copy); + } + + SECTION("reset with value") { + auto copy = v; + auto const data_ptr = copy.data(); + + REQUIRE(v.data() != data_ptr); + + v.reset(std::move(copy)); + + CHECK(v.data() != bytes_ptr); + CHECK(v.data() == data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == view{bytes}); + } + + SECTION("reset with view") { + auto const data_ptr = xdoc; + + REQUIRE(v.data() != data_ptr); + + v.reset(view{xdoc}); + + CHECK(v.data() != bytes_ptr); + CHECK(v.data() != data_ptr); + CHECK(get_deleter(v).has_value()); + + CHECK(v == view{xdoc}); + } + } +} + +TEST_CASE("basic", "[bsoncxx][v1][document][value]") { + auto const invalid = value{nullptr}; + auto const empty_key = bsoncxx::v1::stdx::string_view{}; + + SECTION("invalid") { + REQUIRE_FALSE(invalid); + + CHECK(invalid.data() == nullptr); + CHECK(invalid.size() == 0u); + CHECK_FALSE(invalid.empty()); + + CHECK(invalid.begin() == invalid.begin()); + CHECK(invalid.end() == invalid.end()); + CHECK(invalid.begin() == invalid.end()); + + CHECK(invalid.find(empty_key) == invalid.end()); + CHECK_FALSE(invalid[empty_key]); + + CHECK(invalid == invalid); + CHECK_FALSE(invalid != invalid); + CHECK(invalid != view{}); + CHECK(invalid == value{invalid}); + } + + SECTION("default") { + value const v; + + REQUIRE(v); + + CHECK(v.data() == value{}.data()); + CHECK(v.size() == 5u); + CHECK(v.empty()); + + CHECK(v.begin() == v.begin()); + CHECK(v.end() == v.end()); + CHECK(v.begin() == v.end()); + + CHECK(v.find(empty_key) == v.end()); + CHECK_FALSE(v[empty_key]); + + CHECK(v == v); + CHECK_FALSE(v != v); + CHECK(v == view{}); + CHECK(v == value{v}); + + CHECK_FALSE(v == invalid); + CHECK(v != invalid); + CHECK(v.begin() == invalid.begin()); + CHECK(v.end() == invalid.end()); + } + + SECTION("valid") { + std::uint8_t const xdoc[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // { 'x': 1 } + + auto xdoc_owner = std::unique_ptr(new std::uint8_t[sizeof(xdoc)]); + std::memcpy(xdoc_owner.get(), xdoc, sizeof(xdoc)); + auto const data_ptr = xdoc_owner.get(); + + value const v{std::move(xdoc_owner)}; + + REQUIRE(v); + + CHECK(v != value{}); + + CHECK(v.data() == data_ptr); + CHECK(v.size() == sizeof(xdoc)); + CHECK(v.size() == v.length()); + CHECK_FALSE(v.empty()); + + CHECK(v.begin() == v.begin()); + CHECK(v.begin() == v.cbegin()); + CHECK(v.end() == v.end()); + CHECK(v.end() == v.cend()); + CHECK(v.begin() != v.end()); + + CHECK(v == v); + CHECK_FALSE(v != v); + CHECK(v != view{}); + CHECK(v == value{v}); + + CHECK_FALSE(v == invalid); + CHECK(v != invalid); + CHECK(v.begin() != invalid.begin()); + CHECK(v.end() == invalid.end()); + } +} + +BSONCXX_PRIVATE_WARNINGS_PUSH(); +BSONCXX_PRIVATE_WARNINGS_DISABLE(Clang("-Wunused-member-function")); + +struct Q { + // void to_bson(Q const&, value&) {} + void to_bson(Q const&, value&&) {} + void to_bson(Q&, value&&) {} + void to_bson(Q&&, value&) {} + + // void from_bson(Q&, value const&) {} + void from_bson(Q const&, value const&) {} + void from_bson(Q&, value&) {} + void from_bson(Q&, value&&) {} +}; + +struct S { + std::uint8_t bytes[12] = {}; // { 's': i } + bool should_throw = false; + + S() = default; + + explicit S(std::uint8_t i, bool should_throw) + : bytes{12, 0, 0, 0, 16, 's', '\0', i, 0, 0, 0, 0}, should_throw{should_throw} { + REQUIRE(view{bytes}); + } + + explicit S(std::uint8_t i) : S{i, false} {} + + friend bool operator==(S const& lhs, S const& rhs) { + return std::memcmp(lhs.bytes, rhs.bytes, sizeof(bytes)) == 0; + } + + friend bool operator!=(S const& lhs, S const& rhs) { + return !(lhs == rhs); + } + + friend std::ostream& operator<<(std::ostream& os, S const& s) { + return os << 'S' << static_cast(s.bytes[7]); + } + + friend void to_bson(S const& s, value& v) { + if (s.should_throw) { + std::ostringstream oss; + oss << s; + throw std::runtime_error(std::move(oss).str()); + } + + REQUIRE(v.empty()); + v.reset(view{s.bytes}); + REQUIRE(v.size() == sizeof(s.bytes)); + } + + friend void from_bson(S& s, view v) { + if (s.should_throw) { + std::ostringstream oss; + oss << s; + throw std::runtime_error(std::move(oss).str()); + } + + REQUIRE(v.size() == sizeof(s.bytes)); + std::memcpy(s.bytes, v.data(), sizeof(s.bytes)); + } +}; + +BSONCXX_PRIVATE_WARNINGS_POP(); + +TEST_CASE("user-defined types", "[bsoncxx][v1][document][value]") { + SECTION("valid") { + S const s1{1}; + S const s2{2}; + + REQUIRE(s1 == s1); + REQUIRE(s2 == s2); + REQUIRE(s1 != s2); + + value v{s1}; // to_bson + auto const& cv = v; + + CHECK(cv.data() != static_cast(s1.bytes)); + CHECK(cv == view{s1.bytes}); + + S s = cv.get(); // from_bson + CHECK(s == s1); + + v = s2; // to_bson + + cv.get(s); // from_bson + CHECK(s == s2); + } + + SECTION("exception safety") { + S s1{1, false}; + S s2{2, true}; + + view const vs1{s1.bytes}; + + value v{vs1}; + + REQUIRE(v == vs1); + + CHECK_THROWS_WITH(value{s2}, Catch::Matchers::Equals("S2")); // to_bson + + CHECK(v == vs1); + + CHECK_THROWS_WITH(v = s2, Catch::Matchers::Equals("S2")); // to_bson + CHECK(v == vs1); + + CHECK_THROWS_WITH(v.get(s2), Catch::Matchers::Equals("S2")); // from_bson + CHECK(v == vs1); + } +} + +TEST_CASE("StringMaker", "[bsoncxx][test][v1][document][value]") { + SECTION("invalid") { + value v{nullptr}; + + REQUIRE_FALSE(static_cast(v)); + + CHECK(bsoncxx::test::stringify(v) == "invalid"); + } + + SECTION("empty") { + value v; + + REQUIRE(static_cast(v)); + + CHECK(bsoncxx::test::stringify(v) == "{}"); + } + + SECTION("one") { + // {"x": 1} + std::uint8_t const bytes[] = { + // clang-format off + 12, 0, 0, 0, + 16, 'x', '\0', 1, 0, 0, 0, // "x": 1 + 0, + // clang-format on + }; + auto ptr = new std::uint8_t[sizeof(bytes)]; + std::memcpy(ptr, bytes, sizeof(bytes)); + + value v{ptr}; + + REQUIRE(static_cast(v)); + + CHECK(bsoncxx::test::stringify(v) == R"({"x": 1})"); + } + + SECTION("two") { + // {"x": 1, "y": 2} + std::uint8_t const bytes[] = { + // clang-format off + 19, 0, 0, 0, + 16, 'x', '\0', 1, 0, 0, 0, // "x": 1 + 16, 'y', '\0', 2, 0, 0, 0, // "y": 2 + 0, + // clang-format on + }; + auto ptr = new std::uint8_t[sizeof(bytes)]; + std::memcpy(ptr, bytes, sizeof(bytes)); + + value v{ptr}; + + REQUIRE(static_cast(v)); + + CHECK(bsoncxx::test::stringify(v) == R"({"x": 1, "y": 2})"); + } + + SECTION("three") { + // {"x": 1, "y": 2, "z": 3} + std::uint8_t bytes[] = { + // clang-format off + 26, 0, 0, 0, + 16, 'x', '\0', 1, 0, 0, 0, // "x": 1 + 16, 'y', '\0', 2, 0, 0, 0, // "y": 2 + 16, 'z', '\0', 3, 0, 0, 0, // "z": 3 + 0, + // clang-format on + }; + auto ptr = new std::uint8_t[sizeof(bytes)]; + std::memcpy(ptr, bytes, sizeof(bytes)); + + value v{ptr}; + + REQUIRE(static_cast(v)); + + CHECK(bsoncxx::test::stringify(v) == R"({"x": 1, "y": 2, "z": 3})"); + } +} + +namespace static_assertions { + +BSONCXX_PRIVATE_WARNINGS_DISABLE(Clang("-Wunused-template")); + +template +void copy_list_init_fn(T) {} + +template +using get_ns_expr = decltype(std::declval().template get()); + +static_assert(!std::is_constructible::value, "Q does not have a valid to_bson() overload"); +static_assert( + !bsoncxx::detail::is_detected::value, + "Q does not have a valid from_bson() overload"); + +static_assert(!std::is_convertible::value, "to_bson() ctor must be explicit"); +static_assert(std::is_constructible::value, "S::to_bson() must be found via ADL"); +static_assert(std::is_assignable::value, "S::to_bson() must be found via ADL"); + +template +using empty_direct_list_init_expr = decltype(new (nullptr) T({})); +template +using empty_copy_list_init_expr = decltype(copy_list_init_fn({})); +static_assert( + !bsoncxx::detail::is_detected::value, + "bsoncxx::v1::document::value does not support empty list initialization"); +static_assert( + !bsoncxx::detail::is_detected::value, + "bsoncxx::v1::document::value does not support empty list initialization"); + +template +using list_init_nullptr = decltype(new (nullptr) T(nullptr)); // value(std::uint8_t*) +static_assert( + bsoncxx::detail::is_detected::value, + "value(nullptr) must unambiguously select the std::uint8_t* ctor"); + +template +using list_init_nullptr_list = decltype(new (nullptr) T({{}, {}})); // value(unique_ptr_type) +static_assert( + bsoncxx::detail::is_detected::value, + "value({nullptr, {}}) must unambiguously select the unique_ptr_type ctor"); + +} // namespace static_assertions + +} // namespace diff --git a/src/bsoncxx/test/v1/document/value.hh b/src/bsoncxx/test/v1/document/value.hh new file mode 100644 index 0000000000..8d974007d0 --- /dev/null +++ b/src/bsoncxx/test/v1/document/value.hh @@ -0,0 +1,26 @@ +// 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. + +#pragma once + +#include + +// + +#include // StringMaker + +#include + +template <> +struct Catch::StringMaker : StringMaker {}; diff --git a/src/bsoncxx/test/v1/document/view.cpp b/src/bsoncxx/test/v1/document/view.cpp new file mode 100644 index 0000000000..ffae690b4b --- /dev/null +++ b/src/bsoncxx/test/v1/document/view.cpp @@ -0,0 +1,452 @@ +// 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 + +#include + +namespace { + +using bsoncxx::v1::document::view; +using code = bsoncxx::v1::document::view::errc; + +TEST_CASE("error code", "[bsoncxx][v1][document][view][error]") { + using bsoncxx::v1::source_errc; + using bsoncxx::v1::type_errc; + + auto const& category = bsoncxx::v1::document::view::error_category(); + CHECK_THAT(category.name(), Catch::Matchers::Equals("bsoncxx::v1::document::view")); + + 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() == "unknown: -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_length; + + 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_length) == source_errc::bsoncxx); + CHECK(make_error_code(code::invalid_data) == source_errc::bsoncxx); + } + + SECTION("type") { + CHECK(make_error_code(code::invalid_length) == type_errc::invalid_argument); + CHECK(make_error_code(code::invalid_data) == type_errc::runtime_error); + } +} + +TEST_CASE("exceptions", "[bsoncxx][v1][document][view]") { + auto const empty_key = bsoncxx::v1::stdx::string_view{}; + + SECTION("invalid_view") { + view v{nullptr}; + + REQUIRE_NOTHROW(v.end()); + REQUIRE_NOTHROW(v[empty_key]); + + CHECK(v.begin() == v.end()); + CHECK(v.find(empty_key) == v.end()); + } + + SECTION("invalid_length") { + std::uint8_t data[] = {5, 0, 0, 0, 0}; // {} + auto const data_ptr = data; + + REQUIRE(data[0] == sizeof(data)); + + auto const expr = [&data](std::size_t length) { return view{data, length}; }; + + CHECK_THROWS_WITH_CODE(expr(0), code::invalid_length); + CHECK_THROWS_WITH_CODE(expr(sizeof(data) - 1u), code::invalid_length); + + CHECK(expr(sizeof(data)).data() == data_ptr); + CHECK(expr(sizeof(data) + 1u).data() == data_ptr); + + ++data[0]; + + CHECK_THROWS_WITH_CODE((view{data, sizeof(data)}), code::invalid_length); + } + + SECTION("invalid_data") { + SECTION("view") { + std::uint8_t const data[] = {5u, 0u, 0u, 0u, 1u}; + view v{data}; + + REQUIRE_NOTHROW(v.end()); + + CHECK_THROWS_WITH_CODE(v.begin(), code::invalid_data); + CHECK_THROWS_WITH_CODE(v.find(empty_key), code::invalid_data); + } + + SECTION("const_iterator") { + std::uint8_t data[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // { 'x': 1 } + view const v{data}; + auto iter = v.begin(); + REQUIRE(iter != v.end()); + + std::memset(data, 0u, sizeof(data)); + + REQUIRE_NOTHROW(v.end()); + + CHECK_THROWS_WITH_CODE(++iter, code::invalid_data); + } + } + + SECTION("invalid_key_length") { + try { + auto const size = std::size_t{INT_MAX} + 1u; + std::unique_ptr data{new char[size]}; // make_unique_for_overwrite. + auto const big_string = bsoncxx::v1::stdx::string_view{data.get(), size}; + + view const v; + + CHECK(v.find(big_string) == v.end()); + } catch (std::bad_alloc const& ex) { + WARN("could not allocate big_string: " << ex.what()); + } + } +} + +TEST_CASE("basic", "[bsoncxx][v1][document][view]") { + auto const invalid = view{nullptr}; + auto const empty_key = bsoncxx::v1::stdx::string_view{}; + + SECTION("invalid") { + REQUIRE_FALSE(invalid); + + CHECK(invalid.data() == nullptr); + CHECK(invalid.size() == 0u); + CHECK_FALSE(invalid.empty()); + + CHECK(invalid.begin() == invalid.begin()); + CHECK(invalid.end() == invalid.end()); + CHECK(invalid.begin() == invalid.end()); + + CHECK(invalid.find(empty_key) == invalid.end()); + CHECK_FALSE(invalid[empty_key]); + + CHECK(invalid == invalid); + CHECK_FALSE(invalid != invalid); + } + + SECTION("default") { + view const v; + + REQUIRE(v); + + CHECK(v.data() == view{}.data()); + CHECK(v.size() == 5u); + CHECK(v.empty()); + + CHECK(v.begin() == v.begin()); + CHECK(v.end() == v.end()); + CHECK(v.begin() == v.end()); + + CHECK(v.find(empty_key) == v.end()); + CHECK_FALSE(v[empty_key]); + + CHECK(v == v); + CHECK_FALSE(v != v); + + CHECK_FALSE(v == invalid); + CHECK(v != invalid); + CHECK(v.begin() == invalid.begin()); + CHECK(v.end() == invalid.end()); + } + + SECTION("valid") { + // { 'x': 1, 'y': 2 } + std::uint8_t const data[] = {19, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 16, 'y', '\0', 2, 0, 0, 0, 0}; + view const v{data}; + + REQUIRE(v); + + CHECK(v != view{}); + + CHECK(v.data() == data); + CHECK(v.size() == sizeof(data)); + CHECK_FALSE(v.empty()); + + CHECK(v.begin() == v.begin()); + CHECK(v.end() == v.end()); + CHECK(v.begin() != v.end()); + + CHECK(v == v); + CHECK_FALSE(v != v); + + CHECK_FALSE(v == invalid); + CHECK(v != invalid); + CHECK(v.begin() != invalid.begin()); + CHECK(v.end() == invalid.end()); + + CHECK(v.find("x") != v.end()); + CHECK(v.find("y") != v.end()); + CHECK(v.find("z") == v.end()); + } + + SECTION("iterator") { + // { 'x': 1, 'y': 2 } + std::uint8_t const data[] = {19, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 16, 'y', '\0', 2, 0, 0, 0, 0}; + view const v{data}; + + auto iter = v.begin(); // x: 1 + + { + auto const citer = v.begin(); + + REQUIRE(citer != v.end()); + REQUIRE(*citer); + + CHECK(citer->raw() == data); + CHECK(citer->length() == v.length()); + CHECK(citer->offset() == 4u); + CHECK(citer->keylen() == 1u); + + CHECK(citer->type_id() == bsoncxx::v1::types::id::k_int32); + CHECK(citer->key() == "x"); + + CHECK(citer->get_int32() == bsoncxx::v1::types::b_int32{1}); + CHECK_THROWS_WITH_CODE(citer->get_document(), bsoncxx::v1::types::view::errc::type_mismatch); + CHECK_THROWS_WITH_CODE(citer->get_array(), bsoncxx::v1::types::view::errc::type_mismatch); + + CHECK(citer->type_view() == bsoncxx::v1::types::b_int32{1}); + CHECK(citer->type_value() == citer->type_view()); + + CHECK_FALSE((*citer)[empty_key]); + CHECK_FALSE((*citer)[0]); + } + + { + auto pre = iter; + auto post = iter; + + REQUIRE(pre == iter); + REQUIRE(post == iter); + + CHECK(++pre != iter); + CHECK(post++ == iter); + + CHECK(pre == post); + + CHECK(pre != v.end()); + CHECK(post != v.end()); + + REQUIRE(*pre); + + CHECK(pre->raw() == data); + CHECK(pre->length() == v.length()); + CHECK(pre->offset() == 11u); + CHECK(pre->keylen() == 1u); + + CHECK(pre->type_id() == bsoncxx::v1::types::id::k_int32); + CHECK(pre->key() == "y"); + CHECK(pre->get_int32() == bsoncxx::v1::types::b_int32{2}); + } + + ++iter; // y: 2 + + { + auto pre = iter; + auto post = iter; + + REQUIRE(pre == iter); + REQUIRE(post == iter); + + CHECK(++pre != iter); + CHECK(post++ == iter); + + CHECK(pre == post); + + CHECK(pre == v.end()); + CHECK(post == v.end()); + + CHECK_FALSE(*pre); + + CHECK(pre->raw() == nullptr); + CHECK(pre->length() == 0u); + CHECK(pre->offset() == 0u); + CHECK(pre->keylen() == 0u); + } + + iter = v.end(); + + { + auto pre = iter; + auto post = iter; + + REQUIRE(pre == iter); + REQUIRE(post == iter); + + CHECK(++pre == iter); + CHECK(post++ == iter); + + CHECK(pre == post); + + CHECK(pre == v.end()); + CHECK(post == v.end()); + + CHECK_FALSE(*pre); + } + } +} + +TEST_CASE("StringMaker", "[bsoncxx][test][v1][document][view]") { + SECTION("invalid") { + view doc{nullptr}; + + REQUIRE_FALSE(static_cast(doc)); + + CHECK(bsoncxx::test::stringify(doc) == "invalid"); + } + + SECTION("empty") { + view doc; + + REQUIRE(static_cast(doc)); + + CHECK(bsoncxx::test::stringify(doc) == R"({})"); + } + + SECTION("one") { + // {"x": 1} + std::uint8_t const bytes[] = { + // clang-format off + 12, 0, 0, 0, + 16, 'x', '\0', 1, 0, 0, 0, // "x": 1 + 0, + // clang-format on + }; + + view doc{bytes}; + + REQUIRE(static_cast(doc)); + + CHECK(bsoncxx::test::stringify(doc) == R"({"x": 1})"); + } + + SECTION("two") { + // {"x": 1, "y": 2} + std::uint8_t const bytes[] = { + // clang-format off + 19, 0, 0, 0, + 16, 'x', '\0', 1, 0, 0, 0, // "x": 1 + 16, 'y', '\0', 2, 0, 0, 0, // "y": 2 + 0, + // clang-format on + }; + + view doc{bytes}; + + REQUIRE(static_cast(doc)); + + CHECK(bsoncxx::test::stringify(doc) == R"({"x": 1, "y": 2})"); + } + + SECTION("three") { + // {"x": 1, "y": 2, "z": 3} + std::uint8_t const bytes[] = { + // clang-format off + 26, 0, 0, 0, + 16, 'x', '\0', 1, 0, 0, 0, // "x": 1 + 16, 'y', '\0', 2, 0, 0, 0, // "y": 2 + 16, 'z', '\0', 3, 0, 0, 0, // "z": 3 + 0, + // clang-format on + }; + + view doc{bytes}; + + REQUIRE(static_cast(doc)); + + CHECK(bsoncxx::test::stringify(doc) == R"({"x": 1, "y": 2, "z": 3})"); + } +} + +} // namespace + +std::string Catch::StringMaker::convert(bsoncxx::v1::document::view const& value) try { + if (!value) { + return "invalid"; + } + + auto const end = value.end(); + auto iter = value.begin(); + + if (iter == end) { + return "{}"; + } + + std::string res; + res += '{'; + res += bsoncxx::test::stringify(*iter); + for (++iter; iter != end; ++iter) { + res += ", "; + res += bsoncxx::test::stringify(*iter); + } + res += '}'; + return res; +} catch (bsoncxx::v1::exception const& ex) { + WARN("exception during stringification: " << ex.what()); + if (ex.code() == code::invalid_data) { + return "invalid"; + } else { + throw; + } +} diff --git a/src/bsoncxx/test/v1/document/view.hh b/src/bsoncxx/test/v1/document/view.hh new file mode 100644 index 0000000000..c5c4f7fc4d --- /dev/null +++ b/src/bsoncxx/test/v1/document/view.hh @@ -0,0 +1,41 @@ +// 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. + +#pragma once + +#include + +// + +#include // StringMaker + +#include + +#include + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::document::view const& value); +}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::document::view::const_iterator const& value) { + if (auto const e = *value) { + return bsoncxx::test::stringify(e); + } + + return "end"; + } +}; diff --git a/src/bsoncxx/test/v1/element/view.cpp b/src/bsoncxx/test/v1/element/view.cpp new file mode 100644 index 0000000000..5d05175213 --- /dev/null +++ b/src/bsoncxx/test/v1/element/view.cpp @@ -0,0 +1,308 @@ +// 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 + +namespace { + +using bsoncxx::v1::element::view; +using code = bsoncxx::v1::element::view::errc; + +TEST_CASE("error code", "[bsoncxx][v1][element][view][error]") { + using bsoncxx::v1::source_errc; + using bsoncxx::v1::type_errc; + + auto const& category = bsoncxx::v1::element::view::error_category(); + CHECK_THAT(category.name(), Catch::Matchers::Equals("bsoncxx::v1::element::view")); + + 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() == "unknown: -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_view; + + 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_view) == source_errc::bsoncxx); + CHECK(make_error_code(code::invalid_data) == source_errc::bsoncxx); + } + + SECTION("type") { + CHECK(make_error_code(code::invalid_view) == type_errc::runtime_error); + CHECK(make_error_code(code::invalid_data) == type_errc::runtime_error); + } +} + +TEST_CASE("exceptions", "[bsoncxx][v1][element][view]") { + SECTION("invalid_view") { + SECTION("view") { + view v; + + CHECK_FALSE(v); + + CHECK_NOTHROW(v.raw(), code::invalid_view); + CHECK_NOTHROW(v.length(), code::invalid_view); + CHECK_NOTHROW(v.offset(), code::invalid_view); + CHECK_NOTHROW(v.keylen(), code::invalid_view); + + CHECK_THROWS_WITH_CODE(v.type_id(), code::invalid_view); + CHECK_THROWS_WITH_CODE(v.key(), code::invalid_view); +#pragma push_macro("X") +#undef X +#define X(_name, _value) CHECK_THROWS_WITH_CODE(v.get_##_name(), code::invalid_view); + BSONCXX_V1_TYPES_XMACRO(X) +#pragma pop_macro("X") + + CHECK_THROWS_WITH_CODE(v.type_view(), code::invalid_view); + CHECK_THROWS_WITH_CODE(v.type_value(), code::invalid_view); + + CHECK_FALSE(v[""]); + CHECK_FALSE(v[0]); + } + + SECTION("key") { + SECTION("none") { + bsoncxx::v1::document::view doc; + + auto const x = doc["x"]; + CHECK_FALSE(x); + CHECK_THROWS_WITH_CODE(x.key(), code::invalid_view); + CHECK_THROWS_WITH(x.key(), "view is invalid"); // No key. + } + + SECTION("element") { + std::uint8_t data[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // { 'x': 1 } + auto const doc = bsoncxx::v1::document::view{data}; + + auto const x = doc["x"]; + REQUIRE(x); + REQUIRE(x.key() == "x"); + + auto const y = x["y"]; + CHECK_FALSE(y); + CHECK_THROWS_WITH_CODE(y.key(), code::invalid_view); + CHECK_THROWS_WITH(y.key(), Catch::Matchers::ContainsSubstring(R"("x")")); + + auto const z = y["z"]; + CHECK_FALSE(z); + CHECK_THROWS_WITH_CODE(z.key(), code::invalid_view); + CHECK_THROWS_WITH(z.key(), Catch::Matchers::ContainsSubstring(R"("x")")); + } + } + } + + SECTION("invalid_data") { + std::uint8_t data[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // { 'x': 1 } + auto const doc = bsoncxx::v1::document::view{data}; + auto const iter = doc.begin(); + REQUIRE(iter != doc.end()); + auto const v = *iter; + + std::memset(data, 0u, sizeof(data)); + + CHECK_NOTHROW(v.raw()); + CHECK_NOTHROW(v.length()); + CHECK_NOTHROW(v.offset()); + CHECK_NOTHROW(v.keylen()); + + CHECK_THROWS_WITH_CODE(v.key(), code::invalid_data); + CHECK_THROWS_WITH_CODE(v.key(), code::invalid_data); + +#pragma push_macro("X") +#undef X +#define X(_name, _value) CHECK_THROWS_WITH_CODE(v.get_##_name(), code::invalid_data); + BSONCXX_V1_TYPES_XMACRO(X) +#pragma pop_macro("X") + + CHECK_THROWS_WITH_CODE(v.type_view(), code::invalid_data); + CHECK_THROWS_WITH_CODE(v.type_value(), code::invalid_data); + + CHECK_FALSE(v[""]); + CHECK_FALSE(v[0]); + } +} + +TEST_CASE("basic", "[bsoncxx][v1][element][view]") { + SECTION("int32") { + // { 'x': 1 } + std::uint8_t const data[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; + bsoncxx::v1::document::view doc{data}; + auto const iter = doc.begin(); + REQUIRE(iter != doc.end()); + auto const x = *iter; + + CHECK(x.type_id() == bsoncxx::v1::types::id::k_int32); + CHECK(x.key() == "x"); + + CHECK(x.get_int32() == bsoncxx::v1::types::b_int32{1}); + CHECK_THROWS_WITH_CODE(x.get_document(), bsoncxx::v1::types::view::errc::type_mismatch); + CHECK_THROWS_WITH_CODE(x.get_array(), bsoncxx::v1::types::view::errc::type_mismatch); + + CHECK(x.type_view() == bsoncxx::v1::types::b_int32{1}); + CHECK(x.type_value() == x.type_view()); + + CHECK_FALSE(x[""]); + CHECK_FALSE(x[0]); + } + + SECTION("document") { + // { 'x': { 'y': 1 } } + std::uint8_t const data[] = {20, 0, 0, 0, 3, 'x', '\0', 12, 0, 0, 0, 16, 'y', '\0', 2, 0, 0, 0, 0, 0}; + bsoncxx::v1::document::view doc{data}; + bsoncxx::v1::document::view subdoc{data + 7}; + auto const iter = doc.begin(); + REQUIRE(iter != doc.end()); + auto const x = *iter; + + CHECK(x.type_id() == bsoncxx::v1::types::id::k_document); + CHECK(x.key() == "x"); + + CHECK(x.get_document() == bsoncxx::v1::types::b_document{subdoc}); + CHECK_THROWS_WITH_CODE(x.get_int32(), bsoncxx::v1::types::view::errc::type_mismatch); + CHECK_THROWS_WITH_CODE(x.get_array(), bsoncxx::v1::types::view::errc::type_mismatch); + + CHECK(x.type_view() == bsoncxx::v1::types::b_document{subdoc}); + CHECK(x.type_value() == x.type_view()); + + CHECK_FALSE(x[""]); + CHECK_FALSE(x[0]); + + auto const y = x["y"]; + REQUIRE(y); + + CHECK(y.type_id() == bsoncxx::v1::types::id::k_int32); + CHECK(y.key() == "y"); + + CHECK(y.get_int32() == bsoncxx::v1::types::b_int32{2}); + CHECK_THROWS_WITH_CODE(y.get_document(), bsoncxx::v1::types::view::errc::type_mismatch); + CHECK_THROWS_WITH_CODE(y.get_array(), bsoncxx::v1::types::view::errc::type_mismatch); + + CHECK(y.type_view() == bsoncxx::v1::types::b_int32{2}); + CHECK(y.type_value() == y.type_view()); + } + + SECTION("array") { + // { 'x': [ 1 ] } + std::uint8_t const data[] = {20, 0, 0, 0, 4, 'x', '\0', 12, 0, 0, 0, 16, '0', '\0', 2, 0, 0, 0, 0, 0}; + bsoncxx::v1::document::view doc{data}; + bsoncxx::v1::array::view subarr{data + 7}; + auto const iter = doc.begin(); + REQUIRE(iter != doc.end()); + auto const x = *iter; + + CHECK(x.type_id() == bsoncxx::v1::types::id::k_array); + CHECK(x.key() == "x"); + + CHECK(x.get_array() == bsoncxx::v1::types::b_array{subarr}); + CHECK_THROWS_WITH_CODE(x.get_int32(), bsoncxx::v1::types::view::errc::type_mismatch); + CHECK_THROWS_WITH_CODE(x.get_document(), bsoncxx::v1::types::view::errc::type_mismatch); + + CHECK(x.type_view() == bsoncxx::v1::types::b_array{subarr}); + CHECK(x.type_value() == x.type_view()); + + CHECK_FALSE(x[""]); + CHECK_FALSE(x[1]); + + auto const e = x[0]; + REQUIRE(e); + + CHECK(e.type_id() == bsoncxx::v1::types::id::k_int32); + CHECK(e.key() == "0"); + + CHECK(e.get_int32() == bsoncxx::v1::types::b_int32{2}); + CHECK_THROWS_WITH_CODE(e.get_document(), bsoncxx::v1::types::view::errc::type_mismatch); + CHECK_THROWS_WITH_CODE(e.get_array(), bsoncxx::v1::types::view::errc::type_mismatch); + + CHECK(e.type_view() == bsoncxx::v1::types::b_int32{2}); + CHECK(e.type_value() == e.type_view()); + } +} + +TEST_CASE("StringMaker", "[bsoncxx][test][v1][element][view]") { + // {"x": 1} + std::uint8_t const bytes[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; + + bsoncxx::v1::document::view doc{bytes}; + + REQUIRE(doc); + + CHECK(bsoncxx::test::stringify(doc["x"]) == R"("x": 1)"); + CHECK(bsoncxx::test::stringify(doc["y"]) == "invalid"); +} + +} // namespace + +std::string Catch::StringMaker::convert(bsoncxx::v1::element::view const& value) try { + if (!value) { + return "invalid"; + } + + std::string res; + res += bsoncxx::test::stringify(value.key()); + res += ": "; + res += bsoncxx::test::stringify(value.type_view()); + return res; +} catch (bsoncxx::v1::exception const& ex) { + if (ex.code() == code::invalid_view || ex.code() == code::invalid_data) { + return "invalid"; + } else { + throw; + } +} diff --git a/src/bsoncxx/test/v1/element/view.hh b/src/bsoncxx/test/v1/element/view.hh new file mode 100644 index 0000000000..b7b3b415f3 --- /dev/null +++ b/src/bsoncxx/test/v1/element/view.hh @@ -0,0 +1,26 @@ +// 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. + +#pragma once + +#include + +// + +#include + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::element::view const& value); +}; diff --git a/src/bsoncxx/test/v1/exception.cpp b/src/bsoncxx/test/v1/exception.cpp new file mode 100644 index 0000000000..e3a5edbc5b --- /dev/null +++ b/src/bsoncxx/test/v1/exception.cpp @@ -0,0 +1,58 @@ +// 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 + +namespace { + +TEST_CASE("source", "[bsoncxx][v1][error]") { + auto const& c = bsoncxx::v1::source_error_category(); + + SECTION("name") { + CHECK_THAT(c.name(), Catch::Matchers::Equals("bsoncxx::v1::source_errc")); + } + + SECTION("message") { + CHECK(c.message(-1) == "unknown: -1"); + CHECK(c.message(0) == "zero"); + CHECK(c.message(1) == "bsoncxx"); + CHECK(c.message(2) == "bson"); + CHECK(c.message(3) == "unknown: 3"); + } +} + +TEST_CASE("type", "[bsoncxx][v1][error]") { + auto const& c = bsoncxx::v1::type_error_category(); + + SECTION("name") { + CHECK_THAT(c.name(), Catch::Matchers::Equals("bsoncxx::v1::type_errc")); + } + + SECTION("message") { + CHECK(c.message(-1) == "unknown: -1"); + CHECK(c.message(0) == "zero"); + CHECK(c.message(1) == "invalid argument"); + CHECK(c.message(2) == "runtime error"); + CHECK(c.message(3) == "unknown: 3"); + } +} + +} // namespace diff --git a/src/bsoncxx/test/v1/exception.hh b/src/bsoncxx/test/v1/exception.hh new file mode 100644 index 0000000000..6b5d05e281 --- /dev/null +++ b/src/bsoncxx/test/v1/exception.hh @@ -0,0 +1,33 @@ +// 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. + +#pragma once + +#include + +// + +#include + +CATCH_REGISTER_ENUM( + bsoncxx::v1::source_errc, + bsoncxx::v1::source_errc::zero, + bsoncxx::v1::source_errc::bsoncxx, + bsoncxx::v1::source_errc::bson) + +CATCH_REGISTER_ENUM( + bsoncxx::v1::type_errc, + bsoncxx::v1::type_errc::zero, + bsoncxx::v1::type_errc::invalid_argument, + bsoncxx::v1::type_errc::runtime_error) diff --git a/src/bsoncxx/test/v1/oid.cpp b/src/bsoncxx/test/v1/oid.cpp new file mode 100644 index 0000000000..894539ac36 --- /dev/null +++ b/src/bsoncxx/test/v1/oid.cpp @@ -0,0 +1,192 @@ +// 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 + +namespace { + +using bsoncxx::v1::oid; +using code = bsoncxx::v1::oid::errc; + +TEST_CASE("error code", "[bsoncxx][v1][oid][error]") { + using bsoncxx::v1::source_errc; + using bsoncxx::v1::type_errc; + + auto const& category = bsoncxx::v1::oid::error_category(); + CHECK_THAT(category.name(), Catch::Matchers::Equals("bsoncxx::v1::oid")); + + 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() == "unknown: -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::empty_string; + + 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::null_bytes_ptr) == source_errc::bsoncxx); + CHECK(make_error_code(code::invalid_length) == source_errc::bsoncxx); + CHECK(make_error_code(code::empty_string) == source_errc::bsoncxx); + CHECK(make_error_code(code::invalid_string) == source_errc::bsoncxx); + } + + SECTION("type") { + CHECK(make_error_code(code::null_bytes_ptr) == type_errc::invalid_argument); + CHECK(make_error_code(code::invalid_length) == type_errc::invalid_argument); + CHECK(make_error_code(code::empty_string) == type_errc::invalid_argument); + CHECK(make_error_code(code::invalid_string) == type_errc::invalid_argument); + } +} + +TEST_CASE("exceptions", "[bsoncxx][v1][oid]") { + SECTION("null_bytes_ptr") { + auto const expr = [] { oid o{nullptr, 0u}; }; + + CHECK_THROWS_WITH_CODE(expr(), code::null_bytes_ptr); + } + + SECTION("invalid_length") { + static constexpr auto k_oid_length = bsoncxx::v1::oid::k_oid_length; + + std::uint8_t bytes[k_oid_length + 1u]; + + SECTION("too short") { + auto const expr = [&] { oid o{bytes, bsoncxx::v1::oid::k_oid_length - 1u}; }; + + CHECK_THROWS_WITH_CODE(expr(), code::invalid_length); + } + + SECTION("too long") { + auto const expr = [&] { oid o{bytes, bsoncxx::v1::oid::k_oid_length + 1u}; }; + + CHECK_THROWS_WITH_CODE(expr(), code::invalid_length); + } + } + + SECTION("empty_string") { + auto const expr = [&] { oid o{""}; }; + + CHECK_THROWS_WITH_CODE(expr(), code::empty_string); + } + + SECTION("invalid_string") { + auto const expr = [] { oid o{"invalid"}; }; + + CHECK_THROWS_WITH_CODE(expr(), code::invalid_string); + } +} + +TEST_CASE("basic", "[bsoncxx][v1][oid]") { + SECTION("default") { + CHECK(oid{} != oid{}); // Random and unique per process. + } + + SECTION("zero") { + unsigned char const zeroes[oid::k_oid_length]{}; + oid o{zeroes, sizeof(zeroes)}; + + CHECK(oid::size() == oid::k_oid_length); + CHECK(o.size() == oid::size()); + CHECK(o.size() == 12); + + { + std::time_t time = o.get_time_t(); + char str[sizeof("YYYY-MM-DD HH:MM:SS")]; + CHECK(std::strftime(str, sizeof(str), "%F %T", std::gmtime(&time)) == sizeof(str) - 1u); + CHECK(std::string(str) == "1970-01-01 00:00:00"); + } + + std::vector const bytes{o.bytes(), o.bytes() + o.size()}; + + CHECK(bytes == std::vector(oid::k_oid_length)); + } + + SECTION("values") { + // Timestamp: 946771199 (0x386e94ff) + // Value: 286462997 (0x11131415) + // Counter: 2171427 (0x212223) + oid const o{"386e94ff1112131415212223"}; + + CHECK(o == o); + CHECK_FALSE(o != o); + CHECK_FALSE(o > o); + CHECK_FALSE(o < o); + CHECK(o >= o); + CHECK(o <= o); + + { + std::time_t time = o.get_time_t(); + char str[sizeof("YYYY-MM-DD HH:MM:SS")]; + CHECK(std::strftime(str, sizeof(str), "%F %T", std::gmtime(&time)) == sizeof(str) - 1u); + CHECK(std::string(str) == "2000-01-01 23:59:59"); + } + + CHECK(o < oid{"389622001112131415212223"}); // Timestamp: 2000-02-01 00:00:00 + CHECK(o > oid{"386d43801112131415212223"}); // Timestamp: 2000-01-01 00:00:00 + CHECK(o < oid{"386e94ffffffffffff212223"}); // Value: 1099511627775 + CHECK(o > oid{"386e94ff0000000000212223"}); // Value: 0 + CHECK(o < oid{"386e94ff1112131415ffffff"}); // Counter: 16777215 + CHECK(o > oid{"386e94ff1112131415000000"}); // Counter: 0 + } +} + +TEST_CASE("stringify", "[bsoncxx][test][v1][oid]") { + oid o{"507f1f77bcf86cd799439011"}; + CHECK(bsoncxx::test::stringify(o) == "507f1f77bcf86cd799439011"); +} + +} // namespace diff --git a/src/bsoncxx/test/v1/oid.hh b/src/bsoncxx/test/v1/oid.hh new file mode 100644 index 0000000000..45c167e806 --- /dev/null +++ b/src/bsoncxx/test/v1/oid.hh @@ -0,0 +1,40 @@ +// 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. + +#pragma once + +#include + +// + +#include + +CATCH_REGISTER_ENUM( + bsoncxx::v1::oid::errc, + bsoncxx::v1::oid::errc::zero, + bsoncxx::v1::oid::errc::null_bytes_ptr, + bsoncxx::v1::oid::errc::invalid_length, + bsoncxx::v1::oid::errc::empty_string, + bsoncxx::v1::oid::errc::invalid_string) + +namespace Catch { + +template <> +struct StringMaker { + static std::string convert(bsoncxx::v1::oid const& value) { + return value.to_string(); + } +}; + +} // namespace Catch diff --git a/src/bsoncxx/test/v1/stdx/optional.test.cpp b/src/bsoncxx/test/v1/stdx/optional.test.cpp index 0941939ea7..99c59da1bd 100644 --- a/src/bsoncxx/test/v1/stdx/optional.test.cpp +++ b/src/bsoncxx/test/v1/stdx/optional.test.cpp @@ -30,7 +30,8 @@ #include -#include +#include +#include #include diff --git a/src/bsoncxx/test/v1/stdx/string_view.test.cpp b/src/bsoncxx/test/v1/stdx/string_view.test.cpp index 43fd3342d9..1bd235b85b 100644 --- a/src/bsoncxx/test/v1/stdx/string_view.test.cpp +++ b/src/bsoncxx/test/v1/stdx/string_view.test.cpp @@ -35,7 +35,8 @@ #include #include -#include +#include +#include namespace { diff --git a/src/bsoncxx/test/v1/types/id.cpp b/src/bsoncxx/test/v1/types/id.cpp new file mode 100644 index 0000000000..51a08cb4a2 --- /dev/null +++ b/src/bsoncxx/test/v1/types/id.cpp @@ -0,0 +1,110 @@ +// 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 + +namespace { + +using bsoncxx::v1::types::binary_subtype; +using bsoncxx::v1::types::id; + +TEST_CASE("to_string", "[bsoncxx][v1][types][id]") { + SECTION("unknown") { + CHECK(to_string(static_cast(0)) == "?"); // 0x00 (BSON_TYPE_EOD) + } + + SECTION("values") { + // BSONCXX_V1_TYPES_XMACRO: update below. + CHECK(to_string(id::k_minkey) == "minkey"); + CHECK(to_string(id::k_double) == "double"); + CHECK(to_string(id::k_string) == "string"); + CHECK(to_string(id::k_document) == "document"); + CHECK(to_string(id::k_array) == "array"); + CHECK(to_string(id::k_binary) == "binary"); + CHECK(to_string(id::k_undefined) == "undefined"); + CHECK(to_string(id::k_oid) == "oid"); + CHECK(to_string(id::k_bool) == "bool"); + CHECK(to_string(id::k_date) == "date"); + CHECK(to_string(id::k_null) == "null"); + CHECK(to_string(id::k_regex) == "regex"); + CHECK(to_string(id::k_dbpointer) == "dbpointer"); + CHECK(to_string(id::k_code) == "code"); + CHECK(to_string(id::k_symbol) == "symbol"); + CHECK(to_string(id::k_codewscope) == "codewscope"); + CHECK(to_string(id::k_int32) == "int32"); + CHECK(to_string(id::k_timestamp) == "timestamp"); + CHECK(to_string(id::k_int64) == "int64"); + CHECK(to_string(id::k_decimal128) == "decimal128"); + CHECK(to_string(id::k_maxkey) == "maxkey"); + // BSONCXX_V1_TYPES_XMACRO: update above. + } +} + +TEST_CASE("to_string", "[bsoncxx][v1][types][binary_subtype]") { + SECTION("unknown") { + CHECK(to_string(static_cast(UINT8_MAX)) == "?"); // 0xFF + } + + SECTION("values") { + // BSONCXX_V1_BINARY_SUBTYPES_XMACRO: update below. + CHECK(to_string(binary_subtype::k_binary) == "binary"); + CHECK(to_string(binary_subtype::k_function) == "function"); + CHECK(to_string(binary_subtype::k_binary_deprecated) == "binary_deprecated"); + CHECK(to_string(binary_subtype::k_uuid_deprecated) == "uuid_deprecated"); + CHECK(to_string(binary_subtype::k_uuid) == "uuid"); + CHECK(to_string(binary_subtype::k_md5) == "md5"); + CHECK(to_string(binary_subtype::k_encrypted) == "encrypted"); + CHECK(to_string(binary_subtype::k_column) == "column"); + CHECK(to_string(binary_subtype::k_sensitive) == "sensitive"); + CHECK(to_string(binary_subtype::k_vector) == "vector"); + CHECK(to_string(binary_subtype::k_user) == "user"); + // BSONCXX_V1_BINARY_SUBTYPES_XMACRO: update above. + } +} + +TEST_CASE("StringMaker", "[bsoncxx][test][v1][types][id]") { +#pragma push_macro("X") +#undef X +#define X(_name, _value) id::k_##_name, + id values[] = {BSONCXX_V1_TYPES_XMACRO(X)}; +#pragma pop_macro("X") + + for (auto const& value : values) { + CHECK(bsoncxx::test::stringify(value) == "k_" + to_string(value)); + } +} + +TEST_CASE("StringMaker", "[bsoncxx][test][v1][types][binary_subtype]") { +#pragma push_macro("X") +#undef X +#define X(_name, _value) binary_subtype::k_##_name, + binary_subtype values[] = {BSONCXX_V1_BINARY_SUBTYPES_XMACRO(X)}; +#pragma pop_macro("X") + + for (auto const& value : values) { + CHECK(bsoncxx::test::stringify(value) == "k_" + to_string(value)); + } +} + +} // namespace diff --git a/src/bsoncxx/test/v1/types/id.hh b/src/bsoncxx/test/v1/types/id.hh new file mode 100644 index 0000000000..3ea83465ab --- /dev/null +++ b/src/bsoncxx/test/v1/types/id.hh @@ -0,0 +1,35 @@ +// 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. + +#pragma once + +#include + +// + +#include + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::id const& value) { + return "k_" + to_string(value); + } +}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::binary_subtype const& value) { + return "k_" + to_string(value); + } +}; diff --git a/src/bsoncxx/test/v1/types/value.cpp b/src/bsoncxx/test/v1/types/value.cpp new file mode 100644 index 0000000000..e1e2bb245e --- /dev/null +++ b/src/bsoncxx/test/v1/types/value.cpp @@ -0,0 +1,594 @@ +// 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 +#include +#include + +#include +#include + +#include + +namespace { + +using namespace bsoncxx::v1::types; +using code = bsoncxx::v1::types::value::errc; + +TEST_CASE("error code", "[bsoncxx][v1][types][value][error]") { + using bsoncxx::v1::source_errc; + using bsoncxx::v1::type_errc; + + auto const& category = bsoncxx::v1::types::value::error_category(); + CHECK_THAT(category.name(), Catch::Matchers::Equals("bsoncxx::v1::types::value")); + + 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() == "unknown: -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_type; + + 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_type) == source_errc::bsoncxx); + CHECK(make_error_code(code::invalid_length_u32) == source_errc::bsoncxx); + } + + SECTION("type") { + CHECK(make_error_code(code::invalid_type) == type_errc::invalid_argument); + CHECK(make_error_code(code::invalid_length_u32) == type_errc::invalid_argument); + } +} + +TEST_CASE("exceptions", "[bsoncxx][v1][types][value]") { + using namespace bsoncxx::v1::types; + + SECTION("invalid_type") { + bsoncxx::v1::types::view v; + + bsoncxx::v1::types::view::internal::type_id(v, bsoncxx::v1::types::id{}); // BSON_TYPE_EOD + + CHECK_THROWS_WITH_CODE(value{v}, code::invalid_type); + } + + SECTION("invalid_length_u32") { +#pragma push_macro("X") +#undef X +#define X(_name, _value) value{b_##_name{}}, + + value values[] = {BSONCXX_V1_TYPES_XMACRO(X)}; +#pragma pop_macro("X") + + try { + auto const size = std::size_t{UINT32_MAX} + 1u; + std::unique_ptr data{new unsigned char[size]}; // make_unique_for_overwrite. + + auto const big_string = bsoncxx::v1::stdx::string_view{reinterpret_cast(data.get()), size}; + + REQUIRE(big_string.size() == size); + + SECTION("b_string") { + auto const expr = [&] { value v{b_string{big_string}}; }; + CHECK_THROWS_WITH_CODE(expr(), code::invalid_length_u32); + } + + SECTION("b_dbpointer") { + auto const expr = [&] { value v{b_dbpointer{big_string, bsoncxx::v1::oid{}}}; }; + CHECK_THROWS_WITH_CODE(expr(), code::invalid_length_u32); + } + + SECTION("b_code") { + auto const expr = [&] { value v{b_code{big_string}}; }; + CHECK_THROWS_WITH_CODE(expr(), code::invalid_length_u32); + } + + SECTION("b_symbol") { + auto const expr = [&] { value v{b_symbol{big_string}}; }; + CHECK_THROWS_WITH_CODE(expr(), code::invalid_length_u32); + } + + SECTION("b_codewscope") { + auto const expr = [&] { value v{b_codewscope{big_string, bsoncxx::v1::document::view{}}}; }; + CHECK_THROWS_WITH_CODE(expr(), code::invalid_length_u32); + } + + SECTION("bytes") { + auto const expr = [&] { value v{nullptr, std::size_t{UINT32_MAX} + 1u, binary_subtype::k_binary}; }; + CHECK_THROWS_WITH_CODE(expr(), code::invalid_length_u32); + } + } catch (std::bad_alloc const& ex) { + WARN("could not allocate big data: " << ex.what()); + } + } +} + +TEST_CASE("ownership", "[bsoncxx][v1][types][value]") { + value target{"old"}; + value source{"new"}; + + REQUIRE(source.type_id() == id::k_string); + REQUIRE(target.type_id() == id::k_string); + + auto const data = source.get_string().value.data(); // "new" + + SECTION("move") { + auto move = std::move(source); + + CHECK(source.type_id() == id::k_null); + + REQUIRE(move.type_id() == id::k_string); + CHECK(move.get_string().value.data() == data); + CHECK(move.get_string().value == "new"); + + target = std::move(move); + + CHECK(move.type_id() == id::k_null); + + REQUIRE(target.type_id() == id::k_string); + CHECK(target.get_string().value.data() == data); + CHECK(target.get_string().value == "new"); + } + + SECTION("copy") { + auto copy = source; + + REQUIRE(source.type_id() == id::k_string); + CHECK(source.get_string().value.data() == data); + CHECK(source.get_string().value == "new"); + + REQUIRE(copy.type_id() == id::k_string); + auto const copy_data = copy.get_string().value.data(); + CHECK(copy.get_string().value == "new"); + CHECK(copy_data != data); + + target = copy; + + REQUIRE(copy.type_id() == id::k_string); + CHECK(copy.get_string().value == copy_data); + + REQUIRE(target.type_id() == id::k_string); + auto const target_data = target.get_string().value.data(); + CHECK(target.get_string().value == "new"); + CHECK(target_data != data); + CHECK(target_data != copy_data); + } +} + +TEST_CASE("basic", "[bsoncxx][v1][types][value]") { + SECTION("default") { + value v; + + CHECK(v.type_id() == id::k_null); + + CHECK(v == v); + CHECK_FALSE(v != v); + + CHECK(v == value{}); + CHECK_FALSE(v != value{}); + + CHECK_FALSE(v == value{1}); + CHECK_FALSE(value{1} == v); + CHECK(v != value{1}); + CHECK(value{1} != v); + + CHECK(v == view{}); + CHECK_FALSE(v != view{}); + + CHECK_FALSE(v == view{b_int32{1}}); + CHECK_FALSE(view{b_int32{1}} == v); + CHECK(v != view{b_int32{1}}); + CHECK(view{b_int32{1}} != v); + } + + SECTION("value") { + value v{1}; + + CHECK(v.type_id() == id::k_int32); + + CHECK(v == v); + CHECK_FALSE(v != v); + + CHECK_FALSE(v == value{}); + CHECK(v != value{}); + + CHECK(v == value{1}); + CHECK(value{1} == v); + CHECK_FALSE(v != value{1}); + CHECK_FALSE(value{1} != v); + + CHECK_FALSE(v == view{}); + CHECK(v != view{}); + + CHECK(v == view{b_int32{1}}); + CHECK(view{b_int32{1}} == v); + CHECK_FALSE(v != view{b_int32{1}}); + CHECK_FALSE(view{b_int32{1}} != v); + } +} + +TEST_CASE("b_types", "[bsoncxx][v1][types][value]") { + bsoncxx::v1::oid const o{"507f1f77bcf86cd799439011"}; + std::uint8_t const data[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // {"x": 1} + bsoncxx::v1::document::view const doc{data}; + bsoncxx::v1::array::view const arr{data}; + + // BSONCXX_V1_TYPES_XMACRO: update below. + view const views[] = { + b_double{}, + b_string{"string"}, + b_document{doc}, + b_array{arr}, + b_binary{binary_subtype::k_binary, sizeof(data), data}, + b_undefined{}, + b_oid{o}, + b_bool{}, + b_date{}, + b_null{}, + b_regex{"regex"}, + b_dbpointer{"dbpointer", o}, + b_code{"code"}, + b_symbol{"symbol"}, + b_codewscope{"code", doc}, + b_int32{}, + b_timestamp{}, + b_int64{}, + b_decimal128{}, + b_maxkey{}, + b_minkey{}, + }; + // BSONCXX_V1_TYPES_XMACRO: update above. + +#pragma push_macro("X") +#undef X +#define X(_name, _value) \ + case id::k_##_name: { \ + /* v1::types::view */ \ + CHECK(value{v} == v); \ + \ + /* v1::types::b_ */ \ + CHECK(value{v.get_##_name()} == v); \ + } break; + + for (auto const& v : views) { + switch (v.type_id()) { + BSONCXX_V1_TYPES_XMACRO(X) + + default: + FAIL(); + } + } +#pragma pop_macro("X") +} + +TEST_CASE("constructors", "[bsoncxx][v1][types][value]") { + auto const k_binary = binary_subtype::k_binary; + auto const k_encrypted = binary_subtype::k_encrypted; + + bsoncxx::v1::stdx::string_view const sv{"sv"}; + bsoncxx::v1::decimal128 const d{"123"}; + std::chrono::milliseconds const ms{123}; + bsoncxx::v1::oid const o{"507f1f77bcf86cd799439011"}; + std::uint8_t const data[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // {"x": 1} + std::uint32_t const data_len{sizeof(data)}; + bsoncxx::v1::document::view const doc{data}; + bsoncxx::v1::array::view const arr{data}; + std::vector const vec{data, data + data_len}; + + CHECK(value{nullptr} == view{b_null{}}); + CHECK(value{sv} == view{b_string{sv}}); + CHECK(value{std::int32_t{123}} == view{b_int32{123}}); + CHECK(value{std::int64_t{456}} == view{b_int64{456}}); + CHECK(value{123.456} == view{b_double{123.456}}); + CHECK(value{true} == view{b_bool{true}}); + CHECK(value{d} == view{b_decimal128{d}}); + CHECK(value{ms} == view{b_date{ms}}); + CHECK(value{doc} == view{b_document{doc}}); + CHECK(value{arr} == view{b_array{arr}}); + CHECK(value{vec} == view{(b_binary{k_binary, data_len, data})}); + CHECK(value{vec, k_encrypted} == value{(b_binary{k_encrypted, data_len, vec.data()})}); + CHECK(value{sv, o} == view{b_dbpointer{sv, o}}); + CHECK(value{sv, doc} == view{b_codewscope{sv, doc}}); + CHECK(value{sv, sv} == view{b_regex{sv, sv}}); + CHECK(value{sv.data()} == view{b_string{sv}}); + CHECK(value{std::string{sv}} == view{b_string{sv}}); + CHECK(value{data, sizeof(data)} == view{b_binary{k_binary, data_len, data}}); + CHECK(value{data, sizeof(data), k_encrypted} == view{b_binary{k_encrypted, data_len, data}}); +} + +TEST_CASE("StringMaker", "[bsoncxx][test][v1][types][value]") { + bsoncxx::v1::oid const o{"507f1f77bcf86cd799439011"}; + std::uint8_t const data[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // {"x": 1} + bsoncxx::v1::document::view const doc{data}; + bsoncxx::v1::array::view const arr{data}; + + // BSONCXX_V1_TYPES_XMACRO: update below. + view const views[] = { + b_double{}, + b_string{"string"}, + b_document{doc}, + b_array{arr}, + b_binary{binary_subtype::k_binary, sizeof(data), data}, + b_undefined{}, + b_oid{o}, + b_bool{}, + b_date{}, + b_null{}, + b_regex{"regex"}, + b_dbpointer{"dbpointer", o}, + b_code{"code"}, + b_symbol{"symbol"}, + b_codewscope{"code", doc}, + b_int32{}, + b_timestamp{}, + b_int64{}, + b_decimal128{}, + b_maxkey{}, + b_minkey{}, + }; + // BSONCXX_V1_TYPES_XMACRO: update above. + +#pragma push_macro("X") +#undef X +#define X(_name, _value) \ + case id::k_##_name: \ + CHECK(bsoncxx::test::stringify(value{v}) == bsoncxx::test::stringify(v.get_##_name())); \ + break; + + for (auto const& v : views) { + switch (v.type_id()) { + BSONCXX_V1_TYPES_XMACRO(X) + + default: + FAIL(); + } + } +#pragma pop_macro("X") +} + +namespace static_assertions { + +BSONCXX_PRIVATE_WARNINGS_PUSH(); +BSONCXX_PRIVATE_WARNINGS_DISABLE(GNU("-Wunused-function")); +BSONCXX_PRIVATE_WARNINGS_DISABLE(Clang("-Wunused-template")); +BSONCXX_PRIVATE_WARNINGS_DISABLE(Clang("-Wunneeded-member-function")); + +#define IMPLIES(a, b) ((!(a)) || (b)) + +template +struct result {}; + +using bsoncxx::detail::invoke_result_t; + +namespace btype_vs_view { + +template +struct overload { + // Prerequisite: Args is not BType or view. + static_assert( + IMPLIES( + (sizeof...(Args) == 1), + !(bsoncxx::detail::disjunction..., std::is_same...>::value)), + "Arg must not be BType or view"); + + // Prerequisite: BType(Args...) is valid. + static_assert(std::is_constructible::value, "BType(Args...) must be valid"); + + static result<1> fn(BType) { + return {}; + } + + static result<2> fn(view) { + return {}; + } + + template + using non_list_expr = decltype((fn)(std::declval()...)); + + template + using list_expr = decltype((fn)({std::declval()...})); + + // `fn({})` is always ambiguous. + static_assert(!bsoncxx::detail::is_detected::value, "must be ill-formed"); + + // `fn(arg)` is always ambiguous. + static_assert( + IMPLIES((sizeof...(Args) == 1), !(bsoncxx::detail::is_detected::value)), + "must be ill-formed"); + + // `fn({})` and `fn({arg})` is always ambiguous, but not `fn({args...})`. + static_assert( + IMPLIES((sizeof...(Args) < 2), !(bsoncxx::detail::is_detected::value)), + "must be ill-formed"); +}; + +using bsoncxx::v1::stdx::string_view; + +// BSONCXX_V1_TYPES_XMACRO: update below + +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; + +// BSONCXX_V1_TYPES_XMACRO: update above + +} // namespace btype_vs_view + +namespace btype_vs_value { + +template +struct overload { + // Prerequisite: Args is not BType or view. + static_assert( + IMPLIES( + (sizeof...(Args) == 1), + !(bsoncxx::detail::disjunction..., std::is_same...>::value)), + "Arg must not be BType or view"); + + // Prerequisite: BType(Args...) is valid. + static_assert(std::is_constructible::value, "BType(Args...) must be valid"); + + static result<1> fn(BType) { + return {}; + } + + static result<2> fn(value) { + return {}; + } + + template + using non_list_expr = decltype((fn)(std::declval()...)); + + template + using list_expr = decltype((fn)({std::declval()...})); + + // `fn({})` is always ambiguous. + static_assert(!bsoncxx::detail::is_detected::value, "must be ill-formed"); + + // `fn(arg)` is always ambiguous. + static_assert( + IMPLIES((sizeof...(Args) == 1), !(bsoncxx::detail::is_detected::value)), + "must be ill-formed"); + + // `fn({})` and `fn({arg})` is always ambiguous, but not `fn({args...})`. + static_assert( + IMPLIES((sizeof...(Args) < 2), !(bsoncxx::detail::is_detected::value)), + "must be ill-formed"); +}; + +using bsoncxx::v1::stdx::string_view; + +// BSONCXX_V1_TYPES_XMACRO: update below + +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; +template struct overload; + +// BSONCXX_V1_TYPES_XMACRO: update above + +} // namespace btype_vs_value + +namespace view_vs_value { + +result<1> overload(view) { + return {}; +} + +result<2> overload(value) { + return {}; +} + +static_assert(std::is_same, decltype(overload(std::declval()))>::value, "must not be ambiguous"); +static_assert(std::is_same, decltype(overload(std::declval()))>::value, "must not be ambiguous"); + +#pragma push_macro("X") +#undef X +#define X(_name, _value) \ + static_assert( \ + std::is_same, decltype(overload(std::declval()))>::value, "must not be ambiguous"); + +BSONCXX_V1_TYPES_XMACRO(X) +#pragma pop_macro("X") + +} // namespace view_vs_value + +BSONCXX_PRIVATE_WARNINGS_POP(); + +} // namespace static_assertions + +} // namespace diff --git a/src/bsoncxx/test/v1/types/value.hh b/src/bsoncxx/test/v1/types/value.hh new file mode 100644 index 0000000000..807a061c9a --- /dev/null +++ b/src/bsoncxx/test/v1/types/value.hh @@ -0,0 +1,31 @@ +// 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. + +#pragma once + +#include + +// + +#include + +#include + +CATCH_REGISTER_ENUM( + bsoncxx::v1::types::value::errc, + bsoncxx::v1::types::value::errc::zero, + bsoncxx::v1::types::value::errc::invalid_length_u32) + +template <> +struct Catch::StringMaker : StringMaker {}; diff --git a/src/bsoncxx/test/v1/types/view.cpp b/src/bsoncxx/test/v1/types/view.cpp new file mode 100644 index 0000000000..818e8e5d44 --- /dev/null +++ b/src/bsoncxx/test/v1/types/view.cpp @@ -0,0 +1,520 @@ +// 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 +#include + +#include +#include + +namespace { + +using namespace bsoncxx::v1::types; +using code = bsoncxx::v1::types::view::errc; + +TEST_CASE("error code", "[bsoncxx][v1][types][view][error]") { + using bsoncxx::v1::source_errc; + using bsoncxx::v1::type_errc; + + auto const& category = bsoncxx::v1::types::view::error_category(); + CHECK_THAT(category.name(), Catch::Matchers::Equals("bsoncxx::v1::types::view")); + + 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() == "unknown: -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::type_mismatch; + + 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::type_mismatch) == source_errc::bsoncxx); + } + + SECTION("type") { + CHECK(make_error_code(code::type_mismatch) == type_errc::runtime_error); + } +} + +TEST_CASE("exceptions", "[bsoncxx][v1][types][view]") { + using bsoncxx::v1::types::id; + + SECTION("type_mismatch") { +#pragma push_macro("X") +#pragma push_macro("Y") +#undef X +#undef Y + +#define X(_name, _value) bsoncxx::v1::types::b_##_name{}, +#define Y(_name, _value) \ + CHECKED_IF(id::k_##_name == v.type_id()) { \ + CHECK_NOTHROW(v.get_##_name()); \ + } \ + else { \ + CHECK_THROWS_WITH_CODE(v.get_##_name(), code::type_mismatch); \ + } + + view views[] = {BSONCXX_V1_TYPES_XMACRO(X)}; + + for (auto const& v : views) { + BSONCXX_V1_TYPES_XMACRO(Y) + } +#pragma pop_macro("X") +#pragma pop_macro("Y") + } +} + +TEST_CASE("basic", "[bsoncxx][v1][types][view]") { + // {"x": 1} + std::uint8_t const data[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; + bsoncxx::v1::document::view doc{data}; + REQUIRE(doc); + + auto const e_valid = *doc.begin(); + auto const e_invalid = *doc.end(); + REQUIRE(e_valid); + REQUIRE_FALSE(e_invalid); + + SECTION("default") { + view v; + + CHECK(v == v); + CHECK_FALSE(v != v); + + CHECK(v == view{}); + CHECK_FALSE(v != view{}); + + CHECK_FALSE(v == b_int32{1}); + CHECK_FALSE(b_int32{1} == v); + CHECK(v != b_int32{1}); + CHECK(b_int32{1} != v); + + CHECK_FALSE(v == e_valid); + CHECK_FALSE(e_valid == v); + CHECK(v != e_valid); + CHECK(e_valid != v); + + CHECK_THROWS_WITH_CODE(v == e_invalid, bsoncxx::v1::element::view::errc::invalid_view); + CHECK_THROWS_WITH_CODE(v != e_invalid, bsoncxx::v1::element::view::errc::invalid_view); + CHECK_THROWS_WITH_CODE(e_invalid == v, bsoncxx::v1::element::view::errc::invalid_view); + CHECK_THROWS_WITH_CODE(e_invalid != v, bsoncxx::v1::element::view::errc::invalid_view); + } + + SECTION("value") { + view v{b_int32{1}}; + + CHECK(v == v); + CHECK_FALSE(v != v); + + CHECK_FALSE(v == view{}); + CHECK(v != view{}); + + CHECK(v == b_int32{1}); + CHECK(b_int32{1} == v); + CHECK_FALSE(v != b_int32{1}); + CHECK_FALSE(b_int32{1} != v); + + CHECK(v == e_valid); + CHECK(e_valid == v); + CHECK_FALSE(v != e_valid); + CHECK_FALSE(e_valid != v); + + CHECK_THROWS_WITH_CODE(v == e_invalid, bsoncxx::v1::element::view::errc::invalid_view); + CHECK_THROWS_WITH_CODE(v != e_invalid, bsoncxx::v1::element::view::errc::invalid_view); + CHECK_THROWS_WITH_CODE(e_invalid == v, bsoncxx::v1::element::view::errc::invalid_view); + CHECK_THROWS_WITH_CODE(e_invalid != v, bsoncxx::v1::element::view::errc::invalid_view); + } +} + +TEST_CASE("b_types", "[bsoncxx][v1][types][view]") { + std::uint8_t const data_a[] = {10, 0, 0, 0, 2, '0', '\0', 'a', '\0', 0}; // {"0": "a"} + std::uint8_t const data_b[] = {10, 0, 0, 0, 2, '1', '\0', 'b', '\0', 0}; // {"1": "b"} + + bsoncxx::v1::oid const o; + bsoncxx::v1::stdx::string_view const sv; + + double const double_a = 1.0; + double const double_b = 2.0; + + bsoncxx::v1::stdx::string_view const string_a{"a"}; + bsoncxx::v1::stdx::string_view const string_b{"b"}; + + bsoncxx::v1::document::view const doc_a{data_a}; + bsoncxx::v1::document::view const doc_b{data_b}; + + bsoncxx::v1::array::view const arr_a{data_a}; + bsoncxx::v1::array::view const arr_b{data_b}; + + bsoncxx::v1::oid const oid_a; + bsoncxx::v1::oid const oid_b; + + std::chrono::milliseconds const date_a{1}; + std::chrono::milliseconds const date_b{2}; + + bsoncxx::v1::stdx::string_view const code_a{"code_a"}; + bsoncxx::v1::stdx::string_view const code_b{"code_b"}; + + bsoncxx::v1::stdx::string_view const symbol_a{"symbol_a"}; + bsoncxx::v1::stdx::string_view const symbol_b{"symbol_b"}; + + std::int32_t const int32_a{1}; + std::int32_t const int32_b{2}; + + std::int64_t const int64_a{1}; + std::int64_t const int64_b{2}; + + bsoncxx::v1::decimal128 const d128_a{"1"}; + bsoncxx::v1::decimal128 const d128_b{"2"}; + +#define BTYPES_ASSERTIONS_COMMON(_name) \ + if (1) { \ + CHECK(b_##_name::type_id == id::k_##_name); \ + CHECK(v.type_id == id::k_##_name); \ + CHECK(v == v); \ + CHECK_FALSE(v != v); \ + } else \ + ((void)0) + +#define BTYPES_ASSERTIONS_SINGLE(_name, _a, _b) \ + if (1) { \ + b_##_name a{_a}; \ + b_##_name b{_b}; \ + \ + CHECK(a == a); \ + CHECK(b == b); \ + CHECK(a != b); \ + \ + /* Implicit Conversion */ \ + decltype(_a) c = a; \ + \ + CHECK(c == _a); \ + CHECK(c != _b); \ + } else \ + ((void)0) + + // BSONCXX_V1_TYPES_XMACRO: update below. + + SECTION("b_minkey") { + b_minkey v; + BTYPES_ASSERTIONS_COMMON(minkey); + } + + SECTION("b_double") { + BSONCXX_PRIVATE_WARNINGS_PUSH(); + BSONCXX_PRIVATE_WARNINGS_DISABLE(GNU("-Wfloat-equal")); + b_double v{0.0}; + BTYPES_ASSERTIONS_COMMON(double); + BTYPES_ASSERTIONS_SINGLE(double, double_a, double_b); + BSONCXX_PRIVATE_WARNINGS_POP(); + } + + SECTION("b_string") { + b_string v; + BTYPES_ASSERTIONS_COMMON(string); + BTYPES_ASSERTIONS_SINGLE(string, string_a, string_b); + } + + SECTION("b_document") { + b_document const v; + BTYPES_ASSERTIONS_COMMON(document); + BTYPES_ASSERTIONS_SINGLE(document, doc_a, doc_b); + } + + SECTION("b_array") { + b_array const v; + BTYPES_ASSERTIONS_COMMON(array); + BTYPES_ASSERTIONS_SINGLE(array, arr_a, arr_b); + } + + SECTION("b_binary") { + b_binary const v; + BTYPES_ASSERTIONS_COMMON(binary); + std::uint8_t const data[] = {1, 2}; + + b_binary a = v; + b_binary b{binary_subtype::k_binary, 0, nullptr}; + + CHECK(a == b); + + a = {binary_subtype::k_binary, 1, nullptr}; // OK + CHECK(a != b); + + b = a; + CHECK(a == b); + + a = {binary_subtype::k_binary, 1, data}; + CHECK(a != b); + + b = a; + CHECK(a == b); + + a = {binary_subtype::k_binary, 2, data}; + CHECK(a != b); + + b = a; + CHECK(a == b); + + a = {binary_subtype::k_encrypted, 2, data}; + CHECK(a != b); + + b = a; + CHECK(a == b); + + std::uint8_t b_data[] = {2, 2}; + b = {binary_subtype::k_encrypted, 2, b_data}; + CHECK(a != b); // a[0] != b[0] + (void)(b_data[0] = 1), (void)(b_data[1] = 1); + CHECK(a != b); // a[1] != b[1] + (void)(b_data[0] = 1), (void)(b_data[1] = 2); + CHECK(a == b); + } + + SECTION("b_undefined") { + b_undefined const v; + BTYPES_ASSERTIONS_COMMON(undefined); + } + + SECTION("b_oid") { + b_oid const v; + BTYPES_ASSERTIONS_COMMON(oid); + BTYPES_ASSERTIONS_SINGLE(oid, oid_a, oid_b); + } + + SECTION("b_bool") { + b_bool const v; + BTYPES_ASSERTIONS_COMMON(bool); + BTYPES_ASSERTIONS_SINGLE(bool, false, true); + } + + SECTION("b_date") { + b_date const v; + BTYPES_ASSERTIONS_COMMON(date); + BTYPES_ASSERTIONS_SINGLE(date, date_a, date_b); + } + + SECTION("b_null") { + b_null const v; + BTYPES_ASSERTIONS_COMMON(null); + } + + SECTION("b_regex") { + b_regex const v; + BTYPES_ASSERTIONS_COMMON(regex); + } + + SECTION("b_dbpointer") { + b_dbpointer v{sv, o}; + BTYPES_ASSERTIONS_COMMON(dbpointer); + } + + SECTION("b_code") { + b_code const v; + BTYPES_ASSERTIONS_COMMON(code); + BTYPES_ASSERTIONS_SINGLE(code, code_a, code_b); + } + + SECTION("b_symbol") { + b_symbol const v; + BTYPES_ASSERTIONS_COMMON(symbol); + BTYPES_ASSERTIONS_SINGLE(symbol, symbol_a, symbol_b); + } + + SECTION("b_codewscope") { + b_codewscope const v; + BTYPES_ASSERTIONS_COMMON(codewscope); + } + + SECTION("b_int32") { + b_int32 const v; + BTYPES_ASSERTIONS_COMMON(int32); + BTYPES_ASSERTIONS_SINGLE(int32, int32_a, int32_b); + } + + SECTION("b_timestamp") { + b_timestamp const v; + BTYPES_ASSERTIONS_COMMON(timestamp); + } + + SECTION("b_int64") { + b_int64 const v; + BTYPES_ASSERTIONS_COMMON(int64); + BTYPES_ASSERTIONS_SINGLE(int64, int64_a, int64_b); + } + + SECTION("b_decimal128") { + b_decimal128 const v; + BTYPES_ASSERTIONS_COMMON(decimal128); + BTYPES_ASSERTIONS_SINGLE(decimal128, d128_a, d128_b); + } + + SECTION("b_maxkey") { + b_maxkey const v; + BTYPES_ASSERTIONS_COMMON(maxkey); + } + + // BSONCXX_V1_TYPES_XMACRO: update above. +} + +TEST_CASE("StringMaker", "[bsoncxx][test][v1][types][view]") { + // BSONCXX_V1_TYPES_XMACRO: update below. + { + bsoncxx::v1::stdx::string_view sv; + bsoncxx::v1::oid o{"507f1f77bcf86cd799439011"}; + + CHECK(bsoncxx::test::stringify(b_double{}) == "0.0"); + CHECK(bsoncxx::test::stringify(b_string{}) == R"("")"); + CHECK(bsoncxx::test::stringify(b_document{}) == "{}"); + CHECK(bsoncxx::test::stringify(b_array{}) == "[]"); + CHECK(bsoncxx::test::stringify(b_binary{}) == "empty"); + CHECK(bsoncxx::test::stringify(b_undefined{}) == "undefined"); + CHECK(bsoncxx::test::stringify(b_oid{o}) == "507f1f77bcf86cd799439011"); + CHECK(bsoncxx::test::stringify(b_bool{}) == "false"); + CHECK(bsoncxx::test::stringify(b_date{}) == "1970-01-01T00:00:00Z"); + CHECK(bsoncxx::test::stringify(b_null{}) == "null"); + CHECK(bsoncxx::test::stringify(b_regex{"regex"}) == R"({"pattern": "regex", "options": ""})"); + CHECK(bsoncxx::test::stringify(b_dbpointer{sv, o}) == R"({"$ref": "", "$oid": 507f1f77bcf86cd799439011})"); + CHECK(bsoncxx::test::stringify(b_code{"code"}) == R"("code")"); + CHECK(bsoncxx::test::stringify(b_symbol{"symbol"}) == R"("symbol")"); + CHECK(bsoncxx::test::stringify(b_codewscope{}) == R"({"code": "", "scope": {}})"); + CHECK(bsoncxx::test::stringify(b_int32{}) == "0"); + CHECK(bsoncxx::test::stringify(b_timestamp{}) == R"({"t": 0, "i": 0})"); + CHECK(bsoncxx::test::stringify(b_int64{}) == "0"); + CHECK(bsoncxx::test::stringify(b_decimal128{}) == "0E-6176"); + CHECK(bsoncxx::test::stringify(b_maxkey{}) == "maxkey"); + CHECK(bsoncxx::test::stringify(b_minkey{}) == "minkey"); + } + // BSONCXX_V1_TYPES_XMACRO: update above. + +#pragma push_macro("X") +#undef X +#define X(_name, _value) b_##_name{}, + + view const views[] = {BSONCXX_V1_TYPES_XMACRO(X)}; +#pragma pop_macro("X") + +#pragma push_macro("X") +#undef X +#define X(_name, _value) \ + case id::k_##_name: \ + CHECK(bsoncxx::test::stringify(v) == bsoncxx::test::stringify(v.get_##_name())); \ + break; + + for (auto const& v : views) { + switch (v.type_id()) { + BSONCXX_V1_TYPES_XMACRO(X) + + default: + FAIL(); + } + } +#pragma pop_macro("X") +} + +namespace static_assertions { + +// BSONCXX_V1_TYPES_XMACRO: update below + +namespace single_value_types { + +// BType -> Value is implicit, but Value -> BType is explicit. +template +struct one_way_implicit_convertible { + template + struct is_explicitly_convertible : bsoncxx::detail::conjunction< + std::is_constructible, + bsoncxx::detail::negation>> {}; + + template + struct is_implicitly_convertible + : bsoncxx::detail::conjunction, std::is_convertible> {}; + + static_assert(is_explicitly_convertible::value, "must be explicit"); + static_assert(is_implicitly_convertible::value, "must be implicit"); +}; + +// b_minkey +template struct one_way_implicit_convertible; +template struct one_way_implicit_convertible; +template struct one_way_implicit_convertible; +template struct one_way_implicit_convertible; +// b_binary +// b_undefined +template struct one_way_implicit_convertible; +template struct one_way_implicit_convertible; +template struct one_way_implicit_convertible; +// b_null +// b_regex +// b_dbpointer +template struct one_way_implicit_convertible; +template struct one_way_implicit_convertible; +// b_codewscope +template struct one_way_implicit_convertible; +// b_timestamp +template struct one_way_implicit_convertible; +template struct one_way_implicit_convertible; +// maxkey + +} // namespace single_value_types + +// BSONCXX_V1_TYPES_XMACRO: update above + +} // namespace static_assertions + +} // namespace diff --git a/src/bsoncxx/test/v1/types/view.hh b/src/bsoncxx/test/v1/types/view.hh new file mode 100644 index 0000000000..a09af35922 --- /dev/null +++ b/src/bsoncxx/test/v1/types/view.hh @@ -0,0 +1,199 @@ +// 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. + +#pragma once + +#include + +// + +#include // StringMaker +#include // StringMaker +#include // StringMaker +#include // StringMaker +#include // StringMaker + +#include +#include +#include + +#include + +#include + +CATCH_REGISTER_ENUM( + bsoncxx::v1::types::view::errc, + bsoncxx::v1::types::view::errc::zero, + bsoncxx::v1::types::view::errc::type_mismatch) + +// BSONCXX_V1_TYPES_XMACRO: update below. + +template <> +struct Catch::StringMaker : StringMaker {}; + +template <> +struct Catch::StringMaker : StringMaker {}; + +template <> +struct Catch::StringMaker : StringMaker {}; + +template <> +struct Catch::StringMaker : StringMaker {}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::b_binary const& value) { + if (!value.bytes || value.size == 0u) { + return "empty"; + } + + std::ostringstream oss; + oss << std::hex; + auto const end = value.bytes + value.size; + for (auto const* ptr = value.bytes; ptr != end; ++ptr) { + oss << std::setw(2) << std::setfill('0') << static_cast(*ptr); + } + return std::move(oss).str(); + } +}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::b_undefined const&) { + return "undefined"; + } +}; + +template <> +struct Catch::StringMaker : StringMaker {}; + +template <> +struct Catch::StringMaker : StringMaker {}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::b_date const& value) { + using time_point = std::chrono::time_point; + return bsoncxx::test::stringify(time_point{value.value}); + } +}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::b_null const&) { + return "null"; + } +}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::b_regex const& value) { + std::string res; + res += R"({"pattern": )"; + res += bsoncxx::test::stringify(value.regex); + res += R"(, "options": )"; + res += bsoncxx::test::stringify(value.options); + res += '}'; + return res; + } +}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::b_dbpointer const& value) { + std::string res; + res += R"({"$ref": )"; + res += bsoncxx::test::stringify(value.collection); + res += R"(, "$oid": )"; + res += bsoncxx::test::stringify(value.value); + res += '}'; + return res; + } +}; + +template <> +struct Catch::StringMaker : StringMaker {}; + +template <> +struct Catch::StringMaker : StringMaker {}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::b_codewscope const& value) { + std::string res; + res += R"({"code": )"; + res += bsoncxx::test::stringify(value.code); + res += R"(, "scope": )"; + res += bsoncxx::test::stringify(value.scope); + res += '}'; + return res; + } +}; + +template <> +struct Catch::StringMaker : StringMaker {}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::b_timestamp const& value) { + std::string res; + res += R"({"t": )"; + res += bsoncxx::test::stringify(value.timestamp); + res += R"(, "i": )"; + res += bsoncxx::test::stringify(value.increment); + res += '}'; + return res; + } +}; + +template <> +struct Catch::StringMaker : StringMaker {}; + +template <> +struct Catch::StringMaker : StringMaker {}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::b_maxkey const&) { + return "maxkey"; + } +}; + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::b_minkey const&) { + return "minkey"; + } +}; + +// BSONCXX_V1_TYPES_XMACRO: update above. + +template <> +struct Catch::StringMaker { + static std::string convert(bsoncxx::v1::types::view const& value) { +#pragma push_macro("X") +#undef X +#define X(_name, _value) \ + case bsoncxx::v1::types::id::k_##_name: \ + return bsoncxx::test::stringify(value.get_##_name()); + + switch (value.type_id()) { + BSONCXX_V1_TYPES_XMACRO(X) + + default: + return "?"; + } +#pragma pop_macro("X") + } +}; From f258f769f9dc527bc6ebf97b39ad5565ac8f9c60 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Wed, 6 Aug 2025 10:47:05 -0500 Subject: [PATCH 03/18] Add some basic find() and subscript assertions --- src/bsoncxx/test/v1/array/value.cpp | 21 +++++++++++++++------ src/bsoncxx/test/v1/document/value.cpp | 8 ++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/bsoncxx/test/v1/array/value.cpp b/src/bsoncxx/test/v1/array/value.cpp index 8ff6a2ceee..f9dfdd74f9 100644 --- a/src/bsoncxx/test/v1/array/value.cpp +++ b/src/bsoncxx/test/v1/array/value.cpp @@ -16,6 +16,8 @@ // +#include + #include #include @@ -463,20 +465,20 @@ TEST_CASE("basic", "[bsoncxx][v1][array][value]") { } SECTION("valid") { - std::uint8_t const xdoc[] = {12, 0, 0, 0, 16, 'x', '\0', 1, 0, 0, 0, 0}; // { 'x': 1 } + std::uint8_t const xarr[] = {12, 0, 0, 0, 16, '0', '\0', 1, 0, 0, 0, 0}; // { '0': 1 } - auto xdoc_owner = std::unique_ptr(new std::uint8_t[sizeof(xdoc)]); - std::memcpy(xdoc_owner.get(), xdoc, sizeof(xdoc)); - auto const data_ptr = xdoc_owner.get(); + auto xarr_owner = std::unique_ptr(new std::uint8_t[sizeof(xarr)]); + std::memcpy(xarr_owner.get(), xarr, sizeof(xarr)); + auto const data_ptr = xarr_owner.get(); - value const v{std::move(xdoc_owner)}; + value const v{std::move(xarr_owner)}; REQUIRE(v); CHECK(v != value{}); CHECK(v.data() == data_ptr); - CHECK(v.size() == sizeof(xdoc)); + CHECK(v.size() == sizeof(xarr)); CHECK(v.size() == v.length()); CHECK_FALSE(v.empty()); @@ -486,6 +488,13 @@ TEST_CASE("basic", "[bsoncxx][v1][array][value]") { CHECK(v.end() == v.cend()); CHECK(v.begin() != v.end()); + CHECK(v.find(0) != v.end()); + CHECK(v.find(0)->get_int32().value == 1); + CHECK(v.find(1) == v.end()); + + CHECK(v[0].get_int32().value == 1); + CHECK_FALSE(v[1]); + CHECK(v == v); CHECK_FALSE(v != v); CHECK(v != view{}); diff --git a/src/bsoncxx/test/v1/document/value.cpp b/src/bsoncxx/test/v1/document/value.cpp index af2b982885..28e8492b74 100644 --- a/src/bsoncxx/test/v1/document/value.cpp +++ b/src/bsoncxx/test/v1/document/value.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -498,6 +499,13 @@ TEST_CASE("basic", "[bsoncxx][v1][document][value]") { CHECK(v.end() == v.cend()); CHECK(v.begin() != v.end()); + CHECK(v.find("x") != v.end()); + CHECK(v.find("x")->get_int32().value == 1); + CHECK(v.find("y") == v.end()); + + CHECK(v["x"].get_int32().value == 1); + CHECK_FALSE(v["y"]); + CHECK(v == v); CHECK_FALSE(v != v); CHECK(v != view{}); From 25354e41377f6315f504406caeea135032ac5422 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Wed, 6 Aug 2025 10:47:06 -0500 Subject: [PATCH 04/18] Fix doc comments for BSON array data --- src/bsoncxx/test/v1/array/value.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bsoncxx/test/v1/array/value.cpp b/src/bsoncxx/test/v1/array/value.cpp index f9dfdd74f9..c2fd20b453 100644 --- a/src/bsoncxx/test/v1/array/value.cpp +++ b/src/bsoncxx/test/v1/array/value.cpp @@ -544,7 +544,7 @@ TEST_CASE("StringMaker", "[bsoncxx][test][v1][array][value]") { } SECTION("two") { - // {"x": 1, "y": 2} + // {"0": 1, "1": 2} std::uint8_t const bytes[] = { // clang-format off 19, 0, 0, 0, @@ -564,7 +564,7 @@ TEST_CASE("StringMaker", "[bsoncxx][test][v1][array][value]") { } SECTION("three") { - // {"x": 1, "y": 2, "z": 3} + // {"0": 1, "1": 2, "2": 3} std::uint8_t bytes[] = { // clang-format off 26, 0, 0, 0, From b2a439309807bc66abe490054768bb3c43839cdf Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Wed, 6 Aug 2025 10:47:07 -0500 Subject: [PATCH 05/18] Do not violate precondition in our own test cases --- src/bsoncxx/test/v1/array/value.cpp | 5 +++-- src/bsoncxx/test/v1/document/value.cpp | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/bsoncxx/test/v1/array/value.cpp b/src/bsoncxx/test/v1/array/value.cpp index c2fd20b453..33e9d4e4c7 100644 --- a/src/bsoncxx/test/v1/array/value.cpp +++ b/src/bsoncxx/test/v1/array/value.cpp @@ -44,8 +44,9 @@ TEST_CASE("exceptions", "[bsoncxx][v1][array][value]") { REQUIRE(data[0] == sizeof(data)); auto const make_ptr = [&data] { - auto res = std::unique_ptr(new std::uint8_t[sizeof(data)]); - std::memcpy(res.get(), data, sizeof(data)); + // Reserve an extra byte to support the `sizeof(data) + 1` test cases. + auto res = std::unique_ptr(new std::uint8_t[sizeof(data) + 1u]); + std::memcpy(res.get(), data, sizeof(data)); // Leave extra byte uninitialized. return res; }; diff --git a/src/bsoncxx/test/v1/document/value.cpp b/src/bsoncxx/test/v1/document/value.cpp index 28e8492b74..4877ff6472 100644 --- a/src/bsoncxx/test/v1/document/value.cpp +++ b/src/bsoncxx/test/v1/document/value.cpp @@ -54,8 +54,9 @@ TEST_CASE("exceptions", "[bsoncxx][v1][document][value]") { REQUIRE(data[0] == sizeof(data)); auto const make_ptr = [&data] { - auto res = std::unique_ptr(new std::uint8_t[sizeof(data)]); - std::memcpy(res.get(), data, sizeof(data)); + // Reserve an extra byte to support the `sizeof(data) + 1` test cases. + auto res = std::unique_ptr(new std::uint8_t[sizeof(data) + 1u]); + std::memcpy(res.get(), data, sizeof(data)); // Leave extra byte uninitialized. return res; }; From 1bc5302ed62f63b10fbe672a83120ba8b06d9aeb Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Wed, 6 Aug 2025 10:47:08 -0500 Subject: [PATCH 06/18] Assert resulting document length in invalid_length assertions --- src/bsoncxx/test/v1/array/value.cpp | 5 ++--- src/bsoncxx/test/v1/document/value.cpp | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/bsoncxx/test/v1/array/value.cpp b/src/bsoncxx/test/v1/array/value.cpp index 33e9d4e4c7..84fa94025f 100644 --- a/src/bsoncxx/test/v1/array/value.cpp +++ b/src/bsoncxx/test/v1/array/value.cpp @@ -53,7 +53,6 @@ TEST_CASE("exceptions", "[bsoncxx][v1][array][value]") { SECTION("invalid_length") { SECTION("value(std::uint8_t*, std::size_t, Deleter)") { auto const deleter = [](std::uint8_t* p) { delete[] p; }; - using deleter_type = std::reference_wrapper; auto const expr = [&](std::size_t length) { return value{make_ptr().release(), length, std::ref(deleter)}; @@ -62,8 +61,8 @@ TEST_CASE("exceptions", "[bsoncxx][v1][array][value]") { CHECK_THROWS_WITH_CODE(expr(0u), code::invalid_length); CHECK_THROWS_WITH_CODE(expr(sizeof(data) - 1u), code::invalid_length); - CHECK(get_deleter(expr(sizeof(data))).has_value()); - CHECK(get_deleter(expr(sizeof(data) + 1u)).has_value()); + CHECK(expr(sizeof(data)).size() == sizeof(data)); + CHECK(expr(sizeof(data) + 1u).size() == sizeof(data)); auto ptr = make_ptr(); ++ptr[0]; diff --git a/src/bsoncxx/test/v1/document/value.cpp b/src/bsoncxx/test/v1/document/value.cpp index 4877ff6472..491b44c4aa 100644 --- a/src/bsoncxx/test/v1/document/value.cpp +++ b/src/bsoncxx/test/v1/document/value.cpp @@ -63,7 +63,6 @@ TEST_CASE("exceptions", "[bsoncxx][v1][document][value]") { SECTION("invalid_length") { SECTION("value(std::uint8_t*, std::size_t, Deleter)") { auto const deleter = [](std::uint8_t* p) { delete[] p; }; - using deleter_type = std::reference_wrapper; auto const expr = [&](std::size_t length) { return value{make_ptr().release(), length, std::ref(deleter)}; @@ -72,8 +71,8 @@ TEST_CASE("exceptions", "[bsoncxx][v1][document][value]") { CHECK_THROWS_WITH_CODE(expr(0u), code::invalid_length); CHECK_THROWS_WITH_CODE(expr(sizeof(data) - 1u), code::invalid_length); - CHECK(get_deleter(expr(sizeof(data))).has_value()); - CHECK(get_deleter(expr(sizeof(data) + 1u)).has_value()); + CHECK(expr(sizeof(data)).size() == sizeof(data)); + CHECK(expr(sizeof(data) + 1u).size() == sizeof(data)); auto ptr = make_ptr(); ++ptr[0]; From 877cd50faa97b19ef4514e6cfd33be6d9cf3fd64 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Wed, 6 Aug 2025 10:47:10 -0500 Subject: [PATCH 07/18] Remove unused values array --- src/bsoncxx/test/v1/types/value.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/bsoncxx/test/v1/types/value.cpp b/src/bsoncxx/test/v1/types/value.cpp index e1e2bb245e..e3c8577937 100644 --- a/src/bsoncxx/test/v1/types/value.cpp +++ b/src/bsoncxx/test/v1/types/value.cpp @@ -110,13 +110,6 @@ TEST_CASE("exceptions", "[bsoncxx][v1][types][value]") { } SECTION("invalid_length_u32") { -#pragma push_macro("X") -#undef X -#define X(_name, _value) value{b_##_name{}}, - - value values[] = {BSONCXX_V1_TYPES_XMACRO(X)}; -#pragma pop_macro("X") - try { auto const size = std::size_t{UINT32_MAX} + 1u; std::unique_ptr data{new unsigned char[size]}; // make_unique_for_overwrite. From 9e3d254ca6624f2b141c3147453683ce2f645d2a Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Wed, 6 Aug 2025 10:47:11 -0500 Subject: [PATCH 08/18] Fix view vs. value in btype_vs_value static assertions --- src/bsoncxx/test/v1/types/value.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bsoncxx/test/v1/types/value.cpp b/src/bsoncxx/test/v1/types/value.cpp index e3c8577937..0e62c3cde0 100644 --- a/src/bsoncxx/test/v1/types/value.cpp +++ b/src/bsoncxx/test/v1/types/value.cpp @@ -487,12 +487,12 @@ namespace btype_vs_value { template struct overload { - // Prerequisite: Args is not BType or view. + // Prerequisite: Args is not BType or value. static_assert( IMPLIES( (sizeof...(Args) == 1), - !(bsoncxx::detail::disjunction..., std::is_same...>::value)), - "Arg must not be BType or view"); + !(bsoncxx::detail::disjunction..., std::is_same...>::value)), + "Arg must not be BType or value"); // Prerequisite: BType(Args...) is valid. static_assert(std::is_constructible::value, "BType(Args...) must be valid"); From 2cfc2d7ce613f381ac4dfb4dc73b27856fdadb2e Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Wed, 6 Aug 2025 10:47:12 -0500 Subject: [PATCH 09/18] Move T -> T::impl conversion functions into T::impl --- src/bsoncxx/lib/bsoncxx/v1/element/view.cpp | 58 ++++----- src/bsoncxx/lib/bsoncxx/v1/element/view.hh | 8 -- src/bsoncxx/lib/bsoncxx/v1/types/value.cpp | 124 ++++++++++---------- src/bsoncxx/lib/bsoncxx/v1/types/value.hh | 9 -- 4 files changed, 91 insertions(+), 108 deletions(-) diff --git a/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp index b5841b7537..3150e4842b 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp @@ -149,6 +149,18 @@ class alignas(BSONCXX_PRIVATE_MAX_ALIGN_T) view::impl { } throw v1::exception{code::invalid_data}; } + + static impl const& with(view const& v) { + return *reinterpret_cast(v._storage.data()); + } + + static impl const* with(view const* v) { + return reinterpret_cast(v->_storage.data()); + } + + static impl* with(view* v) { + return reinterpret_cast(v->_storage.data()); + } }; void view::impl::check() const { @@ -175,11 +187,11 @@ void view::impl::check() const { view::~view() = default; view::view(view const& other) noexcept { - new (internal::impl(this)) impl{internal::impl(other)}; + new (impl::with(this)) impl{impl::with(other)}; } view& view::operator=(view const& other) noexcept { - *internal::impl(this) = internal::impl(other); + *impl::with(this) = impl::with(other); return *this; } @@ -192,53 +204,53 @@ view::view(impl i) { } view::operator bool() const { - return internal::impl(this)->is_valid(); + return impl::with(this)->is_valid(); } std::uint8_t const* view::raw() const { - return internal::impl(this)->raw(); + return impl::with(this)->raw(); } std::uint32_t view::length() const { - return internal::impl(this)->length(); + return impl::with(this)->length(); } std::uint32_t view::offset() const { - return internal::impl(this)->offset(); + return impl::with(this)->offset(); } std::uint32_t view::keylen() const { - return internal::impl(this)->keylen(); + return impl::with(this)->keylen(); } v1::types::id view::type_id() const { - return internal::impl(this)->type_id(); + return impl::with(this)->type_id(); } v1::stdx::string_view view::key() const { - auto const iter = internal::impl(this)->iter(); + auto const iter = impl::with(this)->iter(); return bson_iter_key(&iter); } #pragma push_macro("X") #undef X -#define X(_name, _value) \ - v1::types::b_##_name view::get_##_name() const { \ - return internal::impl(this)->type_view().get_##_name(); \ +#define X(_name, _value) \ + v1::types::b_##_name view::get_##_name() const { \ + return impl::with(this)->type_view().get_##_name(); \ } BSONCXX_V1_TYPES_XMACRO(X) #pragma pop_macro("X") v1::types::view view::type_view() const { - return internal::impl(this)->type_view(); + return impl::with(this)->type_view(); } v1::types::value view::type_value() const { - return internal::impl(this)->type_value(); + return impl::with(this)->type_value(); } v1::element::view view::operator[](v1::stdx::string_view key) const { - auto& impl = internal::impl(*this); + auto& impl = impl::with(*this); if (!impl.is_valid() || impl.type_id_unchecked() != v1::types::id::k_document) { return v1::element::view{impl.to_invalid()}; } @@ -246,7 +258,7 @@ v1::element::view view::operator[](v1::stdx::string_view key) const { } v1::element::view view::operator[](std::uint32_t idx) const { - auto& impl = internal::impl(*this); + auto& impl = impl::with(*this); if (!impl.is_valid() || impl.type_id_unchecked() != v1::types::id::k_array) { return view{impl.to_invalid()}; } @@ -324,19 +336,7 @@ view view::internal::make( } v1::stdx::optional view::internal::to_bson_iter(view const& v) { - return internal::impl(v).iter_unchecked(); -} - -view::impl const& view::internal::impl(view const& self) { - return *reinterpret_cast(self._storage.data()); -} - -view::impl const* view::internal::impl(view const* self) { - return reinterpret_cast(self->_storage.data()); -} - -view::impl* view::internal::impl(view* self) { - return reinterpret_cast(self->_storage.data()); + return impl::with(v).iter_unchecked(); } v1::stdx::optional to_bson_iter(view const& v) { diff --git a/src/bsoncxx/lib/bsoncxx/v1/element/view.hh b/src/bsoncxx/lib/bsoncxx/v1/element/view.hh index 95315a5b5b..ca04c86ffc 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/element/view.hh +++ b/src/bsoncxx/lib/bsoncxx/v1/element/view.hh @@ -36,14 +36,6 @@ class view::internal { bool is_valid = true); static v1::stdx::optional to_bson_iter(view const& v); - - private: - friend view; - - static view::impl const& impl(view const& v); - - static view::impl const* impl(view const* v); - static view::impl* impl(view* v); }; v1::stdx::optional to_bson_iter(view const& v); diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp b/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp index a70626b3ef..a6463728b4 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp @@ -105,6 +105,22 @@ class alignas(BSONCXX_PRIVATE_MAX_ALIGN_T) value::impl { auto v() -> decltype((_value.value)) { return _value.value; } + + static impl const& with(value const& self) { + return *reinterpret_cast(self._storage.data()); + } + + static impl const* with(value const* self) { + return reinterpret_cast(self->_storage.data()); + } + + static impl& with(value& self) { + return *reinterpret_cast(self._storage.data()); + } + + static impl* with(value* self) { + return reinterpret_cast(self->_storage.data()); + } }; value::impl::~impl() { @@ -118,24 +134,24 @@ value::impl::~impl() { } value::~value() { - internal::impl(this)->~impl(); + impl::with(this)->~impl(); } value::value(value&& other) noexcept { - new (internal::impl(this)) impl{std::move(internal::impl(other))}; + new (impl::with(this)) impl{std::move(impl::with(other))}; } value& value::operator=(value&& other) noexcept { - *internal::impl(this) = std::move(internal::impl(other)); + *impl::with(this) = std::move(impl::with(other)); return *this; } value::value(value const& other) { - new (internal::impl(this)) impl{internal::impl(other)}; + new (impl::with(this)) impl{impl::with(other)}; } value& value::operator=(value const& other) { - *internal::impl(this) = internal::impl(other); + *impl::with(this) = impl::with(other); return *this; } @@ -186,8 +202,8 @@ value::value(v1::types::view const& v) : value{} { // BSONCXX_V1_TYPES_XMACRO: update below. value::value(v1::types::b_double const v) : value{} { - internal::impl(this)->t() = BSON_TYPE_DOUBLE; - internal::impl(this)->v().v_double = v; + impl::with(this)->t() = BSON_TYPE_DOUBLE; + impl::with(this)->v().v_double = v; } value::value(v1::types::b_string const v) : value{} { @@ -196,9 +212,9 @@ value::value(v1::types::b_string const v) : value{} { } auto const len = static_cast(v.value.size()); - internal::impl(this)->t() = BSON_TYPE_UTF8; + impl::with(this)->t() = BSON_TYPE_UTF8; - auto& v_utf8 = internal::impl(this)->v().v_utf8; + auto& v_utf8 = impl::with(this)->v().v_utf8; v_utf8.str = to_bson_copy(v.value); v_utf8.len = len; @@ -208,9 +224,9 @@ value::value(v1::types::b_document const v) : value{} { // Range is guaranteed by bsoncxx::v1::document::view::raw_size(). auto const data_len = static_cast(v.value.size()); - internal::impl(this)->t() = BSON_TYPE_DOCUMENT; + impl::with(this)->t() = BSON_TYPE_DOCUMENT; - auto& v_doc = internal::impl(this)->v().v_doc; + auto& v_doc = impl::with(this)->v().v_doc; v_doc.data = to_bson_copy(v.value.data(), v.value.size()); v_doc.data_len = data_len; @@ -220,18 +236,18 @@ value::value(v1::types::b_array const v) : value{} { // Range is guaranteed by bsoncxx::v1::document::view::raw_size(). auto const data_len = static_cast(v.value.size()); - internal::impl(this)->t() = BSON_TYPE_ARRAY; + impl::with(this)->t() = BSON_TYPE_ARRAY; - auto& v_doc = internal::impl(this)->v().v_doc; + auto& v_doc = impl::with(this)->v().v_doc; v_doc.data = to_bson_copy(v.value.data(), v.value.size()); v_doc.data_len = data_len; } value::value(v1::types::b_binary const v) : value{} { - internal::impl(this)->t() = BSON_TYPE_BINARY; + impl::with(this)->t() = BSON_TYPE_BINARY; - auto& v_binary = internal::impl(this)->v().v_binary; + auto& v_binary = impl::with(this)->v().v_binary; v_binary.subtype = static_cast(v.subtype); v_binary.data = to_bson_copy(v.bytes, v.size); @@ -239,32 +255,32 @@ value::value(v1::types::b_binary const v) : value{} { } value::value(v1::types::b_undefined) : value{} { - internal::impl(this)->t() = BSON_TYPE_UNDEFINED; + impl::with(this)->t() = BSON_TYPE_UNDEFINED; } value::value(v1::types::b_oid const v) : value{} { - internal::impl(this)->t() = BSON_TYPE_OID; - std::memcpy(internal::impl(this)->v().v_oid.bytes, v.value.bytes(), v.value.size()); + impl::with(this)->t() = BSON_TYPE_OID; + std::memcpy(impl::with(this)->v().v_oid.bytes, v.value.bytes(), v.value.size()); } value::value(v1::types::b_bool const v) : value{} { - internal::impl(this)->t() = BSON_TYPE_BOOL; - internal::impl(this)->v().v_bool = v.value; + impl::with(this)->t() = BSON_TYPE_BOOL; + impl::with(this)->v().v_bool = v.value; } value::value(v1::types::b_date const v) : value{} { - internal::impl(this)->t() = BSON_TYPE_DATE_TIME; - internal::impl(this)->v().v_datetime = v.value.count(); + impl::with(this)->t() = BSON_TYPE_DATE_TIME; + impl::with(this)->v().v_datetime = v.value.count(); } value::value(v1::types::b_null) { - (new (internal::impl(this)) impl{})->t() = BSON_TYPE_NULL; + (new (impl::with(this)) impl{})->t() = BSON_TYPE_NULL; } value::value(v1::types::b_regex const v) : value{} { - internal::impl(this)->t() = BSON_TYPE_REGEX; + impl::with(this)->t() = BSON_TYPE_REGEX; - auto& v_regex = internal::impl(this)->v().v_regex; + auto& v_regex = impl::with(this)->v().v_regex; v_regex.regex = to_bson_copy(v.regex); v_regex.options = v.options.empty() ? nullptr : to_bson_copy(v.options); @@ -276,9 +292,9 @@ value::value(v1::types::b_dbpointer const v) : value{} { } auto const collection_len = static_cast(v.collection.size()); - internal::impl(this)->t() = BSON_TYPE_DBPOINTER; + impl::with(this)->t() = BSON_TYPE_DBPOINTER; - auto& v_dbpointer = internal::impl(this)->v().v_dbpointer; + auto& v_dbpointer = impl::with(this)->v().v_dbpointer; v_dbpointer.collection = to_bson_copy(v.collection); v_dbpointer.collection_len = collection_len; @@ -291,9 +307,9 @@ value::value(v1::types::b_code const v) : value{} { } auto const code_len = static_cast(v.code.size()); - internal::impl(this)->t() = BSON_TYPE_CODE; + impl::with(this)->t() = BSON_TYPE_CODE; - auto& v_code = internal::impl(this)->v().v_code; + auto& v_code = impl::with(this)->v().v_code; v_code.code = to_bson_copy(v.code); v_code.code_len = code_len; @@ -305,9 +321,9 @@ value::value(v1::types::b_symbol const v) : value{} { } auto const len = static_cast(v.symbol.size()); - internal::impl(this)->t() = BSON_TYPE_SYMBOL; + impl::with(this)->t() = BSON_TYPE_SYMBOL; - auto& v_symbol = internal::impl(this)->v().v_symbol; + auto& v_symbol = impl::with(this)->v().v_symbol; v_symbol.symbol = to_bson_copy(v.symbol); v_symbol.len = len; @@ -322,9 +338,9 @@ value::value(v1::types::b_codewscope const v) : value{} { // Range is guaranteed by bsoncxx::v1::document::view::raw_size(). auto const scope_len = static_cast(v.scope.size()); - internal::impl(this)->t() = BSON_TYPE_CODEWSCOPE; + impl::with(this)->t() = BSON_TYPE_CODEWSCOPE; - auto& v_codewscope = internal::impl(this)->v().v_codewscope; + auto& v_codewscope = impl::with(this)->v().v_codewscope; v_codewscope.code = to_bson_copy(v.code); v_codewscope.code_len = code_len; @@ -333,39 +349,39 @@ value::value(v1::types::b_codewscope const v) : value{} { } value::value(v1::types::b_int32 const v) : value{} { - internal::impl(this)->t() = BSON_TYPE_INT32; - internal::impl(this)->v().v_int32 = v.value; + impl::with(this)->t() = BSON_TYPE_INT32; + impl::with(this)->v().v_int32 = v.value; } value::value(v1::types::b_timestamp const v) : value{} { - internal::impl(this)->t() = BSON_TYPE_TIMESTAMP; + impl::with(this)->t() = BSON_TYPE_TIMESTAMP; - auto& v_timestamp = internal::impl(this)->v().v_timestamp; + auto& v_timestamp = impl::with(this)->v().v_timestamp; v_timestamp.timestamp = v.timestamp; v_timestamp.increment = v.increment; } value::value(v1::types::b_int64 const v) : value{} { - internal::impl(this)->t() = BSON_TYPE_INT64; - internal::impl(this)->v().v_int64 = v.value; + impl::with(this)->t() = BSON_TYPE_INT64; + impl::with(this)->v().v_int64 = v.value; } value::value(v1::types::b_decimal128 const v) : value{} { - internal::impl(this)->t() = BSON_TYPE_DECIMAL128; + impl::with(this)->t() = BSON_TYPE_DECIMAL128; - auto& v_decimal128 = internal::impl(this)->v().v_decimal128; + auto& v_decimal128 = impl::with(this)->v().v_decimal128; v_decimal128.high = v.value.high(); v_decimal128.low = v.value.low(); } value::value(v1::types::b_maxkey) : value{} { - internal::impl(this)->t() = BSON_TYPE_MAXKEY; + impl::with(this)->t() = BSON_TYPE_MAXKEY; } value::value(v1::types::b_minkey) : value{} { - internal::impl(this)->t() = BSON_TYPE_MINKEY; + impl::with(this)->t() = BSON_TYPE_MINKEY; } // BSONCXX_V1_TYPES_XMACRO: update above. @@ -379,11 +395,11 @@ value::value(std::uint8_t const* data, std::size_t size, v1::types::binary_subty } v1::types::id value::type_id() const { - return static_cast(internal::impl(this)->t()); + return static_cast(impl::with(this)->t()); } v1::types::view value::view() const { - return v1::types::view::internal::make(internal::impl(this)->_value); + return v1::types::view::internal::make(impl::with(this)->_value); } std::error_category const& value::error_category() { @@ -456,26 +472,10 @@ value::internal::make(std::uint8_t const* raw, std::uint32_t length, std::uint32 } value ret; - bson_value_copy(bson_iter_value(&iter), &internal::impl(ret)._value); + bson_value_copy(bson_iter_value(&iter), &impl::with(ret)._value); return ret; } -value::impl const& value::internal::impl(value const& self) { - return *reinterpret_cast(self._storage.data()); -} - -value::impl const* value::internal::impl(value const* self) { - return reinterpret_cast(self->_storage.data()); -} - -value::impl& value::internal::impl(value& self) { - return *reinterpret_cast(self._storage.data()); -} - -value::impl* value::internal::impl(value* self) { - return reinterpret_cast(self->_storage.data()); -} - } // namespace types } // namespace v1 } // namespace bsoncxx diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/value.hh b/src/bsoncxx/lib/bsoncxx/v1/types/value.hh index a131b43e27..982005dd24 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/types/value.hh +++ b/src/bsoncxx/lib/bsoncxx/v1/types/value.hh @@ -30,15 +30,6 @@ class value::internal { public: static v1::stdx::optional make(std::uint8_t const* raw, std::uint32_t length, std::uint32_t offset, std::uint32_t keylen); - - private: - friend value; - - static value::impl const& impl(value const& self); - static value::impl const* impl(value const* self); - - static value::impl& impl(value& self); - static value::impl* impl(value* self); }; } // namespace types From 8de4ee14aa5aaed91257c8b8a3a9bb8ae1d21482 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Wed, 6 Aug 2025 11:26:16 -0500 Subject: [PATCH 10/18] ClangFormat --- src/bsoncxx/lib/bsoncxx/v1/element/view.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp index 3150e4842b..5de4952643 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp @@ -234,8 +234,8 @@ v1::stdx::string_view view::key() const { #pragma push_macro("X") #undef X -#define X(_name, _value) \ - v1::types::b_##_name view::get_##_name() const { \ +#define X(_name, _value) \ + v1::types::b_##_name view::get_##_name() const { \ return impl::with(this)->type_view().get_##_name(); \ } BSONCXX_V1_TYPES_XMACRO(X) From 2b76d2c1574ecdc73ae161b8bd2642c1733e0586 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 14 Aug 2025 10:50:40 -0500 Subject: [PATCH 11/18] Distinguish "missing" vs. "invalid" after calling bson_iter_* --- src/bsoncxx/lib/bsoncxx/v1/array/view.cpp | 12 ++++--- src/bsoncxx/lib/bsoncxx/v1/document/view.cpp | 33 ++++++++++++++------ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/bsoncxx/lib/bsoncxx/v1/array/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/array/view.cpp index 7a30b0c1ea..152200b052 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/array/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/array/view.cpp @@ -57,12 +57,16 @@ view::const_iterator view::find(std::uint32_t i) const { bson_iter_t iter; - if (!bson_iter_init_find_w_len(&iter, &bson, key.c_str(), static_cast(key.length()))) { - return this->end(); + if (bson_iter_init_find_w_len(&iter, &bson, key.c_str(), static_cast(key.length()))) { + return const_iterator::internal::make_const_iterator( + _view.data(), _view.length(), bson_iter_offset(&iter), bson_iter_key_len(&iter)); } - return const_iterator::internal::make_const_iterator( - _view.data(), _view.length(), bson_iter_offset(&iter), bson_iter_key_len(&iter)); + if (iter.err_off != 0) { + throw v1::exception{code::invalid_data}; + } + + return this->end(); } } // namespace array diff --git a/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp index f2a593e78d..b1a602b98f 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp @@ -68,12 +68,16 @@ view::const_iterator view::cbegin() const { throw v1::exception{code::invalid_data}; } - if (!bson_iter_next(&iter)) { - return this->cend(); + if (bson_iter_next(&iter)) { + return const_iterator::internal::make_const_iterator( + _data, this->size(), bson_iter_offset(&iter), bson_iter_key_len(&iter)); } - return const_iterator::internal::make_const_iterator( - _data, this->size(), bson_iter_offset(&iter), bson_iter_key_len(&iter)); + if (iter.err_off != 0) { + throw v1::exception{code::invalid_data}; + } + + return this->cend(); } view::const_iterator view::find(v1::stdx::string_view key) const { @@ -98,12 +102,16 @@ view::const_iterator view::find(v1::stdx::string_view key) const { bson_iter_t iter; - if (!bson_iter_init_find_w_len(&iter, &bson, key.data(), static_cast(key.size()))) { - return this->end(); + if (bson_iter_init_find_w_len(&iter, &bson, key.data(), static_cast(key.size()))) { + return const_iterator::internal::make_const_iterator( + _data, this->size(), bson_iter_offset(&iter), bson_iter_key_len(&iter)); } - return const_iterator::internal::make_const_iterator( - _data, this->size(), bson_iter_offset(&iter), bson_iter_key_len(&iter)); + if (iter.err_off != 0) { + throw v1::exception{code::invalid_data}; + } + + return this->end(); } std::error_category const& view::error_category() { @@ -183,10 +191,15 @@ view::const_iterator& view::const_iterator::operator++() { if (bson_iter_next(&iter)) { _element = v1::element::view::internal::make( _element.raw(), _element.length(), bson_iter_offset(&iter), bson_iter_key_len(&iter)); - } else { - _element = {}; + return *this; + } + + if (iter.err_off != 0) { + throw v1::exception{code::invalid_data}; } + _element = {}; + return *this; } From 4c6b7351643fbb8f0cb41cec176c8cdd2ec7de5e Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 14 Aug 2025 10:50:41 -0500 Subject: [PATCH 12/18] Use std::array instead of raw arrays --- src/bsoncxx/lib/bsoncxx/v1/document/view.cpp | 5 +++-- src/bsoncxx/lib/bsoncxx/v1/element/view.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp index b1a602b98f..208a11f2de 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp @@ -20,6 +20,7 @@ #include +#include #include #include #include @@ -45,11 +46,11 @@ static_assert( namespace { -constexpr std::uint8_t k_default_view[5] = {5u, 0u, 0u, 0u, 0u}; +constexpr std::array k_default_view = {{5u, 0u, 0u, 0u, 0u}}; } // namespace -view::view() : view{k_default_view} {} +view::view() : view{k_default_view.data()} {} view::view(std::uint8_t const* data, std::size_t length) : view{data} { if (length < _empty_length || length < this->size()) { diff --git a/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp index 5de4952643..20614342aa 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp @@ -58,7 +58,7 @@ class alignas(BSONCXX_PRIVATE_MAX_ALIGN_T) view::impl { BSONCXX_PRIVATE_WARNINGS_PUSH(); BSONCXX_PRIVATE_WARNINGS_DISABLE(GNU("-Wunused")); - unsigned char _padding[_padding_size]; // Reserved. + std::array _padding = {}; // Reserved. BSONCXX_PRIVATE_WARNINGS_POP(); bool _is_valid = {}; // Last byte. From 81fa5c09a53a5b7e58e76508eef6c8a6972cf8fb Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 14 Aug 2025 10:50:42 -0500 Subject: [PATCH 13/18] BSON binary subtype values 128-255 are "user defined" --- src/bsoncxx/include/bsoncxx/v1/types/id.hpp | 4 +++- src/bsoncxx/lib/bsoncxx/v1/types/id.cpp | 7 +++++- src/bsoncxx/test/v1/types/id.cpp | 24 ++++++++++++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/bsoncxx/include/bsoncxx/v1/types/id.hpp b/src/bsoncxx/include/bsoncxx/v1/types/id.hpp index 8e297dfd04..a12a863675 100644 --- a/src/bsoncxx/include/bsoncxx/v1/types/id.hpp +++ b/src/bsoncxx/include/bsoncxx/v1/types/id.hpp @@ -84,12 +84,14 @@ enum class binary_subtype : std::uint8_t { k_column = 0x07, ///< Compressed BSON column. k_sensitive = 0x08, ///< Sensitive. k_vector = 0x09, ///< Vector. - k_user = 0x80, ///< User defined. + k_user = 0x80, ///< User defined (up to 0xFF, inclusive). }; /// /// Return the name of the enumerator (e.g. `"binary"` given `k_binary`). /// +/// @note Values in the range [0x80, 0xFF] are all equivalent to "k_user". +/// /// @attention This feature is experimental! It is not ready for use! /// BSONCXX_ABI_EXPORT_CDECL(std::string) to_string(binary_subtype rhs); diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/id.cpp b/src/bsoncxx/lib/bsoncxx/v1/types/id.cpp index c133cdebd2..3adc0db4f5 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/types/id.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/types/id.cpp @@ -49,7 +49,12 @@ std::string to_string(binary_subtype rhs) { BSONCXX_V1_BINARY_SUBTYPES_XMACRO(X) default: - return "?"; + // All BSON binary subtype values in the range [0x80, 0xFF] are "user defined". + if (rhs >= binary_subtype::k_user) { + return "user"; + } else { + return "?"; + } } #pragma pop_macro("X") } diff --git a/src/bsoncxx/test/v1/types/id.cpp b/src/bsoncxx/test/v1/types/id.cpp index 51a08cb4a2..e454ab7841 100644 --- a/src/bsoncxx/test/v1/types/id.cpp +++ b/src/bsoncxx/test/v1/types/id.cpp @@ -63,7 +63,29 @@ TEST_CASE("to_string", "[bsoncxx][v1][types][id]") { TEST_CASE("to_string", "[bsoncxx][v1][types][binary_subtype]") { SECTION("unknown") { - CHECK(to_string(static_cast(UINT8_MAX)) == "?"); // 0xFF + for (int i = 0; i < int{UINT8_MAX} + 1; ++i) { + CAPTURE(i); + +#pragma push_macro("X") +#undef X +#define X(_name, _value) \ + case binary_subtype::k_##_name: \ + break; + + switch (static_cast(i)) { + // Ignore named enumerators: handled by the "values" section. + BSONCXX_V1_BINARY_SUBTYPES_XMACRO(X) + + default: + // All BSON binary subtype values in the range [0x80, 0xFF] are "user defined". + if (i >= static_cast(binary_subtype::k_user)) { + CHECK(to_string(static_cast(i)) == "user"); + } else { + CHECK(to_string(static_cast(i)) == "?"); + } + } +#pragma pop_macro("X") + } } SECTION("values") { From 22c67c2d65c4cfc6c36e0ddce2e7e80831a53e49 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 14 Aug 2025 10:50:44 -0500 Subject: [PATCH 14/18] Minor mem-list-init improvement --- src/bsoncxx/lib/bsoncxx/v1/types/value.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp b/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp index a6463728b4..acb760efb2 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp @@ -62,8 +62,9 @@ class alignas(BSONCXX_PRIVATE_MAX_ALIGN_T) value::impl { ~impl(); - impl(impl&& other) noexcept { - _value = other._value; // Ownership transfer. + impl(impl&& other) noexcept + : _value{other._value} // Ownership transfer. + { other._value = {BSON_TYPE_NULL, {}, {}}; } From 4258c047f89456a7e65e42938ba1371349ac6275 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 14 Aug 2025 10:50:45 -0500 Subject: [PATCH 15/18] Make WSAGuard Windows-only and static local --- src/bsoncxx/include/bsoncxx/v1/oid.hpp | 4 ++++ src/bsoncxx/lib/bsoncxx/v1/oid.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/bsoncxx/include/bsoncxx/v1/oid.hpp b/src/bsoncxx/include/bsoncxx/v1/oid.hpp index f02e6ee0bf..129876c0af 100644 --- a/src/bsoncxx/include/bsoncxx/v1/oid.hpp +++ b/src/bsoncxx/include/bsoncxx/v1/oid.hpp @@ -56,6 +56,10 @@ class oid { /// [`bson_oid_init`](https://mongoc.org/libbson/current/bson_oid_init.html) function with the default /// [`bson_context_t`](https://mongoc.org/libbson/current/bson_context_t.html). /// + /// @important On Windows only, the first call to this function initializes a static local variable which loads the + /// Winsock DLL by calling `WSAStartup()`. The Winsock DLL is unloaded by calling `WSACleanup()` when the static + /// local variable is destroyed. + /// /// @throws bsoncxx::v1::exception (on Windows only) with a `std::system_category()` error code /// (as returned by /// [`WSAGetLastError()`](https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsagetlasterror)) diff --git a/src/bsoncxx/lib/bsoncxx/v1/oid.cpp b/src/bsoncxx/lib/bsoncxx/v1/oid.cpp index 47e0cd9c0e..79e957f254 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/oid.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/oid.cpp @@ -36,10 +36,10 @@ static_assert(is_semitrivial::value, "bsoncxx::v1::oid must be semitrivial" constexpr std::size_t oid::k_oid_length; oid::oid() { +#if defined(_WIN32) // Ensure the Winsock DLL is initialized prior to calling `gethostname` in `bsoncxx::v1::oid::oid()`: // - bson_oid_init -> bson_context_get_default -> ... -> _bson_context_init_random -> gethostname. - struct WSAGuard { -#if defined(_WIN32) + static struct WSAGuard { ~WSAGuard() { (void)WSACleanup(); } @@ -56,11 +56,11 @@ oid::oid() { WSAGetLastError(), std::system_category(), "WSAStartup() failed in bsoncxx::v1::oid::oid()"}; } } + } wsa_guard; #endif - }; bson_oid_t oid; - ((void)WSAGuard{}, bson_oid_init(&oid, nullptr)); + bson_oid_init(&oid, nullptr); std::memcpy(_bytes.data(), oid.bytes, sizeof(oid.bytes)); } From 116890e0261ea7d777e3556d4dba0710ac8d14e7 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 14 Aug 2025 10:50:46 -0500 Subject: [PATCH 16/18] Use bsoncxx::make_unique_for_overwrite --- src/bsoncxx/test/v1/decimal128.cpp | 4 +++- src/bsoncxx/test/v1/document/view.cpp | 4 +++- src/bsoncxx/test/v1/types/value.cpp | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/bsoncxx/test/v1/decimal128.cpp b/src/bsoncxx/test/v1/decimal128.cpp index 8e5172496d..3eb018ef5c 100644 --- a/src/bsoncxx/test/v1/decimal128.cpp +++ b/src/bsoncxx/test/v1/decimal128.cpp @@ -23,6 +23,8 @@ #include #include +#include + #include #include @@ -100,7 +102,7 @@ TEST_CASE("exceptions", "[bsoncxx][v1][decimal128]") { SECTION("invalid_string_length") { try { auto const size = std::size_t{INT_MAX} + 1u; - std::unique_ptr data{new char[size]}; // make_unique_for_overwrite + auto const data = bsoncxx::make_unique_for_overwrite(size); auto const big_string = bsoncxx::v1::stdx::string_view{data.get(), size}; auto const expr = [&] { decimal128 d128{big_string}; }; diff --git a/src/bsoncxx/test/v1/document/view.cpp b/src/bsoncxx/test/v1/document/view.cpp index ffae690b4b..fb5f935bbb 100644 --- a/src/bsoncxx/test/v1/document/view.cpp +++ b/src/bsoncxx/test/v1/document/view.cpp @@ -27,6 +27,8 @@ #include #include +#include + #include #include @@ -152,7 +154,7 @@ TEST_CASE("exceptions", "[bsoncxx][v1][document][view]") { SECTION("invalid_key_length") { try { auto const size = std::size_t{INT_MAX} + 1u; - std::unique_ptr data{new char[size]}; // make_unique_for_overwrite. + auto const data = bsoncxx::make_unique_for_overwrite(size); auto const big_string = bsoncxx::v1::stdx::string_view{data.get(), size}; view const v; diff --git a/src/bsoncxx/test/v1/types/value.cpp b/src/bsoncxx/test/v1/types/value.cpp index 0e62c3cde0..4196989872 100644 --- a/src/bsoncxx/test/v1/types/value.cpp +++ b/src/bsoncxx/test/v1/types/value.cpp @@ -33,6 +33,8 @@ #include #include +#include + #include #include @@ -112,7 +114,7 @@ TEST_CASE("exceptions", "[bsoncxx][v1][types][value]") { SECTION("invalid_length_u32") { try { auto const size = std::size_t{UINT32_MAX} + 1u; - std::unique_ptr data{new unsigned char[size]}; // make_unique_for_overwrite. + auto const data = bsoncxx::make_unique_for_overwrite(size); auto const big_string = bsoncxx::v1::stdx::string_view{reinterpret_cast(data.get()), size}; From 3de63ff11a211436588f6ac697b3cdfbafc048ec Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 14 Aug 2025 10:50:48 -0500 Subject: [PATCH 17/18] Fix and cleanup include directives --- src/bsoncxx/test/v1/array/view.cpp | 1 - src/bsoncxx/test/v1/element/view.cpp | 1 - src/bsoncxx/test/v1/oid.cpp | 1 - src/bsoncxx/test/v1/types/id.cpp | 2 -- src/bsoncxx/test/v1/types/value.cpp | 2 +- src/bsoncxx/test/v1/types/view.cpp | 2 -- 6 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/bsoncxx/test/v1/array/view.cpp b/src/bsoncxx/test/v1/array/view.cpp index d330c66c5f..09674fee8b 100644 --- a/src/bsoncxx/test/v1/array/view.cpp +++ b/src/bsoncxx/test/v1/array/view.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include diff --git a/src/bsoncxx/test/v1/element/view.cpp b/src/bsoncxx/test/v1/element/view.cpp index 5d05175213..74aa199b81 100644 --- a/src/bsoncxx/test/v1/element/view.cpp +++ b/src/bsoncxx/test/v1/element/view.cpp @@ -21,7 +21,6 @@ #include #include -#include #include #include diff --git a/src/bsoncxx/test/v1/oid.cpp b/src/bsoncxx/test/v1/oid.cpp index 894539ac36..ffd2e9ef11 100644 --- a/src/bsoncxx/test/v1/oid.cpp +++ b/src/bsoncxx/test/v1/oid.cpp @@ -18,7 +18,6 @@ #include -#include #include #include #include diff --git a/src/bsoncxx/test/v1/types/id.cpp b/src/bsoncxx/test/v1/types/id.cpp index e454ab7841..a166d0326d 100644 --- a/src/bsoncxx/test/v1/types/id.cpp +++ b/src/bsoncxx/test/v1/types/id.cpp @@ -18,8 +18,6 @@ #include -#include - #include #include diff --git a/src/bsoncxx/test/v1/types/value.cpp b/src/bsoncxx/test/v1/types/value.cpp index 4196989872..25b193b929 100644 --- a/src/bsoncxx/test/v1/types/value.cpp +++ b/src/bsoncxx/test/v1/types/value.cpp @@ -27,7 +27,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/bsoncxx/test/v1/types/view.cpp b/src/bsoncxx/test/v1/types/view.cpp index 818e8e5d44..6732e59bd5 100644 --- a/src/bsoncxx/test/v1/types/view.cpp +++ b/src/bsoncxx/test/v1/types/view.cpp @@ -24,10 +24,8 @@ #include #include -#include #include #include -#include #include #include From e5c7fa9b21fca3c27725aabb327f0155cc4602d9 Mon Sep 17 00:00:00 2001 From: Ezra Chung Date: Thu, 14 Aug 2025 10:50:47 -0500 Subject: [PATCH 18/18] Use common format for unknown error codes --- src/bsoncxx/lib/bsoncxx/v1/decimal128.cpp | 2 +- src/bsoncxx/lib/bsoncxx/v1/document/view.cpp | 2 +- src/bsoncxx/lib/bsoncxx/v1/element/view.cpp | 2 +- src/bsoncxx/lib/bsoncxx/v1/oid.cpp | 2 +- src/bsoncxx/lib/bsoncxx/v1/types/value.cpp | 2 +- src/bsoncxx/lib/bsoncxx/v1/types/view.cpp | 2 +- src/bsoncxx/test/v1/decimal128.cpp | 2 +- src/bsoncxx/test/v1/document/view.cpp | 2 +- src/bsoncxx/test/v1/element/view.cpp | 2 +- src/bsoncxx/test/v1/oid.cpp | 2 +- src/bsoncxx/test/v1/types/value.cpp | 2 +- src/bsoncxx/test/v1/types/view.cpp | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/bsoncxx/lib/bsoncxx/v1/decimal128.cpp b/src/bsoncxx/lib/bsoncxx/v1/decimal128.cpp index b7acba6ef4..f46f76f780 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/decimal128.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/decimal128.cpp @@ -80,7 +80,7 @@ std::error_category const& decimal128::error_category() { case code::invalid_string_data: return "string is not a valid Decimal128 representation"; default: - return "unknown: " + std::to_string(v); + return std::string(this->name()) + ':' + std::to_string(v); } } diff --git a/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp index 208a11f2de..c2e128d3f0 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/document/view.cpp @@ -130,7 +130,7 @@ std::error_category const& view::error_category() { case code::invalid_data: return "data is invalid"; default: - return "unknown: " + std::to_string(v); + return std::string(this->name()) + ':' + std::to_string(v); } } diff --git a/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp index 20614342aa..8c571df4ff 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/element/view.cpp @@ -280,7 +280,7 @@ std::error_category const& view::error_category() { case code::invalid_data: return "data is invalid"; default: - return "unknown: " + std::to_string(v); + return std::string(this->name()) + ':' + std::to_string(v); } } diff --git a/src/bsoncxx/lib/bsoncxx/v1/oid.cpp b/src/bsoncxx/lib/bsoncxx/v1/oid.cpp index 79e957f254..cf72e185f4 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/oid.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/oid.cpp @@ -133,7 +133,7 @@ std::error_category const& oid::error_category() { case code::invalid_string: return "string is not a valid ObjectID representation"; default: - return "unknown: " + std::to_string(v); + return std::string(this->name()) + ':' + std::to_string(v); } } diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp b/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp index acb760efb2..9f0302b01f 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/types/value.cpp @@ -418,7 +418,7 @@ std::error_category const& value::error_category() { case code::invalid_length_u32: return "length is too long (exceeds UINT32_MAX)"; default: - return "unknown: " + std::to_string(v); + return std::string(this->name()) + ':' + std::to_string(v); } } diff --git a/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp b/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp index 41785b42be..c0f765fbc4 100644 --- a/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp +++ b/src/bsoncxx/lib/bsoncxx/v1/types/view.cpp @@ -85,7 +85,7 @@ std::error_category const& view::error_category() { case code::type_mismatch: return "requested type does not match the underlying type"; default: - return "unknown: " + std::to_string(v); + return std::string(this->name()) + ':' + std::to_string(v); } } diff --git a/src/bsoncxx/test/v1/decimal128.cpp b/src/bsoncxx/test/v1/decimal128.cpp index 3eb018ef5c..563f5647fb 100644 --- a/src/bsoncxx/test/v1/decimal128.cpp +++ b/src/bsoncxx/test/v1/decimal128.cpp @@ -50,7 +50,7 @@ TEST_CASE("error code", "[bsoncxx][v1][decimal128][error]") { CHECK(ec.category() == category); CHECK(ec.value() == -1); CHECK(ec); - CHECK(ec.message() == "unknown: -1"); + CHECK(ec.message() == std::string(category.name()) + ":-1"); } SECTION("zero") { diff --git a/src/bsoncxx/test/v1/document/view.cpp b/src/bsoncxx/test/v1/document/view.cpp index fb5f935bbb..860f3c2428 100644 --- a/src/bsoncxx/test/v1/document/view.cpp +++ b/src/bsoncxx/test/v1/document/view.cpp @@ -54,7 +54,7 @@ TEST_CASE("error code", "[bsoncxx][v1][document][view][error]") { CHECK(ec.category() == category); CHECK(ec.value() == -1); CHECK(ec); - CHECK(ec.message() == "unknown: -1"); + CHECK(ec.message() == std::string(category.name()) + ":-1"); } SECTION("zero") { diff --git a/src/bsoncxx/test/v1/element/view.cpp b/src/bsoncxx/test/v1/element/view.cpp index 74aa199b81..b4b6dd04e5 100644 --- a/src/bsoncxx/test/v1/element/view.cpp +++ b/src/bsoncxx/test/v1/element/view.cpp @@ -49,7 +49,7 @@ TEST_CASE("error code", "[bsoncxx][v1][element][view][error]") { CHECK(ec.category() == category); CHECK(ec.value() == -1); CHECK(ec); - CHECK(ec.message() == "unknown: -1"); + CHECK(ec.message() == std::string(category.name()) + ":-1"); } SECTION("zero") { diff --git a/src/bsoncxx/test/v1/oid.cpp b/src/bsoncxx/test/v1/oid.cpp index ffd2e9ef11..d3435c8ce9 100644 --- a/src/bsoncxx/test/v1/oid.cpp +++ b/src/bsoncxx/test/v1/oid.cpp @@ -47,7 +47,7 @@ TEST_CASE("error code", "[bsoncxx][v1][oid][error]") { CHECK(ec.category() == category); CHECK(ec.value() == -1); CHECK(ec); - CHECK(ec.message() == "unknown: -1"); + CHECK(ec.message() == std::string(category.name()) + ":-1"); } SECTION("zero") { diff --git a/src/bsoncxx/test/v1/types/value.cpp b/src/bsoncxx/test/v1/types/value.cpp index 25b193b929..b546e15756 100644 --- a/src/bsoncxx/test/v1/types/value.cpp +++ b/src/bsoncxx/test/v1/types/value.cpp @@ -60,7 +60,7 @@ TEST_CASE("error code", "[bsoncxx][v1][types][value][error]") { CHECK(ec.category() == category); CHECK(ec.value() == -1); CHECK(ec); - CHECK(ec.message() == "unknown: -1"); + CHECK(ec.message() == std::string(category.name()) + ":-1"); } SECTION("zero") { diff --git a/src/bsoncxx/test/v1/types/view.cpp b/src/bsoncxx/test/v1/types/view.cpp index 6732e59bd5..b45e46ba9a 100644 --- a/src/bsoncxx/test/v1/types/view.cpp +++ b/src/bsoncxx/test/v1/types/view.cpp @@ -53,7 +53,7 @@ TEST_CASE("error code", "[bsoncxx][v1][types][view][error]") { CHECK(ec.category() == category); CHECK(ec.value() == -1); CHECK(ec); - CHECK(ec.message() == "unknown: -1"); + CHECK(ec.message() == std::string(category.name()) + ":-1"); } SECTION("zero") {