1use openapiv3::{ReferenceOr, SchemaKind, Type as OapiType};
4use proc_macro2::TokenStream;
5use quote::{format_ident, quote};
6use syn::{FnArg, GenericArgument, PathArguments, Type};
7
8use crate::openapi::Operation;
9use crate::types::TypeGenerator;
10use crate::{GeneratedTypeKind, Generator, TypeOverride};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ParamRole {
15 Path,
17 Query {
21 unknown_field: Option<proc_macro2::Ident>,
22 },
23 Body,
25 Other,
27}
28
29pub struct MethodTransformer<'a> {
31 generator: &'a Generator,
32 types_mod: &'a syn::Ident,
33}
34
35impl<'a> MethodTransformer<'a> {
36 pub fn new(generator: &'a Generator, types_mod: &'a syn::Ident) -> Self {
37 Self {
38 generator,
39 types_mod,
40 }
41 }
42
43 fn resolve_response_type(
45 &self,
46 op: &Operation,
47 kind: GeneratedTypeKind,
48 default_suffix: &str,
49 ) -> TokenStream {
50 let types_mod = self.types_mod;
51 let overrides = self.generator.type_overrides();
52 let op_name = op.name();
53
54 match overrides.get(op.method, &op.path, kind) {
55 Some(TypeOverride::Rename { name, .. }) => {
56 let ident = format_ident!("{}", name);
57 quote! { #types_mod::#ident }
58 }
59 Some(TypeOverride::Replace(replacement)) => replacement.clone(),
60 None => {
61 let ident = format_ident!("{}{}", op_name, default_suffix);
62 quote! { #types_mod::#ident }
63 }
64 }
65 }
66
67 pub fn transform(
73 &self,
74 method: &syn::TraitItemFn,
75 op: &Operation,
76 param_roles: &[Option<ParamRole>],
77 ) -> syn::Result<TokenStream> {
78 let suffixes = self.generator.response_suffixes();
79
80 let ok_type = self.resolve_response_type(op, GeneratedTypeKind::Ok, &suffixes.ok_suffix);
82
83 let transformed_params = self.transform_params(method, op, param_roles)?;
85
86 let is_async = method.sig.asyncness.is_some();
88
89 let return_type = if op.has_error_responses() {
91 let err_type =
92 self.resolve_response_type(op, GeneratedTypeKind::Err, &suffixes.err_suffix);
93 quote! { ::core::result::Result<#ok_type, #err_type> }
94 } else {
95 ok_type.clone()
97 };
98
99 let method_name = &method.sig.ident;
100
101 if is_async {
103 Ok(quote! {
105 fn #method_name(#transformed_params) -> impl ::core::marker::Send + ::core::future::Future<Output = #return_type>;
106 })
107 } else {
108 Ok(quote! {
110 fn #method_name(#transformed_params) -> #return_type;
111 })
112 }
113 }
114
115 fn transform_params(
127 &self,
128 method: &syn::TraitItemFn,
129 op: &Operation,
130 param_roles: &[Option<ParamRole>],
131 ) -> syn::Result<TokenStream> {
132 let type_gen = self.generator.type_generator();
133
134 let mut all_params: Vec<&syn::PatType> = Vec::new();
135
136 for arg in &method.sig.inputs {
137 match arg {
138 FnArg::Receiver(_) => {
139 return Err(syn::Error::new_spanned(
140 arg,
141 "oxapi trait methods must be static (no self)",
142 ));
143 }
144 FnArg::Typed(pat_type) => {
145 all_params.push(pat_type);
146 }
147 }
148 }
149
150 let has_any_explicit = param_roles.iter().any(|r| r.is_some());
153
154 let roles: Vec<ParamRole> = all_params
156 .iter()
157 .enumerate()
158 .map(|(idx, pat_type)| {
159 if let Some(Some(role)) = param_roles.get(idx) {
161 return role.clone();
162 }
163 if has_any_explicit {
165 return ParamRole::Other;
166 }
167 detect_role_from_type(&pat_type.ty)
169 })
170 .collect();
171
172 let mut transformed = Vec::new();
174 for (idx, pat_type) in all_params.iter().enumerate() {
175 let pat = &pat_type.pat;
176 let ty = &pat_type.ty;
177 let role = roles.get(idx).cloned().unwrap_or(ParamRole::Other);
178
179 let transformed_ty = self.transform_type_with_role(ty, op, type_gen, role)?;
180 transformed.push(quote! { #pat: #transformed_ty });
181 }
182
183 Ok(quote! { #(#transformed),* })
184 }
185
186 fn transform_type_with_role(
191 &self,
192 ty: &Type,
193 op: &Operation,
194 type_gen: &TypeGenerator,
195 role: ParamRole,
196 ) -> syn::Result<TokenStream> {
197 match ty {
198 Type::Path(type_path) => {
199 if !has_type_elision(ty) {
201 return Ok(quote! { #ty });
203 }
204
205 let prefix_segments: Vec<_> = type_path
207 .path
208 .segments
209 .iter()
210 .take(type_path.path.segments.len().saturating_sub(1))
211 .collect();
212
213 let last_segment = type_path
214 .path
215 .segments
216 .last()
217 .ok_or_else(|| syn::Error::new_spanned(ty, "empty type path"))?;
218
219 let inner_type = match &role {
221 ParamRole::Path => self.generate_path_inner_type(op, type_gen),
222 ParamRole::Query { unknown_field } => {
223 self.generate_query_inner_type(op, type_gen, unknown_field.clone())
224 }
225 ParamRole::Body => self.generate_body_inner_type(op, type_gen),
226 ParamRole::Other => {
227 return Ok(quote! { #ty });
229 }
230 };
231
232 let type_ident = &last_segment.ident;
234 let leading_colon = type_path.path.leading_colon;
235
236 if prefix_segments.is_empty() {
237 Ok(quote! { #leading_colon #type_ident<#inner_type> })
239 } else {
240 Ok(quote! { #leading_colon #(#prefix_segments ::)* #type_ident<#inner_type> })
242 }
243 }
244 _ => {
245 Ok(quote! { #ty })
247 }
248 }
249 }
250
251 fn generate_path_inner_type(&self, op: &Operation, type_gen: &TypeGenerator) -> TokenStream {
253 let types_mod = self.types_mod;
254 let overrides = self.generator.type_overrides();
255
256 if let Some(TypeOverride::Replace(replacement)) =
258 overrides.get(op.method, &op.path, GeneratedTypeKind::Path)
259 {
260 return replacement.clone();
261 }
262
263 if let Some((name, _)) = type_gen.generate_path_struct(op, overrides) {
265 quote! { #types_mod::#name }
266 } else {
267 quote! { () }
269 }
270 }
271
272 fn generate_query_inner_type(
277 &self,
278 op: &Operation,
279 type_gen: &TypeGenerator,
280 unknown_field: Option<proc_macro2::Ident>,
281 ) -> TokenStream {
282 let types_mod = self.types_mod;
283 let overrides = self.generator.type_overrides();
284
285 if let Some(TypeOverride::Replace(replacement)) =
287 overrides.get(op.method, &op.path, GeneratedTypeKind::Query)
288 {
289 return replacement.clone();
290 }
291
292 if let Some((name, _)) =
294 type_gen.generate_query_struct(op, overrides, unknown_field.as_ref())
295 {
296 quote! { #types_mod::#name }
297 } else {
298 quote! { ::std::collections::HashMap<String, String> }
300 }
301 }
302
303 fn generate_body_inner_type(&self, op: &Operation, type_gen: &TypeGenerator) -> TokenStream {
305 let types_mod = self.types_mod;
306
307 if let Some(body) = &op.request_body {
308 if let Some(schema) = &body.schema {
309 let op_name = op.operation_id.as_deref().unwrap_or(&op.path);
310 let body_type = type_gen.request_body_type(body, op_name);
311
312 let needs_prefix = match schema {
314 ReferenceOr::Reference { .. } => true,
315 ReferenceOr::Item(inline) => {
316 matches!(&inline.schema_kind, SchemaKind::Type(OapiType::Object(_)))
317 }
318 };
319
320 if needs_prefix {
321 quote! { #types_mod::#body_type }
322 } else {
323 body_type
324 }
325 } else {
326 quote! { serde_json::Value }
327 }
328 } else {
329 quote! { serde_json::Value }
330 }
331 }
332}
333
334fn has_type_elision(ty: &Type) -> bool {
336 match ty {
337 Type::Infer(_) => true,
338 Type::Path(type_path) => {
339 if let Some(last) = type_path.path.segments.last()
341 && let PathArguments::AngleBracketed(args) = &last.arguments
342 {
343 return args
344 .args
345 .iter()
346 .any(|arg| matches!(arg, GenericArgument::Type(Type::Infer(_))));
347 }
348 false
349 }
350 _ => false,
351 }
352}
353
354fn detect_role_from_type(ty: &Type) -> ParamRole {
357 if let Type::Path(type_path) = ty
358 && let Some(last) = type_path.path.segments.last()
359 {
360 let name = last.ident.to_string();
361 match name.as_str() {
362 "Path" => return ParamRole::Path,
363 "Query" => {
364 return ParamRole::Query {
365 unknown_field: None,
366 };
367 }
368 "Json" => return ParamRole::Body,
369 _ => {}
370 }
371 }
372 ParamRole::Other
373}