blob: a26486771920549ea2ac97e2f5536f983fabc380 [file] [log] [blame]
Brian Silverman67cdbd62022-08-15 06:03:55 -07001use std::{path::Path, ptr};
Brian Silverman7edd1ce2022-07-23 16:10:54 -07002
3use thiserror::Error;
4
5use aos_configuration_fbs::aos::Configuration as RustConfiguration;
Brian Silverman67cdbd62022-08-15 06:03:55 -07006use aos_flatbuffers::{transmute_table_to, NonSizePrefixedFlatbuffer};
Brian Silverman7edd1ce2022-07-23 16:10:54 -07007
8autocxx::include_cpp! (
9#include "aos/configuration.h"
10#include "aos/configuration_for_rust.h"
11#include "aos/configuration_generated.h"
12
13safety!(unsafe)
14
15generate!("aos::Configuration")
16generate!("aos::Channel")
17generate!("aos::Node")
18block!("flatbuffers::String")
19block!("flatbuffers::Verifier")
20
21generate!("aos::configuration::GetChannelForRust")
Brian Silverman67cdbd62022-08-15 06:03:55 -070022generate!("aos::configuration::GetNodeForRust")
Brian Silverman7edd1ce2022-07-23 16:10:54 -070023);
24
25#[cxx::bridge]
26mod ffi2 {
27 #[namespace = "aos::configuration"]
28 unsafe extern "C++" {
29 include!("aos/configuration_for_rust.h");
30 fn MaybeReadConfigForRust(path: &str, extra_import_paths: &[&str]) -> Vec<u8>;
31 }
32}
33
34pub use ffi::aos::{Channel, Configuration, Node};
35
36#[derive(Clone, Copy, Eq, PartialEq, Debug, Error)]
37pub enum ChannelLookupError {
38 #[error("channel not found")]
39 NotFound,
40}
41
42#[derive(Clone, Copy, Eq, PartialEq, Debug, Error)]
Brian Silverman67cdbd62022-08-15 06:03:55 -070043pub enum NodeLookupError {
44 #[error("node not found")]
45 NotFound,
46}
47
48#[derive(Clone, Copy, Eq, PartialEq, Debug, Error)]
Brian Silverman7edd1ce2022-07-23 16:10:54 -070049pub enum ReadConfigError {
50 #[error("duplicate imports or invalid paths")]
51 ReadFailed,
52}
53
Brian Silverman67cdbd62022-08-15 06:03:55 -070054impl<'a> Into<&'a Configuration> for RustConfiguration<'a> {
55 fn into(self) -> &'a Configuration {
56 unsafe { transmute_table_to(&self._tab) }
57 }
58}
59
60/// A trait with useful helper methods for a `Configuration` table. Import this trait to get these
61/// methods on both the `Configuration` struct generated by autocxx from the C++ code generated by
62/// flatbuffers, and the `Configuration` struct generated by flatbuffers in Rust.
63pub trait ConfigurationExt<'a>: Into<&'a Configuration> {
64 fn get_channel(
65 self,
Brian Silverman7edd1ce2022-07-23 16:10:54 -070066 name: &str,
67 typename: &str,
68 application_name: &str,
Brian Silverman67cdbd62022-08-15 06:03:55 -070069 node: Option<&Node>,
70 ) -> Result<&'a Channel, ChannelLookupError> {
Brian Silverman7edd1ce2022-07-23 16:10:54 -070071 // SAFETY: All the input references are valid pointers, and we're not doing anything with
72 // the result yet. It doesn't store any of the input references.
73 let channel = unsafe {
Brian Silverman67cdbd62022-08-15 06:03:55 -070074 ffi::aos::configuration::GetChannelForRust(
75 self.into(),
76 name,
77 typename,
78 application_name,
79 node.map_or(ptr::null(), |p| p),
80 )
Brian Silverman7edd1ce2022-07-23 16:10:54 -070081 };
82 if channel.is_null() {
83 Err(ChannelLookupError::NotFound)
84 } else {
85 // SAFETY: We know this is a valid pointer now, and we're returning it with the lifetime
Brian Silverman67cdbd62022-08-15 06:03:55 -070086 // inherited from `self` which owns it.
Brian Silverman7edd1ce2022-07-23 16:10:54 -070087 Ok(unsafe { &*channel })
88 }
89 }
Brian Silverman67cdbd62022-08-15 06:03:55 -070090
91 fn get_node(self, name: &str) -> Result<&'a Node, NodeLookupError> {
92 // SAFETY: All the input references are valid pointers, and we're not doing anything with
93 // the result yet. It doesn't store any of the input references.
94 let node = unsafe { ffi::aos::configuration::GetNodeForRust(self.into(), name) };
95 if node.is_null() {
96 Err(NodeLookupError::NotFound)
97 } else {
98 // SAFETY: We know this is a valid pointer now, and we're returning it with the lifetime
99 // inherited from `self` which owns it.
100 Ok(unsafe { &*node })
101 }
102 }
Brian Silverman7edd1ce2022-07-23 16:10:54 -0700103}
104
Brian Silverman67cdbd62022-08-15 06:03:55 -0700105impl<'a, T: Into<&'a Configuration>> ConfigurationExt<'a> for T {}
106
Brian Silverman7edd1ce2022-07-23 16:10:54 -0700107/// # Panics
108///
109/// `path` must be valid UTF-8.
110pub fn read_config_from(
111 path: &Path,
Brian Silverman67cdbd62022-08-15 06:03:55 -0700112) -> Result<NonSizePrefixedFlatbuffer<RustConfiguration<'static>, Vec<u8>>, ReadConfigError> {
Brian Silverman7edd1ce2022-07-23 16:10:54 -0700113 read_config_from_import_paths(path, &[])
114}
115
116/// # Panics
117///
118/// `path` and all members of `extra_import_paths` must be valid UTF-8.
119pub fn read_config_from_import_paths(
120 path: &Path,
121 extra_import_paths: &[&Path],
Brian Silverman67cdbd62022-08-15 06:03:55 -0700122) -> Result<NonSizePrefixedFlatbuffer<RustConfiguration<'static>, Vec<u8>>, ReadConfigError> {
Brian Silverman7edd1ce2022-07-23 16:10:54 -0700123 let extra_import_paths: Vec<_> = extra_import_paths
124 .iter()
125 .map(|p| p.to_str().expect("Paths must be UTF-8"))
126 .collect();
127 let buffer = ffi2::MaybeReadConfigForRust(
128 path.to_str().expect("Paths must be UTF-8"),
129 &extra_import_paths,
130 );
131 if buffer.is_empty() {
132 return Err(ReadConfigError::ReadFailed);
133 }
134 // SAFETY: The C++ code returns a valid flatbuffer (unless it returned an error, which we
135 // checked above).
136 return Ok(unsafe { NonSizePrefixedFlatbuffer::new_unchecked(buffer) });
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
Brian Silverman67cdbd62022-08-15 06:03:55 -0700143 use aos_flatbuffers::Flatbuffer;
144
Brian Silverman7edd1ce2022-07-23 16:10:54 -0700145 #[test]
146 fn read_config() {
147 let config = read_config_from(Path::new("aos/testdata/config1.json")).unwrap();
148 assert!(
149 config
150 .message()
151 .channels()
152 .unwrap()
153 .iter()
154 .find(|channel| channel.type_() == Some(".aos.bar"))
155 .is_some(),
156 "Failed to find the .aos.bar channel: {:?}",
157 config.message()
158 );
159 }
160}