diff --git a/Cargo.toml b/Cargo.toml index d8c6bd5..060baf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "duktape-rs" -version = "0.0.1" +version = "0.0.2" authors = ["envis10n "] edition = "2018" repository = "https://gitlab.com/envis10n/duktape-rs" diff --git a/README.md b/README.md index da22dd3..d21918f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,51 @@ -# duktape-rs +# duktape-rs -Safe(er) rust wrapper for dukbind. +*Safe(er) rust wrapper for dukbind.* -This is currently a work in progress. +## Work In Progress +This library is a work in progress and is currently limited in features. -Returned values from eval are a wrapper for serde_json::Value (as DukValue::Val(serde_json::Value)) that also provide None, NaN, and Infinity. \ No newline at end of file +## What can it do? +At the moment, duktape-rs + + - [x] Provides a safe* wrapper around dukbind (raw FFI bindings for duktape). + - [x] Provides manageable value returns that can be modified and passed back to the duktape context. + - [x] Supports heap pointers (for objects), including setting and getting properties of an object (as DukValue). + - [x] Can eval a &str and return the result (DukResult) + - [x] Supports handling (what I assume to be) most JS errors that crop up during eval *minimally tested* + + *Safety not guaranteed +## Where are the docs? +For some reason docs.rs has a problem with compiling dukbind and won't generate them :/ +Check back another time for documentation *Coming Soon™* + +## Basics + + extern crate duktape-rs; + + use duktape-rs::DukContext; + + fn main() { + // Create a new context + let mut ctx = DukContext::new(); + + // Eval 5+5 + let val = ctx.eval_string("5+5").unwrap(); + + // Destroy the heap (do this when you are done using the context) + ctx.destroy(); + + // Compare the value as an i64 against 10 + assert_eq!(val.as_i64().expect("Not an i64"), 10) + } + +## Objects +Objects in duktape are returned as heap pointers that have to be stored and returned as a wrapper around that pointer. + + let mut ctx = DukContext::new(); + + let obj = ctx.eval_string("({ok: true})").unwrap().as_object().expect("Not an object"); + + let val = obj.get_prop("ok").unwrap().as_bool().expect("Not a bool"); + + println!("Value: {}", val); //-> Value: true diff --git a/src/lib.rs b/src/lib.rs index d093e5a..c356735 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,8 @@ pub enum DukErrorCode { Range = DUK_ERR_RANGE_ERROR, Syntax = DUK_ERR_SYNTAX_ERROR, Type = DUK_ERR_TYPE_ERROR, - URI = DUK_ERR_URI_ERROR + URI = DUK_ERR_URI_ERROR, + NullPtr } #[derive(Clone, Debug)] @@ -82,6 +83,29 @@ pub struct DukObject { } impl DukObject { + /// Encode to JSON string. + pub fn encode(&mut self) -> Option { + unsafe { + match self.context.ctx { + Some(ctx) => { + let idx = duk_push_heapptr(ctx, self.heap); + if duk_is_undefined(ctx, idx) == 0 { + duk_dup(ctx, idx); + let raw = duk_json_encode(ctx, -1); + use std::ffi::CStr; + let t = CStr::from_ptr(raw); + let cow = t.to_string_lossy(); + duk_pop_2(ctx); + Some(String::from(cow)) + } else { + duk_pop(ctx); + None + } + }, + None => None + } + } + } pub fn get_prop(&mut self, name: &str) -> DukResult { unsafe { let ctx = self.context.ctx.expect("Invalid context pointer."); @@ -95,6 +119,59 @@ impl DukObject { } } } + pub fn set_prop(&mut self, name: &str, value: DukValue) -> DukResult<()> { + match self.context.ctx { + Some(ctx) => { + unsafe { + duk_push_heapptr(ctx, self.heap); + if duk_is_undefined(ctx, -1) == 0 { + let mut ok: bool = true; + match value { + DukValue::Undefined => duk_push_undefined(ctx), + DukValue::Null => duk_push_null(ctx), + DukValue::Number(ref n) => { + if n.is_nan() { + duk_push_nan(ctx); + } else if n.is_infinity() { + duk_push_lstring(ctx, "Infinity".as_ptr() as *const i8, "Infinity".len() as duk_size_t); + } else { + duk_push_number(ctx, n.as_f64()); + } + }, + DukValue::Boolean(b) => duk_push_boolean(ctx, value.as_duk_bool().expect("Not a boolean!")), + DukValue::String(s) => { + let t = &s; + duk_push_lstring(ctx, t.as_ptr() as *const i8, t.len() as duk_size_t); + }, + DukValue::Object(ref o) => { + duk_push_heapptr(ctx, o.heap); + if duk_is_undefined(ctx, -1) == 1 { + duk_pop(ctx); + ok = false; + } + } + }; + if ok { + if duk_put_prop_lstring(ctx, -2, name.as_ptr() as *const i8, name.len() as duk_size_t) == 1 { + duk_pop_2(ctx); + Ok(()) + } else { + duk_pop_2(ctx); + Err(DukError::from(DukErrorCode::Error, "Failed to set prop.")) + } + } else { + duk_pop(ctx); + Err(DukError::from(DukErrorCode::Error, "Error setting prop.")) + } + } else { + duk_pop(ctx); + Err(DukError::from(DukErrorCode::NullPtr, "Invalid heap pointer.")) + } + } + }, + None => Err(DukError::from(DukErrorCode::NullPtr, "Invalid context pointer.")) + } + } pub fn new(context: DukContext) -> DukObject { unsafe { let ctx = context.ctx.expect("Invalid context pointer."); @@ -129,6 +206,18 @@ impl DukValue { DukValue::Object(ref _o) => Some(String::from("[object]")) } } + pub fn as_duk_bool(&self) -> Option { + match self { + DukValue::Boolean(b) => { + if *b { + Some(1) + } else { + Some(0) + } + }, + _ => None + } + } pub fn as_bool(&self) -> Option { match self { DukValue::Boolean(b) => Some(*b), @@ -167,6 +256,12 @@ impl DukValue { _ => false } } + pub fn is_bool(&self) -> bool { + match self { + DukValue::Boolean(_b) => true, + _ => false + } + } pub fn as_i64(&self) -> Option { match self { DukValue::Number(ref n) => Some(n.as_i64()), @@ -241,6 +336,18 @@ impl DukContext { self.ctx = None; } } + fn decode_json(&mut self, json: &str) -> DukValue { + match self.ctx { + Some(ctx) => { + unsafe { + duk_push_lstring(ctx, json.as_ptr() as *const i8, json.len() as duk_size_t); + duk_json_decode(ctx, -1); + self.get_value() + } + }, + None => DukValue::Undefined + } + } fn get_value(&mut self) -> DukValue { unsafe { let t = duk_get_type(self.ctx.expect("Invalid context pointer"), -1); @@ -310,43 +417,10 @@ mod tests { use super::*; #[test] fn test_eval_ret() { - let mut ctx = DukContext::new(); - let mut res = ctx.eval_string("({'ok': true})").expect("Eval error!"); - let o: &mut DukObject = res.as_object().expect("Should be an object here."); - match o.get_prop("ok") { - Ok(r) => { - println!("Test OK {}", r.as_bool().expect("NOT A BOOL")); - ctx.destroy(); - }, - Err(e) => { - println!("Error: {:?}", e); - ctx.destroy(); - } - } - } - #[test] - fn test_eval_ret_multi() { - let mut ctx = DukContext::new(); - let mut res = ctx.eval_string("({derp:{ok: true}})").expect("Eval error!"); - let o: &mut DukObject = res.as_object().expect("Should be an object here."); - match o.get_prop("derp") { - Ok(mut r) => { - let o2: &mut DukObject = r.as_object().expect("Should be another object here!"); - match o2.get_prop("ok") { - Ok(r2) => { - println!("Test OK: {}", r2.as_bool().expect("Should be a bool here.")); - ctx.destroy(); - }, - Err(e) => { - ctx.destroy(); - panic!("{:?}", e); - } - } - }, - Err(e) => { - ctx.destroy(); - panic!("Error: {:?}", e); - } - } + // Create a new context + let mut ctx = DukContext::new(); + // Eval 5+5 + let val = ctx.eval_string("5+5").unwrap(); + assert_eq!(val.as_i64().expect("Not an i64"), 10) } }