Add more helpful Rust Configuration methods

Also switch where they're attached so they can be used with both
versions of Configuration we have in Rust.

Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I944cd0fc05db460573e9df694e812bdf0f31ffd7
diff --git a/aos/configuration.rs b/aos/configuration.rs
index 479a065..a264867 100644
--- a/aos/configuration.rs
+++ b/aos/configuration.rs
@@ -1,9 +1,9 @@
-use std::path::Path;
+use std::{path::Path, ptr};
 
 use thiserror::Error;
 
 use aos_configuration_fbs::aos::Configuration as RustConfiguration;
-use aos_flatbuffers::{Flatbuffer, NonSizePrefixedFlatbuffer};
+use aos_flatbuffers::{transmute_table_to, NonSizePrefixedFlatbuffer};
 
 autocxx::include_cpp! (
 #include "aos/configuration.h"
@@ -19,6 +19,7 @@
 block!("flatbuffers::Verifier")
 
 generate!("aos::configuration::GetChannelForRust")
+generate!("aos::configuration::GetNodeForRust")
 );
 
 #[cxx::bridge]
@@ -39,40 +40,76 @@
 }
 
 #[derive(Clone, Copy, Eq, PartialEq, Debug, Error)]
+pub enum NodeLookupError {
+    #[error("node not found")]
+    NotFound,
+}
+
+#[derive(Clone, Copy, Eq, PartialEq, Debug, Error)]
 pub enum ReadConfigError {
     #[error("duplicate imports or invalid paths")]
     ReadFailed,
 }
 
-impl Configuration {
-    pub fn get_channel(
-        &self,
+impl<'a> Into<&'a Configuration> for RustConfiguration<'a> {
+    fn into(self) -> &'a Configuration {
+        unsafe { transmute_table_to(&self._tab) }
+    }
+}
+
+/// A trait with useful helper methods for a `Configuration` table. Import this trait to get these
+/// methods on both the `Configuration` struct generated by autocxx from the C++ code generated by
+/// flatbuffers, and the `Configuration` struct generated by flatbuffers in Rust.
+pub trait ConfigurationExt<'a>: Into<&'a Configuration> {
+    fn get_channel(
+        self,
         name: &str,
         typename: &str,
         application_name: &str,
-        node: &Node,
-    ) -> Result<&Channel, ChannelLookupError> {
+        node: Option<&Node>,
+    ) -> Result<&'a Channel, ChannelLookupError> {
         // SAFETY: All the input references are valid pointers, and we're not doing anything with
         // the result yet. It doesn't store any of the input references.
         let channel = unsafe {
-            ffi::aos::configuration::GetChannelForRust(self, name, typename, application_name, node)
+            ffi::aos::configuration::GetChannelForRust(
+                self.into(),
+                name,
+                typename,
+                application_name,
+                node.map_or(ptr::null(), |p| p),
+            )
         };
         if channel.is_null() {
             Err(ChannelLookupError::NotFound)
         } else {
             // SAFETY: We know this is a valid pointer now, and we're returning it with the lifetime
-            // inherited from `configuration` which owns it.
+            // inherited from `self` which owns it.
             Ok(unsafe { &*channel })
         }
     }
+
+    fn get_node(self, name: &str) -> Result<&'a Node, NodeLookupError> {
+        // SAFETY: All the input references are valid pointers, and we're not doing anything with
+        // the result yet. It doesn't store any of the input references.
+        let node = unsafe { ffi::aos::configuration::GetNodeForRust(self.into(), name) };
+        if node.is_null() {
+            Err(NodeLookupError::NotFound)
+        } else {
+            // SAFETY: We know this is a valid pointer now, and we're returning it with the lifetime
+            // inherited from `self` which owns it.
+            Ok(unsafe { &*node })
+        }
+    }
 }
 
+impl<'a, T: Into<&'a Configuration>> ConfigurationExt<'a> for T {}
+
 /// # Panics
 ///
 /// `path` must be valid UTF-8.
 pub fn read_config_from(
     path: &Path,
-) -> Result<impl Flatbuffer<RustConfiguration<'static>>, ReadConfigError> {
+) -> Result<NonSizePrefixedFlatbuffer<RustConfiguration<'static>, Vec<u8>>, ReadConfigError> {
     read_config_from_import_paths(path, &[])
 }
 
@@ -82,7 +119,7 @@
 pub fn read_config_from_import_paths(
     path: &Path,
     extra_import_paths: &[&Path],
-) -> Result<impl Flatbuffer<RustConfiguration<'static>>, ReadConfigError> {
+) -> Result<NonSizePrefixedFlatbuffer<RustConfiguration<'static>, Vec<u8>>, ReadConfigError> {
     let extra_import_paths: Vec<_> = extra_import_paths
         .iter()
         .map(|p| p.to_str().expect("Paths must be UTF-8"))
@@ -103,6 +140,8 @@
 mod tests {
     use super::*;
 
+    use aos_flatbuffers::Flatbuffer;
+
     #[test]
     fn read_config() {
         let config = read_config_from(Path::new("aos/testdata/config1.json")).unwrap();
diff --git a/aos/configuration_for_rust.cc b/aos/configuration_for_rust.cc
index 3c3daad..64d070f 100644
--- a/aos/configuration_for_rust.cc
+++ b/aos/configuration_for_rust.cc
@@ -12,13 +12,18 @@
                     RustStrToStringView(application_name), node);
 }
 
+const Node *GetNodeForRust(const Configuration *config, rust::Str name) {
+  return GetNode(config, RustStrToStringView(name));
+}
+
 rust::Vec<uint8_t> MaybeReadConfigForRust(
     rust::Str path, rust::Slice<const rust::Str> extra_import_paths) {
   std::vector<std::string_view> cc_extra_import_paths;
   for (const rust::Str &extra_import_path : extra_import_paths) {
     cc_extra_import_paths.push_back(RustStrToStringView(extra_import_path));
   }
-  const auto cc_result = MaybeReadConfig(RustStrToStringView(path));
+  const auto cc_result =
+      MaybeReadConfig(RustStrToStringView(path), cc_extra_import_paths);
   rust::Vec<uint8_t> result;
   if (cc_result) {
     result.reserve(cc_result->span().size());
diff --git a/aos/configuration_for_rust.h b/aos/configuration_for_rust.h
index ae18346..9bf9c75 100644
--- a/aos/configuration_for_rust.h
+++ b/aos/configuration_for_rust.h
@@ -10,9 +10,11 @@
                                  rust::Str type, rust::Str application_name,
                                  const Node *node);
 
+const Node *GetNodeForRust(const Configuration *config, rust::Str name);
+
 // Returns a Configuration flatbuffer. Returns an empty vector on errors.
-// TODO: It would be nice to return more detailed errors (not found vs could not
-// parse vs merging error).
+// TODO(Brian): It would be nice to return more detailed errors (not found vs
+// could not parse vs merging error).
 rust::Vec<uint8_t> MaybeReadConfigForRust(
     rust::Str path, rust::Slice<const rust::Str> extra_import_paths);
 
diff --git a/aos/flatbuffers.rs b/aos/flatbuffers.rs
index 073d206..aec61d8 100644
--- a/aos/flatbuffers.rs
+++ b/aos/flatbuffers.rs
@@ -341,6 +341,21 @@
 /// storing references to the flatbuffer anywhere it's a strict increase in flexibility.
 pub type DynFlatbuffer<'buf, T> = Box<dyn Flatbuffer<T> + 'buf>;
 
+/// Transmutes the in-memory flatbuffer data to a type. This is only useful if `T` is a C++-style
+/// flatbuffer generated class, it doesn't match the layout of any type in any language.
+///
+/// It is strongly recommended to explicitly specify `T` using the turbofish operator when using
+/// this function. Mismatches of the type are very bad and leaving it implicit is error prone.
+///
+/// # Safety
+///
+/// This is extremely unsafe. `T` has to be something expecting a flatbuffer table of the correct
+/// type as its address.
+pub unsafe fn transmute_table_to<'a, T>(table: &flatbuffers::Table<'a>) -> &'a T {
+    let address = &table.buf[table.loc];
+    std::mem::transmute(address)
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;