Skip to main content

edifact_rs/
model.rs

1use smallvec::SmallVec;
2use std::borrow::Cow;
3
4/// A half-open byte span within an EDIFACT payload.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7pub struct Span {
8    /// Start byte offset (inclusive).
9    pub start: usize,
10    /// End byte offset (exclusive).
11    pub end: usize,
12}
13
14impl Span {
15    #[inline]
16    /// Construct a span from inclusive start and exclusive end offsets.
17    pub const fn new(start: usize, end: usize) -> Self {
18        Self { start, end }
19    }
20
21    #[inline]
22    /// Shift the span by `delta` bytes.
23    ///
24    /// Uses saturating addition to avoid integer overflow on malformed input.
25    pub const fn offset(self, delta: usize) -> Self {
26        Self {
27            start: self.start.saturating_add(delta),
28            end: self.end.saturating_add(delta),
29        }
30    }
31
32    /// Length of the span in bytes.
33    ///
34    /// In debug builds, asserts `end >= start` (inverted spans are a bug).
35    /// In release builds, returns 0 for inverted spans rather than panicking,
36    /// so a single corrupt span does not abort an entire validation run.
37    #[inline]
38    pub fn len(self) -> usize {
39        debug_assert!(
40            self.end >= self.start,
41            "Span::len: end ({}) < start ({})",
42            self.end,
43            self.start
44        );
45        self.end.saturating_sub(self.start)
46    }
47
48    /// Returns `true` if the span covers zero bytes.
49    #[inline]
50    pub const fn is_empty(self) -> bool {
51        self.start == self.end
52    }
53}
54
55impl std::fmt::Display for Span {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        write!(f, "{}..{}", self.start, self.end)
58    }
59}
60
61/// A single EDIFACT segment, borrowing its data from the source input.
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct Segment<'a> {
64    /// Segment tag, usually three uppercase letters.
65    pub tag: &'a str,
66    /// Span covering the whole segment payload.
67    pub span: Span,
68    /// Span covering only the segment tag.
69    pub tag_span: Span,
70    /// Segment elements in positional order.
71    pub elements: Vec<Element<'a>>,
72}
73
74impl<'a> Segment<'a> {
75    #[inline]
76    /// Construct a segment with default spans.
77    pub fn new(tag: &'a str, elements: Vec<Element<'a>>) -> Self {
78        Self {
79            tag,
80            span: Span::default(),
81            tag_span: Span::default(),
82            elements,
83        }
84    }
85
86    /// Return the element at position `n` (0-indexed), if it exists.
87    #[inline]
88    pub fn get_element(&self, n: usize) -> Option<&Element<'a>> {
89        self.elements.get(n)
90    }
91
92    /// Shorthand: get component 0 of element `n` — the most common access pattern.
93    #[inline]
94    pub fn element_str(&self, n: usize) -> Option<&str> {
95        self.elements.get(n)?.get_component(0)
96    }
97
98    /// Get component `comp` of element `elem` (both 0-based), or `None` if absent.
99    ///
100    /// Mirrors [`OwnedSegment::component_str`], eliminating the need to chain
101    /// `get_element(elem)?.get_component(comp)` in rule closures.
102    #[inline]
103    pub fn component_str(&self, elem: usize, comp: usize) -> Option<&str> {
104        self.elements.get(elem)?.get_component(comp)
105    }
106
107    /// Return the byte span of the element at position `n`, if it exists.
108    #[inline]
109    pub fn element_span(&self, n: usize) -> Option<Span> {
110        Some(self.elements.get(n)?.span)
111    }
112}
113
114/// A data element, which may have one or more component values.
115///
116/// Uses [`SmallVec`] with an inline capacity of 4 to avoid heap allocation
117/// for the common case (≤ 4 components).  Component values borrow from the
118/// original input; if the value contained a release-character sequence the
119/// resolved string is stored as an owned [`Cow::Owned`] variant instead of
120/// using `Box::leak`.
121///
122/// Each entry is a `(value, span)` pair, guaranteeing that the component
123/// string and its byte span are always in sync.
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct Element<'a> {
126    /// Span covering the whole element.
127    pub span: Span,
128    /// Element components in positional order, each paired with its byte span.
129    pub components: SmallVec<[(Cow<'a, str>, Span); 4]>,
130}
131
132impl<'a> Element<'a> {
133    /// Return the component at position `n` (0-indexed), if it exists.
134    #[inline]
135    pub fn get_component(&self, n: usize) -> Option<&str> {
136        self.components.get(n).map(|(c, _)| c.as_ref())
137    }
138
139    /// Return the component at position `n`, or `""` if absent.
140    #[inline]
141    pub fn component_or_empty(&self, n: usize) -> &str {
142        self.components
143            .get(n)
144            .map(|(c, _)| c.as_ref())
145            .unwrap_or("")
146    }
147
148    /// Return the byte span of the component at position `n`, if it exists.
149    #[inline]
150    pub fn component_span(&self, n: usize) -> Option<Span> {
151        self.components.get(n).map(|(_, s)| *s)
152    }
153
154    /// Convenience constructor: wraps string literals as borrowed components.
155    ///
156    /// Useful in tests and when constructing segments for writing.
157    pub fn of(components: &[&'a str]) -> Self {
158        Self {
159            span: Span::default(),
160            components: components
161                .iter()
162                .copied()
163                .map(|c| (Cow::Borrowed(c), Span::default()))
164                .collect(),
165        }
166    }
167}
168
169/// Owned data element used by reader-based parsing APIs.
170///
171/// Each entry in `components` is a `(value, span)` pair, keeping the string
172/// and its byte span structurally in sync.
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub struct OwnedElement {
175    /// Span covering the whole element.
176    pub span: Span,
177    /// Owned element components in positional order, each paired with its byte span.
178    pub components: SmallVec<[(String, Span); 4]>,
179}
180
181impl OwnedElement {
182    #[inline]
183    /// Shift all stored spans by `delta` bytes.
184    pub fn offset(mut self, delta: usize) -> Self {
185        self.span = self.span.offset(delta);
186        for (_, span) in &mut self.components {
187            *span = span.offset(delta);
188        }
189        self
190    }
191}
192
193impl<'a> From<Element<'a>> for OwnedElement {
194    fn from(value: Element<'a>) -> Self {
195        Self {
196            span: value.span,
197            components: value
198                .components
199                .into_iter()
200                .map(|(c, s)| (c.into_owned(), s))
201                .collect(),
202        }
203    }
204}
205
206/// Owned segment used by reader-based parsing APIs.
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct OwnedSegment {
209    /// Segment tag, usually three uppercase letters.
210    pub tag: String,
211    /// Span covering the whole segment payload.
212    pub span: Span,
213    /// Span covering only the segment tag.
214    pub tag_span: Span,
215    /// Owned segment elements in positional order.
216    pub elements: Vec<OwnedElement>,
217}
218
219/// Zero-allocation view of an [`OwnedElement`].
220///
221/// Implements the same accessor methods as [`Element`] without constructing
222/// any intermediate `SmallVec` or `Cow` values.  Use this when you hold an
223/// `&OwnedSegment` reference and want to inspect element data without the
224/// `Vec<Element>` allocation that [`OwnedSegment::as_borrowed`] incurs.
225///
226/// Construct via `BorrowedElement::from(&owned_element)` or through
227/// [`BorrowedSegment::get_element`].
228#[derive(Debug, Clone, Copy)]
229pub struct BorrowedElement<'a>(pub(crate) &'a OwnedElement);
230
231impl<'a> From<&'a OwnedElement> for BorrowedElement<'a> {
232    #[inline]
233    fn from(elem: &'a OwnedElement) -> Self {
234        BorrowedElement(elem)
235    }
236}
237
238impl<'a> BorrowedElement<'a> {
239    /// Return the component at position `n` (0-indexed), if it exists.
240    #[inline]
241    pub fn get_component(&self, n: usize) -> Option<&'a str> {
242        self.0.components.get(n).map(|(s, _)| s.as_str())
243    }
244
245    /// Return the component at position `n`, or `""` if absent.
246    #[inline]
247    pub fn component_or_empty(&self, n: usize) -> &'a str {
248        self.0
249            .components
250            .get(n)
251            .map(|(s, _)| s.as_str())
252            .unwrap_or("")
253    }
254
255    /// Return the byte span of the component at position `n`, if it exists.
256    #[inline]
257    pub fn component_span(&self, n: usize) -> Option<Span> {
258        self.0.components.get(n).map(|(_, s)| *s)
259    }
260
261    /// The byte span covering the whole element.
262    #[inline]
263    pub fn span(&self) -> Span {
264        self.0.span
265    }
266
267    /// Number of components in this element.
268    #[inline]
269    pub fn len(&self) -> usize {
270        self.0.components.len()
271    }
272
273    /// Returns `true` if this element has no components.
274    #[inline]
275    pub fn is_empty(&self) -> bool {
276        self.0.components.is_empty()
277    }
278
279    /// Iterate over all component strings.
280    #[inline]
281    pub fn iter(&self) -> impl Iterator<Item = &'a str> {
282        self.0.components.iter().map(|(c, _)| c.as_str())
283    }
284}
285
286/// Zero-allocation view of an [`OwnedSegment`].
287///
288/// Implements the same accessor methods as [`Segment`] without constructing
289/// a `Vec<Element>`.  Use this when you hold an `&OwnedSegment` reference and
290/// want to read data without the allocations incurred by
291/// [`OwnedSegment::as_borrowed`].
292///
293/// # Construction
294///
295/// The idiomatic way to obtain a `BorrowedSegment` is via [`OwnedSegment::borrow`]
296/// or the [`From`] impl:
297///
298/// ```rust
299/// use edifact_rs::{BorrowedSegment, OwnedSegment, Span};
300///
301/// let seg = OwnedSegment {
302///     tag: "BGM".into(),
303///     span: Span::new(0, 3),
304///     tag_span: Span::new(0, 3),
305///     elements: vec![],
306/// };
307/// let borrowed = BorrowedSegment::from(&seg);
308/// assert_eq!(borrowed.tag(), "BGM");
309/// ```
310///
311/// The `'a` lifetime is tied to the referent — you cannot outlive the
312/// `OwnedSegment` you borrowed from.
313#[derive(Debug, Clone, Copy)]
314pub struct BorrowedSegment<'a>(pub(crate) &'a OwnedSegment);
315
316impl<'a> From<&'a OwnedSegment> for BorrowedSegment<'a> {
317    #[inline]
318    fn from(seg: &'a OwnedSegment) -> Self {
319        BorrowedSegment(seg)
320    }
321}
322
323impl<'a> BorrowedSegment<'a> {
324    /// The segment tag (e.g. `"BGM"`).
325    #[inline]
326    pub fn tag(&self) -> &'a str {
327        &self.0.tag
328    }
329
330    /// Byte span covering the whole segment.
331    #[inline]
332    pub fn span(&self) -> Span {
333        self.0.span
334    }
335
336    /// Byte span covering only the segment tag.
337    #[inline]
338    pub fn tag_span(&self) -> Span {
339        self.0.tag_span
340    }
341
342    /// Return the element at position `n` (0-indexed), if it exists.
343    #[inline]
344    pub fn get_element(&self, n: usize) -> Option<BorrowedElement<'a>> {
345        self.0.elements.get(n).map(BorrowedElement)
346    }
347
348    /// Shorthand: first component of element `n` — the most common access pattern.
349    #[inline]
350    pub fn element_str(&self, n: usize) -> Option<&'a str> {
351        self.0
352            .elements
353            .get(n)?
354            .components
355            .first()
356            .map(|(c, _)| c.as_str())
357    }
358
359    /// Get component `comp` of element `elem` (both 0-based), or `None` if absent.
360    ///
361    /// Mirrors [`OwnedSegment::component_str`].
362    #[inline]
363    pub fn component_str(&self, elem: usize, comp: usize) -> Option<&'a str> {
364        self.0
365            .elements
366            .get(elem)?
367            .components
368            .get(comp)
369            .map(|(c, _)| c.as_str())
370    }
371
372    /// Return the byte span of the element at position `n`, if it exists.
373    #[inline]
374    pub fn element_span(&self, n: usize) -> Option<Span> {
375        Some(self.0.elements.get(n)?.span)
376    }
377
378    /// Iterate over all elements as zero-allocation views.
379    #[inline]
380    pub fn elements(&self) -> impl Iterator<Item = BorrowedElement<'a>> {
381        self.0.elements.iter().map(BorrowedElement)
382    }
383}
384
385impl OwnedSegment {
386    /// Get the first component of element `n`, or `None` if absent.
387    ///
388    /// This is the zero-allocation equivalent of `as_borrowed().element_str(n)`.
389    /// Used internally by [`crate::find_segment_owned`] and the derived
390    /// [`crate::EdifactDeserialize::edifact_deserialize_owned`] implementations.
391    #[inline]
392    pub fn element_str(&self, n: usize) -> Option<&str> {
393        self.elements
394            .get(n)?
395            .components
396            .first()
397            .map(|(s, _)| s.as_str())
398    }
399
400    /// Get component `comp` of element `elem`, or `None` if absent.
401    ///
402    /// Zero-allocation equivalent of `as_borrowed().get_element(elem)?.get_component(comp)`.
403    #[inline]
404    pub fn component_str(&self, elem: usize, comp: usize) -> Option<&str> {
405        self.elements
406            .get(elem)?
407            .components
408            .get(comp)
409            .map(|(s, _)| s.as_str())
410    }
411
412    #[inline]
413    /// Shift all stored spans by `delta` bytes.
414    pub fn offset(mut self, delta: usize) -> Self {
415        self.span = self.span.offset(delta);
416        self.tag_span = self.tag_span.offset(delta);
417        for element in &mut self.elements {
418            element.span = element.span.offset(delta);
419            for (_, span) in &mut element.components {
420                *span = span.offset(delta);
421            }
422        }
423        self
424    }
425
426    #[inline]
427    /// View this owned segment as a borrowed [`Segment`].
428    ///
429    /// **Performance note**: allocates a `Vec<Element<'_>>` on every call.
430    /// When only individual field access is needed, prefer
431    /// [`OwnedSegment::borrow`] → [`BorrowedSegment`] which is O(1).
432    /// `as_borrowed` remains necessary when the callee requires `&[Segment<'_>]`.
433    pub fn as_borrowed(&self) -> Segment<'_> {
434        Segment {
435            tag: self.tag.as_str(),
436            span: self.span,
437            tag_span: self.tag_span,
438            elements: self
439                .elements
440                .iter()
441                .map(|elem| Element {
442                    span: elem.span,
443                    components: elem
444                        .components
445                        .iter()
446                        .map(|(c, s)| (Cow::Borrowed(c.as_str()), *s))
447                        .collect(),
448                })
449                .collect(),
450        }
451    }
452
453    /// Return a zero-allocation view of this segment.
454    ///
455    /// Unlike [`as_borrowed`][OwnedSegment::as_borrowed], this is `O(1)` and
456    /// performs no heap allocation.  The view cannot be passed to APIs that
457    /// require `&[Segment<'_>]`; use [`as_borrowed`][OwnedSegment::as_borrowed]
458    /// for those call sites.
459    #[inline]
460    pub fn borrow(&self) -> BorrowedSegment<'_> {
461        BorrowedSegment(self)
462    }
463}
464
465impl<'a> From<Segment<'a>> for OwnedSegment {
466    fn from(value: Segment<'a>) -> Self {
467        Self {
468            tag: value.tag.to_string(),
469            span: value.span,
470            tag_span: value.tag_span,
471            elements: value.elements.into_iter().map(OwnedElement::from).collect(),
472        }
473    }
474}