protto_derive 0.6.2

Automatically derive Protobuf and Rust conversions.
Documentation
use crate::analysis::attribute_parser;
use crate::analysis::expect_analysis::ExpectMode;
use crate::constants::DEFAULT_CONVERSION_ERROR_SUFFIX;
use quote::quote;

#[derive(Clone)]
pub struct FieldProcessingContext<'a> {
    pub struct_name: &'a syn::Ident,
    pub field_name: &'a syn::Ident,
    pub field_type: &'a syn::Type,
    pub proto_field_ident: syn::Ident,
    pub protto_meta: attribute_parser::ProtoFieldMeta,
    pub expect_mode: ExpectMode,
    pub has_default: bool,
    pub default_fn: Option<String>,
    pub error_name: &'a syn::Ident,
    pub struct_level_error_type: &'a Option<syn::Type>,
    pub struct_level_error_fn: &'a Option<String>,
    pub proto_module: &'a str,
    pub proto_name: &'a str,
}

impl<'a> std::fmt::Debug for FieldProcessingContext<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let error_type = self
            .struct_level_error_type
            .as_ref()
            .map(|et| quote! { #et }.to_string());

        f.debug_struct("FieldProcessingContext")
            .field("struct_name", &self.struct_name)
            .field("field_name", &self.field_name)
            .field("proto_field_ident", &self.proto_field_ident)
            .field("proto_meta", &self.protto_meta)
            .field("expect_mode", &self.expect_mode)
            .field("has_default", &self.has_default)
            .field("default_fn", &self.default_fn)
            .field("error_name", &self.error_name)
            .field("struct_level_error_type", &error_type)
            .field("struct_level_error_fn", &self.struct_level_error_fn)
            .field("proto_module", &self.proto_module)
            .field("proto_name", &self.proto_name)
            .finish()
    }
}

impl<'a> FieldProcessingContext<'a> {
    pub fn new(
        struct_name: &'a syn::Ident,
        field: &'a syn::Field,
        error_name: &'a syn::Ident,
        struct_level_error_type: &'a Option<syn::Type>,
        struct_level_error_fn: &'a Option<String>,
        proto_module: &'a str,
        proto_name: &'a str,
    ) -> Self {
        let field_name = field.ident.as_ref().unwrap();
        let field_type = &field.ty;
        let proto_meta = attribute_parser::ProtoFieldMeta::from_field(field).unwrap_or_default();
        let expect_mode = ExpectMode::from_field_meta(field, &proto_meta);
        let has_default = proto_meta.default_fn.is_some();
        let default_fn = proto_meta.default_fn.clone();

        let proto_field_ident = attribute_parser::get_proto_field_name(field)
            .map(|proto_name| syn::Ident::new(&proto_name, proc_macro2::Span::call_site()))
            .unwrap_or_else(|| field_name.clone());

        Self {
            struct_name,
            field_name,
            field_type,
            proto_field_ident,
            protto_meta: proto_meta,
            expect_mode,
            has_default,
            default_fn,
            error_name,
            struct_level_error_type,
            struct_level_error_fn,
            proto_module,
            proto_name,
        }
    }

    pub fn has_error_fn(&self) -> bool {
        self.struct_level_error_fn.is_some() || self.field_level_error_fn().is_some()
    }

    pub fn get_effective_field_error_fn(&self) -> Option<syn::Path> {
        self.field_level_error_fn()
            .as_ref()
            .or(self.struct_level_error_fn.as_ref())
            .map(|error_fn| {
                syn::parse_str::<syn::Path>(error_fn).expect("Failed to parse error function path")
            })
    }

    pub fn field_level_error_fn(&self) -> &Option<String> {
        &self.protto_meta.error_fn
    }

    pub fn default_error_ident(&self) -> syn::Ident {
        let error_type_name = format!("{}{DEFAULT_CONVERSION_ERROR_SUFFIX}", self.struct_name);
        syn::parse_str(&error_type_name).expect("Failed to parse error type name")
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum CollectionType {
    Map,
    Vec,
    Set,
    Deque,
}

impl CollectionType {
    pub fn from_field_type(field_type: &syn::Type) -> Option<Self> {
        let type_str = quote!(#field_type).to_string();

        if type_str.contains("HashMap") || type_str.contains("BTreeMap") {
            Some(Self::Map)
        } else if type_str.contains("Vec") {
            Some(Self::Vec)
        } else if type_str.contains("HashSet") || type_str.contains("BTreeSet") {
            Some(Self::Set)
        } else if type_str.contains("VecDeque") {
            Some(Self::Deque)
        } else {
            None
        }
    }
}