Skip to content

Commit d408809

Browse files
authored
Reduce boilerplate code with macros (#61)
1 parent 797f852 commit d408809

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2340
-593
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jsonschema = "0.12.1"
1919
chrono = "0.4.19"
2020
as-any = "0.2.0"
2121
mockall_double = "0.2.0"
22+
gateway-addon-rust-codegen = { path = "gateway-addon-rust-codegen" }
2223

2324
[dependencies.serde]
2425
version = "1.0"

gateway-addon-rust-codegen/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/target
2+
Cargo.lock
3+
/.idea

gateway-addon-rust-codegen/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "gateway-addon-rust-codegen"
3+
version = "1.0.0-alpha.1"
4+
edition = "2018"
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
syn = { version = "1.0", features = ["full"] }
11+
quote = "1.0"
12+
proc-macro2 = "1.0"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
imports_granularity = "Crate"
2+
format_code_in_doc_comments = true

gateway-addon-rust-codegen/src/lib.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use proc_macro::TokenStream;
2+
use proc_macro2::TokenStream as TokenStream2;
3+
use quote::quote;
4+
use std::str::FromStr;
5+
use syn::DeriveInput;
6+
7+
#[proc_macro_attribute]
8+
pub fn adapter(_args: TokenStream, input: TokenStream) -> TokenStream {
9+
apply_macro(input, "adapter", "Adapter", None)
10+
}
11+
12+
#[proc_macro_attribute]
13+
pub fn device(_args: TokenStream, input: TokenStream) -> TokenStream {
14+
apply_macro(input, "device", "Device", None)
15+
}
16+
17+
#[proc_macro_attribute]
18+
pub fn property(_args: TokenStream, input: TokenStream) -> TokenStream {
19+
apply_macro(input, "property", "Property", Some("Value"))
20+
}
21+
22+
#[proc_macro_attribute]
23+
pub fn event(_args: TokenStream, input: TokenStream) -> TokenStream {
24+
apply_macro(input, "event", "Event", Some("Data"))
25+
}
26+
27+
#[proc_macro_attribute]
28+
pub fn api_handler(_args: TokenStream, input: TokenStream) -> TokenStream {
29+
apply_macro(input, "api_handler", "ApiHandler", None)
30+
}
31+
32+
fn apply_macro(
33+
input: TokenStream,
34+
name_snail_case: &str,
35+
name_camel_case: &str,
36+
generic_name: Option<&str>,
37+
) -> TokenStream {
38+
if let Ok(ast) = syn::parse2::<DeriveInput>(input.into()) {
39+
alter_struct(ast, name_snail_case, name_camel_case, generic_name).into()
40+
} else {
41+
panic!("`{}` has to be used with structs", name_snail_case)
42+
}
43+
}
44+
45+
fn alter_struct(
46+
ast: DeriveInput,
47+
name_snail_case: &str,
48+
name_camel_case: &str,
49+
generic_name: Option<&str>,
50+
) -> TokenStream2 {
51+
let struct_name = ast.ident.clone();
52+
let visibility = ast.vis.clone();
53+
let struct_built_name = TokenStream2::from_str(&format!("Built{}", struct_name)).unwrap();
54+
55+
let trait_handle_wrapper = TokenStream2::from_str(&format!(
56+
"gateway_addon_rust::{}::Built{}",
57+
name_snail_case, name_camel_case
58+
))
59+
.unwrap();
60+
let trait_build = TokenStream2::from_str(&format!(
61+
"gateway_addon_rust::{}::{}Builder",
62+
name_snail_case, name_camel_case
63+
))
64+
.unwrap();
65+
let struct_built = TokenStream2::from_str(&format!("Built{}", name_camel_case)).unwrap();
66+
let struct_handle = TokenStream2::from_str(&if let Some(generic_name) = generic_name {
67+
format!(
68+
"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}>",
69+
name_snail_case = name_snail_case,
70+
name_camel_case = name_camel_case,
71+
struct_name = struct_name,
72+
generic_name = generic_name,
73+
)
74+
} else {
75+
format!(
76+
"gateway_addon_rust::{}::{}Handle",
77+
name_snail_case, name_camel_case
78+
)
79+
})
80+
.unwrap();
81+
let fn_handle = TokenStream2::from_str(&format!("{}_handle", name_snail_case)).unwrap();
82+
let fn_handle_mut = TokenStream2::from_str(&format!("{}_handle_mut", name_snail_case)).unwrap();
83+
let typedef = TokenStream2::from_str(&if let Some(generic_name) = generic_name {
84+
format!(
85+
"type {generic_name} = <{struct_name} as gateway_addon_rust::{name_snail_case}::{name_camel_case}Structure>::{generic_name};",
86+
name_snail_case = name_snail_case,
87+
name_camel_case = name_camel_case,
88+
struct_name = struct_name,
89+
generic_name = generic_name,
90+
)
91+
} else {
92+
"".to_owned()
93+
})
94+
.unwrap();
95+
96+
quote! {
97+
#ast
98+
impl #trait_build for #struct_name {
99+
type #struct_built = #struct_built_name;
100+
fn build(data: Self, #fn_handle: #struct_handle) -> Self::#struct_built {
101+
#struct_built_name { data, #fn_handle }
102+
}
103+
}
104+
#visibility struct #struct_built_name {
105+
data: #struct_name,
106+
#fn_handle: #struct_handle,
107+
}
108+
impl #trait_handle_wrapper for #struct_built_name {
109+
#typedef
110+
fn #fn_handle(&self) -> &#struct_handle {
111+
&self.#fn_handle
112+
}
113+
fn #fn_handle_mut(&mut self) -> &mut #struct_handle {
114+
&mut self.#fn_handle
115+
}
116+
}
117+
impl std::ops::Deref for #struct_built_name {
118+
type Target = #struct_name;
119+
fn deref(&self) -> &Self::Target {
120+
&self.data
121+
}
122+
}
123+
impl std::ops::DerefMut for #struct_built_name {
124+
fn deref_mut(&mut self) -> &mut Self::Target {
125+
&mut self.data
126+
}
127+
}
128+
}
129+
}

src/action/action_description.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,14 @@ impl<T: Input> ActionDescription<T> {
6565
}
6666

6767
/// Set `@type`.
68+
#[must_use]
6869
pub fn at_type(mut self, at_type: AtType) -> Self {
6970
self.at_type = Some(at_type);
7071
self
7172
}
7273

7374
/// Set `description`.
75+
#[must_use]
7476
pub fn description(mut self, description: impl Into<String>) -> Self {
7577
self.description = Some(description.into());
7678
self
@@ -88,12 +90,14 @@ impl<T: Input> ActionDescription<T> {
8890
/// }))
8991
/// # ;
9092
/// ```
93+
#[must_use]
9194
pub fn input(mut self, input: serde_json::Value) -> Self {
9295
self.input = Some(input);
9396
self
9497
}
9598

9699
/// Set `links`.
100+
#[must_use]
97101
pub fn links(mut self, links: Vec<Link>) -> Self {
98102
self.links = Some(links);
99103
self
@@ -119,6 +123,7 @@ impl<T: Input> ActionDescription<T> {
119123
/// })
120124
/// # ;
121125
/// ```
126+
#[must_use]
122127
pub fn link(mut self, link: Link) -> Self {
123128
match self.links {
124129
None => self.links = Some(vec![link]),
@@ -128,6 +133,7 @@ impl<T: Input> ActionDescription<T> {
128133
}
129134

130135
/// Set `title`.
136+
#[must_use]
131137
pub fn title(mut self, title: impl Into<String>) -> Self {
132138
self.title = Some(title.into());
133139
self

src/action/action_input.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,11 @@ impl Input for NoInput {
131131
}
132132

133133
fn deserialize(value: serde_json::Value) -> Result<Self, WebthingsError> {
134-
if value == serde_json::Value::Null {
134+
if value == json!(null) || value == json!({}) {
135135
Ok(NoInput)
136136
} else {
137137
Err(WebthingsError::Serialization(serde_json::Error::custom(
138-
"Expected no input",
138+
format!("Expected no input, got {:?}", value),
139139
)))
140140
}
141141
}

src/action/action_trait.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ pub trait Action: Send + Sync + 'static {
6868
Err("Action does not implement canceling".to_owned())
6969
}
7070

71+
/// Called once after initialization.
72+
fn post_init(&mut self) {}
73+
7174
#[doc(hidden)]
7275
fn full_description(&self) -> FullActionDescription {
7376
self.description().into_full_description()
@@ -134,6 +137,9 @@ pub trait ActionBase: Send + Sync + AsAny + 'static {
134137

135138
#[doc(hidden)]
136139
async fn cancel(&mut self, action_id: String) -> Result<(), String>;
140+
141+
#[doc(hidden)]
142+
fn post_init(&mut self) {}
137143
}
138144

139145
impl Downcast for dyn ActionBase {}
@@ -158,10 +164,16 @@ impl<T: Action> ActionBase for T {
158164
async fn cancel(&mut self, action_id: String) -> Result<(), String> {
159165
<T as Action>::cancel(self, action_id).await
160166
}
167+
168+
fn post_init(&mut self) {
169+
<T as Action>::post_init(self)
170+
}
161171
}
162172

163173
#[cfg(test)]
164174
pub(crate) mod tests {
175+
use std::ops::{Deref, DerefMut};
176+
165177
use crate::{action::Input, Action, ActionDescription, ActionHandle};
166178
use async_trait::async_trait;
167179
use mockall::mock;
@@ -170,23 +182,40 @@ pub(crate) mod tests {
170182
pub ActionHelper<T: Input> {
171183
pub fn perform(&mut self, action_handle: ActionHandle<T>) -> Result<(), String>;
172184
pub fn cancel(&mut self, action_id: String) -> Result<(), String>;
185+
pub fn post_init(&mut self);
173186
}
174187
}
175188

176189
pub struct MockAction<T: Input> {
177190
action_name: String,
178191
pub action_helper: MockActionHelper<T>,
192+
pub expect_post_init: bool,
179193
}
180194

181195
impl<T: Input> MockAction<T> {
182196
pub fn new(action_name: String) -> Self {
183197
Self {
184198
action_name,
199+
expect_post_init: false,
185200
action_helper: MockActionHelper::new(),
186201
}
187202
}
188203
}
189204

205+
impl<T: Input> Deref for MockAction<T> {
206+
type Target = MockActionHelper<T>;
207+
208+
fn deref(&self) -> &Self::Target {
209+
&self.action_helper
210+
}
211+
}
212+
213+
impl<T: Input> DerefMut for MockAction<T> {
214+
fn deref_mut(&mut self) -> &mut Self::Target {
215+
&mut self.action_helper
216+
}
217+
}
218+
190219
#[async_trait]
191220
impl<T: Input> Action for MockAction<T> {
192221
type Input = T;
@@ -210,5 +239,11 @@ pub(crate) mod tests {
210239
async fn cancel(&mut self, action_id: String) -> Result<(), String> {
211240
self.action_helper.cancel(action_id)
212241
}
242+
243+
fn post_init(&mut self) {
244+
if self.expect_post_init {
245+
self.action_helper.post_init();
246+
}
247+
}
213248
}
214249
}

0 commit comments

Comments
 (0)