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}