Skip to content

Commit 9b522ce

Browse files
committed
refactor!(validation): make it stricter to reflect the new API
Check the interface type, ownership and aggregation in the user validation. Return an hard error if one is incorrect. Signed-off-by: Joshua Chapman <joshua.chapman@secomind.com>
1 parent e03fd0d commit 9b522ce

File tree

9 files changed

+374
-182
lines changed

9 files changed

+374
-182
lines changed

src/client.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ use tokio::{
2626
};
2727
use tracing::{debug, error, trace};
2828

29-
use crate::{aggregate::AstarteObject, interface::mapping::path::MappingError};
29+
use crate::{
30+
aggregate::AstarteObject, error::InterfaceTypeError, interface::mapping::path::MappingError,
31+
validate::UserValidationError,
32+
};
3033
use crate::{
3134
connection::ClientMessage,
3235
event::DeviceEvent,
@@ -43,7 +46,7 @@ use crate::{
4346
Error, Interface,
4447
};
4548
use crate::{
46-
error::{AggregateError, DynError},
49+
error::{AggregationError, DynError},
4750
store::PropertyMapping,
4851
};
4952

@@ -79,7 +82,7 @@ pub enum RecvError {
7982

8083
/// Invalid aggregation between the interface and the data.
8184
#[error(transparent)]
82-
Aggregation(#[from] AggregateError),
85+
Aggregation(#[from] AggregationError),
8386

8487
/// Error when the Device is disconnected from Astarte or client.
8588
///
@@ -365,7 +368,7 @@ impl<S> DeviceClient<S> {
365368
return Ok(());
366369
}
367370

368-
let validated = ValidatedIndividual::validate(mapping, path, individual, timestamp)?;
371+
let validated = ValidatedIndividual::validate(mapping, individual, timestamp)?;
369372

370373
let msg = if mapping.interface().is_property() {
371374
ClientMessage::Property {
@@ -394,8 +397,8 @@ impl<S> DeviceClient<S> {
394397
})?;
395398

396399
let object = interface.as_object_ref().ok_or_else(|| {
397-
let aggr_err = AggregateError::for_interface(
398-
interface_name,
400+
let aggr_err = AggregationError::new(
401+
interface_name.to_string(),
399402
path.to_string(),
400403
InterfaceAggregation::Object,
401404
interface.aggregation(),
@@ -428,12 +431,15 @@ impl<S> DeviceClient<S> {
428431
mapping: path.to_string(),
429432
})?;
430433

431-
let mapping = mapping.as_prop().ok_or_else(|| Error::InterfaceType {
432-
exp: InterfaceTypeDef::Properties,
433-
got: interface.interface_type(),
434+
let mapping = mapping.as_prop().ok_or_else(|| {
435+
Error::Validation(UserValidationError::InterfaceType(InterfaceTypeError::new(
436+
interface.interface_name(),
437+
InterfaceTypeDef::Properties,
438+
interface.interface_type(),
439+
)))
434440
})?;
435441

436-
let validated = ValidatedUnset::validate(mapping, path)?;
442+
let validated = ValidatedUnset::validate(mapping)?;
437443

438444
debug!("unsetting property {interface_name}{path}");
439445

src/connection.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use tokio::{
3232
};
3333
use tracing::{debug, error, info, trace, warn};
3434

35-
use crate::error::AggregateError;
35+
use crate::error::AggregationError;
3636
use crate::store::PropertyMapping;
3737
use crate::transport::TransportError;
3838
use crate::{
@@ -929,8 +929,8 @@ impl<S, C> DeviceReceiver<S, C> {
929929
C: Receive + Sync,
930930
{
931931
let Some(object) = interface.as_object_ref() else {
932-
let aggr_err = AggregateError::for_interface(
933-
interface.interface_name(),
932+
let aggr_err = AggregationError::new(
933+
interface.interface_name().to_string(),
934934
path.to_string(),
935935
InterfaceAggregation::Object,
936936
InterfaceAggregation::Individual,

src/error.rs

Lines changed: 43 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
2121
use crate::interface::error::InterfaceError;
2222
use crate::interface::mapping::path::MappingError;
23-
use crate::interface::{Aggregation, InterfaceTypeDef};
23+
use crate::interface::{Aggregation, InterfaceTypeDef, Ownership};
2424
use crate::introspection::AddInterfaceError;
2525
use crate::properties::PropertiesError;
2626
use crate::retention::RetentionError;
@@ -49,14 +49,6 @@ pub enum Error {
4949
/// Error while operating on the device introspection.
5050
#[error("couldn't complete introspection operation")]
5151
AddInterface(#[from] AddInterfaceError),
52-
/// Invalid interface type when sending or receiving.
53-
#[error("invalid interface type, expected {exp} but got {got}")]
54-
InterfaceType {
55-
/// Expected interface type.
56-
exp: InterfaceTypeDef,
57-
/// Actual interface type.
58-
got: InterfaceTypeDef,
59-
},
6052
/// Couldn't find an interface with the given name.
6153
#[error("couldn't find interface '{name}'")]
6254
InterfaceNotFound {
@@ -88,7 +80,7 @@ pub enum Error {
8880
Validation(#[from] UserValidationError),
8981
/// Invalid aggregation between the interface and the data.
9082
#[error(transparent)]
91-
Aggregation(#[from] AggregateError),
83+
Aggregation(#[from] AggregationError),
9284
/// Infallible conversion.
9385
#[error(transparent)]
9486
Infallible(#[from] Infallible),
@@ -110,88 +102,78 @@ pub enum Error {
110102
Grpc(#[from] crate::transport::grpc::GrpcError),
111103
}
112104

113-
/// Aggregate error variant.
105+
/// Aggregation error.
114106
///
115107
/// This provides additional context in case of an aggregation error
116108
#[derive(Debug, thiserror::Error)]
117-
#[error("invalid aggregation in {ctx} for {interface}{path}, expected {exp} but got {got}")]
109+
#[error("invalid aggregation for {interface}{path}, expected {exp} but got {got}")]
118110
#[non_exhaustive]
119-
pub struct AggregateError {
111+
pub struct AggregationError {
120112
/// Interface name
121113
interface: String,
122114
/// Path
123115
path: String,
124-
/// Context to differentiate interface from payload error
125-
ctx: AggregationCtx,
126116
/// Expected aggregation of the interface.
127117
exp: Aggregation,
128118
/// The actual aggregation.
129119
got: Aggregation,
130120
}
131121

132-
impl AggregateError {
133-
pub(crate) fn new(
134-
interface: String,
135-
path: String,
136-
ctx: AggregationCtx,
137-
exp: Aggregation,
138-
got: Aggregation,
139-
) -> Self {
122+
impl AggregationError {
123+
pub(crate) fn new(interface: String, path: String, exp: Aggregation, got: Aggregation) -> Self {
140124
Self {
141125
interface,
142126
path,
143-
ctx,
144127
exp,
145128
got,
146129
}
147130
}
131+
}
148132

149-
pub(crate) fn for_interface(
150-
interface: impl Into<String>,
151-
path: impl Into<String>,
152-
exp: Aggregation,
153-
got: Aggregation,
154-
) -> Self {
155-
Self::new(
156-
interface.into(),
157-
path.into(),
158-
AggregationCtx::Interface,
159-
exp,
160-
got,
161-
)
162-
}
133+
/// Invalid interface type when sending or receiving.
134+
#[derive(Debug, thiserror::Error)]
135+
#[error("invalid interface type for {name}, expected {exp} but got {got}")]
136+
pub struct InterfaceTypeError {
137+
/// Name of the interface.
138+
name: String,
139+
/// Expected interface type.
140+
exp: InterfaceTypeDef,
141+
/// Actual interface type.
142+
got: InterfaceTypeDef,
143+
}
163144

164-
#[cfg(feature = "message-hub")]
165-
pub(crate) fn for_payload(
166-
interface: impl Into<String>,
167-
path: impl Into<String>,
168-
exp: Aggregation,
169-
got: Aggregation,
145+
impl InterfaceTypeError {
146+
pub(crate) fn new(
147+
name: impl Into<String>,
148+
exp: InterfaceTypeDef,
149+
got: InterfaceTypeDef,
170150
) -> Self {
171-
Self::new(
172-
interface.into(),
173-
path.into(),
174-
AggregationCtx::Payload,
151+
Self {
152+
name: name.into(),
175153
exp,
176154
got,
177-
)
155+
}
178156
}
179157
}
180158

181-
/// Context for the [AggregateError] error variant.
182-
#[derive(Debug, Clone)]
183-
pub enum AggregationCtx {
184-
/// Error occurring when checking the interface.
185-
Interface,
186-
/// Error occurring when checking the payload data.
187-
Payload,
159+
/// Sending data on an interface not owned by the device
160+
#[derive(Debug, thiserror::Error)]
161+
#[error("invalid ownership for interface {name}, expected {exp} but got {got}")]
162+
pub struct OwnershipError {
163+
/// Name of the interface.
164+
name: String,
165+
/// Expected interface ownership.
166+
exp: Ownership,
167+
/// Actual interface ownership.
168+
got: Ownership,
188169
}
189170

190-
impl Display for AggregationCtx {
191-
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
192-
match self {
193-
AggregationCtx::Interface => write!(f, "interface"),
194-
AggregationCtx::Payload => write!(f, "payload"),
171+
impl OwnershipError {
172+
pub(crate) fn new(name: impl Into<String>, exp: Ownership, got: Ownership) -> Self {
173+
Self {
174+
name: name.into(),
175+
exp,
176+
got,
195177
}
196178
}
197179
}

src/interfaces.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ use itertools::Itertools;
2727
use tracing::{debug, trace};
2828

2929
use crate::{
30+
error::InterfaceTypeError,
3031
interface::{
3132
error::InterfaceError,
3233
mapping::path::MappingPath,
3334
reference::{MappingRef, PropertyRef},
35+
InterfaceTypeDef,
3436
},
37+
validate::UserValidationError,
3538
Error, Interface,
3639
};
3740

@@ -162,9 +165,12 @@ impl Interfaces {
162165
name: interface_name.to_string(),
163166
})
164167
.and_then(|interface| {
165-
interface.as_prop_ref().ok_or_else(|| Error::InterfaceType {
166-
exp: crate::interface::InterfaceTypeDef::Properties,
167-
got: interface.interface_type(),
168+
interface.as_prop_ref().ok_or_else(|| {
169+
Error::Validation(UserValidationError::InterfaceType(InterfaceTypeError::new(
170+
interface_name,
171+
InterfaceTypeDef::Properties,
172+
interface.interface_type(),
173+
)))
168174
})
169175
})
170176
.and_then(|interface| {

src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,19 @@ mod test {
115115
"../e2e-test/interfaces/additional/org.astarte-platform.rust.e2etest.DeviceProperty.json"
116116
);
117117

118+
pub(crate) const DEVICE_PROPERTIES_NO_UNSET: &str = r#"{
119+
"interface_name": "org.astarte-platform.rust.examples.individual-properties.DevicePropertyNoUnset",
120+
"version_major": 0,
121+
"version_minor": 1,
122+
"type": "properties",
123+
"ownership": "device",
124+
"mappings": [{
125+
"endpoint": "/%{sensor_id}/enable",
126+
"type": "boolean",
127+
"allow_unset": false
128+
}]
129+
}"#;
130+
118131
pub(crate) async fn mock_astarte_device<I>(
119132
client: AsyncClient,
120133
eventloop: MqttEventLoop,

src/transport/grpc/mod.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ use super::{
5656
use crate::aggregate::AstarteObject;
5757
use crate::builder::ConnectionBuildConfig;
5858
use crate::client::RecvError;
59-
use crate::error::AggregateError;
59+
use crate::error::AggregationError;
6060
use crate::interface::Aggregation;
6161
use crate::retention::memory::SharedVolatileStore;
6262
use crate::retention::{PublishInfo, RetentionId};
@@ -442,8 +442,8 @@ where
442442
};
443443

444444
let individual = data.take_individual().ok_or_else(|| {
445-
let aggr_err = AggregateError::for_payload(
446-
mapping.interface().interface_name(),
445+
let aggr_err = AggregationError::new(
446+
mapping.interface().interface_name().to_string(),
447447
mapping.path().to_string(),
448448
Aggregation::Individual,
449449
Aggregation::Object,
@@ -469,8 +469,8 @@ where
469469
.take_data()
470470
.and_then(|d| d.take_object())
471471
.ok_or_else(|| {
472-
RecvError::Aggregation(AggregateError::for_payload(
473-
object.interface.interface_name(),
472+
RecvError::Aggregation(AggregationError::new(
473+
object.interface.interface_name().to_string(),
474474
path.to_string(),
475475
Aggregation::Object,
476476
Aggregation::Individual,
@@ -1203,7 +1203,6 @@ mod test {
12031203

12041204
let validated_individual = mock_validate_individual(
12051205
mapping_ref,
1206-
&path,
12071206
AstarteType::String(STRING_VALUE.to_string()),
12081207
None,
12091208
)

src/transport/mod.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ pub trait Disconnect {
216216
#[cfg(test)]
217217
mod test {
218218
use crate::aggregate::AstarteObject;
219-
use crate::error::AggregateError;
219+
use crate::error::AggregationError;
220220
use crate::{
221221
interface::{mapping::path::MappingPath, reference::MappingRef},
222222
types::{AstarteType, TypeError},
@@ -231,8 +231,8 @@ mod test {
231231
timestamp: Option<chrono::DateTime<chrono::Utc>>,
232232
) -> Result<ValidatedObject, crate::Error> {
233233
let object = interface.as_object_ref().ok_or_else(|| {
234-
let aggr_err = AggregateError::for_interface(
235-
interface.interface_name(),
234+
let aggr_err = AggregationError::new(
235+
interface.interface_name().to_string(),
236236
path.to_string(),
237237
crate::interface::Aggregation::Object,
238238
interface.aggregation(),
@@ -245,7 +245,6 @@ mod test {
245245

246246
pub(crate) fn mock_validate_individual<'a, D>(
247247
mapping_ref: MappingRef<'a, &'a Interface>,
248-
path: &'a MappingPath<'a>,
249248
data: D,
250249
timestamp: Option<chrono::DateTime<chrono::Utc>>,
251250
) -> Result<ValidatedIndividual, crate::Error>
@@ -254,7 +253,6 @@ mod test {
254253
{
255254
let individual = data.try_into().map_err(|_| TypeError::Conversion)?;
256255

257-
ValidatedIndividual::validate(mapping_ref, path, individual, timestamp)
258-
.map_err(|uve| uve.into())
256+
ValidatedIndividual::validate(mapping_ref, individual, timestamp).map_err(|uve| uve.into())
259257
}
260258
}

0 commit comments

Comments
 (0)