use proc_macro::TokenStream;
use quote::ToTokens;
use syn::PathArguments;
use syn::Type;
use syn::TypePath;
use crate::remoting_serializable::remoting_serializable_inner;
use crate::request_header_custom::request_header_codec_inner;
mod remoting_serializable;
mod request_header_custom;
#[proc_macro_derive(RequestHeaderCodec, attributes(required))]
pub fn request_header_codec(input: TokenStream) -> TokenStream {
request_header_codec_inner(input)
}
#[proc_macro_derive(RemotingSerializable)]
pub fn remoting_serializable(input: TokenStream) -> TokenStream {
remoting_serializable_inner(input)
}
fn get_type_name(ty: &Type) -> String {
ty.to_token_stream().to_string()
}
fn snake_to_camel_case(input: &str) -> String {
let mut camel_case = String::new();
let mut capitalize_next = false;
for c in input.chars() {
if c == '_' {
capitalize_next = true;
} else if capitalize_next {
camel_case.push(c.to_ascii_uppercase());
capitalize_next = false;
} else {
camel_case.push(c);
}
}
camel_case
}
fn is_option_type(ty: &Type) -> Option<&Type> {
match ty {
Type::Path(path) => {
if let Some(segment) = path.path.segments.last() {
if segment.ident == "Option" {
if let PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
return Some(inner_ty);
}
}
}
}
None
}
_ => None,
}
}
fn is_struct_type(ty: &Type) -> bool {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.first() {
if segment.ident == "Option" {
if let syn::PathArguments::AngleBracketed(ref args) = &segment.arguments {
if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
return !is_basic_or_string_type(inner_ty);
}
}
}
}
if let Some(_segment) = path.segments.last() {
if is_basic_or_string_type(ty) {
return false;
}
return true;
}
}
false
}
fn is_basic_or_string_type(ty: &syn::Type) -> bool {
if let syn::Type::Path(syn::TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.last() {
let segment_ident = &segment.ident;
return segment_ident == "String"
|| segment_ident == "CheetahString"
|| segment_ident == "i8"
|| segment_ident == "i16"
|| segment_ident == "i32"
|| segment_ident == "i64"
|| segment_ident == "u8"
|| segment_ident == "u16"
|| segment_ident == "u32"
|| segment_ident == "u64"
|| segment_ident == "f32"
|| segment_ident == "f64"
|| segment_ident == "bool";
}
}
false
}
fn has_serde_flatten_attribute(field: &syn::Field) -> bool {
for attr in &field.attrs {
if let Some(ident) = attr.path().get_ident() {
if ident == "serde" {
let mut has_serde_flatten_attribute = false;
let _ = attr.parse_nested_meta(|meta| {
has_serde_flatten_attribute = meta
.path
.segments
.iter()
.any(|segment| segment.ident == "flatten");
Ok(())
});
return has_serde_flatten_attribute;
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snake_to_camel_case_converts_snake_case_to_camel_case() {
assert_eq!(snake_to_camel_case("hello_world"), "helloWorld");
}
#[test]
fn snake_to_camel_case_handles_empty_string() {
assert_eq!(snake_to_camel_case(""), "");
}
#[test]
fn snake_to_camel_case_handles_single_word() {
assert_eq!(snake_to_camel_case("hello"), "hello");
}
#[test]
fn snake_to_camel_case_handles_multiple_underscores() {
assert_eq!(snake_to_camel_case("hello__world"), "helloWorld");
}
#[test]
fn snake_to_camel_case_handles_trailing_underscore() {
assert_eq!(snake_to_camel_case("hello_world_"), "helloWorld");
}
#[test]
fn snake_to_camel_case_handles_leading_underscore() {
assert_eq!(snake_to_camel_case("_hello_world"), "HelloWorld");
}
#[test]
fn snake_to_camel_case_handles_consecutive_underscores() {
assert_eq!(snake_to_camel_case("hello___world"), "helloWorld");
}
#[test]
fn snake_to_camel_case_handles_all_underscores() {
assert_eq!(snake_to_camel_case("___"), "");
}
}