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}