Skip to main content

edifact_rs_derive/
lib.rs

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