Skip to content

Commit 2c16fa1

Browse files
author
Jesse Williamson
authored
CXX2183 - expose document validation failure details (mongodb#846)
* CXX2183 - expose document validation failure details Ensure that the "details" field is included in errInfo on failure. https://jira.mongodb.org/browse/CXX-2183 Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Basic structure working. Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Tidied up a few things in response to review comments; tried to avoid going /too/ far... Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Update WRT review response; remove checking on failure. Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Remove extraneous test code. Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Clarify comment. Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Review fixes; don't run on pre-5.0 servers; use versioned API. Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Update in response to review comments; reformat; add validation of insert trigger. Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Move flag change Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Fix compiler error. Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Fix compiler error. Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Minor cosmetic adjustment (and nudge build system). Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Fix linter complaint. Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * Clang-format changes. Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com> * clang-format version mismatch Signed-off-by: Jesse Williamson <jesse.williamson@mongodb.com>
1 parent aaab2d1 commit 2c16fa1

File tree

1 file changed

+108
-4
lines changed

1 file changed

+108
-4
lines changed

src/mongocxx/test/collection.cpp

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
// limitations under the License.
1414

1515
#include <chrono>
16+
#include <iostream>
17+
#include <iterator>
18+
#include <new>
19+
#include <sstream>
1620
#include <vector>
1721

1822
#include <bsoncxx/builder/basic/document.hpp>
@@ -2333,10 +2337,10 @@ TEST_CASE("read_concern is inherited from parent", "[collection]") {
23332337
}
23342338
}
23352339

2336-
void find_index_and_validate(collection& coll,
2337-
stdx::string_view index_name,
2338-
const std::function<void(bsoncxx::document::view)>& validate =
2339-
[](bsoncxx::document::view) {}) {
2340+
void find_index_and_validate(
2341+
collection& coll,
2342+
stdx::string_view index_name,
2343+
const std::function<void(bsoncxx::document::view)>& validate = [](bsoncxx::document::view) {}) {
23402344
auto cursor = coll.list_indexes();
23412345

23422346
for (auto&& index : cursor) {
@@ -2808,4 +2812,104 @@ TEST_CASE("Ensure that the WriteConcernError 'errInfo' object is propagated", "[
28082812
REQUIRE(contains_err_info);
28092813
}
28102814

2815+
TEST_CASE("expose writeErrors[].errInfo", "[collection]") {
2816+
// A helper for checking that an error document is well-formed according to our requirements:
2817+
auto writeErrors_well_formed = [](const bsoncxx::document::view& reply_view) -> bool {
2818+
if (!reply_view["writeErrors"]) {
2819+
return false;
2820+
}
2821+
2822+
const auto& errdoc = reply_view["writeErrors"][0];
2823+
2824+
auto error_code = errdoc["code"].get_int32();
2825+
2826+
// The code should always be 121 (DocumentValidationFailure):
2827+
if (121 != error_code) {
2828+
std::ostringstream os;
2829+
os << "writeErrors expected to have code 121, but had " << error_code << " instead";
2830+
throw std::runtime_error(os.str());
2831+
}
2832+
2833+
// We require the "details" field be present:
2834+
if (!errdoc["errInfo"]["details"]) {
2835+
throw std::runtime_error("no \"details\" field in \"writeErrors\"");
2836+
}
2837+
2838+
return true;
2839+
};
2840+
2841+
// Set up our test environment:
2842+
instance::current();
2843+
2844+
mongocxx::options::apm apm_opts;
2845+
2846+
auto client_opts = test_util::add_test_server_api();
2847+
2848+
// We set this by side effect in on_command_succeeded to make sure the callback was actually
2849+
// triggered:
2850+
bool insert_succeeded = false;
2851+
2852+
// Listen to the insertion-failed event: we want to get a copy of the server's
2853+
// response so that we can compare it to the thrown exception later:
2854+
apm_opts.on_command_succeeded([&writeErrors_well_formed, &insert_succeeded](
2855+
const mongocxx::events::command_succeeded_event& ev) {
2856+
if (0 != ev.command_name().compare("insert")) {
2857+
return;
2858+
}
2859+
2860+
REQUIRE(writeErrors_well_formed(ev.reply()));
2861+
2862+
// Make sure that "we" were actually called:
2863+
insert_succeeded = true;
2864+
});
2865+
2866+
client_opts.apm_opts(apm_opts);
2867+
2868+
auto mongodb_client = mongocxx::client(uri{}, client_opts);
2869+
2870+
if (!test_util::newer_than(mongodb_client, "5.0")) {
2871+
WARN("skip: test requires MongoDB server 5.0 or newer");
2872+
return;
2873+
}
2874+
2875+
database db = mongodb_client["prose_test_expose_details"];
2876+
2877+
const std::string collname{"mongo_cxx_driver-expose_details"};
2878+
2879+
// Drop the existing collection, if any:
2880+
db[collname].drop();
2881+
2882+
// Make a new collection with validation checking:
2883+
collection coll = db.create_collection(
2884+
collname,
2885+
make_document(kvp("validator",
2886+
make_document(kvp("field_x", make_document(kvp("$type", "string")))))));
2887+
2888+
SECTION("cause a type violation on insert") {
2889+
bsoncxx::builder::basic::document entry;
2890+
2891+
entry.append(kvp("_id", bsoncxx::oid()), kvp("field_x", 42));
2892+
2893+
try {
2894+
coll.insert_one(entry.view());
2895+
2896+
// We should not make it here (i.e. this is an error):
2897+
CHECK(false);
2898+
} catch (const operation_exception& e) {
2899+
auto rse = e.raw_server_error();
2900+
2901+
// We have no has_value() check:
2902+
CHECK(rse);
2903+
2904+
CHECK(writeErrors_well_formed(*rse));
2905+
} catch (...) {
2906+
// An exception was thrown, but of the wrong type:
2907+
CHECK(false);
2908+
}
2909+
2910+
// Make sure that our callback was actually triggered and completed successfully:
2911+
REQUIRE(insert_succeeded);
2912+
}
2913+
}
2914+
28112915
} // namespace

0 commit comments

Comments
 (0)