autosar_data_abstraction/
lib.rs

1//! # Description
2//!
3//! [![Github Actions](https://github.com/DanielT/autosar-data-abstraction/actions/workflows/CI.yml/badge.svg)](https://github.com/DanielT/autosar-data-abstraction/actions)
4//!
5//! This crate provides an abstraction layer for the AUTOSAR data model.
6//! It is built on top of the crate `autosar-data` and provides complex interactions with
7//! the model on top of the elementary operations of `autosar-data`.
8//!
9//! Rather than transforming the element based model into a new form, it only presents a
10//! view into the existing model, and provides methods to retrieve and modify the data.
11//!
12//! Since the AUTOSAR data model is very complex and has many different types of elements,
13//! this crate does not aim to provide full coverage of all classes.
14//! Instead the focus is on the most common classes and their interactions.
15//!
16//! Any other data can still be accessed through the basic operations of `autosar-data`, because the
17//! calls to `autosar-data` and `autosar-data-abstraction` can be mixed freely.
18//!
19//! # Features
20//!
21//! Autosar Classic Platform:
22//! - Communication:
23//!   - Busses
24//!     - CAN
25//!     - Ethernet (both old and new style)
26//!     - FlexRay
27//!     - not supported: LIN, J1939
28//!   - PDUs
29//!   - Signals
30//!   - Transformations: SomeIp, E2E, Com
31//! - Data Types
32//!   - Basic data types
33//!   - Implementation data types
34//!   - Application data types
35//! - Software Components
36//!   - Atomic SWCs, Compositions, etc.
37//!   - Interfaces
38//!   - Ports
39//!   - Internal behavior: Runnables, Events, etc.
40//! - ECU Configuration
41//!
42//! # Example
43//!
44//! ```rust
45//! # use autosar_data::*;
46//! # use autosar_data_abstraction::*;
47//! # use autosar_data_abstraction::communication::*;
48//! # fn main() -> Result<(), AutosarAbstractionError> {
49//! let model = AutosarModelAbstraction::create("file.arxml", AutosarVersion::Autosar_00049);
50//! let package_1 = model.get_or_create_package("/System")?;
51//! let system = package_1.create_system("System", SystemCategory::SystemExtract)?;
52//! let package_2 = model.get_or_create_package("/Clusters")?;
53//!
54//! // create an Ethernet cluster and a physical channel for VLAN 33
55//! let eth_cluster = system.create_ethernet_cluster("EthCluster", &package_2)?;
56//! let vlan_info = EthernetVlanInfo {
57//!     vlan_id: 33,
58//!     vlan_name: "VLAN_33".to_string(),
59//! };
60//! let eth_channel = eth_cluster.create_physical_channel("EthChannel", Some(&vlan_info))?;
61//! let vlan_info_2 = eth_channel.vlan_info().unwrap();
62//!
63//! // create an ECU instance and connect it to the Ethernet channel
64//! let package_3 = model.get_or_create_package("/Ecus")?;
65//! let ecu_instance_a = system.create_ecu_instance("Ecu_A", &package_3)?;
66//! let ethctrl = ecu_instance_a
67//!     .create_ethernet_communication_controller(
68//!         "EthernetController",
69//!         Some("ab:cd:ef:01:02:03".to_string())
70//!     )?;
71//! let channels_iter = ethctrl.connected_channels();
72//! ethctrl.connect_physical_channel("Ecu_A_connector", &eth_channel)?;
73//! let channels_iter = ethctrl.connected_channels();
74//!
75//! // ...
76//! # Ok(())}
77//! ```
78
79#![warn(missing_docs)]
80
81use std::path::Path;
82
83use autosar_data::{ArxmlFile, AutosarDataError, AutosarModel, AutosarVersion, Element, ElementName, EnumItem};
84use thiserror::Error;
85
86// modules that are visible in the API
87pub mod communication;
88pub mod datatype;
89pub mod ecu_configuration;
90pub mod software_component;
91
92// internal modules that only serve to split up the code
93mod arpackage;
94mod ecuinstance;
95mod system;
96
97// export the content of the internal modules
98pub use arpackage::ArPackage;
99pub use ecuinstance::*;
100pub use system::*;
101
102/// The error type `AutosarAbstractionError` wraps all errors from the crate
103#[derive(Error, Debug)]
104#[non_exhaustive]
105pub enum AutosarAbstractionError {
106    /// converting an autosar-data element to a class in the abstract model failed
107    #[error("conversion error: could not convert {} to {}", .element.element_name(), dest)]
108    ConversionError {
109        /// the element that could not be converted
110        element: Element,
111        /// the name of the destination type
112        dest: String,
113    },
114
115    /// converting an autosar-data element to a class in the abstract model failed
116    #[error("value conversion error: could not convert {} to {}", .value, .dest)]
117    ValueConversionError {
118        /// the value that could not be converted
119        value: String,
120        /// the name of the destination type
121        dest: String,
122    },
123
124    /// `ModelError` wraps [`AutosarDataError`] errors from autosar-data operations, e.g.
125    /// [`AutosarDataError::ItemDeleted`], [`AutosarDataError::IncorrectContentType`], ...
126    #[error("model error: {}", .0)]
127    ModelError(AutosarDataError),
128
129    /// an invalid Autosar path was passed as a parameter
130    #[error("invalid path: {}", .0)]
131    InvalidPath(String),
132
133    /// an item could not be created because another item already fulfills its role in the model
134    #[error("the item already exists")]
135    ItemAlreadyExists,
136
137    /// the function parameter has an invalid value
138    #[error("invalid parameter: {}", .0)]
139    InvalidParameter(String),
140}
141
142impl From<AutosarDataError> for AutosarAbstractionError {
143    fn from(err: AutosarDataError) -> Self {
144        AutosarAbstractionError::ModelError(err)
145    }
146}
147
148//#########################################################
149
150/// The `AbstractionElement` trait is implemented by all classes that represent elements in the AUTOSAR model.
151pub trait AbstractionElement: Clone + PartialEq + TryFrom<autosar_data::Element> {
152    /// Get the underlying `Element` from the abstraction element
153    #[must_use]
154    fn element(&self) -> &Element;
155
156    // fn set_timestamp(&self) {
157    //     todo!()
158    // }
159}
160
161/// The `IdentifiableAbstractionElement` trait is implemented by all classes that represent elements in the AUTOSAR model that have an item name.
162pub trait IdentifiableAbstractionElement: AbstractionElement {
163    /// Get the item name of the element
164    #[must_use]
165    fn name(&self) -> Option<String> {
166        self.element().item_name()
167    }
168
169    /// Set the item name of the element
170    fn set_name(&self, name: &str) -> Result<(), AutosarAbstractionError> {
171        self.element().set_item_name(name)?;
172        Ok(())
173    }
174}
175
176macro_rules! abstraction_element {
177    ($name: ident, $base_elem: ident) => {
178        impl TryFrom<autosar_data::Element> for $name {
179            type Error = AutosarAbstractionError;
180
181            fn try_from(element: autosar_data::Element) -> Result<Self, Self::Error> {
182                if element.element_name() == autosar_data::ElementName::$base_elem {
183                    Ok($name(element))
184                } else {
185                    Err(AutosarAbstractionError::ConversionError {
186                        element,
187                        dest: stringify!($name).to_string(),
188                    })
189                }
190            }
191        }
192
193        impl AbstractionElement for $name {
194            fn element(&self) -> &autosar_data::Element {
195                &self.0
196            }
197        }
198
199        impl From<$name> for autosar_data::Element {
200            fn from(val: $name) -> Self {
201                val.0
202            }
203        }
204    };
205}
206
207pub(crate) use abstraction_element;
208
209//#########################################################
210
211/// The `AutosarModelAbstraction` wraps an `AutosarModel` and provides additional functionality
212#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct AutosarModelAbstraction(AutosarModel);
214
215impl AutosarModelAbstraction {
216    /// Create a new `AutosarModelAbstraction` from an `AutosarModel`
217    #[must_use]
218    pub fn new(model: AutosarModel) -> Self {
219        Self(model)
220    }
221
222    /// create a new `AutosarModelAbstraction` with an empty `AutosarModel`
223    ///
224    /// You must specify a file name for the initial file in the model. This file is not created on disk immediately.
225    /// The model also needs an `AutosarVersion`.
226    pub fn create<P: AsRef<Path>>(file_name: P, version: AutosarVersion) -> Self {
227        let model = AutosarModel::new();
228        // create the initial file in the model - create_file can return a DuplicateFileName
229        // error, but hthis is not a concern for the first file, so it is always safe to unwrap
230        model.create_file(file_name, version).unwrap();
231        Self(model)
232    }
233
234    /// create an `AutosarModelAbstraction` from a file on disk
235    pub fn from_file<P: AsRef<Path>>(file_name: P) -> Result<Self, AutosarAbstractionError> {
236        let model = AutosarModel::new();
237        model.load_file(file_name, true)?;
238        Ok(Self(model))
239    }
240
241    /// Get the underlying `AutosarModel` from the abstraction model
242    #[must_use]
243    pub fn model(&self) -> &AutosarModel {
244        &self.0
245    }
246
247    /// Get the root element of the model
248    #[must_use]
249    pub fn root_element(&self) -> Element {
250        self.0.root_element()
251    }
252
253    /// iterate over all top-level packages
254    pub fn packages(&self) -> impl Iterator<Item = ArPackage> + Send + 'static {
255        self.0
256            .root_element()
257            .get_sub_element(ElementName::ArPackages)
258            .into_iter()
259            .flat_map(|elem| elem.sub_elements())
260            .filter_map(|elem| ArPackage::try_from(elem).ok())
261    }
262
263    /// Get a package by its path or create it if it does not exist
264    pub fn get_or_create_package(&self, path: &str) -> Result<ArPackage, AutosarAbstractionError> {
265        ArPackage::get_or_create(&self.0, path)
266    }
267
268    /// Create a new file in the model
269    pub fn create_file(&self, file_name: &str, version: AutosarVersion) -> Result<ArxmlFile, AutosarAbstractionError> {
270        let arxml_file = self.0.create_file(file_name, version)?;
271        Ok(arxml_file)
272    }
273
274    /// Load a file into the model
275    pub fn load_file<P: AsRef<Path>>(
276        &self,
277        file_name: P,
278        strict: bool,
279    ) -> Result<(ArxmlFile, Vec<AutosarDataError>), AutosarAbstractionError> {
280        let value = self.0.load_file(file_name, strict)?;
281        Ok(value)
282    }
283
284    /// iterate over all files in the model
285    pub fn files(&self) -> impl Iterator<Item = ArxmlFile> + Send + 'static {
286        self.0.files()
287    }
288
289    /// write the model to disk, creating or updating all files in the model
290    pub fn write(&self) -> Result<(), AutosarAbstractionError> {
291        self.0.write()?;
292        Ok(())
293    }
294
295    /// Get an element by its path
296    #[must_use]
297    pub fn get_element_by_path(&self, path: &str) -> Option<Element> {
298        self.0.get_element_by_path(path)
299    }
300
301    /// find an existing SYSTEM in the model, if it exists
302    ///
303    /// # Example
304    ///
305    /// ```
306    /// # use autosar_data::*;
307    /// # use autosar_data_abstraction::*;
308    /// # fn main() -> Result<(), AutosarAbstractionError> {
309    /// let model = AutosarModelAbstraction::create("filename", AutosarVersion::Autosar_00048);
310    /// # let package = model.get_or_create_package("/my/pkg")?;
311    /// let system = package.create_system("System", SystemCategory::SystemExtract)?;
312    /// if let Some(sys_2) = model.find_system() {
313    ///     assert_eq!(system, sys_2);
314    /// }
315    /// # Ok(())}
316    /// ```
317    #[must_use]
318    pub fn find_system(&self) -> Option<System> {
319        System::find(&self.0)
320    }
321}
322
323//#########################################################
324
325/// The `ByteOrder` is used to define the order of bytes in a multi-byte value
326#[derive(Debug, Clone, Copy, PartialEq, Eq)]
327pub enum ByteOrder {
328    /// Most significant byte at the lowest address = big endian
329    MostSignificantByteFirst,
330    /// Most significant byte at the highest address = little endian
331    MostSignificantByteLast,
332    /// The byte order is not defined / not relevant
333    Opaque,
334}
335
336impl TryFrom<EnumItem> for ByteOrder {
337    type Error = AutosarAbstractionError;
338
339    fn try_from(value: EnumItem) -> Result<Self, Self::Error> {
340        match value {
341            EnumItem::MostSignificantByteFirst => Ok(ByteOrder::MostSignificantByteFirst),
342            EnumItem::MostSignificantByteLast => Ok(ByteOrder::MostSignificantByteLast),
343            EnumItem::Opaque => Ok(ByteOrder::Opaque),
344            _ => Err(AutosarAbstractionError::ValueConversionError {
345                value: value.to_string(),
346                dest: "ByteOrder".to_string(),
347            }),
348        }
349    }
350}
351
352impl From<ByteOrder> for EnumItem {
353    fn from(value: ByteOrder) -> Self {
354        match value {
355            ByteOrder::MostSignificantByteFirst => EnumItem::MostSignificantByteFirst,
356            ByteOrder::MostSignificantByteLast => EnumItem::MostSignificantByteLast,
357            ByteOrder::Opaque => EnumItem::Opaque,
358        }
359    }
360}
361
362//##################################################################
363
364pub(crate) fn make_unique_name(model: &AutosarModel, base_path: &str, initial_name: &str) -> String {
365    let mut full_path = format!("{base_path}/{initial_name}");
366    let mut name = initial_name.to_string();
367    let mut counter = 0;
368    while model.get_element_by_path(&full_path).is_some() {
369        counter += 1;
370        name = format!("{initial_name}_{counter}");
371        full_path = format!("{base_path}/{name}");
372    }
373
374    name
375}
376
377//#########################################################
378
379#[cfg(test)]
380mod test {
381    use super::*;
382    use autosar_data::AutosarModel;
383
384    #[test]
385    fn create_model() {
386        // create a new AutosarModelAbstraction based on an existing AutosarModel
387        let raw_model = AutosarModel::new();
388        let model = AutosarModelAbstraction::new(raw_model.clone());
389        assert_eq!(model.model(), &raw_model);
390
391        // create an empty AutosarModelAbstraction from scratch
392        let model = AutosarModelAbstraction::create("filename", AutosarVersion::Autosar_00049);
393        let root = model.root_element();
394        assert_eq!(root.element_name(), ElementName::Autosar);
395    }
396
397    #[test]
398    fn create_model_from_file() {
399        let tempdir = tempfile::tempdir().unwrap();
400        let filename = tempdir.path().join("test.arxml");
401
402        // write a new arxml file to disk
403        let model1 = AutosarModelAbstraction::create(filename.clone(), AutosarVersion::LATEST);
404        model1.write().unwrap();
405
406        // create a new model from the file
407        let model2 = AutosarModelAbstraction::from_file(filename).unwrap();
408        let root = model2.root_element();
409        assert_eq!(root.element_name(), ElementName::Autosar);
410    }
411
412    #[test]
413    fn model_files() {
414        let model = AutosarModelAbstraction::create("file1.arxml", AutosarVersion::Autosar_00049);
415        let file = model.create_file("file2.arxml", AutosarVersion::Autosar_00049).unwrap();
416        let files: Vec<_> = model.files().collect();
417        assert_eq!(files.len(), 2);
418        assert_eq!(files[1], file);
419    }
420
421    #[test]
422    fn model_load_file() {
423        let tempdir = tempfile::tempdir().unwrap();
424        let filename = tempdir.path().join("test.arxml");
425
426        // write a new arxml file to disk
427        let model = AutosarModelAbstraction::create(filename.clone(), AutosarVersion::LATEST);
428        model.write().unwrap();
429
430        // load the file into a new model
431        let model = AutosarModelAbstraction::new(AutosarModel::new());
432        let (_file, errors) = model.load_file(filename, true).unwrap();
433        assert!(errors.is_empty());
434    }
435
436    #[test]
437    fn model_packages() {
438        let model = AutosarModelAbstraction::create("filename", AutosarVersion::Autosar_00049);
439        let package = model.get_or_create_package("/package").unwrap();
440        let package2 = model.get_or_create_package("/other_package").unwrap();
441        model.get_or_create_package("/other_package/sub_package").unwrap();
442        let packages: Vec<_> = model.packages().collect();
443        assert_eq!(packages.len(), 2);
444        assert_eq!(packages[0], package);
445        assert_eq!(packages[1], package2);
446    }
447
448    #[test]
449    fn errors() {
450        let model = AutosarModel::new();
451
452        let err = AutosarAbstractionError::ConversionError {
453            element: model.root_element(),
454            dest: "TEST".to_string(),
455        };
456        let string = format!("{err}");
457        assert!(!string.is_empty());
458
459        let err = AutosarAbstractionError::InvalidPath("lorem ipsum".to_string());
460        let string = format!("{err}");
461        assert!(!string.is_empty());
462
463        let err = AutosarAbstractionError::ItemAlreadyExists;
464        let string = format!("{err}");
465        assert!(!string.is_empty());
466    }
467}