section_parser_derive 0.1.1

This just exists because I cannot define derive macros inside my binary :(
Documentation
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
    parse_macro_input, Data, DataStruct, DeriveInput, Fields, GenericArgument, Ident,
    PathArguments, Type,
};

struct FieldProperties {
    ident: Ident,
    ty: Type,
}

#[proc_macro_derive(SectionParser)]
pub fn derive_section_parser(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let DeriveInput {
        ident: struct_ident,
        data,
        generics,
        ..
    } = parse_macro_input!(item);
    if !generics.params.is_empty() {
        panic!("SectionParser does not support generics yet");
    }

    let DataStruct { fields, .. } = match data {
        Data::Struct(data) => data,
        _ => panic!("SectionParser can only be derived on structs"),
    };
    let fields = match fields {
        Fields::Named(fields) => fields,
        _ => panic!("SectionParser can only be derived on structs with named fields"),
    };

    let error_ident = Ident::new(&format!("{}Error", struct_ident), Span::call_site());

    let mut parsed_fields = Vec::new();

    for field in fields.named {
        let ident = field.ident.expect("Expected named field");
        let path = match field.ty {
            Type::Path(path) => path.path.segments,
            _ => continue,
        };
        let first_segment = path
            .into_iter()
            .next()
            .expect("Expected type with at least one element");
        if first_segment.ident != "Option" {
            continue;
        }
        let option_type_arguments = match first_segment.arguments {
            PathArguments::AngleBracketed(arguments) => arguments.args,
            _ => panic!(
                "Expected angle bracketed arguments for Option, but got {:?}",
                first_segment.arguments
            ),
        };
        let type_argument = option_type_arguments
            .into_iter()
            .next()
            .expect("Expected one type argument for Option");
        let ty = match type_argument {
            GenericArgument::Type(ty) => ty,
            _ => panic!(
                "Expected a type argument for Option, but got {:?}",
                type_argument
            ),
        };

        parsed_fields.push(FieldProperties { ident, ty });
    }

    let getters: TokenStream = parsed_fields
        .iter()
        .map(|FieldProperties { ident, ty, .. }| {
            quote! {
                fn #ident(&mut self) -> ::std::result::Result<#ty, #error_ident> {
                    self.#ident.take().ok_or_else(|| {
                        self.missing_field_error(stringify!(#ident))
                    })
                }
            }
        })
        .collect();

    let setters: TokenStream = parsed_fields.iter().map(|FieldProperties {ident, ty, .. }| {
        let setter = Ident::new(&format!("set_{}", ident), Span::call_site());
        quote! {
            fn #setter(&mut self, #ident: #ty) -> ::std::result::Result<(), #error_ident> {
                if let Some(#ident) = self.#ident.take() {
                    ::std::result::Result::Err(self.duplicate_field_error(stringify!(#ident), #ident))
                } else {
                    self.#ident = ::std::option::Option::Some(#ident);
                    ::std::result::Result::Ok(())
                }
            }
        }
    }).collect();

    let ensure_empty_checks: TokenStream = parsed_fields.iter().map(|FieldProperties { ident, .. }| {
        quote! {
            if let ::std::option::Option::Some(#ident) = self.#ident.take() {
                return ::std::result::Result::Err(self.unexpected_field_error(stringify!(#ident), #ident));
            }
        }
    }).collect();

    let result = quote! {
        impl #struct_ident {
            #getters

            #setters

            fn ensure_empty(mut self) -> ::std::result::Result<(), #error_ident> {
                #ensure_empty_checks

                ::std::result::Result::Ok(())
            }
        }
    };
    result.into()
}