Skip to main content

edifact_rs_derive/
lib.rs

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