enumcapsulate-macros 0.6.3

Procedural macros for 'enumcapsulate' crate
Documentation
use syn::LitStr;

use crate::{
    attr::{DISCRIMINANT, EXCLUDE, FIELD},
    config::ExcludeConfig,
    parse_enumcapsulate_attrs,
};

use super::DiscriminantConfig;

#[derive(Clone, Eq, PartialEq, Debug)]
pub(crate) enum FieldSelector {
    Name(String),
    Index(usize),
}

impl Default for FieldSelector {
    fn default() -> Self {
        Self::Index(0)
    }
}

#[derive(Clone, Default)]
pub(crate) struct VariantConfig {
    discriminant: Option<DiscriminantConfig>,
    exclude: Option<ExcludeConfig>,
    field: Option<FieldSelector>,
}

impl VariantConfig {
    pub(crate) fn from_variant(variant: &syn::Variant) -> Result<Self, syn::Error> {
        let mut this = Self::default();

        parse_enumcapsulate_attrs(&variant.attrs, |meta| {
            if meta.path.is_ident(DISCRIMINANT) {
                let mut discriminant = this.discriminant.take().unwrap_or_default();

                discriminant.parse(&meta, variant)?;

                this.discriminant = Some(discriminant);
            } else if meta.path.is_ident(EXCLUDE) {
                let mut exclude = this.exclude.take().unwrap_or_default();

                exclude.parse(&meta, true)?;

                this.exclude = Some(exclude);
            } else if meta.path.is_ident(FIELD) {
                if this.field.is_some() {
                    return Err(meta.error("field already specified"));
                }

                let value = meta.value()?;

                if value.peek(LitStr) {
                    let lit: syn::LitStr = value.parse()?;
                    this.field = Some(FieldSelector::Name(lit.value()));
                } else {
                    let lit: syn::LitInt = value.parse()?;
                    this.field = Some(FieldSelector::Index(lit.base10_parse()?));
                }
            }

            Ok(())
        })?;

        Ok(this)
    }

    pub(crate) fn selected_field<'a>(
        &self,
        fields: &'a syn::Fields,
    ) -> Result<Option<(&'a syn::Field, usize)>, syn::Error> {
        let default_field_selector = FieldSelector::default();
        let field_selector = self.field.as_ref().unwrap_or(&default_field_selector);

        if fields.is_empty() {
            return Ok(None);
        }

        match field_selector {
            FieldSelector::Name(name) => {
                let (index, field) = fields
                    .iter()
                    .enumerate()
                    .find(|(_, field)| {
                        field
                            .ident
                            .as_ref()
                            .map(|ident| ident == name)
                            .unwrap_or(false)
                    })
                    .expect("field name should have been rejected");

                Ok(Some((field, index)))
            }
            FieldSelector::Index(index) => {
                let field = fields
                    .iter()
                    .nth(*index)
                    .expect("field index should have been rejected");

                Ok(Some((field, *index)))
            }
        }
    }

    pub(crate) fn discriminant(&self) -> Option<&DiscriminantConfig> {
        self.discriminant.as_ref()
    }

    pub(crate) fn exclude(&self) -> Option<&ExcludeConfig> {
        self.exclude.as_ref()
    }

    #[allow(dead_code)]
    pub(crate) fn field(&self) -> Option<&FieldSelector> {
        self.field.as_ref()
    }
}