#![deny(unsafe_code)]
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Data, DeriveInput, Field, Fields, Type, parse_macro_input, spanned::Spanned};
#[proc_macro_derive(EdifactSerialize, attributes(edifact))]
pub fn derive_edifact_serialize(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
impl_serialize(&input)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
#[proc_macro_derive(EdifactDeserialize, attributes(edifact))]
pub fn derive_edifact_deserialize(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
impl_deserialize(&input)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
#[proc_macro_derive(EdifactCompositeDeserialize, attributes(edifact))]
pub fn derive_edifact_composite_deserialize(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
impl_composite_deserialize(&input)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
#[proc_macro_derive(EdifactCompositeSerialize, attributes(edifact))]
pub fn derive_edifact_composite_serialize(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
impl_composite_serialize(&input)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
#[derive(Clone)]
enum Position {
Index(u32),
Code(String),
}
#[derive(Default)]
struct StructAttrs {
segment: Option<String>,
qualifier: Option<String>,
qualifier_span: Option<proc_macro2::Span>,
qualifier_from: Option<u32>,
qualifier_from_span: Option<proc_macro2::Span>,
layout: Option<syn::Path>,
layout_span: Option<proc_macro2::Span>,
}
#[derive(Default)]
struct FieldAttrs {
element: Option<Position>,
element_span: Option<proc_macro2::Span>,
component: Option<u32>,
component_span: Option<proc_macro2::Span>,
composite: bool,
composite_span: Option<proc_macro2::Span>,
group: bool,
group_span: Option<proc_macro2::Span>,
qualifier: Option<String>,
qualifier_span: Option<proc_macro2::Span>,
required: bool,
required_span: Option<proc_macro2::Span>,
}
const MAX_POSITION_INDEX: u32 = 256;
fn check_index_bound(lit: &syn::LitInt, value: u32, key: &str) -> syn::Result<()> {
if value > MAX_POSITION_INDEX {
return Err(syn::Error::new(
lit.span(),
format!(
"`{key}` index {value} exceeds the maximum of {MAX_POSITION_INDEX}; \
UN/EDIFACT allows at most 99 elements per segment and 99 components per composite"
),
));
}
Ok(())
}
fn parse_struct_attrs(input: &DeriveInput) -> syn::Result<StructAttrs> {
let mut out = StructAttrs::default();
for attr in &input.attrs {
if !attr.path().is_ident("edifact") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("segment") {
if out.segment.is_some() {
return Err(meta.error("duplicate `segment`"));
}
let lit = meta.value()?.parse::<syn::LitStr>()?;
let tag = lit.value();
if tag.len() != 3 || !tag.bytes().all(|b| b.is_ascii_uppercase()) {
return Err(syn::Error::new(
lit.span(),
format!(
"segment tag must be exactly 3 ASCII uppercase letters; got {tag:?}"
),
));
}
out.segment = Some(tag);
} else if meta.path.is_ident("qualifier") {
if out.qualifier.is_some() {
return Err(meta.error("duplicate `qualifier`"));
}
out.qualifier = Some(meta.value()?.parse::<syn::LitStr>()?.value());
out.qualifier_span = Some(meta.path.span());
} else if meta.path.is_ident("qualifier_from") {
if out.qualifier_from.is_some() {
return Err(meta.error("duplicate `qualifier_from`"));
}
let lit = meta.value()?.parse::<syn::LitInt>()?;
let idx: u32 = lit.base10_parse()?;
check_index_bound(&lit, idx, "qualifier_from")?;
out.qualifier_from = Some(idx);
out.qualifier_from_span = Some(meta.path.span());
} else if meta.path.is_ident("layout") {
if out.layout.is_some() {
return Err(meta.error("duplicate `layout`"));
}
out.layout_span = Some(meta.path.span());
let value = meta.value()?;
out.layout = Some(if value.peek(syn::LitStr) {
value.parse::<syn::LitStr>()?.parse()?
} else {
value.parse::<syn::Path>()?
});
} else {
return Err(meta.error("unknown struct-level `edifact` key; expected `segment`, `qualifier`, `qualifier_from`, or `layout`"));
}
Ok(())
})?;
}
if out.layout.is_some() && out.segment.is_none() {
return Err(syn::Error::new(
out.layout_span.unwrap_or_else(|| input.span()),
"#[edifact(layout = ...)] requires #[edifact(segment = ...)]: a layout describes one segment",
));
}
if (out.qualifier.is_some() || out.qualifier_from.is_some()) && out.segment.is_none() {
return Err(syn::Error::new(
out.qualifier_span
.or(out.qualifier_from_span)
.unwrap_or_else(|| input.span()),
"#[edifact(qualifier = ...)] / #[edifact(qualifier_from = ...)] require #[edifact(segment = ...)]",
));
}
if out.qualifier.is_some() && out.qualifier_from.is_some() {
return Err(syn::Error::new(
out.qualifier_from_span
.or(out.qualifier_span)
.unwrap_or_else(|| input.span()),
"use either #[edifact(qualifier = ...)] or #[edifact(qualifier_from = ...)], not both",
));
}
Ok(out)
}
fn parse_field_attrs(field: &Field) -> syn::Result<FieldAttrs> {
let mut out = FieldAttrs::default();
for attr in &field.attrs {
if !attr.path().is_ident("edifact") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("element") {
if out.element.is_some() {
return Err(meta.error("duplicate `element`"));
}
out.element_span = Some(meta.path.span());
let value = meta.value()?;
out.element = Some(if value.peek(syn::LitStr) {
let lit = value.parse::<syn::LitStr>()?;
let code = lit.value();
if code.is_empty() {
return Err(syn::Error::new(
lit.span(),
"`element` data element identifier must not be empty",
));
}
Position::Code(code)
} else {
let lit = value.parse::<syn::LitInt>()?;
let idx: u32 = lit.base10_parse()?;
check_index_bound(&lit, idx, "element")?;
Position::Index(idx)
});
} else if meta.path.is_ident("component") {
if out.component.is_some() {
return Err(meta.error("duplicate `component`"));
}
out.component_span = Some(meta.path.span());
let value = meta.value()?;
if value.peek(syn::LitStr) {
let lit = value.parse::<syn::LitStr>()?;
return Err(syn::Error::new(
lit.span(),
"put the data element identifier in `element`: \
`#[edifact(element = \"3055\")]` resolves both the element and the \
component position from the directory",
));
}
let lit = value.parse::<syn::LitInt>()?;
let idx: u32 = lit.base10_parse()?;
check_index_bound(&lit, idx, "component")?;
out.component = Some(idx);
} else if meta.path.is_ident("composite") {
out.composite = true;
out.composite_span = Some(meta.path.span());
} else if meta.path.is_ident("group") {
out.group = true;
out.group_span = Some(meta.path.span());
} else if meta.path.is_ident("qualifier") {
if out.qualifier.is_some() {
return Err(meta.error("duplicate `qualifier`"));
}
out.qualifier = Some(meta.value()?.parse::<syn::LitStr>()?.value());
out.qualifier_span = Some(meta.path.span());
} else if meta.path.is_ident("required") {
out.required = true;
out.required_span = Some(meta.path.span());
} else {
return Err(meta.error("unknown field-level `edifact` key; expected `element`, `component`, `composite`, `group`, `qualifier`, or `required`"));
}
Ok(())
})?;
}
Ok(out)
}
struct Slots {
element: TokenStream2,
component: TokenStream2,
has_component: bool,
names_component: TokenStream2,
}
fn resolve_slots(
struct_attrs: &StructAttrs,
field_data: &[(&syn::Ident, &Type, FieldAttrs)],
) -> syn::Result<(TokenStream2, Vec<Slots>)> {
let mut consts: Vec<TokenStream2> = Vec::new();
let mut slots: Vec<Slots> = Vec::with_capacity(field_data.len());
let mut slot_entries: Vec<TokenStream2> = Vec::new();
if struct_attrs.qualifier.is_some() {
slot_entries.push(quote! { (0usize, 0usize) });
}
for (i, (ident, _, attrs)) in field_data.iter().enumerate() {
if attrs.group {
slots.push(Slots {
element: quote! { 0usize },
component: quote! { 0usize },
has_component: false,
names_component: quote! { false },
});
continue;
}
let component_index = attrs.component.unwrap_or(0);
let slot = match &attrs.element {
Some(Position::Code(code)) => {
let Some(layout) = &struct_attrs.layout else {
return Err(syn::Error::new(
attrs.element_span.unwrap_or_else(|| ident.span()),
format!(
"field `{ident}`: `element = \"{code}\"` addresses a UN/EDIFACT data \
element identifier, which needs a directory to resolve against; add \
#[edifact(layout = path::to::SEGMENT_DEFINITION)] to the struct"
),
));
};
let slot_ident = syn::Ident::new(
&format!("__EDIFACT_SLOT_{i}"),
attrs.element_span.unwrap_or_else(|| ident.span()),
);
let unknown_msg = format!(
"field `{ident}`: data element {code} is not defined exactly once in the \
segment layout — check the identifier against the directory"
);
consts.push(quote! {
const _: () = ::core::assert!(#layout.code_positions(#code) == 1, #unknown_msg);
});
if attrs.component.is_some() {
let conflict_msg = format!(
"field `{ident}`: `component = N` may only accompany an identifier that \
names a whole data element, but {code} names a component inside one"
);
consts.push(quote! {
const _: () =
::core::assert!(#layout.component_slot(#code) == 0, #conflict_msg);
});
consts.push(quote! {
const #slot_ident: (usize, usize) =
if #layout.code_positions(#code) == 1 {
(#layout.element_slot(#code), #component_index as usize)
} else {
(0, 0)
};
});
} else {
consts.push(quote! {
const #slot_ident: (usize, usize) =
if #layout.code_positions(#code) == 1 {
(#layout.element_slot(#code), #layout.component_slot(#code))
} else {
(0, 0)
};
});
}
slot_entries.push(quote! { #slot_ident });
let names_component = if attrs.component.is_some() {
quote! { true }
} else {
quote! { #layout.code_is_component(#code) }
};
Slots {
element: quote! { #slot_ident.0 },
component: quote! { #slot_ident.1 },
has_component: true,
names_component,
}
}
Some(Position::Index(idx)) => {
let idx = *idx;
let explicit = attrs.component.is_some();
slot_entries.push(quote! { (#idx as usize, #component_index as usize) });
Slots {
element: quote! { #idx as usize },
component: quote! { #component_index as usize },
has_component: explicit,
names_component: quote! { #explicit },
}
}
None => {
let idx = i as u32;
let explicit = attrs.component.is_some();
slot_entries.push(quote! { (#idx as usize, #component_index as usize) });
Slots {
element: quote! { #idx as usize },
component: quote! { #component_index as usize },
has_component: explicit,
names_component: quote! { #explicit },
}
}
};
slots.push(slot);
}
if struct_attrs.layout.is_some() && !slot_entries.is_empty() {
let count = slot_entries.len();
consts.push(quote! {
const __EDIFACT_SLOTS: [(usize, usize); #count] = [#(#slot_entries),*];
const _: () = {
let mut i = 0;
while i < __EDIFACT_SLOTS.len() {
let mut j = i + 1;
while j < __EDIFACT_SLOTS.len() {
::core::assert!(
!(__EDIFACT_SLOTS[i].0 == __EDIFACT_SLOTS[j].0
&& __EDIFACT_SLOTS[i].1 == __EDIFACT_SLOTS[j].1),
"two fields resolve to the same element/component slot; \
give each field a distinct data element identifier or index"
);
j += 1;
}
i += 1;
}
};
});
}
Ok((quote! { #(#consts)* }, slots))
}
fn is_option_type(ty: &Type) -> bool {
matches!(ty, Type::Path(p) if p.path.segments.last().is_some_and(|s| s.ident == "Option"))
}
fn is_vec_type(ty: &Type) -> bool {
matches!(ty, Type::Path(p) if p.path.segments.last().is_some_and(|s| s.ident == "Vec"))
}
fn is_string_type(ty: &Type) -> bool {
let Type::Path(p) = ty else { return false };
if p.path.is_ident("String") {
return true;
}
let segs = &p.path.segments;
segs.len() == 3
&& (segs[0].ident == "std" || segs[0].ident == "alloc")
&& segs[1].ident == "string"
&& segs[2].ident == "String"
}
fn is_str_ref_type(ty: &Type) -> bool {
let Type::Reference(r) = ty else { return false };
matches!(r.elem.as_ref(), Type::Path(p) if p.path.is_ident("str"))
}
fn is_str_like(ty: &Type) -> bool {
is_string_type(ty) || is_str_ref_type(ty)
}
fn option_inner_type(ty: &Type) -> Option<&Type> {
let Type::Path(path) = ty else { return None };
let seg = path.path.segments.last()?;
if seg.ident != "Option" {
return None;
}
let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
return None;
};
let syn::GenericArgument::Type(inner) = args.args.first()? else {
return None;
};
Some(inner)
}
fn vec_inner_type(ty: &Type) -> Option<&Type> {
let Type::Path(path) = ty else { return None };
let seg = path.path.segments.last()?;
if seg.ident != "Vec" {
return None;
}
let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
return None;
};
let syn::GenericArgument::Type(inner) = args.args.first()? else {
return None;
};
Some(inner)
}
fn check_duplicate_slots(
field_data: &[(&syn::Ident, &Type, FieldAttrs)],
is_segment_struct: bool,
) -> syn::Result<()> {
if !is_segment_struct {
return Ok(());
}
let mut seen: Vec<((u32, u32), &syn::Ident)> = Vec::with_capacity(field_data.len());
for (i, (ident, _, attrs)) in field_data.iter().enumerate() {
if attrs.group {
continue;
}
let element = match &attrs.element {
Some(Position::Index(idx)) => *idx,
Some(Position::Code(_)) => continue,
None => i as u32,
};
let slot = (element, attrs.component.unwrap_or(0));
if let Some((_, first)) = seen.iter().find(|(s, _)| *s == slot) {
return Err(syn::Error::new(
ident.span(),
format!(
"field `{ident}` maps to element {} component {}, which is already \
claimed by field `{first}`; give each field a distinct \
`#[edifact(element = ..., component = ...)]` slot",
slot.0, slot.1
),
));
}
seen.push((slot, ident));
}
Ok(())
}
struct CompositeSlot<'a> {
ident: &'a syn::Ident,
ty: &'a Type,
index: u32,
optional: bool,
}
fn composite_slots(input: &DeriveInput) -> syn::Result<Vec<CompositeSlot<'_>>> {
let fields = get_named_fields(input)?;
let mut slots: Vec<CompositeSlot<'_>> = Vec::with_capacity(fields.named.len());
for (decl_index, field) in fields.named.iter().enumerate() {
let ident = field.ident.as_ref().expect("named fields checked above");
let attrs = parse_field_attrs(field)?;
for (present, span, key) in [
(attrs.element.is_some(), attrs.element_span, "element"),
(attrs.composite, attrs.composite_span, "composite"),
(attrs.group, attrs.group_span, "group"),
(attrs.qualifier.is_some(), attrs.qualifier_span, "qualifier"),
] {
if present {
return Err(syn::Error::new(
span.unwrap_or_else(|| field.span()),
format!(
"`{key}` has no meaning on a composite struct field; \
a composite maps its fields to components, so only \
`component` and `required` apply"
),
));
}
}
let ty = &field.ty;
let inner = option_inner_type(ty).unwrap_or(ty);
if !is_string_type(inner) {
return Err(syn::Error::new(
ty.span(),
"composite struct fields must be `String` or `Option<String>`; \
a component is a single text value",
));
}
slots.push(CompositeSlot {
ident,
ty,
index: attrs.component.unwrap_or(decl_index as u32),
optional: is_option_type(ty) && !attrs.required,
});
}
let mut seen: Vec<(u32, &syn::Ident)> = Vec::with_capacity(slots.len());
for slot in &slots {
if let Some((_, first)) = seen.iter().find(|(i, _)| *i == slot.index) {
return Err(syn::Error::new(
slot.ident.span(),
format!(
"component {} is already mapped by field `{first}`",
slot.index
),
));
}
seen.push((slot.index, slot.ident));
}
Ok(slots)
}
fn impl_composite_deserialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
let name = &input.ident;
let slots = composite_slots(input)?;
let assignments = slots.iter().map(|slot| {
let CompositeSlot {
ident,
ty,
index,
optional,
} = slot;
let idx = *index as usize;
if *optional {
quote! {
#ident: match __composite.get(#idx) {
Some(v) if !v.is_empty() => Some(::std::string::String::from(v)),
_ => None,
},
}
} else {
let build = if is_option_type(ty) {
quote! { Some(::std::string::String::from(__value)) }
} else {
quote! { ::std::string::String::from(__value) }
};
quote! {
#ident: {
let __value = __composite
.get(#idx)
.filter(|v| !v.is_empty())
.ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredComponent {
tag: ::std::string::String::from(stringify!(#name)),
element_index: 0,
component_index: #idx,
})?;
#build
},
}
}
});
Ok(quote! {
impl ::edifact_rs::EdifactCompositeDeserialize for #name {
fn edifact_deserialize_composite(
__composite: ::edifact_rs::CompositeElement<'_>,
) -> ::core::result::Result<Self, ::edifact_rs::EdifactError> {
::core::result::Result::Ok(Self {
#(#assignments)*
})
}
}
})
}
fn impl_composite_serialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
let name = &input.ident;
let slots = composite_slots(input)?;
let mut ordered: Vec<&CompositeSlot<'_>> = slots.iter().collect();
ordered.sort_by_key(|slot| slot.index);
let highest = ordered.last().map_or(0, |slot| slot.index);
let emits = (0..=highest).map(|index| {
let value = match ordered.iter().find(|slot| slot.index == index) {
Some(slot) => {
let ident = slot.ident;
if is_option_type(slot.ty) {
quote! { self.#ident.as_deref().unwrap_or("") }
} else {
quote! { self.#ident.as_str() }
}
}
None => quote! { "" },
};
if index == 0 {
quote! { __emitter.emit(::edifact_rs::EdifactEvent::Element { value: #value })?; }
} else {
quote! {
__emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: #value })?;
}
}
});
let body = if slots.is_empty() {
quote! { __emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?; }
} else {
quote! { #(#emits)* }
};
Ok(quote! {
impl ::edifact_rs::EdifactCompositeSerialize for #name {
fn edifact_serialize_composite<__E: ::edifact_rs::EventEmitter>(
&self,
__emitter: &mut __E,
) -> ::core::result::Result<(), ::edifact_rs::EdifactError> {
#body
::core::result::Result::Ok(())
}
}
})
}
fn get_named_fields(input: &DeriveInput) -> syn::Result<&syn::FieldsNamed> {
if !input.generics.params.is_empty() {
return Err(syn::Error::new(
input.generics.params.span(),
"EdifactSerialize/EdifactDeserialize do not support generic structs",
));
}
match &input.data {
Data::Struct(s) => match &s.fields {
Fields::Named(f) => Ok(f),
_ => Err(syn::Error::new(
input.span(),
"EdifactSerialize/EdifactDeserialize only support structs with named fields",
)),
},
_ => Err(syn::Error::new(
input.span(),
"EdifactSerialize/EdifactDeserialize only support structs",
)),
}
}
fn validate_field_attrs(
ident: &syn::Ident,
ty: &Type,
attrs: &FieldAttrs,
is_segment_struct: bool,
) -> syn::Result<()> {
if attrs.group && !is_vec_type(ty) {
return Err(syn::Error::new(
attrs.group_span.unwrap_or_else(|| ident.span()),
format!("field `{ident}`: #[edifact(group)] requires Vec<T>"),
));
}
if attrs.group && (attrs.element.is_some() || attrs.component.is_some()) {
return Err(syn::Error::new(
attrs.group_span.unwrap_or_else(|| ident.span()),
format!(
"field `{ident}`: #[edifact(group)] cannot be combined with element/component positioning"
),
));
}
if attrs.composite && attrs.component.is_some() {
return Err(syn::Error::new(
attrs.component_span.unwrap_or_else(|| ident.span()),
format!(
"field `{ident}`: #[edifact(component = ...)] cannot be combined with #[edifact(composite)]"
),
));
}
if attrs.composite && attrs.group {
return Err(syn::Error::new(
attrs.composite_span.unwrap_or_else(|| ident.span()),
format!(
"field `{ident}`: #[edifact(composite)] cannot be combined with #[edifact(group)]"
),
));
}
if is_segment_struct && attrs.group {
return Err(syn::Error::new(
attrs.group_span.unwrap_or_else(|| ident.span()),
format!("field `{ident}`: #[edifact(group)] is only valid on message structs"),
));
}
if !is_segment_struct && (attrs.element.is_some() || attrs.component.is_some()) {
return Err(syn::Error::new(
attrs
.element_span
.or(attrs.component_span)
.unwrap_or_else(|| ident.span()),
format!(
"field `{ident}`: element/component positioning is only valid on segment structs"
),
));
}
if !is_segment_struct && attrs.composite {
return Err(syn::Error::new(
attrs.composite_span.unwrap_or_else(|| ident.span()),
format!("field `{ident}`: #[edifact(composite)] is only valid on segment structs"),
));
}
if is_segment_struct && attrs.qualifier.is_some() {
return Err(syn::Error::new(
attrs.qualifier_span.unwrap_or_else(|| ident.span()),
format!(
"field `{ident}`: #[edifact(qualifier = ...)] is only valid on message struct fields"
),
));
}
if attrs.qualifier.is_some() && attrs.group && !is_vec_type(ty) {
return Err(syn::Error::new(
attrs.qualifier_span.unwrap_or_else(|| ident.span()),
format!("field `{ident}`: qualifier-constrained groups must be Vec<T>"),
));
}
if attrs.required && !is_option_type(ty) {
return Err(syn::Error::new(
attrs.required_span.unwrap_or_else(|| ident.span()),
format!(
"field `{ident}`: #[edifact(required)] only applies to Option<T> fields; \
non-Option fields are always required"
),
));
}
if attrs.required && attrs.composite {
return Err(syn::Error::new(
attrs.required_span.unwrap_or_else(|| ident.span()),
format!(
"field `{ident}`: #[edifact(required)] cannot be combined with \
#[edifact(composite)]; use a non-optional field type to require the \
composite element"
),
));
}
if attrs.required && !is_segment_struct {
return Err(syn::Error::new(
attrs.required_span.unwrap_or_else(|| ident.span()),
format!(
"field `{ident}`: #[edifact(required)] is only valid on segment struct \
element fields; to require a segment in a message struct, use a \
non-optional field type"
),
));
}
Ok(())
}
fn static_element_index(attrs: &FieldAttrs, decl_index: usize) -> u32 {
match &attrs.element {
Some(Position::Index(idx)) => *idx,
_ => decl_index as u32,
}
}
fn impl_serialize_sparse(
name: &syn::Ident,
seg_tag: &str,
struct_attrs: &StructAttrs,
field_data: &[(&syn::Ident, &Type, FieldAttrs)],
slots: &[Slots],
slot_prelude: &TokenStream2,
) -> TokenStream2 {
let mut stmts: Vec<TokenStream2> = Vec::new();
if let Some(qual) = &struct_attrs.qualifier {
stmts.push(quote! {
__parts.push((0usize, 0usize, ::std::borrow::Cow::Borrowed(#qual)));
});
}
for ((ident, ty, attrs), slot) in field_data.iter().zip(slots) {
let (element, component) = (&slot.element, &slot.component);
if attrs.composite {
let serialize_composite = if is_option_type(ty) {
quote! {
if let ::core::option::Option::Some(__v) = &self.#ident {
::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(
__v, &mut __sub,
)?;
}
}
} else {
quote! {
::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(
&self.#ident, &mut __sub,
)?;
}
};
stmts.push(quote! {
{
let mut __sub = ::edifact_rs::VecEmitter::default();
#serialize_composite
let mut __comp = 0usize;
let mut __any = false;
for __event in __sub.events {
match __event {
::edifact_rs::OwnedEdifactEvent::Element { value } => {
__comp = 0;
__any = true;
__parts.push((#element, 0usize, ::std::borrow::Cow::Owned(value)));
}
::edifact_rs::OwnedEdifactEvent::ComponentElement { value } => {
__comp += 1;
__any = true;
__parts.push((#element, __comp, ::std::borrow::Cow::Owned(value)));
}
_ => {}
}
}
if !__any {
__parts.push((#element, 0usize, ::std::borrow::Cow::Borrowed("")));
}
}
});
continue;
}
let value_expr = if is_option_type(ty) {
let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
if inner_is_str {
quote! {
match &self.#ident {
::core::option::Option::Some(__v) => ::std::borrow::Cow::Borrowed(__v.as_str()),
::core::option::Option::None => ::std::borrow::Cow::Borrowed(""),
}
}
} else {
quote! {
match &self.#ident {
::core::option::Option::Some(__v) => {
::std::borrow::Cow::Owned(::std::string::ToString::to_string(__v))
}
::core::option::Option::None => ::std::borrow::Cow::Borrowed(""),
}
}
}
} else if is_string_type(ty) {
quote! { ::std::borrow::Cow::Borrowed(self.#ident.as_str()) }
} else if is_str_ref_type(ty) {
quote! { ::std::borrow::Cow::Borrowed(self.#ident) }
} else {
quote! { ::std::borrow::Cow::Owned(::std::string::ToString::to_string(&self.#ident)) }
};
stmts.push(quote! {
__parts.push((#element, #component, #value_expr));
});
}
let capacity = field_data.len() + usize::from(struct_attrs.qualifier.is_some());
quote! {
impl ::edifact_rs::EdifactSerialize for #name {
fn edifact_serialize<__E: ::edifact_rs::EventEmitter>(
&self,
emitter: &mut __E,
) -> ::core::result::Result<(), ::edifact_rs::EdifactError> {
#slot_prelude
let mut __parts: ::std::vec::Vec<(
usize,
usize,
::std::borrow::Cow<'_, str>,
)> = ::std::vec::Vec::with_capacity(#capacity);
#(#stmts)*
::edifact_rs::emit_sparse_segment(emitter, #seg_tag, &mut __parts)
}
}
}
}
fn impl_serialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
let name = &input.ident;
let struct_attrs = parse_struct_attrs(input)?;
let fields = get_named_fields(input)?;
let is_segment_struct = struct_attrs.segment.is_some();
let field_data: Vec<(&syn::Ident, &Type, FieldAttrs)> = fields
.named
.iter()
.map(|f| {
let attrs = parse_field_attrs(f)?;
let ident = f
.ident
.as_ref()
.ok_or_else(|| syn::Error::new_spanned(f, "only named fields are supported"))?;
validate_field_attrs(ident, &f.ty, &attrs, is_segment_struct)?;
Ok((ident, &f.ty, attrs))
})
.collect::<syn::Result<_>>()?;
check_duplicate_slots(&field_data, is_segment_struct)?;
let (slot_prelude, slots) = resolve_slots(&struct_attrs, &field_data)?;
let uses_code_slots = field_data
.iter()
.any(|(_, _, attrs)| matches!(attrs.element, Some(Position::Code(_))));
let body = if let Some(seg_tag) = &struct_attrs.segment {
if uses_code_slots {
return Ok(impl_serialize_sparse(
name,
seg_tag,
&struct_attrs,
&field_data,
&slots,
&slot_prelude,
));
}
enum Cell {
Qualifier,
Field(usize),
}
let mut grid: std::collections::BTreeMap<u32, std::collections::BTreeMap<u32, Cell>> =
std::collections::BTreeMap::new();
if struct_attrs.qualifier.is_some() {
for (i, (ident, _, attrs)) in field_data.iter().enumerate() {
let elem = static_element_index(attrs, i);
let comp = attrs.component.unwrap_or(0);
if elem == 0 && comp == 0 {
return Err(syn::Error::new(
attrs
.element_span
.or(attrs.component_span)
.unwrap_or_else(|| ident.span()),
format!(
"field `{ident}`: cannot use #[edifact(qualifier = ...)] with a field at element = 0 without component >= 1; the qualifier occupies component 0"
),
));
}
}
grid.entry(0).or_default().insert(0, Cell::Qualifier);
}
for (i, (_, _, attrs)) in field_data.iter().enumerate() {
if attrs.group {
continue;
}
let element = static_element_index(attrs, i);
let component = attrs.component.unwrap_or(0);
grid.entry(element)
.or_default()
.insert(component, Cell::Field(i));
}
let empty_element = quote! {
emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
};
let empty_component = quote! {
emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: "" })?;
};
let mut elem_stmts: Vec<TokenStream2> = Vec::new();
if let Some(&max_element) = grid.keys().max() {
for element in 0..=max_element {
let Some(components) = grid.get(&element) else {
elem_stmts.push(empty_element.clone());
continue;
};
let max_component = components.keys().max().copied().unwrap_or(0);
for component in 0..=max_component {
match components.get(&component) {
Some(Cell::Qualifier) => {
let qual = struct_attrs.qualifier.as_deref().unwrap_or("");
elem_stmts.push(quote! {
emitter.emit(::edifact_rs::EdifactEvent::Element { value: #qual })?;
});
}
Some(Cell::Field(index)) => {
let (ident, ty, attrs) = &field_data[*index];
if attrs.composite {
elem_stmts.push(emit_composite_field(ident, ty));
} else if component == 0 {
elem_stmts.push(emit_element(ident, ty));
} else {
elem_stmts.push(emit_component_element(ident, ty));
}
}
None if component == 0 => elem_stmts.push(empty_element.clone()),
None => elem_stmts.push(empty_component.clone()),
}
}
}
}
quote! {
emitter.emit(::edifact_rs::EdifactEvent::StartSegment { tag: #seg_tag })?;
#(#elem_stmts)*
emitter.emit(::edifact_rs::EdifactEvent::EndSegment)?;
}
} else {
let stmts: Vec<TokenStream2> = field_data
.iter()
.map(|(ident, ty, attrs)| {
if attrs.group || is_vec_type(ty) {
quote! {
for __item in &self.#ident {
::edifact_rs::EdifactSerialize::edifact_serialize(__item, emitter)?;
}
}
} else {
quote! {
::edifact_rs::EdifactSerialize::edifact_serialize(&self.#ident, emitter)?;
}
}
})
.collect();
quote! { #(#stmts)* }
};
Ok(quote! {
impl ::edifact_rs::EdifactSerialize for #name {
fn edifact_serialize<__E: ::edifact_rs::EventEmitter>(
&self,
emitter: &mut __E,
) -> ::core::result::Result<(), ::edifact_rs::EdifactError> {
#body
::core::result::Result::Ok(())
}
}
})
}
fn emit_element(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
if is_option_type(ty) {
let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
if inner_is_str {
quote! {
match &self.#ident {
::core::option::Option::Some(__v) => {
emitter.emit(::edifact_rs::EdifactEvent::Element { value: __v.as_str() })?;
}
::core::option::Option::None => {
emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
}
}
}
} else {
quote! {
match &self.#ident {
::core::option::Option::Some(__v) => {
let __s = ::std::string::ToString::to_string(__v);
emitter.emit(::edifact_rs::EdifactEvent::Element { value: &__s })?;
}
::core::option::Option::None => {
emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
}
}
}
}
} else if is_string_type(ty) {
quote! {
emitter.emit(::edifact_rs::EdifactEvent::Element { value: self.#ident.as_str() })?;
}
} else if is_str_ref_type(ty) {
quote! {
emitter.emit(::edifact_rs::EdifactEvent::Element { value: self.#ident })?;
}
} else {
quote! {
{
let __s = ::std::string::ToString::to_string(&self.#ident);
emitter.emit(::edifact_rs::EdifactEvent::Element { value: &__s })?;
}
}
}
}
fn emit_component_element(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
if is_option_type(ty) {
let inner_is_str = option_inner_type(ty).is_some_and(is_str_like);
if inner_is_str {
quote! {
match &self.#ident {
::core::option::Option::Some(__v) => {
emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: __v.as_str() })?;
}
::core::option::Option::None => {
emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: "" })?;
}
}
}
} else {
quote! {
match &self.#ident {
::core::option::Option::Some(__v) => {
let __s = ::std::string::ToString::to_string(__v);
emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: &__s })?;
}
::core::option::Option::None => {
emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: "" })?;
}
}
}
}
} else if is_string_type(ty) {
quote! {
emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: self.#ident.as_str() })?;
}
} else if is_str_ref_type(ty) {
quote! {
emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: self.#ident })?;
}
} else {
quote! {
{
let __s = ::std::string::ToString::to_string(&self.#ident);
emitter.emit(::edifact_rs::EdifactEvent::ComponentElement { value: &__s })?;
}
}
}
}
fn emit_composite_field(ident: &syn::Ident, ty: &Type) -> TokenStream2 {
if is_option_type(ty) {
quote! {
match &self.#ident {
::core::option::Option::Some(__v) => {
::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(__v, emitter)?;
}
::core::option::Option::None => {
emitter.emit(::edifact_rs::EdifactEvent::Element { value: "" })?;
}
}
}
} else {
quote! {
::edifact_rs::EdifactCompositeSerialize::edifact_serialize_composite(&self.#ident, emitter)?;
}
}
}
fn impl_deserialize(input: &DeriveInput) -> syn::Result<TokenStream2> {
let name = &input.ident;
let struct_attrs = parse_struct_attrs(input)?;
let fields = get_named_fields(input)?;
let is_segment_struct = struct_attrs.segment.is_some();
let field_data: Vec<(&syn::Ident, &Type, FieldAttrs)> = fields
.named
.iter()
.map(|f| {
let attrs = parse_field_attrs(f)?;
let ident = f
.ident
.as_ref()
.ok_or_else(|| syn::Error::new_spanned(f, "only named fields are supported"))?;
validate_field_attrs(ident, &f.ty, &attrs, is_segment_struct)?;
Ok((ident, &f.ty, attrs))
})
.collect::<syn::Result<_>>()?;
check_duplicate_slots(&field_data, is_segment_struct)?;
let (slot_prelude, slots) = resolve_slots(&struct_attrs, &field_data)?;
let field_names: Vec<&syn::Ident> = field_data.iter().map(|(id, _, _)| *id).collect();
let (body, owned_body, segment_tag_impl) = if let Some(seg_tag) = &struct_attrs.segment {
let qualifier_guard = if let Some(qual) = &struct_attrs.qualifier {
quote! {
if __seg.element_str(0).unwrap_or("") != #qual {
return ::core::result::Result::Err(
::edifact_rs::EdifactError::MissingRequiredElement {
tag: #seg_tag.to_owned(),
element_index: 0,
}
);
}
}
} else if let Some(idx) = struct_attrs.qualifier_from {
quote! {
match __seg.element_str(#idx as usize) {
::core::option::Option::None => return ::core::result::Result::Err(
::edifact_rs::EdifactError::MissingRequiredElement {
tag: #seg_tag.to_owned(),
element_index: #idx as usize,
}
),
::core::option::Option::Some("") => return ::core::result::Result::Err(
::edifact_rs::EdifactError::InvalidFieldValue {
tag: #seg_tag.to_owned(),
element_index: #idx as usize,
value: ::std::string::String::new(),
}
),
::core::option::Option::Some(__qual_val) => { let _ = __qual_val; }
}
}
} else {
quote! {}
};
let find_seg = if let Some(qual) = &struct_attrs.qualifier {
quote! {
::edifact_rs::find_qualified_segment(segments, #seg_tag, #qual)
}
} else {
quote! {
::edifact_rs::find_segment(segments, #seg_tag)
}
};
let field_inits: Vec<TokenStream2> = field_data
.iter()
.zip(slots.iter())
.map(|((ident, ty, attrs), slot)| -> syn::Result<TokenStream2> {
let idx = &slot.element;
if attrs.composite {
if is_option_type(ty) {
let inner_ty = option_inner_type(ty)
.ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
return Ok(quote! {
let #ident = match ::edifact_rs::composite_element(__seg, #idx) {
::core::option::Option::Some(__composite) => {
::core::option::Option::Some(
<#inner_ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(__composite)?
)
}
::core::option::Option::None => ::core::option::Option::None,
};
});
}
return Ok(quote! {
let #ident = <#ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
::edifact_rs::composite_element(__seg, #idx).ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
tag: #seg_tag.to_owned(),
element_index: #idx,
})?
)?;
});
}
let comp = &slot.component;
let value_expr = if slot.has_component {
quote! {
__seg.get_element(#idx).and_then(|__e| __e.get_component(#comp))
}
} else {
quote! { __seg.element_str(#idx) }
};
let names_component = &slot.names_component;
let missing_required_err = quote! {
if #names_component {
::edifact_rs::EdifactError::MissingRequiredComponent {
tag: #seg_tag.to_owned(),
element_index: #idx,
component_index: #comp,
}
} else {
::edifact_rs::EdifactError::MissingRequiredElement {
tag: #seg_tag.to_owned(),
element_index: #idx,
}
}
};
Ok(if is_option_type(ty) {
let inner_ty = option_inner_type(ty);
let inner_is_str = inner_ty.is_some_and(is_str_like);
if attrs.required {
if inner_is_str {
quote! {
let #ident = ::core::option::Option::Some(
#value_expr
.filter(|__s| !__s.is_empty())
.ok_or_else(|| #missing_required_err)?
.to_owned()
);
}
} else {
let inner_ty = inner_ty
.ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
quote! {
let #ident = ::core::option::Option::Some(
#value_expr
.filter(|__s| !__s.is_empty())
.ok_or_else(|| #missing_required_err)?
.parse::<#inner_ty>()
.map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?
);
}
}
} else if inner_is_str {
quote! {
let #ident = #value_expr
.filter(|__s| !__s.is_empty())
.map(::std::string::String::from);
}
} else {
let inner_ty = inner_ty
.ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
quote! {
let #ident = #value_expr
.filter(|__s| !__s.is_empty())
.map(|__s| __s.parse::<#inner_ty>()
.map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })
)
.transpose()?;
}
}
} else if is_str_like(ty) {
quote! {
let #ident = #value_expr
.filter(|__s| !__s.is_empty())
.ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
tag: #seg_tag.to_owned(),
element_index: #idx,
})?
.to_owned();
}
} else {
quote! {
let #ident = #value_expr
.filter(|__s| !__s.is_empty())
.ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
tag: #seg_tag.to_owned(),
element_index: #idx,
})?
.parse::<#ty>()
.map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?;
}
})
})
.collect::<syn::Result<_>>()?;
let body = quote! {
#slot_prelude
let __seg = #find_seg
.ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
tag: #seg_tag.to_owned(),
expected_position: "message body".to_owned(),
})?;
#qualifier_guard
#(#field_inits)*
::core::result::Result::Ok(Self { #(#field_names),* })
};
let qualifier_match = if let Some(qual) = &struct_attrs.qualifier {
quote! {
const QUALIFIER_PATTERN: ::core::option::Option<&'static str> =
::core::option::Option::Some(#qual);
}
} else if let Some(idx) = struct_attrs.qualifier_from {
quote! {
fn matches_segment(seg: &::edifact_rs::Segment<'_>) -> bool {
seg.tag == Self::SEGMENT_TAG
&& !seg.element_str(#idx as usize).unwrap_or("").is_empty()
}
fn matches_owned_segment(seg: &::edifact_rs::OwnedSegment) -> bool {
seg.tag == Self::SEGMENT_TAG
&& !seg
.elements
.get(#idx as usize)
.and_then(|e| e.components.first())
.map(|(c, _)| c.as_str())
.unwrap_or("")
.is_empty()
}
}
} else {
quote! {}
};
let seg_tag_impl = quote! {
impl ::edifact_rs::EdifactSegmentTag for #name {
const SEGMENT_TAG: &'static str = #seg_tag;
#qualifier_match
}
};
let find_seg_owned = if let Some(qual) = &struct_attrs.qualifier {
quote! {
::edifact_rs::find_qualified_segment_owned(segments, #seg_tag, #qual)
}
} else {
quote! {
::edifact_rs::find_segment_owned(segments, #seg_tag)
}
};
let field_inits_owned: Vec<TokenStream2> = field_data
.iter()
.zip(slots.iter())
.map(|((ident, ty, attrs), slot)| -> syn::Result<TokenStream2> {
let idx = &slot.element;
if attrs.composite {
if is_option_type(ty) {
let inner_ty = option_inner_type(ty)
.ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
return Ok(quote! {
let #ident = match __seg.elements.get(#idx) {
::core::option::Option::Some(__e) => {
let __cows = __e.components.iter()
.map(|(s, _)| ::std::borrow::Cow::Borrowed(s.as_str()))
.collect::<::std::vec::Vec<::std::borrow::Cow<'_, str>>>();
::core::option::Option::Some(
<#inner_ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
::edifact_rs::CompositeElement::from_slice(&__cows)
)?
)
}
::core::option::Option::None => ::core::option::Option::None,
};
});
}
return Ok(quote! {
let #ident = {
let __cows = __seg.elements.get(#idx)
.ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
tag: #seg_tag.to_owned(),
element_index: #idx,
})?
.components.iter()
.map(|(s, _)| ::std::borrow::Cow::Borrowed(s.as_str()))
.collect::<::std::vec::Vec<::std::borrow::Cow<'_, str>>>();
<#ty as ::edifact_rs::EdifactCompositeDeserialize>::edifact_deserialize_composite(
::edifact_rs::CompositeElement::from_slice(&__cows)
)?
};
});
}
let comp = &slot.component;
let value_expr_owned = if slot.has_component {
quote! { __seg.component_str(#idx, #comp) }
} else {
quote! { __seg.element_str(#idx) }
};
let names_component = &slot.names_component;
let missing_required_err_owned = quote! {
if #names_component {
::edifact_rs::EdifactError::MissingRequiredComponent {
tag: #seg_tag.to_owned(),
element_index: #idx,
component_index: #comp,
}
} else {
::edifact_rs::EdifactError::MissingRequiredElement {
tag: #seg_tag.to_owned(),
element_index: #idx,
}
}
};
Ok(if is_option_type(ty) {
if let Some(inner_ty) = option_inner_type(ty) {
if attrs.required {
if is_str_like(inner_ty) {
quote! {
let #ident = ::core::option::Option::Some(
#value_expr_owned
.filter(|__s| !__s.is_empty())
.ok_or_else(|| #missing_required_err_owned)?
.to_owned()
);
}
} else {
quote! {
let #ident = ::core::option::Option::Some(
#value_expr_owned
.filter(|__s| !__s.is_empty())
.ok_or_else(|| #missing_required_err_owned)?
.parse::<#inner_ty>()
.map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?
);
}
}
} else if is_str_like(inner_ty) {
quote! {
let #ident = #value_expr_owned
.filter(|__s| !__s.is_empty())
.map(::std::string::String::from);
}
} else {
quote! {
let #ident = #value_expr_owned
.filter(|__s| !__s.is_empty())
.map(|__s| __s.parse::<#inner_ty>()
.map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })
)
.transpose()?;
}
}
} else {
quote! {
let #ident = #value_expr_owned
.filter(|__s| !__s.is_empty())
.map(::std::string::String::from);
}
}
} else if is_str_like(ty) {
quote! {
let #ident = #value_expr_owned
.filter(|__s| !__s.is_empty())
.ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
tag: #seg_tag.to_owned(),
element_index: #idx,
})?
.to_owned();
}
} else {
quote! {
let #ident = #value_expr_owned
.filter(|__s| !__s.is_empty())
.ok_or_else(|| ::edifact_rs::EdifactError::MissingRequiredElement {
tag: #seg_tag.to_owned(),
element_index: #idx,
})?
.parse::<#ty>()
.map_err(|_| ::edifact_rs::EdifactError::InvalidText { offset: __seg.span.start })?;
}
})
})
.collect::<syn::Result<_>>()?;
let owned_body = quote! {
#slot_prelude
let __seg = #find_seg_owned
.ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
tag: #seg_tag.to_owned(),
expected_position: "message body".to_owned(),
})?;
#qualifier_guard
#(#field_inits_owned)*
::core::result::Result::Ok(Self { #(#field_names),* })
};
(body, owned_body, seg_tag_impl)
} else {
let field_inits: Vec<TokenStream2> = field_data
.iter()
.map(|(ident, ty, attrs)| -> syn::Result<TokenStream2> {
Ok(if let Some(qual) = &attrs.qualifier {
if attrs.group || is_vec_type(ty) {
let inner_ty = vec_inner_type(ty)
.ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
quote! {
let #ident = segments
.iter()
.filter(|__seg| {
__seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG
&& __seg.element_str(0).unwrap_or("") == #qual
})
.map(|__seg| {
::edifact_rs::EdifactDeserialize::edifact_deserialize(
::core::slice::from_ref(__seg),
)
})
.collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, ::edifact_rs::EdifactError>>()?;
}
} else if is_option_type(ty) {
let inner_ty = option_inner_type(ty)
.ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
quote! {
let #ident = match ::edifact_rs::find_qualified_segment(
segments,
<#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
#qual,
) {
::core::option::Option::Some(__seg) => {
::core::option::Option::Some(
::edifact_rs::EdifactDeserialize::edifact_deserialize(
::core::slice::from_ref(__seg),
)?
)
}
::core::option::Option::None => ::core::option::Option::None,
};
}
} else {
quote! {
let __seg = ::edifact_rs::find_qualified_segment(
segments,
<#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
#qual,
)
.ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
tag: <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG.to_owned(),
expected_position: "message body".to_owned(),
})?;
let #ident = ::edifact_rs::EdifactDeserialize::edifact_deserialize(
::core::slice::from_ref(__seg),
)?;
}
}
} else if attrs.group || is_vec_type(ty) {
let inner_ty = vec_inner_type(ty)
.ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
quote! {
let #ident = ::edifact_rs::find_segments_typed::<#inner_ty>(segments)
.map(|__seg| {
<#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize(
::core::slice::from_ref(__seg),
)
})
.collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, _>>()?;
}
} else if is_option_type(ty) {
let inner_ty = option_inner_type(ty)
.ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
quote! {
let #ident = if segments
.iter()
.any(|__seg| __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG)
{
::core::option::Option::Some(
<#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize(segments)?
)
} else {
::core::option::Option::None
};
}
} else {
quote! {
let #ident = ::edifact_rs::EdifactDeserialize::edifact_deserialize(segments)?;
}
})
})
.collect::<syn::Result<_>>()?;
let body = quote! {
#(#field_inits)*
::core::result::Result::Ok(Self { #(#field_names),* })
};
let field_inits_owned: Vec<TokenStream2> = field_data
.iter()
.map(|(ident, ty, attrs)| -> syn::Result<TokenStream2> {
Ok(if let Some(qual) = &attrs.qualifier {
if attrs.group || is_vec_type(ty) {
let inner_ty = vec_inner_type(ty)
.ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
quote! {
let #ident = segments
.iter()
.filter(|__seg| {
__seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG
&& __seg.element_str(0).unwrap_or("") == #qual
})
.map(|__seg| {
<#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
::core::slice::from_ref(__seg),
)
})
.collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, ::edifact_rs::EdifactError>>()?;
}
} else if is_option_type(ty) {
let inner_ty = option_inner_type(ty)
.ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
quote! {
let #ident = match ::edifact_rs::find_qualified_segment_owned(
segments,
<#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
#qual,
) {
::core::option::Option::Some(__seg) => {
::core::option::Option::Some(
<#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
::core::slice::from_ref(__seg),
)?
)
}
::core::option::Option::None => ::core::option::Option::None,
};
}
} else {
quote! {
let __seg = ::edifact_rs::find_qualified_segment_owned(
segments,
<#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG,
#qual,
)
.ok_or_else(|| ::edifact_rs::EdifactError::MissingSegment {
tag: <#ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG.to_owned(),
expected_position: "message body".to_owned(),
})?;
let #ident = <#ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
::core::slice::from_ref(__seg),
)?;
}
}
} else if attrs.group || is_vec_type(ty) {
let inner_ty = vec_inner_type(ty)
.ok_or_else(|| syn::Error::new(ident.span(), "expected Vec<T>"))?;
quote! {
let #ident = segments
.iter()
.filter(|__seg| <#inner_ty as ::edifact_rs::EdifactSegmentTag>::matches_owned_segment(__seg))
.map(|__seg| {
<#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(
::core::slice::from_ref(__seg),
)
})
.collect::<::core::result::Result<::std::vec::Vec<#inner_ty>, _>>()?;
}
} else if is_option_type(ty) {
let inner_ty = option_inner_type(ty)
.ok_or_else(|| syn::Error::new(ident.span(), "expected Option<T>"))?;
quote! {
let #ident = if segments
.iter()
.any(|__seg| __seg.tag == <#inner_ty as ::edifact_rs::EdifactSegmentTag>::SEGMENT_TAG)
{
::core::option::Option::Some(
<#inner_ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(segments)?
)
} else {
::core::option::Option::None
};
}
} else {
quote! {
let #ident = <#ty as ::edifact_rs::EdifactDeserialize>::edifact_deserialize_owned(segments)?;
}
})
})
.collect::<syn::Result<_>>()?;
let owned_body = quote! {
#(#field_inits_owned)*
::core::result::Result::Ok(Self { #(#field_names),* })
};
(body, owned_body, quote! {})
};
Ok(quote! {
impl ::edifact_rs::EdifactDeserialize for #name {
fn edifact_deserialize(
segments: &[::edifact_rs::Segment<'_>],
) -> ::core::result::Result<Self, ::edifact_rs::EdifactError> {
#body
}
fn edifact_deserialize_owned(
segments: &[::edifact_rs::OwnedSegment],
) -> ::core::result::Result<Self, ::edifact_rs::EdifactError> {
#owned_body
}
}
#segment_tag_impl
})
}
#[cfg(test)]
mod tests {
#[test]
fn trybuild_ui() {
if std::env::var_os("EDIFACT_UI_TESTS").is_none() {
eprintln!(
"skipping derive UI suite: set EDIFACT_UI_TESTS=1 to run it \
(expectations are pinned to the MSRV toolchain)"
);
return;
}
let t = trybuild::TestCases::new();
t.pass("tests/ui/pass_*.rs");
t.compile_fail("tests/ui/fail_*.rs");
}
}