wilton_rust/
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/wilton-iot/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_rust
27//!#[macro_use]
28//!extern crate serde_derive;
29//!extern crate wilton_rust;
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_rust::register_wiltocall("hello", |obj: MyIn| { hello(obj) });
46//!    // return success status to Wilton
47//!    wilton_rust::create_wilton_error(None)
48//!}
49//!
50//! ```
51//!
52//! See an [example](https://github.com/wilton-iot/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/wilton-iot/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<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<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]
151#[allow(private_no_mangle_fns)]
152extern "system" fn wilton_cb(
153    call_ctx: *mut c_void,
154    json_in: *const c_char,
155    json_in_len: c_int,
156    json_out: *mut *mut c_char,
157    json_out_len: *mut c_int
158) -> *mut c_char {
159    unsafe {
160        std::panic::catch_unwind(|| {
161            let data: &[u8] = if (null::<c_char>() != json_in) && (json_in_len > 0) {
162                std::slice::from_raw_parts(json_in as *const u8, json_in_len as usize)
163            } else {
164                EMPTY_JSON_INPUT.as_bytes()
165            };
166            // https://stackoverflow.com/a/32270215/314015
167            let callback_boxed_ptr = std::mem::transmute::<*mut c_void, *mut WiltonCallback>(call_ctx);
168            let callback_boxed_ref: &mut WiltonCallback = &mut *callback_boxed_ptr;
169            let callback_ref: &mut Fn(&[u8]) -> Result<String, String> = &mut **callback_boxed_ref;
170            match callback_ref(data) {
171                Ok(res) => {
172                    *json_out = copy_to_wilton_bufer(&res);
173                    *json_out_len = res.len() as c_int;
174                    null_mut::<c_char>()
175                }
176                Err(e) => copy_to_wilton_bufer(&e)
177            }
178        }).unwrap_or_else(|e| {
179            copy_to_wilton_bufer(panicmsg(&e))
180        })
181    }
182}
183
184/// Registers a closure, that can be called from JavaScript
185///
186/// This function takes a closure and registers it with Wilton, so
187/// it can be called from JavaScript using [wiltoncall](https://wilton-iot.github.io/wilton/docs/html/namespacewiltoncall.html)
188/// API.
189///
190/// Closure must take a single argument - a struct that implements [serde::Deserialize](https://docs.serde.rs/serde/trait.Deserialize.html)
191/// and must return a struct that implements [serde::Serialize](https://docs.serde.rs/serde/trait.Serialize.html).
192/// Closure input argument is converted from JavaScript object to Rust struct object.
193/// Closure output is returned to JavaScript as a JSON (that can be immediately converted to JavaScript object).
194///
195/// If closure panics, its panic message is converted into JavaScript `Error` message (that can be
196/// caugth and handled on JavaScript side).
197///
198///# Arguments
199///
200///* `name` - name this call, that should be used from JavaScript to invoke the closure
201///* `callback` - closure, that will be called from JavaScript
202///
203///# Example
204///
205/// ```
206/// // declare input/output structs
207///#[derive(Deserialize)]
208///struct MyIn { ... }
209///#[derive(Serialize)]
210///struct MyOut { ... }
211/// ...
212/// // write a function that does some work
213///fn hello(obj: MyIn) -> MyOut { ... }
214/// ...
215/// // register that function inside the `wilton_module_init` function,
216/// // that will be called by Wilton during the Rust module load
217///#[no_mangle]
218///pub extern "C" fn wilton_module_init() -> *mut std::os::raw::c_char {
219///    // register a call, error checking omitted
220///    wilton_rust::register_wiltocall("hello", |obj: MyIn| { hello(obj) });
221///    // return success status to Wilton
222///    wilton_rust::create_wilton_error(None)
223///}
224///
225/// ```
226pub fn register_wiltocall<I: serde::de::DeserializeOwned, O: serde::Serialize, F: 'static + Fn(I) -> O>(
227    name: &str,
228    callback: F
229) -> Result<(), String> {
230    unsafe {
231        use std::error::Error;
232        let name_bytes = name.as_bytes();
233        let callback_erased = move |json_in: &[u8]| -> Result<String, String> {
234            match serde_json::from_slice(json_in) {
235                Ok(obj_in) => {
236                        let obj_out = callback(obj_in);
237                        match serde_json::to_string_pretty(&obj_out) {
238                            Ok(json_out) => Ok(json_out),
239                            Err(e) => Err(String::from(e.description()))
240                        }
241                },
242                Err(e) => Err(String::from(e.description()))
243            }
244        };
245        let callback_fatty: WiltonCallback = Box::new(callback_erased);
246        let callback_slim: Box<WiltonCallback> = Box::new(callback_fatty);
247        let callback_bare: *mut WiltonCallback = Box::into_raw(callback_slim);
248        // unboxed callbacks are leaked here: 16 byte per callback
249        // it seems not easy to make their destructors to run after main
250        // https://stackoverflow.com/a/27826181/314015
251        // it may be easier to suppress wilton_module_init leaks in valgrind
252        // let callback_unleak = Box::from_raw(callback_bare);
253
254        let err: *mut c_char = wiltoncall_register(
255            name_bytes.as_ptr() as *const c_char,
256            name_bytes.len() as c_int,
257            callback_bare as *mut c_void,
258            wilton_cb);
259
260        if null_mut::<c_char>() != err {
261            Err(convert_wilton_error(err))
262        } else {
263            Ok(())
264        }
265    }
266}
267
268/// Create an error message, that can be passed back to Wilton
269///
270/// Helper function, that can be used with Rust `Result`s, returned
271/// from `wilton_rust::register_wiltoncall` function.
272///
273///# Arguments
274///
275///* `error_opt` - optional error message, that should be passed back to Wilton
276///
277///# Example
278///
279///```
280/// // register a call
281///let res = wilton_rust::register_wiltocall("hello", |obj: MyObj1| { hello(obj) });
282///
283/// // check for error
284///if res.is_err() {
285///    // return error message to Wilton
286///    return wilton_rust::create_wilton_error(res.err());
287///}
288///
289/// // return success status to Wilton
290///wilton_rust::create_wilton_error(None)
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_rust::runscript(&call_desc) {
325///    Ok(res) => res,
326///    Err(e) => panic!(e)
327///}
328///```
329///
330pub fn runscript(call_desc: &serde_json::Value) -> Result<String, String> {
331    unsafe {
332        use std::error::Error;
333        match serde_json::to_string_pretty(&call_desc) {
334            Err(e) => Err(String::from(e.description())),
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.description())),
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}