Skip to main content

astarte_interfaces/interface/datastream/
individual.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//! Datastream with aggregation individual.
20//!
21//! In case aggregation is individual, each mapping is treated as an independent value and is
22//! managed individually.
23
24use std::{borrow::Cow, fmt::Display, str::FromStr};
25
26use serde::{Deserialize, Serialize};
27
28use crate::{
29    error::Error,
30    interface::{
31        name::InterfaceName, version::InterfaceVersion, AggregationIndividual, MappingVec, Schema,
32    },
33    mapping::{
34        datastream::individual::DatastreamIndividualMapping, path::MappingPath, MappingError,
35    },
36    schema::{Aggregation, InterfaceJson, InterfaceType, Mapping, Ownership},
37};
38
39/// Interface of type datastream individual.
40///
41/// For this interface all the mappings have distinct configurations.
42#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
43#[serde(try_from = "InterfaceJson<std::borrow::Cow<str>>")]
44pub struct DatastreamIndividual {
45    pub(crate) name: InterfaceName,
46    pub(crate) version: InterfaceVersion,
47    pub(crate) ownership: Ownership,
48    pub(crate) mappings: MappingVec<DatastreamIndividualMapping>,
49    #[cfg(feature = "doc-fields")]
50    #[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
51    pub(crate) description: Option<String>,
52    #[cfg(feature = "doc-fields")]
53    #[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
54    pub(crate) doc: Option<String>,
55}
56
57impl Schema for DatastreamIndividual {
58    type Mapping = DatastreamIndividualMapping;
59
60    fn name(&self) -> &str {
61        self.name.as_ref()
62    }
63
64    fn interface_name(&self) -> &InterfaceName {
65        &self.name
66    }
67
68    fn version_major(&self) -> i32 {
69        self.version.version_major()
70    }
71
72    fn version_minor(&self) -> i32 {
73        self.version.version_minor()
74    }
75
76    fn version(&self) -> InterfaceVersion {
77        self.version
78    }
79
80    fn interface_type(&self) -> InterfaceType {
81        InterfaceType::Datastream
82    }
83
84    fn ownership(&self) -> Ownership {
85        self.ownership
86    }
87
88    fn aggregation(&self) -> Aggregation {
89        Aggregation::Individual
90    }
91
92    #[cfg(feature = "doc-fields")]
93    #[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
94    fn description(&self) -> Option<&str> {
95        self.description.as_deref()
96    }
97
98    #[cfg(feature = "doc-fields")]
99    #[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
100    fn doc(&self) -> Option<&str> {
101        self.doc.as_deref()
102    }
103
104    fn iter_mappings(&self) -> impl Iterator<Item = &Self::Mapping> {
105        self.mappings.iter()
106    }
107
108    fn mappings_len(&self) -> usize {
109        self.mappings.len()
110    }
111
112    fn iter_interface_mappings(&self) -> impl Iterator<Item = Mapping<Cow<'_, str>>> {
113        self.iter_mappings().map(Mapping::from)
114    }
115}
116
117impl AggregationIndividual for DatastreamIndividual {
118    fn mapping(&self, path: &MappingPath) -> Option<&Self::Mapping> {
119        self.mappings.get(path)
120    }
121}
122
123impl Display for DatastreamIndividual {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        write!(f, "{}:{}", self.name, self.version)
126    }
127}
128
129impl<T> TryFrom<InterfaceJson<T>> for DatastreamIndividual
130where
131    T: AsRef<str> + Into<String>,
132{
133    type Error = Error;
134
135    fn try_from(value: InterfaceJson<T>) -> Result<Self, Self::Error> {
136        if value.interface_type != InterfaceType::Datastream
137            || value.aggregation.unwrap_or_default() != Aggregation::Individual
138        {
139            return Err(Error::InterfaceConversion {
140                exp_type: InterfaceType::Datastream,
141                exp_aggregation: Aggregation::Individual,
142                got_type: value.interface_type,
143                got_aggregation: value.aggregation.unwrap_or_default(),
144            });
145        }
146
147        let name = InterfaceName::from_str_ref(value.interface_name)?;
148        let version = InterfaceVersion::try_new(value.version_major, value.version_minor)?;
149
150        let mappings = value
151            .mappings
152            .into_iter()
153            .map(DatastreamIndividualMapping::try_from)
154            .collect::<Result<Vec<DatastreamIndividualMapping>, MappingError>>()
155            .and_then(MappingVec::try_from)?;
156
157        Ok(Self {
158            name: name.into_string(),
159            version,
160            ownership: value.ownership,
161            mappings,
162            #[cfg(feature = "doc-fields")]
163            description: value.description.map(T::into),
164            #[cfg(feature = "doc-fields")]
165            doc: value.doc.map(T::into),
166        })
167    }
168}
169
170impl Serialize for DatastreamIndividual {
171    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
172    where
173        S: serde::Serializer,
174    {
175        InterfaceJson::<Cow<str>>::from(self).serialize(serializer)
176    }
177}
178
179impl FromStr for DatastreamIndividual {
180    type Err = Error;
181
182    fn from_str(s: &str) -> Result<Self, Self::Err> {
183        let interface: InterfaceJson<Cow<str>> = serde_json::from_str(s)?;
184
185        Self::try_from(interface)
186    }
187}