Skip to main content

edifact_rs_derive/
lib.rs

1//! Derive macros for `EdifactSerialize` and `EdifactDeserialize`.
2//!
3//! # Segment struct (single segment)
4//!
5//! ```ignore
6//! #[derive(EdifactSerialize, EdifactDeserialize)]
7//! #[edifact(segment = "BGM")]
8//! pub struct BgmSegment {
9//!     #[edifact(element = 0)]
10//!     pub doc_name_code: String,
11//!     #[edifact(element = 1)]
12//!     pub doc_id: String,
13//!     #[edifact(element = 2)]
14//!     pub msg_function: Option<String>,
15//! }
16//! ```
17//!
18//! # Segment struct with qualifier
19//!
20//! ```ignore
21//! #[derive(EdifactSerialize, EdifactDeserialize)]
22//! #[edifact(segment = "NAD", qualifier = "MS")]
23//! pub struct NadMs {
24//!     #[edifact(element = 1)]
25//!     pub party_id: String,
26//! }
27//! ```
28//!
29//! # Message struct (multiple segments)
30//!
31//! ```ignore
32//! #[derive(EdifactSerialize, EdifactDeserialize)]
33//! pub struct OrdersMessage {
34//!     pub bgm: BgmSegment,
35//!     pub buyer: NadMs,
36//!     #[edifact(group)]
37//!     pub lines: Vec<LinSegment>,
38//! }
39//! ```
40//!
41//! # `#[edifact(group)]` and `Vec<T>` fields
42//!
43//! The `#[edifact(group)]` attribute marks a `Vec<T>` field as a contiguous group of
44//! repeated segments.  Without the attribute, `Vec<T>` on a segment struct collects
45//! all matching segments from the window into the `Vec`.
46//!
47//! **Note**: `#[edifact(group)]` enforces two compile-time structural constraints:
48//!
49//! 1. The annotated field **must** be of type `Vec<T>` — any other type is rejected
50//!    with a clear error message.
51//! 2. `#[edifact(group)]` cannot be combined with `#[edifact(element = ...)]` or
52//!    `#[edifact(component = ...)]` — positional placement and group semantics are
53//!    mutually exclusive.
54//!
55//! At runtime the generated deserialization code collects contiguous occurrences of
56//! the inner segment type `T` into the `Vec` using
57//! `edifact_rs::helpers::contiguous_groups_by_qualifier`.
58//! This is behaviorally different from a bare `Vec<T>` without `#[edifact(group)]`,
59//! which uses `edifact_rs::helpers::find_segments_typed` and does
60//! not enforce contiguity.
61//!
62//! # `#[edifact(required)]` on `Option<T>` fields
63//!
64//! By default, `Option<T>` fields produce `None` when the element is absent.
65//! Annotating an `Option<T>` field with `#[edifact(required)]` changes this:
66//! instead of `None`, deserialization returns
67//! `edifact_rs::EdifactError::MissingRequiredElement`
68//! when the element is absent or empty.  The Rust type stays `Option<T>`, which
69//! is useful when the EDIFACT specification mandates the element but your domain
70//! model treats it as optional for other reasons.
71//!
72//! ```ignore
73//! #[derive(EdifactSerialize, EdifactDeserialize)]
74//! #[edifact(segment = "DTM")]
75//! pub struct DtmSegment {
76//!     #[edifact(element = 0)]
77//!     qualifier: String,
78//!     /// Required by the spec but kept as Option in the domain model.
79//!     #[edifact(element = 1, required)]
80//!     date_time: Option<String>,
81//!     #[edifact(element = 2)]
82//!     format_code: Option<String>,
83//! }
84//! ```
85//!
86//! # Non-`String` fields and `Display` / `FromStr`
87//! Non-`String` field types (e.g. `u32`, `bool`, your own newtype) are serialized via
88//! `Display` and deserialized via `FromStr`.  The derive macro does **not** add a
89//! compile-time bound; if the type does not implement both traits the generated code
90//! will fail to compile with a standard "trait not satisfied" error.
91//!
92//! To avoid surprises, ensure any non-`String` field type implements both:
93//! ```ignore
94//! impl std::fmt::Display for MyCode { ... }
95//! impl std::str::FromStr for MyCode { ... }
96//! ```
97
98use proc_macro::TokenStream;
99use proc_macro2::TokenStream as TokenStream2;
100use quote::quote;
101use syn::{Data, DeriveInput, Field, Fields, Type, parse_macro_input, spanned::Spanned};
102
103// ── entry points ───────────────────────────────────────────────────────────────
104
105#[proc_macro_derive(EdifactSerialize, attributes(edifact))]
106/// Derive `edifact_rs::EdifactSerialize` for segment or message structs.
107///
108/// # Limitations
109///
110/// - **No generics**: the struct must not have generic type parameters.
111/// - **No lifetime parameters**: the struct must own all its data (`String`,
112///   not `&str`).  Borrow-based structs such as `Segment<'a>` cannot use this
113///   derive macro.
114pub fn derive_edifact_serialize(input: TokenStream) -> TokenStream {
115    let input = parse_macro_input!(input as DeriveInput);
116    impl_serialize(&input)
117        .unwrap_or_else(|e| e.to_compile_error())
118        .into()
119}
120
121#[proc_macro_derive(EdifactDeserialize, attributes(edifact))]
122/// Derive `edifact_rs::EdifactDeserialize` for segment or message structs.
123///
124/// # Limitations
125///
126/// - **No generics**: the struct must not have generic type parameters.
127/// - **No lifetime parameters**: the struct must own all its data (`String`,
128///   not `&str`).  Add owned wrapper types or clone components at the
129///   deserialization site if lifetime flexibility is required.
130pub fn derive_edifact_deserialize(input: TokenStream) -> TokenStream {
131    let input = parse_macro_input!(input as DeriveInput);
132    impl_deserialize(&input)
133        .unwrap_or_else(|e| e.to_compile_error())
134        .into()
135}
136
137// ── attribute containers ───────────────────────────────────────────────────────
138
139#[derive(Default)]
140struct StructAttrs {
141    /// `#[edifact(segment = "TAG")]`
142    segment: Option<String>,
143    /// `#[edifact(qualifier = "Q")]` — element 0 value for segment matching
144    qualifier: Option<String>,
145    qualifier_span: Option<proc_macro2::Span>,
146    /// `#[edifact(qualifier_from = N)]` — zero-based element index; qualifier is dynamic at runtime.
147    qualifier_from: Option<u32>,
148    qualifier_from_span: Option<proc_macro2::Span>,
149}
150
151#[derive(Default)]
152struct FieldAttrs {
153    /// `#[edifact(element = N)]` — zero-based element index
154    element: Option<u32>,
155    element_span: Option<proc_macro2::Span>,
156    /// `#[edifact(component = N)]` — component index within the element (for composite data elements)
157    component: Option<u32>,
158    component_span: Option<proc_macro2::Span>,
159    /// `#[edifact(composite)]` — map the field as a full composite element via composite serde traits.
160    composite: bool,
161    composite_span: Option<proc_macro2::Span>,
162    /// `#[edifact(group)]` — `Vec<T>`: each item is a separate segment
163    group: bool,
164    group_span: Option<proc_macro2::Span>,
165    /// `#[edifact(qualifier = "Q")]` — message field constrained to qualifier.
166    qualifier: Option<String>,
167    qualifier_span: Option<proc_macro2::Span>,
168    /// `#[edifact(required)]` — treat an `Option<T>` field as mandatory.
169    ///
170    /// Without this attribute, `Option<T>` fields produce `None` when the element
171    /// is absent.  With `#[edifact(required)]` the deserialization emits
172    /// `EdifactError::MissingRequiredElement` instead, even though the Rust type is
173    /// still `Option<T>`.  This is useful for elements that the EDIFACT spec marks as
174    /// mandatory but which your domain model represents as optional for other reasons.
175    required: bool,
176    required_span: Option<proc_macro2::Span>,
177}
178
179// ── attribute parsing ──────────────────────────────────────────────────────────
180
181fn parse_struct_attrs(input: &DeriveInput) -> syn::Result<StructAttrs> {
182    let mut out = StructAttrs::default();
183    for attr in &input.attrs {
184        if !attr.path().is_ident("edifact") {
185            continue;
186        }
187        attr.parse_nested_meta(|meta| {
188            if meta.path.is_ident("segment") {
189                let lit = meta.value()?.parse::<syn::LitStr>()?;
190                let tag = lit.value();
191                if tag.len() != 3 || !tag.bytes().all(|b| b.is_ascii_uppercase()) {
192                    return Err(syn::Error::new(
193                        lit.span(),
194                        format!(
195                            "segment tag must be exactly 3 ASCII uppercase letters; got {tag:?}"
196                        ),
197                    ));
198                }
199                out.segment = Some(tag);
200            } else if meta.path.is_ident("qualifier") {
201                out.qualifier = Some(meta.value()?.parse::<syn::LitStr>()?.value());
202                out.qualifier_span = Some(meta.path.span());
203            } else if meta.path.is_ident("qualifier_from") {
204                let idx: u32 = meta.value()?.parse::<syn::LitInt>()?.base10_parse()?;
205                out.qualifier_from = Some(idx);
206                out.qualifier_from_span = Some(meta.path.span());
207            } else {
208                return Err(meta.error("unknown struct-level `edifact` key; expected `segment`, `qualifier`, or `qualifier_from`"));
209            }
210            Ok(())
211        })?;
212    }
213    if (out.qualifier.is_some() || out.qualifier_from.is_some()) && out.segment.is_none() {
214        return Err(syn::Error::new(
215            out.qualifier_span
216                .or(out.qualifier_from_span)
217                .unwrap_or_else(|| input.span()),
218            "#[edifact(qualifier = ...)] / #[edifact(qualifier_from = ...)] require #[edifact(segment = ...)]",
219        ));
220    }
221    if out.qualifier.is_some() && out.qualifier_from.is_some() {
222        return Err(syn::Error::new(
223            out.qualifier_from_span
224                .or(out.qualifier_span)
225                .unwrap_or_else(|| input.span()),
226            "use either #[edifact(qualifier = ...)] or #[edifact(qualifier_from = ...)], not both",
227        ));
228    }
229    Ok(out)
230}
231
232fn parse_field_attrs(field: &Field) -> syn::Result<FieldAttrs> {
233    let mut out = FieldAttrs::default();
234    for attr in &field.attrs {
235        if !attr.path().is_ident("edifact") {
236            continue;
237        }
238        attr.parse_nested_meta(|meta| {
239            if meta.path.is_ident("element") {
240                out.element = Some(meta.value()?.parse::<syn::LitInt>()?.base10_parse()?);
241                out.element_span = Some(meta.path.span());
242            } else if meta.path.is_ident("component") {
243                out.component = Some(meta.value()?.parse::<syn::LitInt>()?.base10_parse()?);
244                out.component_span = Some(meta.path.span());
245            } else if meta.path.is_ident("composite") {
246                out.composite = true;
247                out.composite_span = Some(meta.path.span());
248            } else if meta.path.is_ident("group") {
249                out.group = true;
250                out.group_span = Some(meta.path.span());
251            } else if meta.path.is_ident("qualifier") {
252                out.qualifier = Some(meta.value()?.parse::<syn::LitStr>()?.value());
253                out.qualifier_span = Some(meta.path.span());
254            } else if meta.path.is_ident("required") {
255                out.required = true;
256                out.required_span = Some(meta.path.span());
257            } else {
258                return Err(meta.error("unknown field-level `edifact` key; expected `element`, `component`, `composite`, `group`, `qualifier`, or `required`"));
259            }
260            Ok(())
261        })?;
262    }
263    Ok(out)
264}
265
266// ── type helpers ───────────────────────────────────────────────────────────────
267
268fn is_option_type(ty: &Type) -> bool {
269    matches!(ty, Type::Path(p) if p.path.segments.last().is_some_and(|s| s.ident == "Option"))
270}
271
272fn is_vec_type(ty: &Type) -> bool {
273    matches!(ty, Type::Path(p) if p.path.segments.last().is_some_and(|s| s.ident == "Vec"))
274}
275
276/// Returns `true` for the `String` path type.
277///
278/// Accepts only:
279/// - `String` (bare, single-segment)
280/// - `std::string::String` (fully qualified standard library path)
281/// - `alloc::string::String` (fully qualified alloc path for `no_std` contexts)
282///
283/// A user-defined type whose last segment is `String` but that does not match
284/// one of these three forms is **not** treated as a string type, which prevents
285/// accidental string-extraction code generation for unrelated user types.
286///
287/// **Shadowing caveat:** bare `String` (single-segment, no path prefix) is matched
288/// by name only. If a crate shadows the standard-library `String` with a local type
289/// of the same name, this function will still classify it as a string type and the
290/// derive macro will generate incorrect string-extraction code rather than a
291/// composite or element parse. To avoid this, always use the fully-qualified path
292/// (`std::string::String`) in struct fields when `String` is shadowed in scope.
293fn is_string_type(ty: &Type) -> bool {
294    let Type::Path(p) = ty else { return false };
295    // Single-segment bare "String"
296    if p.path.is_ident("String") {
297        return true;
298    }
299    // Fully-qualified std::string::String or alloc::string::String
300    let segs = &p.path.segments;
301    segs.len() == 3
302        && (segs[0].ident == "std" || segs[0].ident == "alloc")
303        && segs[1].ident == "string"
304        && segs[2].ident == "String"
305}
306
307/// Returns `true` for `&str` or `&'_ str` reference types.
308fn is_str_ref_type(ty: &Type) -> bool {
309    let Type::Reference(r) = ty else { return false };
310    matches!(r.elem.as_ref(), Type::Path(p) if p.path.is_ident("str"))
311}
312
313/// Returns `true` when `ty` is a type that can yield `&str` without allocating
314/// (i.e. `String` or `&str`).
315fn is_str_like(ty: &Type) -> bool {
316    is_string_type(ty) || is_str_ref_type(ty)
317}
318
319fn option_inner_type(ty: &Type) -> Option<&Type> {
320    let Type::Path(path) = ty else { return None };
321    let seg = path.path.segments.last()?;
322    if seg.ident != "Option" {
323        return None;
324    }
325    let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
326        return None;
327    };
328    let syn::GenericArgument::Type(inner) = args.args.first()? else {
329        return None;
330    };
331    Some(inner)
332}
333
334fn vec_inner_type(ty: &Type) -> Option<&Type> {
335    let Type::Path(path) = ty else { return None };
336    let seg = path.path.segments.last()?;
337    if seg.ident != "Vec" {
338        return None;
339    }
340    let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
341        return None;
342    };
343    let syn::GenericArgument::Type(inner) = args.args.first()? else {
344        return None;
345    };
346    Some(inner)
347}
348
349// ── named field extraction ─────────────────────────────────────────────────────
350
351fn get_named_fields(input: &DeriveInput) -> syn::Result<&syn::FieldsNamed> {
352    if !input.generics.params.is_empty() {
353        return Err(syn::Error::new(
354            input.generics.params.span(),
355            "EdifactSerialize/EdifactDeserialize do not support generic structs",
356        ));
357    }
358    match &input.data {
359        Data::Struct(s) => match &s.fields {
360            Fields::Named(f) => Ok(f),
361            _ => Err(syn::Error::new(
362                input.span(),
363                "EdifactSerialize/EdifactDeserialize only support structs with named fields",
364            )),
365        },
366        _ => Err(syn::Error::new(
367            input.span(),
368            "EdifactSerialize/EdifactDeserialize only support structs",
369        )),
370    }
371}
372
373fn validate_field_attrs(
374    ident: &syn::Ident,
375    ty: &Type,
376    attrs: &FieldAttrs,
377    is_segment_struct: bool,
378) -> syn::Result<()> {
379    if attrs.group && !is_vec_type(ty) {
380        return Err(syn::Error::new(
381            attrs.group_span.unwrap_or_else(|| ident.span()),
382            format!("field `{ident}`: #[edifact(group)] requires Vec<T>"),
383        ));
384    }
385    if attrs.group && (attrs.element.is_some() || attrs.component.is_some()) {
386        return Err(syn::Error::new(
387            attrs.group_span.unwrap_or_else(|| ident.span()),
388            format!(
389                "field `{ident}`: #[edifact(group)] cannot be combined with element/component positioning"
390            ),
391        ));
392    }
393    if attrs.composite && attrs.component.is_some() {
394        return Err(syn::Error::new(
395            attrs.component_span.unwrap_or_else(|| ident.span()),
396            format!(
397                "field `{ident}`: #[edifact(component = ...)] cannot be combined with #[edifact(composite)]"
398            ),
399        ));
400    }
401    if attrs.composite && attrs.group {
402        return Err(syn::Error::new(
403            attrs.composite_span.unwrap_or_else(|| ident.span()),
404            format!(
405                "field `{ident}`: #[edifact(composite)] cannot be combined with #[edifact(group)]"
406            ),
407        ));
408    }
409    if is_segment_struct && attrs.group {
410        return Err(syn::Error::new(
411            attrs.group_span.unwrap_or_else(|| ident.span()),
412            format!("field `{ident}`: #[edifact(group)] is only valid on message structs"),
413        ));
414    }
415    if !is_segment_struct && (attrs.element.is_some() || attrs.component.is_some()) {
416        return Err(syn::Error::new(
417            attrs
418                .element_span
419                .or(attrs.component_span)
420                .unwrap_or_else(|| ident.span()),
421            format!(
422                "field `{ident}`: element/component positioning is only valid on segment structs"
423            ),
424        ));
425    }
426    if !is_segment_struct && attrs.composite {
427        return Err(syn::Error::new(
428            attrs.composite_span.unwrap_or_else(|| ident.span()),
429            format!("field `{ident}`: #[edifact(composite)] is only valid on segment structs"),
430        ));
431    }
432    if is_segment_struct && attrs.qualifier.is_some() {
433        return Err(syn::Error::new(
434            attrs.qualifier_span.unwrap_or_else(|| ident.span()),
435            format!(
436                "field `{ident}`: #[edifact(qualifier = ...)] is only valid on message struct fields"
437            ),
438        ));
439    }
440    if attrs.qualifier.is_some() && attrs.group && !is_vec_type(ty) {
441        return Err(syn::Error::new(
442            attrs.qualifier_span.unwrap_or_else(|| ident.span()),
443            format!("field `{ident}`: qualifier-constrained groups must be Vec<T>"),
444        ));
445    }
446    if attrs.required && !is_option_type(ty) {
447        return Err(syn::Error::new(
448            attrs.required_span.unwrap_or_else(|| ident.span()),
449            format!(
450                "field `{ident}`: #[edifact(required)] only applies to Option<T> fields; \
451                 non-Option fields are always required"
452            ),
453        ));
454    }
455    if attrs.required && attrs.composite {
456        return Err(syn::Error::new(
457            attrs.required_span.unwrap_or_else(|| ident.span()),
458            format!(
459                "field `{ident}`: #[edifact(required)] cannot be combined with \
460                 #[edifact(composite)]; use a non-optional field type to require the \
461                 composite element"
462            ),
463        ));
464    }
465    if attrs.required && !is_segment_struct {
466        return Err(syn::Error::new(
467            attrs.required_span.unwrap_or_else(|| ident.span()),
468            format!(
469                "field `{ident}`: #[edifact(required)] is only valid on segment struct \
470                 element fields; to require a segment in a message struct, use a \
471                 non-optional field type"
472            ),
473        ));
474    }
475    Ok(())
476}
477
478// ── EdifactSerialize ───────────────────────────────────────────────────────────
479
480fn impl_serialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
481    let name = &input.ident;
482    let struct_attrs = parse_struct_attrs(input)?;
483    let fields = get_named_fields(input)?;
484    let is_segment_struct = struct_attrs.segment.is_some();
485
486    // Collect (field_ident, field_type, FieldAttrs).
487    let field_data: Vec<(&syn::Ident, &Type, FieldAttrs)> = fields
488        .named
489        .iter()
490        .map(|f| {
491            let attrs = parse_field_attrs(f)?;
492            let ident = f
493                .ident
494                .as_ref()
495                .ok_or_else(|| syn::Error::new_spanned(f, "only named fields are supported"))?;
496            validate_field_attrs(ident, &f.ty, &attrs, is_segment_struct)?;
497            Ok((ident, &f.ty, attrs))
498        })
499        .collect::<syn::Result<_>>()?;
500
501    let body = if let Some(seg_tag) = &struct_attrs.segment {
502        // ── Segment struct: emit one EDIFACT segment ──────────────────────────
503        // When a struct-level qualifier is declared, inject it at slot 0.
504        // Fields at (element=0, component>=1) extend it as composite components.
505        // Fields at element >= 1 are emitted as regular elements.
506        let (qualifier_emit, start_slot, elem0_comp_stmts) = if let Some(qual) =
507            &struct_attrs.qualifier
508        {
509            // Error only if a field claims element=0 with no component or component=0.
510            for (i, (ident, _, attrs)) in field_data.iter().enumerate() {
511                let elem = attrs.element.unwrap_or(i as u32);
512                let comp = attrs.component.unwrap_or(0);
513                if elem == 0 && comp == 0 {
514                    return Err(syn::Error::new(
515                        attrs
516                            .element_span
517                            .or(attrs.component_span)
518                            .unwrap_or_else(|| ident.span()),
519                        format!(
520                            "field `{}`: cannot use #[edifact(qualifier = ...)] with a field at element = 0 without component >= 1; the qualifier occupies component 0",
521                            ident
522                        ),
523                    ));
524                }
525            }
526            // Collect fields at element=0, component>0, sorted by component.
527            let mut comp_fields: Vec<(u32, usize)> = field_data
528                .iter()
529                .enumerate()
530                .filter_map(|(i, (_, _, attrs))| {
531                    let elem = attrs.element.unwrap_or(i as u32);
532                    let comp = attrs.component.unwrap_or(0);
533                    if elem == 0 && comp > 0 {
534                        Some((comp, i))
535                    } else {
536                        None
537                    }
538                })
539                .collect();
540            comp_fields.sort_by_key(|(c, _)| *c);
541            let comp_stmts: Vec<TokenStream2> = comp_fields
542                .iter()
543                .map(|(_, fi)| {
544                    let (ident, ty, _) = &field_data[*fi];
545                    emit_component_element(ident, ty)
546                })
547                .collect();
548            let q = quote! {
549                emitter.emit(::edifact_rs::EdifactEvent::Element { value: #qual })?;
550            };
551            (q, 1u32, quote! { #(#comp_stmts)* })
552        } else {
553            (quote! {}, 0u32, quote! {})
554        };
555
556        // Rebuild indexed/field_map excluding element=0 fields (handled above).
557        let regular_field_data: Vec<(u32, usize)> = field_data
558            .iter()
559            .enumerate()
560            .filter_map(|(i, (_, _, attrs))| {
561                let elem = attrs.element.unwrap_or(i as u32);
562                if elem < start_slot {
563                    None
564                } else {
565                    Some((elem, i))
566                }
567            })
568            .collect();
569        let reg_max_idx = regular_field_data
570            .iter()
571            .map(|(e, _)| *e)
572            .max()
573            .unwrap_or(start_slot.saturating_sub(1));
574        let reg_field_map: std::collections::HashMap<u32, usize> =
575            regular_field_data.iter().copied().collect();
576
577        let mut elem_stmts: Vec<TokenStream2> = Vec::new();
578        for slot in start_slot..=reg_max_idx {
579            if let Some(&fi) = reg_field_map.get(&slot) {
580                let (ident, ty, attrs) = &field_data[fi];
581                if attrs.composite {
582                    elem_stmts.push(emit_composite_field(ident, ty));
583                } else {
584                    elem_stmts.push(emit_element(ident, ty));
585                }
586            } else {
587                // Gap: emit an empty element separator.
588                elem_stmts.push(quote! {
589                    emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
590                });
591            }
592        }
593
594        quote! {
595            emitter.emit(::edifact_rs::EdifactEvent::StartSegment { tag: #seg_tag })?;
596            #qualifier_emit
597            #elem0_comp_stmts
598            #(#elem_stmts)*
599            emitter.emit(::edifact_rs::EdifactEvent::EndSegment)?;
600        }
601    } else {
602        // ── Message struct: delegate to each field ────────────────────────────
603        let stmts: Vec<TokenStream2> = field_data
604            .iter()
605            .map(|(ident, ty, attrs)| {
606                if attrs.group || is_vec_type(ty) {
607                    quote! {
608                        for __item in &self.#ident {
609                            ::edifact_rs::EdifactSerialize::edifact_serialize(__item, emitter)?;
610                        }
611                    }
612                } else {
613                    quote! {
614                        ::edifact_rs::EdifactSerialize::edifact_serialize(&self.#ident, emitter)?;
615                    }
616                }
617            })
618            .collect();
619        quote! { #(#stmts)* }
620    };
621
622    Ok(quote! {
623        impl ::edifact_rs::EdifactSerialize for #name {
624            fn edifact_serialize<__E: ::edifact_rs::EventEmitter>(
625                &self,
626                emitter: &mut __E,
627            ) -> ::core::result::Result<(), ::edifact_rs::EdifactError> {
628                #body
629                ::core::result::Result::Ok(())
630            }
631        }
632    })
633}
634
635/// Generate the token stream that emits field `ident` (of type `ty`) as one element.
636///
637/// For `String` and `&str` fields the value is emitted zero-copy via `.as_str()`
638/// (or directly).  All other types fall back to `ToString::to_string`.
639fn emit_element(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
640    if is_option_type(ty) {
641        let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
642        if inner_is_str {
643            quote! {
644                match &self.#ident {
645                    ::core::option::Option::Some(__v) => {
646                        emitter.emit(::edifact_rs::EdifactEvent::Element { value: __v.as_str() })?;
647                    }
648                    ::core::option::Option::None => {
649                        emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
650                    }
651                }
652            }
653        } else {
654            quote! {
655                match &self.#ident {
656                    ::core::option::Option::Some(__v) => {
657                        let __s = ::std::string::ToString::to_string(__v);
658                        emitter.emit(::edifact_rs::EdifactEvent::Element { value: &__s })?;
659                    }
660                    ::core::option::Option::None => {
661                        emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
662                    }
663                }
664            }
665        }
666    } else if is_string_type(ty) {
667        quote! {
668            emitter.emit(::edifact_rs::EdifactEvent::Element { value: self.#ident.as_str() })?;
669        }
670    } else if is_str_ref_type(ty) {
671        quote! {
672            emitter.emit(::edifact_rs::EdifactEvent::Element { value: self.#ident })?;
673        }
674    } else {
675        quote! {
676            {
677                let __s = ::std::string::ToString::to_string(&self.#ident);
678                emitter.emit(::edifact_rs::EdifactEvent::Element { value: &__s })?;
679            }
680        }
681    }
682}
683
684/// Generate the token stream that emits field `ident` as a composite component (`ComponentElement`).
685///
686/// For `String` and `&str` fields the value is emitted zero-copy.
687fn emit_component_element(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
688    if is_option_type(ty) {
689        let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
690        if inner_is_str {
691            quote! {
692                match &self.#ident {
693                    ::core::option::Option::Some(__v) => {
694                        emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: __v.as_str() })?;
695                    }
696                    ::core::option::Option::None => {
697                        emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: "" })?;
698                    }
699                }
700            }
701        } else {
702            quote! {
703                match &self.#ident {
704                    ::core::option::Option::Some(__v) => {
705                        let __s = ::std::string::ToString::to_string(__v);
706                        emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: &__s })?;
707                    }
708                    ::core::option::Option::None => {
709                        emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: "" })?;
710                    }
711                }
712            }
713        }
714    } else if is_string_type(ty) {
715        quote! {
716            emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: self.#ident.as_str() })?;
717        }
718    } else if is_str_ref_type(ty) {
719        quote! {
720            emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: self.#ident })?;
721        }
722    } else {
723        quote! {
724            {
725                let __s = ::std::string::ToString::to_string(&self.#ident);
726                emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: &__s })?;
727            }
728        }
729    }
730}
731
732/// Generate the token stream that emits a full composite field via `EdifactCompositeSerialize`.
733fn emit_composite_field(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
734    if is_option_type(ty) {
735        quote! {
736            match &self.#ident {
737                ::core::option::Option::Some(__v) => {
738                    ::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(__v, emitter)?;
739                }
740                ::core::option::Option::None => {
741                    emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
742                }
743            }
744        }
745    } else {
746        quote! {
747            ::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(&self.#ident, emitter)?;
748        }
749    }
750}
751
752// ── EdifactDeserialize ─────────────────────────────────────────────────────────
753
754fn impl_deserialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
755    let name = &input.ident;
756    let struct_attrs = parse_struct_attrs(input)?;
757    let fields = get_named_fields(input)?;
758    let is_segment_struct = struct_attrs.segment.is_some();
759
760    let field_data: Vec<(&syn::Ident, &Type, FieldAttrs)> = fields
761        .named
762        .iter()
763        .map(|f| {
764            let attrs = parse_field_attrs(f)?;
765            let ident = f
766                .ident
767                .as_ref()
768                .ok_or_else(|| syn::Error::new_spanned(f, "only named fields are supported"))?;
769            validate_field_attrs(ident, &f.ty, &attrs, is_segment_struct)?;
770            Ok((ident, &f.ty, attrs))
771        })
772        .collect::<syn::Result<_>>()?;
773
774    let field_names: Vec<&syn::Ident> = field_data.iter().map(|(id, _, _)| *id).collect();
775
776    let (body, owned_body, segment_tag_impl) = if let Some(seg_tag) = &struct_attrs.segment {
777        // ── Segment struct ────────────────────────────────────────────────────
778        let qualifier_guard = if let Some(qual) = &struct_attrs.qualifier {
779            quote! {
780                if __seg.element_str(0).unwrap_or("") != #qual {
781                    return ::core::result::Result::Err(
782                        ::edifact_rs::EdifactError::MissingRequiredElement {
783                            tag: #seg_tag.to_owned(),
784                            element_index: 0,
785                        }
786                    );
787                }
788            }
789        } else if let Some(idx) = struct_attrs.qualifier_from {
790            quote! {
791                match __seg.element_str(#idx as usize) {
792                    None => return ::core::result::Result::Err(
793                        ::edifact_rs::EdifactError::MissingRequiredElement {
794                            tag: #seg_tag.to_owned(),
795                            element_index: #idx as usize,
796                        }
797                    ),
798                    Some("") => return ::core::result::Result::Err(
799                        ::edifact_rs::EdifactError::InvalidFieldValue {
800                            tag: #seg_tag.to_owned(),
801                            element_index: #idx as usize,
802                            value: ::std::string::String::new(),
803                        }
804                    ),
805                    Some(__qual_val) => { let _ = __qual_val; }
806                }
807            }
808        } else {
809            quote! {}
810        };
811
812        let find_seg = if let Some(qual) = &struct_attrs.qualifier {
813            quote! {
814                ::edifact_rs::helpers::find_qualified_segment(segments, #seg_tag, #qual)
815            }
816        } else {
817            quote! {
818                ::edifact_rs::helpers::find_segment(segments, #seg_tag)
819            }
820        };
821
822        let field_inits: Vec<TokenStream2> = field_data
823            .iter()
824            .enumerate()
825            .map(|(decl_i, (ident, ty, attrs))| -> syn::Result<TokenStream2> {
826                let idx = attrs.element.unwrap_or(decl_i as u32) as usize;
827                if attrs.composite {
828                    if is_option_type(ty) {
829                        let inner_ty = option_inner_type(ty)
830                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
831                        return Ok(quote! {
832                            let #ident = match ::edifact_rs::helpers::composite_element(__seg, #idx) {
833                                ::core::option::Option::Some(__composite) => {
834                                    ::core::option::Option::Some(
835                                        <#inner_ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(__composite)?
836                                    )
837                                }
838                                ::core::option::Option::None => ::core::option::Option::None,
839                            };
840                        });
841                    }
842                    return Ok(quote! {
843                        let #ident = <#ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
844                            ::edifact_rs::helpers::composite_element(__seg, #idx).ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
845                                tag: #seg_tag.to_owned(),
846                                element_index: #idx as usize,
847                            })?
848                        )?;
849                    });
850                }
851                let component_idx: Option<usize> = attrs.component.map(|c| c as usize);
852                let value_expr = if let Some(comp) = component_idx {
853                    quote! {
854                        __seg.get_element(#idx).and_then(|__e| __e.get_component(#comp))
855                    }
856                } else {
857                    quote! { __seg.element_str(#idx) }
858                };
859                // Build the correct "missing required" error depending on whether the
860                // field targets a component within an element or a whole element.
861                let missing_required_err = if let Some(comp) = component_idx {
862                    quote! {
863                        ::edifact_rs::EdifactError::MissingRequiredComponent {
864                            tag: #seg_tag.to_owned(),
865                            element_index: #idx as usize,
866                            component_index: #comp as usize,
867                        }
868                    }
869                } else {
870                    quote! {
871                        ::edifact_rs::EdifactError::MissingRequiredElement {
872                            tag: #seg_tag.to_owned(),
873                            element_index: #idx as usize,
874                        }
875                    }
876                };
877                Ok(if is_option_type(ty) {
878                    let inner_ty = option_inner_type(ty);
879                    let inner_is_str = inner_ty.is_some_and(is_str_like);
880                    if attrs.required {
881                        // #[edifact(required)] on Option<T>: treat absence as an error.
882                        // Emits MissingRequiredComponent when combined with component = N,
883                        // MissingRequiredElement otherwise.
884                        if inner_is_str {
885                            quote! {
886                                let #ident = ::core::option::Option::Some(
887                                    #value_expr
888                                        .filter(|__s| !__s.is_empty())
889                                        .ok_or_else(|| #missing_required_err)?
890                                        .to_owned()
891                                );
892                            }
893                        } else {
894                            let inner_ty = inner_ty
895                                .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
896                            quote! {
897                                let #ident = ::core::option::Option::Some(
898                                    #value_expr
899                                        .filter(|__s| !__s.is_empty())
900                                        .ok_or_else(|| #missing_required_err)?
901                                        .parse::<#inner_ty>()
902                                        .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?
903                                );
904                            }
905                        }
906                    } else if inner_is_str {
907                        quote! {
908                            let #ident = #value_expr
909                                .filter(|__s| !__s.is_empty())
910                                .map(::std::string::String::from);
911                        }
912                    } else {
913                        let inner_ty = inner_ty
914                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
915                        quote! {
916                            let #ident = #value_expr
917                                .filter(|__s| !__s.is_empty())
918                                .map(|__s| __s.parse::<#inner_ty>()
919                                    .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })
920                                )
921                                .transpose()?;
922                        }
923                    }
924                } else if is_str_like(ty) {
925                    quote! {
926                        let #ident = #value_expr
927                            .filter(|__s| !__s.is_empty())
928                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
929                                tag: #seg_tag.to_owned(),
930                                element_index: #idx as usize,
931                            })?
932                            .to_owned();
933                    }
934                } else {
935                    quote! {
936                        let #ident = #value_expr
937                            .filter(|__s| !__s.is_empty())
938                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
939                                tag: #seg_tag.to_owned(),
940                                element_index: #idx as usize,
941                            })?
942                            .parse::<#ty>()
943                            .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?;
944                    }
945                })
946            })
947            .collect::<syn::Result<_>>()?;
948
949        let body = quote! {
950            let __seg = #find_seg
951                .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
952                    tag: #seg_tag.to_owned(),
953                    expected_position: "message body".to_owned(),
954                })?;
955            #qualifier_guard
956            #(#field_inits)*
957            ::core::result::Result::Ok(Self { #(#field_names),* })
958        };
959
960        // Also generate EdifactSegmentTag impl.
961        let qualifier_match = if let Some(qual) = &struct_attrs.qualifier {
962            quote! {
963                fn matches_segment(seg: &::edifact_rs::Segment<'_>) -> bool {
964                    seg.tag == Self::SEGMENT_TAG
965                        && seg.element_str(0).unwrap_or("") == #qual
966                }
967            }
968        } else if let Some(idx) = struct_attrs.qualifier_from {
969            quote! {
970                fn matches_segment(seg: &::edifact_rs::Segment<'_>) -> bool {
971                    seg.tag == Self::SEGMENT_TAG
972                        && !seg.element_str(#idx as usize).unwrap_or("").is_empty()
973                }
974            }
975        } else {
976            quote! {}
977        };
978
979        let seg_tag_impl = quote! {
980            impl ::edifact_rs::EdifactSegmentTag for #name {
981                const SEGMENT_TAG: &'static str = #seg_tag;
982                #qualifier_match
983            }
984        };
985
986        // ── Owned-segment deserialization path ────────────────────────────────
987        // Works directly on `&[OwnedSegment]` without allocating a `Vec<Segment>`.
988        let find_seg_owned = if let Some(qual) = &struct_attrs.qualifier {
989            quote! {
990                ::edifact_rs::helpers::find_qualified_segment_owned(segments, #seg_tag, #qual)
991            }
992        } else {
993            quote! {
994                ::edifact_rs::helpers::find_segment_owned(segments, #seg_tag)
995            }
996        };
997
998        let field_inits_owned: Vec<TokenStream2> = field_data
999            .iter()
1000            .enumerate()
1001            .map(|(decl_i, (ident, ty, attrs))| -> syn::Result<TokenStream2> {
1002                let idx = attrs.element.unwrap_or(decl_i as u32) as usize;
1003                if attrs.composite {
1004                    if is_option_type(ty) {
1005                        let inner_ty = option_inner_type(ty)
1006                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1007                        return Ok(quote! {
1008                            let #ident = match __seg.elements.get(#idx) {
1009                                ::core::option::Option::Some(__e) => {
1010                                    let __cows = __e.components.iter()
1011                                        .map(|(s, _)| ::std::borrow::Cow::Borrowed(s.as_str()))
1012                                        .collect::<::std::vec::Vec<::std::borrow::Cow<'_, str>>>();
1013                                    ::core::option::Option::Some(
1014                                        <#inner_ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
1015                                            ::edifact_rs::CompositeElement::from_slice(&__cows)
1016                                        )?
1017                                    )
1018                                }
1019                                ::core::option::Option::None => ::core::option::Option::None,
1020                            };
1021                        });
1022                    }
1023                    return Ok(quote! {
1024                        let #ident = {
1025                            let __cows = __seg.elements.get(#idx)
1026                                .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1027                                    tag: #seg_tag.to_owned(),
1028                                    element_index: #idx as usize,
1029                                })?
1030                                .components.iter()
1031                                .map(|(s, _)| ::std::borrow::Cow::Borrowed(s.as_str()))
1032                                .collect::<::std::vec::Vec<::std::borrow::Cow<'_, str>>>();
1033                            <#ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
1034                                ::edifact_rs::CompositeElement::from_slice(&__cows)
1035                            )?
1036                        };
1037                    });
1038                }
1039                let component_idx_owned: Option<usize> = attrs.component.map(|c| c as usize);
1040                let value_expr_owned = if let Some(comp) = component_idx_owned {
1041                    quote! { __seg.component_str(#idx, #comp) }
1042                } else {
1043                    quote! { __seg.element_str(#idx) }
1044                };
1045                // Build the correct "missing required" error for the owned path.
1046                let missing_required_err_owned = if let Some(comp) = component_idx_owned {
1047                    quote! {
1048                        ::edifact_rs::EdifactError::MissingRequiredComponent {
1049                            tag: #seg_tag.to_owned(),
1050                            element_index: #idx as usize,
1051                            component_index: #comp as usize,
1052                        }
1053                    }
1054                } else {
1055                    quote! {
1056                        ::edifact_rs::EdifactError::MissingRequiredElement {
1057                            tag: #seg_tag.to_owned(),
1058                            element_index: #idx as usize,
1059                        }
1060                    }
1061                };
1062                Ok(if is_option_type(ty) {
1063                    if let Some(inner_ty) = option_inner_type(ty) {
1064                        if attrs.required {
1065                            // #[edifact(required)] on Option<T>: absence is an error.
1066                            // Emits MissingRequiredComponent when combined with component = N,
1067                            // MissingRequiredElement otherwise.
1068                            if is_str_like(inner_ty) {
1069                                quote! {
1070                                    let #ident = ::core::option::Option::Some(
1071                                        #value_expr_owned
1072                                            .filter(|__s| !__s.is_empty())
1073                                            .ok_or_else(|| #missing_required_err_owned)?
1074                                            .to_owned()
1075                                    );
1076                                }
1077                            } else {
1078                                quote! {
1079                                    let #ident = ::core::option::Option::Some(
1080                                        #value_expr_owned
1081                                            .filter(|__s| !__s.is_empty())
1082                                            .ok_or_else(|| #missing_required_err_owned)?
1083                                            .parse::<#inner_ty>()
1084                                            .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?
1085                                    );
1086                                }
1087                            }
1088                        } else if is_str_like(inner_ty) {
1089                            quote! {
1090                                let #ident = #value_expr_owned
1091                                    .filter(|__s| !__s.is_empty())
1092                                    .map(::std::string::String::from);
1093                            }
1094                        } else {
1095                            quote! {
1096                                let #ident = #value_expr_owned
1097                                    .filter(|__s| !__s.is_empty())
1098                                    .map(|__s| __s.parse::<#inner_ty>()
1099                                        .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })
1100                                    )
1101                                    .transpose()?;
1102                            }
1103                        }
1104                    } else {
1105                        // Fallback: treat as String (should not happen with well-formed types).
1106                        quote! {
1107                            let #ident = #value_expr_owned
1108                                .filter(|__s| !__s.is_empty())
1109                                .map(::std::string::String::from);
1110                        }
1111                    }
1112                } else if is_str_like(ty) {
1113                    quote! {
1114                        let #ident = #value_expr_owned
1115                            .filter(|__s| !__s.is_empty())
1116                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1117                                tag: #seg_tag.to_owned(),
1118                                element_index: #idx as usize,
1119                            })?
1120                            .to_owned();
1121                    }
1122                } else {
1123                    quote! {
1124                        let #ident = #value_expr_owned
1125                            .filter(|__s| !__s.is_empty())
1126                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1127                                tag: #seg_tag.to_owned(),
1128                                element_index: #idx as usize,
1129                            })?
1130                            .parse::<#ty>()
1131                            .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?;
1132                    }
1133                })
1134            })
1135            .collect::<syn::Result<_>>()?;
1136
1137        let owned_body = quote! {
1138            let __seg = #find_seg_owned
1139                .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
1140                    tag: #seg_tag.to_owned(),
1141                    expected_position: "message body".to_owned(),
1142                })?;
1143            #qualifier_guard
1144            #(#field_inits_owned)*
1145            ::core::result::Result::Ok(Self { #(#field_names),* })
1146        };
1147
1148        (body, owned_body, seg_tag_impl)
1149    } else {
1150        // ── Message struct: delegate to each field ────────────────────────────
1151        let field_inits: Vec<TokenStream2> = field_data
1152            .iter()
1153            .map(|(ident, ty, attrs)| -> syn::Result<TokenStream2> {
1154                Ok(if let Some(qual) = &attrs.qualifier {
1155                    if attrs.group || is_vec_type(ty) {
1156                        let inner_ty = vec_inner_type(ty)
1157                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
1158                        quote! {
1159                            let #ident = segments
1160                                .iter()
1161                                .filter(|__seg| {
1162                                    __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG
1163                                        && __seg.element_str(0).unwrap_or("") == #qual
1164                                })
1165                                .map(|__seg| {
1166                                    ::edifact_rs::EdifactDeserialize::edifact_deserialize(
1167                                        ::core::slice::from_ref(__seg),
1168                                    )
1169                                })
1170                                .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, ::edifact_rs::EdifactError>>()?;
1171                        }
1172                    } else if is_option_type(ty) {
1173                        let inner_ty = option_inner_type(ty)
1174                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1175                        quote! {
1176                            let #ident = match ::edifact_rs::helpers::find_qualified_segment(
1177                                segments,
1178                                <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
1179                                #qual,
1180                            ) {
1181                                ::core::option::Option::Some(__seg) => {
1182                                    ::core::option::Option::Some(
1183                                        ::edifact_rs::EdifactDeserialize::edifact_deserialize(
1184                                            ::core::slice::from_ref(__seg),
1185                                        )?
1186                                    )
1187                                }
1188                                ::core::option::Option::None => ::core::option::Option::None,
1189                            };
1190                        }
1191                    } else {
1192                        quote! {
1193                            let __seg = ::edifact_rs::helpers::find_qualified_segment(
1194                                segments,
1195                                <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
1196                                #qual,
1197                            )
1198                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
1199                                tag: <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG.to_owned(),
1200                                expected_position: "message body".to_owned(),
1201                            })?;
1202                            let #ident = ::edifact_rs::EdifactDeserialize::edifact_deserialize(
1203                                ::core::slice::from_ref(__seg),
1204                            )?;
1205                        }
1206                    }
1207                } else if attrs.group || is_vec_type(ty) {
1208                    let inner_ty = vec_inner_type(ty)
1209                        .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
1210                    quote! {
1211                        let #ident = ::edifact_rs::helpers::find_segments_typed::<#inner_ty>(segments)
1212                            .map(|__seg| {
1213                                <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize(
1214                                    ::core::slice::from_ref(__seg),
1215                                )
1216                            })
1217                            .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, _>>()?;
1218                    }
1219                } else if is_option_type(ty) {
1220                    let inner_ty = option_inner_type(ty)
1221                        .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1222                    quote! {
1223                        let #ident = if segments
1224                            .iter()
1225                            .any(|__seg| __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG)
1226                        {
1227                            ::core::option::Option::Some(
1228                                <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize(segments)?
1229                            )
1230                        } else {
1231                            ::core::option::Option::None
1232                        };
1233                    }
1234                } else {
1235                    quote! {
1236                        let #ident = ::edifact_rs::EdifactDeserialize::edifact_deserialize(segments)?;
1237                    }
1238                })
1239            })
1240            .collect::<syn::Result<_>>()?;
1241
1242        let body = quote! {
1243            #(#field_inits)*
1244            ::core::result::Result::Ok(Self { #(#field_names),* })
1245        };
1246
1247        // ── Owned-segment message deserialization path ────────────────────────
1248        // Works directly on `&[OwnedSegment]` without converting to `Vec<Segment>`.
1249        let field_inits_owned: Vec<TokenStream2> = field_data
1250            .iter()
1251            .map(|(ident, ty, attrs)| -> syn::Result<TokenStream2> {
1252                Ok(if let Some(qual) = &attrs.qualifier {
1253                    if attrs.group || is_vec_type(ty) {
1254                        let inner_ty = vec_inner_type(ty)
1255                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
1256                        quote! {
1257                            let #ident = segments
1258                                .iter()
1259                                .filter(|__seg| {
1260                                    __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG
1261                                        && __seg.element_str(0).unwrap_or("") == #qual
1262                                })
1263                                .map(|__seg| {
1264                                    <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
1265                                        ::core::slice::from_ref(__seg),
1266                                    )
1267                                })
1268                                .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, ::edifact_rs::EdifactError>>()?;
1269                        }
1270                    } else if is_option_type(ty) {
1271                        let inner_ty = option_inner_type(ty)
1272                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1273                        quote! {
1274                            let #ident = match ::edifact_rs::helpers::find_qualified_segment_owned(
1275                                segments,
1276                                <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
1277                                #qual,
1278                            ) {
1279                                ::core::option::Option::Some(__seg) => {
1280                                    ::core::option::Option::Some(
1281                                        <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
1282                                            ::core::slice::from_ref(__seg),
1283                                        )?
1284                                    )
1285                                }
1286                                ::core::option::Option::None => ::core::option::Option::None,
1287                            };
1288                        }
1289                    } else {
1290                        quote! {
1291                            let __seg = ::edifact_rs::helpers::find_qualified_segment_owned(
1292                                segments,
1293                                <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
1294                                #qual,
1295                            )
1296                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
1297                                tag: <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG.to_owned(),
1298                                expected_position: "message body".to_owned(),
1299                            })?;
1300                            let #ident = <#ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
1301                                ::core::slice::from_ref(__seg),
1302                            )?;
1303                        }
1304                    }
1305                } else if attrs.group || is_vec_type(ty) {
1306                    let inner_ty = vec_inner_type(ty)
1307                        .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
1308                    quote! {
1309                        let #ident = segments
1310                            .iter()
1311                            .filter(|__seg| <#inner_ty as ::edifact_rs::EdifactSegmentTag>::matches_owned_segment(__seg))
1312                            .map(|__seg| {
1313                                <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
1314                                    ::core::slice::from_ref(__seg),
1315                                )
1316                            })
1317                            .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, _>>()?;
1318                    }
1319                } else if is_option_type(ty) {
1320                    let inner_ty = option_inner_type(ty)
1321                        .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1322                    quote! {
1323                        let #ident = if segments
1324                            .iter()
1325                            .any(|__seg| __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG)
1326                        {
1327                            ::core::option::Option::Some(
1328                                <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(segments)?
1329                            )
1330                        } else {
1331                            ::core::option::Option::None
1332                        };
1333                    }
1334                } else {
1335                    quote! {
1336                        let #ident = <#ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(segments)?;
1337                    }
1338                })
1339            })
1340            .collect::<syn::Result<_>>()?;
1341
1342        let owned_body = quote! {
1343            #(#field_inits_owned)*
1344            ::core::result::Result::Ok(Self { #(#field_names),* })
1345        };
1346
1347        (body, owned_body, quote! {})
1348    };
1349
1350    Ok(quote! {
1351        impl ::edifact_rs::EdifactDeserialize for #name {
1352            fn edifact_deserialize(
1353                segments: &[::edifact_rs::Segment<'_>],
1354            ) -> ::core::result::Result<Self, ::edifact_rs::EdifactError> {
1355                #body
1356            }
1357
1358            fn edifact_deserialize_owned(
1359                segments: &[::edifact_rs::OwnedSegment],
1360            ) -> ::core::result::Result<Self, ::edifact_rs::EdifactError> {
1361                #owned_body
1362            }
1363        }
1364        #segment_tag_impl
1365    })
1366}
1367
1368#[cfg(test)]
1369mod tests {
1370    #[test]
1371    fn trybuild_ui() {
1372        let t = trybuild::TestCases::new();
1373        t.pass("tests/ui/pass_*.rs");
1374        t.compile_fail("tests/ui/fail_*.rs");
1375    }
1376}