Skip to main content

astarte_device_sdk_derive/
lib.rs

1// This file is part of Astarte.
2//
3// Copyright 2023-2026 SECO Mind Srl
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//    http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17// SPDX-License-Identifier: Apache-2.0
18
19//! Proc macro helpers for the [Astarte Device SDK](https://crates.io/crates/astarte-device-sdk)
20
21use std::fmt::Debug;
22
23use darling::{FromDeriveInput, FromField};
24use proc_macro::TokenStream;
25
26use quote::{quote, quote_spanned};
27use syn::{GenericParam, Generics, parse_macro_input, parse_quote};
28
29use crate::{case::RenameRule, event::FromEventDerive};
30
31mod case;
32mod event;
33
34/// Handle for the `#[derive(IntoAstarteObject)]` derive macro.
35///
36/// Uses the `#[astarte_object(..)]` attribute for configuration.
37///
38/// ### Example
39///
40/// ```no_compile
41/// #[derive(IntoAstarteObject)]
42/// #[astarte_object(rename_all = "camelCase")]
43/// struct Foo {
44///     #[astarte_object(rename = "some")]
45///     bar: String
46/// }
47/// ```
48#[derive(Debug, FromDeriveInput)]
49#[darling(attributes(astarte_object), supports(struct_named))]
50struct ObjectDerive {
51    /// Rename the fields in the resulting HashMap, see the [`RenameRule`] variants.
52    #[darling(default)]
53    rename_all: Option<RenameRule>,
54    /// name of the struct
55    ident: syn::Ident,
56    /// Generic bounds
57    generics: syn::Generics,
58    /// fields
59    data: darling::ast::Data<(), ObjectField>,
60}
61
62impl ObjectDerive {
63    fn quote(&self) -> darling::Result<proc_macro2::TokenStream> {
64        let st_name = &self.ident;
65
66        let Some(fields) = self.data.as_ref().take_struct() else {
67            return Err(darling::Error::unsupported_shape_with_expected(
68                "enum",
69                &"object must be astruct",
70            )
71            .with_span(&self.ident));
72        };
73
74        let mut errors = darling::Error::accumulator();
75
76        let capacity = fields.len();
77        let fields = fields.iter()
78            .filter_map(|field| {
79            errors.handle_in(||
80                field.field_name(self.rename_all).ok_or_else(||
81                darling::Error::custom( "missing struct fields")
82                    .with_span(&self.ident)
83            ))
84        }).map(|(field_i, field_n)| {
85            quote_spanned! {field_i.span() =>
86                // TODO *Temporarily* ignore this new lint will be fixed in a new pr
87                #[allow(unknown_lints)]
88                #[allow(clippy::unnecessary_fallible_conversions)]
89                let v: astarte_device_sdk::types::AstarteData = ::std::convert::TryInto::try_into(value.#field_i)?;
90                object.insert(#field_n.to_string(), v);
91            }
92        }).collect::<Vec<_>>();
93
94        errors.finish()?;
95
96        let generics = Self::add_trait_bound(&self.generics);
97        let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
98
99        Ok(quote! {
100            impl #impl_generics ::std::convert::TryFrom<#st_name #ty_generics> for astarte_device_sdk::aggregate::AstarteObject #where_clause {
101                type Error = astarte_device_sdk::error::Error;
102
103                fn try_from(value: #st_name #ty_generics) -> ::std::result::Result<Self, Self::Error> {
104                    let mut object = Self::with_capacity(#capacity);
105                    #(#fields)*
106                    Ok(object)
107                }
108            }
109        })
110    }
111
112    pub fn add_trait_bound(generics: &Generics) -> Generics {
113        let mut generics = generics.clone();
114
115        for param in &mut generics.params {
116            if let GenericParam::Type(type_param) = param {
117                type_param.bounds.push(parse_quote!(
118                    std::convert::TryInto<astarte_device_sdk::types::AstarteData, Error = astarte_device_sdk::error::Error>
119                ));
120            }
121        }
122
123        generics
124    }
125}
126
127#[derive(Debug, FromField)]
128#[darling(attributes(astarte_object))]
129struct ObjectField {
130    /// Rename the fields to the given value
131    #[darling(default)]
132    rename: Option<String>,
133    /// Name of the field
134    ident: Option<syn::Ident>,
135}
136
137impl ObjectField {
138    fn field_name(&self, rename_rule: Option<RenameRule>) -> Option<(&syn::Ident, String)> {
139        self.ident.as_ref().map(|i| {
140            let mut name = i.to_string();
141
142            if let Some(rename) = &self.rename {
143                name = rename.clone();
144            } else if let Some(rename_rule) = rename_rule {
145                name = rename_rule.apply_to_field(&name);
146            }
147
148            (i, name)
149        })
150    }
151}
152
153/// Derive macro `#[derive(IntoAstarteObject)]` to implement IntoAstarteObject.
154///
155/// ### Example
156///
157/// ```no_compile
158/// #[derive(IntoAstarteObject)]
159/// struct Foo {
160///     bar: String
161/// }
162/// ```
163#[proc_macro_derive(IntoAstarteObject, attributes(astarte_object))]
164pub fn astarte_aggregate_derive(input: TokenStream) -> TokenStream {
165    let input = parse_macro_input!(input as syn::DeriveInput);
166
167    match ObjectDerive::from_derive_input(&input).and_then(|obj| obj.quote()) {
168        Ok(t) => t.into(),
169        Err(error) => error.write_errors().into(),
170    }
171}
172
173/// Derive macro `#[derive(FromEvent)]` to implement the FromEvent trait.
174///
175/// ### Example
176///
177/// To derive the trait for an individual.
178///
179/// ```no_compile
180/// #[derive(FromEvent)]
181/// #[from_event(interface = "com.example.Sensor")]
182/// enum Sensor {
183///     #[mapping(endpoint = "/sensor/luminosity")]
184///     Luminosity(i32),
185///     #[mapping(endpoint = "/sensor/temerature")]
186///     Temperature(Option<f64>),
187/// }
188/// ```
189///
190/// To derive the trait it for an object.
191///
192/// ```no_compile
193/// #[derive(FromEvent)]
194/// #[from_event(interface = "com.example.Foo", path = "/obj", aggregation = "object")]
195/// struct Foo {
196///     bar: String
197/// }
198/// ```
199///
200///
201/// To derive the trait it for a property.
202///
203/// ```no_compile
204/// #[derive(FromEvent)]
205/// #[from_event(interface = "com.example.Sensor", interface_type = "property", aggregation = "individual")]
206/// enum Sensor {
207///     #[mapping(endpoint = "/sensor/luminosity")]
208///     Luminosity(i32),
209///     #[mapping(endpoint = "/sensor/temerature")]
210///     Temperature(Option<f64>),
211/// }
212/// ```
213#[proc_macro_derive(FromEvent, attributes(from_event, mapping))]
214pub fn from_event_derive(input: TokenStream) -> TokenStream {
215    let input = parse_macro_input!(input as syn::DeriveInput);
216
217    let res = FromEventDerive::from_derive_input(&input).and_then(|from_event| from_event.quote());
218
219    // Build the trait implementation
220    match res {
221        Ok(t) => t.into(),
222        Err(err) => err.write_errors().into(),
223    }
224}