Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jsonschema = "0.12.1"
chrono = "0.4.19"
as-any = "0.2.0"
mockall_double = "0.2.0"
gateway-addon-rust-codegen = { path = "gateway-addon-rust-codegen" }

[dependencies.serde]
version = "1.0"
Expand Down
3 changes: 3 additions & 0 deletions gateway-addon-rust-codegen/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
Cargo.lock
/.idea
12 changes: 12 additions & 0 deletions gateway-addon-rust-codegen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "gateway-addon-rust-codegen"
version = "1.0.0-alpha.1"
edition = "2018"

[lib]
proc-macro = true

[dependencies]
syn = { version = "1.0", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"
2 changes: 2 additions & 0 deletions gateway-addon-rust-codegen/rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
imports_granularity = "Crate"
format_code_in_doc_comments = true
228 changes: 228 additions & 0 deletions gateway-addon-rust-codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use std::str::FromStr;
use syn::DeriveInput;

/// Use this on a struct to generate a built adapter around it, including useful impls.
///
/// # Examples
/// ```
/// # use gateway_addon_rust::prelude::*;
/// # use async_trait::async_trait;
/// #[adapter]
/// struct ExampleAdapter {
/// foo: i32,
/// }
///
/// #[async_trait]
/// impl Adapter for BuiltExampleAdapter {
/// async fn on_unload(&mut self) -> Result<(), String> {
/// println!("Foo: {}", self.foo);
/// Ok(())
/// }
/// }
/// ```
/// will expand to
/// ```
/// # use gateway_addon_rust::{prelude::*, adapter::AdapterHandleWrapper};
/// # use std::ops::{Deref, DerefMut};
/// # use async_trait::async_trait;
/// struct ExampleAdapter {
/// foo: i32,
/// }
///
/// struct BuiltExampleAdapter {
/// data: ExampleAdapter,
/// adapter_handle: AdapterHandle,
/// }
///
/// impl AdapterHandleWrapper for BuiltExampleAdapter {
/// fn adapter_handle(&self) -> &AdapterHandle {
/// &self.adapter_handle
/// }
/// fn adapter_handle_mut(&mut self) -> &mut AdapterHandle {
/// &mut self.adapter_handle
/// }
/// }
///
/// impl BuildAdapter for ExampleAdapter {
/// type BuiltAdapter = BuiltExampleAdapter;
/// fn build(data: Self, adapter_handle: AdapterHandle) -> Self::BuiltAdapter {
/// BuiltExampleAdapter {
/// data,
/// adapter_handle,
/// }
/// }
/// }
///
/// impl Deref for BuiltExampleAdapter {
/// type Target = ExampleAdapter;
/// fn deref(&self) -> &Self::Target {
/// &self.data
/// }
/// }
///
/// impl DerefMut for BuiltExampleAdapter {
/// fn deref_mut(&mut self) -> &mut Self::Target {
/// &mut self.data
/// }
/// }
///
/// #[async_trait]
/// impl Adapter for BuiltExampleAdapter {
/// // ...
/// }
/// ```
#[proc_macro_attribute]
pub fn adapter(_args: TokenStream, input: TokenStream) -> TokenStream {
apply_macro(input, "adapter", "Adapter")
}

/// Use this on a struct to generate a built device around it, including useful impls.
///
/// # Examples
/// ```
/// # use gateway_addon_rust::prelude::*;
/// # use async_trait::async_trait;
/// #[device]
/// struct ExamplDevice {
/// foo: i32,
/// }
///
/// impl DeviceStructure for ExampleDevice {
/// // ...
/// # fn id(&self) -> String {
/// # "example-device".to_owned()
/// # }
/// # fn description(&self) -> DeviceDescription {
/// # DeviceDescription::default()
/// # }
/// }
///
/// #[async_trait]
/// impl Device for BuiltExampleDevice {}
/// ```
/// will expand to
/// ```
/// # use gateway_addon_rust::{prelude::*, device::DeviceHandleWrapper};
/// # use std::ops::{Deref, DerefMut};
/// # use async_trait::async_trait;
/// struct ExampleDevice { foo: i32 }
///
/// struct BuiltExampleDevice{
/// data: ExampleDevice,,
/// device_handle: DeviceHandle
/// }
///
/// impl DeviceHandleWrapper for BuiltExampleDevice {
/// fn device_handle(&self) -> &DeviceHandle {
/// &self.device_handle
/// }
/// fn device_handle_mut(&mut self) -> &mut DeviceHandle {
/// &mut self.device_handle
/// }
/// }
///
/// impl BuildDevice for ExampleDevice {
/// type BuiltDevice = BuiltExampleDevice;
/// fn build(data: Self, device_handle: DeviceHandle) -> Self::BuiltDevice {
/// BuiltExampleDevice { data, device_handle }
/// }
/// }
///
/// impl Deref for BuiltExampleDevice {
/// type Target = ExampleDevice;
/// fn deref(&self) -> &Self::Target {
/// &self.data
/// }
/// }
///
/// impl DerefMut for BuiltExampleDevice {
/// fn deref_mut(&mut self) -> &mut Self::Target {
/// &mut self.data
/// }
/// }
///
/// impl DeviceStructure for ExampleDevice {
/// // ...
/// # fn id(&self) -> String {
/// # "example-device".to_owned()
/// # }
/// # fn description(&self) -> DeviceDescription {
/// # DeviceDescription::default()
/// # }
/// }
///
/// #[async_trait]
/// impl Device for BuiltExampleDevice {}
/// ```
#[proc_macro_attribute]
pub fn device(_args: TokenStream, input: TokenStream) -> TokenStream {
apply_macro(input, "device", "Device")
}

fn apply_macro(input: TokenStream, name_snail_case: &str, name_camel_case: &str) -> TokenStream {
if let Ok(ast) = syn::parse2::<DeriveInput>(input.into()) {
alter_struct(ast, name_snail_case, name_camel_case).into()
} else {
panic!("`{}` has to be used with structs", name_snail_case)
}
}

fn alter_struct(ast: DeriveInput, name_snail_case: &str, name_camel_case: &str) -> TokenStream2 {
let trait_handle_wrapper = TokenStream2::from_str(&format!(
"gateway_addon_rust::{}::{}HandleWrapper",
name_snail_case, name_camel_case
))
.unwrap();
let trait_build = TokenStream2::from_str(&format!(
"gateway_addon_rust::{}::Build{}",
name_snail_case, name_camel_case
))
.unwrap();
let struct_built = TokenStream2::from_str(&format!("Built{}", name_camel_case)).unwrap();
let struct_handle = TokenStream2::from_str(&format!(
"gateway_addon_rust::{}::{}Handle",
name_snail_case, name_camel_case
))
.unwrap();
let fn_handle = TokenStream2::from_str(&format!("{}_handle", name_snail_case)).unwrap();
let fn_handle_mut = TokenStream2::from_str(&format!("{}_handle_mut", name_snail_case)).unwrap();

let struct_name = ast.ident.clone();
let struct_built_name = TokenStream2::from_str(&format!("Built{}", struct_name)).unwrap();

quote! {
#ast
impl #trait_build for #struct_name {
type #struct_built = #struct_built_name;
fn build(data: Self, #fn_handle: #struct_handle) -> Self::#struct_built {
#struct_built_name { data, #fn_handle }
}
}
struct #struct_built_name {
data: #struct_name,
#fn_handle: #struct_handle,
}
impl #trait_handle_wrapper for #struct_built_name {
fn #fn_handle(&self) -> &#struct_handle {
&self.#fn_handle
}
fn #fn_handle_mut(&mut self) -> &mut #struct_handle {
&mut self.#fn_handle
}
}
impl std::ops::Deref for #struct_built_name {
type Target = #struct_name;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl std::ops::DerefMut for #struct_built_name {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
}
}
43 changes: 23 additions & 20 deletions src/adapter/adapter_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.*
*/

use crate::{client::Client, error::WebthingsError, Adapter, Device, DeviceBuilder, DeviceHandle};
use crate::{
client::Client,
device::{BuildDevice, DeviceStructure},
error::WebthingsError,
Adapter, Device, DeviceHandle,
};
use std::{
collections::HashMap,
sync::{Arc, Weak},
Expand Down Expand Up @@ -38,16 +43,12 @@ impl AdapterHandle {
}
}

/// Build and add a new device using the given [device builder][crate::DeviceBuilder].
pub async fn add_device<D, B>(
/// Build and add a new device using the given data struct.
pub async fn add_device<D: BuildDevice + DeviceStructure>(
&mut self,
device_builder: B,
) -> Result<Arc<Mutex<Box<dyn Device>>>, WebthingsError>
where
D: Device,
B: DeviceBuilder<Device = D>,
{
let device_description = device_builder.full_description()?;
device: D,
) -> Result<Arc<Mutex<Box<dyn Device>>>, WebthingsError> {
let device_description = device.full_description()?;

let message: Message = DeviceAddedNotificationMessageData {
plugin_id: self.plugin_id.clone(),
Expand All @@ -65,16 +66,16 @@ impl AdapterHandle {
self.weak.clone(),
self.plugin_id.clone(),
self.adapter_id.clone(),
device_builder.id(),
device_builder.description(),
device.id(),
device.description(),
);

let properties = device_builder.properties();
let actions = device_builder.actions();
let events = device_builder.events();
let properties = device.properties();
let actions = device.actions();
let events = device.events();

let device: Arc<Mutex<Box<dyn Device>>> =
Arc::new(Mutex::new(Box::new(device_builder.build(device_handle))));
Arc::new(Mutex::new(Box::new(D::build(device, device_handle))));
let device_weak = Arc::downgrade(&device);

{
Expand Down Expand Up @@ -145,7 +146,9 @@ impl AdapterHandle {
#[cfg(test)]
pub(crate) mod tests {
use crate::{
client::Client, device::tests::MockDeviceBuilder, AdapterHandle, Device, DeviceBuilder,
client::Client,
device::{tests::MockDevice, DeviceStructure},
AdapterHandle, Device,
};
use rstest::{fixture, rstest};
use std::sync::Arc;
Expand All @@ -156,8 +159,8 @@ pub(crate) mod tests {
adapter: &mut AdapterHandle,
device_id: &str,
) -> Arc<Mutex<Box<dyn Device>>> {
let device_builder = MockDeviceBuilder::new(device_id.to_owned());
let expected_description = device_builder.full_description().unwrap();
let device = MockDevice::new(device_id.to_owned());
let expected_description = device.full_description().unwrap();

let plugin_id = adapter.plugin_id.to_owned();
let adapter_id = adapter.adapter_id.to_owned();
Expand All @@ -178,7 +181,7 @@ pub(crate) mod tests {
.times(1)
.returning(|_| Ok(()));

adapter.add_device(device_builder).await.unwrap()
adapter.add_device(device).await.unwrap()
}

const PLUGIN_ID: &str = "plugin_id";
Expand Down
Loading