#![warn(missing_docs)]
use proc_macro_crate::{FoundCrate, crate_name};
use proc_macro2::Span;
use quote::{ToTokens, quote};
use syn::{
Data, DeriveInput, Error, Field, Fields, GenericParam, Generics, Index, parse_macro_input,
parse_quote,
};
fn add_trait_bounds(mut generics: Generics) -> Generics {
for param in &mut generics.params {
if let GenericParam::Type(ref mut type_param) = *param {
type_param.bounds.push(parse_quote!(::std::fmt::Debug));
}
}
generics
}
#[proc_macro_derive(ApproxEq)]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let DeriveInput {
ident,
data,
generics,
..
} = parse_macro_input!(input);
let lox_test_utils = match crate_name("lox-test-utils") {
Ok(FoundCrate::Itself) => quote!(crate),
Ok(FoundCrate::Name(name)) => {
let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
quote!(::#ident)
}
Err(_) => quote!(::lox_test_utils),
};
let fields: Vec<proc_macro2::TokenStream> = match data {
Data::Struct(data) => match data.fields {
Fields::Named(fields) => fields
.named
.into_iter()
.map(|f| {
let f = f.ident.expect("named field must have an identifier");
quote! {#f}
})
.collect::<Vec<proc_macro2::TokenStream>>(),
Fields::Unnamed(fields) => fields
.unnamed
.into_iter()
.enumerate()
.map(|(idx, _)| {
let idx = Index::from(idx);
quote! {#idx}
})
.collect(),
Fields::Unit => {
return Error::new(ident.span(), "unit structs are not supported")
.into_compile_error()
.into();
}
},
_ => {
return Error::new(ident.span(), format!("{} is not a struct", ident))
.into_compile_error()
.into();
}
}
.iter()
.map(|f| {
quote! {
results.merge(stringify!(#f).to_string(), self.#f.approx_eq(&rhs.#f, atol, rtol));
}
})
.collect();
let generics = add_trait_bounds(generics);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let results = quote! {#lox_test_utils::approx_eq::results::ApproxEqResults};
let output = quote! {
impl #impl_generics #lox_test_utils::approx_eq::ApproxEq for #ident #ty_generics #where_clause {
fn approx_eq(&self, rhs: &Self, atol: f64, rtol: f64) -> #results {
let mut results = #results::new();
#(#fields)*
results
}
}
};
output.into()
}
fn generate_call_to_deserializer_for_covariance_matrix_kvn_type(
expected_kvn_name: &str,
) -> proc_macro2::TokenStream {
quote! {
match crate::ndm::kvn::parser::get_next_nonempty_line(lines) {
None => Err(crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedEndOfInput {
keyword: #expected_kvn_name.to_string()
}),
Some(next_line) => {
let result = crate::ndm::kvn::parser::parse_kvn_covariance_matrix(
lines,
).map_err(|x| match crate::ndm::kvn::KvnDeserializerErr::from(x) {
crate::ndm::kvn::KvnDeserializerErr::InvalidCovarianceMatrixFormat { .. } => crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedKeyword {
found: "".to_string(),
expected: "".to_string(),
},
crate::ndm::kvn::KvnDeserializerErr::UnexpectedEndOfInput { keyword } => crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedEndOfInput {
keyword
},
e => e,
})?;
Ok(result)
}
}
}
}
fn generate_call_to_deserializer_for_kvn_type(
type_name: &str,
type_name_new: &syn::Path,
expected_kvn_name: &str,
with_keyword_matching: bool,
unpack_value: bool,
) -> Result<proc_macro2::TokenStream, proc_macro::TokenStream> {
let unpack_insert = if unpack_value {
quote! { .value }
} else {
quote! {}
};
match type_name {
"String" | "f64" | "i32" | "u64" | "NonNegativeDouble" | "NegativeDouble"
| "PositiveDouble" => {
let parser = match type_name {
"String" => quote! {
crate::ndm::kvn::parser::parse_kvn_string_line(
next_line
).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x))?
},
"f64" | "NonNegativeDouble" | "NegativeDouble" | "PositiveDouble" => quote! {
crate::ndm::kvn::parser::parse_kvn_numeric_line(
next_line,
true, ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x))?
},
"i32" | "u64" => quote! {
crate::ndm::kvn::parser::parse_kvn_integer_line(
next_line,
true, ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x))?
},
_ => unreachable!(),
};
if with_keyword_matching {
Ok(quote! {
match crate::ndm::kvn::parser::get_next_nonempty_line(lines) {
None => Err(crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedEndOfInput {
keyword: #expected_kvn_name.to_string()
}),
Some(next_line) => {
let line_matches = crate::ndm::kvn::parser::kvn_line_matches_key(
#expected_kvn_name,
next_line,
)?;
let result = if line_matches {
let next_line = lines.next().expect("line must exist after successful peek");
Ok(#parser #unpack_insert)
} else {
Err(crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedKeyword {
found: next_line.to_string(),
expected: #expected_kvn_name.to_string(),
})
};
result
}
}
})
} else {
Ok(quote! {
{
let next_line = lines.next().expect("line must exist after successful peek");
#parser #unpack_insert
}
})
}
}
"common::StateVectorAccType" => Ok(quote! {
match crate::ndm::kvn::parser::get_next_nonempty_line(lines) {
None => Err(crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedEndOfInput {
keyword: #expected_kvn_name.to_string()
}),
Some(next_line) => {
let result = crate::ndm::kvn::parser::parse_kvn_state_vector(
next_line,
).map_err(|x| match crate::ndm::kvn::KvnDeserializerErr::from(x) {
crate::ndm::kvn::KvnDeserializerErr::InvalidStateVectorFormat { .. } => crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedKeyword {
found: "".to_string(),
expected: "".to_string(),
},
e => e,
}).map(|x| x.into());
if result.is_ok() {
let _ = lines.next().expect("line must exist after successful peek");
}
result
}
}
}),
_ => Ok(quote! {
{
let has_next_line = crate::ndm::kvn::parser::get_next_nonempty_line(lines).is_some();
let result = if has_next_line {
#type_name_new::deserialize(lines)
} else {
Err(crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedEndOfInput {
keyword: #expected_kvn_name.to_string()
})
};
result
}
}),
}
}
fn get_generic_type_argument(field: &Field) -> Option<(String, &syn::Path)> {
if let syn::Type::Path(type_path) = &field.ty {
let path_part = type_path.path.segments.first();
if let Some(path_part) = path_part
&& let syn::PathArguments::AngleBracketed(type_argument) = &path_part.arguments
&& let Some(syn::GenericArgument::Type(syn::Type::Path(r#type))) =
&type_argument.args.first()
{
return Some((
r#type
.path
.segments
.clone()
.into_iter()
.map(|ident| ident.to_token_stream().to_string())
.reduce(|a, b| a + "::" + &b)
.expect("type path must have at least one segment"),
&r#type.path,
));
}
}
None
}
fn generate_call_to_deserializer_for_option_type(
expected_kvn_name: &str,
field: &Field,
) -> Result<proc_macro2::TokenStream, proc_macro::TokenStream> {
let (type_name, type_ident) = get_generic_type_argument(field).ok_or(
syn::Error::new_spanned(field, "Malformed type for `#[derive(KvnDeserialize)]`")
.into_compile_error(),
)?;
let deserializer_for_kvn_type = generate_call_to_deserializer_for_kvn_type(
type_name.as_ref(),
type_ident,
expected_kvn_name,
true,
true,
)?;
let condition_shortcut = match type_name.as_str() {
"String" | "f64" | "i32" | "u64" => quote! {},
_ => quote! { ! #type_ident::should_check_key_match() || },
};
let value = match type_name.as_ref() {
"NonNegativeDouble" | "NegativeDouble" | "PositiveDouble" => {
quote! { #type_ident (item) }
}
_ => quote! { item },
};
Ok(quote! {
match crate::ndm::kvn::parser::get_next_nonempty_line(lines) {
None => None,
Some(next_line) => {
let line_matches = crate::ndm::kvn::parser::kvn_line_matches_key(
#expected_kvn_name,
next_line,
)?;
if #condition_shortcut line_matches {
let result = #deserializer_for_kvn_type;
match result {
Ok(item) => Some(#value),
Err(crate::ndm::kvn::KvnDeserializerErr::UnexpectedKeyword { .. }) |
Err(crate::ndm::kvn::KvnDeserializerErr::UnexpectedEndOfInput { .. }) => None,
Err(e) => Err(e)?,
}
} else {
None
}
}
}
})
}
fn generate_call_to_deserializer_for_vec_type(
expected_kvn_name: &str,
field: &Field,
) -> Result<proc_macro2::TokenStream, proc_macro::TokenStream> {
let (type_name, type_ident) = get_generic_type_argument(field).ok_or(
syn::Error::new_spanned(field, "Malformed type for `#[derive(KvnDeserialize)]`")
.into_compile_error(),
)?;
let expected_kvn_name = expected_kvn_name.trim_end_matches("_LIST");
let deserializer_for_kvn_type = generate_call_to_deserializer_for_kvn_type(
type_name.as_ref(),
type_ident,
expected_kvn_name,
true,
true,
)?;
Ok(quote! {
{
let mut items: Vec<#type_ident> = Vec::new();
let mut is_retry = false;
loop {
let result = #deserializer_for_kvn_type;
match result {
Ok(item) => {
is_retry = false;
items.push(item)
},
Err(crate::ndm::kvn::KvnDeserializerErr::UnexpectedKeyword { .. }) |
Err(crate::ndm::kvn::KvnDeserializerErr::UnexpectedEndOfInput { .. }) => if is_retry {
break;
} else {
is_retry = true;
continue;
},
Err(e) => Err(e)?,
}
}
items
}
})
}
fn get_prefix_and_postfix_keyword(attrs: &[syn::Attribute]) -> Option<(String, String)> {
let mut keyword: Option<syn::LitStr> = None;
for attr in attrs.iter() {
if !attr.path().is_ident("kvn") {
continue;
}
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("prefix_and_postfix_keyword") {
let value = meta.value()?;
keyword = value.parse()?;
Ok(())
} else {
Err(meta.error("unsupported attribute"))
}
});
}
keyword.map(|keyword| {
let keyword = keyword.value().to_uppercase();
(format!("{keyword}_START"), format!("{keyword}_STOP"))
})
}
fn is_value_unit_struct(item: &DeriveInput) -> bool {
item.attrs.iter().any(|attr| {
attr.path().is_ident("kvn")
&& attr
.parse_nested_meta(|meta| {
if meta.path.is_ident("value_unit_struct") {
Ok(())
} else {
Err(meta.error("unsupported attribute"))
}
})
.is_ok()
})
}
fn extract_type_path(ty: &syn::Type) -> Option<&syn::Path> {
match *ty {
syn::Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
_ => None,
}
}
fn handle_version_field(
type_name: &proc_macro2::Ident,
field: &syn::Field,
) -> Result<(proc_macro2::TokenStream, proc_macro2::TokenStream), proc_macro2::TokenStream> {
let string_type_name = type_name.to_string();
if !string_type_name.ends_with("Type") {
return Err(syn::Error::new_spanned(
type_name,
"Types with \"version\" field should be of the form SomethingType",
)
.into_compile_error());
}
let message_type_name = string_type_name
.trim_end_matches("Type")
.to_string()
.to_uppercase();
let field_name = field
.ident
.as_ref()
.expect("named field must have an identifier");
let expected_kvn_name = format!("CCSDS_{message_type_name}_VERS");
let field_type = extract_type_path(&field.ty)
.expect("field must have a valid type path")
.to_token_stream()
.to_string();
let field_type_new = extract_type_path(&field.ty).expect("field must have a valid type path");
let parser = generate_call_to_deserializer_for_kvn_type(
&field_type,
field_type_new,
&expected_kvn_name,
true,
true,
)?;
Ok((
quote! { let #field_name = #parser?; },
quote! { #field_name, },
))
}
fn deserializer_for_struct_with_named_fields(
type_name: &proc_macro2::Ident,
fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
is_value_unit_struct: bool,
struct_level_prefix_and_postfix_keyword: Option<(String, String)>,
) -> proc_macro2::TokenStream {
if &type_name.to_string() == "UserDefinedType" {
return quote! {
Ok(Default::default())
};
}
if is_value_unit_struct {
let mut deserializer = None;
let mut unit_type: Option<String> = None;
let mut unit_field_name_ident: Option<&proc_macro2::Ident> = None;
let mut field_type: Option<String> = None;
let mut field_type_new: Option<&syn::Path> = None;
for (index, field) in fields.iter().enumerate() {
let field_name_ident = field
.ident
.as_ref()
.expect("named field must have an identifier");
let field_name = field_name_ident.to_token_stream().to_string();
match index {
0 => {
if field_name.as_str() != "base" {
return syn::Error::new_spanned(
field,
"The first field in a value unit struct should be called \"base\"",
)
.into_compile_error();
}
let local_field_type = extract_type_path(&field.ty)
.expect("field must have a valid type path")
.to_token_stream()
.to_string();
let local_field_type_new =
extract_type_path(&field.ty).expect("field must have a valid type path");
match local_field_type.as_str() {
"KvnDateTimeValue" | "String" | "f64" | "i32" | "NonNegativeDouble"
| "NegativeDouble" | "PositiveDouble" => {
match generate_call_to_deserializer_for_kvn_type(
&local_field_type,
local_field_type_new,
"undefined",
false,
false,
) {
Ok(deserializer_for_kvn_type) => {
deserializer = Some(deserializer_for_kvn_type)
}
Err(e) => return e.into(),
}
}
_ => {
return syn::Error::new_spanned(
field,
"Unsupported field type for deserializer",
)
.into_compile_error();
}
};
field_type = Some(local_field_type);
field_type_new = Some(local_field_type_new);
}
1 => {
if field_name.as_str() != "units" && field_name.as_str() != "parameter" {
return syn::Error::new_spanned(
field,
"The second field in a value unit struct should be called \"units\" or \"parameter\"",
)
.into_compile_error();
}
unit_type = get_generic_type_argument(field).map(|x| x.0);
unit_field_name_ident = Some(field_name_ident);
}
_ => {
return syn::Error::new_spanned(
field,
"Only two fields are allowed: \"base\" and (\"units\" or \"parameters\"",
)
.into_compile_error();
}
}
}
let unit_type = unit_type.expect("value unit struct must have a unit type field");
let unit_field_name_ident =
unit_field_name_ident.expect("value unit struct must have a unit field name");
let field_type = field_type.expect("value unit struct must have a base field type");
let field_type_new =
field_type_new.expect("value unit struct must have a base field type path");
let unit_type_ident = syn::Ident::new(&unit_type, Span::call_site());
let base = match field_type.as_str() {
"NonNegativeDouble" | "NegativeDouble" | "PositiveDouble" => {
quote! { #field_type_new (kvn_value.value) }
}
_ => quote! { kvn_value.value },
};
match deserializer {
None => syn::Error::new_spanned(fields, "Unable to create deserializer for struct")
.into_compile_error(),
Some(deserializer) => quote! {
let kvn_value = #deserializer;
Ok(#type_name {
base: #base,
#unit_field_name_ident: kvn_value.unit.map(|unit| #unit_type_ident (unit)),
})
},
}
} else {
let field_deserializers: Result<Vec<_>, _> = fields.iter().filter(|field| {
if type_name != "OemCovarianceMatrixType" {
return true
}
let field_name = field.ident.as_ref().expect("named field must have an identifier").to_token_stream().to_string();
!field_name.starts_with("cx")
&& !field_name.starts_with("cy")
&& !field_name.starts_with("cz")
}).map(|field| {
let field_name = field.ident.as_ref().expect("named field must have an identifier");
let expected_kvn_name = field_name.to_token_stream().to_string().to_uppercase();
let field_type_new = extract_type_path(&field.ty)
.expect("field must have a valid type path");
let field_main_type = field_type_new.segments.iter()
.next_back()
.expect("type path must have at least one segment")
.ident
.to_string();
if field_name == "version" {
return handle_version_field(type_name, field);
}
let parser = match field_main_type.as_str() {
"String" | "f64" | "i32" => {
let deserializer_for_kvn_type = generate_call_to_deserializer_for_kvn_type(
&field_main_type,
field_type_new,
&expected_kvn_name,
true,
true,
)?;
quote! {
#deserializer_for_kvn_type?
}
}
"Option" => {
generate_call_to_deserializer_for_option_type(
&expected_kvn_name,
field
)?
}
"Vec" => generate_call_to_deserializer_for_vec_type(&expected_kvn_name, field)?,
_ => {
let condition_shortcut = match field_main_type.as_str() {
"String" => quote! {},
_ => quote! { ! #field_type_new::should_check_key_match() || },
};
quote! {
match crate::ndm::kvn::parser::get_next_nonempty_line(lines) {
None => Err(crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedEndOfInput {
keyword: #expected_kvn_name.to_string()
})?,
Some(next_line) => {
let line_matches = crate::ndm::kvn::parser::kvn_line_matches_key(
#expected_kvn_name,
next_line,
)?;
if #condition_shortcut line_matches {
#field_type_new::deserialize(lines)?
} else {
Err(crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedKeyword {
found: next_line.to_string(),
expected: #expected_kvn_name.to_string(),
})?
}
}
}
}
}
};
let field_prefix_and_postfix_keyword_checks = get_prefix_and_postfix_keyword(&field.attrs);
let wrapped_parser = add_prefix_and_postfix_keyword_checks(field_prefix_and_postfix_keyword_checks, parser, true);
Ok((
quote! { let #field_name = #wrapped_parser; },
quote! { #field_name, }
))
})
.collect();
if let Err(e) = field_deserializers {
return e;
}
let mut field_deserializers =
field_deserializers.expect("field deserializers already checked for errors");
if type_name == "OemCovarianceMatrixType" {
let covariance_matrix_parser =
generate_call_to_deserializer_for_covariance_matrix_kvn_type("COVARIANCE_MATRIX");
field_deserializers.push((
quote! { let covariance_matrix = #covariance_matrix_parser?; },
quote! {},
));
for (field, field_type) in [
("cx_x", "PositionCovarianceType"),
("cy_x", "PositionCovarianceType"),
("cy_y", "PositionCovarianceType"),
("cz_x", "PositionCovarianceType"),
("cz_y", "PositionCovarianceType"),
("cz_z", "PositionCovarianceType"),
("cx_dot_x", "PositionVelocityCovarianceType"),
("cx_dot_y", "PositionVelocityCovarianceType"),
("cx_dot_z", "PositionVelocityCovarianceType"),
("cx_dot_x_dot", "VelocityCovarianceType"),
("cy_dot_x", "PositionVelocityCovarianceType"),
("cy_dot_y", "PositionVelocityCovarianceType"),
("cy_dot_z", "PositionVelocityCovarianceType"),
("cy_dot_x_dot", "VelocityCovarianceType"),
("cy_dot_y_dot", "VelocityCovarianceType"),
("cz_dot_x", "PositionVelocityCovarianceType"),
("cz_dot_y", "PositionVelocityCovarianceType"),
("cz_dot_z", "PositionVelocityCovarianceType"),
("cz_dot_x_dot", "VelocityCovarianceType"),
("cz_dot_y_dot", "VelocityCovarianceType"),
("cz_dot_z_dot", "VelocityCovarianceType"),
] {
let field_ident = syn::Ident::new(field, Span::call_site());
let type_ident = syn::Ident::new(field_type, Span::call_site());
field_deserializers.push((
quote! {},
quote! {
#field_ident: #type_ident {
base: covariance_matrix.#field_ident,
units: None,
},
},
));
}
}
let (field_deserializers, fields): (Vec<_>, Vec<_>) =
field_deserializers.into_iter().unzip();
let parser_to_wrap = quote! {
#(#field_deserializers)*
Ok(#type_name {
#(#fields)*
})
};
let wrapped_parser = add_prefix_and_postfix_keyword_checks(
struct_level_prefix_and_postfix_keyword,
parser_to_wrap,
false,
);
quote! {
#wrapped_parser
}
}
}
fn add_prefix_and_postfix_keyword_checks(
prefix_and_postfix_keyword: Option<(String, String)>,
parser_to_wrap: proc_macro2::TokenStream,
is_field: bool,
) -> proc_macro2::TokenStream {
match prefix_and_postfix_keyword {
None => parser_to_wrap,
Some((prefix_keyword, postfix_keyword)) => {
let mismatch_handler = if is_field {
quote! { Default::default() }
} else {
quote! {
Err(
crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedKeyword {
found: next_line.to_string(),
expected: #prefix_keyword.to_string(),
},
)?
}
};
quote! {
match crate::ndm::kvn::parser::get_next_nonempty_line(lines) {
None => Err(
crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedEndOfInput {
keyword: #prefix_keyword.to_string(),
},
)?,
Some(next_line) => {
let line_matches = crate::ndm::kvn::parser::kvn_line_matches_key(
#prefix_keyword,
next_line,
)?;
if line_matches {
lines.next().expect("line must exist after successful peek");
let result = { #parser_to_wrap };
match crate::ndm::kvn::parser::get_next_nonempty_line(lines) {
None => Err(
crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedEndOfInput {
keyword: #postfix_keyword.to_string(),
},
)?,
Some(next_line) => {
let line_matches = crate::ndm::kvn::parser::kvn_line_matches_key(
#postfix_keyword,
next_line,
)?;
if line_matches {
lines.next().expect("line must exist after successful peek");
} else {
Err(
crate::ndm::kvn::KvnDeserializerErr::<String>::UnexpectedKeyword {
found: next_line.to_string(),
expected: #postfix_keyword.to_string(),
},
)?
}
}
};
result
} else {
#mismatch_handler
}
}
}
}
}
}
}
fn deserializers_for_struct_with_unnamed_fields(
type_name: &proc_macro2::Ident,
fields: &syn::punctuated::Punctuated<Field, syn::token::Comma>,
) -> proc_macro2::TokenStream {
let field = fields
.first()
.expect("We expect exactly one item in structs with unnamed fields");
if &type_name.to_string() == "EpochType" {
return quote! {
Ok(#type_name (
crate::ndm::kvn::parser::parse_kvn_datetime_line(
lines.next().expect("line must exist after successful peek")
).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x))
.map(|x| x)?.full_value
))
};
}
let field_type_new = extract_type_path(&field.ty).expect("field must have a valid type path");
let field_type = field_type_new.to_token_stream().to_string();
let deserializer_for_kvn_type = generate_call_to_deserializer_for_kvn_type(
&field_type,
field_type_new,
"unnamed field",
false,
true,
);
let deserializer_for_kvn_type = match deserializer_for_kvn_type {
Ok(deserializer_for_kvn_type) => deserializer_for_kvn_type,
Err(e) => return e.into(),
};
quote! {
Ok(#type_name (
#deserializer_for_kvn_type
))
}
}
#[proc_macro_derive(KvnDeserialize, attributes(kvn))]
pub fn derive_kvn_deserialize(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let item = syn::parse_macro_input!(item as syn::DeriveInput);
let type_name = &item.ident;
let is_value_unit_struct = is_value_unit_struct(&item);
let prefix_and_postfix_keyword = get_prefix_and_postfix_keyword(&item.attrs);
let Data::Struct(strukt) = item.data else {
return syn::Error::new_spanned(
&item,
"only structs are supported for `#[derive(KvnDeserialize)]`",
)
.into_compile_error()
.into();
};
let (struct_deserializer, should_check_key_match) = match strukt.fields {
Fields::Named(syn::FieldsNamed { named, .. }) => (
deserializer_for_struct_with_named_fields(
type_name,
&named,
is_value_unit_struct,
prefix_and_postfix_keyword,
),
is_value_unit_struct,
),
Fields::Unnamed(syn::FieldsUnnamed { unnamed, .. }) => (
deserializers_for_struct_with_unnamed_fields(type_name, &unnamed),
true,
),
_ => {
return syn::Error::new_spanned(
&strukt.fields,
"only named fields are supported for `#[derive(KvnDeserialize)]`",
)
.into_compile_error()
.into();
}
};
let (impl_generics, type_generics, where_clause) = item.generics.split_for_impl();
let struct_deserializer = quote! {
impl #impl_generics crate::ndm::kvn::KvnDeserializer for #type_name #type_generics
#where_clause
{
fn deserialize<'a>(lines: &mut ::std::iter::Peekable<impl Iterator<Item = &'a str>>)
-> Result<#type_name, crate::ndm::kvn::KvnDeserializerErr<String>> {
#struct_deserializer
}
fn should_check_key_match () -> bool {
#should_check_key_match
}
}
};
struct_deserializer.into()
}