message_definition_cache.cpp
Go to the documentation of this file.
2 
3 #include <filesystem>
4 #include <fstream>
5 #include <functional>
6 #include <optional>
7 #include <regex>
8 #include <set>
9 #include <string>
10 #include <unordered_set>
11 #include <utility>
12 
13 #include <ament_index_cpp/get_package_share_directory.hpp>
14 #include <ament_index_cpp/get_resource.hpp>
15 #include <ament_index_cpp/get_resources.hpp>
16 #include <rcutils/logging_macros.h>
17 
19 
20 namespace foxglove {
21 
22 // Match datatype names (foo_msgs/Bar or foo_msgs/msg/Bar or foo_msgs/srv/Bar)
23 static const std::regex PACKAGE_TYPENAME_REGEX{
24  R"(^([a-zA-Z0-9_]+)/(?:(msg|srv|action)/)?([a-zA-Z0-9_]+)$)"};
25 
26 // Match field types from .msg definitions ("foo_msgs/Bar" in "foo_msgs/Bar[] bar")
27 static const std::regex MSG_FIELD_TYPE_REGEX{R"((?:^|\n)\s*([a-zA-Z0-9_/]+)(?:\[[^\]]*\])?\s+)"};
28 
29 // match field types from `.idl` definitions ("foo_msgs/msg/bar" in #include <foo_msgs/msg/Bar.idl>)
30 static const std::regex IDL_FIELD_TYPE_REGEX{
31  R"((?:^|\n)#include\s+(?:"|<)([a-zA-Z0-9_/]+)\.idl(?:"|>))"};
32 
33 static const std::unordered_set<std::string> PRIMITIVE_TYPES{
34  "bool", "byte", "char", "float32", "float64", "int8", "uint8",
35  "int16", "uint16", "int32", "uint32", "int64", "uint64", "string"};
36 
37 enum class DefinitionType {
38  Message,
39  Service,
40  Action,
41 };
42 
43 static std::set<std::string> parse_msg_dependencies(const std::string& text,
44  const std::string& package_context) {
45  std::set<std::string> dependencies;
46 
47  for (std::sregex_iterator iter(text.begin(), text.end(), MSG_FIELD_TYPE_REGEX);
48  iter != std::sregex_iterator(); ++iter) {
49  std::string type = (*iter)[1];
50  if (PRIMITIVE_TYPES.find(type) != PRIMITIVE_TYPES.end()) {
51  continue;
52  }
53  if (type.find('/') == std::string::npos) {
54  dependencies.insert(package_context + '/' + std::move(type));
55  } else {
56  dependencies.insert(std::move(type));
57  }
58  }
59  return dependencies;
60 }
61 
62 static std::set<std::string> parse_idl_dependencies(const std::string& text) {
63  std::set<std::string> dependencies;
64 
65  for (std::sregex_iterator iter(text.begin(), text.end(), IDL_FIELD_TYPE_REGEX);
66  iter != std::sregex_iterator(); ++iter) {
67  dependencies.insert((*iter)[1]);
68  }
69  return dependencies;
70 }
71 
72 std::set<std::string> parse_dependencies(MessageDefinitionFormat format, const std::string& text,
73  const std::string& package_context) {
74  switch (format) {
76  return parse_msg_dependencies(text, package_context);
78  return parse_idl_dependencies(text);
79  default:
80  throw std::runtime_error("switch is not exhaustive");
81  }
82 }
83 
85  DefinitionType definitionType) {
86  switch (format) {
88  switch (definitionType) {
90  return ".msg";
92  return ".srv";
94  return ".action";
95  default:
96  throw std::runtime_error("unknown definition type");
97  }
99  return ".idl";
100  default:
101  throw std::runtime_error("switch is not exhaustive");
102  }
103 }
104 
105 static std::string delimiter(const DefinitionIdentifier& definition_identifier) {
106  std::string result =
107  "================================================================================\n";
108  switch (definition_identifier.format) {
110  result += "MSG: ";
111  break;
113  result += "IDL: ";
114  break;
115  default:
116  throw std::runtime_error("switch is not exhaustive");
117  }
118  result += definition_identifier.package_resource_name;
119  result += "\n";
120  return result;
121 }
122 
123 static std::vector<std::string> split_string(const std::string& str,
124  const std::string& delimiter = "\n") {
125  std::vector<std::string> strings;
126  std::string::size_type pos = 0;
127  std::string::size_type prev = 0;
128 
129  while ((pos = str.find(delimiter, prev)) != std::string::npos) {
130  strings.push_back(str.substr(prev, pos - prev));
131  prev = pos + delimiter.size();
132  }
133 
134  // Get the last substring (or only, if delimiter is not found)
135  strings.push_back(str.substr(prev));
136 
137  return strings;
138 }
139 
140 inline bool ends_with(const std::string& str, const std::string& suffix) {
141  return str.size() >= suffix.size() &&
142  0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
143 }
144 
145 std::string remove_action_subtype(const std::string action_type) {
146  const auto action_subtype_suffixes = {
147  std::string(ACTION_FEEDBACK_MESSAGE_SUFFIX),
152  };
153 
154  for (const auto& suffix : action_subtype_suffixes) {
155  if (ends_with(action_type, suffix)) {
156  return action_type.substr(0, action_type.length() - suffix.length());
157  }
158  }
159 
160  return action_type;
161 }
162 
163 std::string remove_service_subtype(const std::string service_type) {
164  const std::vector<std::string> service_subtype_suffixes = {
167  };
168 
169  for (const auto& suffix : service_subtype_suffixes) {
170  if (ends_with(service_type, suffix)) {
171  return service_type.substr(0, service_type.length() - suffix.length());
172  }
173  }
174 
175  return service_type;
176 }
177 
179  const std::string& package_context)
180  : dependencies(parse_dependencies(format, text, package_context))
181  , text(std::move(text))
182  , format(format) {}
183 
185  const DefinitionIdentifier& definition_identifier) {
186  if (auto it = msg_specs_by_definition_identifier_.find(definition_identifier);
188  return it->second;
189  }
190 
191  std::smatch match;
192  if (!std::regex_match(definition_identifier.package_resource_name, match,
194  throw std::invalid_argument("Invalid package resource name: " +
195  definition_identifier.package_resource_name);
196  }
197  const std::string package = match[1].str();
198  const std::string subfolder = match[2].str();
199  const std::string type_name = match[3].str();
200 
201  const auto& format = definition_identifier.format;
202  std::string filename = "";
203  if (subfolder == "action") {
204  // The action type name includes the subtype which we have to remove to get the action name.
205  // Type name: Fibonacci_FeedbackMessage -> Action name: Fibonacci
206  filename =
208  } else if (subfolder == "srv") {
209  // The service type name includes the subtype which we have to remove to get the service name.
210  // Type name: SetBool_Request -> Service name: SetBool
211  filename =
213  } else {
214  filename = type_name + extension_for_format(format, DefinitionType::Message);
215  }
216 
217  // Get the package share directory, or throw a PackageNotFoundError
218  const std::string share_dir = ament_index_cpp::get_package_share_directory(package);
219 
220  // Get the rosidl_interfaces index contents for this package
221  std::string index_contents;
222  if (!ament_index_cpp::get_resource("rosidl_interfaces", package, index_contents)) {
223  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
224  }
225 
226  // Find the first line that ends with the filename we're looking for
227  const auto lines = split_string(index_contents);
228  const auto it = std::find_if(lines.begin(), lines.end(), [&filename](const std::string& line) {
229  std::filesystem::path filePath(line);
230  return filePath.filename() == filename;
231  });
232  if (it == lines.end()) {
233  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
234  }
235 
236  // Read the file
237  const std::string full_path = share_dir + std::filesystem::path::preferred_separator + *it;
238  std::ifstream file{full_path};
239  if (!file.good()) {
240  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
241  }
242 
243  if (subfolder == "action") {
244  if (definition_identifier.format == MessageDefinitionFormat::IDL) {
245  RCUTILS_LOG_ERROR_NAMED("foxglove_bridge",
246  "Action IDL definitions are currently not supported");
247  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
248  }
249 
250  const auto split_definitions = foxglove_bridge::splitMessageDefinitions(file);
251  if (split_definitions.size() != 3) {
252  throw std::invalid_argument("Invalid action definition in " + filename +
253  ": Expected 3 definitions, got " +
254  std::to_string(split_definitions.size()));
255  }
256 
257  const auto& goalDef = split_definitions[0];
258  const auto& resultDef = split_definitions[1];
259  const auto& feedbackDef = split_definitions[2];
260 
261  // Define type definitions for each action subtype.
262  // These type definitions may include additional fields such as the goal_id.
263  // See also https://design.ros2.org/articles/actions.html
264  const std::map<std::string, std::string> action_type_definitions = {
265  {ACTION_FEEDBACK_MESSAGE_SUFFIX, "unique_identifier_msgs/UUID goal_id\n" + feedbackDef},
267  "unique_identifier_msgs/UUID goal_id\n"},
269  "int8 status\n" + resultDef},
271  "unique_identifier_msgs/UUID goal_id\n" + goalDef},
273  "bool accepted\nbuiltin_interfaces/msg/Time stamp"}};
274 
275  // Create a MessageSpec instance for every action subtype and add it to the cache.
276  const std::string action_name = remove_action_subtype(type_name);
277  for (const auto& [action_suffix, definition] : action_type_definitions) {
278  DefinitionIdentifier definition_id;
279  definition_id.format = definition_identifier.format;
280  definition_id.package_resource_name = package + "/action/" + action_name + action_suffix;
282  definition_id, MessageSpec(definition_id.format, definition, package));
283  }
284 
285  // Find the the subtype that was originally requested and return it.
286  const auto it = msg_specs_by_definition_identifier_.find(definition_identifier);
287  if (it == msg_specs_by_definition_identifier_.end()) {
288  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
289  }
290  return it->second;
291  } else if (subfolder == "srv") {
292  if (definition_identifier.format == MessageDefinitionFormat::IDL) {
293  RCUTILS_LOG_ERROR_NAMED("foxglove_bridge",
294  "Service IDL definitions are currently not supported");
295  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
296  }
297 
298  const auto split_definitions = foxglove_bridge::splitMessageDefinitions(file);
299  if (split_definitions.size() != 2) {
300  throw std::invalid_argument("Invalid service definition in " + filename +
301  ": Expected 2 definitions, got " +
302  std::to_string(split_definitions.size()));
303  }
304 
305  const auto& requestDef = split_definitions[0];
306  const auto& responseDef = split_definitions[1];
307  const std::map<std::string, std::string> service_type_definitions = {
309 
310  // Create a MessageSpec instance for both the request and response subtypes and add it to the
311  // cache.
312  const std::string service_name = remove_service_subtype(type_name);
313  for (const auto& [subType, definition] : service_type_definitions) {
314  DefinitionIdentifier definition_id;
315  definition_id.format = definition_identifier.format;
316  definition_id.package_resource_name = package + "/srv/" + service_name + subType;
318  definition_id, MessageSpec(definition_id.format, definition, package));
319  }
320 
321  // Find the the subtype that was originally requested and return it.
322  const auto it = msg_specs_by_definition_identifier_.find(definition_identifier);
323  if (it == msg_specs_by_definition_identifier_.end()) {
324  throw DefinitionNotFoundError(definition_identifier.package_resource_name);
325  }
326  return it->second;
327  } else {
328  // Normal message type.
329  const MessageSpec& spec =
331  .emplace(definition_identifier,
332  MessageSpec(definition_identifier.format,
333  std::string{std::istreambuf_iterator(file), {}}, package))
334  .first->second;
335 
336  // "References and pointers to data stored in the container are only invalidated by erasing that
337  // element, even when the corresponding iterator is invalidated."
338  return spec;
339  }
340 } // namespace foxglove
341 
342 std::pair<MessageDefinitionFormat, std::string> MessageDefinitionCache::get_full_text(
343  const std::string& root_package_resource_name) {
344  std::unordered_set<DefinitionIdentifier, DefinitionIdentifierHash> seen_deps;
345 
346  std::function<std::string(const DefinitionIdentifier&)> append_recursive =
347  [&](const DefinitionIdentifier& definition_identifier) {
348  const MessageSpec& spec = load_message_spec(definition_identifier);
349  std::string result = spec.text;
350  for (const auto& dep_name : spec.dependencies) {
351  DefinitionIdentifier dep{definition_identifier.format, dep_name};
352  bool inserted = seen_deps.insert(dep).second;
353  if (inserted) {
354  result += "\n";
355  result += delimiter(dep);
356  result += append_recursive(dep);
357  }
358  }
359  return result;
360  };
361 
362  std::string result;
364  try {
365  result = append_recursive(DefinitionIdentifier{format, root_package_resource_name});
366  } catch (const DefinitionNotFoundError& err) {
367  // log that we've fallen back
368  RCUTILS_LOG_WARN_NAMED("foxglove_bridge", "no .msg definition for %s, falling back to IDL",
369  err.what());
371  DefinitionIdentifier root_definition_identifier{format, root_package_resource_name};
372  result = delimiter(root_definition_identifier) + append_recursive(root_definition_identifier);
373  }
374  return std::make_pair(format, result);
375 }
376 
377 } // namespace foxglove
foxglove::DefinitionType::Message
@ Message
foxglove::MessageDefinitionFormat::IDL
@ IDL
foxglove
Definition: base64.hpp:8
foxglove::DefinitionIdentifier::package_resource_name
std::string package_resource_name
Definition: message_definition_cache.hpp:33
foxglove::remove_action_subtype
std::string remove_action_subtype(const std::string action_type)
Definition: message_definition_cache.cpp:145
utils.hpp
format
Definition: format.py:1
foxglove::PACKAGE_TYPENAME_REGEX
static const std::regex PACKAGE_TYPENAME_REGEX
Definition: message_definition_cache.cpp:23
definition
const char * definition()
foxglove::PRIMITIVE_TYPES
static const std::unordered_set< std::string > PRIMITIVE_TYPES
Definition: message_definition_cache.cpp:33
foxglove::MessageSpec::MessageSpec
MessageSpec(MessageDefinitionFormat format, std::string text, const std::string &package_context)
Definition: message_definition_cache.cpp:178
foxglove::MessageDefinitionFormat
MessageDefinitionFormat
Definition: message_definition_cache.hpp:19
foxglove::delimiter
static std::string delimiter(const DefinitionIdentifier &definition_identifier)
Definition: message_definition_cache.cpp:105
foxglove::MessageDefinitionCache::load_message_spec
const MessageSpec & load_message_spec(const DefinitionIdentifier &definition_identifier)
Definition: message_definition_cache.cpp:184
foxglove::SERVICE_RESPONSE_MESSAGE_SUFFIX
constexpr char SERVICE_RESPONSE_MESSAGE_SUFFIX[]
Definition: message_definition_cache.hpp:14
foxglove::DefinitionType::Action
@ Action
foxglove::parse_dependencies
std::set< std::string > parse_dependencies(MessageDefinitionFormat format, const std::string &text, const std::string &package_context)
Definition: message_definition_cache.cpp:72
foxglove::MessageSpec
Definition: message_definition_cache.hpp:24
foxglove::IDL_FIELD_TYPE_REGEX
static const std::regex IDL_FIELD_TYPE_REGEX
Definition: message_definition_cache.cpp:30
foxglove::DefinitionNotFoundError
Definition: message_definition_cache.hpp:40
package
string package
foxglove::DefinitionType::Service
@ Service
foxglove::SERVICE_REQUEST_MESSAGE_SUFFIX
constexpr char SERVICE_REQUEST_MESSAGE_SUFFIX[]
Definition: message_definition_cache.hpp:13
foxglove_bridge::splitMessageDefinitions
std::vector< std::string > splitMessageDefinitions(std::istream &stream)
Definition: utils.hpp:27
foxglove::MessageDefinitionFormat::MSG
@ MSG
foxglove::DefinitionIdentifier::format
MessageDefinitionFormat format
Definition: message_definition_cache.hpp:32
foxglove::MessageDefinitionCache::msg_specs_by_definition_identifier_
std::unordered_map< DefinitionIdentifier, MessageSpec, DefinitionIdentifierHash > msg_specs_by_definition_identifier_
Definition: message_definition_cache.hpp:81
message_definition_cache.hpp
foxglove::extension_for_format
static const char * extension_for_format(MessageDefinitionFormat format, DefinitionType definitionType)
Definition: message_definition_cache.cpp:84
foxglove::MSG_FIELD_TYPE_REGEX
static const std::regex MSG_FIELD_TYPE_REGEX
Definition: message_definition_cache.cpp:27
foxglove::DefinitionNotFoundError::what
const char * what() const noexcept override
Definition: message_definition_cache.hpp:48
foxglove::ACTION_GOAL_SERVICE_SUFFIX
constexpr char ACTION_GOAL_SERVICE_SUFFIX[]
Definition: message_definition_cache.hpp:15
foxglove::ends_with
bool ends_with(const std::string &str, const std::string &suffix)
Definition: message_definition_cache.cpp:140
std
foxglove::ACTION_FEEDBACK_MESSAGE_SUFFIX
constexpr char ACTION_FEEDBACK_MESSAGE_SUFFIX[]
Definition: message_definition_cache.hpp:17
foxglove::DefinitionIdentifier
Definition: message_definition_cache.hpp:31
foxglove::ACTION_RESULT_SERVICE_SUFFIX
constexpr char ACTION_RESULT_SERVICE_SUFFIX[]
Definition: message_definition_cache.hpp:16
foxglove::MessageSpec::dependencies
const std::set< std::string > dependencies
Definition: message_definition_cache.hpp:26
foxglove::remove_service_subtype
std::string remove_service_subtype(const std::string service_type)
Definition: message_definition_cache.cpp:163
foxglove::split_string
static std::vector< std::string > split_string(const std::string &str, const std::string &delimiter="\n")
Definition: message_definition_cache.cpp:123
foxglove::MessageDefinitionCache::get_full_text
std::pair< MessageDefinitionFormat, std::string > get_full_text(const std::string &package_resource_name)
Definition: message_definition_cache.cpp:342
foxglove::parse_idl_dependencies
static std::set< std::string > parse_idl_dependencies(const std::string &text)
Definition: message_definition_cache.cpp:62
foxglove::parse_msg_dependencies
static std::set< std::string > parse_msg_dependencies(const std::string &text, const std::string &package_context)
Definition: message_definition_cache.cpp:43
foxglove::DefinitionType
DefinitionType
Definition: message_definition_cache.cpp:37
foxglove::MessageSpec::text
const std::string text
Definition: message_definition_cache.hpp:27


foxglove_bridge
Author(s): Foxglove
autogenerated on Tue May 20 2025 02:34:26