trouble_host_macros/
lib.rs

1//! Procedural Macros for the `trouble_host` crate.
2//!
3//! This crate is enabled by the 'derive' feature of the `trouble_host` crate.
4//! It adds helper macros to simplify the creation of GATT services and servers.
5
6extern crate proc_macro;
7
8mod characteristic;
9mod ctxt;
10mod server;
11mod service;
12mod uuid;
13
14use characteristic::{Characteristic, CharacteristicArgs, DescriptorArgs};
15use ctxt::Ctxt;
16use proc_macro::TokenStream;
17use server::{ServerArgs, ServerBuilder};
18use service::{ServiceArgs, ServiceBuilder};
19use syn::spanned::Spanned;
20use syn::{parse_macro_input, Error};
21
22/// Gatt Service attribute macro.
23///
24///
25///
26/// # Example
27/// ```rust no_run
28/// use trouble_host::prelude::*;
29///
30/// #[gatt_server]
31/// struct MyGattServer {
32///     hrs: HeartRateService,
33///     bas: BatteryService,
34/// }
35///
36/// ```
37#[proc_macro_attribute]
38pub fn gatt_server(args: TokenStream, item: TokenStream) -> TokenStream {
39    let server_args = {
40        let mut attributes = ServerArgs::default();
41        let arg_parser = syn::meta::parser(|meta| attributes.parse(meta));
42
43        syn::parse_macro_input!(args with arg_parser);
44        attributes
45    };
46    let ctxt = Ctxt::new();
47    let server_properties = syn::parse_macro_input!(item as syn::ItemStruct);
48
49    let result = ServerBuilder::new(server_properties, server_args).build();
50
51    match ctxt.check() {
52        Ok(()) => result.into(),
53        Err(e) => e.into(),
54    }
55}
56
57/// Gatt Service attribute macro.
58///
59/// # Example
60///
61/// ```rust no_run
62/// use trouble_host::prelude::*;
63///
64/// const DESCRIPTOR_VALUE: &str = "Can be specified from a const";
65///
66/// #[gatt_service(uuid = "7e701cf1-b1df-42a1-bb5f-6a1028c793b0")]
67/// struct HeartRateService {
68///    /// Docstrings can be
69///    /// Multiple lines
70///    #[descriptor(uuid = "2a21", read, value = [0x00,0x01,0x02,0x03])]
71///    #[characteristic(uuid = characteristic::HEART_RATE_MEASUREMENT, read, notify, value = 3.14)]
72///    rate: f32,
73///    #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, read, value = DESCRIPTOR_VALUE)]
74///    #[characteristic(uuid = "2a28", read, write, notify, value = 42.0)]
75///    /// Can be in any order
76///    location: f32,
77///    #[characteristic(uuid = "2a39", write)]
78///    control: u8,
79///    #[characteristic(uuid = "2a63", read, notify)]
80///    energy_expended: u16,
81/// }
82/// ```
83#[proc_macro_attribute]
84pub fn gatt_service(args: TokenStream, item: TokenStream) -> TokenStream {
85    // Get arguments from the gatt_service macro attribute
86    let service_arguments = parse_macro_input!(args as ServiceArgs);
87
88    // Parse the contents of the struct
89    let mut service_props = parse_macro_input!(item as syn::ItemStruct);
90
91    let ctxt = Ctxt::new(); // error handling context, must be initialized after parse_macro_input calls.
92
93    let mut fields: Vec<syn::Field> = {
94        let struct_fields = match &mut service_props.fields {
95            syn::Fields::Named(n) => n,
96            _ => {
97                let s = service_props.ident;
98                ctxt.error_spanned_by(s, "gatt_service structs must have named fields, not tuples.");
99                return ctxt.check().unwrap_err().into();
100            }
101        };
102        struct_fields.named.iter().cloned().collect()
103    };
104
105    // Parse fields tagged as characteristics, remove them from the fields vec and store them in a separate vec.
106    let mut characteristics: Vec<Characteristic> = Vec::new();
107    let mut err: Option<syn::Error> = None;
108    fields.retain(|field| check_for_characteristic(field, &mut err, &mut characteristics));
109
110    // If there was an error parsing the characteristics, return the error
111    if let Some(err) = err {
112        let desc = err.to_string();
113        ctxt.error_spanned_by(
114            err.into_compile_error(),
115            format!("Parsing characteristics was unsuccessful:\n{}", desc),
116        );
117        return ctxt.check().unwrap_err().into();
118    }
119
120    // Build the service struct
121    let result = ServiceBuilder::new(service_props, service_arguments)
122        .process_characteristics_and_fields(fields, characteristics)
123        .build();
124
125    match ctxt.check() {
126        Ok(()) => result.into(),
127        Err(e) => e.into(),
128    }
129}
130
131/// Check if a field has a characteristic attribute and parse it.
132///
133/// If so also check if that field has descriptors and/or docstrings.
134///
135/// # Example
136///
137/// ```rust
138/// use trouble_host::prelude::*;
139///
140/// #[gatt_service(uuid = "180f")]
141/// struct BatteryService {
142///    /// Docstrings can be
143///    /// Multiple lines
144///    #[characteristic(uuid = "2a19", read, write, notify, value = 99)]
145///    #[descriptor(uuid = "2a20", read, value = [0x00,0x01,0x02,0x03])]
146///    #[descriptor(uuid = "2a20", read, value = "Demo description")]
147///    level: u8,
148///    #[descriptor(uuid = "2a21", read, value = VAL)]
149///    #[characteristic(uuid = "2a22", read, write, notify, value = 42.0)]
150///    /// Can be in any order
151///    rate_of_discharge: f32,
152///}
153/// ```
154fn check_for_characteristic(
155    field: &syn::Field,
156    err: &mut Option<syn::Error>,
157    characteristics: &mut Vec<Characteristic>,
158) -> bool {
159    const RETAIN: bool = true;
160    const REMOVE: bool = false;
161
162    let Some(attr) = field.attrs.iter().find(|attr| attr.path().is_ident("characteristic")) else {
163        return RETAIN; // If the field does not have a characteristic attribute, retain it.
164    };
165    let mut descriptors = Vec::new();
166    let mut doc_string = String::new();
167    let mut characteristic_checked = false;
168    for attr in &field.attrs {
169        if let Some(ident) = attr.path().get_ident() {
170            match ident.to_string().as_str() {
171                "doc" => {
172                    if let Ok(meta_name_value) = attr.meta.require_name_value() {
173                        if let syn::Expr::Lit(value) = &meta_name_value.value {
174                            if let Some(text) = &value.lit.span().source_text() {
175                                let text: Vec<&str> = text.split("///").collect();
176                                if let Some(text) = text.get(1) {
177                                    if !doc_string.is_empty() {
178                                        doc_string.push('\n');
179                                    }
180                                    doc_string.push_str(text);
181                                }
182                            }
183                        }
184                    }
185                }
186                "descriptor" => match DescriptorArgs::parse(attr) {
187                    Ok(args) => descriptors.push(args),
188                    Err(e) => {
189                        *err = Some(e);
190                        return REMOVE; // If there was an error parsing the descriptor, remove the field.
191                    }
192                },
193                "characteristic" => {
194                    // make sure we only have one characteristic meta tag
195                    if characteristic_checked {
196                        *err = Some(Error::new(
197                            attr.path().span(),
198                            "only one characteristic tag should be applied per field",
199                        ));
200                        return REMOVE; // If there was an error parsing the descriptor, remove the field.
201                    } else {
202                        characteristic_checked = true;
203                    }
204                }
205                "descriptors" => {
206                    *err = Some(Error::new(
207                        attr.path().span(),
208                        "specify a descriptor like: #[descriptor(uuid = \"1234\", value = \"Hello World\", read, write, notify)]\nCan be specified multiple times.",
209                    ));
210                    return REMOVE; // If there was an error parsing the descriptor, remove the field.
211                }
212                _ => {
213                    *err = Some(Error::new(
214                        attr.path().span(),
215                        "only doc (///), descriptor and characteristic tags are supported.",
216                    ));
217                    return REMOVE; // If there was an error parsing the descriptor, remove the field.
218                }
219            }
220        }
221    }
222    let mut args = match CharacteristicArgs::parse(attr) {
223        Ok(args) => args,
224        Err(e) => {
225            *err = Some(e);
226            return REMOVE; // If there was an error parsing the characteristic, remove the field.
227        }
228    };
229    args.doc_string = doc_string;
230    args.descriptors = descriptors;
231    characteristics.push(Characteristic::new(field, args));
232    REMOVE // Successfully parsed, remove the field from the fields vec.
233}