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