blob: 31a259af6121e310d48b1b1b9f0b3dc6324aadff [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
Brian Silverman6d499362022-08-18 17:50:46 -07005use aos_configuration_fbs::aos::{Channel as RustChannel, 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 Silverman6d499362022-08-18 17:50:46 -070023
24generate!("aos::configuration::HasChannelTypeForRust")
25generate!("aos::configuration::GetChannelTypeForRust")
26generate!("aos::configuration::HasChannelNameForRust")
27generate!("aos::configuration::GetChannelNameForRust")
Brian Silverman7edd1ce2022-07-23 16:10:54 -070028);
29
30#[cxx::bridge]
31mod ffi2 {
32 #[namespace = "aos::configuration"]
33 unsafe extern "C++" {
34 include!("aos/configuration_for_rust.h");
35 fn MaybeReadConfigForRust(path: &str, extra_import_paths: &[&str]) -> Vec<u8>;
36 }
37}
38
39pub use ffi::aos::{Channel, Configuration, Node};
40
41#[derive(Clone, Copy, Eq, PartialEq, Debug, Error)]
42pub enum ChannelLookupError {
43 #[error("channel not found")]
44 NotFound,
45}
46
47#[derive(Clone, Copy, Eq, PartialEq, Debug, Error)]
Brian Silverman67cdbd62022-08-15 06:03:55 -070048pub enum NodeLookupError {
49 #[error("node not found")]
50 NotFound,
51}
52
53#[derive(Clone, Copy, Eq, PartialEq, Debug, Error)]
Brian Silverman7edd1ce2022-07-23 16:10:54 -070054pub enum ReadConfigError {
55 #[error("duplicate imports or invalid paths")]
56 ReadFailed,
57}
58
Brian Silverman67cdbd62022-08-15 06:03:55 -070059impl<'a> Into<&'a Configuration> for RustConfiguration<'a> {
60 fn into(self) -> &'a Configuration {
61 unsafe { transmute_table_to(&self._tab) }
62 }
63}
64
65/// A trait with useful helper methods for a `Configuration` table. Import this trait to get these
66/// methods on both the `Configuration` struct generated by autocxx from the C++ code generated by
67/// flatbuffers, and the `Configuration` struct generated by flatbuffers in Rust.
68pub trait ConfigurationExt<'a>: Into<&'a Configuration> {
69 fn get_channel(
70 self,
Brian Silverman7edd1ce2022-07-23 16:10:54 -070071 name: &str,
72 typename: &str,
73 application_name: &str,
Brian Silverman67cdbd62022-08-15 06:03:55 -070074 node: Option<&Node>,
75 ) -> Result<&'a Channel, ChannelLookupError> {
Brian Silverman7edd1ce2022-07-23 16:10:54 -070076 // SAFETY: All the input references are valid pointers, and we're not doing anything with
77 // the result yet. It doesn't store any of the input references.
78 let channel = unsafe {
Brian Silverman67cdbd62022-08-15 06:03:55 -070079 ffi::aos::configuration::GetChannelForRust(
80 self.into(),
81 name,
82 typename,
83 application_name,
84 node.map_or(ptr::null(), |p| p),
85 )
Brian Silverman7edd1ce2022-07-23 16:10:54 -070086 };
87 if channel.is_null() {
88 Err(ChannelLookupError::NotFound)
89 } else {
90 // SAFETY: We know this is a valid pointer now, and we're returning it with the lifetime
Brian Silverman67cdbd62022-08-15 06:03:55 -070091 // inherited from `self` which owns it.
Brian Silverman7edd1ce2022-07-23 16:10:54 -070092 Ok(unsafe { &*channel })
93 }
94 }
Brian Silverman67cdbd62022-08-15 06:03:55 -070095
96 fn get_node(self, name: &str) -> Result<&'a Node, NodeLookupError> {
97 // SAFETY: All the input references are valid pointers, and we're not doing anything with
98 // the result yet. It doesn't store any of the input references.
99 let node = unsafe { ffi::aos::configuration::GetNodeForRust(self.into(), name) };
100 if node.is_null() {
101 Err(NodeLookupError::NotFound)
102 } else {
103 // SAFETY: We know this is a valid pointer now, and we're returning it with the lifetime
104 // inherited from `self` which owns it.
105 Ok(unsafe { &*node })
106 }
107 }
Brian Silverman7edd1ce2022-07-23 16:10:54 -0700108}
109
Brian Silverman67cdbd62022-08-15 06:03:55 -0700110impl<'a, T: Into<&'a Configuration>> ConfigurationExt<'a> for T {}
111
Brian Silverman6d499362022-08-18 17:50:46 -0700112impl<'a> Into<&'a Channel> for RustChannel<'a> {
113 fn into(self) -> &'a Channel {
114 unsafe { transmute_table_to(&self._tab) }
115 }
116}
117
118pub trait ChannelExt<'a>: Into<&'a Channel> {
119 fn type_(self) -> Option<&'a str> {
120 let c = self.into();
121 ffi::aos::configuration::HasChannelTypeForRust(c)
122 .then(move || ffi::aos::configuration::GetChannelTypeForRust(c))
123 }
124
125 fn name(self) -> Option<&'a str> {
126 let c = self.into();
127 ffi::aos::configuration::HasChannelNameForRust(c)
128 .then(move || ffi::aos::configuration::GetChannelNameForRust(c))
129 }
130}
131
132impl<'a, T: Into<&'a Channel>> ChannelExt<'a> for T {}
133
Brian Silverman7edd1ce2022-07-23 16:10:54 -0700134/// # Panics
135///
136/// `path` must be valid UTF-8.
137pub fn read_config_from(
138 path: &Path,
Brian Silverman67cdbd62022-08-15 06:03:55 -0700139) -> Result<NonSizePrefixedFlatbuffer<RustConfiguration<'static>, Vec<u8>>, ReadConfigError> {
Brian Silverman7edd1ce2022-07-23 16:10:54 -0700140 read_config_from_import_paths(path, &[])
141}
142
143/// # Panics
144///
145/// `path` and all members of `extra_import_paths` must be valid UTF-8.
146pub fn read_config_from_import_paths(
147 path: &Path,
148 extra_import_paths: &[&Path],
Brian Silverman67cdbd62022-08-15 06:03:55 -0700149) -> Result<NonSizePrefixedFlatbuffer<RustConfiguration<'static>, Vec<u8>>, ReadConfigError> {
Brian Silverman7edd1ce2022-07-23 16:10:54 -0700150 let extra_import_paths: Vec<_> = extra_import_paths
151 .iter()
152 .map(|p| p.to_str().expect("Paths must be UTF-8"))
153 .collect();
154 let buffer = ffi2::MaybeReadConfigForRust(
155 path.to_str().expect("Paths must be UTF-8"),
156 &extra_import_paths,
157 );
158 if buffer.is_empty() {
159 return Err(ReadConfigError::ReadFailed);
160 }
161 // SAFETY: The C++ code returns a valid flatbuffer (unless it returned an error, which we
162 // checked above).
163 return Ok(unsafe { NonSizePrefixedFlatbuffer::new_unchecked(buffer) });
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
Brian Silverman67cdbd62022-08-15 06:03:55 -0700170 use aos_flatbuffers::Flatbuffer;
James Kuszmaul1cd3c2b2024-05-21 17:08:10 -0700171 use aos_testing_path::artifact_path;
Brian Silverman67cdbd62022-08-15 06:03:55 -0700172
Brian Silverman7edd1ce2022-07-23 16:10:54 -0700173 #[test]
174 fn read_config() {
James Kuszmaul1cd3c2b2024-05-21 17:08:10 -0700175 let config =
176 read_config_from(&artifact_path(Path::new("aos/testdata/config1.json"))).unwrap();
Brian Silverman7edd1ce2022-07-23 16:10:54 -0700177 assert!(
178 config
179 .message()
180 .channels()
181 .unwrap()
182 .iter()
183 .find(|channel| channel.type_() == Some(".aos.bar"))
184 .is_some(),
185 "Failed to find the .aos.bar channel: {:?}",
186 config.message()
187 );
188 }
189}