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}