wilton_rusty/lib.rs
1/*
2 * Copyright 2018, alex at staticlibs.net
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Rust modules support for [Wilton JavaScript runtime](https://github.com/wiltonruntime/wilton)
18//!
19//! Usage example:
20//!
21//! ```
22//!// configure Cargo to build a shared library
23//!//[lib]
24//!//crate-type = ["dylib"]
25//!
26//!// in lib.rs, import serde and wilton_rusty
27//!//#[macro_use]
28//!//extern crate serde_derive;
29//!//extern crate wilton_rusty;
30//!// ...
31//!// declare input/output structs
32//!#[derive(Deserialize)]
33//!struct MyIn { }
34//!#[derive(Serialize)]
35//!struct MyOut { }
36//!
37//!// write a function that does some work
38//!fn hello(obj: MyIn) -> MyOut { }
39//!
40//!// register that function inside the `wilton_module_init` function,
41//!// that will be called by Wilton during the Rust module load
42//!#[no_mangle]
43//!pub extern "C" fn wilton_module_init() -> *mut std::os::raw::c_char {
44//! // register a call, error checking omitted
45//! wilton_rusty::register_wiltocall("hello", |obj: MyIn| { hello(obj) });
46//! // return success status to Wilton
47//! wilton_rusty::create_wilton_error(None)
48//!}
49//!
50//! ```
51//!
52//! See an [example](https://github.com/wiltonruntime/wilton_examples/blob/master/rust/test.js#L17)
53//! how to load and use Rust library from JavaScript.
54
55extern crate serde;
56extern crate serde_json;
57
58use std::os::raw::*;
59use std::ptr::null;
60use std::ptr::null_mut;
61
62
63// wilton C API import
64// https://github.com/wiltonruntime/wilton_core/tree/master/include/wilton
65
66extern "system" {
67
68fn wilton_alloc(
69 size_bytes: c_int
70) -> *mut c_char;
71
72fn wilton_free(
73 buffer: *mut c_char
74) -> ();
75
76fn wiltoncall_register(
77 call_name: *const c_char,
78 call_name_len: c_int,
79 call_ctx: *mut c_void,
80 call_cb: extern "system" fn(
81 call_ctx: *mut c_void,
82 json_in: *const c_char,
83 json_in_len: c_int,
84 json_out: *mut *mut c_char,
85 json_out_len: *mut c_int
86 ) -> *mut c_char
87) -> *mut c_char;
88
89fn wiltoncall_runscript(
90 script_engine_name: *const c_char,
91 script_engine_name_len: c_int,
92 json_in: *const c_char,
93 json_in_len: c_int,
94 json_out: *mut *mut c_char,
95 json_out_len: *mut c_int
96) -> *mut c_char;
97
98}
99
100
101static EMPTY_JSON_INPUT: &'static str = "{}";
102type WiltonCallback = Box<dyn Fn(&[u8]) -> Result<String, String>>;
103
104
105// helper functions
106
107fn copy_to_wilton_bufer(data: &str) -> *mut c_char {
108 unsafe {
109 let res: *mut c_char = wilton_alloc((data.len() + 1) as c_int);
110 std::ptr::copy_nonoverlapping(data.as_ptr() as *const c_char, res, data.len());
111 *res.offset(data.len() as isize) = '\0' as c_char;
112 res
113 }
114}
115
116fn convert_wilton_error(err: *mut c_char) -> String {
117 unsafe {
118 use std::ffi::CStr;
119 if null::<c_char>() != err {
120 let res = match CStr::from_ptr(err).to_str() {
121 Ok(val) => String::from(val),
122 // generally cannot happen
123 Err(_) => String::from("Unknown error")
124 };
125 wilton_free(err);
126 res
127 } else {
128 // generally cannot happen
129 String::from("No error")
130 }
131 }
132}
133
134// https://github.com/rustytools/errloc_macros/blob/79f5378e913293cb1b4a561fb7dc8d5cbcd09bc6/src/lib.rs#L44
135fn panicmsg<'a>(e: &'a std::boxed::Box<dyn std::any::Any + std::marker::Send + 'static>) -> &'a str {
136 match e.downcast_ref::<&str>() {
137 Some(st) => st,
138 None => {
139 match e.downcast_ref::<std::string::String>() {
140 Some(stw) => stw.as_str(),
141 None => "()",
142 }
143 },
144 }
145}
146
147
148// callback that is passed to wilton
149
150#[no_mangle]
151extern "system" fn wilton_cb(
152 call_ctx: *mut c_void,
153 json_in: *const c_char,
154 json_in_len: c_int,
155 json_out: *mut *mut c_char,
156 json_out_len: *mut c_int
157) -> *mut c_char {
158 unsafe {
159 std::panic::catch_unwind(|| {
160 let data: &[u8] = if (null::<c_char>() != json_in) && (json_in_len > 0) {
161 std::slice::from_raw_parts(json_in as *const u8, json_in_len as usize)
162 } else {
163 EMPTY_JSON_INPUT.as_bytes()
164 };
165 // https://stackoverflow.com/a/32270215/314015
166 let callback_boxed_ptr = std::mem::transmute::<*mut c_void, *mut WiltonCallback>(call_ctx);
167 let callback_boxed_ref: &mut WiltonCallback = &mut *callback_boxed_ptr;
168 let callback_ref: &mut dyn Fn(&[u8]) -> Result<String, String> = &mut **callback_boxed_ref;
169 match callback_ref(data) {
170 Ok(res) => {
171 *json_out = copy_to_wilton_bufer(&res);
172 *json_out_len = res.len() as c_int;
173 null_mut::<c_char>()
174 }
175 Err(e) => copy_to_wilton_bufer(&e)
176 }
177 }).unwrap_or_else(|e| {
178 copy_to_wilton_bufer(panicmsg(&e))
179 })
180 }
181}
182
183/// Registers a closure, that can be called from JavaScript
184///
185/// This function takes a closure and registers it with Wilton, so
186/// it can be called from JavaScript using [wiltoncall](https://wiltonruntime.github.io/wilton/docs/html/namespacewiltoncall.html)
187/// API.
188///
189/// Closure must take a single argument - a struct that implements [serde::Deserialize](https://docs.serde.rs/serde/trait.Deserialize.html)
190/// and must return a struct that implements [serde::Serialize](https://docs.serde.rs/serde/trait.Serialize.html).
191/// Closure input argument is converted from JavaScript object to Rust struct object.
192/// Closure output is returned to JavaScript as a JSON (that can be immediately converted to JavaScript object).
193///
194/// If closure panics, its panic message is converted into JavaScript `Error` message (that can be
195/// caugth and handled on JavaScript side).
196///
197///# Arguments
198///
199///* `name` - name this call, that should be used from JavaScript to invoke the closure
200///* `callback` - closure, that will be called from JavaScript
201///
202///# Example
203///
204/// ```
205/// // declare input/output structs
206///#[derive(Deserialize)]
207///struct MyIn { }
208///#[derive(Serialize)]
209///struct MyOut { }
210///
211/// // write a function that does some work
212///fn hello(obj: MyIn) -> MyOut { }
213///
214/// // register that function inside the `wilton_module_init` function,
215/// // that will be called by Wilton during the Rust module load
216///#[no_mangle]
217///pub extern "C" fn wilton_module_init() -> *mut std::os::raw::c_char {
218/// // register a call, error checking omitted
219/// wilton_rusty::register_wiltocall("hello", |obj: MyIn| { hello(obj) });
220/// // return success status to Wilton
221/// wilton_rusty::create_wilton_error(None)
222///}
223///
224/// ```
225pub fn register_wiltocall<I: serde::de::DeserializeOwned, O: serde::Serialize, F: 'static + Fn(I) -> O>(
226 name: &str,
227 callback: F
228) -> Result<(), String> {
229 unsafe {
230 let name_bytes = name.as_bytes();
231 let callback_erased = move |json_in: &[u8]| -> Result<String, String> {
232 match serde_json::from_slice(json_in) {
233 Ok(obj_in) => {
234 let obj_out = callback(obj_in);
235 match serde_json::to_string_pretty(&obj_out) {
236 Ok(json_out) => Ok(json_out),
237 Err(e) => Err(String::from(e.to_string()))
238 }
239 },
240 Err(e) => Err(String::from(e.to_string()))
241 }
242 };
243 let callback_fatty: WiltonCallback = Box::new(callback_erased);
244 let callback_slim: Box<WiltonCallback> = Box::new(callback_fatty);
245 let callback_bare: *mut WiltonCallback = Box::into_raw(callback_slim);
246 // unboxed callbacks are leaked here: 16 byte per callback
247 // it seems not easy to make their destructors to run after main
248 // https://stackoverflow.com/a/27826181/314015
249 // it may be easier to suppress wilton_module_init leaks in valgrind
250 // let callback_unleak = Box::from_raw(callback_bare);
251
252 let err: *mut c_char = wiltoncall_register(
253 name_bytes.as_ptr() as *const c_char,
254 name_bytes.len() as c_int,
255 callback_bare as *mut c_void,
256 wilton_cb);
257
258 if null_mut::<c_char>() != err {
259 Err(convert_wilton_error(err))
260 } else {
261 Ok(())
262 }
263 }
264}
265
266/// Create an error message, that can be passed back to Wilton
267///
268/// Helper function, that can be used with Rust `Result`s, returned
269/// from `wilton_rusty::register_wiltoncall` function.
270///
271///# Arguments
272///
273///* `error_opt` - optional error message, that should be passed back to Wilton
274///
275///# Example
276///
277///```
278/// // register a call
279///let res = wilton_rusty::register_wiltocall("hello", |obj: MyObj1| { hello(obj) });
280///
281/// // check for error
282///if res.is_err() {
283/// // return error message to Wilton
284/// // return wilton_rusty::create_wilton_error(res.err());
285/// ()
286///}
287///
288/// // return success status to Wilton
289///wilton_rusty::create_wilton_error(None)
290///()
291///```
292///
293pub fn create_wilton_error(error_opt: Option<String>) -> *mut c_char {
294 match error_opt {
295 Some(msg) => copy_to_wilton_bufer(&msg),
296 None => null_mut::<c_char>()
297 }
298}
299
300/// Call JavaScript function
301///
302/// Allows to call a specified JavaScript function
303/// passing arguments as a list of JSON values and receiving
304/// result as a `String`.
305///
306///# Arguments (JSON call descriptor fields)
307///
308///* `module` - name of the RequireJS module
309///* `func` - name of the function field in the module object (optional: not needed if module
310/// itself is a function)
311///* `args` - function arguments (optional)
312///
313///# Example
314///
315///```
316/// // create call descriptor
317///let call_desc = json!({
318/// "module": "lodash/string",
319/// "func": "capitalize",
320/// "args": [msg]
321///});
322///
323/// // perform the call and check the results
324///match wilton_rusty::runscript(&call_desc) {
325/// Ok(res) => res,
326/// Err(e) => panic!(e)
327///}
328///()
329///```
330///
331pub fn runscript(call_desc: &serde_json::Value) -> Result<String, String> {
332 unsafe {
333 match serde_json::to_string_pretty(&call_desc) {
334 Err(e) => Err(String::from(e.to_string())),
335 Ok(ref mut json) => {
336 let empty = String::new();
337 json.push('\0'); // required by some of JS engines
338 let json_bytes = json.as_bytes();
339 let mut out: *mut c_char = null_mut::<c_char>();
340 let mut out_len: c_int = 0;
341 let err: *mut c_char = wiltoncall_runscript(
342 empty.as_bytes().as_ptr() as *const c_char,
343 0 as c_int,
344 json_bytes.as_ptr() as *const c_char,
345 (json_bytes.len() - 1) as c_int,
346 &mut out as *mut *mut c_char,
347 &mut out_len as *mut c_int);
348 if null_mut::<c_char>() != err {
349 Err(convert_wilton_error(err))
350 } else {
351 let res = if (null_mut::<c_char>() != out) && (out_len > 0) {
352 let slice = std::slice::from_raw_parts(out as *const u8, out_len as usize);
353 let vec = slice.to_vec();
354 match String::from_utf8(vec) {
355 Err(e) => Err(String::from(e.to_string())),
356 Ok(str) => Ok(str)
357 }
358 } else {
359 Ok(String::new())
360 };
361 if null_mut::<c_char>() != out {
362 wilton_free(out);
363 }
364 res
365 }
366 }
367 }
368 }
369}