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::*;