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//! `#[edifact(group)]` is a documentation and validation marker: it makes the
49//! repeating-group intent explicit and enforces two compile-time constraints:
50//!
51//! 1. The annotated field **must** be of type `Vec<T>` — any other type is rejected
52//!    with a clear error message.
53//! 2. `#[edifact(group)]` cannot be combined with `#[edifact(element = ...)]` or
54//!    `#[edifact(component = ...)]` — positional placement and group semantics are
55//!    mutually exclusive.
56//!
57//! At runtime it generates the same code as a bare `Vec<T>`: every segment matching
58//! `T`'s tag and qualifier is collected, in document order, without a contiguity
59//! requirement.  When you need contiguity enforced, use
60//! [`contiguous_groups_by_qualifier`] directly on the parsed segments.
61//!
62//! # `#[edifact(repeat)]` — a repeating *data element*
63//!
64//! `group` repeats the **segment**; `repeat` repeats the **data element inside
65//! one segment** (ISO 9735-1 §8.6). Both are `Vec<T>` in Rust and nothing but
66//! the attribute can tell them apart:
67//!
68//! ```text
69//! RFF+ON:1'RFF+ON:2'      two RFF segments        → #[edifact(group)]
70//! RFF+ON:1*ON:2           one RFF, two occurrences → #[edifact(repeat)]
71//! ```
72//!
73//! A repetition separator divides whole occurrences, so every component of the
74//! element is transferred in each one — which is why the qualifier `ON` above
75//! appears twice. The derive handles that: a non-repeating field in the same
76//! element is emitted once per occurrence, and a repeating one contributes its
77//! *k*-th item.
78//!
79//! ```ignore
80//! #[derive(EdifactDeserialize, EdifactSerialize)]
81//! #[edifact(segment = "RFF")]
82//! struct OrderReferences {
83//!     #[edifact(element = 0, component = 0)]
84//!     qualifier: String,                     // "ON", in every occurrence
85//!     #[edifact(element = 0, component = 1, repeat)]
86//!     numbers: Vec<String>,                  // ["1", "2"]
87//! }
88//! ```
89//!
90//! An occurrence that omits the component yields `""` rather than being dropped:
91//! §8.7.3 makes the position of an occurrence significant, so `DE*DE***DE`
92//! deliberately transfers two empty ones. An empty `Vec` emits the element as
93//! absent, which keeps the elements after it in position (§8.7.1).
94//!
95//! Writing a repetition needs an active repetition separator; without one the
96//! writer returns `RepetitionSeparatorNotDeclared` (`E037`) rather than emitting
97//! output that reads back as a single occurrence.
98//!
99//! [`contiguous_groups_by_qualifier`]: https://docs.rs/edifact-rs/latest/edifact_rs/fn.contiguous_groups_by_qualifier.html
100//!
101//! # `#[edifact(required)]` on `Option<T>` fields
102//!
103//! By default, `Option<T>` fields produce `None` when the element is absent.
104//! Annotating an `Option<T>` field with `#[edifact(required)]` changes this:
105//! instead of `None`, deserialization returns
106//! `edifact_rs::EdifactError::MissingRequiredElement`
107//! when the element is absent or empty.  The Rust type stays `Option<T>`, which
108//! is useful when the EDIFACT specification mandates the element but your domain
109//! model treats it as optional for other reasons.
110//!
111//! ```ignore
112//! #[derive(EdifactSerialize, EdifactDeserialize)]
113//! #[edifact(segment = "DTM")]
114//! pub struct DtmSegment {
115//!     #[edifact(element = 0)]
116//!     qualifier: String,
117//!     /// Required by the spec but kept as Option in the domain model.
118//!     #[edifact(element = 1, required)]
119//!     date_time: Option<String>,
120//!     #[edifact(element = 2)]
121//!     format_code: Option<String>,
122//! }
123//! ```
124//!
125//! # Non-`String` fields and `Display` / `FromStr`
126//! Non-`String` field types (e.g. `u32`, `bool`, your own newtype) are serialized via
127//! `Display` and deserialized via `FromStr`.  The derive macro does **not** add a
128//! compile-time bound; if the type does not implement both traits the generated code
129//! will fail to compile with a standard "trait not satisfied" error.
130//!
131//! To avoid surprises, ensure any non-`String` field type implements both:
132//! ```ignore
133//! impl std::fmt::Display for MyCode { ... }
134//! impl std::str::FromStr for MyCode { ... }
135//! ```
136
137use proc_macro::TokenStream;
138use proc_macro2::TokenStream as TokenStream2;
139use quote::quote;
140use syn::{Data, DeriveInput, Field, Fields, Type, parse_macro_input, spanned::Spanned};
141
142// ── entry points ───────────────────────────────────────────────────────────────
143
144#[proc_macro_derive(EdifactSerialize, attributes(edifact))]
145/// Derive `edifact_rs::EdifactSerialize` for segment or message structs.
146///
147/// # Limitations
148///
149/// - **No generics**: the struct must not have generic type parameters.
150/// - **No lifetime parameters**: the struct must own all its data (`String`,
151///   not `&str`).  Borrow-based structs such as `Segment<'a>` cannot use this
152///   derive macro.
153pub fn derive_edifact_serialize(input: TokenStream) -> TokenStream {
154    let input = parse_macro_input!(input as DeriveInput);
155    impl_serialize(&input)
156        .unwrap_or_else(|e| e.to_compile_error())
157        .into()
158}
159
160#[proc_macro_derive(EdifactDeserialize, attributes(edifact))]
161/// Derive `edifact_rs::EdifactDeserialize` for segment or message structs.
162///
163/// # Limitations
164///
165/// - **No generics**: the struct must not have generic type parameters.
166/// - **No lifetime parameters**: the struct must own all its data (`String`,
167///   not `&str`).  Add owned wrapper types or clone components at the
168///   deserialization site if lifetime flexibility is required.
169pub fn derive_edifact_deserialize(input: TokenStream) -> TokenStream {
170    let input = parse_macro_input!(input as DeriveInput);
171    impl_deserialize(&input)
172        .unwrap_or_else(|e| e.to_compile_error())
173        .into()
174}
175
176#[proc_macro_derive(EdifactCompositeDeserialize, attributes(edifact))]
177/// Derive `edifact_rs::EdifactCompositeDeserialize` for a composite-element struct.
178///
179/// Each named field maps to one component of the composite, in declaration
180/// order, unless `#[edifact(component = N)]` overrides the index. `Option<T>`
181/// fields are optional; a bare field is required and an absent or empty
182/// component is an `EdifactError::MissingRequiredComponent`.
183///
184/// Pair the resulting type with `#[edifact(element = N, composite)]` on a
185/// segment struct's field.
186///
187/// # Limitations
188///
189/// Same as [`macro@EdifactDeserialize`]: named-field structs only, no generics,
190/// no lifetime parameters. Field types must be `String` or `Option<String>`.
191pub fn derive_edifact_composite_deserialize(input: TokenStream) -> TokenStream {
192    let input = parse_macro_input!(input as DeriveInput);
193    impl_composite_deserialize(&input)
194        .unwrap_or_else(|e| e.to_compile_error())
195        .into()
196}
197
198#[proc_macro_derive(EdifactCompositeSerialize, attributes(edifact))]
199/// Derive `edifact_rs::EdifactCompositeSerialize` for a composite-element struct.
200///
201/// The mirror of [`macro@EdifactCompositeDeserialize`]: field `n` is emitted as
202/// component `n`, `None` becomes an empty component, and any gap left by a
203/// `component = N` override is filled so later components keep their positions.
204///
205/// # Limitations
206///
207/// Same as [`macro@EdifactSerialize`]: named-field structs only, no generics,
208/// no lifetime parameters. Field types must be `String` or `Option<String>`.
209pub fn derive_edifact_composite_serialize(input: TokenStream) -> TokenStream {
210    let input = parse_macro_input!(input as DeriveInput);
211    impl_composite_serialize(&input)
212        .unwrap_or_else(|e| e.to_compile_error())
213        .into()
214}
215
216// ── attribute containers ───────────────────────────────────────────────────────
217
218/// How a field's element slot was written in the attribute.
219///
220/// `Index` is the historical positional form.  `Code` is a UN/EDIFACT data
221/// element identifier resolved against the struct's `layout` — during *const
222/// evaluation*, so a stale or mistyped identifier is a compile error rather
223/// than a silent read of the neighbouring element.
224#[derive(Clone)]
225enum Position {
226    /// `#[edifact(element = 4)]`
227    Index(u32),
228    /// `#[edifact(element = "3055")]`
229    Code(String),
230}
231
232#[derive(Default)]
233struct StructAttrs {
234    /// `#[edifact(segment = "TAG")]`
235    segment: Option<String>,
236    /// `#[edifact(qualifier = "Q")]` — element 0 value for segment matching
237    qualifier: Option<String>,
238    qualifier_span: Option<proc_macro2::Span>,
239    /// `#[edifact(qualifier_from = N)]` — zero-based element index; qualifier is dynamic at runtime.
240    qualifier_from: Option<u32>,
241    qualifier_from_span: Option<proc_macro2::Span>,
242    /// `#[edifact(layout = path::to::SEGMENT_DEFINITION)]` — the directory
243    /// definition that code-based `element` attributes resolve against.
244    layout: Option<syn::Path>,
245    layout_span: Option<proc_macro2::Span>,
246}
247
248#[derive(Default)]
249struct FieldAttrs {
250    /// `#[edifact(element = N)]` or `#[edifact(element = "3055")]`
251    element: Option<Position>,
252    element_span: Option<proc_macro2::Span>,
253    /// `#[edifact(component = N)]` — component index within the element (for composite data elements)
254    component: Option<u32>,
255    component_span: Option<proc_macro2::Span>,
256    /// `#[edifact(composite)]` — map the field as a full composite element via composite serde traits.
257    composite: bool,
258    composite_span: Option<proc_macro2::Span>,
259    /// `#[edifact(group)]` — `Vec<T>`: each item is a separate segment
260    group: bool,
261    group_span: Option<proc_macro2::Span>,
262    /// `#[edifact(qualifier = "Q")]` — message field constrained to qualifier.
263    qualifier: Option<String>,
264    qualifier_span: Option<proc_macro2::Span>,
265    /// `#[edifact(required)]` — treat an `Option<T>` field as mandatory.
266    ///
267    /// Without this attribute, `Option<T>` fields produce `None` when the element
268    /// is absent.  With `#[edifact(required)]` the deserialization emits
269    /// `EdifactError::MissingRequiredElement` instead, even though the Rust type is
270    /// still `Option<T>`.  This is useful for elements that the EDIFACT spec marks as
271    /// mandatory but which your domain model represents as optional for other reasons.
272    required: bool,
273    required_span: Option<proc_macro2::Span>,
274    /// `#[edifact(repeat)]` — `Vec<T>`: the *data element* repeats within one
275    /// segment (ISO 9735-1 §8.6), rather than the segment repeating.
276    ///
277    /// Distinct from `group`, which is about repeated segments. `RFF+ON:1*ON:2`
278    /// is one `RFF` whose element 0 occurs twice; `RFF+ON:1'RFF+ON:2'` is two
279    /// `RFF` segments. Both are `Vec<T>` in Rust and nothing but the attribute
280    /// can tell them apart.
281    repeat: bool,
282    repeat_span: Option<proc_macro2::Span>,
283}
284
285// ── attribute parsing ──────────────────────────────────────────────────────────
286
287/// Largest accepted `element` / `component` index.
288///
289/// Serialization emits one statement per slot up to the highest declared index,
290/// so an unbounded value makes rustc generate an arbitrary amount of code — a
291/// typo'd `element = 200000` was enough to exhaust memory and kill the compiler.
292/// UN/EDIFACT caps segments at 99 data elements and composites at 99 components,
293/// so 256 is generous.
294const MAX_POSITION_INDEX: u32 = 256;
295
296fn check_index_bound(lit: &syn::LitInt, value: u32, key: &str) -> syn::Result<()> {
297    if value > MAX_POSITION_INDEX {
298        return Err(syn::Error::new(
299            lit.span(),
300            format!(
301                "`{key}` index {value} exceeds the maximum of {MAX_POSITION_INDEX}; \
302                 UN/EDIFACT allows at most 99 elements per segment and 99 components per composite"
303            ),
304        ));
305    }
306    Ok(())
307}
308
309fn parse_struct_attrs(input: &DeriveInput) -> syn::Result<StructAttrs> {
310    let mut out = StructAttrs::default();
311    for attr in &input.attrs {
312        if !attr.path().is_ident("edifact") {
313            continue;
314        }
315        attr.parse_nested_meta(|meta| {
316            if meta.path.is_ident("segment") {
317                if out.segment.is_some() {
318                    return Err(meta.error("duplicate `segment`"));
319                }
320                let lit = meta.value()?.parse::<syn::LitStr>()?;
321                let tag = lit.value();
322                if tag.len() != 3 || !tag.bytes().all(|b| b.is_ascii_uppercase()) {
323                    return Err(syn::Error::new(
324                        lit.span(),
325                        format!(
326                            "segment tag must be exactly 3 ASCII uppercase letters; got {tag:?}"
327                        ),
328                    ));
329                }
330                out.segment = Some(tag);
331            } else if meta.path.is_ident("qualifier") {
332                if out.qualifier.is_some() {
333                    return Err(meta.error("duplicate `qualifier`"));
334                }
335                out.qualifier = Some(meta.value()?.parse::<syn::LitStr>()?.value());
336                out.qualifier_span = Some(meta.path.span());
337            } else if meta.path.is_ident("qualifier_from") {
338                if out.qualifier_from.is_some() {
339                    return Err(meta.error("duplicate `qualifier_from`"));
340                }
341                let lit = meta.value()?.parse::<syn::LitInt>()?;
342                let idx: u32 = lit.base10_parse()?;
343                check_index_bound(&lit, idx, "qualifier_from")?;
344                out.qualifier_from = Some(idx);
345                out.qualifier_from_span = Some(meta.path.span());
346            } else if meta.path.is_ident("layout") {
347                if out.layout.is_some() {
348                    return Err(meta.error("duplicate `layout`"));
349                }
350                out.layout_span = Some(meta.path.span());
351                let value = meta.value()?;
352                // Accept both `layout = crate::defs::NAD` and the string form
353                // `layout = "crate::defs::NAD"`, since attribute paths are
354                // commonly written either way.
355                out.layout = Some(if value.peek(syn::LitStr) {
356                    value.parse::<syn::LitStr>()?.parse()?
357                } else {
358                    value.parse::<syn::Path>()?
359                });
360            } else {
361                return Err(meta.error("unknown struct-level `edifact` key; expected `segment`, `qualifier`, `qualifier_from`, or `layout`"));
362            }
363            Ok(())
364        })?;
365    }
366    if out.layout.is_some() && out.segment.is_none() {
367        return Err(syn::Error::new(
368            out.layout_span.unwrap_or_else(|| input.span()),
369            "#[edifact(layout = ...)] requires #[edifact(segment = ...)]: a layout describes one segment",
370        ));
371    }
372    if (out.qualifier.is_some() || out.qualifier_from.is_some()) && out.segment.is_none() {
373        return Err(syn::Error::new(
374            out.qualifier_span
375                .or(out.qualifier_from_span)
376                .unwrap_or_else(|| input.span()),
377            "#[edifact(qualifier = ...)] / #[edifact(qualifier_from = ...)] require #[edifact(segment = ...)]",
378        ));
379    }
380    if out.qualifier.is_some() && out.qualifier_from.is_some() {
381        return Err(syn::Error::new(
382            out.qualifier_from_span
383                .or(out.qualifier_span)
384                .unwrap_or_else(|| input.span()),
385            "use either #[edifact(qualifier = ...)] or #[edifact(qualifier_from = ...)], not both",
386        ));
387    }
388    Ok(out)
389}
390
391fn parse_field_attrs(field: &Field) -> syn::Result<FieldAttrs> {
392    let mut out = FieldAttrs::default();
393    for attr in &field.attrs {
394        if !attr.path().is_ident("edifact") {
395            continue;
396        }
397        attr.parse_nested_meta(|meta| {
398            if meta.path.is_ident("element") {
399                if out.element.is_some() {
400                    return Err(meta.error("duplicate `element`"));
401                }
402                out.element_span = Some(meta.path.span());
403                let value = meta.value()?;
404                out.element = Some(if value.peek(syn::LitStr) {
405                    let lit = value.parse::<syn::LitStr>()?;
406                    let code = lit.value();
407                    if code.is_empty() {
408                        return Err(syn::Error::new(
409                            lit.span(),
410                            "`element` data element identifier must not be empty",
411                        ));
412                    }
413                    Position::Code(code)
414                } else {
415                    let lit = value.parse::<syn::LitInt>()?;
416                    let idx: u32 = lit.base10_parse()?;
417                    check_index_bound(&lit, idx, "element")?;
418                    Position::Index(idx)
419                });
420            } else if meta.path.is_ident("component") {
421                if out.component.is_some() {
422                    return Err(meta.error("duplicate `component`"));
423                }
424                out.component_span = Some(meta.path.span());
425                let value = meta.value()?;
426                if value.peek(syn::LitStr) {
427                    let lit = value.parse::<syn::LitStr>()?;
428                    return Err(syn::Error::new(
429                        lit.span(),
430                        "put the data element identifier in `element`: \
431                         `#[edifact(element = \"3055\")]` resolves both the element and the \
432                         component position from the directory",
433                    ));
434                }
435                let lit = value.parse::<syn::LitInt>()?;
436                let idx: u32 = lit.base10_parse()?;
437                check_index_bound(&lit, idx, "component")?;
438                out.component = Some(idx);
439            } else if meta.path.is_ident("composite") {
440                out.composite = true;
441                out.composite_span = Some(meta.path.span());
442            } else if meta.path.is_ident("group") {
443                out.group = true;
444                out.group_span = Some(meta.path.span());
445            } else if meta.path.is_ident("qualifier") {
446                if out.qualifier.is_some() {
447                    return Err(meta.error("duplicate `qualifier`"));
448                }
449                out.qualifier = Some(meta.value()?.parse::<syn::LitStr>()?.value());
450                out.qualifier_span = Some(meta.path.span());
451            } else if meta.path.is_ident("required") {
452                out.required = true;
453                out.required_span = Some(meta.path.span());
454            } else if meta.path.is_ident("repeat") {
455                out.repeat = true;
456                out.repeat_span = Some(meta.path.span());
457            } else {
458                return Err(meta.error("unknown field-level `edifact` key; expected `element`, `component`, `composite`, `group`, `qualifier`, `repeat`, or `required`"));
459            }
460            Ok(())
461        })?;
462    }
463    Ok(out)
464}
465
466// ── slot resolution ────────────────────────────────────────────────────────────
467
468/// The element/component slot a field maps to, as token expressions.
469struct Slots {
470    /// `usize` expression for the zero-based element index.
471    element: TokenStream2,
472    /// `usize` expression for the zero-based component index.
473    component: TokenStream2,
474    /// Whether the field reads through a component accessor.
475    ///
476    /// Always true for a code-addressed field: whether the identifier landed on
477    /// a component or a whole element is only known after const evaluation, and
478    /// reading component 0 is equivalent to reading the element either way.
479    has_component: bool,
480    /// `bool` expression: does this field address a component *inside* a
481    /// composite?
482    ///
483    /// Decides between [`EdifactError::MissingRequiredComponent`] and
484    /// [`EdifactError::MissingRequiredElement`].  It cannot be folded into
485    /// `has_component`, because a code naming the first component of a composite
486    /// resolves to component index 0 just like a whole element does — and
487    /// reporting `E021` where `E008` belongs sends downstream routing to the
488    /// wrong branch.  For a code slot this is a `const` lookup, so the branch
489    /// folds away.
490    names_component: TokenStream2,
491}
492
493/// Resolve every field's slot, emitting the `const` items that code-addressed
494/// fields need.
495///
496/// The returned prelude must be placed at the top of each generated function
497/// body that uses the slots.  It carries three compile-time guarantees:
498/// every identifier exists in the layout, none is ambiguous, and no two fields
499/// claim the same slot.
500fn resolve_slots(
501    struct_attrs: &StructAttrs,
502    field_data: &[(&syn::Ident, &Type, FieldAttrs)],
503) -> syn::Result<(TokenStream2, Vec<Slots>)> {
504    let mut consts: Vec<TokenStream2> = Vec::new();
505    let mut slots: Vec<Slots> = Vec::with_capacity(field_data.len());
506    let mut slot_entries: Vec<TokenStream2> = Vec::new();
507
508    if struct_attrs.qualifier.is_some() {
509        // The struct-level qualifier owns element 0 / component 0.
510        slot_entries.push(quote! { (0usize, 0usize) });
511    }
512
513    for (i, (ident, _, attrs)) in field_data.iter().enumerate() {
514        if attrs.group {
515            slots.push(Slots {
516                element: quote! { 0usize },
517                component: quote! { 0usize },
518                has_component: false,
519                names_component: quote! { false },
520            });
521            continue;
522        }
523        let component_index = attrs.component.unwrap_or(0);
524        let slot = match &attrs.element {
525            Some(Position::Code(code)) => {
526                let Some(layout) = &struct_attrs.layout else {
527                    return Err(syn::Error::new(
528                        attrs.element_span.unwrap_or_else(|| ident.span()),
529                        format!(
530                            "field `{ident}`: `element = \"{code}\"` addresses a UN/EDIFACT data \
531                             element identifier, which needs a directory to resolve against; add \
532                             #[edifact(layout = path::to::SEGMENT_DEFINITION)] to the struct"
533                        ),
534                    ));
535                };
536                let slot_ident = syn::Ident::new(
537                    &format!("__EDIFACT_SLOT_{i}"),
538                    attrs.element_span.unwrap_or_else(|| ident.span()),
539                );
540                let unknown_msg = format!(
541                    "field `{ident}`: data element {code} is not defined exactly once in the \
542                     segment layout — check the identifier against the directory"
543                );
544                consts.push(quote! {
545                    const _: () = ::core::assert!(#layout.code_positions(#code) == 1, #unknown_msg);
546                });
547                if attrs.component.is_some() {
548                    let conflict_msg = format!(
549                        "field `{ident}`: `component = N` may only accompany an identifier that \
550                         names a whole data element, but {code} names a component inside one"
551                    );
552                    consts.push(quote! {
553                        const _: () =
554                            ::core::assert!(#layout.component_slot(#code) == 0, #conflict_msg);
555                    });
556                    consts.push(quote! {
557                        // The guard above already reported an unresolvable
558                        // identifier by name; short-circuit so `element_slot`
559                        // does not panic a second time with a vaguer message.
560                        const #slot_ident: (usize, usize) =
561                            if #layout.code_positions(#code) == 1 {
562                                (#layout.element_slot(#code), #component_index as usize)
563                            } else {
564                                (0, 0)
565                            };
566                    });
567                } else {
568                    consts.push(quote! {
569                        const #slot_ident: (usize, usize) =
570                            if #layout.code_positions(#code) == 1 {
571                                (#layout.element_slot(#code), #layout.component_slot(#code))
572                            } else {
573                                (0, 0)
574                            };
575                    });
576                }
577                slot_entries.push(quote! { #slot_ident });
578                let names_component = if attrs.component.is_some() {
579                    // `component = N` on a whole-element identifier: the field
580                    // does address a component inside that composite.
581                    quote! { true }
582                } else {
583                    quote! { #layout.code_is_component(#code) }
584                };
585                Slots {
586                    element: quote! { #slot_ident.0 },
587                    component: quote! { #slot_ident.1 },
588                    has_component: true,
589                    names_component,
590                }
591            }
592            Some(Position::Index(idx)) => {
593                let idx = *idx;
594                let explicit = attrs.component.is_some();
595                slot_entries.push(quote! { (#idx as usize, #component_index as usize) });
596                Slots {
597                    element: quote! { #idx as usize },
598                    component: quote! { #component_index as usize },
599                    has_component: explicit,
600                    names_component: quote! { #explicit },
601                }
602            }
603            None => {
604                // Declaration order is the implicit element index.
605                let idx = i as u32;
606                let explicit = attrs.component.is_some();
607                slot_entries.push(quote! { (#idx as usize, #component_index as usize) });
608                Slots {
609                    element: quote! { #idx as usize },
610                    component: quote! { #component_index as usize },
611                    has_component: explicit,
612                    names_component: quote! { #explicit },
613                }
614            }
615        };
616        slots.push(slot);
617    }
618
619    // Slot collisions between code-addressed fields (and between a code and a
620    // positional field) can only be seen after const evaluation, so the check
621    // itself has to run there.  Macro-time `check_duplicate_slots` still covers
622    // the all-positional case with a friendlier message.
623    if struct_attrs.layout.is_some() && !slot_entries.is_empty() {
624        let count = slot_entries.len();
625        consts.push(quote! {
626            const __EDIFACT_SLOTS: [(usize, usize); #count] = [#(#slot_entries),*];
627            const _: () = {
628                let mut i = 0;
629                while i < __EDIFACT_SLOTS.len() {
630                    let mut j = i + 1;
631                    while j < __EDIFACT_SLOTS.len() {
632                        ::core::assert!(
633                            !(__EDIFACT_SLOTS[i].0 == __EDIFACT_SLOTS[j].0
634                                && __EDIFACT_SLOTS[i].1 == __EDIFACT_SLOTS[j].1),
635                            "two fields resolve to the same element/component slot; \
636                             give each field a distinct data element identifier or index"
637                        );
638                        j += 1;
639                    }
640                    i += 1;
641                }
642            };
643        });
644    }
645
646    Ok((quote! { #(#consts)* }, slots))
647}
648
649// ── type helpers ───────────────────────────────────────────────────────────────
650
651fn is_option_type(ty: &Type) -> bool {
652    matches!(ty, Type::Path(p) if p.path.segments.last().is_some_and(|s| s.ident == "Option"))
653}
654
655fn is_vec_type(ty: &Type) -> bool {
656    matches!(ty, Type::Path(p) if p.path.segments.last().is_some_and(|s| s.ident == "Vec"))
657}
658
659/// Returns `true` for the `String` path type.
660///
661/// Accepts only:
662/// - `String` (bare, single-segment)
663/// - `std::string::String` (fully qualified standard library path)
664/// - `alloc::string::String` (fully qualified alloc path for `no_std` contexts)
665///
666/// A user-defined type whose last segment is `String` but that does not match
667/// one of these three forms is **not** treated as a string type, which prevents
668/// accidental string-extraction code generation for unrelated user types.
669///
670/// **Shadowing caveat:** bare `String` (single-segment, no path prefix) is matched
671/// by name only. If a crate shadows the standard-library `String` with a local type
672/// of the same name, this function will still classify it as a string type and the
673/// derive macro will generate incorrect string-extraction code rather than a
674/// composite or element parse. To avoid this, always use the fully-qualified path
675/// (`std::string::String`) in struct fields when `String` is shadowed in scope.
676fn is_string_type(ty: &Type) -> bool {
677    let Type::Path(p) = ty else { return false };
678    // Single-segment bare "String"
679    if p.path.is_ident("String") {
680        return true;
681    }
682    // Fully-qualified std::string::String or alloc::string::String
683    let segs = &p.path.segments;
684    segs.len() == 3
685        && (segs[0].ident == "std" || segs[0].ident == "alloc")
686        && segs[1].ident == "string"
687        && segs[2].ident == "String"
688}
689
690/// Returns `true` for `&str` or `&'_ str` reference types.
691fn is_str_ref_type(ty: &Type) -> bool {
692    let Type::Reference(r) = ty else { return false };
693    matches!(r.elem.as_ref(), Type::Path(p) if p.path.is_ident("str"))
694}
695
696/// Returns `true` when `ty` is a type that can yield `&str` without allocating
697/// (i.e. `String` or `&str`).
698fn is_str_like(ty: &Type) -> bool {
699    is_string_type(ty) || is_str_ref_type(ty)
700}
701
702fn option_inner_type(ty: &Type) -> Option<&Type> {
703    let Type::Path(path) = ty else { return None };
704    let seg = path.path.segments.last()?;
705    if seg.ident != "Option" {
706        return None;
707    }
708    let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
709        return None;
710    };
711    let syn::GenericArgument::Type(inner) = args.args.first()? else {
712        return None;
713    };
714    Some(inner)
715}
716
717fn vec_inner_type(ty: &Type) -> Option<&Type> {
718    let Type::Path(path) = ty else { return None };
719    let seg = path.path.segments.last()?;
720    if seg.ident != "Vec" {
721        return None;
722    }
723    let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
724        return None;
725    };
726    let syn::GenericArgument::Type(inner) = args.args.first()? else {
727        return None;
728    };
729    Some(inner)
730}
731
732// ── named field extraction ─────────────────────────────────────────────────────
733
734/// Reject two fields that map to the same `(element, component)` slot.
735///
736/// Serialization keys its emit table by slot, so a duplicate silently dropped
737/// one field from the output while deserialization still read both — an
738/// asymmetric, compile-clean data loss.
739///
740/// Only positional fields are checked here; code-addressed fields have no
741/// macro-time index, and are covered by the const-evaluated uniqueness check
742/// emitted by [`resolve_slots`].
743fn check_duplicate_slots(
744    field_data: &[(&syn::Ident, &Type, FieldAttrs)],
745    is_segment_struct: bool,
746) -> syn::Result<()> {
747    if !is_segment_struct {
748        return Ok(());
749    }
750    let mut seen: Vec<((u32, u32), &syn::Ident)> = Vec::with_capacity(field_data.len());
751    for (i, (ident, _, attrs)) in field_data.iter().enumerate() {
752        // Group fields are not positional, so they occupy no slot.
753        if attrs.group {
754            continue;
755        }
756        let element = match &attrs.element {
757            Some(Position::Index(idx)) => *idx,
758            Some(Position::Code(_)) => continue,
759            None => i as u32,
760        };
761        let slot = (element, attrs.component.unwrap_or(0));
762        if let Some((_, first)) = seen.iter().find(|(s, _)| *s == slot) {
763            return Err(syn::Error::new(
764                ident.span(),
765                format!(
766                    "field `{ident}` maps to element {} component {}, which is already \
767                     claimed by field `{first}`; give each field a distinct \
768                     `#[edifact(element = ..., component = ...)]` slot",
769                    slot.0, slot.1
770                ),
771            ));
772        }
773        seen.push((slot, ident));
774    }
775    Ok(())
776}
777
778// ── composite derives ─────────────────────────────────────────────────────────
779
780/// One resolved component slot of a composite struct.
781struct CompositeSlot<'a> {
782    ident: &'a syn::Ident,
783    ty: &'a Type,
784    index: u32,
785    optional: bool,
786}
787
788/// Resolve each field of a composite struct to a component index.
789///
790/// Declaration order is the default; `#[edifact(component = N)]` overrides it.
791/// Only the attributes that mean something for a composite are accepted — the
792/// segment-level ones (`element`, `group`, `qualifier`, `composite`) have no
793/// meaning inside one and are rejected rather than silently ignored.
794fn composite_slots(input: &DeriveInput) -> syn::Result<Vec<CompositeSlot<'_>>> {
795    let fields = get_named_fields(input)?;
796    let mut slots: Vec<CompositeSlot<'_>> = Vec::with_capacity(fields.named.len());
797
798    for (decl_index, field) in fields.named.iter().enumerate() {
799        let ident = field.ident.as_ref().expect("named fields checked above");
800        let attrs = parse_field_attrs(field)?;
801
802        for (present, span, key) in [
803            (attrs.element.is_some(), attrs.element_span, "element"),
804            (attrs.composite, attrs.composite_span, "composite"),
805            (attrs.group, attrs.group_span, "group"),
806            (attrs.qualifier.is_some(), attrs.qualifier_span, "qualifier"),
807        ] {
808            if present {
809                return Err(syn::Error::new(
810                    span.unwrap_or_else(|| field.span()),
811                    format!(
812                        "`{key}` has no meaning on a composite struct field; \
813                         a composite maps its fields to components, so only \
814                         `component` and `required` apply"
815                    ),
816                ));
817            }
818        }
819
820        let ty = &field.ty;
821        let inner = option_inner_type(ty).unwrap_or(ty);
822        if !is_string_type(inner) {
823            return Err(syn::Error::new(
824                ty.span(),
825                "composite struct fields must be `String` or `Option<String>`; \
826                 a component is a single text value",
827            ));
828        }
829
830        slots.push(CompositeSlot {
831            ident,
832            ty,
833            index: attrs.component.unwrap_or(decl_index as u32),
834            optional: is_option_type(ty) && !attrs.required,
835        });
836    }
837
838    // Two fields on one component would make the mapping ambiguous in one
839    // direction and lossy in the other.
840    let mut seen: Vec<(u32, &syn::Ident)> = Vec::with_capacity(slots.len());
841    for slot in &slots {
842        if let Some((_, first)) = seen.iter().find(|(i, _)| *i == slot.index) {
843            return Err(syn::Error::new(
844                slot.ident.span(),
845                format!(
846                    "component {} is already mapped by field `{first}`",
847                    slot.index
848                ),
849            ));
850        }
851        seen.push((slot.index, slot.ident));
852    }
853    Ok(slots)
854}
855
856fn impl_composite_deserialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
857    let name = &input.ident;
858    let slots = composite_slots(input)?;
859
860    let assignments = slots.iter().map(|slot| {
861        let CompositeSlot {
862            ident,
863            ty,
864            index,
865            optional,
866        } = slot;
867        let idx = *index as usize;
868        if *optional {
869            quote! {
870                #ident: match __composite.get(#idx) {
871                    Some(v) if !v.is_empty() => Some(::std::string::String::from(v)),
872                    _ => None,
873                },
874            }
875        } else {
876            // An absent or empty component in a required slot is the exact
877            // condition `MissingRequiredComponent` exists to name.
878            let build = if is_option_type(ty) {
879                quote! { Some(::std::string::String::from(__value)) }
880            } else {
881                quote! { ::std::string::String::from(__value) }
882            };
883            quote! {
884                #ident: {
885                    let __value = __composite
886                        .get(#idx)
887                        .filter(|v| !v.is_empty())
888                        .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredComponent {
889                            tag: ::std::string::String::from(stringify!(#name)),
890                            element_index: 0,
891                            component_index: #idx,
892                        })?;
893                    #build
894                },
895            }
896        }
897    });
898
899    Ok(quote! {
900        impl ::edifact_rs::EdifactCompositeDeserialize for #name {
901            fn edifact_deserialize_composite(
902                __composite: ::edifact_rs::CompositeElement<'_>,
903            ) -> ::core::result::Result<Self, ::edifact_rs::EdifactError> {
904                ::core::result::Result::Ok(Self {
905                    #(#assignments)*
906                })
907            }
908        }
909    })
910}
911
912fn impl_composite_serialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
913    let name = &input.ident;
914    let slots = composite_slots(input)?;
915
916    // Emit in component order, filling any slot a `component = N` override
917    // skipped so the components that follow keep their positions.
918    let mut ordered: Vec<&CompositeSlot<'_>> = slots.iter().collect();
919    ordered.sort_by_key(|slot| slot.index);
920    let highest = ordered.last().map_or(0, |slot| slot.index);
921
922    let emits = (0..=highest).map(|index| {
923        let value = match ordered.iter().find(|slot| slot.index == index) {
924            Some(slot) => {
925                let ident = slot.ident;
926                if is_option_type(slot.ty) {
927                    quote! { self.#ident.as_deref().unwrap_or("") }
928                } else {
929                    quote! { self.#ident.as_str() }
930                }
931            }
932            None => quote! { "" },
933        };
934        // Component 0 opens the element; the rest extend it.
935        if index == 0 {
936            quote! { __emitter.emit(::edifact_rs::EdifactEvent::Element { value: #value })?; }
937        } else {
938            quote! {
939                __emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: #value })?;
940            }
941        }
942    });
943
944    // A field-less composite still occupies its element slot.
945    let body = if slots.is_empty() {
946        quote! { __emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?; }
947    } else {
948        quote! { #(#emits)* }
949    };
950
951    Ok(quote! {
952        impl ::edifact_rs::EdifactCompositeSerialize for #name {
953            fn edifact_serialize_composite<__E: ::edifact_rs::EventEmitter>(
954                &self,
955                __emitter: &mut __E,
956            ) -> ::core::result::Result<(), ::edifact_rs::EdifactError> {
957                #body
958                ::core::result::Result::Ok(())
959            }
960        }
961    })
962}
963
964fn get_named_fields(input: &DeriveInput) -> syn::Result<&syn::FieldsNamed> {
965    if !input.generics.params.is_empty() {
966        return Err(syn::Error::new(
967            input.generics.params.span(),
968            "EdifactSerialize/EdifactDeserialize do not support generic structs",
969        ));
970    }
971    match &input.data {
972        Data::Struct(s) => match &s.fields {
973            Fields::Named(f) => Ok(f),
974            _ => Err(syn::Error::new(
975                input.span(),
976                "EdifactSerialize/EdifactDeserialize only support structs with named fields",
977            )),
978        },
979        _ => Err(syn::Error::new(
980            input.span(),
981            "EdifactSerialize/EdifactDeserialize only support structs",
982        )),
983    }
984}
985
986fn validate_field_attrs(
987    ident: &syn::Ident,
988    ty: &Type,
989    attrs: &FieldAttrs,
990    is_segment_struct: bool,
991) -> syn::Result<()> {
992    if attrs.group && !is_vec_type(ty) {
993        return Err(syn::Error::new(
994            attrs.group_span.unwrap_or_else(|| ident.span()),
995            format!("field `{ident}`: #[edifact(group)] requires Vec<T>"),
996        ));
997    }
998    if attrs.group && (attrs.element.is_some() || attrs.component.is_some()) {
999        return Err(syn::Error::new(
1000            attrs.group_span.unwrap_or_else(|| ident.span()),
1001            format!(
1002                "field `{ident}`: #[edifact(group)] cannot be combined with element/component positioning"
1003            ),
1004        ));
1005    }
1006    if attrs.repeat && !is_vec_type(ty) {
1007        return Err(syn::Error::new(
1008            attrs.repeat_span.unwrap_or_else(|| ident.span()),
1009            format!(
1010                "field `{ident}`: #[edifact(repeat)] requires Vec<T> — a repeating data element \
1011                 has one occurrence per item"
1012            ),
1013        ));
1014    }
1015    if attrs.repeat && !is_segment_struct {
1016        return Err(syn::Error::new(
1017            attrs.repeat_span.unwrap_or_else(|| ident.span()),
1018            format!(
1019                "field `{ident}`: #[edifact(repeat)] is only valid on segment structs; a message \
1020                 struct repeats whole segments, which is #[edifact(group)]"
1021            ),
1022        ));
1023    }
1024    if attrs.repeat && attrs.group {
1025        return Err(syn::Error::new(
1026            attrs.repeat_span.unwrap_or_else(|| ident.span()),
1027            format!(
1028                "field `{ident}`: #[edifact(repeat)] repeats a data element inside one segment; \
1029                 #[edifact(group)] repeats the segment itself — they are different structures"
1030            ),
1031        ));
1032    }
1033    if attrs.repeat && attrs.composite {
1034        return Err(syn::Error::new(
1035            attrs.repeat_span.unwrap_or_else(|| ident.span()),
1036            format!(
1037                "field `{ident}`: #[edifact(repeat)] cannot be combined with \
1038                 #[edifact(composite)]; map one component across the occurrences instead"
1039            ),
1040        ));
1041    }
1042    if attrs.composite && attrs.component.is_some() {
1043        return Err(syn::Error::new(
1044            attrs.component_span.unwrap_or_else(|| ident.span()),
1045            format!(
1046                "field `{ident}`: #[edifact(component = ...)] cannot be combined with #[edifact(composite)]"
1047            ),
1048        ));
1049    }
1050    if attrs.composite && attrs.group {
1051        return Err(syn::Error::new(
1052            attrs.composite_span.unwrap_or_else(|| ident.span()),
1053            format!(
1054                "field `{ident}`: #[edifact(composite)] cannot be combined with #[edifact(group)]"
1055            ),
1056        ));
1057    }
1058    if is_segment_struct && attrs.group {
1059        return Err(syn::Error::new(
1060            attrs.group_span.unwrap_or_else(|| ident.span()),
1061            format!("field `{ident}`: #[edifact(group)] is only valid on message structs"),
1062        ));
1063    }
1064    if !is_segment_struct && (attrs.element.is_some() || attrs.component.is_some()) {
1065        return Err(syn::Error::new(
1066            attrs
1067                .element_span
1068                .or(attrs.component_span)
1069                .unwrap_or_else(|| ident.span()),
1070            format!(
1071                "field `{ident}`: element/component positioning is only valid on segment structs"
1072            ),
1073        ));
1074    }
1075    if !is_segment_struct && attrs.composite {
1076        return Err(syn::Error::new(
1077            attrs.composite_span.unwrap_or_else(|| ident.span()),
1078            format!("field `{ident}`: #[edifact(composite)] is only valid on segment structs"),
1079        ));
1080    }
1081    if is_segment_struct && attrs.qualifier.is_some() {
1082        return Err(syn::Error::new(
1083            attrs.qualifier_span.unwrap_or_else(|| ident.span()),
1084            format!(
1085                "field `{ident}`: #[edifact(qualifier = ...)] is only valid on message struct fields"
1086            ),
1087        ));
1088    }
1089    if attrs.qualifier.is_some() && attrs.group && !is_vec_type(ty) {
1090        return Err(syn::Error::new(
1091            attrs.qualifier_span.unwrap_or_else(|| ident.span()),
1092            format!("field `{ident}`: qualifier-constrained groups must be Vec<T>"),
1093        ));
1094    }
1095    if attrs.required && !is_option_type(ty) {
1096        return Err(syn::Error::new(
1097            attrs.required_span.unwrap_or_else(|| ident.span()),
1098            format!(
1099                "field `{ident}`: #[edifact(required)] only applies to Option<T> fields; \
1100                 non-Option fields are always required"
1101            ),
1102        ));
1103    }
1104    if attrs.required && attrs.composite {
1105        return Err(syn::Error::new(
1106            attrs.required_span.unwrap_or_else(|| ident.span()),
1107            format!(
1108                "field `{ident}`: #[edifact(required)] cannot be combined with \
1109                 #[edifact(composite)]; use a non-optional field type to require the \
1110                 composite element"
1111            ),
1112        ));
1113    }
1114    if attrs.required && !is_segment_struct {
1115        return Err(syn::Error::new(
1116            attrs.required_span.unwrap_or_else(|| ident.span()),
1117            format!(
1118                "field `{ident}`: #[edifact(required)] is only valid on segment struct \
1119                 element fields; to require a segment in a message struct, use a \
1120                 non-optional field type"
1121            ),
1122        ));
1123    }
1124    Ok(())
1125}
1126
1127// ── EdifactSerialize ───────────────────────────────────────────────────────────
1128
1129/// Element index known at macro-expansion time; declaration order is the default.
1130///
1131/// Code-addressed fields have no such index — callers reach this only on the
1132/// positional path, which `resolve_slots` keeps separate.
1133fn static_element_index(attrs: &FieldAttrs, decl_index: usize) -> u32 {
1134    match &attrs.element {
1135        Some(Position::Index(idx)) => *idx,
1136        _ => decl_index as u32,
1137    }
1138}
1139
1140/// Generate `EdifactSerialize` for a segment struct that addresses fields by
1141/// UN/EDIFACT data element identifier.
1142///
1143/// The positional path lays out its emit order at macro-expansion time, which a
1144/// code-addressed struct cannot do: its slots are only known once the `const`
1145/// items in `slot_prelude` are evaluated.  So each field contributes a
1146/// `(element, component, value)` triple and
1147/// [`emit_sparse_segment`][edifact_rs::emit_sparse_segment] orders them.
1148/// Absent optional values still contribute an empty triple, so a trailing `None`
1149/// produces the same empty element the positional path emits.
1150fn impl_serialize_sparse(
1151    name: &syn::Ident,
1152    seg_tag: &str,
1153    struct_attrs: &StructAttrs,
1154    field_data: &[(&syn::Ident, &Type, FieldAttrs)],
1155    slots: &[Slots],
1156    slot_prelude: &TokenStream2,
1157) -> TokenStream2 {
1158    let mut stmts: Vec<TokenStream2> = Vec::new();
1159
1160    if let Some(qual) = &struct_attrs.qualifier {
1161        stmts.push(quote! {
1162            __parts.push((0usize, 0usize, ::std::borrow::Cow::Borrowed(#qual)));
1163        });
1164    }
1165
1166    for ((ident, ty, attrs), slot) in field_data.iter().zip(slots) {
1167        let (element, component) = (&slot.element, &slot.component);
1168        if attrs.composite {
1169            // A composite field owns its whole element; replay its own events
1170            // into consecutive component slots.
1171            let serialize_composite = if is_option_type(ty) {
1172                quote! {
1173                    if let ::core::option::Option::Some(__v) = &self.#ident {
1174                        ::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(
1175                            __v, &mut __sub,
1176                        )?;
1177                    }
1178                }
1179            } else {
1180                quote! {
1181                    ::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(
1182                        &self.#ident, &mut __sub,
1183                    )?;
1184                }
1185            };
1186            stmts.push(quote! {
1187                {
1188                    let mut __sub = ::edifact_rs::VecEmitter::default();
1189                    #serialize_composite
1190                    let mut __comp = 0usize;
1191                    let mut __any = false;
1192                    for __event in __sub.events {
1193                        match __event {
1194                            ::edifact_rs::OwnedEdifactEvent::Element { value } => {
1195                                __comp = 0;
1196                                __any = true;
1197                                __parts.push((#element, 0usize, ::std::borrow::Cow::Owned(value)));
1198                            }
1199                            ::edifact_rs::OwnedEdifactEvent::ComponentElement { value } => {
1200                                __comp += 1;
1201                                __any = true;
1202                                __parts.push((#element, __comp, ::std::borrow::Cow::Owned(value)));
1203                            }
1204                            _ => {}
1205                        }
1206                    }
1207                    if !__any {
1208                        __parts.push((#element, 0usize, ::std::borrow::Cow::Borrowed("")));
1209                    }
1210                }
1211            });
1212            continue;
1213        }
1214
1215        let value_expr = if is_option_type(ty) {
1216            let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
1217            if inner_is_str {
1218                quote! {
1219                    match &self.#ident {
1220                        ::core::option::Option::Some(__v) => ::std::borrow::Cow::Borrowed(__v.as_str()),
1221                        ::core::option::Option::None => ::std::borrow::Cow::Borrowed(""),
1222                    }
1223                }
1224            } else {
1225                quote! {
1226                    match &self.#ident {
1227                        ::core::option::Option::Some(__v) => {
1228                            ::std::borrow::Cow::Owned(::std::string::ToString::to_string(__v))
1229                        }
1230                        ::core::option::Option::None => ::std::borrow::Cow::Borrowed(""),
1231                    }
1232                }
1233            }
1234        } else if is_string_type(ty) {
1235            quote! { ::std::borrow::Cow::Borrowed(self.#ident.as_str()) }
1236        } else if is_str_ref_type(ty) {
1237            quote! { ::std::borrow::Cow::Borrowed(self.#ident) }
1238        } else {
1239            quote! { ::std::borrow::Cow::Owned(::std::string::ToString::to_string(&self.#ident)) }
1240        };
1241
1242        stmts.push(quote! {
1243            __parts.push((#element, #component, #value_expr));
1244        });
1245    }
1246
1247    let capacity = field_data.len() + usize::from(struct_attrs.qualifier.is_some());
1248
1249    quote! {
1250        impl ::edifact_rs::EdifactSerialize for #name {
1251            fn edifact_serialize<__E: ::edifact_rs::EventEmitter>(
1252                &self,
1253                emitter: &mut __E,
1254            ) -> ::core::result::Result<(), ::edifact_rs::EdifactError> {
1255                #slot_prelude
1256                let mut __parts: ::std::vec::Vec<(
1257                    usize,
1258                    usize,
1259                    ::std::borrow::Cow<'_, str>,
1260                )> = ::std::vec::Vec::with_capacity(#capacity);
1261                #(#stmts)*
1262                ::edifact_rs::emit_sparse_segment(emitter, #seg_tag, &mut __parts)
1263            }
1264        }
1265    }
1266}
1267
1268fn impl_serialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
1269    let name = &input.ident;
1270    let struct_attrs = parse_struct_attrs(input)?;
1271    let fields = get_named_fields(input)?;
1272    let is_segment_struct = struct_attrs.segment.is_some();
1273
1274    // Collect (field_ident, field_type, FieldAttrs).
1275    let field_data: Vec<(&syn::Ident, &Type, FieldAttrs)> = fields
1276        .named
1277        .iter()
1278        .map(|f| {
1279            let attrs = parse_field_attrs(f)?;
1280            let ident = f
1281                .ident
1282                .as_ref()
1283                .ok_or_else(|| syn::Error::new_spanned(f, "only named fields are supported"))?;
1284            validate_field_attrs(ident, &f.ty, &attrs, is_segment_struct)?;
1285            Ok((ident, &f.ty, attrs))
1286        })
1287        .collect::<syn::Result<_>>()?;
1288    check_duplicate_slots(&field_data, is_segment_struct)?;
1289    let (slot_prelude, slots) = resolve_slots(&struct_attrs, &field_data)?;
1290    let uses_code_slots = field_data
1291        .iter()
1292        .any(|(_, _, attrs)| matches!(attrs.element, Some(Position::Code(_))));
1293
1294    let body = if let Some(seg_tag) = &struct_attrs.segment {
1295        if uses_code_slots {
1296            return Ok(impl_serialize_sparse(
1297                name,
1298                seg_tag,
1299                &struct_attrs,
1300                &field_data,
1301                &slots,
1302                &slot_prelude,
1303            ));
1304        }
1305        // ── Segment struct: emit one EDIFACT segment ──────────────────────────
1306        //
1307        // Fields are laid out on a two-dimensional grid — data element index by
1308        // component index — at macro-expansion time, so the generated code stays
1309        // straight-line event emission with no runtime allocation.
1310        //
1311        // The grid is what makes components work at all. Keying the layout on
1312        // the element index alone collapsed every field sharing an element into
1313        // one entry, so all but the last were silently dropped on write:
1314        // a `DTM` with three `component` fields went out as `DTM+102'` instead of
1315        // `DTM+137:20260101:102'`, and the loss was invisible until a partner
1316        // rejected the file.
1317
1318        let mut grid: std::collections::BTreeMap<u32, std::collections::BTreeMap<u32, Cell>> =
1319            std::collections::BTreeMap::new();
1320
1321        if struct_attrs.qualifier.is_some() {
1322            // The qualifier occupies element 0 / component 0, so a field cannot.
1323            for (i, (ident, _, attrs)) in field_data.iter().enumerate() {
1324                let elem = static_element_index(attrs, i);
1325                let comp = attrs.component.unwrap_or(0);
1326                if elem == 0 && comp == 0 {
1327                    return Err(syn::Error::new(
1328                        attrs
1329                            .element_span
1330                            .or(attrs.component_span)
1331                            .unwrap_or_else(|| ident.span()),
1332                        format!(
1333                            "field `{ident}`: cannot use #[edifact(qualifier = ...)] with a field at element = 0 without component >= 1; the qualifier occupies component 0"
1334                        ),
1335                    ));
1336                }
1337            }
1338            grid.entry(0).or_default().insert(0, Cell::Qualifier);
1339        }
1340
1341        for (i, (_, _, attrs)) in field_data.iter().enumerate() {
1342            // Group fields belong to message structs and occupy no slot;
1343            // `validate_field_attrs` already rejects them on a segment struct.
1344            if attrs.group {
1345                continue;
1346            }
1347            let element = static_element_index(attrs, i);
1348            let component = attrs.component.unwrap_or(0);
1349            // `check_duplicate_slots` has already proved this cell is free.
1350            grid.entry(element)
1351                .or_default()
1352                .insert(component, Cell::Field(i));
1353        }
1354
1355        let empty_element = quote! {
1356            emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
1357        };
1358        let empty_component = quote! {
1359            emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: "" })?;
1360        };
1361
1362        let mut elem_stmts: Vec<TokenStream2> = Vec::new();
1363        if let Some(&max_element) = grid.keys().max() {
1364            for element in 0..=max_element {
1365                let Some(components) = grid.get(&element) else {
1366                    // A declared gap between elements is an empty element.
1367                    elem_stmts.push(empty_element.clone());
1368                    continue;
1369                };
1370                let max_component = components.keys().max().copied().unwrap_or(0);
1371
1372                // A repetition separator divides whole occurrences, so an
1373                // element with any repeating field is emitted as a unit: every
1374                // component is re-stated for each occurrence.
1375                let repeating: Vec<u32> = components
1376                    .iter()
1377                    .filter_map(|(component, cell)| match cell {
1378                        Cell::Field(index) if field_data[*index].2.repeat => Some(*component),
1379                        _ => None,
1380                    })
1381                    .collect();
1382                if !repeating.is_empty() {
1383                    if let Some(&component) = components.iter().find_map(|(c, cell)| match cell {
1384                        Cell::Field(index) if field_data[*index].2.composite => Some(c),
1385                        _ => None,
1386                    }) {
1387                        let (ident, _, _) = &field_data[match components[&component] {
1388                            Cell::Field(index) => index,
1389                            Cell::Qualifier => unreachable!("a qualifier is not composite"),
1390                        }];
1391                        return Err(syn::Error::new(
1392                            ident.span(),
1393                            format!(
1394                                "field `{ident}`: a data element cannot hold both a \
1395                                 #[edifact(composite)] field and a #[edifact(repeat)] one — the \
1396                                 composite already spans the whole element"
1397                            ),
1398                        ));
1399                    }
1400                    let cells: Vec<Option<(&syn::Ident, &Type, bool)>> = (0..=max_component)
1401                        .map(|component| match components.get(&component) {
1402                            Some(Cell::Field(index)) => {
1403                                let (ident, ty, attrs) = &field_data[*index];
1404                                Some((*ident, *ty, attrs.repeat))
1405                            }
1406                            // The struct-level qualifier is a literal, not a
1407                            // field, and it is always at component 0 — where a
1408                            // repeating element's qualifier belongs anyway.
1409                            Some(Cell::Qualifier) | None => None,
1410                        })
1411                        .collect();
1412                    let mut cells = cells;
1413                    if matches!(components.get(&0), Some(Cell::Qualifier)) {
1414                        let qual = struct_attrs.qualifier.clone().unwrap_or_default();
1415                        let literal = syn::LitStr::new(&qual, proc_macro2::Span::call_site());
1416                        elem_stmts.push(emit_repeating_element_with_qualifier(
1417                            &literal,
1418                            &cells[1..],
1419                            &repeat_lengths(&field_data, components),
1420                        ));
1421                        continue;
1422                    }
1423                    cells.truncate(usize::try_from(max_component).unwrap_or(0) + 1);
1424                    elem_stmts.push(emit_repeating_element(
1425                        &cells,
1426                        &repeat_lengths(&field_data, components),
1427                    ));
1428                    continue;
1429                }
1430
1431                for component in 0..=max_component {
1432                    match components.get(&component) {
1433                        Some(Cell::Qualifier) => {
1434                            let qual = struct_attrs.qualifier.as_deref().unwrap_or("");
1435                            elem_stmts.push(quote! {
1436                                emitter.emit(::edifact_rs::EdifactEvent::Element { value: #qual })?;
1437                            });
1438                        }
1439                        Some(Cell::Field(index)) => {
1440                            let (ident, ty, attrs) = &field_data[*index];
1441                            if attrs.composite {
1442                                // A composite field emits its own Element plus
1443                                // ComponentElement events for the whole slot.
1444                                elem_stmts.push(emit_composite_field(ident, ty));
1445                            } else if component == 0 {
1446                                elem_stmts.push(emit_element(ident, ty));
1447                            } else {
1448                                elem_stmts.push(emit_component_element(ident, ty));
1449                            }
1450                        }
1451                        None if component == 0 => elem_stmts.push(empty_element.clone()),
1452                        None => elem_stmts.push(empty_component.clone()),
1453                    }
1454                }
1455            }
1456        }
1457
1458        quote! {
1459            emitter.emit(::edifact_rs::EdifactEvent::StartSegment { tag: #seg_tag })?;
1460            #(#elem_stmts)*
1461            emitter.emit(::edifact_rs::EdifactEvent::EndSegment)?;
1462        }
1463    } else {
1464        // ── Message struct: delegate to each field ────────────────────────────
1465        let stmts: Vec<TokenStream2> = field_data
1466            .iter()
1467            .map(|(ident, ty, attrs)| {
1468                if attrs.group || is_vec_type(ty) {
1469                    quote! {
1470                        for __item in &self.#ident {
1471                            ::edifact_rs::EdifactSerialize::edifact_serialize(__item, emitter)?;
1472                        }
1473                    }
1474                } else {
1475                    quote! {
1476                        ::edifact_rs::EdifactSerialize::edifact_serialize(&self.#ident, emitter)?;
1477                    }
1478                }
1479            })
1480            .collect();
1481        quote! { #(#stmts)* }
1482    };
1483
1484    Ok(quote! {
1485        impl ::edifact_rs::EdifactSerialize for #name {
1486            fn edifact_serialize<__E: ::edifact_rs::EventEmitter>(
1487                &self,
1488                emitter: &mut __E,
1489            ) -> ::core::result::Result<(), ::edifact_rs::EdifactError> {
1490                #body
1491                ::core::result::Result::Ok(())
1492            }
1493        }
1494    })
1495}
1496
1497/// Generate the `let` binding that reads a `#[edifact(repeat)]` field.
1498///
1499/// One item per occurrence of the data element, taken from the same component
1500/// position in each — `RFF+ON:1*ON:2` at component 1 is `["1", "2"]`.
1501///
1502/// A missing element yields an empty `Vec` rather than an error: the field is a
1503/// collection, and "no occurrences" is what an absent repeating element means.
1504fn repeating_field_init(
1505    ident: &syn::Ident,
1506    ty: &Type,
1507    element: &TokenStream2,
1508    component: &TokenStream2,
1509    owned: bool,
1510) -> syn::Result<TokenStream2> {
1511    let inner = vec_inner_type(ty).ok_or_else(|| {
1512        syn::Error::new(
1513            ident.span(),
1514            format!("field `{ident}`: #[edifact(repeat)] requires Vec<T>"),
1515        )
1516    })?;
1517    let reader = if owned {
1518        quote! { ::edifact_rs::repeated_components_owned }
1519    } else {
1520        quote! { ::edifact_rs::repeated_components }
1521    };
1522    // `&str` items would borrow from the segment, which the owned path cannot
1523    // offer and the borrowed path only can with a lifetime the derive does not
1524    // thread; `String` and parsed scalars cover the real cases.
1525    let convert = if is_string_type(inner) {
1526        quote! { ::core::result::Result::Ok(::std::string::ToString::to_string(__value)) }
1527    } else {
1528        let message = format!(
1529            "field `{ident}`: repeating value is not a valid {}",
1530            quote!(#inner)
1531        );
1532        quote! {
1533            __value.parse::<#inner>().map_err(|_| ::edifact_rs::EdifactError::InvalidFieldValue {
1534                tag: __seg.tag.to_string(),
1535                element_index: #element,
1536                value: ::std::format!("{}: {}", #message, __value),
1537            })
1538        }
1539    };
1540    Ok(quote! {
1541        let #ident = #reader(__seg, #element, #component)
1542            .map(|__value| #convert)
1543            .collect::<::core::result::Result<::std::vec::Vec<#inner>, ::edifact_rs::EdifactError>>()?;
1544    })
1545}
1546
1547/// What occupies one `(element, component)` cell of a segment's layout.
1548enum Cell {
1549    /// The struct-level `#[edifact(qualifier = "…")]`, which owns (0, 0).
1550    Qualifier,
1551    /// An index into `field_data`.
1552    Field(usize),
1553}
1554
1555/// `Vec::len()` expressions for every repeating field of one element.
1556fn repeat_lengths(
1557    field_data: &[(&syn::Ident, &Type, FieldAttrs)],
1558    components: &std::collections::BTreeMap<u32, Cell>,
1559) -> Vec<TokenStream2> {
1560    components
1561        .values()
1562        .filter_map(|cell| match cell {
1563            Cell::Field(index) if field_data[*index].2.repeat => {
1564                let ident = field_data[*index].0;
1565                Some(quote! { self.#ident.len() })
1566            }
1567            _ => None,
1568        })
1569        .collect()
1570}
1571
1572/// [`emit_repeating_element`] for an element whose component 0 is the
1573/// struct-level qualifier literal rather than a field.
1574fn emit_repeating_element_with_qualifier(
1575    qualifier: &syn::LitStr,
1576    rest: &[Option<(&syn::Ident, &Type, bool)>],
1577    lengths: &[TokenStream2],
1578) -> TokenStream2 {
1579    let values: Vec<TokenStream2> = rest
1580        .iter()
1581        .map(|cell| match cell {
1582            Some((ident, ty, repeat)) => occurrence_value(ident, ty, *repeat),
1583            None => quote! { ::std::borrow::Cow::Borrowed("") },
1584        })
1585        .collect();
1586    quote! {
1587        {
1588            let __n = ::core::cmp::max(1usize, [#(#lengths),*].into_iter().max().unwrap_or(0));
1589            for __k in 0..__n {
1590                let __event = if __k == 0 {
1591                    ::edifact_rs::EdifactEvent::Element { value: #qualifier }
1592                } else {
1593                    ::edifact_rs::EdifactEvent::RepeatElement { value: #qualifier }
1594                };
1595                emitter.emit(__event)?;
1596                #(
1597                    let __v = #values;
1598                    emitter.emit(::edifact_rs::EdifactEvent::ComponentElement {
1599                        value: __v.as_ref(),
1600                    })?;
1601                )*
1602            }
1603        }
1604    }
1605}
1606
1607/// One component's value at occurrence `__k`, as a `Cow<str>` expression.
1608///
1609/// A repeating field takes its `__k`-th item; a non-repeating one is constant
1610/// across occurrences, which is exactly right for `RFF+ON:1*ON:2` — the
1611/// qualifier `ON` belongs to every occurrence, and the standard requires it to
1612/// be transferred in each.
1613fn occurrence_value(ident: &syn::Ident, ty: &Type, repeat: bool) -> TokenStream2 {
1614    let empty = quote! { ::std::borrow::Cow::Borrowed("") };
1615    if repeat {
1616        let inner_is_str = vec_inner_type(ty).is_some_and(is_str_like);
1617        let map = if inner_is_str {
1618            quote! { |__v| ::std::borrow::Cow::Borrowed(__v.as_ref()) }
1619        } else {
1620            quote! { |__v| ::std::borrow::Cow::Owned(::std::string::ToString::to_string(__v)) }
1621        };
1622        return quote! { self.#ident.get(__k).map(#map).unwrap_or(#empty) };
1623    }
1624    if is_option_type(ty) {
1625        return if option_inner_type(ty).is_some_and(is_str_like) {
1626            quote! {
1627                self.#ident
1628                    .as_deref()
1629                    .map(::std::borrow::Cow::Borrowed)
1630                    .unwrap_or(#empty)
1631            }
1632        } else {
1633            quote! {
1634                self.#ident
1635                    .as_ref()
1636                    .map(|__v| ::std::borrow::Cow::Owned(::std::string::ToString::to_string(__v)))
1637                    .unwrap_or(#empty)
1638            }
1639        };
1640    }
1641    if is_string_type(ty) {
1642        quote! { ::std::borrow::Cow::Borrowed(self.#ident.as_str()) }
1643    } else if is_str_ref_type(ty) {
1644        quote! { ::std::borrow::Cow::Borrowed(self.#ident) }
1645    } else {
1646        quote! { ::std::borrow::Cow::Owned(::std::string::ToString::to_string(&self.#ident)) }
1647    }
1648}
1649
1650/// Emit a data element that repeats (ISO 9735-1 §8.6).
1651///
1652/// The repetition separator divides whole **occurrences**, not components, so
1653/// every component of the element is re-emitted for each one — `RFF+ON:1*ON:2`
1654/// carries its `ON` qualifier twice. `components` is the element's full
1655/// component list in order; `None` is a gap that stays empty.
1656///
1657/// The occurrence count is the longest repeating field in the element, and at
1658/// least one: an element with no occurrences is still transferred, as absent,
1659/// which is what keeps the elements after it in position (§8.7.1).
1660fn emit_repeating_element(
1661    components: &[Option<(&syn::Ident, &Type, bool)>],
1662    lengths: &[TokenStream2],
1663) -> TokenStream2 {
1664    let values: Vec<TokenStream2> = components
1665        .iter()
1666        .map(|cell| match cell {
1667            Some((ident, ty, repeat)) => occurrence_value(ident, ty, *repeat),
1668            None => quote! { ::std::borrow::Cow::Borrowed("") },
1669        })
1670        .collect();
1671    let (first, rest) = values.split_first().expect("an element has ≥1 component");
1672    quote! {
1673        {
1674            let __n = ::core::cmp::max(1usize, [#(#lengths),*].into_iter().max().unwrap_or(0));
1675            for __k in 0..__n {
1676                let __v = #first;
1677                let __event = if __k == 0 {
1678                    ::edifact_rs::EdifactEvent::Element { value: __v.as_ref() }
1679                } else {
1680                    ::edifact_rs::EdifactEvent::RepeatElement { value: __v.as_ref() }
1681                };
1682                emitter.emit(__event)?;
1683                #(
1684                    let __v = #rest;
1685                    emitter.emit(::edifact_rs::EdifactEvent::ComponentElement {
1686                        value: __v.as_ref(),
1687                    })?;
1688                )*
1689            }
1690        }
1691    }
1692}
1693
1694/// Generate the token stream that emits field `ident` (of type `ty`) as one element.
1695///
1696/// For `String` and `&str` fields the value is emitted zero-copy via `.as_str()`
1697/// (or directly).  All other types fall back to `ToString::to_string`.
1698fn emit_element(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
1699    if is_option_type(ty) {
1700        let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
1701        if inner_is_str {
1702            quote! {
1703                match &self.#ident {
1704                    ::core::option::Option::Some(__v) => {
1705                        emitter.emit(::edifact_rs::EdifactEvent::Element { value: __v.as_str() })?;
1706                    }
1707                    ::core::option::Option::None => {
1708                        emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
1709                    }
1710                }
1711            }
1712        } else {
1713            quote! {
1714                match &self.#ident {
1715                    ::core::option::Option::Some(__v) => {
1716                        let __s = ::std::string::ToString::to_string(__v);
1717                        emitter.emit(::edifact_rs::EdifactEvent::Element { value: &__s })?;
1718                    }
1719                    ::core::option::Option::None => {
1720                        emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
1721                    }
1722                }
1723            }
1724        }
1725    } else if is_string_type(ty) {
1726        quote! {
1727            emitter.emit(::edifact_rs::EdifactEvent::Element { value: self.#ident.as_str() })?;
1728        }
1729    } else if is_str_ref_type(ty) {
1730        quote! {
1731            emitter.emit(::edifact_rs::EdifactEvent::Element { value: self.#ident })?;
1732        }
1733    } else {
1734        quote! {
1735            {
1736                let __s = ::std::string::ToString::to_string(&self.#ident);
1737                emitter.emit(::edifact_rs::EdifactEvent::Element { value: &__s })?;
1738            }
1739        }
1740    }
1741}
1742
1743/// Generate the token stream that emits field `ident` as a composite component (`ComponentElement`).
1744///
1745/// For `String` and `&str` fields the value is emitted zero-copy.
1746fn emit_component_element(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
1747    if is_option_type(ty) {
1748        let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
1749        if inner_is_str {
1750            quote! {
1751                match &self.#ident {
1752                    ::core::option::Option::Some(__v) => {
1753                        emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: __v.as_str() })?;
1754                    }
1755                    ::core::option::Option::None => {
1756                        emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: "" })?;
1757                    }
1758                }
1759            }
1760        } else {
1761            quote! {
1762                match &self.#ident {
1763                    ::core::option::Option::Some(__v) => {
1764                        let __s = ::std::string::ToString::to_string(__v);
1765                        emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: &__s })?;
1766                    }
1767                    ::core::option::Option::None => {
1768                        emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: "" })?;
1769                    }
1770                }
1771            }
1772        }
1773    } else if is_string_type(ty) {
1774        quote! {
1775            emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: self.#ident.as_str() })?;
1776        }
1777    } else if is_str_ref_type(ty) {
1778        quote! {
1779            emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: self.#ident })?;
1780        }
1781    } else {
1782        quote! {
1783            {
1784                let __s = ::std::string::ToString::to_string(&self.#ident);
1785                emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: &__s })?;
1786            }
1787        }
1788    }
1789}
1790
1791/// Generate the token stream that emits a full composite field via `EdifactCompositeSerialize`.
1792fn emit_composite_field(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
1793    if is_option_type(ty) {
1794        quote! {
1795            match &self.#ident {
1796                ::core::option::Option::Some(__v) => {
1797                    ::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(__v, emitter)?;
1798                }
1799                ::core::option::Option::None => {
1800                    emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
1801                }
1802            }
1803        }
1804    } else {
1805        quote! {
1806            ::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(&self.#ident, emitter)?;
1807        }
1808    }
1809}
1810
1811// ── EdifactDeserialize ─────────────────────────────────────────────────────────
1812
1813fn impl_deserialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
1814    let name = &input.ident;
1815    let struct_attrs = parse_struct_attrs(input)?;
1816    let fields = get_named_fields(input)?;
1817    let is_segment_struct = struct_attrs.segment.is_some();
1818
1819    let field_data: Vec<(&syn::Ident, &Type, FieldAttrs)> = fields
1820        .named
1821        .iter()
1822        .map(|f| {
1823            let attrs = parse_field_attrs(f)?;
1824            let ident = f
1825                .ident
1826                .as_ref()
1827                .ok_or_else(|| syn::Error::new_spanned(f, "only named fields are supported"))?;
1828            validate_field_attrs(ident, &f.ty, &attrs, is_segment_struct)?;
1829            Ok((ident, &f.ty, attrs))
1830        })
1831        .collect::<syn::Result<_>>()?;
1832    check_duplicate_slots(&field_data, is_segment_struct)?;
1833    let (slot_prelude, slots) = resolve_slots(&struct_attrs, &field_data)?;
1834
1835    let field_names: Vec<&syn::Ident> = field_data.iter().map(|(id, _, _)| *id).collect();
1836
1837    let (body, owned_body, segment_tag_impl) = if let Some(seg_tag) = &struct_attrs.segment {
1838        // ── Segment struct ────────────────────────────────────────────────────
1839        let qualifier_guard = if let Some(qual) = &struct_attrs.qualifier {
1840            quote! {
1841                if __seg.element_str(0).unwrap_or("") != #qual {
1842                    return ::core::result::Result::Err(
1843                        ::edifact_rs::EdifactError::MissingRequiredElement {
1844                            tag: #seg_tag.to_owned(),
1845                            element_index: 0,
1846                        }
1847                    );
1848                }
1849            }
1850        } else if let Some(idx) = struct_attrs.qualifier_from {
1851            quote! {
1852                // Fully-qualified patterns: a user type named `Some`/`None` in
1853                // scope (e.g. `pub use MyOpt::*`) would otherwise shadow the
1854                // std variants and break the generated code.
1855                match __seg.element_str(#idx as usize) {
1856                    ::core::option::Option::None => return ::core::result::Result::Err(
1857                        ::edifact_rs::EdifactError::MissingRequiredElement {
1858                            tag: #seg_tag.to_owned(),
1859                            element_index: #idx as usize,
1860                        }
1861                    ),
1862                    ::core::option::Option::Some("") => return ::core::result::Result::Err(
1863                        ::edifact_rs::EdifactError::InvalidFieldValue {
1864                            tag: #seg_tag.to_owned(),
1865                            element_index: #idx as usize,
1866                            value: ::std::string::String::new(),
1867                        }
1868                    ),
1869                    ::core::option::Option::Some(__qual_val) => { let _ = __qual_val; }
1870                }
1871            }
1872        } else {
1873            quote! {}
1874        };
1875
1876        let find_seg = if let Some(qual) = &struct_attrs.qualifier {
1877            quote! {
1878                ::edifact_rs::find_qualified_segment(segments, #seg_tag, #qual)
1879            }
1880        } else {
1881            quote! {
1882                ::edifact_rs::find_segment(segments, #seg_tag)
1883            }
1884        };
1885
1886        let field_inits: Vec<TokenStream2> = field_data
1887            .iter()
1888            .zip(slots.iter())
1889            .map(|((ident, ty, attrs), slot)| -> syn::Result<TokenStream2> {
1890                let idx = &slot.element;
1891                if attrs.repeat {
1892                    return repeating_field_init(ident, ty, &slot.element, &slot.component, false);
1893                }
1894                if attrs.composite {
1895                    if is_option_type(ty) {
1896                        let inner_ty = option_inner_type(ty)
1897                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1898                        return Ok(quote! {
1899                            let #ident = match ::edifact_rs::composite_element(__seg, #idx) {
1900                                ::core::option::Option::Some(__composite) => {
1901                                    ::core::option::Option::Some(
1902                                        <#inner_ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(__composite)?
1903                                    )
1904                                }
1905                                ::core::option::Option::None => ::core::option::Option::None,
1906                            };
1907                        });
1908                    }
1909                    return Ok(quote! {
1910                        let #ident = <#ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
1911                            ::edifact_rs::composite_element(__seg, #idx).ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1912                                tag: #seg_tag.to_owned(),
1913                                element_index: #idx,
1914                            })?
1915                        )?;
1916                    });
1917                }
1918                let comp = &slot.component;
1919                let value_expr = if slot.has_component {
1920                    quote! {
1921                        __seg.get_element(#idx).and_then(|__e| __e.get_component(#comp))
1922                    }
1923                } else {
1924                    quote! { __seg.element_str(#idx) }
1925                };
1926                // Report the variant that matches what the field actually
1927                // addresses.  For a code slot `names_component` is a `const`
1928                // lookup, so this branch folds away.
1929                let names_component = &slot.names_component;
1930                let missing_required_err = quote! {
1931                    if #names_component {
1932                        ::edifact_rs::EdifactError::MissingRequiredComponent {
1933                            tag: #seg_tag.to_owned(),
1934                            element_index: #idx,
1935                            component_index: #comp,
1936                        }
1937                    } else {
1938                        ::edifact_rs::EdifactError::MissingRequiredElement {
1939                            tag: #seg_tag.to_owned(),
1940                            element_index: #idx,
1941                        }
1942                    }
1943                };
1944                Ok(if is_option_type(ty) {
1945                    let inner_ty = option_inner_type(ty);
1946                    let inner_is_str = inner_ty.is_some_and(is_str_like);
1947                    if attrs.required {
1948                        // #[edifact(required)] on Option<T>: treat absence as an error.
1949                        // Emits MissingRequiredComponent when combined with component = N,
1950                        // MissingRequiredElement otherwise.
1951                        if inner_is_str {
1952                            quote! {
1953                                let #ident = ::core::option::Option::Some(
1954                                    #value_expr
1955                                        .filter(|__s| !__s.is_empty())
1956                                        .ok_or_else(|| #missing_required_err)?
1957                                        .to_owned()
1958                                );
1959                            }
1960                        } else {
1961                            let inner_ty = inner_ty
1962                                .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1963                            quote! {
1964                                let #ident = ::core::option::Option::Some(
1965                                    #value_expr
1966                                        .filter(|__s| !__s.is_empty())
1967                                        .ok_or_else(|| #missing_required_err)?
1968                                        .parse::<#inner_ty>()
1969                                        .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?
1970                                );
1971                            }
1972                        }
1973                    } else if inner_is_str {
1974                        quote! {
1975                            let #ident = #value_expr
1976                                .filter(|__s| !__s.is_empty())
1977                                .map(::std::string::String::from);
1978                        }
1979                    } else {
1980                        let inner_ty = inner_ty
1981                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1982                        quote! {
1983                            let #ident = #value_expr
1984                                .filter(|__s| !__s.is_empty())
1985                                .map(|__s| __s.parse::<#inner_ty>()
1986                                    .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })
1987                                )
1988                                .transpose()?;
1989                        }
1990                    }
1991                } else if is_str_like(ty) {
1992                    quote! {
1993                        let #ident = #value_expr
1994                            .filter(|__s| !__s.is_empty())
1995                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1996                                tag: #seg_tag.to_owned(),
1997                                element_index: #idx,
1998                            })?
1999                            .to_owned();
2000                    }
2001                } else {
2002                    quote! {
2003                        let #ident = #value_expr
2004                            .filter(|__s| !__s.is_empty())
2005                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
2006                                tag: #seg_tag.to_owned(),
2007                                element_index: #idx,
2008                            })?
2009                            .parse::<#ty>()
2010                            .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?;
2011                    }
2012                })
2013            })
2014            .collect::<syn::Result<_>>()?;
2015
2016        let body = quote! {
2017            #slot_prelude
2018            let __seg = #find_seg
2019                .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
2020                    tag: #seg_tag.to_owned(),
2021                    expected_position: "message body".to_owned(),
2022                })?;
2023            #qualifier_guard
2024            #(#field_inits)*
2025            ::core::result::Result::Ok(Self { #(#field_names),* })
2026        };
2027
2028        // Also generate EdifactSegmentTag impl.
2029        // Declare the qualifier through `QUALIFIER_PATTERN` rather than by
2030        // hand-rolling `matches_segment`.  Overriding only the borrowed matcher
2031        // left `matches_owned_segment` (which consults `QUALIFIER_PATTERN`)
2032        // matching on tag alone, so the owned deserialization path picked up
2033        // wrongly-qualified segments and then failed to parse them.
2034        let qualifier_match = if let Some(qual) = &struct_attrs.qualifier {
2035            quote! {
2036                const QUALIFIER_PATTERN: ::core::option::Option<&'static str> =
2037                    ::core::option::Option::Some(#qual);
2038            }
2039        } else if let Some(idx) = struct_attrs.qualifier_from {
2040            // "Any non-empty value at element `idx`" cannot be expressed as a
2041            // `QUALIFIER_PATTERN` (which is element 0 only), so both matchers are
2042            // overridden explicitly and must stay in agreement.
2043            quote! {
2044                fn matches_segment(seg: &::edifact_rs::Segment<'_>) -> bool {
2045                    seg.tag == Self::SEGMENT_TAG
2046                        && !seg.element_str(#idx as usize).unwrap_or("").is_empty()
2047                }
2048
2049                fn matches_owned_segment(seg: &::edifact_rs::OwnedSegment) -> bool {
2050                    seg.tag == Self::SEGMENT_TAG
2051                        && !seg
2052                            .elements
2053                            .get(#idx as usize)
2054                            .and_then(|e| e.components.first())
2055                            .map(|(c, _)| c.as_str())
2056                            .unwrap_or("")
2057                            .is_empty()
2058                }
2059            }
2060        } else {
2061            quote! {}
2062        };
2063
2064        let seg_tag_impl = quote! {
2065            impl ::edifact_rs::EdifactSegmentTag for #name {
2066                const SEGMENT_TAG: &'static str = #seg_tag;
2067                #qualifier_match
2068            }
2069        };
2070
2071        // ── Owned-segment deserialization path ────────────────────────────────
2072        // Works directly on `&[OwnedSegment]` without allocating a `Vec<Segment>`.
2073        let find_seg_owned = if let Some(qual) = &struct_attrs.qualifier {
2074            quote! {
2075                ::edifact_rs::find_qualified_segment_owned(segments, #seg_tag, #qual)
2076            }
2077        } else {
2078            quote! {
2079                ::edifact_rs::find_segment_owned(segments, #seg_tag)
2080            }
2081        };
2082
2083        let field_inits_owned: Vec<TokenStream2> = field_data
2084            .iter()
2085            .zip(slots.iter())
2086            .map(|((ident, ty, attrs), slot)| -> syn::Result<TokenStream2> {
2087                let idx = &slot.element;
2088                if attrs.repeat {
2089                    return repeating_field_init(ident, ty, &slot.element, &slot.component, true);
2090                }
2091                if attrs.composite {
2092                    if is_option_type(ty) {
2093                        let inner_ty = option_inner_type(ty)
2094                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
2095                        return Ok(quote! {
2096                            let #ident = match __seg.elements.get(#idx) {
2097                                ::core::option::Option::Some(__e) => {
2098                                    let __cows = __e.components.iter()
2099                                        .map(|(s, _)| ::std::borrow::Cow::Borrowed(s.as_str()))
2100                                        .collect::<::std::vec::Vec<::std::borrow::Cow<'_, str>>>();
2101                                    ::core::option::Option::Some(
2102                                        <#inner_ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
2103                                            ::edifact_rs::CompositeElement::from_slice(&__cows)
2104                                        )?
2105                                    )
2106                                }
2107                                ::core::option::Option::None => ::core::option::Option::None,
2108                            };
2109                        });
2110                    }
2111                    return Ok(quote! {
2112                        let #ident = {
2113                            let __cows = __seg.elements.get(#idx)
2114                                .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
2115                                    tag: #seg_tag.to_owned(),
2116                                    element_index: #idx,
2117                                })?
2118                                .components.iter()
2119                                .map(|(s, _)| ::std::borrow::Cow::Borrowed(s.as_str()))
2120                                .collect::<::std::vec::Vec<::std::borrow::Cow<'_, str>>>();
2121                            <#ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
2122                                ::edifact_rs::CompositeElement::from_slice(&__cows)
2123                            )?
2124                        };
2125                    });
2126                }
2127                let comp = &slot.component;
2128                let value_expr_owned = if slot.has_component {
2129                    quote! { __seg.component_str(#idx, #comp) }
2130                } else {
2131                    quote! { __seg.element_str(#idx) }
2132                };
2133                // Same variant selection as the borrowed path; the two must
2134                // agree or the same input yields different error codes.
2135                let names_component = &slot.names_component;
2136                let missing_required_err_owned = quote! {
2137                    if #names_component {
2138                        ::edifact_rs::EdifactError::MissingRequiredComponent {
2139                            tag: #seg_tag.to_owned(),
2140                            element_index: #idx,
2141                            component_index: #comp,
2142                        }
2143                    } else {
2144                        ::edifact_rs::EdifactError::MissingRequiredElement {
2145                            tag: #seg_tag.to_owned(),
2146                            element_index: #idx,
2147                        }
2148                    }
2149                };
2150                Ok(if is_option_type(ty) {
2151                    if let Some(inner_ty) = option_inner_type(ty) {
2152                        if attrs.required {
2153                            // #[edifact(required)] on Option<T>: absence is an error.
2154                            // Emits MissingRequiredComponent when combined with component = N,
2155                            // MissingRequiredElement otherwise.
2156                            if is_str_like(inner_ty) {
2157                                quote! {
2158                                    let #ident = ::core::option::Option::Some(
2159                                        #value_expr_owned
2160                                            .filter(|__s| !__s.is_empty())
2161                                            .ok_or_else(|| #missing_required_err_owned)?
2162                                            .to_owned()
2163                                    );
2164                                }
2165                            } else {
2166                                quote! {
2167                                    let #ident = ::core::option::Option::Some(
2168                                        #value_expr_owned
2169                                            .filter(|__s| !__s.is_empty())
2170                                            .ok_or_else(|| #missing_required_err_owned)?
2171                                            .parse::<#inner_ty>()
2172                                            .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?
2173                                    );
2174                                }
2175                            }
2176                        } else if is_str_like(inner_ty) {
2177                            quote! {
2178                                let #ident = #value_expr_owned
2179                                    .filter(|__s| !__s.is_empty())
2180                                    .map(::std::string::String::from);
2181                            }
2182                        } else {
2183                            quote! {
2184                                let #ident = #value_expr_owned
2185                                    .filter(|__s| !__s.is_empty())
2186                                    .map(|__s| __s.parse::<#inner_ty>()
2187                                        .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })
2188                                    )
2189                                    .transpose()?;
2190                            }
2191                        }
2192                    } else {
2193                        // Fallback: treat as String (should not happen with well-formed types).
2194                        quote! {
2195                            let #ident = #value_expr_owned
2196                                .filter(|__s| !__s.is_empty())
2197                                .map(::std::string::String::from);
2198                        }
2199                    }
2200                } else if is_str_like(ty) {
2201                    quote! {
2202                        let #ident = #value_expr_owned
2203                            .filter(|__s| !__s.is_empty())
2204                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
2205                                tag: #seg_tag.to_owned(),
2206                                element_index: #idx,
2207                            })?
2208                            .to_owned();
2209                    }
2210                } else {
2211                    quote! {
2212                        let #ident = #value_expr_owned
2213                            .filter(|__s| !__s.is_empty())
2214                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
2215                                tag: #seg_tag.to_owned(),
2216                                element_index: #idx,
2217                            })?
2218                            .parse::<#ty>()
2219                            .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?;
2220                    }
2221                })
2222            })
2223            .collect::<syn::Result<_>>()?;
2224
2225        let owned_body = quote! {
2226            #slot_prelude
2227            let __seg = #find_seg_owned
2228                .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
2229                    tag: #seg_tag.to_owned(),
2230                    expected_position: "message body".to_owned(),
2231                })?;
2232            #qualifier_guard
2233            #(#field_inits_owned)*
2234            ::core::result::Result::Ok(Self { #(#field_names),* })
2235        };
2236
2237        (body, owned_body, seg_tag_impl)
2238    } else {
2239        // ── Message struct: delegate to each field ────────────────────────────
2240        let field_inits: Vec<TokenStream2> = field_data
2241            .iter()
2242            .map(|(ident, ty, attrs)| -> syn::Result<TokenStream2> {
2243                Ok(if let Some(qual) = &attrs.qualifier {
2244                    if attrs.group || is_vec_type(ty) {
2245                        let inner_ty = vec_inner_type(ty)
2246                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
2247                        quote! {
2248                            let #ident = segments
2249                                .iter()
2250                                .filter(|__seg| {
2251                                    __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG
2252                                        && __seg.element_str(0).unwrap_or("") == #qual
2253                                })
2254                                .map(|__seg| {
2255                                    ::edifact_rs::EdifactDeserialize::edifact_deserialize(
2256                                        ::core::slice::from_ref(__seg),
2257                                    )
2258                                })
2259                                .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, ::edifact_rs::EdifactError>>()?;
2260                        }
2261                    } else if is_option_type(ty) {
2262                        let inner_ty = option_inner_type(ty)
2263                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
2264                        quote! {
2265                            let #ident = match ::edifact_rs::find_qualified_segment(
2266                                segments,
2267                                <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
2268                                #qual,
2269                            ) {
2270                                ::core::option::Option::Some(__seg) => {
2271                                    ::core::option::Option::Some(
2272                                        ::edifact_rs::EdifactDeserialize::edifact_deserialize(
2273                                            ::core::slice::from_ref(__seg),
2274                                        )?
2275                                    )
2276                                }
2277                                ::core::option::Option::None => ::core::option::Option::None,
2278                            };
2279                        }
2280                    } else {
2281                        quote! {
2282                            let __seg = ::edifact_rs::find_qualified_segment(
2283                                segments,
2284                                <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
2285                                #qual,
2286                            )
2287                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
2288                                tag: <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG.to_owned(),
2289                                expected_position: "message body".to_owned(),
2290                            })?;
2291                            let #ident = ::edifact_rs::EdifactDeserialize::edifact_deserialize(
2292                                ::core::slice::from_ref(__seg),
2293                            )?;
2294                        }
2295                    }
2296                } else if attrs.group || is_vec_type(ty) {
2297                    let inner_ty = vec_inner_type(ty)
2298                        .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
2299                    quote! {
2300                        let #ident = ::edifact_rs::find_segments_typed::<#inner_ty>(segments)
2301                            .map(|__seg| {
2302                                <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize(
2303                                    ::core::slice::from_ref(__seg),
2304                                )
2305                            })
2306                            .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, _>>()?;
2307                    }
2308                } else if is_option_type(ty) {
2309                    let inner_ty = option_inner_type(ty)
2310                        .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
2311                    quote! {
2312                        let #ident = if segments
2313                            .iter()
2314                            .any(|__seg| __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG)
2315                        {
2316                            ::core::option::Option::Some(
2317                                <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize(segments)?
2318                            )
2319                        } else {
2320                            ::core::option::Option::None
2321                        };
2322                    }
2323                } else {
2324                    quote! {
2325                        let #ident = ::edifact_rs::EdifactDeserialize::edifact_deserialize(segments)?;
2326                    }
2327                })
2328            })
2329            .collect::<syn::Result<_>>()?;
2330
2331        let body = quote! {
2332            #(#field_inits)*
2333            ::core::result::Result::Ok(Self { #(#field_names),* })
2334        };
2335
2336        // ── Owned-segment message deserialization path ────────────────────────
2337        // Works directly on `&[OwnedSegment]` without converting to `Vec<Segment>`.
2338        let field_inits_owned: Vec<TokenStream2> = field_data
2339            .iter()
2340            .map(|(ident, ty, attrs)| -> syn::Result<TokenStream2> {
2341                Ok(if let Some(qual) = &attrs.qualifier {
2342                    if attrs.group || is_vec_type(ty) {
2343                        let inner_ty = vec_inner_type(ty)
2344                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
2345                        quote! {
2346                            let #ident = segments
2347                                .iter()
2348                                .filter(|__seg| {
2349                                    __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG
2350                                        && __seg.element_str(0).unwrap_or("") == #qual
2351                                })
2352                                .map(|__seg| {
2353                                    <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
2354                                        ::core::slice::from_ref(__seg),
2355                                    )
2356                                })
2357                                .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, ::edifact_rs::EdifactError>>()?;
2358                        }
2359                    } else if is_option_type(ty) {
2360                        let inner_ty = option_inner_type(ty)
2361                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
2362                        quote! {
2363                            let #ident = match ::edifact_rs::find_qualified_segment_owned(
2364                                segments,
2365                                <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
2366                                #qual,
2367                            ) {
2368                                ::core::option::Option::Some(__seg) => {
2369                                    ::core::option::Option::Some(
2370                                        <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
2371                                            ::core::slice::from_ref(__seg),
2372                                        )?
2373                                    )
2374                                }
2375                                ::core::option::Option::None => ::core::option::Option::None,
2376                            };
2377                        }
2378                    } else {
2379                        quote! {
2380                            let __seg = ::edifact_rs::find_qualified_segment_owned(
2381                                segments,
2382                                <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
2383                                #qual,
2384                            )
2385                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
2386                                tag: <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG.to_owned(),
2387                                expected_position: "message body".to_owned(),
2388                            })?;
2389                            let #ident = <#ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
2390                                ::core::slice::from_ref(__seg),
2391                            )?;
2392                        }
2393                    }
2394                } else if attrs.group || is_vec_type(ty) {
2395                    let inner_ty = vec_inner_type(ty)
2396                        .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
2397                    quote! {
2398                        let #ident = segments
2399                            .iter()
2400                            .filter(|__seg| <#inner_ty as ::edifact_rs::EdifactSegmentTag>::matches_owned_segment(__seg))
2401                            .map(|__seg| {
2402                                <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
2403                                    ::core::slice::from_ref(__seg),
2404                                )
2405                            })
2406                            .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, _>>()?;
2407                    }
2408                } else if is_option_type(ty) {
2409                    let inner_ty = option_inner_type(ty)
2410                        .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
2411                    quote! {
2412                        let #ident = if segments
2413                            .iter()
2414                            .any(|__seg| __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG)
2415                        {
2416                            ::core::option::Option::Some(
2417                                <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(segments)?
2418                            )
2419                        } else {
2420                            ::core::option::Option::None
2421                        };
2422                    }
2423                } else {
2424                    quote! {
2425                        let #ident = <#ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(segments)?;
2426                    }
2427                })
2428            })
2429            .collect::<syn::Result<_>>()?;
2430
2431        let owned_body = quote! {
2432            #(#field_inits_owned)*
2433            ::core::result::Result::Ok(Self { #(#field_names),* })
2434        };
2435
2436        (body, owned_body, quote! {})
2437    };
2438
2439    Ok(quote! {
2440        impl ::edifact_rs::EdifactDeserialize for #name {
2441            fn edifact_deserialize(
2442                segments: &[::edifact_rs::Segment<'_>],
2443            ) -> ::core::result::Result<Self, ::edifact_rs::EdifactError> {
2444                #body
2445            }
2446
2447            fn edifact_deserialize_owned(
2448                segments: &[::edifact_rs::OwnedSegment],
2449            ) -> ::core::result::Result<Self, ::edifact_rs::EdifactError> {
2450                #owned_body
2451            }
2452        }
2453        #segment_tag_impl
2454    })
2455}
2456
2457#[cfg(test)]
2458mod tests {
2459    /// Compile-fail / compile-pass suite for the derive macros.
2460    ///
2461    /// The blessed `.stderr` files hold only this crate's own diagnostics, so
2462    /// they are stable across toolchains and CI runs the suite on both MSRV and
2463    /// stable.  Keep it that way: an expectation that captures a *rustc*
2464    /// warning or note will drift on the next release and drown real
2465    /// regressions in noise.  If a UI case triggers an incidental lint, silence
2466    /// it at the source (see `tests/ui/support.rs`) rather than blessing it.
2467    ///
2468    /// The suite is off by default because it is slow; set `EDIFACT_UI_TESTS=1`
2469    /// to run it, or use `just ui` / `just ui-msrv`.
2470    ///
2471    /// Re-bless after intentional message changes with `just ui-bless`.
2472    #[test]
2473    fn trybuild_ui() {
2474        if std::env::var_os("EDIFACT_UI_TESTS").is_none() {
2475            eprintln!(
2476                "skipping derive UI suite: set EDIFACT_UI_TESTS=1 to run it \
2477                 (expectations are pinned to the MSRV toolchain)"
2478            );
2479            return;
2480        }
2481        let t = trybuild::TestCases::new();
2482        t.pass("tests/ui/pass_*.rs");
2483        t.compile_fail("tests/ui/fail_*.rs");
2484    }
2485}