Skip to main content

edifact_rs_derive/
lib.rs

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