1use syn::{GenericArgument, PathArguments, Token, Type, TypePath, punctuated::Punctuated};
9
10use crate::errors::{Error, Result};
11
12pub const JSON: &str = "Json";
17pub const RESULT: &str = "Result";
18pub const OPTION: &str = "Option";
19pub const VEC: &str = "Vec";
20pub const STATE: &str = "State";
21pub const SSE: &str = "Sse";
22
23pub struct WrapperMatch<'a> {
32 pub generic_args: &'a Punctuated<GenericArgument, Token![,]>,
33}
34
35impl std::fmt::Debug for WrapperMatch<'_> {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.debug_struct("WrapperMatch")
38 .field("generic_args_len", &self.generic_args.len())
39 .finish()
40 }
41}
42
43impl<'a> WrapperMatch<'a> {
44 pub fn first_type(&self) -> Option<&'a Type> {
46 self.generic_args.iter().find_map(as_type_arg)
47 }
48
49 pub fn second_type(&self) -> Option<&'a Type> {
51 self.generic_args.iter().filter_map(as_type_arg).nth(1)
52 }
53
54 pub fn nth_type(&self, n: usize) -> Option<&'a Type> {
56 self.generic_args.iter().filter_map(as_type_arg).nth(n)
57 }
58}
59
60fn as_type_arg(arg: &GenericArgument) -> Option<&Type> {
61 if let GenericArgument::Type(ty) = arg {
62 Some(ty)
63 } else {
64 None
65 }
66}
67
68pub fn try_extract_wrapper<'a>(ty: &'a Type, wrapper_name: &str) -> Option<WrapperMatch<'a>> {
81 let last = last_segment(ty)?;
82 if last.ident != wrapper_name {
83 return None;
84 }
85 match &last.arguments {
86 PathArguments::AngleBracketed(args) if !args.args.is_empty() => Some(WrapperMatch {
87 generic_args: &args.args,
88 }),
89 _ => None,
90 }
91}
92
93pub fn extract_wrapper<'a>(ty: &'a Type, wrapper_name: &'static str) -> Result<WrapperMatch<'a>> {
99 let span = span_of(ty);
100
101 let last = last_segment(ty)
102 .ok_or_else(|| Error::unsupported_type(span, ty, "empty type path", "use a named type"))?;
103
104 if last.ident != wrapper_name {
105 return Err(Error::missing_wrapper(span, wrapper_name, ty));
106 }
107
108 match &last.arguments {
109 PathArguments::AngleBracketed(args) if !args.args.is_empty() => Ok(WrapperMatch {
110 generic_args: &args.args,
111 }),
112 PathArguments::AngleBracketed(_) | PathArguments::None => {
113 Err(Error::empty_generic_args(span, wrapper_name))
114 }
115 PathArguments::Parenthesized(_) => Err(Error::unsupported_type(
116 span,
117 ty,
118 "parenthesised arguments are not supported",
119 "use angle-bracketed generics: `Wrapper<T>`",
120 )),
121 }
122}
123
124pub fn is_primitive(ty: &Type) -> bool {
132 match ty {
133 Type::Tuple(t) if t.elems.is_empty() => true,
135 Type::Path(_) => matches!(
136 last_ident_str(ty).as_deref(),
137 Some(
138 "String"
139 | "str"
140 | "bool"
141 | "i8"
142 | "i16"
143 | "i32"
144 | "i64"
145 | "i128"
146 | "isize"
147 | "u8"
148 | "u16"
149 | "u32"
150 | "u64"
151 | "u128"
152 | "usize"
153 | "f32"
154 | "f64"
155 )
156 ),
157 _ => false,
158 }
159}
160
161pub fn is_primitive_type_name(type_name: &str) -> bool {
177 matches!(
178 type_name,
179 "()" | "String"
180 | "str"
181 | "i8"
182 | "i16"
183 | "i32"
184 | "i64"
185 | "i128"
186 | "u8"
187 | "u16"
188 | "u32"
189 | "u64"
190 | "u128"
191 | "f32"
192 | "f64"
193 | "bool"
194 | "usize"
195 | "isize"
196 | "serde_json::Value"
197 | "Json<serde_json::Value>"
198 )
199}
200
201pub fn innermost_custom_type(ty: &Type) -> Option<&Type> {
212 for wrapper in &[RESULT, JSON, OPTION, VEC, STATE, SSE] {
213 if let Some(m) = try_extract_wrapper(ty, wrapper)
214 && let Some(inner) = m.first_type()
215 {
216 return innermost_custom_type(inner);
217 }
218 }
219 if is_primitive(ty) { None } else { Some(ty) }
221}
222
223fn last_segment(ty: &Type) -> Option<&syn::PathSegment> {
228 if let Type::Path(TypePath { path, .. }) = ty {
229 path.segments.last()
230 } else {
231 None
232 }
233}
234
235fn last_ident_str(ty: &Type) -> Option<String> {
236 last_segment(ty).map(|seg| seg.ident.to_string())
237}
238
239fn span_of(ty: &Type) -> proc_macro2::Span {
240 use syn::spanned::Spanned;
241 ty.span()
242}
243
244pub fn extract_first_generic_arg_string(type_str: &str) -> Option<String> {
260 let start = type_str.find('<')? + 1;
261 let mut depth = 0;
262 let mut end = start;
263
264 for (i, ch) in type_str[start..].char_indices() {
265 match ch {
266 '<' => depth += 1,
267 '>' if depth == 0 => {
268 end = start + i;
269 break;
270 }
271 '>' => depth -= 1,
272 ',' if depth == 0 => {
273 end = start + i;
274 break;
275 }
276 _ => {}
277 }
278 }
279
280 if end > start {
281 Some(type_str[start..end].trim().to_string())
282 } else {
283 None
284 }
285}
286
287#[cfg(test)]
292mod tests {
293 use super::*;
294 use crate::errors::type_display;
295
296 fn parse_type(s: &str) -> Type {
297 syn::parse_str(s).unwrap()
298 }
299
300 #[test]
303 fn bare_json() {
304 let ty = parse_type("Json<Planet>");
305 let m = try_extract_wrapper(&ty, JSON).unwrap();
306 assert!(m.first_type().is_some());
307 }
308
309 #[test]
310 fn qualified_json() {
311 let ty = parse_type("axum::extract::Json<Planet>");
312 let m = try_extract_wrapper(&ty, JSON).unwrap();
313 assert!(m.first_type().is_some());
314 }
315
316 #[test]
317 fn qualified_result() {
318 let ty = parse_type("std::result::Result<Json<Planet>, AppError>");
319 let m = try_extract_wrapper(&ty, RESULT).unwrap();
320 assert!(m.first_type().is_some());
321 assert!(m.second_type().is_some());
322 }
323
324 #[test]
325 fn wrong_wrapper_returns_none() {
326 let ty = parse_type("Option<String>");
327 assert!(try_extract_wrapper(&ty, JSON).is_none());
328 }
329
330 #[test]
331 fn wrapper_without_args_returns_none() {
332 let ty = parse_type("Json");
334 assert!(try_extract_wrapper(&ty, JSON).is_none());
335 }
336
337 #[test]
340 fn extract_wrong_wrapper_gives_error() {
341 let ty = parse_type("Vec<Planet>");
342 let err = extract_wrapper(&ty, JSON).unwrap_err();
343 let msg = err.to_string();
344 assert!(msg.contains("expected `Json` wrapper"));
345 assert!(msg.contains("Vec<Planet>"));
346 }
347
348 #[test]
351 fn primitives_identified() {
352 for s in &["String", "i32", "u64", "f64", "bool", "usize"] {
353 assert!(is_primitive(&parse_type(s)), "{} should be primitive", s);
354 }
355 }
356
357 #[test]
358 fn unit_type_is_primitive() {
359 let ty: Type = syn::parse_str("()").unwrap();
360 assert!(is_primitive(&ty));
361 }
362
363 #[test]
364 fn custom_type_not_primitive() {
365 assert!(!is_primitive(&parse_type("Planet")));
366 assert!(!is_primitive(&parse_type("AppError")));
367 }
368
369 #[test]
372 fn unwraps_result_json_vec() {
373 let ty = parse_type("Result<Json<Vec<Planet>>, AppError>");
374 let inner = innermost_custom_type(&ty).unwrap();
375 assert_eq!(type_display(inner), "Planet");
376 }
377
378 #[test]
379 fn primitive_innermost_returns_none() {
380 let ty = parse_type("Json<String>");
381 assert!(innermost_custom_type(&ty).is_none());
382 }
383
384 #[test]
385 fn custom_type_at_root_returned_as_is() {
386 let ty = parse_type("Planet");
387 let inner = innermost_custom_type(&ty).unwrap();
388 assert_eq!(type_display(inner), "Planet");
389 }
390
391 #[test]
394 fn second_type_on_result() {
395 let ty = parse_type("Result<Json<Planet>, AppError>");
396 let m = try_extract_wrapper(&ty, RESULT).unwrap();
397 let second = m.second_type().unwrap();
398 assert_eq!(type_display(second), "AppError");
399 }
400
401 #[test]
402 fn nth_type_indexing() {
403 let ty = parse_type("Result<Json<Planet>, AppError>");
404 let m = try_extract_wrapper(&ty, RESULT).unwrap();
405 assert!(m.nth_type(0).is_some());
406 assert!(m.nth_type(1).is_some());
407 assert!(m.nth_type(2).is_none());
408 }
409}