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))?; }
937        } else {
938            quote! {
939                __emitter.emit(::edifact_rs::EdifactEvent::component(#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(""))?; }
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::EdifactEvent::Element { value } => {
1195                                __comp = 0;
1196                                __any = true;
1197                                __parts.push((#element, 0usize, ::std::borrow::Cow::Owned(value)));
1198                            }
1199                            ::edifact_rs::EdifactEvent::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(""))?;
1357        };
1358        let empty_component = quote! {
1359            emitter.emit(::edifact_rs::EdifactEvent::component(""))?;
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(#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::start(#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) -> syn::Result<TokenStream2> {
1510    let inner = vec_inner_type(ty).ok_or_else(|| {
1511        syn::Error::new(
1512            ident.span(),
1513            format!("field `{ident}`: #[edifact(repeat)] requires Vec<T>"),
1514        )
1515    })?;
1516    // `&str` items would borrow from the segment with a lifetime the derive does
1517    // not thread; `String` and parsed scalars cover the real cases.
1518    let convert = if is_string_type(inner) {
1519        quote! { ::core::result::Result::Ok(::std::string::ToString::to_string(__value)) }
1520    } else {
1521        let message = format!(
1522            "field `{ident}`: repeating value is not a valid {}",
1523            quote!(#inner)
1524        );
1525        quote! {
1526            __value.parse::<#inner>().map_err(|_| ::edifact_rs::EdifactError::InvalidFieldValue {
1527                tag: __seg.tag().to_string(),
1528                element_index: #element,
1529                value: ::std::format!("{}: {}", #message, __value),
1530            })
1531        }
1532    };
1533    Ok(quote! {
1534        let #ident = __seg.repeated_component(#element, #component)
1535            .map(|__value| #convert)
1536            .collect::<::core::result::Result<::std::vec::Vec<#inner>, ::edifact_rs::EdifactError>>()?;
1537    })
1538}
1539
1540/// What occupies one `(element, component)` cell of a segment's layout.
1541enum Cell {
1542    /// The struct-level `#[edifact(qualifier = "…")]`, which owns (0, 0).
1543    Qualifier,
1544    /// An index into `field_data`.
1545    Field(usize),
1546}
1547
1548/// `Vec::len()` expressions for every repeating field of one element.
1549fn repeat_lengths(
1550    field_data: &[(&syn::Ident, &Type, FieldAttrs)],
1551    components: &std::collections::BTreeMap<u32, Cell>,
1552) -> Vec<TokenStream2> {
1553    components
1554        .values()
1555        .filter_map(|cell| match cell {
1556            Cell::Field(index) if field_data[*index].2.repeat => {
1557                let ident = field_data[*index].0;
1558                Some(quote! { self.#ident.len() })
1559            }
1560            _ => None,
1561        })
1562        .collect()
1563}
1564
1565/// [`emit_repeating_element`] for an element whose component 0 is the
1566/// struct-level qualifier literal rather than a field.
1567fn emit_repeating_element_with_qualifier(
1568    qualifier: &syn::LitStr,
1569    rest: &[Option<(&syn::Ident, &Type, bool)>],
1570    lengths: &[TokenStream2],
1571) -> TokenStream2 {
1572    let values: Vec<TokenStream2> = rest
1573        .iter()
1574        .map(|cell| match cell {
1575            Some((ident, ty, repeat)) => occurrence_value(ident, ty, *repeat),
1576            None => quote! { ::std::borrow::Cow::Borrowed("") },
1577        })
1578        .collect();
1579    quote! {
1580        {
1581            let __n = ::core::cmp::max(1usize, [#(#lengths),*].into_iter().max().unwrap_or(0));
1582            for __k in 0..__n {
1583                let __event = if __k == 0 {
1584                    ::edifact_rs::EdifactEvent::element(#qualifier)
1585                } else {
1586                    ::edifact_rs::EdifactEvent::repeat(#qualifier)
1587                };
1588                emitter.emit(__event)?;
1589                #(
1590                    let __v = #values;
1591                    emitter.emit(::edifact_rs::EdifactEvent::component(__v.as_ref()))?;
1592                )*
1593            }
1594        }
1595    }
1596}
1597
1598/// One component's value at occurrence `__k`, as a `Cow<str>` expression.
1599///
1600/// A repeating field takes its `__k`-th item; a non-repeating one is constant
1601/// across occurrences, which is exactly right for `RFF+ON:1*ON:2` — the
1602/// qualifier `ON` belongs to every occurrence, and the standard requires it to
1603/// be transferred in each.
1604fn occurrence_value(ident: &syn::Ident, ty: &Type, repeat: bool) -> TokenStream2 {
1605    let empty = quote! { ::std::borrow::Cow::Borrowed("") };
1606    if repeat {
1607        let inner_is_str = vec_inner_type(ty).is_some_and(is_str_like);
1608        let map = if inner_is_str {
1609            quote! { |__v| ::std::borrow::Cow::Borrowed(__v.as_ref()) }
1610        } else {
1611            quote! { |__v| ::std::borrow::Cow::Owned(::std::string::ToString::to_string(__v)) }
1612        };
1613        return quote! { self.#ident.get(__k).map(#map).unwrap_or(#empty) };
1614    }
1615    if is_option_type(ty) {
1616        return if option_inner_type(ty).is_some_and(is_str_like) {
1617            quote! {
1618                self.#ident
1619                    .as_deref()
1620                    .map(::std::borrow::Cow::Borrowed)
1621                    .unwrap_or(#empty)
1622            }
1623        } else {
1624            quote! {
1625                self.#ident
1626                    .as_ref()
1627                    .map(|__v| ::std::borrow::Cow::Owned(::std::string::ToString::to_string(__v)))
1628                    .unwrap_or(#empty)
1629            }
1630        };
1631    }
1632    if is_string_type(ty) {
1633        quote! { ::std::borrow::Cow::Borrowed(self.#ident.as_str()) }
1634    } else if is_str_ref_type(ty) {
1635        quote! { ::std::borrow::Cow::Borrowed(self.#ident) }
1636    } else {
1637        quote! { ::std::borrow::Cow::Owned(::std::string::ToString::to_string(&self.#ident)) }
1638    }
1639}
1640
1641/// Emit a data element that repeats (ISO 9735-1 §8.6).
1642///
1643/// The repetition separator divides whole **occurrences**, not components, so
1644/// every component of the element is re-emitted for each one — `RFF+ON:1*ON:2`
1645/// carries its `ON` qualifier twice. `components` is the element's full
1646/// component list in order; `None` is a gap that stays empty.
1647///
1648/// The occurrence count is the longest repeating field in the element, and at
1649/// least one: an element with no occurrences is still transferred, as absent,
1650/// which is what keeps the elements after it in position (§8.7.1).
1651fn emit_repeating_element(
1652    components: &[Option<(&syn::Ident, &Type, bool)>],
1653    lengths: &[TokenStream2],
1654) -> TokenStream2 {
1655    let values: Vec<TokenStream2> = components
1656        .iter()
1657        .map(|cell| match cell {
1658            Some((ident, ty, repeat)) => occurrence_value(ident, ty, *repeat),
1659            None => quote! { ::std::borrow::Cow::Borrowed("") },
1660        })
1661        .collect();
1662    let (first, rest) = values.split_first().expect("an element has ≥1 component");
1663    quote! {
1664        {
1665            let __n = ::core::cmp::max(1usize, [#(#lengths),*].into_iter().max().unwrap_or(0));
1666            for __k in 0..__n {
1667                let __v = #first;
1668                let __event = if __k == 0 {
1669                    ::edifact_rs::EdifactEvent::element(__v.as_ref())
1670                } else {
1671                    ::edifact_rs::EdifactEvent::repeat(__v.as_ref())
1672                };
1673                emitter.emit(__event)?;
1674                #(
1675                    let __v = #rest;
1676                    emitter.emit(::edifact_rs::EdifactEvent::component(__v.as_ref()))?;
1677                )*
1678            }
1679        }
1680    }
1681}
1682
1683/// Generate the token stream that emits field `ident` (of type `ty`) as one element.
1684///
1685/// For `String` and `&str` fields the value is emitted zero-copy via `.as_str()`
1686/// (or directly).  All other types fall back to `ToString::to_string`.
1687fn emit_element(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
1688    if is_option_type(ty) {
1689        let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
1690        if inner_is_str {
1691            quote! {
1692                match &self.#ident {
1693                    ::core::option::Option::Some(__v) => {
1694                        emitter.emit(::edifact_rs::EdifactEvent::element(__v.as_str()))?;
1695                    }
1696                    ::core::option::Option::None => {
1697                        emitter.emit(::edifact_rs::EdifactEvent::element(""))?;
1698                    }
1699                }
1700            }
1701        } else {
1702            quote! {
1703                match &self.#ident {
1704                    ::core::option::Option::Some(__v) => {
1705                        let __s = ::std::string::ToString::to_string(__v);
1706                        emitter.emit(::edifact_rs::EdifactEvent::element(&__s))?;
1707                    }
1708                    ::core::option::Option::None => {
1709                        emitter.emit(::edifact_rs::EdifactEvent::element(""))?;
1710                    }
1711                }
1712            }
1713        }
1714    } else if is_string_type(ty) {
1715        quote! {
1716            emitter.emit(::edifact_rs::EdifactEvent::element(self.#ident.as_str()))?;
1717        }
1718    } else if is_str_ref_type(ty) {
1719        quote! {
1720            emitter.emit(::edifact_rs::EdifactEvent::element(self.#ident))?;
1721        }
1722    } else {
1723        quote! {
1724            {
1725                let __s = ::std::string::ToString::to_string(&self.#ident);
1726                emitter.emit(::edifact_rs::EdifactEvent::element(&__s))?;
1727            }
1728        }
1729    }
1730}
1731
1732/// Generate the token stream that emits field `ident` as a composite component (`ComponentElement`).
1733///
1734/// For `String` and `&str` fields the value is emitted zero-copy.
1735fn emit_component_element(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
1736    if is_option_type(ty) {
1737        let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
1738        if inner_is_str {
1739            quote! {
1740                match &self.#ident {
1741                    ::core::option::Option::Some(__v) => {
1742                        emitter.emit(::edifact_rs::EdifactEvent::component(__v.as_str()))?;
1743                    }
1744                    ::core::option::Option::None => {
1745                        emitter.emit(::edifact_rs::EdifactEvent::component(""))?;
1746                    }
1747                }
1748            }
1749        } else {
1750            quote! {
1751                match &self.#ident {
1752                    ::core::option::Option::Some(__v) => {
1753                        let __s = ::std::string::ToString::to_string(__v);
1754                        emitter.emit(::edifact_rs::EdifactEvent::component(&__s))?;
1755                    }
1756                    ::core::option::Option::None => {
1757                        emitter.emit(::edifact_rs::EdifactEvent::component(""))?;
1758                    }
1759                }
1760            }
1761        }
1762    } else if is_string_type(ty) {
1763        quote! {
1764            emitter.emit(::edifact_rs::EdifactEvent::component(self.#ident.as_str()))?;
1765        }
1766    } else if is_str_ref_type(ty) {
1767        quote! {
1768            emitter.emit(::edifact_rs::EdifactEvent::component(self.#ident))?;
1769        }
1770    } else {
1771        quote! {
1772            {
1773                let __s = ::std::string::ToString::to_string(&self.#ident);
1774                emitter.emit(::edifact_rs::EdifactEvent::component(&__s))?;
1775            }
1776        }
1777    }
1778}
1779
1780/// Generate the token stream that emits a full composite field via `EdifactCompositeSerialize`.
1781fn emit_composite_field(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
1782    if is_option_type(ty) {
1783        quote! {
1784            match &self.#ident {
1785                ::core::option::Option::Some(__v) => {
1786                    ::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(__v, emitter)?;
1787                }
1788                ::core::option::Option::None => {
1789                    emitter.emit(::edifact_rs::EdifactEvent::element(""))?;
1790                }
1791            }
1792        }
1793    } else {
1794        quote! {
1795            ::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(&self.#ident, emitter)?;
1796        }
1797    }
1798}
1799
1800// ── EdifactDeserialize ─────────────────────────────────────────────────────────
1801
1802fn impl_deserialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
1803    let name = &input.ident;
1804    let struct_attrs = parse_struct_attrs(input)?;
1805    let fields = get_named_fields(input)?;
1806    let is_segment_struct = struct_attrs.segment.is_some();
1807
1808    let field_data: Vec<(&syn::Ident, &Type, FieldAttrs)> = fields
1809        .named
1810        .iter()
1811        .map(|f| {
1812            let attrs = parse_field_attrs(f)?;
1813            let ident = f
1814                .ident
1815                .as_ref()
1816                .ok_or_else(|| syn::Error::new_spanned(f, "only named fields are supported"))?;
1817            validate_field_attrs(ident, &f.ty, &attrs, is_segment_struct)?;
1818            Ok((ident, &f.ty, attrs))
1819        })
1820        .collect::<syn::Result<_>>()?;
1821    check_duplicate_slots(&field_data, is_segment_struct)?;
1822    let (slot_prelude, slots) = resolve_slots(&struct_attrs, &field_data)?;
1823
1824    let field_names: Vec<&syn::Ident> = field_data.iter().map(|(id, _, _)| *id).collect();
1825
1826    let (body, segment_tag_impl) = if let Some(seg_tag) = &struct_attrs.segment {
1827        // ── Segment struct ────────────────────────────────────────────────────
1828        let qualifier_guard = if let Some(qual) = &struct_attrs.qualifier {
1829            quote! {
1830                if __seg.element_str(0).unwrap_or("") != #qual {
1831                    return ::core::result::Result::Err(
1832                        ::edifact_rs::EdifactError::MissingRequiredElement {
1833                            tag: #seg_tag.to_owned(),
1834                            element_index: 0,
1835                        }
1836                    );
1837                }
1838            }
1839        } else if let Some(idx) = struct_attrs.qualifier_from {
1840            quote! {
1841                // Fully-qualified patterns: a user type named `Some`/`None` in
1842                // scope (e.g. `pub use MyOpt::*`) would otherwise shadow the
1843                // std variants and break the generated code.
1844                match __seg.element_str(#idx as usize) {
1845                    ::core::option::Option::None => return ::core::result::Result::Err(
1846                        ::edifact_rs::EdifactError::MissingRequiredElement {
1847                            tag: #seg_tag.to_owned(),
1848                            element_index: #idx as usize,
1849                        }
1850                    ),
1851                    ::core::option::Option::Some("") => return ::core::result::Result::Err(
1852                        ::edifact_rs::EdifactError::InvalidFieldValue {
1853                            tag: #seg_tag.to_owned(),
1854                            element_index: #idx as usize,
1855                            value: ::std::string::String::new(),
1856                        }
1857                    ),
1858                    ::core::option::Option::Some(__qual_val) => { let _ = __qual_val; }
1859                }
1860            }
1861        } else {
1862            quote! {}
1863        };
1864
1865        let find_seg = if let Some(qual) = &struct_attrs.qualifier {
1866            quote! {
1867                ::edifact_rs::find_qualified_segment(segments, #seg_tag, #qual)
1868            }
1869        } else {
1870            quote! {
1871                ::edifact_rs::find_segment(segments, #seg_tag)
1872            }
1873        };
1874
1875        let field_inits: Vec<TokenStream2> = field_data
1876            .iter()
1877            .zip(slots.iter())
1878            .map(|((ident, ty, attrs), slot)| -> syn::Result<TokenStream2> {
1879                let idx = &slot.element;
1880                if attrs.repeat {
1881                    return repeating_field_init(ident, ty, &slot.element, &slot.component);
1882                }
1883                if attrs.composite {
1884                    if is_option_type(ty) {
1885                        let inner_ty = option_inner_type(ty)
1886                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1887                        return Ok(quote! {
1888                            let #ident = match ::edifact_rs::composite_element(__seg, #idx) {
1889                                ::core::option::Option::Some(__composite) => {
1890                                    ::core::option::Option::Some(
1891                                        <#inner_ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(__composite)?
1892                                    )
1893                                }
1894                                ::core::option::Option::None => ::core::option::Option::None,
1895                            };
1896                        });
1897                    }
1898                    return Ok(quote! {
1899                        let #ident = <#ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
1900                            ::edifact_rs::composite_element(__seg, #idx).ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1901                                tag: #seg_tag.to_owned(),
1902                                element_index: #idx,
1903                            })?
1904                        )?;
1905                    });
1906                }
1907                let comp = &slot.component;
1908                let value_expr = if slot.has_component {
1909                    quote! {
1910                        __seg.get_element(#idx).and_then(|__e| __e.get_component(#comp))
1911                    }
1912                } else {
1913                    quote! { __seg.element_str(#idx) }
1914                };
1915                // Report the variant that matches what the field actually
1916                // addresses.  For a code slot `names_component` is a `const`
1917                // lookup, so this branch folds away.
1918                let names_component = &slot.names_component;
1919                let missing_required_err = quote! {
1920                    if #names_component {
1921                        ::edifact_rs::EdifactError::MissingRequiredComponent {
1922                            tag: #seg_tag.to_owned(),
1923                            element_index: #idx,
1924                            component_index: #comp,
1925                        }
1926                    } else {
1927                        ::edifact_rs::EdifactError::MissingRequiredElement {
1928                            tag: #seg_tag.to_owned(),
1929                            element_index: #idx,
1930                        }
1931                    }
1932                };
1933                Ok(if is_option_type(ty) {
1934                    let inner_ty = option_inner_type(ty);
1935                    let inner_is_str = inner_ty.is_some_and(is_str_like);
1936                    if attrs.required {
1937                        // #[edifact(required)] on Option<T>: treat absence as an error.
1938                        // Emits MissingRequiredComponent when combined with component = N,
1939                        // MissingRequiredElement otherwise.
1940                        if inner_is_str {
1941                            quote! {
1942                                let #ident = ::core::option::Option::Some(
1943                                    #value_expr
1944                                        .filter(|__s| !__s.is_empty())
1945                                        .ok_or_else(|| #missing_required_err)?
1946                                        .to_owned()
1947                                );
1948                            }
1949                        } else {
1950                            let inner_ty = inner_ty
1951                                .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
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                                        .parse::<#inner_ty>()
1958                                        .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?
1959                                );
1960                            }
1961                        }
1962                    } else if inner_is_str {
1963                        quote! {
1964                            let #ident = #value_expr
1965                                .filter(|__s| !__s.is_empty())
1966                                .map(::std::string::String::from);
1967                        }
1968                    } else {
1969                        let inner_ty = inner_ty
1970                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1971                        quote! {
1972                            let #ident = #value_expr
1973                                .filter(|__s| !__s.is_empty())
1974                                .map(|__s| __s.parse::<#inner_ty>()
1975                                    .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })
1976                                )
1977                                .transpose()?;
1978                        }
1979                    }
1980                } else if is_str_like(ty) {
1981                    quote! {
1982                        let #ident = #value_expr
1983                            .filter(|__s| !__s.is_empty())
1984                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1985                                tag: #seg_tag.to_owned(),
1986                                element_index: #idx,
1987                            })?
1988                            .to_owned();
1989                    }
1990                } else {
1991                    quote! {
1992                        let #ident = #value_expr
1993                            .filter(|__s| !__s.is_empty())
1994                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1995                                tag: #seg_tag.to_owned(),
1996                                element_index: #idx,
1997                            })?
1998                            .parse::<#ty>()
1999                            .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?;
2000                    }
2001                })
2002            })
2003            .collect::<syn::Result<_>>()?;
2004
2005        let body = quote! {
2006            #slot_prelude
2007            let __seg = #find_seg
2008                .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
2009                    tag: #seg_tag.to_owned(),
2010                    expected_position: "message body".to_owned(),
2011                })?;
2012            #qualifier_guard
2013            #(#field_inits)*
2014            ::core::result::Result::Ok(Self { #(#field_names),* })
2015        };
2016
2017        // Also generate EdifactSegmentTag impl.
2018        // Declare the qualifier through `QUALIFIER_PATTERN` rather than by
2019        // hand-rolling `matches_segment`, so that every consumer of the trait —
2020        // the `Vec<T>` blanket impl, `find_segments_typed`, `contiguous_groups` —
2021        // sees the same rule.
2022        let qualifier_match = if let Some(qual) = &struct_attrs.qualifier {
2023            quote! {
2024                const QUALIFIER_PATTERN: ::core::option::Option<&'static str> =
2025                    ::core::option::Option::Some(#qual);
2026            }
2027        } else if let Some(idx) = struct_attrs.qualifier_from {
2028            // "Any non-empty value at element `idx`" cannot be expressed as a
2029            // `QUALIFIER_PATTERN`, which addresses element 0 only, so the matcher
2030            // is overridden explicitly.
2031            quote! {
2032                fn matches_segment(seg: &::edifact_rs::Segment<'_>) -> bool {
2033                    seg.tag == Self::SEGMENT_TAG
2034                        && !seg.element_str(#idx as usize).unwrap_or("").is_empty()
2035                }
2036            }
2037        } else {
2038            quote! {}
2039        };
2040
2041        let seg_tag_impl = quote! {
2042            impl ::edifact_rs::EdifactSegmentTag for #name {
2043                const SEGMENT_TAG: &'static str = #seg_tag;
2044                #qualifier_match
2045            }
2046        };
2047
2048        (body, seg_tag_impl)
2049    } else {
2050        // ── Message struct: delegate to each field ────────────────────────────
2051        let field_inits: Vec<TokenStream2> = field_data
2052            .iter()
2053            .map(|(ident, ty, attrs)| -> syn::Result<TokenStream2> {
2054                Ok(if let Some(qual) = &attrs.qualifier {
2055                    if attrs.group || is_vec_type(ty) {
2056                        let inner_ty = vec_inner_type(ty)
2057                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
2058                        quote! {
2059                            let #ident = segments
2060                                .iter()
2061                                .filter(|__seg| {
2062                                    __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG
2063                                        && __seg.element_str(0).unwrap_or("") == #qual
2064                                })
2065                                .map(|__seg| {
2066                                    ::edifact_rs::EdifactDeserialize::edifact_deserialize(
2067                                        ::core::slice::from_ref(__seg),
2068                                    )
2069                                })
2070                                .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, ::edifact_rs::EdifactError>>()?;
2071                        }
2072                    } else if is_option_type(ty) {
2073                        let inner_ty = option_inner_type(ty)
2074                            .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
2075                        quote! {
2076                            let #ident = match ::edifact_rs::find_qualified_segment(
2077                                segments,
2078                                <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
2079                                #qual,
2080                            ) {
2081                                ::core::option::Option::Some(__seg) => {
2082                                    ::core::option::Option::Some(
2083                                        ::edifact_rs::EdifactDeserialize::edifact_deserialize(
2084                                            ::core::slice::from_ref(__seg),
2085                                        )?
2086                                    )
2087                                }
2088                                ::core::option::Option::None => ::core::option::Option::None,
2089                            };
2090                        }
2091                    } else {
2092                        quote! {
2093                            let __seg = ::edifact_rs::find_qualified_segment(
2094                                segments,
2095                                <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
2096                                #qual,
2097                            )
2098                            .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
2099                                tag: <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG.to_owned(),
2100                                expected_position: "message body".to_owned(),
2101                            })?;
2102                            let #ident = ::edifact_rs::EdifactDeserialize::edifact_deserialize(
2103                                ::core::slice::from_ref(__seg),
2104                            )?;
2105                        }
2106                    }
2107                } else if attrs.group || is_vec_type(ty) {
2108                    let inner_ty = vec_inner_type(ty)
2109                        .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
2110                    quote! {
2111                        let #ident = ::edifact_rs::find_segments_typed::<#inner_ty>(segments)
2112                            .map(|__seg| {
2113                                <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize(
2114                                    ::core::slice::from_ref(__seg),
2115                                )
2116                            })
2117                            .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, _>>()?;
2118                    }
2119                } else if is_option_type(ty) {
2120                    let inner_ty = option_inner_type(ty)
2121                        .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
2122                    quote! {
2123                        let #ident = if segments
2124                            .iter()
2125                            .any(|__seg| __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG)
2126                        {
2127                            ::core::option::Option::Some(
2128                                <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize(segments)?
2129                            )
2130                        } else {
2131                            ::core::option::Option::None
2132                        };
2133                    }
2134                } else {
2135                    quote! {
2136                        let #ident = ::edifact_rs::EdifactDeserialize::edifact_deserialize(segments)?;
2137                    }
2138                })
2139            })
2140            .collect::<syn::Result<_>>()?;
2141
2142        let body = quote! {
2143            #(#field_inits)*
2144            ::core::result::Result::Ok(Self { #(#field_names),* })
2145        };
2146
2147        (body, quote! {})
2148    };
2149
2150    Ok(quote! {
2151        impl ::edifact_rs::EdifactDeserialize for #name {
2152            fn edifact_deserialize(
2153                segments: &[::edifact_rs::Segment<'_>],
2154            ) -> ::core::result::Result<Self, ::edifact_rs::EdifactError> {
2155                #body
2156            }
2157        }
2158        #segment_tag_impl
2159    })
2160}
2161
2162#[cfg(test)]
2163mod tests {
2164    /// Compile-fail / compile-pass suite for the derive macros.
2165    ///
2166    /// The blessed `.stderr` files hold only this crate's own diagnostics, so
2167    /// they are stable across toolchains and CI runs the suite on both MSRV and
2168    /// stable.  Keep it that way: an expectation that captures a *rustc*
2169    /// warning or note will drift on the next release and drown real
2170    /// regressions in noise.  If a UI case triggers an incidental lint, silence
2171    /// it at the source (see `tests/ui/support.rs`) rather than blessing it.
2172    ///
2173    /// The suite is off by default because it is slow; set `EDIFACT_UI_TESTS=1`
2174    /// to run it, or use `just ui` / `just ui-msrv`.
2175    ///
2176    /// Re-bless after intentional message changes with `just ui-bless`.
2177    #[test]
2178    fn trybuild_ui() {
2179        if std::env::var_os("EDIFACT_UI_TESTS").is_none() {
2180            eprintln!(
2181                "skipping derive UI suite: set EDIFACT_UI_TESTS=1 to run it \
2182                 (expectations are pinned to the MSRV toolchain)"
2183            );
2184            return;
2185        }
2186        let t = trybuild::TestCases::new();
2187        t.pass("tests/ui/pass_*.rs");
2188        t.compile_fail("tests/ui/fail_*.rs");
2189    }
2190}