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