Skip to content

Commit bb6d655

Browse files
authored
fix(tesseract): Handle JS exceptions in Rust with safe call (#9677)
1 parent 98d334b commit bb6d655

File tree

6 files changed

+160
-23
lines changed

6 files changed

+160
-23
lines changed

packages/cubejs-backend-native/js/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,8 +450,18 @@ export const sql4sql = async (instance: SqlInterfaceInstance, sqlQuery: string,
450450

451451
export const buildSqlAndParams = (cubeEvaluator: any): String => {
452452
const native = loadNative();
453-
454-
return native.buildSqlAndParams(cubeEvaluator);
453+
const safeCallFn = (fn: Function, thisArg: any, ...args: any[]) => {
454+
try {
455+
return {
456+
result: fn.apply(thisArg, args),
457+
};
458+
} catch (e: any) {
459+
return {
460+
error: e.toString(),
461+
};
462+
}
463+
};
464+
return native.buildSqlAndParams(cubeEvaluator, safeCallFn);
455465
};
456466

457467
export type ResultRow = Record<string, string>;

packages/cubejs-backend-native/src/node_export.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,18 @@ fn build_sql_and_params(cx: FunctionContext) -> JsResult<JsValue> {
585585
.unwrap()?,
586586
));
587587

588+
let safe_call_fn = neon_context_holder
589+
.with_context(|cx| {
590+
if let Ok(func) = cx.argument::<JsFunction>(1) {
591+
Some(func)
592+
} else {
593+
None
594+
}
595+
})
596+
.unwrap();
597+
598+
neon_context_holder.set_safe_call_fn(safe_call_fn).unwrap();
599+
588600
let context_holder = NativeContextHolder::<NeonInnerTypes<FunctionContext<'static>>>::new(
589601
neon_context_holder,
590602
);

rust/cubenativeutils/src/wrappers/neon/context.rs

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,81 @@ impl<'cx> NoenContextLifetimeExpand<'cx> for FunctionContext<'cx> {
2929
}
3030
}
3131

32+
pub struct SafeCallFn<'a> {
33+
safe_fn: &'a Option<Handle<'static, JsFunction>>,
34+
}
35+
36+
impl<'a> SafeCallFn<'a> {
37+
pub fn new(safe_fn: &'a Option<Handle<'static, JsFunction>>) -> Self {
38+
Self { safe_fn }
39+
}
40+
41+
pub fn safe_call<C: Context<'static>, T: Value>(
42+
&self,
43+
cx: &mut C,
44+
func: &Handle<'static, JsFunction>,
45+
this: Handle<'static, T>,
46+
mut args: Vec<Handle<'static, JsValue>>,
47+
) -> Result<Handle<'static, JsValue>, CubeError> {
48+
if let Some(safe_fn) = self.safe_fn {
49+
args.insert(0, this.upcast());
50+
51+
args.insert(0, func.upcast());
52+
53+
let res = safe_fn
54+
.call(cx, this, args)
55+
.map_err(|_| CubeError::internal(format!("Failed to call safe function")))?;
56+
let res = res.downcast::<JsObject, _>(cx).map_err(|_| {
57+
CubeError::internal(format!("Result of safe function call should be object"))
58+
})?;
59+
let result_field = res.get_value(cx, "result").map_err(|_| {
60+
CubeError::internal(format!(
61+
"Failed wile get `result` field of safe call function result"
62+
))
63+
})?;
64+
let err_field = res.get_value(cx, "error").map_err(|_| {
65+
CubeError::internal(format!(
66+
"Failed wile get `error` field of safe call function result"
67+
))
68+
})?;
69+
if !err_field.is_a::<JsUndefined, _>(cx) {
70+
let error_string = err_field.downcast::<JsString, _>(cx).map_err(|_| {
71+
CubeError::internal(format!(
72+
"Error in safe call function result should be string"
73+
))
74+
})?;
75+
Err(CubeError::internal(error_string.value(cx)))
76+
} else if !result_field.is_a::<JsUndefined, _>(cx) {
77+
Ok(result_field)
78+
} else {
79+
Err(CubeError::internal(format!(
80+
"Safe call function should return object with result or error field"
81+
)))
82+
}
83+
} else {
84+
let res = func
85+
.call(cx, this, args)
86+
.map_err(|_| CubeError::internal(format!("Failed to call function")))?;
87+
Ok(res)
88+
}
89+
}
90+
}
91+
3292
pub struct ContextWrapper<C: Context<'static>> {
3393
cx: C,
94+
safe_call_fn: Option<Handle<'static, JsFunction>>,
3495
}
3596

3697
impl<C: Context<'static>> ContextWrapper<C> {
3798
pub fn new(cx: C) -> Rc<RefCell<Self>> {
38-
Rc::new(RefCell::new(Self { cx }))
99+
Rc::new(RefCell::new(Self {
100+
cx,
101+
safe_call_fn: None,
102+
}))
103+
}
104+
105+
pub fn set_safe_call_fn(&mut self, fn_handle: Option<Handle<'static, JsFunction>>) {
106+
self.safe_call_fn = fn_handle;
39107
}
40108

41109
pub fn with_context<T, F>(&mut self, f: F) -> T
@@ -45,6 +113,14 @@ impl<C: Context<'static>> ContextWrapper<C> {
45113
f(&mut self.cx)
46114
}
47115

116+
pub fn with_context_and_safe_fn<T, F>(&mut self, f: F) -> T
117+
where
118+
F: FnOnce(&mut C, SafeCallFn) -> T,
119+
{
120+
let safe_call_fn = SafeCallFn::new(&self.safe_call_fn);
121+
f(&mut self.cx, safe_call_fn)
122+
}
123+
48124
pub fn get_context(&mut self) -> &mut C {
49125
&mut self.cx
50126
}
@@ -116,6 +192,36 @@ impl<C: Context<'static>> ContextHolder<C> {
116192
))
117193
}
118194
}
195+
196+
pub fn with_context_and_safe_fn<T, F>(&self, f: F) -> Result<T, CubeError>
197+
where
198+
F: FnOnce(&mut C, SafeCallFn) -> T,
199+
{
200+
if let Some(context) = self.context.upgrade() {
201+
let mut cx = context.borrow_mut();
202+
let res = cx.with_context_and_safe_fn(f);
203+
Ok(res)
204+
} else {
205+
Err(CubeError::internal(format!(
206+
"Call to neon context outside of its lifetime"
207+
)))
208+
}
209+
}
210+
211+
pub fn set_safe_call_fn(
212+
&self,
213+
f: Option<Handle<'static, JsFunction>>,
214+
) -> Result<(), CubeError> {
215+
if let Some(context) = self.context.upgrade() {
216+
let mut cx = context.borrow_mut();
217+
cx.set_safe_call_fn(f);
218+
Ok(())
219+
} else {
220+
Err(CubeError::internal(format!(
221+
"Call to neon context outside of its lifetime"
222+
)))
223+
}
224+
}
119225
}
120226

121227
impl<C: Context<'static> + 'static> NativeContext<NeonInnerTypes<C>> for ContextHolder<C> {

rust/cubenativeutils/src/wrappers/neon/object/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ use self::{
1010
neon_struct::NeonStruct,
1111
};
1212
use super::inner_types::NeonInnerTypes;
13-
use crate::wrappers::{neon::context::ContextHolder, object::NativeObject};
13+
use crate::wrappers::{
14+
neon::context::{ContextHolder, SafeCallFn},
15+
object::NativeObject,
16+
};
1417
use cubesql::CubeError;
1518
use neon::prelude::*;
1619

@@ -64,6 +67,14 @@ impl<C: Context<'static> + 'static, V: Value + 'static> NeonTypeHandle<C, V> {
6467
})?
6568
}
6669

70+
pub fn map_neon_object_with_safe_call_fn<T, F>(&self, f: F) -> Result<T, CubeError>
71+
where
72+
F: FnOnce(&mut C, &Handle<'static, V>, SafeCallFn) -> T,
73+
{
74+
self.context
75+
.with_context_and_safe_fn(|cx, safe_call_fn| f(cx, &self.object, safe_call_fn))
76+
}
77+
6778
pub fn is_a<U: Value>(&self) -> Result<bool, CubeError> {
6879
self.context.with_context(|cx| self.object.is_a::<U, _>(cx))
6980
}

rust/cubenativeutils/src/wrappers/neon/object/neon_function.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ impl<C: Context<'static> + 'static> NativeFunction<NeonInnerTypes<C>> for NeonFu
3535
.into_iter()
3636
.map(|arg| -> Result<_, CubeError> { Ok(arg.into_object().get_object()) })
3737
.collect::<Result<Vec<_>, _>>()?;
38-
let neon_reuslt = self.object.map_neon_object(|cx, neon_object| {
39-
let null = cx.null();
40-
neon_object
41-
.call(cx, null, neon_args)
42-
.map_err(|_| CubeError::internal("Failed to call function ".to_string()))
43-
})??;
38+
let neon_reuslt =
39+
self.object
40+
.map_neon_object_with_safe_call_fn(|cx, neon_object, safe_call_fn| {
41+
let null = cx.null();
42+
safe_call_fn.safe_call(cx, neon_object, null, neon_args)
43+
})??;
4444
Ok(NativeObjectHandle::new(NeonObject::new(
4545
self.object.context.clone(),
4646
neon_reuslt,

rust/cubenativeutils/src/wrappers/neon/object/neon_struct.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,17 @@ impl<C: Context<'static> + 'static> NativeStruct<NeonInnerTypes<C>> for NeonStru
9797
.map(|arg| -> Result<_, CubeError> { Ok(arg.into_object().get_object()) })
9898
.collect::<Result<Vec<_>, _>>()?;
9999

100-
let neon_reuslt = self.object.map_neon_object(|cx, neon_object| {
101-
let neon_method = neon_object
102-
.get::<JsFunction, _, _>(cx, method)
103-
.map_err(|_| CubeError::internal(format!("Method `{}` not found", method)))?;
104-
neon_method
105-
.call(cx, *neon_object, neon_args)
106-
.map_err(|err| {
107-
CubeError::internal(format!(
108-
"Failed to call method `{} {} {:?}",
109-
method, err, err
110-
))
111-
})
112-
})??;
100+
let neon_reuslt =
101+
self.object
102+
.map_neon_object_with_safe_call_fn(|cx, neon_object, safe_call_fn| {
103+
let neon_method =
104+
neon_object
105+
.get::<JsFunction, _, _>(cx, method)
106+
.map_err(|_| {
107+
CubeError::internal(format!("Method `{}` not found", method))
108+
})?;
109+
safe_call_fn.safe_call(cx, &neon_method, *neon_object, neon_args)
110+
})??;
113111
Ok(NativeObjectHandle::new(NeonObject::new(
114112
self.object.context.clone(),
115113
neon_reuslt,

0 commit comments

Comments
 (0)