Skip to main content

astarte_device_sdk/session/
mod.rs

1// This file is part of Astarte.
2//
3// Copyright 2025 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//! Handles the storage of the current introspection to maintain
20//! a persistent session with the Astarte MQTT server.
21
22use std::future::Future;
23
24use astarte_interfaces::Interface;
25use itertools::Itertools;
26
27use crate::{error::DynError, interfaces::Interfaces};
28
29mod sqlite;
30
31/// Interface data associated with the astarte introspection.
32#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord, Hash)]
33pub struct IntrospectionInterface<S = String> {
34    /// Name of the interface.
35    name: S,
36    /// Major version.
37    version_major: i32,
38    /// Minor version.
39    version_minor: i32,
40}
41
42impl<S> IntrospectionInterface<S> {
43    /// Create a new instance of the struct
44    pub fn new(name: S, version_major: i32, version_minor: i32) -> Self {
45        Self {
46            name,
47            version_major,
48            version_minor,
49        }
50    }
51
52    /// Get the name of the interface
53    pub fn name(&self) -> &S {
54        &self.name
55    }
56    /// Get the major version of the interface
57    pub fn version_major(&self) -> i32 {
58        self.version_major
59    }
60    /// Get the minor version of the interface
61    pub fn version_minor(&self) -> i32 {
62        self.version_minor
63    }
64}
65
66impl<T: PartialEq<U>, U> PartialEq<IntrospectionInterface<U>> for IntrospectionInterface<T> {
67    fn eq(&self, other: &IntrospectionInterface<U>) -> bool {
68        self.name() == other.name()
69            && self.version_major() == other.version_major()
70            && self.version_minor() == other.version_minor()
71    }
72}
73
74impl<'a> From<&'a Interface> for IntrospectionInterface<&'a str> {
75    fn from(val: &'a Interface) -> Self {
76        IntrospectionInterface::new(
77            val.interface_name(),
78            val.version_major(),
79            val.version_minor(),
80        )
81    }
82}
83
84impl From<&Interface> for IntrospectionInterface {
85    fn from(val: &Interface) -> Self {
86        IntrospectionInterface::new(
87            val.interface_name().to_owned(),
88            val.version_major(),
89            val.version_minor(),
90        )
91    }
92}
93
94impl<'a> From<&'a Interfaces> for Vec<IntrospectionInterface<&'a str>> {
95    fn from(val: &'a Interfaces) -> Self {
96        val.iter().map(|i| i.into()).collect_vec()
97    }
98}
99
100impl From<&Interfaces> for Vec<IntrospectionInterface> {
101    fn from(val: &Interfaces) -> Self {
102        val.iter().map(|i| i.into()).collect_vec()
103    }
104}
105
106impl From<IntrospectionInterface<&str>> for IntrospectionInterface {
107    fn from(value: IntrospectionInterface<&str>) -> Self {
108        IntrospectionInterface {
109            name: value.name.to_string(),
110            version_major: value.version_major,
111            version_minor: value.version_minor,
112        }
113    }
114}
115
116/// Error returned by the retention.
117#[derive(Debug, thiserror::Error)]
118#[non_exhaustive]
119pub enum SessionError {
120    /// Error in the store introspection method
121    #[error("couldn't store the introspection")]
122    AddInterfaces(#[source] DynError),
123    /// Error in the load introspection method
124    #[error("couldn't load the introspection")]
125    LoadIntrospection(#[source] DynError),
126    /// Error in the remove introspection method
127    #[error("couldn't remove the interfaces")]
128    RemoveInterfaces(#[source] DynError),
129}
130
131impl SessionError {
132    pub(crate) fn add_interfaces(err: impl Into<DynError>) -> Self {
133        Self::AddInterfaces(err.into())
134    }
135
136    pub(crate) fn load_introspection(err: impl Into<DynError>) -> Self {
137        Self::LoadIntrospection(err.into())
138    }
139
140    pub(crate) fn remove_interfaces(err: impl Into<DynError>) -> Self {
141        Self::RemoveInterfaces(err.into())
142    }
143}
144
145/// Trait for persistently storing and managing session-related data.
146pub trait StoredSession: Clone + Send + Sync {
147    /// Clears all [`IntrospectionInterface`]s from the persistent store.
148    /// This method should not return an error or panic since it is called
149    /// during the connection of the device.
150    fn clear_introspection(&self) -> impl Future<Output = ()> + Send;
151
152    /// Stores the complete introspection in the table.
153    /// This method perform the same action as [`StoredSession::add_interfaces`]
154    /// but disallows returning an error.
155    /// This method should not return an error or panic since it is called
156    /// during the connection of the device.
157    fn store_introspection(
158        &self,
159        interfaces: &[IntrospectionInterface],
160    ) -> impl Future<Output = ()> + Send;
161
162    /// Adds a slice of `IntrospectionInterface` to the persistent store.
163    fn add_interfaces(
164        &self,
165        interfaces: &[IntrospectionInterface<&str>],
166    ) -> impl Future<Output = Result<(), SessionError>> + Send;
167
168    /// Loads all stored `IntrospectionInterface`s from the persistent store.
169    fn load_introspection(
170        &self,
171    ) -> impl Future<Output = Result<Vec<IntrospectionInterface>, SessionError>> + Send;
172
173    /// Removes a specific slice of `IntrospectionInterface`s from the persistent store.
174    fn remove_interfaces(
175        &self,
176        interfaces: &[IntrospectionInterface<&str>],
177    ) -> impl Future<Output = Result<(), SessionError>> + Send;
178}
179
180#[cfg(test)]
181pub(crate) mod tests {
182    use std::str::FromStr;
183
184    use astarte_interfaces::Interface;
185    use pretty_assertions::assert_eq;
186
187    use crate::test::{DEVICE_OBJECT, DEVICE_PROPERTIES, SERVER_INDIVIDUAL};
188
189    use super::IntrospectionInterface;
190
191    impl<'a> From<&'a IntrospectionInterface> for IntrospectionInterface<&'a str> {
192        fn from(val: &'a IntrospectionInterface) -> Self {
193            IntrospectionInterface::new(val.name(), val.version_major(), val.version_minor())
194        }
195    }
196
197    impl IntrospectionInterface {
198        pub(crate) fn as_ref(&self) -> IntrospectionInterface<&str> {
199            self.into()
200        }
201    }
202
203    #[test]
204    fn test_from_interface() {
205        let interface = Interface::from_str(DEVICE_OBJECT).unwrap();
206
207        let introspection_if_data_ref: IntrospectionInterface<&str> = From::from(&interface);
208
209        assert_eq!(interface.interface_name(), introspection_if_data_ref.name);
210        assert_eq!(
211            interface.version_major(),
212            introspection_if_data_ref.version_major
213        );
214        assert_eq!(
215            interface.version_minor(),
216            introspection_if_data_ref.version_minor
217        );
218
219        let introspection_if_data: IntrospectionInterface = From::from(&interface);
220
221        assert_eq!(interface.interface_name(), introspection_if_data.name);
222        assert_eq!(
223            interface.version_major(),
224            introspection_if_data.version_major
225        );
226        assert_eq!(
227            interface.version_minor(),
228            introspection_if_data.version_minor
229        );
230
231        assert_eq!(introspection_if_data_ref, introspection_if_data.as_ref());
232    }
233
234    #[test]
235    fn test_from_interfaces() {
236        use crate::session::Interfaces;
237
238        let interfaces = [
239            Interface::from_str(DEVICE_PROPERTIES).unwrap(),
240            Interface::from_str(DEVICE_OBJECT).unwrap(),
241            Interface::from_str(SERVER_INDIVIDUAL).unwrap(),
242        ];
243
244        let interfaces = Interfaces::from_iter(interfaces);
245
246        let introsopection_interface_vec: Vec<IntrospectionInterface<&str>> =
247            From::from(&interfaces);
248
249        assert!(interfaces.matches(&introsopection_interface_vec));
250
251        let introsopection_interface_vec: Vec<IntrospectionInterface> = From::from(&interfaces);
252
253        assert!(interfaces.matches(&introsopection_interface_vec));
254    }
255}