diff --git a/Cargo.toml b/Cargo.toml index ec52415..5c8a8dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/gateway-addon-rust-codegen/.gitignore b/gateway-addon-rust-codegen/.gitignore new file mode 100644 index 0000000..2de3917 --- /dev/null +++ b/gateway-addon-rust-codegen/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +/.idea diff --git a/gateway-addon-rust-codegen/Cargo.toml b/gateway-addon-rust-codegen/Cargo.toml new file mode 100644 index 0000000..59ba68f --- /dev/null +++ b/gateway-addon-rust-codegen/Cargo.toml @@ -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" \ No newline at end of file diff --git a/gateway-addon-rust-codegen/rustfmt.toml b/gateway-addon-rust-codegen/rustfmt.toml new file mode 100644 index 0000000..66f5103 --- /dev/null +++ b/gateway-addon-rust-codegen/rustfmt.toml @@ -0,0 +1,2 @@ +imports_granularity = "Crate" +format_code_in_doc_comments = true \ No newline at end of file diff --git a/gateway-addon-rust-codegen/src/lib.rs b/gateway-addon-rust-codegen/src/lib.rs new file mode 100644 index 0000000..a71c450 --- /dev/null +++ b/gateway-addon-rust-codegen/src/lib.rs @@ -0,0 +1,129 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::str::FromStr; +use syn::DeriveInput; + +#[proc_macro_attribute] +pub fn adapter(_args: TokenStream, input: TokenStream) -> TokenStream { + apply_macro(input, "adapter", "Adapter", None) +} + +#[proc_macro_attribute] +pub fn device(_args: TokenStream, input: TokenStream) -> TokenStream { + apply_macro(input, "device", "Device", None) +} + +#[proc_macro_attribute] +pub fn property(_args: TokenStream, input: TokenStream) -> TokenStream { + apply_macro(input, "property", "Property", Some("Value")) +} + +#[proc_macro_attribute] +pub fn event(_args: TokenStream, input: TokenStream) -> TokenStream { + apply_macro(input, "event", "Event", Some("Data")) +} + +#[proc_macro_attribute] +pub fn api_handler(_args: TokenStream, input: TokenStream) -> TokenStream { + apply_macro(input, "api_handler", "ApiHandler", None) +} + +fn apply_macro( + input: TokenStream, + name_snail_case: &str, + name_camel_case: &str, + generic_name: Option<&str>, +) -> TokenStream { + if let Ok(ast) = syn::parse2::(input.into()) { + alter_struct(ast, name_snail_case, name_camel_case, generic_name).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, + generic_name: Option<&str>, +) -> TokenStream2 { + let struct_name = ast.ident.clone(); + let visibility = ast.vis.clone(); + let struct_built_name = TokenStream2::from_str(&format!("Built{}", struct_name)).unwrap(); + + let trait_handle_wrapper = TokenStream2::from_str(&format!( + "gateway_addon_rust::{}::Built{}", + name_snail_case, name_camel_case + )) + .unwrap(); + let trait_build = TokenStream2::from_str(&format!( + "gateway_addon_rust::{}::{}Builder", + 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(&if let Some(generic_name) = generic_name { + format!( + "gateway_addon_rust::{name_snail_case}::{name_camel_case}Handle<<{struct_name} as gateway_addon_rust::{name_snail_case}::{name_camel_case}Structure>::{generic_name}>", + name_snail_case = name_snail_case, + name_camel_case = name_camel_case, + struct_name = struct_name, + generic_name = generic_name, + ) + } else { + 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 typedef = TokenStream2::from_str(&if let Some(generic_name) = generic_name { + format!( + "type {generic_name} = <{struct_name} as gateway_addon_rust::{name_snail_case}::{name_camel_case}Structure>::{generic_name};", + name_snail_case = name_snail_case, + name_camel_case = name_camel_case, + struct_name = struct_name, + generic_name = generic_name, + ) + } else { + "".to_owned() + }) + .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 } + } + } + #visibility struct #struct_built_name { + data: #struct_name, + #fn_handle: #struct_handle, + } + impl #trait_handle_wrapper for #struct_built_name { + #typedef + 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 + } + } + } +} diff --git a/src/action/action_description.rs b/src/action/action_description.rs index d4fb507..9f1bdcc 100644 --- a/src/action/action_description.rs +++ b/src/action/action_description.rs @@ -65,12 +65,14 @@ impl ActionDescription { } /// Set `@type`. + #[must_use] pub fn at_type(mut self, at_type: AtType) -> Self { self.at_type = Some(at_type); self } /// Set `description`. + #[must_use] pub fn description(mut self, description: impl Into) -> Self { self.description = Some(description.into()); self @@ -88,12 +90,14 @@ impl ActionDescription { /// })) /// # ; /// ``` + #[must_use] pub fn input(mut self, input: serde_json::Value) -> Self { self.input = Some(input); self } /// Set `links`. + #[must_use] pub fn links(mut self, links: Vec) -> Self { self.links = Some(links); self @@ -119,6 +123,7 @@ impl ActionDescription { /// }) /// # ; /// ``` + #[must_use] pub fn link(mut self, link: Link) -> Self { match self.links { None => self.links = Some(vec![link]), @@ -128,6 +133,7 @@ impl ActionDescription { } /// Set `title`. + #[must_use] pub fn title(mut self, title: impl Into) -> Self { self.title = Some(title.into()); self diff --git a/src/action/action_input.rs b/src/action/action_input.rs index 2494071..234ba99 100644 --- a/src/action/action_input.rs +++ b/src/action/action_input.rs @@ -131,11 +131,11 @@ impl Input for NoInput { } fn deserialize(value: serde_json::Value) -> Result { - if value == serde_json::Value::Null { + if value == json!(null) || value == json!({}) { Ok(NoInput) } else { Err(WebthingsError::Serialization(serde_json::Error::custom( - "Expected no input", + format!("Expected no input, got {:?}", value), ))) } } diff --git a/src/action/action_trait.rs b/src/action/action_trait.rs index 84ddb97..eb8966c 100644 --- a/src/action/action_trait.rs +++ b/src/action/action_trait.rs @@ -68,6 +68,9 @@ pub trait Action: Send + Sync + 'static { Err("Action does not implement canceling".to_owned()) } + /// Called once after initialization. + fn post_init(&mut self) {} + #[doc(hidden)] fn full_description(&self) -> FullActionDescription { self.description().into_full_description() @@ -134,6 +137,9 @@ pub trait ActionBase: Send + Sync + AsAny + 'static { #[doc(hidden)] async fn cancel(&mut self, action_id: String) -> Result<(), String>; + + #[doc(hidden)] + fn post_init(&mut self) {} } impl Downcast for dyn ActionBase {} @@ -158,10 +164,16 @@ impl ActionBase for T { async fn cancel(&mut self, action_id: String) -> Result<(), String> { ::cancel(self, action_id).await } + + fn post_init(&mut self) { + ::post_init(self) + } } #[cfg(test)] pub(crate) mod tests { + use std::ops::{Deref, DerefMut}; + use crate::{action::Input, Action, ActionDescription, ActionHandle}; use async_trait::async_trait; use mockall::mock; @@ -170,23 +182,40 @@ pub(crate) mod tests { pub ActionHelper { pub fn perform(&mut self, action_handle: ActionHandle) -> Result<(), String>; pub fn cancel(&mut self, action_id: String) -> Result<(), String>; + pub fn post_init(&mut self); } } pub struct MockAction { action_name: String, pub action_helper: MockActionHelper, + pub expect_post_init: bool, } impl MockAction { pub fn new(action_name: String) -> Self { Self { action_name, + expect_post_init: false, action_helper: MockActionHelper::new(), } } } + impl Deref for MockAction { + type Target = MockActionHelper; + + fn deref(&self) -> &Self::Target { + &self.action_helper + } + } + + impl DerefMut for MockAction { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.action_helper + } + } + #[async_trait] impl Action for MockAction { type Input = T; @@ -210,5 +239,11 @@ pub(crate) mod tests { async fn cancel(&mut self, action_id: String) -> Result<(), String> { self.action_helper.cancel(action_id) } + + fn post_init(&mut self) { + if self.expect_post_init { + self.action_helper.post_init(); + } + } } } diff --git a/src/adapter/adapter_builder.rs b/src/adapter/adapter_builder.rs new file mode 100644 index 0000000..bc9b274 --- /dev/null +++ b/src/adapter/adapter_builder.rs @@ -0,0 +1,163 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/.* + */ + +use crate::{Adapter, AdapterHandle}; +use as_any::AsAny; + +/// A trait used to specify the structure of a WebthingsIO adapter. +/// +/// # Examples +/// ```no_run +/// # use gateway_addon_rust::{prelude::*, plugin::connect, example::ExampleDevice, error::WebthingsError, adapter::BuiltAdapter}; +/// # use webthings_gateway_ipc_types::DeviceWithoutId; +/// # use async_trait::async_trait; +/// # use as_any::Downcast; +/// struct ExampleAdapter { foo: i32 } +/// +/// impl AdapterStructure for ExampleAdapter { +/// fn id(&self) -> String { +/// "example-adapter".to_owned() +/// } +/// +/// fn name(&self) -> String { +/// "Example Adapter".to_owned() +/// } +/// } +/// ``` +pub trait AdapterStructure: Send + Sync + AsAny + 'static { + /// ID of the adapter. + fn id(&self) -> String; + + /// Name of the adapter. + fn name(&self) -> String; +} + +/// A trait used to build an [Adapter] around a data struct and an [adapter handle][AdapterHandle]. +/// +/// When you use the [adapter][macro@crate::adapter] macro, this will be implemented automatically. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, adapter::{BuiltAdapter, AdapterBuilder}}; +/// # use async_trait::async_trait; +/// struct ExampleAdapter { +/// foo: i32, +/// } +/// +/// impl AdapterStructure for ExampleAdapter { +/// // ... +/// # fn id(&self) -> String { +/// # "example-adapter".to_owned() +/// # } +/// # fn name(&self) -> String { +/// # "Example Adapter".to_owned() +/// # } +/// } +/// +/// struct BuiltExampleAdapter { +/// data: ExampleAdapter, +/// adapter_handle: AdapterHandle, +/// } +/// +/// impl BuiltAdapter for BuiltExampleAdapter { +/// // ... +/// # fn adapter_handle(&self) -> &AdapterHandle { +/// # &self.adapter_handle +/// # } +/// # fn adapter_handle_mut(&mut self) -> &mut AdapterHandle { +/// # &mut self.adapter_handle +/// # } +/// } +/// +/// #[async_trait] +/// impl Adapter for BuiltExampleAdapter {} +/// +/// impl AdapterBuilder for ExampleAdapter { +/// type BuiltAdapter = BuiltExampleAdapter; +/// fn build(data: Self, adapter_handle: AdapterHandle) -> Self::BuiltAdapter { +/// BuiltExampleAdapter { +/// data, +/// adapter_handle, +/// } +/// } +/// } +/// ``` +pub trait AdapterBuilder: AdapterStructure { + /// Type of [Adapter] to build. + type BuiltAdapter: Adapter; + + /// Build the [adapter][Adapter] from a data struct and an [adapter handle][AdapterHandle]. + fn build(data: Self, adapter_handle: AdapterHandle) -> Self::BuiltAdapter; +} + +#[cfg(test)] +pub(crate) mod tests { + use crate::{ + adapter::{tests::BuiltMockAdapter, AdapterBuilder}, + AdapterHandle, AdapterStructure, + }; + use mockall::mock; + use std::time::Duration; + use webthings_gateway_ipc_types::DeviceWithoutId; + + mock! { + pub AdapterHelper { + pub async fn on_unload(&mut self) -> Result<(), String>; + pub async fn on_start_pairing(&mut self, timeout: Duration) -> Result<(), String>; + pub async fn on_cancel_pairing(&mut self) -> Result<(), String>; + pub async fn on_device_saved( + &mut self, + device_id: String, + device_description: DeviceWithoutId + ) -> Result<(), String>; + pub async fn on_remove_device(&mut self, device_id: String) -> Result<(), String>; + } + } + + pub struct MockAdapter { + adapter_name: String, + pub adapter_helper: MockAdapterHelper, + } + + impl MockAdapter { + pub fn new(adapter_name: String) -> Self { + Self { + adapter_name, + adapter_helper: MockAdapterHelper::new(), + } + } + } + + impl std::ops::Deref for MockAdapter { + type Target = MockAdapterHelper; + fn deref(&self) -> &Self::Target { + &self.adapter_helper + } + } + + impl std::ops::DerefMut for MockAdapter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.adapter_helper + } + } + + impl AdapterStructure for MockAdapter { + fn id(&self) -> String { + self.adapter_name.to_owned() + } + + fn name(&self) -> String { + self.adapter_name.to_owned() + } + } + + impl AdapterBuilder for MockAdapter { + type BuiltAdapter = BuiltMockAdapter; + fn build(data: Self, adapter_handle: AdapterHandle) -> Self::BuiltAdapter { + BuiltMockAdapter::new(data, adapter_handle) + } + } +} diff --git a/src/adapter/adapter_handle.rs b/src/adapter/adapter_handle.rs index 3931235..6899b0f 100644 --- a/src/adapter/adapter_handle.rs +++ b/src/adapter/adapter_handle.rs @@ -4,7 +4,9 @@ * 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::DeviceBuilder, error::WebthingsError, Adapter, Device, DeviceHandle, +}; use std::{ collections::HashMap, sync::{Arc, Weak}, @@ -38,16 +40,12 @@ impl AdapterHandle { } } - /// Build and add a new device using the given [device builder][crate::DeviceBuilder]. - pub async fn add_device( + /// Build and add a new device using the given data struct. + pub async fn add_device( &mut self, - device_builder: B, - ) -> Result>>, WebthingsError> - where - D: Device, - B: DeviceBuilder, - { - let device_description = device_builder.full_description()?; + device: D, + ) -> Result>>, WebthingsError> { + let device_description = device.full_description()?; let message: Message = DeviceAddedNotificationMessageData { plugin_id: self.plugin_id.clone(), @@ -65,16 +63,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>> = - 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); { @@ -83,15 +81,15 @@ impl AdapterHandle { device_handle.weak = device_weak; for property_builder in properties { - device_handle.add_property(property_builder); + device_handle.add_property(property_builder).await; } for action in actions { - device_handle.add_action(action); + device_handle.add_action(action).await; } - for event in events { - device_handle.add_event(event); + for event_builder in events { + device_handle.add_event(event_builder).await; } } @@ -145,7 +143,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; @@ -156,8 +156,8 @@ pub(crate) mod tests { adapter: &mut AdapterHandle, device_id: &str, ) -> Arc>> { - 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(); @@ -178,7 +178,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"; diff --git a/src/adapter/adapter_macro.rs b/src/adapter/adapter_macro.rs new file mode 100644 index 0000000..5ec1ff6 --- /dev/null +++ b/src/adapter/adapter_macro.rs @@ -0,0 +1,88 @@ +/// 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, +/// } +/// +/// impl AdapterStructure for ExampleAdapter { +/// // ... +/// # fn id(&self) -> String { +/// # "example-adapter".to_owned() +/// # } +/// # fn name(&self) -> String { +/// # "Example Adapter".to_owned() +/// # } +/// } +/// +/// #[async_trait] +/// impl Adapter for BuiltExampleAdapter { +/// // ... +/// } +/// ``` +/// will expand to +/// ``` +/// # use gateway_addon_rust::{prelude::*, adapter::{BuiltAdapter, AdapterBuilder}}; +/// # use std::ops::{Deref, DerefMut}; +/// # use async_trait::async_trait; +/// struct ExampleAdapter { +/// foo: i32, +/// } +/// +/// impl AdapterStructure for ExampleAdapter { +/// // ... +/// # fn id(&self) -> String { +/// # "example-adapter".to_owned() +/// # } +/// # fn name(&self) -> String { +/// # "Example Adapter".to_owned() +/// # } +/// } +/// +/// struct BuiltExampleAdapter { +/// data: ExampleAdapter, +/// adapter_handle: AdapterHandle, +/// } +/// +/// impl BuiltAdapter for BuiltExampleAdapter { +/// fn adapter_handle(&self) -> &AdapterHandle { +/// &self.adapter_handle +/// } +/// fn adapter_handle_mut(&mut self) -> &mut AdapterHandle { +/// &mut self.adapter_handle +/// } +/// } +/// +/// impl AdapterBuilder 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 { +/// // ... +/// } +/// ``` +pub use gateway_addon_rust_codegen::adapter; diff --git a/src/adapter/adapter_message_handler.rs b/src/adapter/adapter_message_handler.rs index efd77e5..efa67bd 100644 --- a/src/adapter/adapter_message_handler.rs +++ b/src/adapter/adapter_message_handler.rs @@ -28,7 +28,7 @@ impl MessageHandler for dyn Adapter { .await .map_err(|err| format!("Could not unload adapter: {}", err))?; - self.adapter_handle_mut() + self.adapter_handle() .unload() .await .map_err(|err| format!("Could not send unload response: {}", err))?; @@ -72,7 +72,7 @@ impl MessageHandler for dyn Adapter { data: DeviceRemoveActionRequestMessageData { device_id, .. }, .. }) => { - self.adapter_handle_mut() + self.adapter_handle() .get_device(device_id) .ok_or_else(|| format!("Unknown device: {}", device_id))? .lock() @@ -90,7 +90,7 @@ impl MessageHandler for dyn Adapter { #[cfg(test)] mod tests { use crate::{ - adapter::tests::{add_mock_device, MockAdapter}, + adapter::tests::{add_mock_device, BuiltMockAdapter}, message_handler::MessageHandler, plugin::tests::{add_mock_adapter, plugin}, Plugin, @@ -138,9 +138,8 @@ mod tests { { let mut adapter = adapter.lock().await; - let adapter = adapter.downcast_mut::().unwrap(); + let adapter = adapter.downcast_mut::().unwrap(); adapter - .adapter_helper .expect_on_remove_device() .withf(move |device_id| device_id == DEVICE_ID) .times(1) @@ -152,7 +151,7 @@ mod tests { assert!(adapter .lock() .await - .adapter_handle_mut() + .adapter_handle() .get_device(DEVICE_ID) .is_none()) } @@ -172,9 +171,8 @@ mod tests { adapter .lock() .await - .downcast_mut::() + .downcast_mut::() .unwrap() - .adapter_helper .expect_on_unload() .times(1) .returning(|| Ok(())); @@ -211,9 +209,8 @@ mod tests { { let mut adapter = adapter.lock().await; - let adapter = adapter.downcast_mut::().unwrap(); + let adapter = adapter.downcast_mut::().unwrap(); adapter - .adapter_helper .expect_on_start_pairing() .withf(move |t| t.as_secs() == timeout as u64) .times(1) @@ -236,9 +233,8 @@ mod tests { { let mut adapter = adapter.lock().await; - let adapter = adapter.downcast_mut::().unwrap(); + let adapter = adapter.downcast_mut::().unwrap(); adapter - .adapter_helper .expect_on_cancel_pairing() .times(1) .returning(|| Ok(())); @@ -275,9 +271,8 @@ mod tests { { let mut adapter = adapter.lock().await; - let adapter = adapter.downcast_mut::().unwrap(); + let adapter = adapter.downcast_mut::().unwrap(); adapter - .adapter_helper .expect_on_device_saved() .withf(move |id, description| id == DEVICE_ID && description == &device_description) .times(1) diff --git a/src/adapter/adapter_trait.rs b/src/adapter/adapter_trait.rs index ff194b8..c5488da 100644 --- a/src/adapter/adapter_trait.rs +++ b/src/adapter/adapter_trait.rs @@ -4,8 +4,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/.* */ -//! A module for everything related to WebthingsIO adapters. - use crate::AdapterHandle; use as_any::{AsAny, Downcast}; use async_trait::async_trait; @@ -14,36 +12,44 @@ use webthings_gateway_ipc_types::DeviceWithoutId; /// A trait used to specify the behaviour of a WebthingsIO adapter. /// -/// Wraps an [adapter handle][AdapterHandle] and defines how to react on gateway requests. Created through a [plugin][crate::Plugin]. +/// Defines how to react on gateway requests. /// /// # Examples /// ```no_run -/// # use gateway_addon_rust::{prelude::*, plugin::connect, example::ExampleDeviceBuilder, error::WebthingsError}; +/// # use gateway_addon_rust::{prelude::*, plugin::connect, example::ExampleDevice, error::WebthingsError, adapter::BuiltAdapter}; /// # use webthings_gateway_ipc_types::DeviceWithoutId; /// # use async_trait::async_trait; /// # use as_any::Downcast; -/// struct ExampleAdapter(AdapterHandle); +/// #[adapter] +/// struct ExampleAdapter { foo: i32 } /// -/// #[async_trait] -/// impl Adapter for ExampleAdapter { -/// fn adapter_handle_mut(&mut self) -> &mut AdapterHandle { -/// &mut self.0 -/// } +/// impl AdapterStructure for ExampleAdapter { +/// // ... +/// # fn id(&self) -> String { +/// # "example-adapter".to_owned() +/// # } +/// # fn name(&self) -> String { +/// # "Example Adapter".to_owned() +/// # } +/// } /// -/// async fn on_remove_device(&mut self, device_id: String) -> Result<(), String> { -/// log::debug!("Device {} removed", device_id); +/// #[async_trait] +/// impl Adapter for BuiltExampleAdapter { +/// async fn on_unload(&mut self) -> Result<(), String> { +/// println!("Foo: {}", self.foo); /// Ok(()) /// } /// } /// -/// impl ExampleAdapter { -/// # pub fn new(adapter_handle: AdapterHandle) -> Self { -/// # Self(adapter_handle) -/// # } -/// +/// # impl ExampleAdapter { +/// # pub fn new(foo: i32) -> Self { +/// # Self { foo } +/// # } +/// # } +/// impl BuiltExampleAdapter { /// pub async fn init(&mut self) -> Result<(), WebthingsError> { /// self.adapter_handle_mut() -/// .add_device(ExampleDeviceBuilder::new()) +/// .add_device(ExampleDevice::new()) /// .await?; /// Ok(()) /// } @@ -53,12 +59,12 @@ use webthings_gateway_ipc_types::DeviceWithoutId; /// pub async fn main() -> Result<(), WebthingsError> { /// let mut plugin = connect("example-addon").await?; /// let adapter = plugin -/// .create_adapter("example-adapter", "Example Adapter", ExampleAdapter::new) +/// .add_adapter(ExampleAdapter::new(42)) /// .await?; /// adapter /// .lock() /// .await -/// .downcast_mut::() +/// .downcast_mut::() /// .unwrap() /// .init() /// .await?; @@ -67,10 +73,7 @@ use webthings_gateway_ipc_types::DeviceWithoutId; /// } /// ``` #[async_trait] -pub trait Adapter: Send + Sync + AsAny + 'static { - /// Return the wrapped [adapter handle][AdapterHandle]. - fn adapter_handle_mut(&mut self) -> &mut AdapterHandle; - +pub trait Adapter: BuiltAdapter + Send + Sync + AsAny + 'static { /// Called when this Adapter should be unloaded. async fn on_unload(&mut self) -> Result<(), String> { Ok(()) @@ -111,48 +114,84 @@ pub trait Adapter: Send + Sync + AsAny + 'static { impl Downcast for dyn Adapter {} +/// A trait used to wrap an [adapter handle][AdapterHandle]. +/// +/// When you use the [adapter][macro@crate::adapter] macro, this will be implemented automatically. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, adapter::BuiltAdapter}; +/// # use async_trait::async_trait; +/// struct BuiltExampleAdapter { +/// adapter_handle: AdapterHandle, +/// } +/// +/// impl BuiltAdapter for BuiltExampleAdapter { +/// fn adapter_handle(&self) -> &AdapterHandle { +/// &self.adapter_handle +/// } +/// fn adapter_handle_mut(&mut self) -> &mut AdapterHandle { +/// &mut self.adapter_handle +/// } +/// } +/// ``` +pub trait BuiltAdapter { + /// Return a reference to the wrapped [adapter handle][AdapterHandle]. + fn adapter_handle(&self) -> &AdapterHandle; + + /// Return a mutable reference to the wrapped [adapter handle][AdapterHandle]. + fn adapter_handle_mut(&mut self) -> &mut AdapterHandle; +} + #[cfg(test)] pub(crate) mod tests { - use crate::{Adapter, AdapterHandle}; + use crate::{ + adapter::{tests::MockAdapter, BuiltAdapter}, + Adapter, AdapterHandle, + }; use async_trait::async_trait; - use mockall::mock; use std::time::Duration; use webthings_gateway_ipc_types::DeviceWithoutId; - mock! { - pub AdapterHelper { - pub async fn on_unload(&mut self) -> Result<(), String>; - pub async fn on_start_pairing(&mut self, timeout: Duration) -> Result<(), String>; - pub async fn on_cancel_pairing(&mut self) -> Result<(), String>; - pub async fn on_device_saved( - &mut self, - device_id: String, - device_description: DeviceWithoutId - ) -> Result<(), String>; - pub async fn on_remove_device(&mut self, device_id: String) -> Result<(), String>; - } - } - - pub struct MockAdapter { + pub struct BuiltMockAdapter { + data: MockAdapter, adapter_handle: AdapterHandle, - pub adapter_helper: MockAdapterHelper, } - impl MockAdapter { - pub fn new(adapter_handle: AdapterHandle) -> Self { + impl BuiltMockAdapter { + pub fn new(data: MockAdapter, adapter_handle: AdapterHandle) -> Self { Self { + data, adapter_handle, - adapter_helper: MockAdapterHelper::new(), } } } - #[async_trait] - impl Adapter for MockAdapter { + impl BuiltAdapter for BuiltMockAdapter { + fn adapter_handle(&self) -> &AdapterHandle { + &self.adapter_handle + } + fn adapter_handle_mut(&mut self) -> &mut AdapterHandle { &mut self.adapter_handle } + } + + impl std::ops::Deref for BuiltMockAdapter { + type Target = MockAdapter; + fn deref(&self) -> &Self::Target { + &self.data + } + } + impl std::ops::DerefMut for BuiltMockAdapter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } + } + + #[async_trait] + impl Adapter for BuiltMockAdapter { async fn on_unload(&mut self) -> Result<(), String> { self.adapter_helper.on_unload().await } diff --git a/src/adapter/mod.rs b/src/adapter/mod.rs index cca95d9..ba10e74 100644 --- a/src/adapter/mod.rs +++ b/src/adapter/mod.rs @@ -6,14 +6,18 @@ //! A module for everything related to WebthingsIO adapters. +mod adapter_builder; mod adapter_handle; +mod adapter_macro; pub(crate) mod adapter_message_handler; mod adapter_trait; +pub use adapter_builder::*; pub use adapter_handle::*; +pub use adapter_macro::*; pub use adapter_trait::*; #[cfg(test)] pub(crate) mod tests { - pub use super::{adapter_handle::tests::*, adapter_trait::tests::*}; + pub use super::{adapter_builder::tests::*, adapter_handle::tests::*, adapter_trait::tests::*}; } diff --git a/src/api_handler/api_handler_handle.rs b/src/api_handler/api_handler_handle.rs new file mode 100644 index 0000000..6fa4507 --- /dev/null +++ b/src/api_handler/api_handler_handle.rs @@ -0,0 +1,73 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/.* + */ + +use crate::{client::Client, error::WebthingsError}; +use std::sync::Arc; +use tokio::sync::Mutex; +use webthings_gateway_ipc_types::ApiHandlerUnloadResponseMessageData; + +/// A struct which represents an instance of a WebthingsIO API Handler. +/// +/// Use it to notify the gateway. +#[derive(Clone)] +pub struct ApiHandlerHandle { + pub(crate) client: Arc>, + pub plugin_id: String, +} + +impl ApiHandlerHandle { + pub(crate) fn new(client: Arc>, plugin_id: String) -> Self { + Self { client, plugin_id } + } + + /// Unload this API Handler. + pub async fn unload(&self) -> Result<(), WebthingsError> { + let message = ApiHandlerUnloadResponseMessageData { + plugin_id: self.plugin_id.clone(), + package_name: self.plugin_id.clone(), + } + .into(); + + self.client.lock().await.send_message(&message).await + } +} + +#[cfg(test)] +pub(crate) mod tests { + use crate::{api_handler::ApiHandlerHandle, client::Client}; + use rstest::{fixture, rstest}; + use std::sync::Arc; + use tokio::sync::Mutex; + use webthings_gateway_ipc_types::Message; + + const PLUGIN_ID: &str = "plugin_id"; + + #[fixture] + fn api_handler() -> ApiHandlerHandle { + let client = Arc::new(Mutex::new(Client::new())); + ApiHandlerHandle::new(client, PLUGIN_ID.to_owned()) + } + + #[rstest] + #[tokio::test] + async fn test_unload(api_handler: ApiHandlerHandle) { + api_handler + .client + .lock() + .await + .expect_send_message() + .withf(move |msg| match msg { + Message::ApiHandlerUnloadResponse(msg) => { + msg.data.plugin_id == PLUGIN_ID && msg.data.package_name == PLUGIN_ID + } + _ => false, + }) + .times(1) + .returning(|_| Ok(())); + + api_handler.unload().await.unwrap(); + } +} diff --git a/src/api_handler/api_handler_macro.rs b/src/api_handler/api_handler_macro.rs new file mode 100644 index 0000000..d0255c1 --- /dev/null +++ b/src/api_handler/api_handler_macro.rs @@ -0,0 +1,74 @@ +/// Use this on a struct to generate a built API Handler around it, including useful impls. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, api_handler::{api_handler, BuiltApiHandler, ApiHandler, ApiHandlerBuilder, ApiHandlerHandle, ApiRequest, ApiResponse}}; +/// # use async_trait::async_trait; +/// #[api_handler] +/// struct ExampleApiHandler { +/// foo: i32, +/// } +/// +/// #[async_trait] +/// impl ApiHandler for BuiltExampleApiHandler { +/// // ... +/// # async fn handle_request(&mut self, _: ApiRequest) -> Result { +/// # Err("".to_owned()) +/// # } +/// } +/// ``` +/// will expand to +/// ``` +/// # use gateway_addon_rust::{prelude::*, api_handler::{BuiltApiHandler, ApiHandlerBuilder, ApiHandler, ApiHandlerHandle, ApiRequest, ApiResponse}}; +/// # use std::ops::{Deref, DerefMut}; +/// # use async_trait::async_trait; +/// struct ExampleApiHandler { +/// foo: i32, +/// } +/// +/// struct BuiltExampleApiHandler { +/// data: ExampleApiHandler, +/// api_handler_handle: ApiHandlerHandle, +/// } +/// +/// impl BuiltApiHandler for BuiltExampleApiHandler { +/// fn api_handler_handle(&self) -> &ApiHandlerHandle { +/// &self.api_handler_handle +/// } +/// fn api_handler_handle_mut(&mut self) -> &mut ApiHandlerHandle { +/// &mut self.api_handler_handle +/// } +/// } +/// +/// impl ApiHandlerBuilder for ExampleApiHandler { +/// type BuiltApiHandler = BuiltExampleApiHandler; +/// fn build(data: Self, api_handler_handle: ApiHandlerHandle) -> Self::BuiltApiHandler { +/// BuiltExampleApiHandler { +/// data, +/// api_handler_handle, +/// } +/// } +/// } +/// +/// impl Deref for BuiltExampleApiHandler { +/// type Target = ExampleApiHandler; +/// fn deref(&self) -> &Self::Target { +/// &self.data +/// } +/// } +/// +/// impl DerefMut for BuiltExampleApiHandler { +/// fn deref_mut(&mut self) -> &mut Self::Target { +/// &mut self.data +/// } +/// } +/// +/// #[async_trait] +/// impl ApiHandler for BuiltExampleApiHandler { +/// // ... +/// # async fn handle_request(&mut self, _: ApiRequest) -> Result { +/// # Err("".to_owned()) +/// # } +/// } +/// ``` +pub use gateway_addon_rust_codegen::api_handler; diff --git a/src/api_handler/api_handler_message_handler.rs b/src/api_handler/api_handler_message_handler.rs index 5d46e23..9fdc07e 100644 --- a/src/api_handler/api_handler_message_handler.rs +++ b/src/api_handler/api_handler_message_handler.rs @@ -6,47 +6,32 @@ use crate::{ api_handler::{ApiHandler, ApiResponse}, - client::Client, message_handler::{MessageHandler, MessageResult}, }; use async_trait::async_trait; use serde_json::json; -use std::sync::Arc; -use tokio::sync::Mutex; use webthings_gateway_ipc_types::{ - ApiHandlerApiRequest, ApiHandlerApiResponseMessageData, ApiHandlerUnloadRequest, - ApiHandlerUnloadResponseMessageData, Message as IPCMessage, + ApiHandlerApiRequest, ApiHandlerApiResponseMessageData, Message as IPCMessage, }; #[async_trait] -impl MessageHandler for (Arc>, Arc>) { +impl MessageHandler for dyn ApiHandler { async fn handle_message(&mut self, message: IPCMessage) -> Result { - let (api_handler, client) = self; - let mut api_handler = api_handler.lock().await; - let mut client = client.lock().await; - match message { - IPCMessage::ApiHandlerUnloadRequest(ApiHandlerUnloadRequest { data, .. }) => { + IPCMessage::ApiHandlerUnloadRequest(_) => { log::info!("Received request to unload api handler"); - api_handler - .on_unload() + self.on_unload() .await .map_err(|err| format!("Could not unload api handler: {}", err))?; - let message = ApiHandlerUnloadResponseMessageData { - plugin_id: data.plugin_id.clone(), - package_name: data.plugin_id.clone(), - } - .into(); - - client - .send_message(&message) + self.api_handler_handle() + .unload() .await .map_err(|err| format!("Could not send unload response: {}", err))?; } IPCMessage::ApiHandlerApiRequest(ApiHandlerApiRequest { data, .. }) => { - let result = api_handler.handle_request(data.request).await; + let result = self.handle_request(data.request).await; let response = result.clone().unwrap_or_else(|err| ApiResponse { content: serde_json::Value::String(err), @@ -61,7 +46,10 @@ impl MessageHandler for (Arc>, Arc>) { } .into(); - client + self.api_handler_handle() + .client + .lock() + .await .send_message(&message) .await .map_err(|err| format!("{:?}", err))?; @@ -78,7 +66,7 @@ impl MessageHandler for (Arc>, Arc>) { #[cfg(test)] mod tests { use crate::{ - api_handler::{tests::MockApiHandler, ApiRequest, ApiResponse}, + api_handler::{api_handler_trait::tests::BuiltMockApiHandler, ApiRequest, ApiResponse}, message_handler::MessageHandler, plugin::tests::{plugin, set_mock_api_handler}, Plugin, @@ -108,13 +96,11 @@ mod tests { .api_handler .lock() .await - .downcast_mut::() + .downcast_mut::() .unwrap() - .api_handler_helper .expect_on_unload() .times(1) .returning(|| Ok(())); - plugin .client .lock() @@ -126,7 +112,6 @@ mod tests { }) .times(1) .returning(|_| Ok(())); - plugin.handle_message(message).await.unwrap(); } @@ -161,9 +146,8 @@ mod tests { .api_handler .lock() .await - .downcast_mut::() + .downcast_mut::() .unwrap() - .api_handler_helper .expect_handle_request() .withf(move |req| req.method == request.method && req.path == request.path) .times(1) diff --git a/src/api_handler/api_handler_trait.rs b/src/api_handler/api_handler_trait.rs index 8ab5546..57c8dd2 100644 --- a/src/api_handler/api_handler_trait.rs +++ b/src/api_handler/api_handler_trait.rs @@ -4,7 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/.* */ -use crate::api_handler::{ApiRequest, ApiResponse}; +use crate::api_handler::{ApiHandlerHandle, ApiRequest, ApiResponse}; use as_any::{AsAny, Downcast}; use async_trait::async_trait; @@ -15,19 +15,22 @@ use async_trait::async_trait; /// # Examples /// ```no_run /// # use gateway_addon_rust::{ -/// # prelude::*, plugin::connect, example::ExampleDeviceBuilder, -/// # api_handler::{ApiHandler, ApiRequest, ApiResponse}, error::WebthingsError +/// # prelude::*, plugin::connect, +/// # api_handler::{api_handler, ApiHandler, ApiRequest, ApiResponse}, error::WebthingsError /// # }; /// # use async_trait::async_trait; /// # use serde_json::json; -/// struct ExampleApiHandler(); +/// #[api_handler] +/// struct ExampleApiHandler { +/// foo: i32, +/// } /// /// #[async_trait] -/// impl ApiHandler for ExampleApiHandler { +/// impl ApiHandler for BuiltExampleApiHandler { /// async fn handle_request(&mut self, request: ApiRequest) -> Result { /// match request.path.as_ref() { /// "/example-route" => Ok(ApiResponse { -/// content: json!("foo"), +/// content: serde_json::to_value(self.foo).unwrap(), /// content_type: json!("text/plain"), /// status: 200, /// }), @@ -38,7 +41,9 @@ use async_trait::async_trait; /// /// # impl ExampleApiHandler { /// # pub fn new() -> Self { -/// # Self() +/// # Self { +/// # foo: 42, +/// # } /// # } /// # } /// # @@ -51,7 +56,7 @@ use async_trait::async_trait; /// } /// ``` #[async_trait] -pub trait ApiHandler: Send + Sync + AsAny + 'static { +pub trait ApiHandler: BuiltApiHandler + Send + Sync + AsAny + 'static { /// Called when this API Handler should be unloaded. async fn on_unload(&mut self) -> Result<(), String> { Ok(()) @@ -63,16 +68,112 @@ pub trait ApiHandler: Send + Sync + AsAny + 'static { impl Downcast for dyn ApiHandler {} +/// A trait used to wrap an [API handler handle][ApiHandlerHandle]. +/// +/// When you use the [api_handler][macro@crate::api_handler::api_handler] macro, this will be implemented automatically. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, api_handler::{BuiltApiHandler, ApiHandlerHandle}}; +/// # use async_trait::async_trait; +/// struct BuiltExampleApiHandler { +/// api_handler_handle: ApiHandlerHandle, +/// } +/// +/// impl BuiltApiHandler for BuiltExampleApiHandler { +/// fn api_handler_handle(&self) -> &ApiHandlerHandle { +/// &self.api_handler_handle +/// } +/// fn api_handler_handle_mut(&mut self) -> &mut ApiHandlerHandle { +/// &mut self.api_handler_handle +/// } +/// } +/// ``` +pub trait BuiltApiHandler { + /// Return a reference to the wrapped [API Handler handle][ApiHandlerHandle]. + fn api_handler_handle(&self) -> &ApiHandlerHandle; + + /// Return a mutable reference to the wrapped [API Handler handle][ApiHandlerHandle]. + fn api_handler_handle_mut(&mut self) -> &mut ApiHandlerHandle; +} + +/// A trait used to build an [API Handler][ApiHandler] around a data struct and an [API Handler handle][ApiHandlerHandle]. +/// +/// When you use the [api_handler][macro@crate::api_handler::api_handler] macro, this will be implemented automatically. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, api_handler::{BuiltApiHandler, ApiHandlerBuilder, ApiHandler, ApiHandlerHandle, ApiRequest, ApiResponse}}; +/// # use async_trait::async_trait; +/// struct ExampleApiHandler { +/// foo: i32, +/// } +/// +/// struct BuiltExampleApiHandler { +/// data: ExampleApiHandler, +/// api_handler_handle: ApiHandlerHandle, +/// } +/// +/// impl BuiltApiHandler for BuiltExampleApiHandler { +/// // ... +/// # fn api_handler_handle(&self) -> &ApiHandlerHandle { +/// # &self.api_handler_handle +/// # } +/// # fn api_handler_handle_mut(&mut self) -> &mut ApiHandlerHandle { +/// # &mut self.api_handler_handle +/// # } +/// } +/// +/// #[async_trait] +/// impl ApiHandler for BuiltExampleApiHandler { +/// // ... +/// # async fn handle_request(&mut self, _: ApiRequest) -> Result { +/// # Err("".to_owned()) +/// # } +/// } +/// +/// impl ApiHandlerBuilder for ExampleApiHandler { +/// type BuiltApiHandler = BuiltExampleApiHandler; +/// fn build(data: Self, api_handler_handle: ApiHandlerHandle) -> Self::BuiltApiHandler { +/// BuiltExampleApiHandler { +/// data, +/// api_handler_handle, +/// } +/// } +/// } +/// ``` +pub trait ApiHandlerBuilder { + /// Type of [ApiHandler] to build. + type BuiltApiHandler: ApiHandler; + + /// Build the [API Handler][ApiHandler] from a data struct and an [API Handler handle][ApiHandlerHandle]. + fn build(data: Self, api_handler_handle: ApiHandlerHandle) -> Self::BuiltApiHandler; +} + pub(crate) struct NoopApiHandler; +pub(crate) struct BuiltNoopApiHandler { + api_handler_handle: ApiHandlerHandle, +} -impl NoopApiHandler { - pub fn new() -> Self { - Self +impl ApiHandlerBuilder for NoopApiHandler { + type BuiltApiHandler = BuiltNoopApiHandler; + fn build(_data: Self, api_handler_handle: ApiHandlerHandle) -> Self::BuiltApiHandler { + BuiltNoopApiHandler { api_handler_handle } + } +} + +impl BuiltApiHandler for BuiltNoopApiHandler { + fn api_handler_handle(&self) -> &ApiHandlerHandle { + &self.api_handler_handle + } + + fn api_handler_handle_mut(&mut self) -> &mut ApiHandlerHandle { + &mut self.api_handler_handle } } #[async_trait] -impl ApiHandler for NoopApiHandler { +impl ApiHandler for BuiltNoopApiHandler { async fn handle_request(&mut self, _request: ApiRequest) -> Result { Err("No Api Handler registered".to_owned()) } @@ -80,37 +181,65 @@ impl ApiHandler for NoopApiHandler { #[cfg(test)] pub(crate) mod tests { - use crate::api_handler::{ApiHandler, ApiRequest, ApiResponse}; + use crate::api_handler::{ + ApiHandler, ApiHandlerBuilder, ApiHandlerHandle, ApiRequest, ApiResponse, BuiltApiHandler, + }; use async_trait::async_trait; use mockall::mock; mock! { - pub ApiHandlerHelper { + pub ApiHandler{ pub async fn on_unload(&mut self) -> Result<(), String>; pub async fn handle_request(&mut self, request: ApiRequest) -> Result; } } - pub struct MockApiHandler { - pub api_handler_helper: MockApiHandlerHelper, + pub struct BuiltMockApiHandler { + data: MockApiHandler, + api_handler_handle: ApiHandlerHandle, } - impl MockApiHandler { - pub fn new() -> Self { - Self { - api_handler_helper: MockApiHandlerHelper::default(), + impl ApiHandlerBuilder for MockApiHandler { + type BuiltApiHandler = BuiltMockApiHandler; + fn build(data: Self, api_handler_handle: ApiHandlerHandle) -> Self::BuiltApiHandler { + BuiltMockApiHandler { + data, + api_handler_handle, } } } + impl BuiltApiHandler for BuiltMockApiHandler { + fn api_handler_handle(&self) -> &ApiHandlerHandle { + &self.api_handler_handle + } + + fn api_handler_handle_mut(&mut self) -> &mut ApiHandlerHandle { + &mut self.api_handler_handle + } + } + + impl std::ops::Deref for BuiltMockApiHandler { + type Target = MockApiHandler; + fn deref(&self) -> &Self::Target { + &self.data + } + } + + impl std::ops::DerefMut for BuiltMockApiHandler { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } + } + #[async_trait] - impl ApiHandler for MockApiHandler { + impl ApiHandler for BuiltMockApiHandler { async fn on_unload(&mut self) -> Result<(), String> { - self.api_handler_helper.on_unload().await + self.data.on_unload().await } async fn handle_request(&mut self, request: ApiRequest) -> Result { - self.api_handler_helper.handle_request(request).await + self.data.handle_request(request).await } } } diff --git a/src/api_handler/mod.rs b/src/api_handler/mod.rs index 3e7bf47..c99fbf3 100644 --- a/src/api_handler/mod.rs +++ b/src/api_handler/mod.rs @@ -6,9 +6,13 @@ //! A module for everything related to WebthingsIO API Handlers. +mod api_handler_handle; +mod api_handler_macro; pub(crate) mod api_handler_message_handler; mod api_handler_trait; +pub use api_handler_handle::*; +pub use api_handler_macro::*; pub use api_handler_trait::*; /// An [ApiHandler](crate::api_handler::ApiHandler) request. diff --git a/src/device/device_builder.rs b/src/device/device_builder.rs index 955f49b..441fde7 100644 --- a/src/device/device_builder.rs +++ b/src/device/device_builder.rs @@ -8,27 +8,18 @@ use crate::{ actions, error::WebthingsError, events, properties, Actions, Device, DeviceDescription, DeviceHandle, Events, Properties, }; - use std::collections::BTreeMap; - use webthings_gateway_ipc_types::Device as FullDeviceDescription; /// A trait used to specify the structure of a WoT device. /// -/// Builds a [Device] instance. Created through an [adapter][crate::Adapter]. -/// /// # Examples /// ``` /// # #[macro_use] -/// # extern crate gateway_addon_rust; -/// # use gateway_addon_rust::{prelude::*, example::{ExampleDevice, ExamplePropertyBuilder, ExampleEvent, ExampleAction}}; -/// # fn main() {} -/// // ... -/// pub struct ExampleDeviceBuilder(); -/// -/// impl DeviceBuilder for ExampleDeviceBuilder { -/// type Device = ExampleDevice; +/// # use gateway_addon_rust::{prelude::*, example::{ExampleProperty, ExampleEvent, ExampleAction}}; +/// pub struct ExampleDevice { foo: i32 } /// +/// impl DeviceStructure for ExampleDevice { /// fn id(&self) -> String { /// "example-device".to_owned() /// } @@ -38,7 +29,7 @@ use webthings_gateway_ipc_types::Device as FullDeviceDescription; /// } /// /// fn properties(&self) -> Properties { -/// properties![ExamplePropertyBuilder::new()] +/// properties![ExampleProperty::new()] /// } /// /// fn actions(&self) -> Actions { @@ -48,26 +39,19 @@ use webthings_gateway_ipc_types::Device as FullDeviceDescription; /// fn events(&self) -> Events { /// events![ExampleEvent::new()] /// } -/// -/// fn build(self, device_handle: DeviceHandle) -> Self::Device { -/// ExampleDevice::new(device_handle) -/// } /// } /// ``` -pub trait DeviceBuilder: Send + Sync + 'static { - /// Type of [device][Device] this builds. - type Device: Device; - +pub trait DeviceStructure: Send + Sync + 'static { /// ID of the device. fn id(&self) -> String; /// [WoT description][DeviceDescription] of the device. fn description(&self) -> DeviceDescription; - /// A list of [properties][crate::PropertyBuilder] this device should own. + /// A list of [properties][crate::property::PropertyBuilder] this device should own. /// /// Note that the desired list consists of boxed objects implementing [PropertyBuilderBase][crate::property::PropertyBuilderBase]. - /// You can use the convenienve macro [properties!][crate::properties] to create this list [PropertyBuilder][crate::PropertyBuilder]s. + /// You can use the convenienve macro [properties!][crate::properties] to create this list [PropertyBuilder][crate::property::PropertyBuilder]s. fn properties(&self) -> Properties { properties![] } @@ -80,17 +64,14 @@ pub trait DeviceBuilder: Send + Sync + 'static { actions![] } - /// A list of [events][crate::Event] this device should own. + /// A list of [events][crate::event::EventBuilder] this device should own. /// - /// Note that the desired list consists of boxed objects implementing [EventBase][crate::event::EventBase]. - /// You can use the convenienve macro [events!][crate::events] to create this list from [Event][crate::Event]s. + /// Note that the desired list consists of boxed objects implementing [EventBuilderBase][crate::event::EventBuilderBase]. + /// You can use the convenienve macro [events!][crate::events] to create this list from [EventBuilder][crate::event::EventBuilder]s. fn events(&self) -> Events { events![] } - /// Build a new instance of this device using the given [device handle][DeviceHandle]. - fn build(self, device_handle: DeviceHandle) -> Self::Device; - #[doc(hidden)] fn full_description(&self) -> Result { let mut property_descriptions = BTreeMap::new(); @@ -120,31 +101,102 @@ pub trait DeviceBuilder: Send + Sync + 'static { } } +/// A trait used to build a [Device] around a data struct and a [device handle][DeviceHandle]. +/// +/// When you use the [device][macro@crate::device] macro, this will be implemented automatically. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, device::{BuiltDevice, DeviceBuilder}}; +/// # use async_trait::async_trait; +/// struct ExampleDevice { +/// foo: i32, +/// } +/// +/// struct BuiltExampleDevice { +/// data: ExampleDevice, +/// device_handle: DeviceHandle, +/// } +/// +/// impl BuiltDevice for BuiltExampleDevice { +/// // ... +/// # fn device_handle(&self) -> &DeviceHandle { +/// # &self.device_handle +/// # } +/// # fn device_handle_mut(&mut self) -> &mut DeviceHandle { +/// # &mut self.device_handle +/// # } +/// } +/// +/// #[async_trait] +/// impl Device for BuiltExampleDevice {} +/// +/// impl DeviceStructure for ExampleDevice { +/// /// ... +/// # fn id(&self) -> String { +/// # "example-device".to_owned() +/// # } +/// # fn description(&self) -> DeviceDescription { +/// # DeviceDescription::default() +/// # } +/// } +/// +/// impl DeviceBuilder for ExampleDevice { +/// type BuiltDevice = BuiltExampleDevice; +/// fn build(data: Self, device_handle: DeviceHandle) -> Self::BuiltDevice { +/// BuiltExampleDevice { +/// data, +/// device_handle, +/// } +/// } +/// } +/// ``` +pub trait DeviceBuilder: DeviceStructure { + /// Type of [Device] to build. + type BuiltDevice: Device; + + /// Build the [device][Device] from a data struct and an [device handle][DeviceHandle]. + fn build(data: Self, device_handle: DeviceHandle) -> Self::BuiltDevice; +} + #[cfg(test)] pub(crate) mod tests { use crate::{ action::{tests::MockAction, NoInput}, actions, - device::tests::MockDevice, + device::{tests::BuiltMockDevice, DeviceBuilder}, event::{tests::MockEvent, NoData}, events, properties, - property::tests::MockPropertyBuilder, - Actions, DeviceBuilder, DeviceDescription, DeviceHandle, Events, Properties, + property::tests::MockProperty, + Actions, DeviceDescription, DeviceHandle, DeviceStructure, Events, Properties, }; - pub struct MockDeviceBuilder { + pub struct MockDevice { device_id: String, } - impl MockDeviceBuilder { + impl MockDevice { pub fn new(device_id: String) -> Self { Self { device_id } } - } - impl DeviceBuilder for MockDeviceBuilder { - type Device = MockDevice; + pub const PROPERTY_BOOL: &'static str = "property_bool"; + pub const PROPERTY_U8: &'static str = "property_u8"; + pub const PROPERTY_I32: &'static str = "property_i32"; + pub const PROPERTY_F32: &'static str = "property_f32"; + pub const PROPERTY_OPTI32: &'static str = "property_opti32"; + pub const PROPERTY_STRING: &'static str = "property_string"; + pub const ACTION_NOINPUT: &'static str = "action_noinput"; + pub const ACTION_BOOL: &'static str = "action_bool"; + pub const ACTION_U8: &'static str = "action_u8"; + pub const ACTION_I32: &'static str = "action_i32"; + pub const ACTION_F32: &'static str = "action_f32"; + pub const ACTION_OPTI32: &'static str = "action_opti32"; + pub const ACTION_STRING: &'static str = "action_string"; + pub const EVENT_NODATA: &'static str = "event_nodata"; + } + impl DeviceStructure for MockDevice { fn id(&self) -> String { self.device_id.clone() } @@ -155,12 +207,12 @@ pub(crate) mod tests { fn properties(&self) -> Properties { properties![ - MockPropertyBuilder::::new(MockDevice::PROPERTY_BOOL.to_owned()), - MockPropertyBuilder::::new(MockDevice::PROPERTY_U8.to_owned()), - MockPropertyBuilder::::new(MockDevice::PROPERTY_I32.to_owned()), - MockPropertyBuilder::::new(MockDevice::PROPERTY_F32.to_owned()), - MockPropertyBuilder::>::new(MockDevice::PROPERTY_OPTI32.to_owned()), - MockPropertyBuilder::::new(MockDevice::PROPERTY_STRING.to_owned()) + MockProperty::::new(MockDevice::PROPERTY_BOOL.to_owned()), + MockProperty::::new(MockDevice::PROPERTY_U8.to_owned()), + MockProperty::::new(MockDevice::PROPERTY_I32.to_owned()), + MockProperty::::new(MockDevice::PROPERTY_F32.to_owned()), + MockProperty::>::new(MockDevice::PROPERTY_OPTI32.to_owned()), + MockProperty::::new(MockDevice::PROPERTY_STRING.to_owned()) ] } @@ -181,9 +233,12 @@ pub(crate) mod tests { MockDevice::EVENT_NODATA.to_owned() )] } + } - fn build(self, device_handle: DeviceHandle) -> Self::Device { - MockDevice::new(device_handle) + impl DeviceBuilder for MockDevice { + type BuiltDevice = BuiltMockDevice; + fn build(data: Self, device_handle: DeviceHandle) -> Self::BuiltDevice { + BuiltMockDevice::new(data, device_handle) } } } diff --git a/src/device/device_description.rs b/src/device/device_description.rs index 7a900f9..8d1a5b8 100644 --- a/src/device/device_description.rs +++ b/src/device/device_description.rs @@ -12,7 +12,7 @@ use webthings_gateway_ipc_types::{ /// A struct which represents a WoT [device description][webthings_gateway_ipc_types::Device]. /// -/// This is used by [DeviceBuilder][crate::DeviceBuilder]. +/// This is used by [DeviceStructure][crate::DeviceStructure]. /// /// Use the provided builder methods instead of directly writing to the struct fields. /// @@ -89,12 +89,14 @@ impl DeviceDescription { } /// Set `@context`. + #[must_use] pub fn at_context(mut self, at_context: impl Into) -> Self { self.at_context = Some(at_context.into()); self } /// Set `@type`. + #[must_use] pub fn at_types(mut self, at_types: Vec) -> Self { self.at_type = Some(at_types); self @@ -111,6 +113,7 @@ impl DeviceDescription { /// .at_type(AtType::OnOffSwitch) /// # ; /// ``` + #[must_use] pub fn at_type(mut self, at_type: AtType) -> Self { match self.at_type { None => self.at_type = Some(vec![at_type]), @@ -120,24 +123,28 @@ impl DeviceDescription { } /// Set `baseHref`. + #[must_use] pub fn base_href(mut self, base_href: impl Into) -> Self { self.base_href = Some(base_href.into()); self } /// Set `credentialsRequired`. + #[must_use] pub fn credentials_required(mut self, credentials_required: bool) -> Self { self.credentials_required = Some(credentials_required); self } /// Set `description`. + #[must_use] pub fn description(mut self, description: impl Into) -> Self { self.description = Some(description.into()); self } /// Set `links`. + #[must_use] pub fn links(mut self, links: Vec) -> Self { self.links = Some(links); self @@ -163,6 +170,7 @@ impl DeviceDescription { /// }) /// # ; /// ``` + #[must_use] pub fn link(mut self, link: Link) -> Self { match self.links { None => self.links = Some(vec![link]), @@ -172,12 +180,14 @@ impl DeviceDescription { } /// Set `pin`. + #[must_use] pub fn pin(mut self, pin: DevicePin) -> Self { self.pin = Some(pin); self } /// Set `title`. + #[must_use] pub fn title(mut self, title: impl Into) -> Self { self.title = Some(title.into()); self diff --git a/src/device/device_handle.rs b/src/device/device_handle.rs index dbb6f1b..9b37dd8 100644 --- a/src/device/device_handle.rs +++ b/src/device/device_handle.rs @@ -8,7 +8,7 @@ use crate::{ action::ActionBase, client::Client, error::WebthingsError, - event::{EventBase, EventHandleBase}, + event::{EventBase, EventBuilderBase}, property::{PropertyBase, PropertyBuilderBase}, ActionHandle, Adapter, Device, DeviceDescription, }; @@ -36,7 +36,7 @@ pub struct DeviceHandle { pub connected: bool, properties: HashMap>>>, actions: HashMap>>>, - events: HashMap>>>, + events: HashMap>>>, } impl DeviceHandle { @@ -63,7 +63,7 @@ impl DeviceHandle { } } - pub(crate) fn add_property(&mut self, property_builder: Box) { + pub(crate) async fn add_property(&mut self, property_builder: Box) { let name = property_builder.name(); let property = Arc::new(Mutex::new(property_builder.build( @@ -74,7 +74,8 @@ impl DeviceHandle { self.device_id.clone(), ))); - self.properties.insert(name, property); + self.properties.insert(name, property.clone()); + property.lock().await.post_init(); } /// Get a reference to all the [properties][crate::Property] which this device owns. @@ -108,12 +109,13 @@ impl DeviceHandle { } } - pub(crate) fn add_action(&mut self, action: Box) { + pub(crate) async fn add_action(&mut self, action: Box) { let name = action.name(); let action = Arc::new(Mutex::new(action)); - self.actions.insert(name, action); + self.actions.insert(name, action.clone()); + action.lock().await.post_init(); } /// Get a reference to all the [actions][crate::action::Action] which this device owns. @@ -168,33 +170,29 @@ impl DeviceHandle { action.cancel(action_id).await } - pub(crate) fn add_event(&mut self, event: Box) { - let name = event.name(); + pub(crate) async fn add_event(&mut self, event_builder: Box) { + let name = event_builder.name(); - let event_handle = event.build_event_handle( + let event = Arc::new(Mutex::new(event_builder.build( self.client.clone(), self.weak.clone(), self.plugin_id.clone(), self.adapter_id.clone(), self.device_id.clone(), - name.clone(), - ); + ))); - let event = Arc::new(Mutex::new(event_handle)); + self.events.insert(name, event.clone()); - self.events.insert(name, event); + event.lock().await.post_init(); } /// Get a reference to all the [events][crate::event::Event] which this device owns. - pub fn events(&self) -> &HashMap>>> { + pub fn events(&self) -> &HashMap>>> { &self.events } /// Get an [event][crate::Event] which this device owns by ID. - pub fn get_event( - &self, - name: impl Into, - ) -> Option>>> { + pub fn get_event(&self, name: impl Into) -> Option>>> { self.events.get(&name.into()).cloned() } @@ -209,7 +207,7 @@ impl DeviceHandle { let name = name.into(); if let Some(event) = self.events.get(&name.clone()) { let event = event.lock().await; - event.raise(data).await?; + event.event_handle().raise(data).await?; Ok(()) } else { Err(WebthingsError::UnknownEvent(name)) @@ -238,7 +236,7 @@ pub(crate) mod tests { action::{tests::MockAction, NoInput}, client::Client, event::{tests::MockEvent, NoData}, - property::tests::MockPropertyBuilder, + property::tests::MockProperty, DeviceDescription, DeviceHandle, }; use rstest::{fixture, rstest}; @@ -269,10 +267,11 @@ pub(crate) mod tests { } #[rstest] - fn test_get_property(mut device: DeviceHandle) { - device.add_property(Box::new(MockPropertyBuilder::::new( - PROPERTY_NAME.to_owned(), - ))); + #[tokio::test] + async fn test_get_property(mut device: DeviceHandle) { + device + .add_property(Box::new(MockProperty::::new(PROPERTY_NAME.to_owned()))) + .await; assert!(device.get_property(PROPERTY_NAME).is_some()) } @@ -282,8 +281,11 @@ pub(crate) mod tests { } #[rstest] - fn test_get_action(mut device: DeviceHandle) { - device.add_action(Box::new(MockAction::::new(ACTION_NAME.to_owned()))); + #[tokio::test] + async fn test_get_action(mut device: DeviceHandle) { + device + .add_action(Box::new(MockAction::::new(ACTION_NAME.to_owned()))) + .await; assert!(device.get_action(ACTION_NAME).is_some()) } @@ -293,8 +295,11 @@ pub(crate) mod tests { } #[rstest] - fn test_get_event(mut device: DeviceHandle) { - device.add_event(Box::new(MockEvent::::new(EVENT_NAME.to_owned()))); + #[tokio::test] + async fn test_get_event(mut device: DeviceHandle) { + device + .add_event(Box::new(MockEvent::::new(EVENT_NAME.to_owned()))) + .await; assert!(device.get_event(EVENT_NAME).is_some()) } @@ -307,15 +312,16 @@ pub(crate) mod tests { #[tokio::test] async fn test_set_property_value(mut device: DeviceHandle) { let value = 42; - device.add_property(Box::new(MockPropertyBuilder::::new( - PROPERTY_NAME.to_owned(), - ))); + device + .add_property(Box::new(MockProperty::::new(PROPERTY_NAME.to_owned()))) + .await; device .client .lock() .await .expect_send_message() + .times(1) .returning(|_| Ok(())); assert!(device @@ -337,13 +343,16 @@ pub(crate) mod tests { #[rstest] #[tokio::test] async fn test_raise_event(mut device: DeviceHandle) { - device.add_event(Box::new(MockEvent::::new(EVENT_NAME.to_owned()))); + device + .add_event(Box::new(MockEvent::::new(EVENT_NAME.to_owned()))) + .await; device .client .lock() .await .expect_send_message() + .times(1) .returning(|_| Ok(())); assert!(device.raise_event(EVENT_NAME, None).await.is_ok()); @@ -380,4 +389,31 @@ pub(crate) mod tests { assert!(device.set_connected(connected).await.is_ok()); assert_eq!(device.connected, connected); } + + #[rstest] + #[tokio::test] + async fn test_event_post_init(mut device: DeviceHandle) { + let mut mock_event = MockEvent::::new(EVENT_NAME.to_owned()); + mock_event.expect_post_init = true; + mock_event.expect_post_init().times(1).returning(|| ()); + device.add_event(Box::new(mock_event)).await; + } + + #[rstest] + #[tokio::test] + async fn test_action_post_init(mut device: DeviceHandle) { + let mut mock_action = MockAction::::new(ACTION_NAME.to_owned()); + mock_action.expect_post_init = true; + mock_action.expect_post_init().times(1).returning(|| ()); + device.add_action(Box::new(mock_action)).await; + } + + #[rstest] + #[tokio::test] + async fn test_property_post_init(mut device: DeviceHandle) { + let mut mock_property = MockProperty::::new(PROPERTY_NAME.to_owned()); + mock_property.expect_post_init = true; + mock_property.expect_post_init().times(1).returning(|| ()); + device.add_property(Box::new(mock_property)).await; + } } diff --git a/src/device/device_macro.rs b/src/device/device_macro.rs new file mode 100644 index 0000000..c81543c --- /dev/null +++ b/src/device/device_macro.rs @@ -0,0 +1,88 @@ +/// Use this on a struct to generate a built device around it, including useful impls. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, device::DeviceBuilder}; +/// # use async_trait::async_trait; +/// #[device] +/// struct ExampleDevice { +/// 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::{BuiltDevice, DeviceBuilder}}; +/// # use std::ops::{Deref, DerefMut}; +/// # use async_trait::async_trait; +/// struct ExampleDevice { +/// foo: i32, +/// } +/// +/// struct BuiltExampleDevice { +/// data: ExampleDevice, +/// device_handle: DeviceHandle, +/// } +/// +/// impl BuiltDevice for BuiltExampleDevice { +/// fn device_handle(&self) -> &DeviceHandle { +/// &self.device_handle +/// } +/// fn device_handle_mut(&mut self) -> &mut DeviceHandle { +/// &mut self.device_handle +/// } +/// } +/// +/// impl DeviceBuilder 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 { +/// // ... +/// } +/// ``` +pub use gateway_addon_rust_codegen::device; diff --git a/src/device/device_message_handler.rs b/src/device/device_message_handler.rs index fff302d..f60cb80 100644 --- a/src/device/device_message_handler.rs +++ b/src/device/device_message_handler.rs @@ -20,7 +20,7 @@ impl MessageHandler for dyn Device { match message { IPCMessage::DeviceSetPropertyCommand(DeviceSetPropertyCommand { data, .. }) => { let property = self - .device_handle_mut() + .device_handle() .get_property(&data.property_name) .ok_or_else(|| { format!( @@ -45,7 +45,7 @@ impl MessageHandler for dyn Device { } IPCMessage::DeviceRequestActionRequest(DeviceRequestActionRequest { data, .. }) => { let result = self - .device_handle_mut() + .device_handle() .request_action( data.action_name.clone(), data.action_id.clone(), @@ -63,7 +63,7 @@ impl MessageHandler for dyn Device { } .into(); - self.device_handle_mut() + self.device_handle() .client .lock() .await @@ -95,7 +95,7 @@ impl MessageHandler for dyn Device { } .into(); - self.device_handle_mut() + self.device_handle() .client .lock() .await @@ -122,11 +122,11 @@ pub(crate) mod tests { action::{tests::MockAction, Input, NoInput}, adapter::tests::add_mock_device, device::tests::MockDevice, - event::NoData, + event::{tests::BuiltMockEvent, BuiltEvent, NoData}, message_handler::MessageHandler, plugin::tests::{add_mock_adapter, plugin}, - property::{self, tests::MockProperty}, - EventHandle, Plugin, PropertyHandle, + property::{self, tests::BuiltMockProperty}, + Plugin, PropertyHandle, }; use as_any::Downcast; use rstest::rstest; @@ -161,8 +161,8 @@ pub(crate) mod tests { let device = add_mock_device(adapter.lock().await.adapter_handle_mut(), DEVICE_ID).await; { - let mut device = device.lock().await; - let action = device.device_handle_mut().get_action(action_name).unwrap(); + let device = device.lock().await; + let action = device.device_handle().get_action(action_name).unwrap(); let mut action = action.lock().await; let action = action.as_any_mut().downcast_mut::>().unwrap(); action @@ -213,9 +213,9 @@ pub(crate) mod tests { let device = add_mock_device(adapter.lock().await.adapter_handle_mut(), DEVICE_ID).await; { - let mut device = device.lock().await; + let device = device.lock().await; let action = device - .device_handle_mut() + .device_handle() .get_action(action_name.to_owned()) .unwrap(); let mut action = action.lock().await; @@ -284,15 +284,11 @@ pub(crate) mod tests { { let expected_value = expected_value.clone(); - let mut device = device.lock().await; - let property = device - .device_handle_mut() - .get_property(property_name) - .unwrap(); + let device = device.lock().await; + let property = device.device_handle().get_property(property_name).unwrap(); let mut property = property.lock().await; - let property = property.downcast_mut::>().unwrap(); + let property = property.downcast_mut::>().unwrap(); property - .property_helper .expect_on_update() .withf(move |value| value == &expected_value) .times(1) @@ -340,7 +336,7 @@ pub(crate) mod tests { assert!(device .lock() .await - .device_handle_mut() + .device_handle() .adapter .upgrade() .is_some()) @@ -355,13 +351,13 @@ pub(crate) mod tests { let property = device .lock() .await - .device_handle_mut() + .device_handle() .get_property(MockDevice::PROPERTY_I32) .unwrap(); assert!(property .lock() .await - .property_handle_mut() + .property_handle() .downcast_ref::>() .unwrap() .device @@ -378,14 +374,15 @@ pub(crate) mod tests { let event = device .lock() .await - .device_handle_mut() + .device_handle() .get_event(MockDevice::EVENT_NODATA) .unwrap(); assert!(event .lock() .await - .downcast_ref::>() + .downcast_ref::>() .unwrap() + .event_handle() .device .upgrade() .is_some()) diff --git a/src/device/device_trait.rs b/src/device/device_trait.rs index ae9b23d..a4fe223 100644 --- a/src/device/device_trait.rs +++ b/src/device/device_trait.rs @@ -10,59 +10,104 @@ use async_trait::async_trait; /// A trait used to specify the behaviour of a WoT device. /// -/// Wraps a [device handle][DeviceHandle] and defines how to react on gateway requests. Built by a [device builder][crate::DeviceBuilder]. +/// Defines how to react on gateway requests. Built by an [adapter][crate::Adapter]. /// /// # Examples /// ``` /// # use gateway_addon_rust::prelude::*; -/// struct ExampleDevice(DeviceHandle); +/// # use async_trait::async_trait; +/// #[device] +/// struct ExampleDevice { +/// foo: i32, +/// } +/// +/// impl DeviceStructure for ExampleDevice { +/// // ... +/// # fn id(&self) -> String { +/// # "example-device".to_owned() +/// # } +/// # fn description(&self) -> DeviceDescription { +/// # DeviceDescription::default() +/// # } +/// } /// -/// impl Device for ExampleDevice { +/// #[async_trait] +/// impl Device for BuiltExampleDevice {} +/// ``` +#[async_trait] +pub trait Device: BuiltDevice + Send + Sync + AsAny + 'static {} + +impl Downcast for dyn Device {} + +/// A trait used to wrap a [device handle][DeviceHandle]. +/// +/// When you use the [device][macro@crate::device] macro, this will be implemented automatically. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, device::BuiltDevice}; +/// # use async_trait::async_trait; +/// struct BuiltExampleDevice { +/// device_handle: DeviceHandle, +/// } +/// +/// impl BuiltDevice for BuiltExampleDevice { +/// fn device_handle(&self) -> &DeviceHandle { +/// &self.device_handle +/// } /// fn device_handle_mut(&mut self) -> &mut DeviceHandle { -/// &mut self.0 +/// &mut self.device_handle /// } /// } /// ``` -#[async_trait] -pub trait Device: Send + Sync + AsAny + 'static { - /// Return the wrapped [device handle][DeviceHandle]. +pub trait BuiltDevice { + /// Return a reference to the wrapped [device handle][DeviceHandle]. + fn device_handle(&self) -> &DeviceHandle; + + /// Return a mutable reference to the wrapped [device handle][DeviceHandle]. fn device_handle_mut(&mut self) -> &mut DeviceHandle; } -impl Downcast for dyn Device {} - #[cfg(test)] pub(crate) mod tests { - use crate::device::{Device, DeviceHandle}; + use crate::device::{tests::MockDevice, BuiltDevice, Device, DeviceHandle}; - pub struct MockDevice { + pub struct BuiltMockDevice { + data: MockDevice, device_handle: DeviceHandle, } - impl MockDevice { - pub const PROPERTY_BOOL: &'static str = "property_bool"; - pub const PROPERTY_U8: &'static str = "property_u8"; - pub const PROPERTY_I32: &'static str = "property_i32"; - pub const PROPERTY_F32: &'static str = "property_f32"; - pub const PROPERTY_OPTI32: &'static str = "property_opti32"; - pub const PROPERTY_STRING: &'static str = "property_string"; - pub const ACTION_NOINPUT: &'static str = "action_noinput"; - pub const ACTION_BOOL: &'static str = "action_bool"; - pub const ACTION_U8: &'static str = "action_u8"; - pub const ACTION_I32: &'static str = "action_i32"; - pub const ACTION_F32: &'static str = "action_f32"; - pub const ACTION_OPTI32: &'static str = "action_opti32"; - pub const ACTION_STRING: &'static str = "action_string"; - pub const EVENT_NODATA: &'static str = "event_nodata"; - - pub fn new(device_handle: DeviceHandle) -> Self { - MockDevice { device_handle } + impl BuiltMockDevice { + pub fn new(data: MockDevice, device_handle: DeviceHandle) -> Self { + Self { + data, + device_handle, + } } } - impl Device for MockDevice { + impl BuiltDevice for BuiltMockDevice { + fn device_handle(&self) -> &DeviceHandle { + &self.device_handle + } + fn device_handle_mut(&mut self) -> &mut DeviceHandle { &mut self.device_handle } } + + impl std::ops::Deref for BuiltMockDevice { + type Target = MockDevice; + fn deref(&self) -> &Self::Target { + &self.data + } + } + + impl std::ops::DerefMut for BuiltMockDevice { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } + } + + impl Device for BuiltMockDevice {} } diff --git a/src/device/mod.rs b/src/device/mod.rs index b24d5cc..3c20ca2 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -9,12 +9,14 @@ mod device_builder; mod device_description; mod device_handle; +mod device_macro; pub(crate) mod device_message_handler; mod device_trait; pub use device_builder::*; pub use device_description::*; pub use device_handle::*; +pub use device_macro::*; pub use device_trait::*; #[cfg(test)] diff --git a/src/event/event_builder.rs b/src/event/event_builder.rs new file mode 100644 index 0000000..32333f7 --- /dev/null +++ b/src/event/event_builder.rs @@ -0,0 +1,242 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/.* + */ + +use crate::{ + client::Client, + error::WebthingsError, + event::{Data, EventBase}, + Device, Event, EventDescription, EventHandle, +}; +use std::sync::{Arc, Weak}; +use tokio::sync::Mutex; +use webthings_gateway_ipc_types::Event as FullEventDescription; + +/// A trait used to specify the structure and behaviour of a WoT event. +/// +/// Initialized with an [event handle][EventHandle]. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, event::NoData}; +/// # use async_trait::async_trait; +/// # use std::time::Duration; +/// # use tokio::time::sleep; +/// struct ExampleEvent(); +/// +/// #[async_trait] +/// impl EventStructure for ExampleEvent { +/// type Data = NoData; +/// +/// fn name(&self) -> String { +/// "example-event".to_owned() +/// } +/// fn description(&self) -> EventDescription { +/// EventDescription::default() +/// } +/// } +/// ``` +pub trait EventStructure: Send + Sync + 'static { + /// Type of [data][Data] this event contains. + type Data: Data; + + /// Name of the event. + fn name(&self) -> String; + + /// [WoT description][EventDescription] of the event. + fn description(&self) -> EventDescription; + + #[doc(hidden)] + fn full_description(&self) -> Result { + self.description().into_full_description(self.name()) + } +} + +/// A trait used to build an [Event] around a data struct and a [event handle][EventHandle]. +/// +/// When you use the [event][macro@crate::event] macro, this will be implemented automatically. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, event::{BuiltEvent, EventBuilder}}; +/// # use async_trait::async_trait; +/// struct ExampleEvent { +/// foo: i32, +/// } +/// +/// struct BuiltExampleEvent { +/// data: ExampleEvent, +/// event_handle: EventHandle, +/// } +/// +/// impl BuiltEvent for BuiltExampleEvent { +/// // ... +/// # type Data = i32; +/// # fn event_handle(&self) -> &EventHandle { +/// # &self.event_handle +/// # } +/// # fn event_handle_mut(&mut self) -> &mut EventHandle { +/// # &mut self.event_handle +/// # } +/// } +/// +/// impl EventStructure for ExampleEvent { +/// /// ... +/// # type Data = i32; +/// # fn name(&self) -> String { +/// # "example-event".to_owned() +/// # } +/// # fn description(&self) -> EventDescription { +/// # EventDescription::default() +/// # } +/// } +/// +/// #[async_trait] +/// impl Event for BuiltExampleEvent {} +/// +/// impl EventBuilder for ExampleEvent { +/// type BuiltEvent = BuiltExampleEvent; +/// fn build(data: Self, event_handle: EventHandle) -> Self::BuiltEvent { +/// BuiltExampleEvent { +/// data, +/// event_handle, +/// } +/// } +/// } +/// ``` +pub trait EventBuilder: EventStructure { + /// Type of [Event] to build. + type BuiltEvent: Event; + + /// Build the [event][Event] from a data struct and an [event handle][EventHandle]. + fn build( + data: Self, + event_handle: EventHandle<::Data>, + ) -> Self::BuiltEvent; +} + +/// An object safe variant of [EventBuilder] + [EventStructure]. +/// +/// Auto-implemented for all objects which implement the [EventBuilder] trait. **You never have to implement this trait yourself.** +/// +/// Forwards all requests to the [EventBuilder] / [EventStructure] implementation. +/// +/// This can (in contrast to to the [EventBuilder] trait) be used to store objects for dynamic dispatch. +pub trait EventBuilderBase: Send + Sync + 'static { + /// Name of the event. + fn name(&self) -> String; + + #[doc(hidden)] + fn full_description(&self) -> Result; + + #[doc(hidden)] + #[allow(clippy::too_many_arguments)] + fn build( + self: Box, + client: Arc>, + device: Weak>>, + plugin_id: String, + adapter_id: String, + device_id: String, + ) -> Box; +} + +impl EventBuilderBase for T { + fn name(&self) -> String { + ::name(self) + } + + fn full_description(&self) -> Result { + ::full_description(self) + } + + fn build( + self: Box, + client: Arc>, + device: Weak>>, + plugin_id: String, + adapter_id: String, + device_id: String, + ) -> Box { + let event_handle = EventHandle::<::Data>::new( + client, + device, + plugin_id, + adapter_id, + device_id, + self.name(), + self.description(), + ); + Box::new(::build(*self, event_handle)) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use std::ops::{Deref, DerefMut}; + + use mockall::mock; + + use crate::{ + event::{tests::BuiltMockEvent, Data, EventBuilder}, + EventDescription, EventHandle, EventStructure, + }; + + mock! { + pub EventHelper { + pub fn post_init(&mut self); + } + } + + pub struct MockEvent { + event_name: String, + pub expect_post_init: bool, + pub event_helper: MockEventHelper, + } + + impl MockEvent { + pub fn new(event_name: String) -> Self { + Self { + event_name, + expect_post_init: false, + event_helper: MockEventHelper::new(), + } + } + } + + impl Deref for MockEvent { + type Target = MockEventHelper; + + fn deref(&self) -> &Self::Target { + &self.event_helper + } + } + + impl DerefMut for MockEvent { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.event_helper + } + } + + impl EventStructure for MockEvent { + type Data = T; + + fn name(&self) -> String { + self.event_name.clone() + } + + fn description(&self) -> EventDescription { + EventDescription::default() + } + } + + impl EventBuilder for MockEvent { + type BuiltEvent = BuiltMockEvent; + + fn build(data: Self, event_handle: EventHandle) -> Self::BuiltEvent { + BuiltMockEvent::new(data, event_handle) + } + } +} diff --git a/src/event/event_description.rs b/src/event/event_description.rs index 1016e44..c24489e 100644 --- a/src/event/event_description.rs +++ b/src/event/event_description.rs @@ -76,24 +76,28 @@ impl EventDescription { } /// Set `@type`. + #[must_use] pub fn at_type(mut self, at_type: AtType) -> Self { self.at_type = Some(at_type); self } /// Set `description`. + #[must_use] pub fn description(mut self, description: impl Into) -> Self { self.description = Some(description.into()); self } /// Set `enum`. + #[must_use] pub fn enum_(mut self, enum_: Vec) -> Self { self.enum_ = Some(enum_); self } /// Set `links`. + #[must_use] pub fn links(mut self, links: Vec) -> Self { self.links = Some(links); self @@ -119,6 +123,7 @@ impl EventDescription { /// }) /// # ; /// ``` + #[must_use] pub fn link(mut self, link: Link) -> Self { match self.links { None => self.links = Some(vec![link]), @@ -128,24 +133,28 @@ impl EventDescription { } /// Set `maximum`. + #[must_use] pub fn maximum>(mut self, maximum: F) -> Self { self.maximum = Some(maximum.into()); self } /// Set `minimum`. + #[must_use] pub fn minimum>(mut self, minimum: F) -> Self { self.minimum = Some(minimum.into()); self } /// Set `multipleOf`. + #[must_use] pub fn multiple_of>(mut self, multiple_of: F) -> Self { self.multiple_of = Some(multiple_of.into()); self } /// Set `title`. + #[must_use] pub fn title(mut self, title: impl Into) -> Self { self.title = Some(title.into()); self @@ -159,12 +168,14 @@ impl EventDescription { /// EventDescription::::default().type_(Type::Number) /// # ; /// ``` + #[must_use] pub fn type_(mut self, type_: Type) -> Self { self.type_ = Some(type_); self } /// Set `unit`. + #[must_use] pub fn unit(mut self, unit: impl Into) -> Self { self.unit = Some(unit.into()); self diff --git a/src/event/event_macro.rs b/src/event/event_macro.rs new file mode 100644 index 0000000..08338f5 --- /dev/null +++ b/src/event/event_macro.rs @@ -0,0 +1,91 @@ +/// Use this on a struct to generate a built event around it, including useful impls. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, event::EventBuilder}; +/// # use async_trait::async_trait; +/// #[event] +/// struct ExampleEvent { +/// foo: i32, +/// } +/// +/// impl EventStructure for ExampleEvent { +/// // ... +/// # type Data = i32; +/// # fn name(&self) -> String { +/// # "example-event".to_owned() +/// # } +/// # fn description(&self) -> EventDescription { +/// # EventDescription::default() +/// # } +/// } +/// +/// #[async_trait] +/// impl Event for BuiltExampleEvent { +/// // ... +/// } +/// ``` +/// will expand to +/// ``` +/// # use gateway_addon_rust::{prelude::*, event::{BuiltEvent, EventBuilder, EventStructure}}; +/// # use std::ops::{Deref, DerefMut}; +/// # use async_trait::async_trait; +/// struct ExampleEvent { +/// foo: i32, +/// } +/// +/// struct BuiltExampleEvent { +/// data: ExampleEvent, +/// event_handle: EventHandle<::Data>, +/// } +/// +/// impl BuiltEvent for BuiltExampleEvent { +/// type Data = ::Data; +/// fn event_handle(&self) -> &EventHandle { +/// &self.event_handle +/// } +/// fn event_handle_mut(&mut self) -> &mut EventHandle { +/// &mut self.event_handle +/// } +/// } +/// +/// impl EventBuilder for ExampleEvent { +/// type BuiltEvent = BuiltExampleEvent; +/// fn build(data: Self, event_handle: EventHandle<::Data>) -> Self::BuiltEvent { +/// BuiltExampleEvent { +/// data, +/// event_handle, +/// } +/// } +/// } +/// +/// impl Deref for BuiltExampleEvent { +/// type Target = ExampleEvent; +/// fn deref(&self) -> &Self::Target { +/// &self.data +/// } +/// } +/// +/// impl DerefMut for BuiltExampleEvent { +/// fn deref_mut(&mut self) -> &mut Self::Target { +/// &mut self.data +/// } +/// } +/// +/// impl EventStructure for ExampleEvent { +/// // ... +/// # type Data = i32; +/// # fn name(&self) -> String { +/// # "example-event".to_owned() +/// # } +/// # fn description(&self) -> EventDescription { +/// # EventDescription::default() +/// # } +/// } +/// +/// #[async_trait] +/// impl Event for BuiltExampleEvent { +/// // ... +/// } +/// ``` +pub use gateway_addon_rust_codegen::event; diff --git a/src/event/event_trait.rs b/src/event/event_trait.rs index 60c9bcc..26ab42e 100644 --- a/src/event/event_trait.rs +++ b/src/event/event_trait.rs @@ -5,169 +5,171 @@ */ use crate::{ - client::Client, error::WebthingsError, event::Data, Device, EventDescription, EventHandle, + event::{Data, EventHandleBase}, + EventHandle, }; use as_any::{AsAny, Downcast}; -use std::sync::{Arc, Weak}; -use tokio::sync::Mutex; -use webthings_gateway_ipc_types::Event as FullEventDescription; - -use super::EventHandleBase; - -/// A trait used to specify the structure and behaviour of a WoT event. +/// A trait used to specify the behaviour of a WoT event. /// -/// Initialized with an [event handle][EventHandle]. +/// Built by a [crate::EventHandle]. /// /// # Examples /// ``` -/// # use gateway_addon_rust::{prelude::*, event::NoData}; +/// # use gateway_addon_rust::{prelude::*, event::NoData, event::BuiltEvent}; /// # use async_trait::async_trait; /// # use std::time::Duration; /// # use tokio::time::sleep; -/// struct ExampleEvent(); +/// #[event] +/// struct ExampleEvent { +/// foo: i32, +/// } /// -/// #[async_trait] -/// impl Event for ExampleEvent { -/// type Data = NoData; +/// impl EventStructure for ExampleEvent { +/// // ... +/// # type Data = NoData; +/// # fn name(&self) -> String { +/// # "example-event".to_owned() +/// # } +/// # fn description(&self) -> EventDescription { +/// # EventDescription::default() +/// # } +/// } /// -/// fn name(&self) -> String { -/// "example-event".to_owned() -/// } -/// fn description(&self) -> EventDescription { -/// EventDescription::default() -/// } -/// fn init(&self, event_handle: EventHandle) { -/// tokio::spawn(async move { +/// #[async_trait] +/// impl Event for BuiltExampleEvent { +/// fn post_init(&mut self) { +/// let event_handle = self.event_handle().clone(); +/// tokio::task::spawn(async move { /// sleep(Duration::from_millis(1000)).await; /// event_handle.raise(NoData).await.unwrap(); /// }); /// } /// } /// ``` -pub trait Event: Send + Sync + 'static { - /// Type of [data][Data] this event contains. - type Data: Data; - - /// Name of the event. - fn name(&self) -> String; - - /// [WoT description][EventDescription] of the event. - fn description(&self) -> EventDescription; - - /// Called once during initialization with an [event handle][EventHandle] which can later be used to raise event instances. - fn init(&self, _event_handle: EventHandle) {} - - #[doc(hidden)] - fn full_description(&self) -> Result { - self.description().into_full_description(self.name()) - } - - #[doc(hidden)] - fn build_event_handle( - &self, - client: Arc>, - device: Weak>>, - plugin_id: String, - adapter_id: String, - device_id: String, - name: String, - ) -> EventHandle { - let event_handle = EventHandle::new( - client, - device, - plugin_id, - adapter_id, - device_id, - name, - self.description(), - ); - self.init(event_handle.clone()); - event_handle - } +pub trait Event: BuiltEvent + Send + Sync + 'static { + /// Called once after initialization. + fn post_init(&mut self) {} } -/// An object safe variant of [Event]. +/// An object safe variant of [Event] + [BuiltEvent]. /// /// Auto-implemented for all objects which implement the [Event] trait. **You never have to implement this trait yourself.** /// -/// Forwards all requests to the [Event] implementation. +/// Forwards all requests to the [Event] / [BuiltEvent] implementation. /// -/// This can (in contrast to the [Event] trait) be used to store objects for dynamic dispatch. +/// This can (in contrast to the [Event] and [BuiltEvent] traits) be used to store objects for dynamic dispatch. pub trait EventBase: Send + Sync + AsAny + 'static { - /// Name of the event. - fn name(&self) -> String; + /// Return a reference to the wrapped [event handle][EventHandle]. + fn event_handle(&self) -> &dyn EventHandleBase; - #[doc(hidden)] - fn full_description(&self) -> Result; + /// Return a mutable reference to the wrapped [event handle][EventHandle]. + fn event_handle_mut(&mut self) -> &mut dyn EventHandleBase; #[doc(hidden)] - fn build_event_handle( - &self, - client: Arc>, - device: Weak>>, - plugin_id: String, - adapter_id: String, - device_id: String, - name: String, - ) -> Box; + fn post_init(&mut self); } impl Downcast for dyn EventBase {} impl EventBase for T { - fn name(&self) -> String { - ::name(self) + fn event_handle(&self) -> &dyn EventHandleBase { + ::event_handle(self) } - fn full_description(&self) -> Result { - ::full_description(self) + fn event_handle_mut(&mut self) -> &mut dyn EventHandleBase { + ::event_handle_mut(self) } - fn build_event_handle( - &self, - client: Arc>, - device: Weak>>, - plugin_id: String, - adapter_id: String, - device_id: String, - name: String, - ) -> Box { - Box::new(::build_event_handle( - self, client, device, plugin_id, adapter_id, device_id, name, - )) + fn post_init(&mut self) { + ::post_init(self) } } +/// A trait used to wrap a [event handle][EventHandle]. +/// +/// When you use the [event][macro@crate::event] macro, this will be implemented automatically. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, event::{BuiltEvent, NoData}}; +/// # use async_trait::async_trait; +/// struct BuiltExampleEvent { +/// event_handle: EventHandle, +/// } +/// +/// impl BuiltEvent for BuiltExampleEvent { +/// type Data = NoData; +/// fn event_handle(&self) -> &EventHandle { +/// &self.event_handle +/// } +/// fn event_handle_mut(&mut self) -> &mut EventHandle { +/// &mut self.event_handle +/// } +/// } +/// ``` +pub trait BuiltEvent { + /// Type of [data][Data] this event contains. + type Data: Data; + + /// Return a reference to the wrapped [event handle][EventHandle]. + fn event_handle(&self) -> &EventHandle; + + /// Return a mutable reference to the wrapped [event handle][EventHandle]. + fn event_handle_mut(&mut self) -> &mut EventHandle; +} + #[cfg(test)] pub(crate) mod tests { - use crate::{event::Data, Event, EventDescription}; + use std::ops::{Deref, DerefMut}; - use std::marker::PhantomData; + use crate::{ + event::{tests::MockEvent, BuiltEvent, Data}, + Event, EventHandle, + }; - pub struct MockEvent { - event_name: String, - _data: PhantomData, + pub struct BuiltMockEvent { + data: MockEvent, + event_handle: EventHandle, } - impl MockEvent { - pub fn new(event_name: String) -> Self { - Self { - event_name, - _data: PhantomData, - } + impl BuiltMockEvent { + pub fn new(data: MockEvent, event_handle: EventHandle) -> Self { + Self { data, event_handle } + } + } + + impl Deref for BuiltMockEvent { + type Target = MockEvent; + + fn deref(&self) -> &Self::Target { + &self.data + } + } + + impl DerefMut for BuiltMockEvent { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data } } - impl Event for MockEvent { + impl BuiltEvent for BuiltMockEvent { type Data = T; - fn name(&self) -> String { - self.event_name.clone() + fn event_handle(&self) -> &EventHandle { + &self.event_handle + } + + fn event_handle_mut(&mut self) -> &mut EventHandle { + &mut self.event_handle } + } - fn description(&self) -> EventDescription { - EventDescription::default() + impl Event for BuiltMockEvent { + fn post_init(&mut self) { + if self.expect_post_init { + self.event_helper.post_init(); + } } } } diff --git a/src/event/mod.rs b/src/event/mod.rs index 45ff4f3..79a2a63 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -6,18 +6,22 @@ //! A module for everything related to WoT events. +mod event_builder; mod event_data; mod event_description; mod event_handle; +mod event_macro; mod event_trait; +pub use event_builder::*; pub use event_data::*; pub use event_description::*; pub use event_handle::*; +pub use event_macro::*; pub use event_trait::*; -/// Convenience type for a collection of [EventBase]. -pub type Events = Vec>; +/// Convenience type for a collection of [EventBuilderBase]. +pub type Events = Vec>; /// Convenience macro for building an [Events]. /// @@ -37,5 +41,5 @@ macro_rules! events [ #[cfg(test)] pub(crate) mod tests { - pub use super::event_trait::tests::*; + pub use super::{event_builder::tests::*, event_trait::tests::*}; } diff --git a/src/example.rs b/src/example.rs index c734287..43ba892 100644 --- a/src/example.rs +++ b/src/example.rs @@ -4,11 +4,23 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/.* */ +#![allow(clippy::new_without_default)] + use crate::{ - action::NoInput, actions, error::WebthingsError, event::NoData, events, plugin::connect, - properties, Action, ActionDescription, ActionHandle, Actions, Adapter, AdapterHandle, Device, - DeviceBuilder, DeviceDescription, DeviceHandle, Event, EventDescription, Events, Properties, - Property, PropertyBuilder, PropertyDescription, PropertyHandle, + action::NoInput, + actions, + adapter::{AdapterBuilder, BuiltAdapter}, + device::{BuiltDevice, DeviceBuilder}, + error::WebthingsError, + event::{BuiltEvent, EventBuilder, NoData}, + events, + plugin::connect, + properties, + property::{BuiltProperty, PropertyBuilder}, + Action, ActionDescription, ActionHandle, Actions, Adapter, AdapterHandle, AdapterStructure, + Device, DeviceDescription, DeviceHandle, DeviceStructure, Event, EventDescription, EventHandle, + EventStructure, Events, Properties, Property, PropertyDescription, PropertyHandle, + PropertyStructure, }; use as_any::Downcast; use async_trait::async_trait; @@ -16,13 +28,11 @@ use async_trait::async_trait; #[tokio::main] pub async fn main() -> Result<(), WebthingsError> { let mut plugin = connect("example-addon").await?; - let adapter = plugin - .create_adapter("example-adapter", "Example Adapter", ExampleAdapter::new) - .await?; + let adapter = plugin.add_adapter(ExampleAdapter::new()).await?; adapter .lock() .await - .downcast_mut::() + .downcast_mut::() .unwrap() .init() .await?; @@ -30,32 +40,113 @@ pub async fn main() -> Result<(), WebthingsError> { Ok(()) } -pub struct ExampleAdapter(AdapterHandle); +pub struct ExampleAdapter; + +pub struct BuiltExampleAdapter { + data: ExampleAdapter, + adapter_handle: AdapterHandle, +} + +impl AdapterStructure for ExampleAdapter { + fn id(&self) -> String { + "example-adapter".to_owned() + } + fn name(&self) -> String { + "Example Adapter".to_owned() + } +} + +impl AdapterBuilder for ExampleAdapter { + type BuiltAdapter = BuiltExampleAdapter; + fn build(data: Self, adapter_handle: AdapterHandle) -> Self::BuiltAdapter { + BuiltExampleAdapter { + data, + adapter_handle, + } + } +} + +impl BuiltAdapter for BuiltExampleAdapter { + fn adapter_handle(&self) -> &AdapterHandle { + &self.adapter_handle + } -impl Adapter for ExampleAdapter { fn adapter_handle_mut(&mut self) -> &mut AdapterHandle { - &mut self.0 + &mut self.adapter_handle } } +impl std::ops::Deref for BuiltExampleAdapter { + type Target = ExampleAdapter; + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl std::ops::DerefMut for BuiltExampleAdapter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +impl Adapter for BuiltExampleAdapter {} + impl ExampleAdapter { - pub fn new(adapter_handle: AdapterHandle) -> Self { - Self(adapter_handle) + pub fn new() -> Self { + Self } +} +impl BuiltExampleAdapter { async fn init(&mut self) -> Result<(), WebthingsError> { self.adapter_handle_mut() - .add_device(ExampleDeviceBuilder::new()) + .add_device(ExampleDevice::new()) .await?; Ok(()) } } -pub struct ExampleDeviceBuilder(); +pub struct ExampleDevice; + +pub struct BuiltExampleDevice { + data: ExampleDevice, + device_handle: DeviceHandle, +} -impl DeviceBuilder for ExampleDeviceBuilder { - type Device = ExampleDevice; +impl DeviceBuilder for ExampleDevice { + type BuiltDevice = BuiltExampleDevice; + fn build(data: Self, device_handle: DeviceHandle) -> Self::BuiltDevice { + BuiltExampleDevice { + data, + device_handle, + } + } +} +impl BuiltDevice for BuiltExampleDevice { + fn device_handle(&self) -> &DeviceHandle { + &self.device_handle + } + + fn device_handle_mut(&mut self) -> &mut DeviceHandle { + &mut self.device_handle + } +} + +impl std::ops::Deref for BuiltExampleDevice { + type Target = ExampleDevice; + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl std::ops::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() } @@ -65,7 +156,7 @@ impl DeviceBuilder for ExampleDeviceBuilder { } fn properties(&self) -> Properties { - properties![ExamplePropertyBuilder::new()] + properties![ExampleProperty::new()] } fn actions(&self) -> Actions { @@ -75,72 +166,78 @@ impl DeviceBuilder for ExampleDeviceBuilder { fn events(&self) -> Events { events![ExampleEvent::new()] } - - fn build(self, device_handle: DeviceHandle) -> Self::Device { - ExampleDevice::new(device_handle) - } } -impl ExampleDeviceBuilder { - #[allow(clippy::new_without_default)] +impl Device for BuiltExampleDevice {} + +impl ExampleDevice { pub fn new() -> Self { - Self() + Self } } -pub struct ExampleDevice(DeviceHandle); +pub struct ExampleProperty; -impl Device for ExampleDevice { - fn device_handle_mut(&mut self) -> &mut DeviceHandle { - &mut self.0 - } +pub struct BuiltExampleProperty { + data: ExampleProperty, + property_handle: PropertyHandle<::Value>, } -impl ExampleDevice { - pub fn new(device_handle: DeviceHandle) -> Self { - Self(device_handle) +impl PropertyBuilder for ExampleProperty { + type BuiltProperty = BuiltExampleProperty; + fn build( + data: Self, + property_handle: PropertyHandle<::Value>, + ) -> Self::BuiltProperty { + BuiltExampleProperty { + data, + property_handle, + } } } -pub struct ExamplePropertyBuilder(); +impl BuiltProperty for BuiltExampleProperty { + type Value = ::Value; -impl PropertyBuilder for ExamplePropertyBuilder { - type Property = ExampleProperty; - type Value = i32; - - fn name(&self) -> String { - "example-property".to_owned() + fn property_handle(&self) -> &PropertyHandle { + &self.property_handle } - fn build(self: Box, property_handle: PropertyHandle) -> Self::Property { - ExampleProperty::new(property_handle) + fn property_handle_mut(&mut self) -> &mut PropertyHandle { + &mut self.property_handle } +} - fn description(&self) -> PropertyDescription { - PropertyDescription::default() +impl std::ops::Deref for BuiltExampleProperty { + type Target = ExampleProperty; + fn deref(&self) -> &Self::Target { + &self.data } } -impl ExamplePropertyBuilder { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self() +impl std::ops::DerefMut for BuiltExampleProperty { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data } } -pub struct ExampleProperty(PropertyHandle); - -impl Property for ExampleProperty { +impl PropertyStructure for ExampleProperty { type Value = i32; - fn property_handle_mut(&mut self) -> &mut PropertyHandle { - &mut self.0 + fn name(&self) -> String { + "example-Property".to_owned() + } + + fn description(&self) -> PropertyDescription { + PropertyDescription::default() } } +impl Property for BuiltExampleProperty {} + impl ExampleProperty { - pub fn new(property_handle: PropertyHandle) -> Self { - Self(property_handle) + pub fn new() -> Self { + Self } } @@ -170,9 +267,49 @@ impl ExampleAction { } } -pub struct ExampleEvent(); +pub struct ExampleEvent; + +pub struct BuiltExampleEvent { + data: ExampleEvent, + event_handle: EventHandle<::Data>, +} + +impl EventBuilder for ExampleEvent { + type BuiltEvent = BuiltExampleEvent; + fn build( + data: Self, + event_handle: EventHandle<::Data>, + ) -> Self::BuiltEvent { + BuiltExampleEvent { data, event_handle } + } +} -impl Event for ExampleEvent { +impl BuiltEvent for BuiltExampleEvent { + type Data = ::Data; + + fn event_handle(&self) -> &EventHandle { + &self.event_handle + } + + fn event_handle_mut(&mut self) -> &mut EventHandle { + &mut self.event_handle + } +} + +impl std::ops::Deref for BuiltExampleEvent { + type Target = ExampleEvent; + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl std::ops::DerefMut for BuiltExampleEvent { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +impl EventStructure for ExampleEvent { type Data = NoData; fn name(&self) -> String { @@ -184,9 +321,10 @@ impl Event for ExampleEvent { } } +impl Event for BuiltExampleEvent {} + impl ExampleEvent { - #[allow(clippy::new_without_default)] pub fn new() -> Self { - Self() + Self } } diff --git a/src/lib.rs b/src/lib.rs index 10a2af9..827ef48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ pub mod device; pub mod error; pub mod event; #[cfg(not(test))] +#[cfg(debug_assertions)] #[doc(hidden)] pub mod example; pub(crate) mod message_handler; @@ -36,14 +37,17 @@ pub mod prelude { pub use crate::{ action::{self, Action, ActionDescription, ActionHandle, Actions}, actions, - adapter::{Adapter, AdapterHandle}, - device::{Device, DeviceBuilder, DeviceDescription, DeviceHandle}, - event::{self, Event, EventDescription, EventHandle, Events}, + adapter::{adapter, Adapter, AdapterHandle, AdapterStructure, BuiltAdapter}, + device::{device, BuiltDevice, Device, DeviceDescription, DeviceHandle, DeviceStructure}, + event::{ + self, event, BuiltEvent, Event, EventDescription, EventHandle, EventStructure, Events, + }, events, plugin::Plugin, properties, property::{ - self, Properties, Property, PropertyBuilder, PropertyDescription, PropertyHandle, + self, property, BuiltProperty, Properties, Property, PropertyDescription, + PropertyHandle, PropertyStructure, }, }; } diff --git a/src/plugin/plugin_connection.rs b/src/plugin/plugin_connection.rs index 2468d07..6f8ec1c 100644 --- a/src/plugin/plugin_connection.rs +++ b/src/plugin/plugin_connection.rs @@ -9,7 +9,12 @@ use mockall_double::double; mod double { #[cfg(not(test))] pub mod plugin { - use crate::{api_handler::NoopApiHandler, client::Client, error::WebthingsError, Plugin}; + use crate::{ + api_handler::{ApiHandlerBuilder, ApiHandlerHandle, NoopApiHandler}, + client::Client, + error::WebthingsError, + Plugin, + }; use futures::stream::{SplitStream, StreamExt}; use std::{collections::HashMap, str::FromStr, sync::Arc}; use tokio::{net::TcpStream, sync::Mutex}; @@ -60,14 +65,20 @@ mod double { } }; + let client = Arc::new(Mutex::new(client)); + let api_handler = Arc::new(Mutex::new(NoopApiHandler::build( + NoopApiHandler, + ApiHandlerHandle::new(client.clone(), plugin_id.clone()), + ))); + Ok(Plugin { plugin_id, preferences, user_profile, - client: Arc::new(Mutex::new(client)), + client, stream, adapters: HashMap::new(), - api_handler: Arc::new(Mutex::new(NoopApiHandler::new())), + api_handler, }) } @@ -90,7 +101,11 @@ mod double { #[cfg(test)] pub mod mock_plugin { - use crate::{api_handler::NoopApiHandler, client::Client, Plugin}; + use crate::{ + api_handler::{ApiHandlerBuilder, ApiHandlerHandle, NoopApiHandler}, + client::Client, + Plugin, + }; use std::{collections::HashMap, sync::Arc}; use tokio::sync::Mutex; use webthings_gateway_ipc_types::{Message as IPCMessage, Preferences, Units, UserProfile}; @@ -98,6 +113,7 @@ mod double { pub(crate) type PluginStream = (); pub fn connect(plugin_id: impl Into) -> Plugin { + let plugin_id = plugin_id.into(); let preferences = Preferences { language: "en-US".to_owned(), units: Units { @@ -114,14 +130,18 @@ mod double { media_dir: "".to_owned(), }; let client = Arc::new(Mutex::new(Client::new())); + let api_handler = Arc::new(Mutex::new(NoopApiHandler::build( + NoopApiHandler, + ApiHandlerHandle::new(client.clone(), plugin_id.clone()), + ))); Plugin { - plugin_id: plugin_id.into(), + plugin_id, preferences, user_profile, client, stream: (), adapters: HashMap::new(), - api_handler: Arc::new(Mutex::new(NoopApiHandler::new())), + api_handler, } } diff --git a/src/plugin/plugin_message_handler.rs b/src/plugin/plugin_message_handler.rs index 6f19143..a992549 100644 --- a/src/plugin/plugin_message_handler.rs +++ b/src/plugin/plugin_message_handler.rs @@ -72,9 +72,7 @@ impl MessageHandler for Plugin { .await } IPCMessage::ApiHandlerUnloadRequest(_) | IPCMessage::ApiHandlerApiRequest(_) => { - (self.api_handler.clone(), self.client.clone()) - .handle_message(message) - .await + self.api_handler.lock().await.handle_message(message).await } msg => Err(format!("Unexpected msg: {:?}", msg)), } diff --git a/src/plugin/plugin_struct.rs b/src/plugin/plugin_struct.rs index ef0341a..b3e0a98 100644 --- a/src/plugin/plugin_struct.rs +++ b/src/plugin/plugin_struct.rs @@ -5,7 +5,8 @@ */ use crate::{ - api_handler::ApiHandler, + adapter::AdapterBuilder, + api_handler::{ApiHandler, ApiHandlerBuilder, ApiHandlerHandle}, client::Client, database::Database, error::WebthingsError, @@ -79,7 +80,7 @@ impl Plugin { .ok_or(WebthingsError::UnknownAdapter(adapter_id)) } - /// Create a new adapter. + /// Add an adapter. /// /// # Examples /// ```no_run @@ -88,30 +89,22 @@ impl Plugin { /// # async fn main() -> Result<(), WebthingsError> { /// # let mut plugin = connect("example-addon").await?; /// let adapter = plugin - /// .create_adapter("example_adapter", "Example Adapter", |adapter_handle| { - /// ExampleAdapter::new(adapter_handle) - /// }) + /// .add_adapter(ExampleAdapter::new()) /// .await?; /// # plugin.event_loop().await; /// # Ok(()) /// # } /// ``` - pub async fn create_adapter( + pub async fn add_adapter( &mut self, - adapter_id: impl Into, - name: impl Into, - constructor: F, - ) -> Result>>, WebthingsError> - where - T: Adapter, - F: FnOnce(AdapterHandle) -> T, - { - let adapter_id = adapter_id.into(); - + adapter: T, + ) -> Result>>, WebthingsError> { + let adapter_id = adapter.id(); + let adapter_name = adapter.name(); let message: Message = AdapterAddedNotificationMessageData { plugin_id: self.plugin_id.clone(), adapter_id: adapter_id.clone(), - name: name.into(), + name: adapter_name, package_name: self.plugin_id.clone(), } .into(); @@ -125,7 +118,7 @@ impl Plugin { ); let adapter: Arc>> = - Arc::new(Mutex::new(Box::new(constructor(adapter_handle)))); + Arc::new(Mutex::new(Box::new(T::build(adapter, adapter_handle)))); let adapter_weak = Arc::downgrade(&adapter); adapter.lock().await.adapter_handle_mut().weak = adapter_weak; self.adapters.insert(adapter_id, adapter.clone()); @@ -134,11 +127,14 @@ impl Plugin { } /// Set a new active [ApiHandler](crate::api_handler::ApiHandler). - pub async fn set_api_handler( + pub async fn set_api_handler( &mut self, api_handler: T, ) -> Result<(), WebthingsError> { - self.api_handler = Arc::new(Mutex::new(api_handler)); + self.api_handler = Arc::new(Mutex::new(T::build( + api_handler, + ApiHandlerHandle::new(self.client.clone(), self.plugin_id.clone()), + ))); let message: Message = ApiHandlerAddedNotificationMessageData { plugin_id: self.plugin_id.clone(), package_name: self.plugin_id.clone(), @@ -199,9 +195,6 @@ pub(crate) mod tests { plugin: &mut Plugin, adapter_id: &str, ) -> Arc>> { - let plugin_id = plugin.plugin_id.to_owned(); - let adapter_id_clone = adapter_id.to_owned(); - plugin .client .lock() @@ -209,7 +202,7 @@ pub(crate) mod tests { .expect_send_message() .withf(move |msg| match msg { Message::AdapterAddedNotification(msg) => { - msg.data.plugin_id == plugin_id && msg.data.adapter_id == adapter_id_clone + msg.data.plugin_id == PLUGIN_ID && msg.data.adapter_id == ADAPTER_ID } _ => false, }) @@ -217,7 +210,7 @@ pub(crate) mod tests { .returning(|_| Ok(())); plugin - .create_adapter(adapter_id, adapter_id, MockAdapter::new) + .add_adapter(MockAdapter::new(adapter_id.to_owned())) .await .unwrap() } @@ -250,7 +243,7 @@ pub(crate) mod tests { #[rstest] #[tokio::test] - async fn test_create_adapter(mut plugin: Plugin) { + async fn test_add_adapter(mut plugin: Plugin) { add_mock_adapter(&mut plugin, ADAPTER_ID).await; assert!(plugin.borrow_adapter(ADAPTER_ID).is_ok()); } diff --git a/src/property/mod.rs b/src/property/mod.rs index b8b0a5f..06f2508 100644 --- a/src/property/mod.rs +++ b/src/property/mod.rs @@ -9,12 +9,14 @@ mod property_builder; mod property_description; mod property_handle; +mod property_macro; mod property_trait; mod property_value; pub use property_builder::*; pub use property_description::*; pub use property_handle::*; +pub use property_macro::*; pub use property_trait::*; pub use property_value::*; @@ -25,8 +27,8 @@ pub type Properties = Vec>; /// /// # Examples /// ``` -/// # use gateway_addon_rust::{prelude::*, example::ExamplePropertyBuilder}; -/// properties![ExamplePropertyBuilder::new()] +/// # use gateway_addon_rust::{prelude::*, example::ExampleProperty}; +/// properties![ExampleProperty::new()] /// # ; /// ``` #[macro_export] diff --git a/src/property/property_builder.rs b/src/property/property_builder.rs index b8b55c6..ddb37ad 100644 --- a/src/property/property_builder.rs +++ b/src/property/property_builder.rs @@ -8,25 +8,22 @@ use crate::{ client::Client, error::WebthingsError, property::{PropertyBase, Value}, - Device, Property, PropertyHandle, + Device, Property, PropertyDescription, PropertyHandle, }; - use std::sync::{Arc, Weak}; use tokio::sync::Mutex; use webthings_gateway_ipc_types::Property as FullPropertyDescription; /// A trait used to specify the structure of a WoT property. /// -/// Builds a [Property] instance. -/// /// # Examples /// ``` -/// # use gateway_addon_rust::{prelude::*, example::ExampleProperty}; -/// // ... -/// struct ExamplePropertyBuilder(); +/// # use gateway_addon_rust::prelude::*; +/// pub struct ExampleProperty { +/// foo: i32, +/// } /// -/// impl PropertyBuilder for ExamplePropertyBuilder { -/// type Property = ExampleProperty; +/// impl PropertyStructure for ExampleProperty { /// type Value = i32; /// /// fn name(&self) -> String { @@ -36,63 +33,94 @@ use webthings_gateway_ipc_types::Property as FullPropertyDescription; /// fn description(&self) -> PropertyDescription { /// PropertyDescription::default() /// } -/// -/// fn build(self: Box, property_handle: PropertyHandle) -> Self::Property { -/// ExampleProperty::new(property_handle) -/// } /// } /// ``` -pub trait PropertyBuilder: Send + Sync + 'static { - /// Type of [property][Property] this builds. - type Property: Property; - +pub trait PropertyStructure: Send + Sync + 'static { /// Type of [value][Value] which `Self::Property` accepts. type Value: Value; /// Name of the property. fn name(&self) -> String; - /// [WoT description][crate::PropertyDescription] of the property. - fn description(&self) -> crate::PropertyDescription; - - /// Build a new instance of this property using the given [property handle][PropertyHandle]. - fn build(self: Box, property_handle: PropertyHandle) -> Self::Property; + /// [WoT description][PropertyDescription] of the property. + fn description(&self) -> PropertyDescription; #[doc(hidden)] fn full_description(&self) -> Result { self.description().into_full_description(self.name()) } +} - #[doc(hidden)] - #[allow(clippy::too_many_arguments)] - fn build_( - self: Box, - client: Arc>, - device: Weak>>, - plugin_id: String, - adapter_id: String, - device_id: String, - ) -> Self::Property { - let property_handle = PropertyHandle::::new( - client, - device, - plugin_id, - adapter_id, - device_id, - self.name(), - self.description(), - ); - self.build(property_handle) - } +/// A trait used to build a [Property] around a data struct and a [property handle][PropertyHandle]. +/// +/// When you use the [property][macro@crate::property] macro, this will be implemented automatically. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, property::{BuiltProperty, PropertyBuilder}}; +/// # use async_trait::async_trait; +/// struct ExampleProperty { +/// foo: i32, +/// } +/// +/// struct BuiltExampleProperty { +/// data: ExampleProperty, +/// property_handle: PropertyHandle, +/// } +/// +/// impl BuiltProperty for BuiltExampleProperty { +/// // ... +/// # type Value = i32; +/// # fn property_handle(&self) -> &PropertyHandle { +/// # &self.property_handle +/// # } +/// # fn property_handle_mut(&mut self) -> &mut PropertyHandle { +/// # &mut self.property_handle +/// # } +/// } +/// +/// impl PropertyStructure for ExampleProperty { +/// /// ... +/// # type Value = i32; +/// # fn name(&self) -> String { +/// # "example-property".to_owned() +/// # } +/// # fn description(&self) -> PropertyDescription { +/// # PropertyDescription::default() +/// # } +/// } +/// +/// #[async_trait] +/// impl Property for BuiltExampleProperty {} +/// +/// impl PropertyBuilder for ExampleProperty { +/// type BuiltProperty = BuiltExampleProperty; +/// fn build(data: Self, property_handle: PropertyHandle) -> Self::BuiltProperty { +/// BuiltExampleProperty { +/// data, +/// property_handle, +/// } +/// } +/// } +/// ``` +pub trait PropertyBuilder: PropertyStructure { + /// Type of [Property] to build. + type BuiltProperty: Property; + + /// Build the [property][Property] from a data struct and an [property handle][PropertyHandle]. + fn build( + data: Self, + property_handle: PropertyHandle<::Value>, + ) -> Self::BuiltProperty; } -/// An object safe variant of [PropertyBuilder]. +/// An object safe variant of [PropertyBuilder] + [PropertyStructure]. /// -/// Auto-implemented for all objects which implement the [PropertyBuilder] trait. **You never have to implement this trait yourself.** +/// Auto-implemented for all objects which implement the [PropertyBuilder] trait. **You never have to implement this trait yourself.** /// -/// Forwards all requests to the [PropertyBuilder] implementation. +/// Forwards all requests to the [PropertyBuilder] / [PropertyStructure] implementation. /// -/// This can (in contrast to the [PropertyBuilder] trait) be used to store objects for dynamic dispatch. +/// This can (in contrast to to the [PropertyBuilder] trait) be used to store objects for dynamic dispatch. pub trait PropertyBuilderBase: Send + Sync + 'static { /// Name of the property. fn name(&self) -> String; @@ -101,6 +129,7 @@ pub trait PropertyBuilderBase: Send + Sync + 'static { fn full_description(&self) -> Result; #[doc(hidden)] + #[allow(clippy::too_many_arguments)] fn build( self: Box, client: Arc>, @@ -113,14 +142,13 @@ pub trait PropertyBuilderBase: Send + Sync + 'static { impl PropertyBuilderBase for T { fn name(&self) -> String { - ::name(self) + ::name(self) } fn full_description(&self) -> Result { - ::full_description(self) + ::full_description(self) } - #[doc(hidden)] fn build( self: Box, client: Arc>, @@ -129,37 +157,67 @@ impl PropertyBuilderBase for T { adapter_id: String, device_id: String, ) -> Box { - Box::new(::build_( - self, client, device, plugin_id, adapter_id, device_id, - )) + let property_handle = PropertyHandle::<::Value>::new( + client, + device, + plugin_id, + adapter_id, + device_id, + self.name(), + self.description(), + ); + Box::new(::build(*self, property_handle)) } } #[cfg(test)] pub(crate) mod tests { + use std::ops::{Deref, DerefMut}; + use crate::{ - property::{self, tests::MockProperty}, - PropertyBuilder, PropertyDescription, PropertyHandle, + property::{self, tests::BuiltMockProperty, PropertyBuilder}, + PropertyDescription, PropertyHandle, PropertyStructure, }; + use mockall::mock; - use std::marker::PhantomData; + mock! { + pub PropertyHelper { + pub fn on_update(&self, value: T) -> Result<(), String>; + pub fn post_init(&mut self); + } + } - pub struct MockPropertyBuilder { + pub struct MockProperty { property_name: String, - _value: PhantomData, + pub expect_post_init: bool, + pub property_helper: MockPropertyHelper, } - impl MockPropertyBuilder { + impl MockProperty { pub fn new(property_name: String) -> Self { Self { property_name, - _value: PhantomData, + expect_post_init: false, + property_helper: MockPropertyHelper::new(), } } } - impl PropertyBuilder for MockPropertyBuilder { - type Property = MockProperty; + impl Deref for MockProperty { + type Target = MockPropertyHelper; + + fn deref(&self) -> &Self::Target { + &self.property_helper + } + } + + impl DerefMut for MockProperty { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.property_helper + } + } + + impl PropertyStructure for MockProperty { type Value = T; fn name(&self) -> String { @@ -169,9 +227,12 @@ pub(crate) mod tests { fn description(&self) -> PropertyDescription { PropertyDescription::default() } + } - fn build(self: Box, property_handle: PropertyHandle) -> Self::Property { - MockProperty::new(property_handle) + impl PropertyBuilder for MockProperty { + type BuiltProperty = BuiltMockProperty; + fn build(data: Self, property_handle: PropertyHandle) -> Self::BuiltProperty { + BuiltMockProperty::new(data, property_handle) } } } diff --git a/src/property/property_description.rs b/src/property/property_description.rs index 495356d..fc5937a 100644 --- a/src/property/property_description.rs +++ b/src/property/property_description.rs @@ -10,7 +10,7 @@ use webthings_gateway_ipc_types::{Link, Property as FullPropertyDescription}; /// A struct which represents a WoT [property description][webthings_gateway_ipc_types::Property]. /// -/// This is used by [PropertyBuilder][crate::PropertyBuilder]. +/// This is used by [PropertyBuilder][crate::property::PropertyBuilder]. /// /// Use the provided builder methods instead of directly writing to the struct fields. /// @@ -107,24 +107,28 @@ impl PropertyDescription { } /// Set `@type`. + #[must_use] pub fn at_type(mut self, at_type: AtType) -> Self { self.at_type = Some(at_type); self } /// Set `description`. + #[must_use] pub fn description(mut self, description: impl Into) -> Self { self.description = Some(description.into()); self } /// Set `enum`. + #[must_use] pub fn enum_(mut self, enum_: Vec) -> Self { self.enum_ = Some(enum_); self } /// Set `links`. + #[must_use] pub fn links(mut self, links: Vec) -> Self { self.links = Some(links); self @@ -150,6 +154,7 @@ impl PropertyDescription { /// }) /// # ; /// ``` + #[must_use] pub fn link(mut self, link: Link) -> Self { match self.links { None => self.links = Some(vec![link]), @@ -159,30 +164,35 @@ impl PropertyDescription { } /// Set `maximum`. + #[must_use] pub fn maximum>(mut self, maximum: F) -> Self { self.maximum = Some(maximum.into()); self } /// Set `minimum`. + #[must_use] pub fn minimum>(mut self, minimum: F) -> Self { self.minimum = Some(minimum.into()); self } /// Set `multipleOf`. + #[must_use] pub fn multiple_of>(mut self, multiple_of: F) -> Self { self.multiple_of = Some(multiple_of.into()); self } /// Set `readOnly`. + #[must_use] pub fn read_only(mut self, read_only: bool) -> Self { self.read_only = Some(read_only); self } /// Set `title`. + #[must_use] pub fn title(mut self, title: impl Into) -> Self { self.title = Some(title.into()); self @@ -196,24 +206,28 @@ impl PropertyDescription { /// PropertyDescription::::default().type_(Type::Number) /// # ; /// ``` + #[must_use] pub fn type_(mut self, type_: Type) -> Self { self.type_ = type_; self } /// Set `unit`. + #[must_use] pub fn unit(mut self, unit: impl Into) -> Self { self.unit = Some(unit.into()); self } /// Set initial `value`. + #[must_use] pub fn value(mut self, value: T) -> Self { self.value = value; self } /// Set `visible`. + #[must_use] pub fn visible(mut self, visible: bool) -> Self { self.visible = Some(visible); self diff --git a/src/property/property_macro.rs b/src/property/property_macro.rs new file mode 100644 index 0000000..bc908ee --- /dev/null +++ b/src/property/property_macro.rs @@ -0,0 +1,91 @@ +/// Use this on a struct to generate a built property around it, including useful impls. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, property::PropertyBuilder}; +/// # use async_trait::async_trait; +/// #[property] +/// struct ExampleProperty { +/// foo: i32, +/// } +/// +/// impl PropertyStructure for ExampleProperty { +/// // ... +/// # type Value = i32; +/// # fn name(&self) -> String { +/// # "example-property".to_owned() +/// # } +/// # fn description(&self) -> PropertyDescription { +/// # PropertyDescription::default() +/// # } +/// } +/// +/// #[async_trait] +/// impl Property for BuiltExampleProperty { +/// // ... +/// } +/// ``` +/// will expand to +/// ``` +/// # use gateway_addon_rust::{prelude::*, property::{BuiltProperty, PropertyBuilder, PropertyStructure}}; +/// # use std::ops::{Deref, DerefMut}; +/// # use async_trait::async_trait; +/// struct ExampleProperty { +/// foo: i32, +/// } +/// +/// struct BuiltExampleProperty { +/// data: ExampleProperty, +/// property_handle: PropertyHandle<::Value>, +/// } +/// +/// impl BuiltProperty for BuiltExampleProperty { +/// type Value = ::Value; +/// fn property_handle(&self) -> &PropertyHandle { +/// &self.property_handle +/// } +/// fn property_handle_mut(&mut self) -> &mut PropertyHandle { +/// &mut self.property_handle +/// } +/// } +/// +/// impl PropertyBuilder for ExampleProperty { +/// type BuiltProperty = BuiltExampleProperty; +/// fn build(data: Self, property_handle: PropertyHandle<::Value>) -> Self::BuiltProperty { +/// BuiltExampleProperty { +/// data, +/// property_handle, +/// } +/// } +/// } +/// +/// impl Deref for BuiltExampleProperty { +/// type Target = ExampleProperty; +/// fn deref(&self) -> &Self::Target { +/// &self.data +/// } +/// } +/// +/// impl DerefMut for BuiltExampleProperty { +/// fn deref_mut(&mut self) -> &mut Self::Target { +/// &mut self.data +/// } +/// } +/// +/// impl PropertyStructure for ExampleProperty { +/// // ... +/// # type Value = i32; +/// # fn name(&self) -> String { +/// # "example-property".to_owned() +/// # } +/// # fn description(&self) -> PropertyDescription { +/// # PropertyDescription::default() +/// # } +/// } +/// +/// #[async_trait] +/// impl Property for BuiltExampleProperty { +/// // ... +/// } +/// ``` +pub use gateway_addon_rust_codegen::property; diff --git a/src/property/property_trait.rs b/src/property/property_trait.rs index 4148e9d..aa11969 100644 --- a/src/property/property_trait.rs +++ b/src/property/property_trait.rs @@ -13,26 +13,35 @@ use async_trait::async_trait; /// A trait used to specify the behaviour of a WoT property. /// -/// Wraps a [property handle][PropertyHandle] and defines how to react on gateway requests. Built by a [crate::PropertyBuilder]. +/// Defines how to react on gateway requests. Built by a [crate::property::PropertyBuilder]. /// /// # Examples /// ``` -/// # use gateway_addon_rust::{prelude::*}; +/// # use gateway_addon_rust::{prelude::*, property::BuiltProperty}; /// # use async_trait::async_trait; -/// struct ExampleProperty(PropertyHandle); -/// -/// #[async_trait] -/// impl Property for ExampleProperty { -/// type Value = i32; +/// #[property] +/// struct ExampleProperty { +/// foo: i32, +/// } /// -/// fn property_handle_mut(&mut self) -> &mut PropertyHandle { -/// &mut self.0 -/// } +/// impl PropertyStructure for ExampleProperty { +/// // ... +/// # type Value = i32; +/// # fn name(&self) -> String { +/// # "example-property".to_owned() +/// # } +/// # fn description(&self) -> PropertyDescription { +/// # PropertyDescription::default() +/// # } +/// } /// +/// #[async_trait] +/// impl Property for BuiltExampleProperty { /// async fn on_update(&mut self, value: Self::Value) -> Result<(), String> { /// log::debug!( -/// "Value changed from {:?} to {:?}", -/// self.0.description.value, +/// "Value with foo {:?} changed from {:?} to {:?}", +/// self.foo, +/// self.property_handle().description.value, /// value, /// ); /// Ok(()) @@ -40,86 +49,153 @@ use async_trait::async_trait; /// } /// ``` #[async_trait] -pub trait Property: Send + Sync + 'static { - /// Type of [value][Value] this property accepts. - type Value: Value; - - /// Return the wrapped [property handle][PropertyHandle]. - fn property_handle_mut(&mut self) -> &mut PropertyHandle; - +pub trait Property: BuiltProperty + Send + Sync + 'static { /// Called when the [value][Value] has been updated through the gateway. /// /// Should return `Ok(())` when the given value is accepted and an `Err` otherwise. - async fn on_update(&mut self, _value: Self::Value) -> Result<(), String> { + async fn on_update(&mut self, _value: ::Value) -> Result<(), String> { Ok(()) } + + /// Called once after initialization. + fn post_init(&mut self) {} } -/// An object safe variant of [Property]. +/// An object safe variant of [Property] + [BuiltProperty]. /// /// Auto-implemented for all objects which implement the [Property] trait. **You never have to implement this trait yourself.** /// -/// Forwards all requests to the [Property] implementation. +/// Forwards all requests to the [Property] / [BuiltProperty] implementations. /// -/// This can (in contrast to the [Property] trait) be used to store objects for dynamic dispatch. +/// This can (in contrast to the [Property] and [BuiltProperty] traits) be used to store objects for dynamic dispatch. #[async_trait] pub trait PropertyBase: Send + Sync + AsAny + 'static { - /// Return the wrapped [property handle][PropertyHandle]. + /// Return a reference to the wrapped [property handle][PropertyHandle]. + fn property_handle(&self) -> &dyn PropertyHandleBase; + + /// Return a mutable reference to the wrapped [property handle][PropertyHandle]. fn property_handle_mut(&mut self) -> &mut dyn PropertyHandleBase; #[doc(hidden)] async fn on_update(&mut self, value: serde_json::Value) -> Result<(), String>; + + #[doc(hidden)] + fn post_init(&mut self) {} } impl Downcast for dyn PropertyBase {} #[async_trait] impl PropertyBase for T { + fn property_handle(&self) -> &dyn PropertyHandleBase { + ::property_handle(self) + } + fn property_handle_mut(&mut self) -> &mut dyn PropertyHandleBase { - ::property_handle_mut(self) + ::property_handle_mut(self) } async fn on_update(&mut self, value: serde_json::Value) -> Result<(), String> { - let value = T::Value::deserialize(Some(value)) + let value = ::Value::deserialize(Some(value)) .map_err(|err| format!("Could not deserialize value: {:?}", err))?; ::on_update(self, value).await } + + fn post_init(&mut self) { + ::post_init(self) + } +} + +/// A trait used to wrap a [property handle][PropertyHandle]. +/// +/// When you use the [property][macro@crate::property] macro, this will be implemented automatically. +/// +/// # Examples +/// ``` +/// # use gateway_addon_rust::{prelude::*, property::BuiltProperty}; +/// # use async_trait::async_trait; +/// struct BuiltExampleProperty { +/// property_handle: PropertyHandle, +/// } +/// +/// impl BuiltProperty for BuiltExampleProperty { +/// type Value = i32; +/// fn property_handle(&self) -> &PropertyHandle { +/// &self.property_handle +/// } +/// fn property_handle_mut(&mut self) -> &mut PropertyHandle { +/// &mut self.property_handle +/// } +/// } +/// ``` +pub trait BuiltProperty { + /// Type of [value][Value] of wrapped [property handle][PropertyHandle]. + type Value: Value; + + /// Return a reference to the wrapped [property handle][PropertyHandle]. + fn property_handle(&self) -> &PropertyHandle; + + /// Return a mutable reference to the wrapped [property handle][PropertyHandle]. + fn property_handle_mut(&mut self) -> &mut PropertyHandle; } #[cfg(test)] pub(crate) mod tests { - use crate::{property, Property, PropertyHandle}; + use crate::{ + property::{self, tests::MockProperty, BuiltProperty}, + Property, PropertyHandle, + }; use async_trait::async_trait; - use mockall::mock; - mock! { - pub PropertyHelper { - pub fn on_update(&self, value: T) -> Result<(), String>; - } - } - - pub struct MockProperty { + pub struct BuiltMockProperty { + data: MockProperty, property_handle: PropertyHandle, - pub property_helper: MockPropertyHelper, } - impl MockProperty { - pub fn new(property_handle: PropertyHandle) -> Self { - MockProperty { + impl BuiltMockProperty { + pub fn new(data: MockProperty, property_handle: PropertyHandle) -> Self { + Self { + data, property_handle, - property_helper: MockPropertyHelper::new(), } } } - #[async_trait] - impl Property for MockProperty { + impl BuiltProperty for BuiltMockProperty { type Value = T; - fn property_handle_mut(&mut self) -> &mut PropertyHandle { + + fn property_handle(&self) -> &PropertyHandle { + &self.property_handle + } + + fn property_handle_mut(&mut self) -> &mut PropertyHandle { &mut self.property_handle } + } + + impl std::ops::Deref for BuiltMockProperty { + type Target = MockProperty; + fn deref(&self) -> &Self::Target { + &self.data + } + } + + impl std::ops::DerefMut for BuiltMockProperty { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } + } + + #[async_trait] + impl Property for BuiltMockProperty { async fn on_update(&mut self, value: Self::Value) -> Result<(), String> { self.property_helper.on_update(value) } + + fn post_init(&mut self) { + if self.expect_post_init { + self.property_helper.post_init(); + } + } } } diff --git a/tests/codegen_visibility.rs b/tests/codegen_visibility.rs new file mode 100644 index 0000000..bb79f49 --- /dev/null +++ b/tests/codegen_visibility.rs @@ -0,0 +1,26 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/.* + */ + +use gateway_addon_rust::prelude::*; + +mod private_module { + use gateway_addon_rust::prelude::*; + + #[adapter] + pub struct TestAdapter; + + impl AdapterStructure for TestAdapter { + fn id(&self) -> String { + "test-adapter".to_owned() + } + + fn name(&self) -> String { + "Test Adapter".to_owned() + } + } +} + +impl Adapter for private_module::BuiltTestAdapter {}