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