use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{Data, Field, Ident};
use crate::util::field_type::FieldType;
pub(crate) fn impl_macro_desensitized(input: &syn::DeriveInput) -> TokenStream {
let name = &input.ident;
let fields = match &input.data {
Data::Struct(data_struct) => &data_struct.fields,
_ => panic!("error: #[derive(Desensitized)] is only supported for structs"),
};
let desensitize_fields = fields
.iter()
.filter(|val| val.ident.is_some() && val.attrs.len() > 0 && !val.ident.as_ref().map(|s| s.to_string().starts_with("_")).unwrap_or(false))
.filter(|val| FieldType::is_string(&val.ty))
.map(|field| {
match &field.ty {
syn::Type::Reference(_) | syn::Type::Slice(_) => panic!("Reference type and slice type are not supported"),
_ => {}
}
let field_name = field.ident.as_ref().unwrap();
let desens_type = find_desensitization_type(field);
match desens_type {
Some(desens_type) => {
let de = desens_type.to_ident();
match desens_type {
_ => {
if FieldType::is_option(&field.ty) {
let is_string = is_string(&field.ty);
if is_string {
quote! { self.#field_name = self.#field_name.as_ref().map(| val | DesensitizedUtil::#de(val.as_str())) }
}else {
quote! { self.#field_name = self.#field_name.as_ref().map(| val | DesensitizedUtil::#de(val.as_ref()).into()) }
}
}else {
let is_string = is_string(&field.ty);
if is_string {
quote! { self.#field_name = DesensitizedUtil::#de(self.#field_name.as_str()) }
}else {
quote! { self.#field_name = DesensitizedUtil::#de(self.#field_name.as_ref()).into() }
}
}
}
}
}
None => quote! {},
}
}).filter(|val| !val.is_empty()).collect::<Vec<_>>();
let expanded = quote! {
impl ::next_web_core::traits::desensitized::Desensitized for #name {
fn desensitize(&mut self) {
use ::next_web::util::desensitized::DesensitizedUtil;
#(#desensitize_fields;)*
}
}
};
expanded.into()
}
fn find_desensitization_type(field: &Field) -> Option<DesensitizationType> {
for attr in &field.attrs {
if attr.path().is_ident("de") {
if let Ok(ident) = attr.parse_args::<Ident>() {
return DesensitizationType::from_str(ident.to_string().as_str());
}
}
}
None
}
#[derive(PartialEq, Eq)]
enum DesensitizationType {
Email,
Phone,
Password,
Name,
IdCard,
BankCard,
Address,
Ip,
}
impl DesensitizationType {
fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"email" => Some(DesensitizationType::Email),
"phone" => Some(DesensitizationType::Phone),
"password" => Some(DesensitizationType::Password),
"name" => Some(DesensitizationType::Name),
"id_card" => Some(DesensitizationType::IdCard),
"bank_card" => Some(DesensitizationType::BankCard),
"address" => Some(DesensitizationType::Address),
"ip" => Some(DesensitizationType::Ip),
_ => panic!("\nUnsupported desensitization type: {}\nThe supported parameters are as follows: \nemail, phone, name, id_card, bank_card, address, ip", s),
}
}
fn to_ident(&self) -> Ident {
let s = match self {
DesensitizationType::Email => "email",
DesensitizationType::Phone => "phone",
DesensitizationType::Password => "password",
DesensitizationType::Name => "name",
DesensitizationType::IdCard => "id_card",
DesensitizationType::BankCard => "bank_card",
DesensitizationType::Address => "address",
DesensitizationType::Ip => "ip",
};
Ident::new(s, Span::mixed_site())
}
}
fn is_string(ty: &syn::Type) -> bool {
if let syn::Type::Path(type_path) = ty {
if type_path.path.segments.len() == 1 {
return type_path
.path
.segments
.first()
.map(|val| val.ident == "String")
.unwrap_or(false);
}
}
false
}