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}