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(¬_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}