Skip to content

Commit

Permalink
Merge pull request #378 from joshuachp/feature/derive-unset
Browse files Browse the repository at this point in the history
feat(derive): add allow unset for Optional values
  • Loading branch information
harlem88 authored Sep 24, 2024
2 parents cb6c2ce + eaff0c9 commit 751fe3c
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 19 deletions.
2 changes: 2 additions & 0 deletions astarte-device-sdk-derive/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

- Implement the FromEvent derive macro for individual interfaces, via the `aggregation` attribute to
the macro [#375](https://github.com/astarte-platform/astarte-device-sdk-rust/pull/375)
- Add the `allow_unset` attribute to permit `Option` values for `Value::Unset`
[#378](https://github.com/astarte-platform/astarte-device-sdk-rust/pull/378)

## [0.8.4] - 2024-09-11

Expand Down
77 changes: 63 additions & 14 deletions astarte-device-sdk-derive/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use syn::{
};

use crate::{
case::RenameRule, parse_attribute_list, parse_name_value_attrs, parse_str_lit,
case::RenameRule, parse_attribute_list, parse_bool_lit, parse_name_value_attrs, parse_str_lit,
parse_struct_fields,
};

Expand Down Expand Up @@ -172,7 +172,7 @@ impl FromEventDerive {
impl #impl_generics astarte_device_sdk::FromEvent for #name #ty_generics #where_clause {
type Err = astarte_device_sdk::event::FromEventError;

fn from_event(event: astarte_device_sdk::DeviceEvent) -> Result<Self, Self::Err> {
fn from_event(event: astarte_device_sdk::DeviceEvent) -> ::std::result::Result<Self, Self::Err> {
use astarte_device_sdk::Value;
use astarte_device_sdk::event::FromEventError;
use astarte_device_sdk::interface::mapping::endpoint::Endpoint;
Expand Down Expand Up @@ -226,16 +226,57 @@ impl FromEventDerive {
let variants = variants.iter().enumerate().map(|(i, v)| {
let variant = &v.name;

quote! {
#i => individual.try_into().map(#name::#variant).map_err(FromEventError::from),
if v.attrs.allow_unset {
quote! {
#i => {
let individual = match event.data {
Value::Individual(individual) => individual,
Value::Unset => {
return Ok(#name::#variant(None));
},
Value::Object(_) => {
return Err(FromEventError::Object {
interface: INTERFACE,
endpoint: event.path,
});
}
};

individual.try_into()
.map(|value| #name::#variant(Some(value)))
.map_err(FromEventError::from)
}
}
} else {
quote! {
#i => {
let individual = match event.data {
Value::Individual(individual) => individual,
Value::Unset => {
return Err(FromEventError::Unset {
interface: INTERFACE,
endpoint: event.path,
});
},
Value::Object(_) => {
return Err(FromEventError::Object {
interface: INTERFACE,
endpoint: event.path,
});
}
};

individual.try_into().map(#name::#variant).map_err(FromEventError::from)
}
}
}
});

quote! {
impl #impl_generics astarte_device_sdk::FromEvent for #name #ty_generics #where_clause {
type Err = astarte_device_sdk::event::FromEventError;

fn from_event(event: astarte_device_sdk::DeviceEvent) -> Result<Self, Self::Err> {
fn from_event(event: astarte_device_sdk::DeviceEvent) -> ::std::result::Result<Self, Self::Err> {
use astarte_device_sdk::Value;
use astarte_device_sdk::AstarteType;
use astarte_device_sdk::event::FromEventError;
Expand All @@ -247,20 +288,13 @@ impl FromEventDerive {
return Err(FromEventError::Interface(event.interface));
}

let Value::Individual(individual) = event.data else {
return Err(FromEventError::Object {
interface: INTERFACE,
endpoint: event.path,
});
};

let endpoints = [ #(#endpoints),* ];

let position = endpoints.iter()
.position(|e| e.eq_mapping(&event.path))
.ok_or_else(|| FromEventError::Path {
interface: INTERFACE,
base_path: event.path,
base_path: event.path.clone(),
})?;

match position {
Expand Down Expand Up @@ -395,12 +429,18 @@ impl TryFrom<&Variant> for IndividualMapping {
/// enum Individual {
/// #[mapping(endpoint = "/sensor")]
/// Sensor(i32),
/// #[mapping(endpoint = "/temp", allow_unset = true)]
/// Temperature(Option<f64>),
/// }
/// ```
#[derive(Debug)]
struct MappingAttr {
/// Endpoint for the enum variant
endpoint: String,
/// Allow [`Option`]al values for properties.
///
/// Defaults to false as in the interfaces definition.
allow_unset: bool,
}

impl Parse for MappingAttr {
Expand All @@ -415,7 +455,16 @@ impl Parse for MappingAttr {
))
.and_then(|expr| parse_str_lit(&expr))?;

Ok(Self { endpoint })
let allow_unset = attrs
.get("allow_unset")
.map(parse_bool_lit)
.transpose()?
.unwrap_or_default();

Ok(Self {
endpoint,
allow_unset,
})
}
}

Expand Down
20 changes: 17 additions & 3 deletions astarte-device-sdk-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@ fn parse_str_lit(expr: &Expr) -> syn::Result<String> {
}
}

/// Parses a [`syn::Lit::Bool`] into a [`bool`].
fn parse_bool_lit(expr: &Expr) -> syn::Result<bool> {
match expr {
Expr::Lit(syn::ExprLit {
lit: syn::Lit::Bool(lit),
..
}) => Ok(lit.value()),
_ => Err(syn::Error::new(
expr.span(),
"expression must be a bool literal",
)),
}
}

/// Handle for the `#[derive(AstarteAggregate)]` derive macro.
///
/// ### Example
Expand Down Expand Up @@ -155,7 +169,7 @@ impl AggregateDerive {
impl #impl_generics astarte_device_sdk::AstarteAggregate for #name #ty_generics #where_clause {
fn astarte_aggregate(
self,
) -> Result<
) -> ::std::result::Result<
std::collections::HashMap<String, astarte_device_sdk::types::AstarteType>,
astarte_device_sdk::error::Error,
> {
Expand Down Expand Up @@ -305,8 +319,8 @@ pub fn astarte_aggregate_derive(input: TokenStream) -> TokenStream {
/// enum Sensor {
/// #[mapping(endpoint = "/sensor/luminosity")]
/// Luminosity(i32),
/// #[mapping(endpoint = "/sensor/temerature")]
/// Temperature(f32),
/// #[mapping(endpoint = "/sensor/temerature", allow_unset = true)]
/// Temperature(Option<f64>),
/// }
/// ```
#[proc_macro_derive(FromEvent, attributes(from_event, mapping))]
Expand Down
38 changes: 36 additions & 2 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ pub enum FromEventError {
/// couldn't parse request from interface
#[error("couldn't parse request from interface {0}")]
Interface(String),
/// object has wrong base path
#[error("object {interface} has wrong base path {base_path}")]
/// couldn't parse the event path
#[error("the interface {interface} has wrong path {base_path}")]
Path {
/// Interface that generated the error
interface: &'static str,
Expand All @@ -65,6 +65,14 @@ pub enum FromEventError {
/// endpoint
endpoint: String,
},
/// unset passed to endpoint without allow unset
#[error("unset passed to {interface}{endpoint} without allow unset")]
Unset {
/// Interface that generated the error
interface: &'static str,
/// endpoint
endpoint: String,
},
/// object missing field
#[error("object {interface} missing field {base_path}/{path}")]
MissingField {
Expand Down Expand Up @@ -207,6 +215,8 @@ mod tests {
Luminosity(i32),
#[mapping(endpoint = "/sensor/temperature")]
Temperature(f64),
#[mapping(endpoint = "/sensor/unsettable", allow_unset = true)]
Unsettable(Option<bool>),
}

let event = DeviceEvent {
Expand All @@ -232,5 +242,29 @@ mod tests {
let expected = Sensor::Temperature(3.);

assert_eq!(temperature, expected);

let event = DeviceEvent {
interface: "com.example.Sensor".to_string(),
path: "/sensor/unsettable".to_string(),
data: Value::Individual(AstarteType::Boolean(true)),
};

let temperature = Sensor::from_event(event).expect("couldn't parse the event");

let expected = Sensor::Unsettable(Some(true));

assert_eq!(temperature, expected);

let event = DeviceEvent {
interface: "com.example.Sensor".to_string(),
path: "/sensor/unsettable".to_string(),
data: Value::Unset,
};

let temperature = Sensor::from_event(event).expect("couldn't parse the event");

let expected = Sensor::Unsettable(None);

assert_eq!(temperature, expected);
}
}

0 comments on commit 751fe3c

Please sign in to comment.