1use alef_core::config::{AdapterConfig, AlefConfig, Language};
2
3pub fn generate_body(adapter: &AdapterConfig, language: Language, config: &AlefConfig) -> anyhow::Result<String> {
5 let body = match language {
6 Language::Python => gen_python_body(adapter, config),
7 Language::Node => gen_node_body(adapter, config),
8 Language::Ruby => gen_ruby_body(adapter, config),
9 Language::Php => gen_php_body(adapter, config),
10 Language::Elixir => gen_elixir_body(adapter, config),
11 Language::Wasm => gen_wasm_body(adapter, config),
12 Language::Ffi => gen_ffi_body(adapter, config),
13 Language::Go => gen_go_body(adapter, config),
14 Language::Java => gen_java_body(adapter, config),
15 Language::Csharp => gen_csharp_body(adapter, config),
16 Language::R => gen_r_body(adapter, config),
17 };
18 Ok(body)
19}
20
21fn call_args(adapter: &AdapterConfig) -> Vec<String> {
23 adapter
24 .params
25 .iter()
26 .map(|p| {
27 if p.optional {
28 format!("{}.map(Into::into)", p.name)
29 } else {
30 format!("{}.into()", p.name)
31 }
32 })
33 .collect()
34}
35
36fn core_let_bindings(adapter: &AdapterConfig, core_import: &str) -> Vec<String> {
38 adapter
39 .params
40 .iter()
41 .map(|p| {
42 if p.optional {
43 format!(
44 "let core_{name} = {name}.map(|v| -> {core_import}::{ty} {{ v.into() }});",
45 name = p.name,
46 core_import = core_import,
47 ty = p.ty,
48 )
49 } else {
50 format!(
51 "let core_{name}: {core_import}::{ty} = {name}.into();",
52 name = p.name,
53 core_import = core_import,
54 ty = p.ty,
55 )
56 }
57 })
58 .collect()
59}
60
61fn core_call_args(adapter: &AdapterConfig) -> Vec<String> {
63 adapter.params.iter().map(|p| format!("core_{}", p.name)).collect()
64}
65
66fn gen_python_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
71 let core_path = &adapter.core_path;
72 let returns = adapter.returns.as_deref().unwrap_or("()");
73 let core_import = config.core_import();
74
75 let let_bindings = core_let_bindings(adapter, &core_import);
76 let core_args = core_call_args(adapter);
77 let core_call_str = core_args.join(", ");
78
79 let bindings_block = if let_bindings.is_empty() {
80 String::new()
81 } else {
82 format!("{}\n ", let_bindings.join("\n "))
83 };
84
85 format!(
86 "let inner = self.inner.clone();\n \
87 {bindings_block}\
88 pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n \
89 let result = inner.{core_path}({core_call_str}).await\n \
90 .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?;\n \
91 Ok({returns}::from(result))\n \
92 }})"
93 )
94}
95
96fn gen_node_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
101 let core_path = &adapter.core_path;
102 let returns = adapter.returns.as_deref().unwrap_or("()");
103
104 let args = call_args(adapter);
105 let call_str = args.join(", ");
106
107 format!(
108 "let core_req = {call_str};\n \
109 self.inner.{core_path}(core_req).await\n \
110 .map({returns}::from)\n \
111 .map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
112 )
113}
114
115fn gen_ruby_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
120 let core_path = &adapter.core_path;
121 let returns = adapter.returns.as_deref().unwrap_or("()");
122
123 let args = call_args(adapter);
124 let call_str = args.join(", ");
125
126 format!(
127 "let rt = tokio::runtime::Runtime::new()\n \
128 .map_err(|e| magnus::Error::new(magnus::exception::runtime_error(), e.to_string()))?;\n \
129 let core_req = {call_str};\n \
130 rt.block_on(async {{ self.inner.{core_path}(core_req).await }})\n \
131 .map({returns}::from)\n \
132 .map_err(|e| magnus::Error::new(magnus::exception::runtime_error(), e.to_string()))"
133 )
134}
135
136fn gen_php_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
141 let core_path = &adapter.core_path;
142 let returns = adapter.returns.as_deref().unwrap_or("()");
143
144 let args = call_args(adapter);
145 let call_str = args.join(", ");
146
147 format!(
148 "WORKER_RUNTIME.block_on(async {{\n \
149 self.inner.{core_path}({call_str}.into()).await\n \
150 }})\n \
151 .map({returns}::from)\n \
152 .map_err(|e| ext_php_rs::exception::PhpException::default(e.to_string()).into())"
153 )
154}
155
156fn gen_elixir_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
161 let core_path = &adapter.core_path;
162 let returns = adapter.returns.as_deref().unwrap_or("()");
163
164 let args = call_args(adapter);
165 let call_str = args.join(", ");
166
167 format!(
168 "let rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;\n \
169 rt.block_on(async {{ client.inner.{core_path}({call_str}).await }})\n \
170 .map({returns}::from)\n \
171 .map_err(|e| e.to_string())"
172 )
173}
174
175fn gen_wasm_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
180 let core_path = &adapter.core_path;
181 let returns = adapter.returns.as_deref().unwrap_or("JsValue");
182
183 let args = call_args(adapter);
184 let call_str = args.join(", ");
185
186 format!(
187 "self.inner.{core_path}({call_str}).await\n \
188 .map({returns}::from)\n \
189 .map_err(|e| JsValue::from_str(&e.to_string()))"
190 )
191}
192
193fn gen_ffi_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
198 let core_path = &adapter.core_path;
199 let prefix = config.ffi_prefix();
200 let owner_type = adapter.owner_type.as_deref().unwrap_or("Self");
201 let owner_snake = to_snake_case(owner_type);
202 let _ = prefix;
203 let _ = owner_snake;
204
205 let conversions: Vec<String> = adapter
206 .params
207 .iter()
208 .map(|p| {
209 if p.ty == "String" || p.ty == "&str" {
210 format!(
211 "let {name} = unsafe {{ std::ffi::CStr::from_ptr({name}) }}\n \
212 .to_str()\n \
213 .unwrap_or_default()\n \
214 .to_owned();",
215 name = p.name,
216 )
217 } else {
218 format!(
219 "let {name}_str = unsafe {{ std::ffi::CStr::from_ptr({name}_json) }}\n \
220 .to_str()\n \
221 .unwrap_or_default();\n \
222 let {name}: {ty} = match serde_json::from_str({name}_str) {{\n \
223 Ok(v) => v,\n \
224 Err(e) => {{\n \
225 update_last_error(e);\n \
226 return std::ptr::null_mut();\n \
227 }}\n \
228 }};",
229 name = p.name,
230 ty = p.ty,
231 )
232 }
233 })
234 .collect();
235
236 let call_args_list: Vec<String> = adapter.params.iter().map(|p| p.name.clone()).collect();
237 let call_str = call_args_list.join(", ");
238 let conversion_block = if conversions.is_empty() {
239 String::new()
240 } else {
241 format!("{}\n ", conversions.join("\n "))
242 };
243
244 format!(
245 "let client = unsafe {{ &*client }};\n \
246 {conversion_block}\
247 let rt = match tokio::runtime::Runtime::new() {{\n \
248 Ok(rt) => rt,\n \
249 Err(e) => {{\n \
250 update_last_error(e);\n \
251 return std::ptr::null_mut();\n \
252 }}\n \
253 }};\n \
254 match rt.block_on(async {{ client.inner.{core_path}({call_str}).await }}) {{\n \
255 Ok(result) => {{\n \
256 let json = serde_json::to_string(&result).unwrap_or_default();\n \
257 std::ffi::CString::new(json).unwrap_or_default().into_raw()\n \
258 }}\n \
259 Err(e) => {{\n \
260 update_last_error(e);\n \
261 std::ptr::null_mut()\n \
262 }}\n \
263 }}"
264 )
265}
266
267fn gen_go_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
272 let name = &adapter.name;
273 let prefix = config.ffi_prefix();
274 let returns = adapter.returns.as_deref().unwrap_or("string");
275 let owner_type = adapter.owner_type.as_deref().unwrap_or("Client");
276 let owner_snake = to_snake_case(owner_type);
277
278 let marshal_block: Vec<String> = adapter
279 .params
280 .iter()
281 .filter(|p| p.ty != "String" && p.ty != "&str")
282 .map(|p| {
283 format!(
284 "{name}JSON, err := json.Marshal({name})\n \
285 if err != nil {{\n \
286 return nil, err\n \
287 }}",
288 name = p.name,
289 )
290 })
291 .collect();
292
293 let c_call_args: Vec<String> = adapter
294 .params
295 .iter()
296 .map(|p| {
297 if p.ty == "String" || p.ty == "&str" {
298 format!("C.CString({})", p.name)
299 } else {
300 format!("C.CString(string({name}JSON))", name = p.name)
301 }
302 })
303 .collect();
304
305 let call_str = c_call_args.join(", ");
306 let marshal_str = if marshal_block.is_empty() {
307 String::new()
308 } else {
309 format!("{}\n ", marshal_block.join("\n "))
310 };
311
312 format!(
313 "{marshal_str}\
314 result := C.{prefix}_{owner_snake}_{name}(c.ptr, {call_str})\n \
315 if result == nil {{\n \
316 return nil, fmt.Errorf(\"%s\", lastError())\n \
317 }}\n \
318 defer C.free(unsafe.Pointer(result))\n \
319 var out {returns}\n \
320 if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {{\n \
321 return nil, err\n \
322 }}\n \
323 return &out, nil"
324 )
325}
326
327fn gen_java_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
332 let name = &adapter.name;
333 let prefix = config.ffi_prefix();
334 let owner_type = adapter.owner_type.as_deref().unwrap_or("Client");
335 let owner_snake = to_snake_case(owner_type);
336
337 let arg_pass = if adapter.params.is_empty() {
338 String::new()
339 } else {
340 format!(
341 ", {}",
342 adapter
343 .params
344 .iter()
345 .map(|p| {
346 if p.ty == "String" || p.ty == "&str" {
347 format!("arena.allocateFrom({})", p.name)
348 } else {
349 p.name.clone()
350 }
351 })
352 .collect::<Vec<_>>()
353 .join(", ")
354 )
355 };
356
357 format!(
358 "try (var arena = Arena.ofConfined()) {{\n\
359 \x20 var result = (MemorySegment) {prefix}_{owner_snake}_{name}.invokeExact(this.handle, arena{arg_pass});\n\
360 \x20 if (result.equals(MemorySegment.NULL)) {{\n\
361 \x20 throw new RuntimeException(lastError());\n\
362 \x20 }}\n\
363 \x20 return result.getString(0);\n\
364 \x20 }}"
365 )
366}
367
368fn gen_csharp_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
373 let name = &adapter.name;
374 let prefix = config.ffi_prefix();
375 let owner_type = adapter.owner_type.as_deref().unwrap_or("Client");
376 let owner_snake = to_snake_case(owner_type);
377
378 let call_args_list: Vec<String> = adapter.params.iter().map(|p| p.name.clone()).collect();
379 let call_str = call_args_list.join(", ");
380 let call_pass = if call_str.is_empty() {
381 String::new()
382 } else {
383 format!(", {}", call_str)
384 };
385
386 format!(
387 "var ptr = {prefix}_{owner_snake}_{name}_native(this.handle{call_pass});\n\
388 \x20 if (ptr == IntPtr.Zero)\n\
389 \x20 throw new InvalidOperationException(GetLastError());\n\
390 \x20 try {{ return Marshal.PtrToStringUTF8(ptr)!; }}\n\
391 \x20 finally {{ FreeString(ptr); }}"
392 )
393}
394
395fn gen_r_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
400 let core_path = &adapter.core_path;
401 let returns = adapter.returns.as_deref().unwrap_or("Robj");
402
403 let args = call_args(adapter);
404 let call_str = args.join(", ");
405
406 format!(
407 "let rt = tokio::runtime::Runtime::new()\n \
408 .map_err(|e| extendr_api::Error::Other(e.to_string()))?;\n \
409 rt.block_on(async {{ self.inner.{core_path}({call_str}).await }})\n \
410 .map({returns}::from)\n \
411 .map_err(|e| extendr_api::Error::Other(e.to_string()))"
412 )
413}
414
415fn to_snake_case(s: &str) -> String {
420 let mut result = String::new();
421 for (i, ch) in s.chars().enumerate() {
422 if ch.is_uppercase() {
423 if i > 0 {
424 result.push('_');
425 }
426 result.extend(ch.to_lowercase());
427 } else {
428 result.push(ch);
429 }
430 }
431 result
432}