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}