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