blob: 31a259af6121e310d48b1b1b9f0b3dc6324aadff [file] [log] [blame]
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()
);
}
}