| use std::{path::Path, ptr}; |
| |
| use thiserror::Error; |
| |
| use aos_configuration_fbs::aos::{Channel as RustChannel, Configuration as RustConfiguration}; |
| use aos_flatbuffers::{transmute_table_to, NonSizePrefixedFlatbuffer}; |
| |
| autocxx::include_cpp! ( |
| #include "aos/configuration.h" |
| #include "aos/configuration_for_rust.h" |
| #include "aos/configuration_generated.h" |
| |
| safety!(unsafe) |
| |
| generate!("aos::Configuration") |
| generate!("aos::Channel") |
| generate!("aos::Node") |
| block!("flatbuffers::String") |
| block!("flatbuffers::Verifier") |
| |
| generate!("aos::configuration::GetChannelForRust") |
| generate!("aos::configuration::GetNodeForRust") |
| |
| generate!("aos::configuration::HasChannelTypeForRust") |
| generate!("aos::configuration::GetChannelTypeForRust") |
| generate!("aos::configuration::HasChannelNameForRust") |
| generate!("aos::configuration::GetChannelNameForRust") |
| ); |
| |
| #[cxx::bridge] |
| mod ffi2 { |
| #[namespace = "aos::configuration"] |
| unsafe extern "C++" { |
| include!("aos/configuration_for_rust.h"); |
| fn MaybeReadConfigForRust(path: &str, extra_import_paths: &[&str]) -> Vec<u8>; |
| } |
| } |
| |
| pub use ffi::aos::{Channel, Configuration, Node}; |
| |
| #[derive(Clone, Copy, Eq, PartialEq, Debug, Error)] |
| pub enum ChannelLookupError { |
| #[error("channel not found")] |
| NotFound, |
| } |
| |
| #[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<'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: 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.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 `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 {} |
| |
| impl<'a> Into<&'a Channel> for RustChannel<'a> { |
| fn into(self) -> &'a Channel { |
| unsafe { transmute_table_to(&self._tab) } |
| } |
| } |
| |
| pub trait ChannelExt<'a>: Into<&'a Channel> { |
| fn type_(self) -> Option<&'a str> { |
| let c = self.into(); |
| ffi::aos::configuration::HasChannelTypeForRust(c) |
| .then(move || ffi::aos::configuration::GetChannelTypeForRust(c)) |
| } |
| |
| fn name(self) -> Option<&'a str> { |
| let c = self.into(); |
| ffi::aos::configuration::HasChannelNameForRust(c) |
| .then(move || ffi::aos::configuration::GetChannelNameForRust(c)) |
| } |
| } |
| |
| impl<'a, T: Into<&'a Channel>> ChannelExt<'a> for T {} |
| |
| /// # Panics |
| /// |
| /// `path` must be valid UTF-8. |
| pub fn read_config_from( |
| path: &Path, |
| ) -> Result<NonSizePrefixedFlatbuffer<RustConfiguration<'static>, Vec<u8>>, ReadConfigError> { |
| read_config_from_import_paths(path, &[]) |
| } |
| |
| /// # Panics |
| /// |
| /// `path` and all members of `extra_import_paths` must be valid UTF-8. |
| pub fn read_config_from_import_paths( |
| path: &Path, |
| extra_import_paths: &[&Path], |
| ) -> 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")) |
| .collect(); |
| let buffer = ffi2::MaybeReadConfigForRust( |
| path.to_str().expect("Paths must be UTF-8"), |
| &extra_import_paths, |
| ); |
| if buffer.is_empty() { |
| return Err(ReadConfigError::ReadFailed); |
| } |
| // SAFETY: The C++ code returns a valid flatbuffer (unless it returned an error, which we |
| // checked above). |
| return Ok(unsafe { NonSizePrefixedFlatbuffer::new_unchecked(buffer) }); |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| use aos_flatbuffers::Flatbuffer; |
| use aos_testing_path::artifact_path; |
| |
| #[test] |
| fn read_config() { |
| let config = |
| read_config_from(&artifact_path(Path::new("aos/testdata/config1.json"))).unwrap(); |
| assert!( |
| config |
| .message() |
| .channels() |
| .unwrap() |
| .iter() |
| .find(|channel| channel.type_() == Some(".aos.bar")) |
| .is_some(), |
| "Failed to find the .aos.bar channel: {:?}", |
| config.message() |
| ); |
| } |
| } |