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::{
84    ArxmlFile, AutosarDataError, AutosarModel, AutosarVersion, Element, ElementName, EnumItem, WeakElement,
85};
86use thiserror::Error;
87
88// modules that are visible in the API
89pub mod communication;
90pub mod datatype;
91pub mod ecu_configuration;
92pub mod software_component;
93
94// internal modules that only serve to split up the code
95mod arpackage;
96mod ecuinstance;
97mod system;
98
99// export the content of the internal modules
100pub use arpackage::ArPackage;
101pub use ecuinstance::*;
102pub use system::*;
103
104/// The error type `AutosarAbstractionError` wraps all errors from the crate
105#[derive(Error, Debug)]
106#[non_exhaustive]
107pub enum AutosarAbstractionError {
108    /// converting an autosar-data element to a class in the abstract model failed
109    #[error("conversion error: could not convert {} to {}", .element.element_name(), dest)]
110    ConversionError {
111        /// the element that could not be converted
112        element: Element,
113        /// the name of the destination type
114        dest: String,
115    },
116
117    /// converting an autosar-data element to a class in the abstract model failed
118    #[error("value conversion error: could not convert {} to {}", .value, .dest)]
119    ValueConversionError {
120        /// the value that could not be converted
121        value: String,
122        /// the name of the destination type
123        dest: String,
124    },
125
126    /// `ModelError` wraps [`AutosarDataError`] errors from autosar-data operations, e.g.
127    /// [`AutosarDataError::ItemDeleted`], [`AutosarDataError::IncorrectContentType`], ...
128    #[error("model error: {}", .0)]
129    ModelError(AutosarDataError),
130
131    /// an invalid Autosar path was passed as a parameter
132    #[error("invalid path: {}", .0)]
133    InvalidPath(String),
134
135    /// an item could not be created because another item already fulfills its role in the model
136    #[error("the item already exists")]
137    ItemAlreadyExists,
138
139    /// the function parameter has an invalid value
140    #[error("invalid parameter: {}", .0)]
141    InvalidParameter(String),
142}
143
144impl From<AutosarDataError> for AutosarAbstractionError {
145    fn from(err: AutosarDataError) -> Self {
146        AutosarAbstractionError::ModelError(err)
147    }
148}
149
150//#########################################################
151
152/// The `AbstractionElement` trait is implemented by all classes that represent elements in the AUTOSAR model.
153pub trait AbstractionElement: Clone + PartialEq + TryFrom<autosar_data::Element> {
154    /// Get the underlying `Element` from the abstraction element
155    #[must_use]
156    fn element(&self) -> &Element;
157
158    /// Remove this element from the model
159    ///
160    /// `deep` indicates whether elements that depend on this element should also be removed.
161    fn remove(self, _deep: bool) -> Result<(), AutosarAbstractionError> {
162        let element = self.element();
163        let Some(parent) = element.parent()? else {
164            // root element cannot be removed
165            return Err(AutosarAbstractionError::InvalidParameter(
166                "cannot remove root element".to_string(),
167            ));
168        };
169
170        if element.is_identifiable() {
171            let model = element.model()?;
172            let path = element.path()?;
173            let inbound_refs = model.get_references_to(&path);
174            for ref_elem in inbound_refs.iter().filter_map(WeakElement::upgrade) {
175                let Ok(Some(parent)) = ref_elem.parent() else {
176                    continue;
177                };
178                match ref_elem.element_name() {
179                    ElementName::FibexElementRef => {
180                        // explicit handling of FIBEX-ELEMENTS -> FIBEX-ELEMENT-REF-CONDITIONAl -> FIBEX-ELEMENT-REF
181                        if let Ok(Some(grandparent)) = parent.parent() {
182                            grandparent.remove_sub_element(parent)?;
183                        }
184                    }
185                    _ => {
186                        // Fallback: just remove the reference
187                        // In many cases this leaves the model in an invalid state, but it
188                        // is always better than a dangling reference
189                        let _ = parent.remove_sub_element(ref_elem);
190                    }
191                }
192            }
193        }
194
195        parent.remove_sub_element(element.clone())?;
196        Ok(())
197    }
198}
199
200/// The `IdentifiableAbstractionElement` trait is implemented by all classes that represent elements in the AUTOSAR model that have an item name.
201pub trait IdentifiableAbstractionElement: AbstractionElement {
202    /// Get the item name of the element
203    #[must_use]
204    fn name(&self) -> Option<String> {
205        self.element().item_name()
206    }
207
208    /// Set the item name of the element
209    fn set_name(&self, name: &str) -> Result<(), AutosarAbstractionError> {
210        self.element().set_item_name(name)?;
211        Ok(())
212    }
213}
214
215macro_rules! abstraction_element {
216    ($name: ident, $base_elem: ident) => {
217        impl TryFrom<autosar_data::Element> for $name {
218            type Error = AutosarAbstractionError;
219
220            fn try_from(element: autosar_data::Element) -> Result<Self, Self::Error> {
221                if element.element_name() == autosar_data::ElementName::$base_elem {
222                    Ok($name(element))
223                } else {
224                    Err(AutosarAbstractionError::ConversionError {
225                        element,
226                        dest: stringify!($name).to_string(),
227                    })
228                }
229            }
230        }
231
232        impl AbstractionElement for $name {
233            fn element(&self) -> &autosar_data::Element {
234                &self.0
235            }
236        }
237
238        impl From<$name> for autosar_data::Element {
239            fn from(val: $name) -> Self {
240                val.0
241            }
242        }
243    };
244}
245
246pub(crate) use abstraction_element;
247
248//#########################################################
249
250/// The `AutosarModelAbstraction` wraps an `AutosarModel` and provides additional functionality
251#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct AutosarModelAbstraction(AutosarModel);
253
254impl AutosarModelAbstraction {
255    /// Create a new `AutosarModelAbstraction` from an `AutosarModel`
256    #[must_use]
257    pub fn new(model: AutosarModel) -> Self {
258        Self(model)
259    }
260
261    /// create a new `AutosarModelAbstraction` with an empty `AutosarModel`
262    ///
263    /// You must specify a file name for the initial file in the model. This file is not created on disk immediately.
264    /// The model also needs an `AutosarVersion`.
265    pub fn create<P: AsRef<Path>>(file_name: P, version: AutosarVersion) -> Self {
266        let model = AutosarModel::new();
267        // create the initial file in the model - create_file can return a DuplicateFileName
268        // error, but hthis is not a concern for the first file, so it is always safe to unwrap
269        model.create_file(file_name, version).unwrap();
270        Self(model)
271    }
272
273    /// create an `AutosarModelAbstraction` from a file on disk
274    pub fn from_file<P: AsRef<Path>>(file_name: P) -> Result<Self, AutosarAbstractionError> {
275        let model = AutosarModel::new();
276        model.load_file(file_name, true)?;
277        Ok(Self(model))
278    }
279
280    /// Get the underlying `AutosarModel` from the abstraction model
281    #[must_use]
282    pub fn model(&self) -> &AutosarModel {
283        &self.0
284    }
285
286    /// Get the root element of the model
287    #[must_use]
288    pub fn root_element(&self) -> Element {
289        self.0.root_element()
290    }
291
292    /// iterate over all top-level packages
293    pub fn packages(&self) -> impl Iterator<Item = ArPackage> + Send + use<> {
294        self.0
295            .root_element()
296            .get_sub_element(ElementName::ArPackages)
297            .into_iter()
298            .flat_map(|elem| elem.sub_elements())
299            .filter_map(|elem| ArPackage::try_from(elem).ok())
300    }
301
302    /// Get a package by its path or create it if it does not exist
303    pub fn get_or_create_package(&self, path: &str) -> Result<ArPackage, AutosarAbstractionError> {
304        ArPackage::get_or_create(&self.0, path)
305    }
306
307    /// Create a new file in the model
308    pub fn create_file(&self, file_name: &str, version: AutosarVersion) -> Result<ArxmlFile, AutosarAbstractionError> {
309        let arxml_file = self.0.create_file(file_name, version)?;
310        Ok(arxml_file)
311    }
312
313    /// Load a file into the model
314    pub fn load_file<P: AsRef<Path>>(
315        &self,
316        file_name: P,
317        strict: bool,
318    ) -> Result<(ArxmlFile, Vec<AutosarDataError>), AutosarAbstractionError> {
319        let value = self.0.load_file(file_name, strict)?;
320        Ok(value)
321    }
322
323    /// iterate over all files in the model
324    pub fn files(&self) -> impl Iterator<Item = ArxmlFile> + Send + use<> {
325        self.0.files()
326    }
327
328    /// write the model to disk, creating or updating all files in the model
329    pub fn write(&self) -> Result<(), AutosarAbstractionError> {
330        self.0.write()?;
331        Ok(())
332    }
333
334    /// Get an element by its path
335    #[must_use]
336    pub fn get_element_by_path(&self, path: &str) -> Option<Element> {
337        self.0.get_element_by_path(path)
338    }
339
340    /// find an existing SYSTEM in the model, if it exists
341    ///
342    /// # Example
343    ///
344    /// ```
345    /// # use autosar_data::*;
346    /// # use autosar_data_abstraction::*;
347    /// # fn main() -> Result<(), AutosarAbstractionError> {
348    /// let model = AutosarModelAbstraction::create("filename", AutosarVersion::Autosar_00048);
349    /// # let package = model.get_or_create_package("/my/pkg")?;
350    /// let system = package.create_system("System", SystemCategory::SystemExtract)?;
351    /// if let Some(sys_2) = model.find_system() {
352    ///     assert_eq!(system, sys_2);
353    /// }
354    /// # Ok(())}
355    /// ```
356    #[must_use]
357    pub fn find_system(&self) -> Option<System> {
358        System::find(&self.0)
359    }
360}
361
362//#########################################################
363
364/// The `ByteOrder` is used to define the order of bytes in a multi-byte value
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366pub enum ByteOrder {
367    /// Most significant byte at the lowest address = big endian
368    MostSignificantByteFirst,
369    /// Most significant byte at the highest address = little endian
370    MostSignificantByteLast,
371    /// The byte order is not defined / not relevant
372    Opaque,
373}
374
375impl TryFrom<EnumItem> for ByteOrder {
376    type Error = AutosarAbstractionError;
377
378    fn try_from(value: EnumItem) -> Result<Self, Self::Error> {
379        match value {
380            EnumItem::MostSignificantByteFirst => Ok(ByteOrder::MostSignificantByteFirst),
381            EnumItem::MostSignificantByteLast => Ok(ByteOrder::MostSignificantByteLast),
382            EnumItem::Opaque => Ok(ByteOrder::Opaque),
383            _ => Err(AutosarAbstractionError::ValueConversionError {
384                value: value.to_string(),
385                dest: "ByteOrder".to_string(),
386            }),
387        }
388    }
389}
390
391impl From<ByteOrder> for EnumItem {
392    fn from(value: ByteOrder) -> Self {
393        match value {
394            ByteOrder::MostSignificantByteFirst => EnumItem::MostSignificantByteFirst,
395            ByteOrder::MostSignificantByteLast => EnumItem::MostSignificantByteLast,
396            ByteOrder::Opaque => EnumItem::Opaque,
397        }
398    }
399}
400
401//##################################################################
402
403pub(crate) fn make_unique_name(model: &AutosarModel, base_path: &str, initial_name: &str) -> String {
404    let mut full_path = format!("{base_path}/{initial_name}");
405    let mut name = initial_name.to_string();
406    let mut counter = 0;
407    while model.get_element_by_path(&full_path).is_some() {
408        counter += 1;
409        name = format!("{initial_name}_{counter}");
410        full_path = format!("{base_path}/{name}");
411    }
412
413    name
414}
415
416//##################################################################
417
418/// check if the element is used anywhere
419pub(crate) fn is_used(element: &Element) -> bool {
420    let Ok(model) = element.model() else {
421        // not connected to model -> unused
422        return false;
423    };
424    let Ok(path) = element.path() else {
425        // not connected to model any more -> unused
426        // this case is only reachable through a race condition (parallel deletion)
427        return false;
428    };
429    let references = model.get_references_to(&path);
430
431    // it is unused if there are no references to it
432    !references.is_empty()
433}
434
435//##################################################################
436
437// returns the named parent and the parent of each element that references the given element
438pub(crate) fn get_reference_parents(element: &Element) -> Result<Vec<(Element, Element)>, AutosarAbstractionError> {
439    let model = element.model()?;
440    let path = element.path()?;
441    let references = model.get_references_to(&path);
442
443    let parents = references
444        .iter()
445        .filter_map(WeakElement::upgrade)
446        .filter_map(|ref_elem| {
447            Some((
448                ref_elem.named_parent().ok().flatten()?,
449                ref_elem.parent().ok().flatten()?,
450            ))
451        })
452        .collect();
453
454    Ok(parents)
455}
456
457//##################################################################
458
459#[cfg(test)]
460mod test {
461    use super::*;
462    use autosar_data::AutosarModel;
463
464    #[test]
465    fn create_model() {
466        // create a new AutosarModelAbstraction based on an existing AutosarModel
467        let raw_model = AutosarModel::new();
468        let model = AutosarModelAbstraction::new(raw_model.clone());
469        assert_eq!(model.model(), &raw_model);
470
471        // create an empty AutosarModelAbstraction from scratch
472        let model = AutosarModelAbstraction::create("filename", AutosarVersion::Autosar_00049);
473        let root = model.root_element();
474        assert_eq!(root.element_name(), ElementName::Autosar);
475    }
476
477    #[test]
478    fn create_model_from_file() {
479        let tempdir = tempfile::tempdir().unwrap();
480        let filename = tempdir.path().join("test.arxml");
481
482        // write a new arxml file to disk
483        let model1 = AutosarModelAbstraction::create(filename.clone(), AutosarVersion::LATEST);
484        model1.write().unwrap();
485
486        // create a new model from the file
487        let model2 = AutosarModelAbstraction::from_file(filename).unwrap();
488        let root = model2.root_element();
489        assert_eq!(root.element_name(), ElementName::Autosar);
490    }
491
492    #[test]
493    fn model_files() {
494        let model = AutosarModelAbstraction::create("file1.arxml", AutosarVersion::Autosar_00049);
495        let file = model.create_file("file2.arxml", AutosarVersion::Autosar_00049).unwrap();
496        let files: Vec<_> = model.files().collect();
497        assert_eq!(files.len(), 2);
498        assert_eq!(files[1], file);
499    }
500
501    #[test]
502    fn model_load_file() {
503        let tempdir = tempfile::tempdir().unwrap();
504        let filename = tempdir.path().join("test.arxml");
505
506        // write a new arxml file to disk
507        let model = AutosarModelAbstraction::create(filename.clone(), AutosarVersion::LATEST);
508        model.write().unwrap();
509
510        // load the file into a new model
511        let model = AutosarModelAbstraction::new(AutosarModel::new());
512        let (_file, errors) = model.load_file(filename, true).unwrap();
513        assert!(errors.is_empty());
514    }
515
516    #[test]
517    fn model_packages() {
518        let model = AutosarModelAbstraction::create("filename", AutosarVersion::Autosar_00049);
519        let package = model.get_or_create_package("/package").unwrap();
520        let package2 = model.get_or_create_package("/other_package").unwrap();
521        model.get_or_create_package("/other_package/sub_package").unwrap();
522        let packages: Vec<_> = model.packages().collect();
523        assert_eq!(packages.len(), 2);
524        assert_eq!(packages[0], package);
525        assert_eq!(packages[1], package2);
526    }
527
528    #[test]
529    fn errors() {
530        let model = AutosarModel::new();
531
532        let err = AutosarAbstractionError::ConversionError {
533            element: model.root_element(),
534            dest: "TEST".to_string(),
535        };
536        let string = format!("{err}");
537        assert!(!string.is_empty());
538
539        let err = AutosarAbstractionError::InvalidPath("lorem ipsum".to_string());
540        let string = format!("{err}");
541        assert!(!string.is_empty());
542
543        let err = AutosarAbstractionError::ItemAlreadyExists;
544        let string = format!("{err}");
545        assert!(!string.is_empty());
546    }
547}