Skip to main content

autosar_data/
lib.rs

1//! Crate autosar-data
2//!
3//! This crate provides functionality to read, modify and write Autosar arxml files,
4//! both separately and in projects consisting of multiple files.
5//!
6//! Features:
7//!
8//!  - read and write arxml files
9//!  - fully validate all data when it is loaded
10//!  - non-strict mode so that invalid but structurally sound data can be loaded
11//!  - various element operations to modify and create sub-elements, data and attributes
12//!  - support for Autosar paths and cross references
13//!  - all operations are thread safe, e.g. it is possible to load multiple files on separate threads
14//!
15//! The crate `autosar-data-abstraction` provides higher level abstractions on top of this crate, which simplify common tasks.
16//!
17//! # Examples
18//!
19//! ```no_run
20//! use autosar_data::*;
21//! # fn main() -> Result<(), AutosarDataError> {
22//! /* load a multi-file data model */
23//! let model = AutosarModel::new();
24//! let (file_1, warnings_1) = model.load_file("some_file.arxml", false)?;
25//! let (file_2, warnings_2) = model.load_file("other_file.arxml", false)?;
26//! /* load a buffer */
27//! # let buffer = b"";
28//! let (file_3, _) = model.load_buffer(buffer, "filename.arxml", true)?;
29//!
30//! /* write all files of the model */
31//! model.write()?;
32//!
33//! /* alternatively: */
34//! for file in model.files() {
35//!     let file_data = file.serialize();
36//!     // do something with file_data
37//! }
38//!
39//! /* iterate over all elements in all files */
40//! for (depth, element) in model.elements_dfs() {
41//!     if element.is_identifiable() {
42//!         /* the element is identifiable using an Autosar path */
43//!         println!("{depth}: {}, {}", element.element_name(), element.path()?);
44//!     } else {
45//!         println!("{depth}: {}", element.element_name());
46//!     }
47//! }
48//!
49//! /* get an element by its Autosar path */
50//! let pdu_element = model.get_element_by_path("/Package/Mid/PduName").unwrap();
51//!
52//! /* work with the content of elements */
53//! if let Some(length) = pdu_element
54//!     .get_sub_element(ElementName::Length)
55//!     .and_then(|elem| elem.character_data())
56//!     .and_then(|cdata| cdata.string_value())
57//! {
58//!     println!("Pdu Length: {length}");
59//! }
60//!
61//! /* modify the attributes of an element */
62//! pdu_element.set_attribute_string(AttributeName::Uuid, "12ab34cd-1234-1234-1234-12ab34cd56ef");
63//! pdu_element.remove_attribute(AttributeName::Uuid);
64//!
65//! # Ok(())
66//! # }
67//! ```
68//!
69//! # Example Programs
70//!
71//! Two complete example programs can be found in the examples directory of the source repository. They are:
72//!
73//!  - businfo, which extracts information about bus settings, frames, pdus and signals from an autosar ECU extract
74//!  - `generate_files`, which for each Autosar version generates an arxml file containing at least one instance of every specified element
75//!
76
77#![warn(missing_docs)]
78
79use autosar_data_specification::{AttributeSpec, CharacterDataSpec, ContentMode, ElementType};
80use fxhash::{FxBuildHasher, FxHashMap};
81use indexmap::IndexMap;
82pub use iterators::*;
83use parking_lot::RwLock;
84use parser::ArxmlParser;
85use smallvec::SmallVec;
86use std::collections::HashSet;
87use std::path::{Path, PathBuf};
88use std::sync::{Arc, Weak};
89use std::{fs::File, io::Read};
90use thiserror::Error;
91
92mod arxmlfile;
93mod autosarmodel;
94mod chardata;
95mod element;
96mod elementraw;
97mod iterators;
98mod lexer;
99mod parser;
100
101// allow public access to the error sub-types
102pub use lexer::ArxmlLexerError;
103pub use parser::ArxmlParserError;
104
105// reexport some of the info from the specification
106pub use autosar_data_specification::AttributeName;
107pub use autosar_data_specification::AutosarVersion;
108pub use autosar_data_specification::ElementName;
109pub use autosar_data_specification::EnumItem;
110
111type FxIndexMap<K, V> = IndexMap<K, V, FxBuildHasher>;
112
113// internal helpers for keeping the path-derived caches of the model consistent
114pub(crate) use autosarmodel::PathRemap;
115
116/// `AutosarModel` is the top level data type in the autosar-data crate.
117///
118/// An instance of `AutosarModel` is required for all other operations.
119///
120/// The model contains the hierarchy of Autosar elements. It can be created manually or loaded from one or more arxml files.
121/// It stores the association between elements and files.
122/// In addition, this top-level structure provides caching of Autosar paths, to allow quick resolution of cross-references.
123#[derive(Clone)]
124pub struct AutosarModel(Arc<RwLock<AutosarModelRaw>>);
125
126// Weak reference to an instance of AutosarModel
127#[derive(Clone)]
128pub(crate) struct WeakAutosarModel(Weak<RwLock<AutosarModelRaw>>);
129
130impl Default for WeakAutosarModel {
131    /// a weak reference that never resolves to a model; used by elements that have no model
132    fn default() -> Self {
133        WeakAutosarModel(Weak::new())
134    }
135}
136
137/// The inner autosar data model (unlocked)
138///
139/// The model contains the hierarchy of Autosar elements. It can be created manually or loaded from one or more arxml files.
140/// It stores the association between elements and files.
141/// In addition, this top-level structure provides caching of Autosar paths, to allow quick resolution of cross-references.
142pub(crate) struct AutosarModelRaw {
143    root_element: Element,
144    /// The list of files making up the model.
145    /// It has its own lock, because all operations that modify the set of files (`load_buffer`,
146    /// `create_file`, `remove_file`) hold this lock across multiple acquisitions of the model
147    /// lock.
148    files: Arc<parking_lot::Mutex<Vec<ArxmlFile>>>,
149    /// `identifiables` is a `HashMap` of all named elements, needed to resolve references without doing a full search.
150    identifiables: FxIndexMap<String, WeakElement>,
151    /// `reference_origins` is a `HashMap` of all referencing elements, both absolute and relative ones,
152    /// indexed by the absolute Autosar path of the element they refer to. For an absolute reference that
153    /// is simply its character data; for a relative reference it is the result of resolving the relative
154    /// path against the reference base named by its BASE attribute.
155    reference_origins: FxHashMap<String, Vec<WeakElement>>, // target path -> [referencing element]*
156    /// `relative_references` maps each referencing element which has a BASE attribute to the key it is
157    /// currently registered under in `reference_origins`. The value is `None` while its reference base
158    /// is not in scope: such a reference has no target path, and is absent from `reference_origins`.
159    ///
160    /// This reverse index exists because the target path of a relative reference frequently cannot be
161    /// recomputed at the moment it is needed: de-registration happens while element locks are held and
162    /// after the element has been detached from the tree, when its base can no longer be resolved.
163    relative_references: FxHashMap<WeakElement, Option<String>>, // referencing element -> target path
164}
165
166/// The error type `AutosarDataError` wraps all errors that can be generated anywhere in the crate
167#[derive(Error, Debug)]
168#[non_exhaustive]
169pub enum AutosarDataError {
170    /// `IoErrorRead`: An `IoError` that occurred while reading a file
171    #[error("Failed to read {}: {ioerror}", .filename.to_string_lossy())]
172    IoErrorRead {
173        /// The filename that caused the error
174        filename: PathBuf,
175        /// The underlying `std::io::Error`
176        ioerror: std::io::Error,
177    },
178
179    /// `IoErrorOpen`: an `IoError` that occurs while opening a file
180    #[error("Failed to open {}: {ioerror}", .filename.to_string_lossy())]
181    IoErrorOpen {
182        /// The filename that caused the error
183        filename: PathBuf,
184        /// The underlying `std::io::Error`
185        ioerror: std::io::Error,
186    },
187
188    /// `IoErrorWrite`: An `IoError` that occurred while writing a file
189    #[error("Failed to write {}: {ioerror}", .filename.to_string_lossy())]
190    IoErrorWrite {
191        /// The filename that caused the error
192        filename: PathBuf,
193        /// The underlying `std::io::Error`
194        ioerror: std::io::Error,
195    },
196
197    /// `DuplicateFilenameError`: The model can't contain two files with identical names
198    #[error("Could not {verb} file {}: A file with this name is already loaded", .filename.to_string_lossy())]
199    DuplicateFilenameError {
200        /// description of the operation that failed
201        verb: &'static str,
202        /// The filename that caused the error
203        filename: PathBuf,
204    },
205
206    /// `LexerError`: An error originating in the lexer, such as unclosed strings, mismatched '<' and '>', etc
207    #[error("Failed to tokenize {} on line {line}: {source}", .filename.to_string_lossy())]
208    LexerError {
209        /// The filename that caused the error
210        filename: PathBuf,
211        /// The line number where the error occurred
212        line: usize,
213        /// The underlying `ArxmlLexerError`
214        source: ArxmlLexerError,
215    },
216
217    /// `ParserError`: A parser error
218    #[error("Failed to parse {}:{line}: {source}", .filename.to_string_lossy())]
219    ParserError {
220        /// The filename that caused the error
221        filename: PathBuf,
222        /// The line number where the error occurred
223        line: usize,
224        /// The underlying `ArxmlParserError`
225        source: ArxmlParserError,
226    },
227
228    /// A file could not be loaded into the model, because the Autosar paths of the new data overlapped with the Autosar paths of the existing data
229    #[error("Loading failed: element path {path} of new data in {} overlaps with the existing loaded data", .filename.to_string_lossy())]
230    OverlappingDataError {
231        /// The filename that caused the error
232        filename: PathBuf,
233        /// Autosar path of the element that caused the error
234        path: String,
235    },
236
237    /// An operation failed because one of the elements involved is in the deleted state and will be freed once its reference count reaches zero
238    #[error("Operation failed: the item has been deleted")]
239    ItemDeleted,
240
241    /// A sub element could not be created at or moved to the given position
242    #[error("Invalid position for an element of this kind")]
243    InvalidPosition,
244
245    /// The Autosar version of the new file or element did not match the version already in use
246    #[error("Version mismatch between existing {} and new {}", .version_cur, .version_new)]
247    VersionMismatch {
248        /// The current version of the model
249        version_cur: AutosarVersion,
250        /// The version of the new file or element
251        version_new: AutosarVersion,
252    },
253
254    /// The Autosar version is not compatible with the data
255    #[error("Version {} is not compatible with the element data", .version)]
256    VersionIncompatibleData {
257        /// The incompatible version
258        version: AutosarVersion,
259    },
260
261    /// A function that only applies to identifiable elements was called on an element which is not identifiable
262    #[error("The element at {} is not identifiable", .xmlpath)]
263    ElementNotIdentifiable {
264        /// The "xml path" (a string representation of the path to the element) where the error occurred
265        xmlpath: String,
266    },
267
268    /// An item name is required to perform this action
269    #[error("An item name is required for element {}", .element)]
270    ItemNameRequired {
271        /// The element where the item name is required
272        element: ElementName,
273    },
274
275    /// The element has the wrong content type for the requested operation, e.g. inserting elements when the content type only allows character data
276    #[error("Incorrect content type for element {}", .element)]
277    IncorrectContentType {
278        /// The element where the content type is incorrect
279        element: ElementName,
280    },
281
282    /// An element accepts character data, but the given character data is not valid for this element
283    #[error("Invalid character data for element {}: {}", .element, .value)]
284    InvalidCharacterData {
285        /// The element where the character data is invalid
286        element: ElementName,
287        /// The invalid character data
288        value: String,
289    },
290
291    /// Could not insert a sub element, because it conflicts with an existing sub element
292    #[error("Element insertion conflict: {} could not be inserted in {} ({})", .element, .parent, .parent_path)]
293    ElementInsertionConflict {
294        /// The name of the parent element
295        parent: ElementName,
296        /// The name of the element that could not be inserted
297        element: ElementName,
298        /// path of the parent element
299        parent_path: String,
300    },
301
302    /// The `ElementName` is not a valid sub element according to the specification.
303    #[error("Element {} is not a valid sub element of {}", .element, .parent)]
304    InvalidSubElement {
305        /// The name of the parent element
306        parent: ElementName,
307        /// The name of the element that is not a valid sub element
308        element: ElementName,
309    },
310
311    /// Remove operation failed: the given element is not a sub element of the element from which it was supposed to be removed
312    #[error("element {} not found in parent {}", .target, .parent)]
313    ElementNotFound {
314        /// The name of the element that was not found
315        target: ElementName,
316        /// The name of the parent element
317        parent: ElementName,
318    },
319
320    /// [`Element::remove_sub_element`] cannot remove the SHORT-NAME of identifiable elements, as this would render the data invalid
321    #[error("the SHORT-NAME sub element may not be removed")]
322    ShortNameRemovalForbidden,
323
324    /// The AUTOSAR root element cannot be removed from the last file containing it, since deleting it is not permitted
325    #[error("the AUTOSAR root element may not be removed from the last file containing it")]
326    RootElementRemovalForbidden,
327
328    /// get/set reference target was called for an element that is not a reference
329    #[error("The current element is not a reference")]
330    NotReferenceElement,
331
332    /// The reference is invalid
333    #[error("The reference is not valid")]
334    InvalidReference,
335
336    /// The reference base of a relative reference is not valid (not a valid label, or not a prefix of the target element)
337    #[error("The reference base is not valid")]
338    InvalidReferenceBase,
339
340    /// An element could not be renamed, since this item name is already used by a different element
341    #[error("Duplicate item name {} in {}", .item_name, .element)]
342    DuplicateItemName {
343        /// The name of the element that could not be renamed
344        element: ElementName,
345        /// The target name that caused the error
346        item_name: String,
347    },
348
349    /// Cannot move an element into its own sub element
350    #[error("Cannot move an element into its own sub element")]
351    ForbiddenMoveToSubElement,
352
353    /// Cannot copy an element (or a hierarchy including the element) into itself
354    #[error("Cannot create a copy that includes the destination")]
355    ForbiddenCopyOfParent,
356
357    /// A parent element is currently locked by a different operation. The operation was aborted to avoid a deadlock.
358    #[error("A parent element is currently locked by a different operation")]
359    ParentElementLocked,
360
361    /// The attribute is invalid here
362    #[error("The attribute is not valid for this element")]
363    InvalidAttribute,
364
365    /// The attribute value is invalid
366    #[error("The given value is not valid for this attribute")]
367    InvalidAttributeValue,
368
369    /// The file is from a different model and may not be used in this operation
370    #[error("The file is from a different model and may not be used in this operation")]
371    InvalidFile,
372
373    /// The file is empty and cannot be serialized
374    #[error("The file is empty and cannot be serialized")]
375    EmptyFile,
376
377    /// The newly loaded file diverges from the combined model on an element which is not splittable according to the metamodel
378    #[error("The new file could not be merged, because it diverges from the model on non-splittable element {}", .path)]
379    InvalidFileMerge {
380        /// The path of the element where the merge failed
381        path: String,
382    },
383
384    /// The operation cannot be completed because the model does not contain any files
385    #[error("The operation cannot be completed because the model does not contain any files")]
386    NoFilesInModel,
387
388    /// Modifying the fileset of this element is not allowed
389    #[error(
390        "Modifying the fileset of this element is not allowed, because the parent of the element is not marked as splittable"
391    )]
392    FilesetModificationForbidden,
393}
394
395/// An Autosar arxml file
396#[derive(Clone)]
397pub struct ArxmlFile(Arc<RwLock<ArxmlFileRaw>>);
398
399/// Weak reference to an arxml file
400///
401/// (see the documentation of [`std::sync::Arc`] for an explanation of weak references)
402#[derive(Clone)]
403pub struct WeakArxmlFile(Weak<RwLock<ArxmlFileRaw>>);
404
405/// The data of an arxml file
406pub(crate) struct ArxmlFileRaw {
407    pub(crate) version: AutosarVersion,
408    model: WeakAutosarModel,
409    pub(crate) filename: PathBuf,
410    pub(crate) xml_standalone: Option<bool>, // preserve the xml standalone attribute
411}
412
413/// An arxml element
414///
415/// This is a wrapper type which provides all the necessary manipulation functions.
416#[derive(Clone)]
417pub struct Element(Arc<RwLock<ElementRaw>>);
418
419/// Weak reference to an Element
420///
421/// (see the documentation of [`std::sync::Arc`] for an explanation of weak references)
422///
423/// This `WeakElement` can be held indefinitely without forcing the referenced data to remain valid.
424/// When access is needed, the method `upgrade()` will attempt to get a strong reference and return an [Element]
425#[derive(Clone)]
426pub struct WeakElement(Weak<RwLock<ElementRaw>>);
427
428/// The data of an arxml element
429pub(crate) struct ElementRaw {
430    pub(crate) parent: ElementOrModel,
431    pub(crate) elemname: ElementName,
432    pub(crate) elemtype: ElementType,
433    pub(crate) content: SmallVec<[ElementContent; 4]>,
434    pub(crate) attributes: SmallVec<[Attribute; 1]>,
435    /// the files that this element is a member of, or `None` if it inherits the membership of its
436    /// parent element
437    ///
438    /// Only elements of a model that is split across several files ever restrict their membership,
439    /// so this is `None` in nearly every element. The set is boxed because a `HashSet` is 48 bytes
440    /// and `ElementRaw` exists once per element: keeping the set out of line saves 40 bytes each,
441    /// which is ~10% of the memory of a loaded model.
442    ///
443    /// An empty set means the same as `None`, so it is never stored: `is_none()` is the check for
444    /// "this element inherits its file membership". `set_file_membership` maintains this.
445    // clippy::box_collection argues that the contents of a HashSet are on the heap already, so the
446    // Box only adds an allocation. That is true, but it is not what this Box is for: the 48 byte
447    // control block of the HashSet is stored inline, and moving it out of line is the whole point.
448    #[allow(clippy::box_collection)]
449    pub(crate) file_membership: Option<Box<HashSet<WeakArxmlFile>>>,
450    pub(crate) comment: Option<String>,
451}
452
453/// A single attribute of an arxml element
454#[derive(Clone, PartialEq, Eq)]
455pub struct Attribute {
456    /// The name of the attribute
457    pub attrname: AttributeName,
458    /// The content of the attribute
459    pub content: CharacterData,
460}
461
462/// One content item inside an arxml element
463///
464/// Elements may contain other elements, character data, or a mixture of both, depending on their type.
465#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
466pub enum ElementContent {
467    /// A sub element
468    Element(Element),
469    /// Character data
470    CharacterData(CharacterData),
471}
472
473/// The enum `CharacterData` provides typed access to the content of elements and attributes
474///
475/// Example:
476///
477/// In the xml string ```<SHORT-NAME>SomeName</SHORT-NAME>``` the character data
478/// "`SomeName`" will be loaded as `CharacterData::String("SomeName`"), while the content of the
479/// attribute <... DEST="UNIT"> will be loaded as `CharacterData::Enum(EnumItem::Unit`)
480#[derive(Debug, Clone)]
481pub enum CharacterData {
482    /// Character data is an enum value
483    Enum(EnumItem),
484    /// Character data is a string
485    String(String),
486    /// Character data is an unsigned integer
487    UnsignedInteger(u64),
488    /// Character data is a floating point number
489    Float(f64),
490}
491
492/// The content type of an [Element]
493#[derive(Debug, Eq, PartialEq, Clone, Copy)]
494pub enum ContentType {
495    /// The element only contains other elements
496    Elements,
497    /// The element only contains character data
498    CharacterData,
499    /// The element contains both character data and sub elements
500    Mixed,
501}
502
503/// Holds a weak reference to either an element or an arxml file
504///
505/// This enum is used for references to the parent of each element. For all elements other than the
506/// root element, the parent is an element. The root element itself has a reference to the `ArxmlFile` structure.
507#[derive(Clone)]
508pub(crate) enum ElementOrModel {
509    /// The element has a parent element. The second field is the model that the element belongs to.
510    ///
511    /// The model is stored here instead of being found by walking up to the root element, because
512    /// `Element::model()` and `Element::min_version()` are called by every operation that creates
513    /// or modifies an element, and the walk costs several atomic operations per level.
514    /// Storing it next to the parent means it cannot be forgotten when an element is re-parented.
515    Element(WeakElement, WeakAutosarModel),
516    Model(WeakAutosarModel),
517    None, // needed while constructing the data trees, otherwise there's a chicken vs. egg problem
518}
519
520impl ElementOrModel {
521    /// the model that this parent reference leads to, or `None` for a detached element
522    pub(crate) fn model(&self) -> Option<AutosarModel> {
523        match self {
524            ElementOrModel::Element(_, model) | ElementOrModel::Model(model) => model.upgrade(),
525            ElementOrModel::None => None,
526        }
527    }
528
529    /// a weak reference to the model, for use in the parent reference of a new sub element
530    pub(crate) fn weak_model(&self) -> WeakAutosarModel {
531        match self {
532            ElementOrModel::Element(_, model) | ElementOrModel::Model(model) => model.clone(),
533            ElementOrModel::None => WeakAutosarModel::default(),
534        }
535    }
536}
537
538/// Possible kinds of compatibility errors that can be found by `ArxmlFile::check_version_compatibility()`
539#[derive(Debug, PartialEq, Clone)]
540pub enum CompatibilityError {
541    /// The element is not allowed in the target version
542    IncompatibleElement {
543        /// The element that is not allowed
544        element: Element,
545        /// The version mask of the element which indicates all allowed versions
546        version_mask: u32,
547    },
548    /// The attribute is not allowed in the target version
549    IncompatibleAttribute {
550        /// The element that contains the incompatible attribute
551        element: Element,
552        /// The incompatible attribute
553        attribute: AttributeName,
554        /// The version mask of the element which indicates all versions where the attribute is allowed
555        version_mask: u32,
556    },
557    /// The attribute value is not allowed in the target version
558    IncompatibleAttributeValue {
559        /// The element that contains the incompatible attribute
560        element: Element,
561        /// The incompatible attribute
562        attribute: AttributeName,
563        /// The incompatible attribute value
564        attribute_value: String,
565        /// The version mask of the element which indicates all versions where the attribute value is allowed
566        version_mask: u32,
567    },
568}
569
570/// information about a sub element
571///
572/// This structure is returned by [`Element::list_valid_sub_elements()`]
573#[derive(Debug, PartialEq, Clone)]
574pub struct ValidSubElementInfo {
575    /// name of the potential sub element
576    pub element_name: ElementName,
577    /// is the sub element named, i.e. does it need to be created with [`Element::create_named_sub_element()`]
578    pub is_named: bool,
579    /// is the sub element currently allowed, given the existing content of the element. Note that some sub elements are mutually exclusive.
580    pub is_allowed: bool,
581}
582
583const CHECK_FILE_SIZE: usize = 4096; // 4kb
584
585/// Check a file to see if it looks like an arxml file
586///
587/// Reads the beginning of the given file and checks if the data starts with a valid arxml header.
588/// If a header is found it immediately returns true and does not check any further data
589///
590/// The function returns false if the file cannot be read or if the data does not start with an arxml header
591///
592/// # Parameters
593/// - filename: name of the file to check
594///
595/// # Example
596///
597/// ```
598/// # let filename = "";
599/// if autosar_data::check_file(filename) {
600///     // it looks like an arxml file
601/// }
602/// ```
603pub fn check_file<P: AsRef<Path>>(filename: P) -> bool {
604    let mut buffer: [u8; CHECK_FILE_SIZE] = [0; CHECK_FILE_SIZE];
605
606    if File::open(filename).and_then(|mut file| file.read(&mut buffer)).is_ok() {
607        check_buffer(&buffer)
608    } else {
609        false
610    }
611}
612
613/// Check a buffer to see if the content looks like arxml data
614///
615/// The function returns true if the buffer starts with a valid arxml header (after skipping whitespace and comments).
616/// This function does not check anything after the header.
617///
618/// # Parameters
619/// - buffer: u8 slice containing the data to check
620///
621/// # Example
622/// ```
623/// # let buffer = Vec::new();
624/// if autosar_data::check_buffer(&buffer) {
625///     // it looks like arxml data
626/// }
627/// ```
628#[must_use]
629pub fn check_buffer(buffer: &[u8]) -> bool {
630    let mut parser = ArxmlParser::new(PathBuf::from("none"), buffer, false);
631    parser.check_arxml_header()
632}
633
634/// Custom Debug implementation for `Attribute`, in order to provide better formatting
635impl std::fmt::Debug for Attribute {
636    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
637        write!(f, "Attribute: {:?} = {:?}", self.attrname, self.content)
638    }
639}
640
641/// provide PartialOrd for attributes; this is used while sorting elements
642impl PartialOrd for Attribute {
643    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
644        Some(self.cmp(other))
645    }
646}
647
648/// provide Ord for attributes; this is used while sorting elements
649impl Ord for Attribute {
650    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
651        self.attrname
652            .to_str()
653            .cmp(other.attrname.to_str())
654            .then(self.content.cmp(&other.content))
655    }
656}
657
658/// Convert a ContentMode (specification) to a ContentType (runtime)
659impl From<ContentMode> for ContentType {
660    fn from(mode: ContentMode) -> Self {
661        match mode {
662            ContentMode::Sequence => ContentType::Elements,
663            ContentMode::Choice => ContentType::Elements,
664            ContentMode::Bag => ContentType::Elements,
665            ContentMode::Characters => ContentType::CharacterData,
666            ContentMode::Mixed => ContentType::Mixed,
667        }
668    }
669}
670
671#[cfg(test)]
672mod test {
673    use std::{error::Error, io::Write, path::PathBuf};
674    use tempfile::tempdir;
675
676    use crate::*;
677
678    #[test]
679    fn error_traits() {
680        let err = AutosarDataError::ParserError {
681            filename: PathBuf::from("filename.arxml"),
682            line: 123,
683            source: crate::parser::ArxmlParserError::InvalidArxmlFileHeader,
684        };
685        assert!(err.source().is_some());
686        let errstr = format!("{err}");
687        let errdbg = format!("{err:#?}");
688        assert!(errstr != errdbg);
689    }
690
691    #[test]
692    fn test_check_file() {
693        let dir = tempdir().unwrap();
694
695        // called on a directory rather than a file -> false
696        assert!(!check_file(dir.path()));
697
698        // nonexistent file -> false
699        let nonexistent = dir.path().with_file_name("nonexistent.arxml");
700        assert!(!check_file(nonexistent));
701
702        // arbitrary non-arxml data -> false
703        let not_arxml_file = dir.path().with_file_name("not_arxml.bin");
704        File::create(&not_arxml_file)
705            .and_then(|mut file| write!(file, "text"))
706            .unwrap();
707        assert!(!check_file(not_arxml_file));
708
709        // file containing a valid arxml header -> true
710        let header = r#"<?xml version="1.0" encoding="utf-8"?>
711        <!-- comment --><!-- comment 2 -->
712        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">"#;
713        let arxml_file = dir.path().with_file_name("file.arxml");
714        File::create(&arxml_file)
715            .and_then(|mut file| file.write(header.as_bytes()))
716            .unwrap();
717        assert!(check_file(arxml_file));
718
719        assert!(check_buffer(header.as_bytes()));
720    }
721
722    #[test]
723    fn attribute_order() {
724        // attribute ordering: first by name, then by value
725        let a1 = Attribute {
726            attrname: AttributeName::Uuid,
727            content: CharacterData::String("Value1".to_string()),
728        };
729
730        let a2 = Attribute {
731            attrname: AttributeName::Uuid,
732            content: CharacterData::String("Value2".to_string()),
733        };
734        assert!(a1 < a2);
735
736        let a3 = Attribute {
737            attrname: AttributeName::T,
738            content: CharacterData::String("xyz".to_string()),
739        };
740        assert!(a3 < a1);
741
742        // PartialOrd
743        assert!(a1.partial_cmp(&a2) == Some(std::cmp::Ordering::Less));
744    }
745}