From 90623594499337d359b1c35154d07f02a0e7e0af Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 4 Oct 2023 14:59:01 +0000 Subject: [PATCH] feat: improved human readable error messages to default logger, with unit test --- include/dpp/restresults.h | 8 ++++++++ src/dpp/cluster/confirmation.cpp | 17 +++++++++++++++++ src/dpp/utility.cpp | 4 ++-- src/unittest/test.cpp | 19 +++++++++++++++++++ src/unittest/test.h | 1 + 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/include/dpp/restresults.h b/include/dpp/restresults.h index 0691b41869..d6ad8bfa57 100644 --- a/include/dpp/restresults.h +++ b/include/dpp/restresults.h @@ -216,6 +216,10 @@ struct DPP_EXPORT error_detail { * @brief Error reason (full message) */ std::string reason; + /** + * @brief Object field index + */ + int index = 0; }; /** @@ -235,6 +239,10 @@ struct DPP_EXPORT error_info { * @brief Field specific error descriptions */ std::vector errors; + /** + * @brief Human readable error message constructed from the above + */ + std::string human_readable; }; /** diff --git a/src/dpp/cluster/confirmation.cpp b/src/dpp/cluster/confirmation.cpp index b2744f8d8f..a02a886443 100644 --- a/src/dpp/cluster/confirmation.cpp +++ b/src/dpp/cluster/confirmation.cpp @@ -82,7 +82,9 @@ error_info confirmation_callback_t::get_error() const { json& errors = j["errors"]; for (auto obj = errors.begin(); obj != errors.end(); ++obj) { + int array_index = 0; if (obj->find("0") != obj->end()) { + array_index = 0; /* An array of error messages */ for (auto index = obj->begin(); index != obj->end(); ++index) { if (index->find("_errors") != index->end()) { @@ -92,6 +94,7 @@ error_info confirmation_callback_t::get_error() const { detail.reason = (*errordetails)["message"].get(); detail.object.clear(); detail.field = obj.key(); + detail.index = array_index; e.errors.emplace_back(detail); } } else { @@ -102,10 +105,13 @@ error_info confirmation_callback_t::get_error() const { detail.reason = (*errordetails)["message"].get(); detail.field = fields.key(); detail.object = obj.key(); + detail.index = array_index; e.errors.emplace_back(detail); } } } + /* Index only increments per field, not per error*/ + array_index++; } } else if (obj->find("_errors") != obj->end()) { @@ -117,11 +123,22 @@ error_info confirmation_callback_t::get_error() const { detail.reason = (*errordetails)["message"].get(); detail.object.clear(); detail.field = obj.key(); + detail.index = 0; e.errors.emplace_back(detail); } } } + e.human_readable = std::to_string(e.code) + ": " + e.message; + std::string prefix = e.errors.size() == 1 ? " " : "\n\t"; + for (const auto& error : e.errors) { + if (error.object.empty()) { + e.human_readable += prefix + "- " + error.field + ": " + error.reason + " (" + error.code + ")"; + } else { + e.human_readable += prefix + "- " + error.object + "[" + std::to_string(error.index) + "]." + error.field + ": " + error.reason + " (" + error.code + ")"; + } + } + return e; } return error_info(); diff --git a/src/dpp/utility.cpp b/src/dpp/utility.cpp index 3c526df04c..1ca4751ca3 100644 --- a/src/dpp/utility.cpp +++ b/src/dpp/utility.cpp @@ -499,10 +499,10 @@ namespace dpp { return [](const dpp::confirmation_callback_t& detail) { if (detail.is_error()) { if (detail.bot) { + error_info e = detail.get_error(); detail.bot->log( dpp::ll_error, - "Error " + std::to_string(detail.get_error().code) + " [" + - detail.get_error().message + "] on API request, returned content was: " + detail.http_info.body + "Error: " + e.human_readable ); } } diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 8173c6842c..488367d4bd 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -58,6 +58,25 @@ Markdown lol ||spoiler|| ~~strikethrough~~ `small *code* block`\n"; set_test(COMPARISON, u1 == u2 && u1 != u3); + dpp::confirmation_callback_t error_test; + set_test(ERRORS, false); + bool error_message_success = false; + error_test.http_info.status = 400; + + error_test.http_info.body = "{\"message\": \"Invalid Form Body\", \"code\": 50035, \"errors\": {\"options\": {\"0\": {\"name\": {\"_errors\": [{\"code\": \"STRING_TYPE_REGEX\", \"message\": \"String value did not match validation regex.\"}, {\"code\": \"APPLICATION_COMMAND_INVALID_NAME\", \"message\": \"Command name is invalid\"}]}}}}}"; + error_message_success = (error_test.get_error().human_readable == "50035: Invalid Form Body\n\t- options[0].name: String value did not match validation regex. (STRING_TYPE_REGEX)\n\t- options[0].name: Command name is invalid (APPLICATION_COMMAND_INVALID_NAME)"); + + error_test.http_info.body = "{\"message\": \"Invalid Form Body\", \"code\": 50035, \"errors\": {\"type\": {\"_errors\": [{\"code\": \"BASE_TYPE_CHOICES\", \"message\": \"Value must be one of {4, 5, 9, 10, 11}.\"}]}}}"; + error_message_success = (error_message_success && error_test.get_error().human_readable == "50035: Invalid Form Body - type: Value must be one of {4, 5, 9, 10, 11}. (BASE_TYPE_CHOICES)"); + + error_test.http_info.body = "{\"message\": \"Unknown Guild\", \"code\": 10004}"; + error_message_success = (error_message_success && error_test.get_error().human_readable == "10004: Unknown Guild"); + + error_test.http_info.body = "{\"message\": \"Invalid Form Body\", \"code\": 50035, \"errors\": {\"allowed_mentions\": {\"_errors\": [{\"code\": \"MESSAGE_ALLOWED_MENTIONS_PARSE_EXCLUSIVE\", \"message\": \"parse:[\\\"users\\\"] and users: [ids...] are mutually exclusive.\"}]}}}"; + error_message_success = (error_message_success && error_test.get_error().human_readable == "50035: Invalid Form Body - allowed_mentions: parse:[\"users\"] and users: [ids...] are mutually exclusive. (MESSAGE_ALLOWED_MENTIONS_PARSE_EXCLUSIVE)"); + + set_test(ERRORS, error_message_success); + set_test(MD_ESC_1, false); set_test(MD_ESC_2, false); std::string escaped1 = dpp::utility::markdown_escape(test_to_escape); diff --git a/src/unittest/test.h b/src/unittest/test.h index c3b295ae45..df6a98d34a 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -144,6 +144,7 @@ DPP_TEST(CHANNELTYPES, "channel type flags", tf_online); DPP_TEST(FORUM_CREATION, "create a forum channel", tf_online); DPP_TEST(FORUM_CHANNEL_GET, "retrieve the created forum channel", tf_online); DPP_TEST(FORUM_CHANNEL_DELETE, "delete the created forum channel", tf_online); +DPP_TEST(ERRORS, "Human readable error translation", tf_offline); DPP_TEST(GUILD_BAN_CREATE, "cluster::guild_ban_add ban three deleted discord accounts", tf_online); DPP_TEST(GUILD_BAN_GET, "cluster::guild_get_ban getting one of the banned accounts", tf_online);