From f3981ec6cdf7f9e48559ef64e98694901aee7b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beat=20K=C3=BCng?= Date: Thu, 1 Feb 2024 14:05:14 +0100 Subject: [PATCH] messages: throw AccessException when trying to access invalid field/index And check for it in the ulog_data example --- examples/ulog_data.cpp | 51 ++++++++++++++++++++++--------------- test/read_api_test.cpp | 6 +++++ ulog_cpp/data_container.hpp | 13 +++++----- ulog_cpp/messages.hpp | 9 ++++++- ulog_cpp/subscription.hpp | 8 +++++- 5 files changed, 57 insertions(+), 30 deletions(-) diff --git a/examples/ulog_data.cpp b/examples/ulog_data.cpp index 63d5a60..eb5b45b 100644 --- a/examples/ulog_data.cpp +++ b/examples/ulog_data.cpp @@ -49,6 +49,7 @@ int main(int argc, char** argv) for (const auto& sub : subscription_names) { std::cout << sub << "\n"; } + std::cout << "\n"; // Get a particular subscription if (subscription_names.find("vehicle_status") != subscription_names.end()) { @@ -62,30 +63,34 @@ int main(int argc, char** argv) std::cout << "Field names: " << "\n"; for (const std::string& field : subscription->fieldNames()) { - std::cout << field << "\n"; + std::cout << " " << field << "\n"; } // Get particular field - auto nav_state_field = subscription->field("nav_state"); + try { + auto nav_state_field = subscription->field("nav_state"); - // Iterate over all samples - std::cout << "nav_state values: \n"; - for (const auto& sample : *subscription) { - // always correctly extracts the type as defined in the message definition, - // gets cast to the value you put in int. - // This also works for arrays and strings. - auto nav_state = sample[nav_state_field].as(); - std::cout << nav_state << ", "; - } - std::cout << "\n"; + // Iterate over all samples + std::cout << "nav_state values: \n "; + for (const auto& sample : *subscription) { + // always correctly extracts the type as defined in the message definition, + // gets cast to the value you put in int. + // This also works for arrays and strings. + auto nav_state = sample[nav_state_field].as(); + std::cout << nav_state << ", "; + } + std::cout << "\n"; - // get a specific sample - auto sample_12 = subscription->at(12); + // get a specific sample + auto sample_12 = subscription->at(12); - // access values by name - auto timestamp = sample_12["timestamp"].as(); + // access values by name + auto timestamp = sample_12["timestamp"].as(); - std::cout << timestamp << "\n"; + std::cout << "timestamp at sample 12: " << timestamp << "\n"; + } catch (const ulog_cpp::AccessException& exception) { + std::cout << "AccessException: " << exception.what() << "\n"; + } } else { std::cout << "No vehicle_status subscription found\n"; } @@ -95,16 +100,20 @@ int main(int argc, char** argv) const auto& message_format = data_container->messageFormats().at("esc_status"); std::cout << "Message format: " << message_format->name() << "\n"; for (const auto& field_name : message_format->fieldNames()) { - std::cout << field_name << "\n"; + std::cout << " " << field_name << "\n"; } } else { std::cout << "No esc_status message format found\n"; } if (subscription_names.find("esc_status") != subscription_names.end()) { - auto esc_status = data_container->subscription("esc_status"); - for (const auto& sample : *esc_status) { - std::cout << "esc_power: " << sample["esc"][7]["esc_power"].as() << "\n"; + try { + auto esc_status = data_container->subscription("esc_status"); + for (const auto& sample : *esc_status) { + std::cout << "esc_power: " << sample["esc"][7]["esc_power"].as() << "\n"; + } + } catch (const ulog_cpp::AccessException& exception) { + std::cout << "AccessException: " << exception.what() << "\n"; } } else { std::cout << "No esc_status subscription found\n"; diff --git a/test/read_api_test.cpp b/test/read_api_test.cpp index 0395cc2..df68caa 100644 --- a/test/read_api_test.cpp +++ b/test/read_api_test.cpp @@ -259,6 +259,12 @@ TEST_CASE("Write complicated, nested data format, then read it") sample[f_child_1][f_c1_c1_2][2][f_c1_c1_2_byte_b].asNativeTypeVariant())); CHECK(std::holds_alternative>( sample[f_child_1][f_c1_unsigned_long].asNativeTypeVariant())); + + // Check exceptions + CHECK_THROWS_AS(sample["non_existent"], ulog_cpp::AccessException); + CHECK_THROWS_AS(sample[f_child_1][f_c1_unsigned_long][100], ulog_cpp::AccessException); + CHECK_THROWS_AS(data_container->subscription("non_existent_subscription"), + ulog_cpp::AccessException); } TEST_SUITE_END(); diff --git a/ulog_cpp/data_container.hpp b/ulog_cpp/data_container.hpp index d8a4052..961ba1b 100644 --- a/ulog_cpp/data_container.hpp +++ b/ulog_cpp/data_container.hpp @@ -105,14 +105,13 @@ class DataContainer : public DataHandlerInterface { return names; } - std::shared_ptr subscription(const std::string& name, int multi_id) const + std::shared_ptr subscription(const std::string& name, int multi_id = 0) const { - return _subscriptions_by_name_and_multi_id.at({name, multi_id}); - } - - std::shared_ptr subscription(const std::string& name) const - { - return _subscriptions_by_name_and_multi_id.at({name, 0}); + const auto it = _subscriptions_by_name_and_multi_id.find({name, multi_id}); + if (it == _subscriptions_by_name_and_multi_id.end()) { + throw AccessException("Subscription not found: " + name); + } + return it->second; } protected: diff --git a/ulog_cpp/messages.hpp b/ulog_cpp/messages.hpp index a2815f7..aa471e8 100644 --- a/ulog_cpp/messages.hpp +++ b/ulog_cpp/messages.hpp @@ -605,7 +605,14 @@ class MessageFormat { * @param name the name of the field * @return the requested field */ - std::shared_ptr field(const std::string& name) const { return _fields.at(name); } + std::shared_ptr field(const std::string& name) const + { + const auto it = _fields.find(name); + if (it == _fields.end()) { + throw AccessException("Field not found: " + name); + } + return it->second; + } private: std::string _name; diff --git a/ulog_cpp/subscription.hpp b/ulog_cpp/subscription.hpp index b0b7e79..bded5b4 100644 --- a/ulog_cpp/subscription.hpp +++ b/ulog_cpp/subscription.hpp @@ -260,7 +260,13 @@ class Subscription { _message_format); } - TypedDataView at(std::size_t n) const { return begin()[n]; } + TypedDataView at(std::size_t n) const + { + if (n >= size()) { + throw AccessException("Index out of range: " + std::to_string(n)); + } + return begin()[n]; + } TypedDataView operator[](std::size_t n) { return at(n); }