1#![deny(unsafe_code)]
2use proc_macro::TokenStream;
101use proc_macro2::TokenStream as TokenStream2;
102use quote::quote;
103use syn::{Data, DeriveInput, Field, Fields, Type, parse_macro_input, spanned::Spanned};
104
105#[proc_macro_derive(EdifactSerialize, attributes(edifact))]
108pub fn derive_edifact_serialize(input: TokenStream) -> TokenStream {
117 let input = parse_macro_input!(input as DeriveInput);
118 impl_serialize(&input)
119 .unwrap_or_else(|e| e.to_compile_error())
120 .into()
121}
122
123#[proc_macro_derive(EdifactDeserialize, attributes(edifact))]
124pub fn derive_edifact_deserialize(input: TokenStream) -> TokenStream {
133 let input = parse_macro_input!(input as DeriveInput);
134 impl_deserialize(&input)
135 .unwrap_or_else(|e| e.to_compile_error())
136 .into()
137}
138
139#[derive(Default)]
142struct StructAttrs {
143 segment: Option<String>,
145 qualifier: Option<String>,
147 qualifier_span: Option<proc_macro2::Span>,
148 qualifier_from: Option<u32>,
150 qualifier_from_span: Option<proc_macro2::Span>,
151}
152
153#[derive(Default)]
154struct FieldAttrs {
155 element: Option<u32>,
157 element_span: Option<proc_macro2::Span>,
158 component: Option<u32>,
160 component_span: Option<proc_macro2::Span>,
161 composite: bool,
163 composite_span: Option<proc_macro2::Span>,
164 group: bool,
166 group_span: Option<proc_macro2::Span>,
167 qualifier: Option<String>,
169 qualifier_span: Option<proc_macro2::Span>,
170 required: bool,
178 required_span: Option<proc_macro2::Span>,
179}
180
181const MAX_POSITION_INDEX: u32 = 256;
191
192fn check_index_bound(lit: &syn::LitInt, value: u32, key: &str) -> syn::Result<()> {
193 if value > MAX_POSITION_INDEX {
194 return Err(syn::Error::new(
195 lit.span(),
196 format!(
197 "`{key}` index {value} exceeds the maximum of {MAX_POSITION_INDEX}; \
198 UN/EDIFACT allows at most 99 elements per segment and 99 components per composite"
199 ),
200 ));
201 }
202 Ok(())
203}
204
205fn parse_struct_attrs(input: &DeriveInput) -> syn::Result<StructAttrs> {
206 let mut out = StructAttrs::default();
207 for attr in &input.attrs {
208 if !attr.path().is_ident("edifact") {
209 continue;
210 }
211 attr.parse_nested_meta(|meta| {
212 if meta.path.is_ident("segment") {
213 if out.segment.is_some() {
214 return Err(meta.error("duplicate `segment`"));
215 }
216 let lit = meta.value()?.parse::<syn::LitStr>()?;
217 let tag = lit.value();
218 if tag.len() != 3 || !tag.bytes().all(|b| b.is_ascii_uppercase()) {
219 return Err(syn::Error::new(
220 lit.span(),
221 format!(
222 "segment tag must be exactly 3 ASCII uppercase letters; got {tag:?}"
223 ),
224 ));
225 }
226 out.segment = Some(tag);
227 } else if meta.path.is_ident("qualifier") {
228 if out.qualifier.is_some() {
229 return Err(meta.error("duplicate `qualifier`"));
230 }
231 out.qualifier = Some(meta.value()?.parse::<syn::LitStr>()?.value());
232 out.qualifier_span = Some(meta.path.span());
233 } else if meta.path.is_ident("qualifier_from") {
234 if out.qualifier_from.is_some() {
235 return Err(meta.error("duplicate `qualifier_from`"));
236 }
237 let lit = meta.value()?.parse::<syn::LitInt>()?;
238 let idx: u32 = lit.base10_parse()?;
239 check_index_bound(&lit, idx, "qualifier_from")?;
240 out.qualifier_from = Some(idx);
241 out.qualifier_from_span = Some(meta.path.span());
242 } else {
243 return Err(meta.error("unknown struct-level `edifact` key; expected `segment`, `qualifier`, or `qualifier_from`"));
244 }
245 Ok(())
246 })?;
247 }
248 if (out.qualifier.is_some() || out.qualifier_from.is_some()) && out.segment.is_none() {
249 return Err(syn::Error::new(
250 out.qualifier_span
251 .or(out.qualifier_from_span)
252 .unwrap_or_else(|| input.span()),
253 "#[edifact(qualifier = ...)] / #[edifact(qualifier_from = ...)] require #[edifact(segment = ...)]",
254 ));
255 }
256 if out.qualifier.is_some() && out.qualifier_from.is_some() {
257 return Err(syn::Error::new(
258 out.qualifier_from_span
259 .or(out.qualifier_span)
260 .unwrap_or_else(|| input.span()),
261 "use either #[edifact(qualifier = ...)] or #[edifact(qualifier_from = ...)], not both",
262 ));
263 }
264 Ok(out)
265}
266
267fn parse_field_attrs(field: &Field) -> syn::Result<FieldAttrs> {
268 let mut out = FieldAttrs::default();
269 for attr in &field.attrs {
270 if !attr.path().is_ident("edifact") {
271 continue;
272 }
273 attr.parse_nested_meta(|meta| {
274 if meta.path.is_ident("element") {
275 if out.element.is_some() {
276 return Err(meta.error("duplicate `element`"));
277 }
278 let lit = meta.value()?.parse::<syn::LitInt>()?;
279 let idx: u32 = lit.base10_parse()?;
280 check_index_bound(&lit, idx, "element")?;
281 out.element = Some(idx);
282 out.element_span = Some(meta.path.span());
283 } else if meta.path.is_ident("component") {
284 if out.component.is_some() {
285 return Err(meta.error("duplicate `component`"));
286 }
287 let lit = meta.value()?.parse::<syn::LitInt>()?;
288 let idx: u32 = lit.base10_parse()?;
289 check_index_bound(&lit, idx, "component")?;
290 out.component = Some(idx);
291 out.component_span = Some(meta.path.span());
292 } else if meta.path.is_ident("composite") {
293 out.composite = true;
294 out.composite_span = Some(meta.path.span());
295 } else if meta.path.is_ident("group") {
296 out.group = true;
297 out.group_span = Some(meta.path.span());
298 } else if meta.path.is_ident("qualifier") {
299 if out.qualifier.is_some() {
300 return Err(meta.error("duplicate `qualifier`"));
301 }
302 out.qualifier = Some(meta.value()?.parse::<syn::LitStr>()?.value());
303 out.qualifier_span = Some(meta.path.span());
304 } else if meta.path.is_ident("required") {
305 out.required = true;
306 out.required_span = Some(meta.path.span());
307 } else {
308 return Err(meta.error("unknown field-level `edifact` key; expected `element`, `component`, `composite`, `group`, `qualifier`, or `required`"));
309 }
310 Ok(())
311 })?;
312 }
313 Ok(out)
314}
315
316fn is_option_type(ty: &Type) -> bool {
319 matches!(ty, Type::Path(p) if p.path.segments.last().is_some_and(|s| s.ident == "Option"))
320}
321
322fn is_vec_type(ty: &Type) -> bool {
323 matches!(ty, Type::Path(p) if p.path.segments.last().is_some_and(|s| s.ident == "Vec"))
324}
325
326fn is_string_type(ty: &Type) -> bool {
344 let Type::Path(p) = ty else { return false };
345 if p.path.is_ident("String") {
347 return true;
348 }
349 let segs = &p.path.segments;
351 segs.len() == 3
352 && (segs[0].ident == "std" || segs[0].ident == "alloc")
353 && segs[1].ident == "string"
354 && segs[2].ident == "String"
355}
356
357fn is_str_ref_type(ty: &Type) -> bool {
359 let Type::Reference(r) = ty else { return false };
360 matches!(r.elem.as_ref(), Type::Path(p) if p.path.is_ident("str"))
361}
362
363fn is_str_like(ty: &Type) -> bool {
366 is_string_type(ty) || is_str_ref_type(ty)
367}
368
369fn option_inner_type(ty: &Type) -> Option<&Type> {
370 let Type::Path(path) = ty else { return None };
371 let seg = path.path.segments.last()?;
372 if seg.ident != "Option" {
373 return None;
374 }
375 let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
376 return None;
377 };
378 let syn::GenericArgument::Type(inner) = args.args.first()? else {
379 return None;
380 };
381 Some(inner)
382}
383
384fn vec_inner_type(ty: &Type) -> Option<&Type> {
385 let Type::Path(path) = ty else { return None };
386 let seg = path.path.segments.last()?;
387 if seg.ident != "Vec" {
388 return None;
389 }
390 let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
391 return None;
392 };
393 let syn::GenericArgument::Type(inner) = args.args.first()? else {
394 return None;
395 };
396 Some(inner)
397}
398
399fn check_duplicate_slots(
407 field_data: &[(&syn::Ident, &Type, FieldAttrs)],
408 is_segment_struct: bool,
409) -> syn::Result<()> {
410 if !is_segment_struct {
411 return Ok(());
412 }
413 let mut seen: Vec<((u32, u32), &syn::Ident)> = Vec::with_capacity(field_data.len());
414 for (i, (ident, _, attrs)) in field_data.iter().enumerate() {
415 if attrs.group {
417 continue;
418 }
419 let slot = (
420 attrs.element.unwrap_or(i as u32),
421 attrs.component.unwrap_or(0),
422 );
423 if let Some((_, first)) = seen.iter().find(|(s, _)| *s == slot) {
424 return Err(syn::Error::new(
425 ident.span(),
426 format!(
427 "field `{ident}` maps to element {} component {}, which is already \
428 claimed by field `{first}`; give each field a distinct \
429 `#[edifact(element = ..., component = ...)]` slot",
430 slot.0, slot.1
431 ),
432 ));
433 }
434 seen.push((slot, ident));
435 }
436 Ok(())
437}
438
439fn get_named_fields(input: &DeriveInput) -> syn::Result<&syn::FieldsNamed> {
440 if !input.generics.params.is_empty() {
441 return Err(syn::Error::new(
442 input.generics.params.span(),
443 "EdifactSerialize/EdifactDeserialize do not support generic structs",
444 ));
445 }
446 match &input.data {
447 Data::Struct(s) => match &s.fields {
448 Fields::Named(f) => Ok(f),
449 _ => Err(syn::Error::new(
450 input.span(),
451 "EdifactSerialize/EdifactDeserialize only support structs with named fields",
452 )),
453 },
454 _ => Err(syn::Error::new(
455 input.span(),
456 "EdifactSerialize/EdifactDeserialize only support structs",
457 )),
458 }
459}
460
461fn validate_field_attrs(
462 ident: &syn::Ident,
463 ty: &Type,
464 attrs: &FieldAttrs,
465 is_segment_struct: bool,
466) -> syn::Result<()> {
467 if attrs.group && !is_vec_type(ty) {
468 return Err(syn::Error::new(
469 attrs.group_span.unwrap_or_else(|| ident.span()),
470 format!("field `{ident}`: #[edifact(group)] requires Vec<T>"),
471 ));
472 }
473 if attrs.group && (attrs.element.is_some() || attrs.component.is_some()) {
474 return Err(syn::Error::new(
475 attrs.group_span.unwrap_or_else(|| ident.span()),
476 format!(
477 "field `{ident}`: #[edifact(group)] cannot be combined with element/component positioning"
478 ),
479 ));
480 }
481 if attrs.composite && attrs.component.is_some() {
482 return Err(syn::Error::new(
483 attrs.component_span.unwrap_or_else(|| ident.span()),
484 format!(
485 "field `{ident}`: #[edifact(component = ...)] cannot be combined with #[edifact(composite)]"
486 ),
487 ));
488 }
489 if attrs.composite && attrs.group {
490 return Err(syn::Error::new(
491 attrs.composite_span.unwrap_or_else(|| ident.span()),
492 format!(
493 "field `{ident}`: #[edifact(composite)] cannot be combined with #[edifact(group)]"
494 ),
495 ));
496 }
497 if is_segment_struct && attrs.group {
498 return Err(syn::Error::new(
499 attrs.group_span.unwrap_or_else(|| ident.span()),
500 format!("field `{ident}`: #[edifact(group)] is only valid on message structs"),
501 ));
502 }
503 if !is_segment_struct && (attrs.element.is_some() || attrs.component.is_some()) {
504 return Err(syn::Error::new(
505 attrs
506 .element_span
507 .or(attrs.component_span)
508 .unwrap_or_else(|| ident.span()),
509 format!(
510 "field `{ident}`: element/component positioning is only valid on segment structs"
511 ),
512 ));
513 }
514 if !is_segment_struct && attrs.composite {
515 return Err(syn::Error::new(
516 attrs.composite_span.unwrap_or_else(|| ident.span()),
517 format!("field `{ident}`: #[edifact(composite)] is only valid on segment structs"),
518 ));
519 }
520 if is_segment_struct && attrs.qualifier.is_some() {
521 return Err(syn::Error::new(
522 attrs.qualifier_span.unwrap_or_else(|| ident.span()),
523 format!(
524 "field `{ident}`: #[edifact(qualifier = ...)] is only valid on message struct fields"
525 ),
526 ));
527 }
528 if attrs.qualifier.is_some() && attrs.group && !is_vec_type(ty) {
529 return Err(syn::Error::new(
530 attrs.qualifier_span.unwrap_or_else(|| ident.span()),
531 format!("field `{ident}`: qualifier-constrained groups must be Vec<T>"),
532 ));
533 }
534 if attrs.required && !is_option_type(ty) {
535 return Err(syn::Error::new(
536 attrs.required_span.unwrap_or_else(|| ident.span()),
537 format!(
538 "field `{ident}`: #[edifact(required)] only applies to Option<T> fields; \
539 non-Option fields are always required"
540 ),
541 ));
542 }
543 if attrs.required && attrs.composite {
544 return Err(syn::Error::new(
545 attrs.required_span.unwrap_or_else(|| ident.span()),
546 format!(
547 "field `{ident}`: #[edifact(required)] cannot be combined with \
548 #[edifact(composite)]; use a non-optional field type to require the \
549 composite element"
550 ),
551 ));
552 }
553 if attrs.required && !is_segment_struct {
554 return Err(syn::Error::new(
555 attrs.required_span.unwrap_or_else(|| ident.span()),
556 format!(
557 "field `{ident}`: #[edifact(required)] is only valid on segment struct \
558 element fields; to require a segment in a message struct, use a \
559 non-optional field type"
560 ),
561 ));
562 }
563 Ok(())
564}
565
566fn impl_serialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
569 let name = &input.ident;
570 let struct_attrs = parse_struct_attrs(input)?;
571 let fields = get_named_fields(input)?;
572 let is_segment_struct = struct_attrs.segment.is_some();
573
574 let field_data: Vec<(&syn::Ident, &Type, FieldAttrs)> = fields
576 .named
577 .iter()
578 .map(|f| {
579 let attrs = parse_field_attrs(f)?;
580 let ident = f
581 .ident
582 .as_ref()
583 .ok_or_else(|| syn::Error::new_spanned(f, "only named fields are supported"))?;
584 validate_field_attrs(ident, &f.ty, &attrs, is_segment_struct)?;
585 Ok((ident, &f.ty, attrs))
586 })
587 .collect::<syn::Result<_>>()?;
588 check_duplicate_slots(&field_data, is_segment_struct)?;
589
590 let body = if let Some(seg_tag) = &struct_attrs.segment {
591 let (qualifier_emit, start_slot, elem0_comp_stmts) = if let Some(qual) =
596 &struct_attrs.qualifier
597 {
598 for (i, (ident, _, attrs)) in field_data.iter().enumerate() {
600 let elem = attrs.element.unwrap_or(i as u32);
601 let comp = attrs.component.unwrap_or(0);
602 if elem == 0 && comp == 0 {
603 return Err(syn::Error::new(
604 attrs
605 .element_span
606 .or(attrs.component_span)
607 .unwrap_or_else(|| ident.span()),
608 format!(
609 "field `{}`: cannot use #[edifact(qualifier = ...)] with a field at element = 0 without component >= 1; the qualifier occupies component 0",
610 ident
611 ),
612 ));
613 }
614 }
615 let mut comp_fields: Vec<(u32, usize)> = field_data
617 .iter()
618 .enumerate()
619 .filter_map(|(i, (_, _, attrs))| {
620 let elem = attrs.element.unwrap_or(i as u32);
621 let comp = attrs.component.unwrap_or(0);
622 if elem == 0 && comp > 0 {
623 Some((comp, i))
624 } else {
625 None
626 }
627 })
628 .collect();
629 comp_fields.sort_by_key(|(c, _)| *c);
630 let comp_stmts: Vec<TokenStream2> = comp_fields
631 .iter()
632 .map(|(_, fi)| {
633 let (ident, ty, _) = &field_data[*fi];
634 emit_component_element(ident, ty)
635 })
636 .collect();
637 let q = quote! {
638 emitter.emit(::edifact_rs::EdifactEvent::Element { value: #qual })?;
639 };
640 (q, 1u32, quote! { #(#comp_stmts)* })
641 } else {
642 (quote! {}, 0u32, quote! {})
643 };
644
645 let regular_field_data: Vec<(u32, usize)> = field_data
647 .iter()
648 .enumerate()
649 .filter_map(|(i, (_, _, attrs))| {
650 let elem = attrs.element.unwrap_or(i as u32);
651 if elem < start_slot {
652 None
653 } else {
654 Some((elem, i))
655 }
656 })
657 .collect();
658 let reg_max_idx = regular_field_data
659 .iter()
660 .map(|(e, _)| *e)
661 .max()
662 .unwrap_or(start_slot.saturating_sub(1));
663 let reg_field_map: std::collections::HashMap<u32, usize> =
664 regular_field_data.iter().copied().collect();
665
666 let mut elem_stmts: Vec<TokenStream2> = Vec::new();
667 for slot in start_slot..=reg_max_idx {
668 if let Some(&fi) = reg_field_map.get(&slot) {
669 let (ident, ty, attrs) = &field_data[fi];
670 if attrs.composite {
671 elem_stmts.push(emit_composite_field(ident, ty));
672 } else {
673 elem_stmts.push(emit_element(ident, ty));
674 }
675 } else {
676 elem_stmts.push(quote! {
678 emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
679 });
680 }
681 }
682
683 quote! {
684 emitter.emit(::edifact_rs::EdifactEvent::StartSegment { tag: #seg_tag })?;
685 #qualifier_emit
686 #elem0_comp_stmts
687 #(#elem_stmts)*
688 emitter.emit(::edifact_rs::EdifactEvent::EndSegment)?;
689 }
690 } else {
691 let stmts: Vec<TokenStream2> = field_data
693 .iter()
694 .map(|(ident, ty, attrs)| {
695 if attrs.group || is_vec_type(ty) {
696 quote! {
697 for __item in &self.#ident {
698 ::edifact_rs::EdifactSerialize::edifact_serialize(__item, emitter)?;
699 }
700 }
701 } else {
702 quote! {
703 ::edifact_rs::EdifactSerialize::edifact_serialize(&self.#ident, emitter)?;
704 }
705 }
706 })
707 .collect();
708 quote! { #(#stmts)* }
709 };
710
711 Ok(quote! {
712 impl ::edifact_rs::EdifactSerialize for #name {
713 fn edifact_serialize<__E: ::edifact_rs::EventEmitter>(
714 &self,
715 emitter: &mut __E,
716 ) -> ::core::result::Result<(), ::edifact_rs::EdifactError> {
717 #body
718 ::core::result::Result::Ok(())
719 }
720 }
721 })
722}
723
724fn emit_element(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
729 if is_option_type(ty) {
730 let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
731 if inner_is_str {
732 quote! {
733 match &self.#ident {
734 ::core::option::Option::Some(__v) => {
735 emitter.emit(::edifact_rs::EdifactEvent::Element { value: __v.as_str() })?;
736 }
737 ::core::option::Option::None => {
738 emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
739 }
740 }
741 }
742 } else {
743 quote! {
744 match &self.#ident {
745 ::core::option::Option::Some(__v) => {
746 let __s = ::std::string::ToString::to_string(__v);
747 emitter.emit(::edifact_rs::EdifactEvent::Element { value: &__s })?;
748 }
749 ::core::option::Option::None => {
750 emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
751 }
752 }
753 }
754 }
755 } else if is_string_type(ty) {
756 quote! {
757 emitter.emit(::edifact_rs::EdifactEvent::Element { value: self.#ident.as_str() })?;
758 }
759 } else if is_str_ref_type(ty) {
760 quote! {
761 emitter.emit(::edifact_rs::EdifactEvent::Element { value: self.#ident })?;
762 }
763 } else {
764 quote! {
765 {
766 let __s = ::std::string::ToString::to_string(&self.#ident);
767 emitter.emit(::edifact_rs::EdifactEvent::Element { value: &__s })?;
768 }
769 }
770 }
771}
772
773fn emit_component_element(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
777 if is_option_type(ty) {
778 let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
779 if inner_is_str {
780 quote! {
781 match &self.#ident {
782 ::core::option::Option::Some(__v) => {
783 emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: __v.as_str() })?;
784 }
785 ::core::option::Option::None => {
786 emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: "" })?;
787 }
788 }
789 }
790 } else {
791 quote! {
792 match &self.#ident {
793 ::core::option::Option::Some(__v) => {
794 let __s = ::std::string::ToString::to_string(__v);
795 emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: &__s })?;
796 }
797 ::core::option::Option::None => {
798 emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: "" })?;
799 }
800 }
801 }
802 }
803 } else if is_string_type(ty) {
804 quote! {
805 emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: self.#ident.as_str() })?;
806 }
807 } else if is_str_ref_type(ty) {
808 quote! {
809 emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: self.#ident })?;
810 }
811 } else {
812 quote! {
813 {
814 let __s = ::std::string::ToString::to_string(&self.#ident);
815 emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: &__s })?;
816 }
817 }
818 }
819}
820
821fn emit_composite_field(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
823 if is_option_type(ty) {
824 quote! {
825 match &self.#ident {
826 ::core::option::Option::Some(__v) => {
827 ::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(__v, emitter)?;
828 }
829 ::core::option::Option::None => {
830 emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
831 }
832 }
833 }
834 } else {
835 quote! {
836 ::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(&self.#ident, emitter)?;
837 }
838 }
839}
840
841fn impl_deserialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
844 let name = &input.ident;
845 let struct_attrs = parse_struct_attrs(input)?;
846 let fields = get_named_fields(input)?;
847 let is_segment_struct = struct_attrs.segment.is_some();
848
849 let field_data: Vec<(&syn::Ident, &Type, FieldAttrs)> = fields
850 .named
851 .iter()
852 .map(|f| {
853 let attrs = parse_field_attrs(f)?;
854 let ident = f
855 .ident
856 .as_ref()
857 .ok_or_else(|| syn::Error::new_spanned(f, "only named fields are supported"))?;
858 validate_field_attrs(ident, &f.ty, &attrs, is_segment_struct)?;
859 Ok((ident, &f.ty, attrs))
860 })
861 .collect::<syn::Result<_>>()?;
862 check_duplicate_slots(&field_data, is_segment_struct)?;
863
864 let field_names: Vec<&syn::Ident> = field_data.iter().map(|(id, _, _)| *id).collect();
865
866 let (body, owned_body, segment_tag_impl) = if let Some(seg_tag) = &struct_attrs.segment {
867 let qualifier_guard = if let Some(qual) = &struct_attrs.qualifier {
869 quote! {
870 if __seg.element_str(0).unwrap_or("") != #qual {
871 return ::core::result::Result::Err(
872 ::edifact_rs::EdifactError::MissingRequiredElement {
873 tag: #seg_tag.to_owned(),
874 element_index: 0,
875 }
876 );
877 }
878 }
879 } else if let Some(idx) = struct_attrs.qualifier_from {
880 quote! {
881 match __seg.element_str(#idx as usize) {
885 ::core::option::Option::None => return ::core::result::Result::Err(
886 ::edifact_rs::EdifactError::MissingRequiredElement {
887 tag: #seg_tag.to_owned(),
888 element_index: #idx as usize,
889 }
890 ),
891 ::core::option::Option::Some("") => return ::core::result::Result::Err(
892 ::edifact_rs::EdifactError::InvalidFieldValue {
893 tag: #seg_tag.to_owned(),
894 element_index: #idx as usize,
895 value: ::std::string::String::new(),
896 }
897 ),
898 ::core::option::Option::Some(__qual_val) => { let _ = __qual_val; }
899 }
900 }
901 } else {
902 quote! {}
903 };
904
905 let find_seg = if let Some(qual) = &struct_attrs.qualifier {
906 quote! {
907 ::edifact_rs::find_qualified_segment(segments, #seg_tag, #qual)
908 }
909 } else {
910 quote! {
911 ::edifact_rs::find_segment(segments, #seg_tag)
912 }
913 };
914
915 let field_inits: Vec<TokenStream2> = field_data
916 .iter()
917 .enumerate()
918 .map(|(decl_i, (ident, ty, attrs))| -> syn::Result<TokenStream2> {
919 let idx = attrs.element.unwrap_or(decl_i as u32) as usize;
920 if attrs.composite {
921 if is_option_type(ty) {
922 let inner_ty = option_inner_type(ty)
923 .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
924 return Ok(quote! {
925 let #ident = match ::edifact_rs::composite_element(__seg, #idx) {
926 ::core::option::Option::Some(__composite) => {
927 ::core::option::Option::Some(
928 <#inner_ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(__composite)?
929 )
930 }
931 ::core::option::Option::None => ::core::option::Option::None,
932 };
933 });
934 }
935 return Ok(quote! {
936 let #ident = <#ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
937 ::edifact_rs::composite_element(__seg, #idx).ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
938 tag: #seg_tag.to_owned(),
939 element_index: #idx as usize,
940 })?
941 )?;
942 });
943 }
944 let component_idx: Option<usize> = attrs.component.map(|c| c as usize);
945 let value_expr = if let Some(comp) = component_idx {
946 quote! {
947 __seg.get_element(#idx).and_then(|__e| __e.get_component(#comp))
948 }
949 } else {
950 quote! { __seg.element_str(#idx) }
951 };
952 let missing_required_err = if let Some(comp) = component_idx {
955 quote! {
956 ::edifact_rs::EdifactError::MissingRequiredComponent {
957 tag: #seg_tag.to_owned(),
958 element_index: #idx as usize,
959 component_index: #comp as usize,
960 }
961 }
962 } else {
963 quote! {
964 ::edifact_rs::EdifactError::MissingRequiredElement {
965 tag: #seg_tag.to_owned(),
966 element_index: #idx as usize,
967 }
968 }
969 };
970 Ok(if is_option_type(ty) {
971 let inner_ty = option_inner_type(ty);
972 let inner_is_str = inner_ty.is_some_and(is_str_like);
973 if attrs.required {
974 if inner_is_str {
978 quote! {
979 let #ident = ::core::option::Option::Some(
980 #value_expr
981 .filter(|__s| !__s.is_empty())
982 .ok_or_else(|| #missing_required_err)?
983 .to_owned()
984 );
985 }
986 } else {
987 let inner_ty = inner_ty
988 .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
989 quote! {
990 let #ident = ::core::option::Option::Some(
991 #value_expr
992 .filter(|__s| !__s.is_empty())
993 .ok_or_else(|| #missing_required_err)?
994 .parse::<#inner_ty>()
995 .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?
996 );
997 }
998 }
999 } else if inner_is_str {
1000 quote! {
1001 let #ident = #value_expr
1002 .filter(|__s| !__s.is_empty())
1003 .map(::std::string::String::from);
1004 }
1005 } else {
1006 let inner_ty = inner_ty
1007 .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1008 quote! {
1009 let #ident = #value_expr
1010 .filter(|__s| !__s.is_empty())
1011 .map(|__s| __s.parse::<#inner_ty>()
1012 .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })
1013 )
1014 .transpose()?;
1015 }
1016 }
1017 } else if is_str_like(ty) {
1018 quote! {
1019 let #ident = #value_expr
1020 .filter(|__s| !__s.is_empty())
1021 .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1022 tag: #seg_tag.to_owned(),
1023 element_index: #idx as usize,
1024 })?
1025 .to_owned();
1026 }
1027 } else {
1028 quote! {
1029 let #ident = #value_expr
1030 .filter(|__s| !__s.is_empty())
1031 .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1032 tag: #seg_tag.to_owned(),
1033 element_index: #idx as usize,
1034 })?
1035 .parse::<#ty>()
1036 .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?;
1037 }
1038 })
1039 })
1040 .collect::<syn::Result<_>>()?;
1041
1042 let body = quote! {
1043 let __seg = #find_seg
1044 .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
1045 tag: #seg_tag.to_owned(),
1046 expected_position: "message body".to_owned(),
1047 })?;
1048 #qualifier_guard
1049 #(#field_inits)*
1050 ::core::result::Result::Ok(Self { #(#field_names),* })
1051 };
1052
1053 let qualifier_match = if let Some(qual) = &struct_attrs.qualifier {
1060 quote! {
1061 const QUALIFIER_PATTERN: ::core::option::Option<&'static str> =
1062 ::core::option::Option::Some(#qual);
1063 }
1064 } else if let Some(idx) = struct_attrs.qualifier_from {
1065 quote! {
1069 fn matches_segment(seg: &::edifact_rs::Segment<'_>) -> bool {
1070 seg.tag == Self::SEGMENT_TAG
1071 && !seg.element_str(#idx as usize).unwrap_or("").is_empty()
1072 }
1073
1074 fn matches_owned_segment(seg: &::edifact_rs::OwnedSegment) -> bool {
1075 seg.tag == Self::SEGMENT_TAG
1076 && !seg
1077 .elements
1078 .get(#idx as usize)
1079 .and_then(|e| e.components.first())
1080 .map(|(c, _)| c.as_str())
1081 .unwrap_or("")
1082 .is_empty()
1083 }
1084 }
1085 } else {
1086 quote! {}
1087 };
1088
1089 let seg_tag_impl = quote! {
1090 impl ::edifact_rs::EdifactSegmentTag for #name {
1091 const SEGMENT_TAG: &'static str = #seg_tag;
1092 #qualifier_match
1093 }
1094 };
1095
1096 let find_seg_owned = if let Some(qual) = &struct_attrs.qualifier {
1099 quote! {
1100 ::edifact_rs::find_qualified_segment_owned(segments, #seg_tag, #qual)
1101 }
1102 } else {
1103 quote! {
1104 ::edifact_rs::find_segment_owned(segments, #seg_tag)
1105 }
1106 };
1107
1108 let field_inits_owned: Vec<TokenStream2> = field_data
1109 .iter()
1110 .enumerate()
1111 .map(|(decl_i, (ident, ty, attrs))| -> syn::Result<TokenStream2> {
1112 let idx = attrs.element.unwrap_or(decl_i as u32) as usize;
1113 if attrs.composite {
1114 if is_option_type(ty) {
1115 let inner_ty = option_inner_type(ty)
1116 .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1117 return Ok(quote! {
1118 let #ident = match __seg.elements.get(#idx) {
1119 ::core::option::Option::Some(__e) => {
1120 let __cows = __e.components.iter()
1121 .map(|(s, _)| ::std::borrow::Cow::Borrowed(s.as_str()))
1122 .collect::<::std::vec::Vec<::std::borrow::Cow<'_, str>>>();
1123 ::core::option::Option::Some(
1124 <#inner_ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
1125 ::edifact_rs::CompositeElement::from_slice(&__cows)
1126 )?
1127 )
1128 }
1129 ::core::option::Option::None => ::core::option::Option::None,
1130 };
1131 });
1132 }
1133 return Ok(quote! {
1134 let #ident = {
1135 let __cows = __seg.elements.get(#idx)
1136 .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1137 tag: #seg_tag.to_owned(),
1138 element_index: #idx as usize,
1139 })?
1140 .components.iter()
1141 .map(|(s, _)| ::std::borrow::Cow::Borrowed(s.as_str()))
1142 .collect::<::std::vec::Vec<::std::borrow::Cow<'_, str>>>();
1143 <#ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
1144 ::edifact_rs::CompositeElement::from_slice(&__cows)
1145 )?
1146 };
1147 });
1148 }
1149 let component_idx_owned: Option<usize> = attrs.component.map(|c| c as usize);
1150 let value_expr_owned = if let Some(comp) = component_idx_owned {
1151 quote! { __seg.component_str(#idx, #comp) }
1152 } else {
1153 quote! { __seg.element_str(#idx) }
1154 };
1155 let missing_required_err_owned = if let Some(comp) = component_idx_owned {
1157 quote! {
1158 ::edifact_rs::EdifactError::MissingRequiredComponent {
1159 tag: #seg_tag.to_owned(),
1160 element_index: #idx as usize,
1161 component_index: #comp as usize,
1162 }
1163 }
1164 } else {
1165 quote! {
1166 ::edifact_rs::EdifactError::MissingRequiredElement {
1167 tag: #seg_tag.to_owned(),
1168 element_index: #idx as usize,
1169 }
1170 }
1171 };
1172 Ok(if is_option_type(ty) {
1173 if let Some(inner_ty) = option_inner_type(ty) {
1174 if attrs.required {
1175 if is_str_like(inner_ty) {
1179 quote! {
1180 let #ident = ::core::option::Option::Some(
1181 #value_expr_owned
1182 .filter(|__s| !__s.is_empty())
1183 .ok_or_else(|| #missing_required_err_owned)?
1184 .to_owned()
1185 );
1186 }
1187 } else {
1188 quote! {
1189 let #ident = ::core::option::Option::Some(
1190 #value_expr_owned
1191 .filter(|__s| !__s.is_empty())
1192 .ok_or_else(|| #missing_required_err_owned)?
1193 .parse::<#inner_ty>()
1194 .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?
1195 );
1196 }
1197 }
1198 } else if is_str_like(inner_ty) {
1199 quote! {
1200 let #ident = #value_expr_owned
1201 .filter(|__s| !__s.is_empty())
1202 .map(::std::string::String::from);
1203 }
1204 } else {
1205 quote! {
1206 let #ident = #value_expr_owned
1207 .filter(|__s| !__s.is_empty())
1208 .map(|__s| __s.parse::<#inner_ty>()
1209 .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })
1210 )
1211 .transpose()?;
1212 }
1213 }
1214 } else {
1215 quote! {
1217 let #ident = #value_expr_owned
1218 .filter(|__s| !__s.is_empty())
1219 .map(::std::string::String::from);
1220 }
1221 }
1222 } else if is_str_like(ty) {
1223 quote! {
1224 let #ident = #value_expr_owned
1225 .filter(|__s| !__s.is_empty())
1226 .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1227 tag: #seg_tag.to_owned(),
1228 element_index: #idx as usize,
1229 })?
1230 .to_owned();
1231 }
1232 } else {
1233 quote! {
1234 let #ident = #value_expr_owned
1235 .filter(|__s| !__s.is_empty())
1236 .ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
1237 tag: #seg_tag.to_owned(),
1238 element_index: #idx as usize,
1239 })?
1240 .parse::<#ty>()
1241 .map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?;
1242 }
1243 })
1244 })
1245 .collect::<syn::Result<_>>()?;
1246
1247 let owned_body = quote! {
1248 let __seg = #find_seg_owned
1249 .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
1250 tag: #seg_tag.to_owned(),
1251 expected_position: "message body".to_owned(),
1252 })?;
1253 #qualifier_guard
1254 #(#field_inits_owned)*
1255 ::core::result::Result::Ok(Self { #(#field_names),* })
1256 };
1257
1258 (body, owned_body, seg_tag_impl)
1259 } else {
1260 let field_inits: Vec<TokenStream2> = field_data
1262 .iter()
1263 .map(|(ident, ty, attrs)| -> syn::Result<TokenStream2> {
1264 Ok(if let Some(qual) = &attrs.qualifier {
1265 if attrs.group || is_vec_type(ty) {
1266 let inner_ty = vec_inner_type(ty)
1267 .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
1268 quote! {
1269 let #ident = segments
1270 .iter()
1271 .filter(|__seg| {
1272 __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG
1273 && __seg.element_str(0).unwrap_or("") == #qual
1274 })
1275 .map(|__seg| {
1276 ::edifact_rs::EdifactDeserialize::edifact_deserialize(
1277 ::core::slice::from_ref(__seg),
1278 )
1279 })
1280 .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, ::edifact_rs::EdifactError>>()?;
1281 }
1282 } else if is_option_type(ty) {
1283 let inner_ty = option_inner_type(ty)
1284 .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1285 quote! {
1286 let #ident = match ::edifact_rs::find_qualified_segment(
1287 segments,
1288 <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
1289 #qual,
1290 ) {
1291 ::core::option::Option::Some(__seg) => {
1292 ::core::option::Option::Some(
1293 ::edifact_rs::EdifactDeserialize::edifact_deserialize(
1294 ::core::slice::from_ref(__seg),
1295 )?
1296 )
1297 }
1298 ::core::option::Option::None => ::core::option::Option::None,
1299 };
1300 }
1301 } else {
1302 quote! {
1303 let __seg = ::edifact_rs::find_qualified_segment(
1304 segments,
1305 <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
1306 #qual,
1307 )
1308 .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
1309 tag: <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG.to_owned(),
1310 expected_position: "message body".to_owned(),
1311 })?;
1312 let #ident = ::edifact_rs::EdifactDeserialize::edifact_deserialize(
1313 ::core::slice::from_ref(__seg),
1314 )?;
1315 }
1316 }
1317 } else if attrs.group || is_vec_type(ty) {
1318 let inner_ty = vec_inner_type(ty)
1319 .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
1320 quote! {
1321 let #ident = ::edifact_rs::find_segments_typed::<#inner_ty>(segments)
1322 .map(|__seg| {
1323 <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize(
1324 ::core::slice::from_ref(__seg),
1325 )
1326 })
1327 .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, _>>()?;
1328 }
1329 } else if is_option_type(ty) {
1330 let inner_ty = option_inner_type(ty)
1331 .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1332 quote! {
1333 let #ident = if segments
1334 .iter()
1335 .any(|__seg| __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG)
1336 {
1337 ::core::option::Option::Some(
1338 <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize(segments)?
1339 )
1340 } else {
1341 ::core::option::Option::None
1342 };
1343 }
1344 } else {
1345 quote! {
1346 let #ident = ::edifact_rs::EdifactDeserialize::edifact_deserialize(segments)?;
1347 }
1348 })
1349 })
1350 .collect::<syn::Result<_>>()?;
1351
1352 let body = quote! {
1353 #(#field_inits)*
1354 ::core::result::Result::Ok(Self { #(#field_names),* })
1355 };
1356
1357 let field_inits_owned: Vec<TokenStream2> = field_data
1360 .iter()
1361 .map(|(ident, ty, attrs)| -> syn::Result<TokenStream2> {
1362 Ok(if let Some(qual) = &attrs.qualifier {
1363 if attrs.group || is_vec_type(ty) {
1364 let inner_ty = vec_inner_type(ty)
1365 .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
1366 quote! {
1367 let #ident = segments
1368 .iter()
1369 .filter(|__seg| {
1370 __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG
1371 && __seg.element_str(0).unwrap_or("") == #qual
1372 })
1373 .map(|__seg| {
1374 <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
1375 ::core::slice::from_ref(__seg),
1376 )
1377 })
1378 .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, ::edifact_rs::EdifactError>>()?;
1379 }
1380 } else if is_option_type(ty) {
1381 let inner_ty = option_inner_type(ty)
1382 .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1383 quote! {
1384 let #ident = match ::edifact_rs::find_qualified_segment_owned(
1385 segments,
1386 <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
1387 #qual,
1388 ) {
1389 ::core::option::Option::Some(__seg) => {
1390 ::core::option::Option::Some(
1391 <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
1392 ::core::slice::from_ref(__seg),
1393 )?
1394 )
1395 }
1396 ::core::option::Option::None => ::core::option::Option::None,
1397 };
1398 }
1399 } else {
1400 quote! {
1401 let __seg = ::edifact_rs::find_qualified_segment_owned(
1402 segments,
1403 <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
1404 #qual,
1405 )
1406 .ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
1407 tag: <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG.to_owned(),
1408 expected_position: "message body".to_owned(),
1409 })?;
1410 let #ident = <#ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
1411 ::core::slice::from_ref(__seg),
1412 )?;
1413 }
1414 }
1415 } else if attrs.group || is_vec_type(ty) {
1416 let inner_ty = vec_inner_type(ty)
1417 .ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
1418 quote! {
1419 let #ident = segments
1420 .iter()
1421 .filter(|__seg| <#inner_ty as ::edifact_rs::EdifactSegmentTag>::matches_owned_segment(__seg))
1422 .map(|__seg| {
1423 <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
1424 ::core::slice::from_ref(__seg),
1425 )
1426 })
1427 .collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, _>>()?;
1428 }
1429 } else if is_option_type(ty) {
1430 let inner_ty = option_inner_type(ty)
1431 .ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
1432 quote! {
1433 let #ident = if segments
1434 .iter()
1435 .any(|__seg| __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG)
1436 {
1437 ::core::option::Option::Some(
1438 <#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(segments)?
1439 )
1440 } else {
1441 ::core::option::Option::None
1442 };
1443 }
1444 } else {
1445 quote! {
1446 let #ident = <#ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(segments)?;
1447 }
1448 })
1449 })
1450 .collect::<syn::Result<_>>()?;
1451
1452 let owned_body = quote! {
1453 #(#field_inits_owned)*
1454 ::core::result::Result::Ok(Self { #(#field_names),* })
1455 };
1456
1457 (body, owned_body, quote! {})
1458 };
1459
1460 Ok(quote! {
1461 impl ::edifact_rs::EdifactDeserialize for #name {
1462 fn edifact_deserialize(
1463 segments: &[::edifact_rs::Segment<'_>],
1464 ) -> ::core::result::Result<Self, ::edifact_rs::EdifactError> {
1465 #body
1466 }
1467
1468 fn edifact_deserialize_owned(
1469 segments: &[::edifact_rs::OwnedSegment],
1470 ) -> ::core::result::Result<Self, ::edifact_rs::EdifactError> {
1471 #owned_body
1472 }
1473 }
1474 #segment_tag_impl
1475 })
1476}
1477
1478#[cfg(test)]
1479mod tests {
1480 #[test]
1490 fn trybuild_ui() {
1491 if std::env::var_os("EDIFACT_UI_TESTS").is_none() {
1492 eprintln!(
1493 "skipping derive UI suite: set EDIFACT_UI_TESTS=1 to run it \
1494 (expectations are pinned to the MSRV toolchain)"
1495 );
1496 return;
1497 }
1498 let t = trybuild::TestCases::new();
1499 t.pass("tests/ui/pass_*.rs");
1500 t.compile_fail("tests/ui/fail_*.rs");
1501 }
1502}