rocketmq_macros/
lib.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18use proc_macro::TokenStream;
19use quote::ToTokens;
20use syn::PathArguments;
21use syn::Type;
22use syn::TypePath;
23
24use crate::remoting_serializable::remoting_serializable_inner;
25use crate::request_header_custom::request_header_codec_inner;
26
27mod remoting_serializable;
28mod request_header_custom;
29
30#[proc_macro_derive(RequestHeaderCodec, attributes(required))]
31pub fn request_header_codec(input: TokenStream) -> TokenStream {
32    request_header_codec_inner(input)
33}
34
35#[proc_macro_derive(RemotingSerializable)]
36pub fn remoting_serializable(input: TokenStream) -> TokenStream {
37    remoting_serializable_inner(input)
38}
39
40fn get_type_name(ty: &Type) -> String {
41    ty.to_token_stream().to_string()
42}
43
44fn snake_to_camel_case(input: &str) -> String {
45    let mut camel_case = String::new();
46    let mut capitalize_next = false;
47
48    for c in input.chars() {
49        if c == '_' {
50            capitalize_next = true;
51        } else if capitalize_next {
52            camel_case.push(c.to_ascii_uppercase());
53            capitalize_next = false;
54        } else {
55            camel_case.push(c);
56        }
57    }
58
59    camel_case
60}
61
62/// Determines if a given type is an `Option<T>` and returns the inner type `T`.
63///
64/// This function checks the provided `syn::Type` to see if it represents an `Option<T>`.
65/// If the type is `Option<T>`, it returns a reference to the inner type `T`.
66/// If the type is not `Option<T>`, it returns `None`.
67///
68/// # Arguments
69///
70/// * `ty` - A reference to the `syn::Type` to check.
71///
72/// # Returns
73///
74/// * `Some(&syn::Type)` if the type is `Option<T>`, containing a reference to the inner type `T`.
75/// * `None` if the type is not `Option<T>`.
76fn is_option_type(ty: &Type) -> Option<&Type> {
77    match ty {
78        Type::Path(path) => {
79            if let Some(segment) = path.path.segments.last() {
80                if segment.ident == "Option" {
81                    if let PathArguments::AngleBracketed(args) = &segment.arguments {
82                        if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
83                            return Some(inner_ty);
84                        }
85                    }
86                }
87            }
88            None
89        }
90        _ => None,
91    }
92}
93
94/// Determines if a given type is a struct, excluding basic types, `String`, `CheetahString`, and
95/// `Option<T>` where `T` is a basic type or string.
96///
97/// This function checks the provided `syn::Type` to see if it represents a struct. It specifically
98/// excludes:
99/// - Basic data types (e.g., `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `f32`, `f64`,
100///   `bool`)
101/// - Standard Rust `String`
102/// - Custom `CheetahString` type
103/// - `Option<T>` where `T` is a basic type or string
104///
105/// # Arguments
106///
107/// * `ty` - A reference to the `syn::Type` to check.
108///
109/// # Returns
110///
111/// * `true` if the type is a struct, otherwise `false`.
112fn is_struct_type(ty: &Type) -> bool {
113    // Check if the type is a path (i.e., a named type)
114    if let Type::Path(TypePath { path, .. }) = ty {
115        // Check if the type is Option<T>
116        if let Some(segment) = path.segments.first() {
117            // If it's Option, check its generic argument
118            if segment.ident == "Option" {
119                if let syn::PathArguments::AngleBracketed(ref args) = &segment.arguments {
120                    // Extract the first argument (T) inside Option
121                    if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
122                        // Recursively check the inner type, if it's not basic or String, return
123                        // true
124                        return !is_basic_or_string_type(inner_ty);
125                    }
126                }
127            }
128        }
129
130        // Check the last segment of the path to determine if it's a basic type or String,
131        // CheetahString
132        if let Some(_segment) = path.segments.last() {
133            // If it's a basic type or String/CheetahString, return false
134            if is_basic_or_string_type(ty) {
135                return false;
136            }
137
138            // If it's neither basic nor String/CheetahString, it's likely a struct
139            return true;
140        }
141    }
142
143    // If none of the conditions match, it's not a struct
144    false
145}
146
147/// Determines if a given type is a basic data type, a standard `String`, or a custom
148/// `CheetahString`.
149///
150/// This function checks the provided `Type` to see if it matches one of the following:
151/// - A basic numeric type (e.g., `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `f32`,
152///   `f64`)
153/// - A boolean type (`bool`)
154/// - A standard Rust `String`
155/// - A custom `CheetahString` type
156///
157/// # Arguments
158///
159/// * `ty` - A reference to the `syn::Type` to check.
160///
161/// # Returns
162///
163/// * `true` if the type is a basic data type, `String`, or `CheetahString`, otherwise `false`.
164fn is_basic_or_string_type(ty: &syn::Type) -> bool {
165    // Check if the type is a path (e.g., a named type)
166    if let syn::Type::Path(syn::TypePath { path, .. }) = ty {
167        // Get the last segment of the path, which should be the type name
168        if let Some(segment) = path.segments.last() {
169            // Extract the identifier of the last segment
170            let segment_ident = &segment.ident;
171
172            // Check if the identifier matches any of the basic types, `String`, or `CheetahString`
173            return segment_ident == "String"
174                || segment_ident == "CheetahString"
175                || segment_ident == "i8"
176                || segment_ident == "i16"
177                || segment_ident == "i32"
178                || segment_ident == "i64"
179                || segment_ident == "u8"
180                || segment_ident == "u16"
181                || segment_ident == "u32"
182                || segment_ident == "u64"
183                || segment_ident == "f32"
184                || segment_ident == "f64"
185                || segment_ident == "bool";
186        }
187    }
188
189    // If the type is not a path or does not match any of the expected types, return false
190    false
191}
192
193/// Checks if a given field has the `serde(flatten)` attribute.
194///
195/// This function iterates over all attributes of the provided field and checks
196/// for the presence of the `serde` attribute. If the `serde` attribute is found,
197/// it then looks inside to see if the `flatten` argument is present.
198///
199/// # Arguments
200///
201/// * `field` - A reference to the `syn::Field` to check for the `serde(flatten)` attribute.
202///
203/// # Returns
204///
205/// * `true` if the `serde(flatten)` attribute is found, otherwise `false`.
206fn has_serde_flatten_attribute(field: &syn::Field) -> bool {
207    // Iterate over all attributes of the field
208    for attr in &field.attrs {
209        // Check if the attribute is named "serde"
210        if let Some(ident) = attr.path().get_ident() {
211            if ident == "serde" {
212                // Initialize a flag to determine if `flatten` is found
213                let mut has_serde_flatten_attribute = false;
214
215                // Parse the nested metadata of the `serde` attribute
216                let _ = attr.parse_nested_meta(|meta| {
217                    // Check if any segment within the `serde` path is named "flatten"
218                    has_serde_flatten_attribute = meta
219                        .path
220                        .segments
221                        .iter()
222                        .any(|segment| segment.ident == "flatten");
223                    Ok(())
224                });
225
226                // Return the result immediately if `flatten` is found
227                return has_serde_flatten_attribute;
228            }
229        }
230    }
231
232    // Return `false` if no `serde(flatten)` attribute was found
233    false
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    #[test]
241    fn snake_to_camel_case_converts_snake_case_to_camel_case() {
242        assert_eq!(snake_to_camel_case("hello_world"), "helloWorld");
243    }
244
245    #[test]
246    fn snake_to_camel_case_handles_empty_string() {
247        assert_eq!(snake_to_camel_case(""), "");
248    }
249
250    #[test]
251    fn snake_to_camel_case_handles_single_word() {
252        assert_eq!(snake_to_camel_case("hello"), "hello");
253    }
254
255    #[test]
256    fn snake_to_camel_case_handles_multiple_underscores() {
257        assert_eq!(snake_to_camel_case("hello__world"), "helloWorld");
258    }
259
260    #[test]
261    fn snake_to_camel_case_handles_trailing_underscore() {
262        assert_eq!(snake_to_camel_case("hello_world_"), "helloWorld");
263    }
264
265    #[test]
266    fn snake_to_camel_case_handles_leading_underscore() {
267        assert_eq!(snake_to_camel_case("_hello_world"), "HelloWorld");
268    }
269
270    #[test]
271    fn snake_to_camel_case_handles_consecutive_underscores() {
272        assert_eq!(snake_to_camel_case("hello___world"), "helloWorld");
273    }
274
275    #[test]
276    fn snake_to_camel_case_handles_all_underscores() {
277        assert_eq!(snake_to_camel_case("___"), "");
278    }
279}