1use syn::punctuated::Punctuated;
2use syn::{Attribute, Expr, ExprLit, Lit, Meta};
3
4pub fn extract_doc_comments(attrs: &[Attribute]) -> Vec<String> {
6 let mut doc_lines = Vec::new();
7 for attr in attrs {
8 if attr.path().is_ident("doc") {
9 if let Meta::NameValue(meta) = &attr.meta {
10 if let Expr::Lit(expr_lit) = &meta.value {
11 if let Lit::Str(lit_str) = &expr_lit.lit {
12 doc_lines.push(lit_str.value());
13 }
14 }
15 }
16 }
17 }
18 doc_lines
19}
20
21pub fn apply_casing(text: &str, case: &str) -> String {
22 match case {
23 "lowercase" => text.to_lowercase(),
24 "UPPERCASE" => text.to_uppercase(),
25 "PascalCase" => {
26 if text.contains('_') {
28 text.split('_')
29 .map(|part| {
30 let mut c = part.chars();
31 match c.next() {
32 None => String::new(),
33 Some(f) => f.to_uppercase().to_string() + c.as_str(),
34 }
35 })
36 .collect()
37 } else {
38 let mut c = text.chars();
40 match c.next() {
41 None => String::new(),
42 Some(f) => f.to_uppercase().to_string() + c.as_str(),
43 }
44 }
45 }
46 "camelCase" => {
47 if text.contains('_') {
49 let parts: Vec<&str> = text.split('_').collect();
50 if parts.is_empty() {
51 return String::new();
52 }
53 let first = parts[0].to_lowercase();
54 let rest: String = parts[1..]
55 .iter()
56 .map(|part| {
57 let mut c = part.chars();
58 match c.next() {
59 None => String::new(),
60 Some(f) => f.to_uppercase().to_string() + c.as_str(),
61 }
62 })
63 .collect();
64 first + &rest
65 } else {
66 let mut c = text.chars();
68 match c.next() {
69 None => String::new(),
70 Some(f) => f.to_lowercase().to_string() + c.as_str(),
71 }
72 }
73 }
74 "snake_case" => {
75 let mut s = String::new();
76 for (i, c) in text.chars().enumerate() {
77 if c.is_uppercase() && i > 0 {
78 s.push('_');
79 }
80 if let Some(lower) = c.to_lowercase().next() {
81 s.push(lower);
82 }
83 }
84 s
85 }
86 "SCREAMING_SNAKE_CASE" => apply_casing(text, "snake_case").to_uppercase(),
87 "kebab-case" => apply_casing(text, "snake_case").replace('_', "-"),
88 "SCREAMING-KEBAB-CASE" => apply_casing(text, "kebab-case").to_uppercase(),
89 _ => text.to_string(),
90 }
91}
92
93pub fn extract_naming_and_doc(
95 attrs: &[Attribute],
96 default_name: &str,
97) -> (String, String, Option<String>, Vec<String>) {
98 let mut doc_lines = Vec::new();
99 let mut clean_doc_lines = Vec::new();
101
102 let mut final_name = default_name.to_string();
103 let mut rename_rule = None;
104
105 for attr in attrs {
107 if attr.path().is_ident("serde") {
108 if let Meta::List(list) = &attr.meta {
109 if let Ok(nested) =
110 list.parse_args_with(Punctuated::<Meta, syn::Token![,]>::parse_terminated)
111 {
112 for meta in nested {
113 if let Meta::NameValue(nv) = meta {
114 if nv.path.is_ident("rename") {
115 if let Expr::Lit(ExprLit {
116 lit: Lit::Str(s), ..
117 }) = nv.value
118 {
119 final_name = s.value();
120 }
121 } else if nv.path.is_ident("rename_all") {
122 if let Expr::Lit(ExprLit {
123 lit: Lit::Str(s), ..
124 }) = nv.value
125 {
126 rename_rule = Some(s.value());
127 }
128 }
129 }
130 }
131 }
132 }
133 }
134 }
135
136 for attr in attrs {
138 if attr.path().is_ident("doc") {
139 if let Meta::NameValue(meta) = &attr.meta {
140 if let Expr::Lit(expr_lit) = &meta.value {
141 if let Lit::Str(lit_str) = &expr_lit.lit {
142 let val = lit_str.value();
143 doc_lines.push(val.clone());
144 let trimmed = val.trim();
145
146 if trimmed.starts_with("@openapi") {
147 let rest = trimmed.strip_prefix("@openapi").unwrap().trim();
148 if rest.starts_with("rename-all") {
149 let rule = rest
150 .strip_prefix("rename-all")
151 .unwrap()
152 .trim()
153 .trim_matches('"');
154 rename_rule = Some(rule.to_string());
155 } else if rest.starts_with("rename") {
156 let name_part = rest
157 .strip_prefix("rename")
158 .unwrap()
159 .trim()
160 .trim_matches('"');
161 final_name = name_part.to_string();
162 } else {
163 }
167 } else {
168 clean_doc_lines.push(val.trim().to_string());
169 }
170 }
171 }
172 }
173 }
174 }
175
176 (
177 final_name,
178 clean_doc_lines.join(" "),
179 rename_rule,
180 doc_lines,
181 )
182}