Skip to content

Commit c6069cd

Browse files
authored
feat(native): Support SafeString for Python & Jinja (#7242)
1 parent f9889d4 commit c6069cd

File tree

13 files changed

+97
-360
lines changed

13 files changed

+97
-360
lines changed

packages/cubejs-backend-native/Cargo.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cubejs-backend-native/python/cube/src/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,14 @@ def context_func(func):
226226
func.cube_context_func = True
227227
return func
228228

229+
class SafeString(str):
230+
is_safe: bool
231+
232+
def __init__(self, v: str):
233+
self.is_safe = True
234+
229235
__all__ = [
230236
'context_func',
231-
'TemplateContext'
237+
'TemplateContext',
238+
'SafeString',
232239
]

packages/cubejs-backend-native/src/cross/clrepr.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ impl std::fmt::Display for CLReprObject {
5656
}
5757
}
5858

59-
#[allow(unused)]
6059
#[derive(Debug)]
6160
pub enum CLReprKind {
6261
String,
@@ -68,17 +67,25 @@ pub enum CLReprKind {
6867
Object,
6968
JsFunction,
7069
#[cfg(feature = "python")]
70+
#[allow(unused)]
7171
PythonRef,
7272
Null,
7373
}
7474

75+
#[derive(Debug, Clone)]
76+
pub enum StringType {
77+
Normal,
78+
#[allow(unused)]
79+
Safe,
80+
}
81+
7582
/// Cross language representation is abstraction to transfer values between
7683
/// JavaScript and Python across Rust. Converting between two different languages requires
7784
/// to use Context which is available on the call (one for python and one for js), which result as
7885
/// blocking.
7986
#[derive(Debug, Clone)]
8087
pub enum CLRepr {
81-
String(String),
88+
String(String, StringType),
8289
Bool(bool),
8390
Float(f64),
8491
Int(i64),
@@ -118,7 +125,7 @@ impl CLRepr {
118125
#[allow(unused)]
119126
pub fn kind(&self) -> CLReprKind {
120127
match self {
121-
CLRepr::String(_) => CLReprKind::String,
128+
CLRepr::String(_, _) => CLReprKind::String,
122129
CLRepr::Bool(_) => CLReprKind::Bool,
123130
CLRepr::Float(_) => CLReprKind::Float,
124131
CLRepr::Int(_) => CLReprKind::Int,
@@ -139,7 +146,7 @@ impl CLRepr {
139146
) -> Result<Self, Throw> {
140147
if from.is_a::<JsString, _>(cx) {
141148
let v = from.downcast_or_throw::<JsString, _>(cx)?;
142-
Ok(CLRepr::String(v.value(cx)))
149+
Ok(CLRepr::String(v.value(cx), StringType::Normal))
143150
} else if from.is_a::<JsArray, _>(cx) {
144151
let v = from.downcast_or_throw::<JsArray, _>(cx)?;
145152
let el = v.to_vec(cx)?;
@@ -205,7 +212,7 @@ impl CLRepr {
205212
#[cfg(feature = "python")] tcx: IntoJsContext,
206213
) -> JsResult<'a, JsValue> {
207214
Ok(match from {
208-
CLRepr::String(v) => cx.string(v).upcast(),
215+
CLRepr::String(v, _) => cx.string(v).upcast(),
209216
CLRepr::Bool(v) => cx.boolean(v).upcast(),
210217
CLRepr::Float(v) => cx.number(v).upcast(),
211218
CLRepr::Int(v) => cx.number(v as f64).upcast(),

packages/cubejs-backend-native/src/cross/clrepr_python.rs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::cross::clrepr::CLReprObject;
2-
use crate::cross::CLRepr;
2+
use crate::cross::{CLRepr, StringType};
33
use pyo3::exceptions::{PyNotImplementedError, PyTypeError};
44
use pyo3::types::{
5-
PyBool, PyComplex, PyDate, PyDict, PyFloat, PyFrame, PyFunction, PyInt, PyList, PySet,
6-
PyString, PyTraceback, PyTuple,
5+
PyBool, PyComplex, PyDate, PyDict, PyFloat, PyFrame, PyFunction, PyInt, PyList, PySequence,
6+
PySet, PyString, PyTraceback, PyTuple,
77
};
88
use pyo3::{AsPyPointer, Py, PyAny, PyErr, PyObject, Python, ToPyObject};
99

@@ -30,7 +30,13 @@ impl CLReprPython for CLRepr {
3030
}
3131

3232
return Ok(if v.get_type().is_subclass_of::<PyString>()? {
33-
Self::String(v.to_string())
33+
let string_type = if v.hasattr("is_safe")? {
34+
StringType::Safe
35+
} else {
36+
StringType::Normal
37+
};
38+
39+
Self::String(v.to_string(), string_type)
3440
} else if v.get_type().is_subclass_of::<PyBool>()? {
3541
Self::Bool(v.downcast::<PyBool>()?.is_true())
3642
} else if v.get_type().is_subclass_of::<PyFloat>()? {
@@ -92,19 +98,28 @@ impl CLReprPython for CLRepr {
9298
"Unable to represent PyDate type as CLR from Python".to_string(),
9399
));
94100
} else if v.get_type().is_subclass_of::<PyFrame>()? {
95-
return Err(PyErr::new::<PyTypeError, _>(
96-
"Unable to represent PyFrame type as CLR from Python".to_string(),
97-
));
101+
let frame = v.downcast::<PyFrame>()?;
102+
103+
return Err(PyErr::new::<PyTypeError, _>(format!(
104+
"Unable to represent PyFrame type as CLR from Python, value: {:?}",
105+
frame
106+
)));
98107
} else if v.get_type().is_subclass_of::<PyTraceback>()? {
99-
return Err(PyErr::new::<PyTypeError, _>(
100-
"Unable to represent PyTraceback type as CLR from Python".to_string(),
101-
));
108+
let trb = v.downcast::<PyTraceback>()?;
109+
110+
return Err(PyErr::new::<PyTypeError, _>(format!(
111+
"Unable to represent PyTraceback type as CLR from Python, value: {:?}",
112+
trb
113+
)));
102114
} else {
103115
let is_sequence = unsafe { pyo3::ffi::PySequence_Check(v.as_ptr()) == 1 };
104116
if is_sequence {
105-
return Err(PyErr::new::<PyTypeError, _>(
106-
"Unable to represent PyTraceback type as CLR from Python".to_string(),
107-
));
117+
let seq = v.downcast::<PySequence>()?;
118+
119+
return Err(PyErr::new::<PyTypeError, _>(format!(
120+
"Unable to represent PySequence type as CLR from Python, value: {:?}",
121+
seq
122+
)));
108123
}
109124

110125
Self::PythonRef(PythonRef::PyObject(v.into()))
@@ -113,7 +128,7 @@ impl CLReprPython for CLRepr {
113128

114129
fn into_py_impl(from: CLRepr, py: Python) -> Result<PyObject, PyErr> {
115130
Ok(match from {
116-
CLRepr::String(v) => PyString::new(py, &v).to_object(py),
131+
CLRepr::String(v, _) => PyString::new(py, &v).to_object(py),
117132
CLRepr::Bool(v) => PyBool::new(py, v).to_object(py),
118133
CLRepr::Float(v) => PyFloat::new(py, v).to_object(py),
119134
CLRepr::Int(v) => {

packages/cubejs-backend-native/src/cross/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ mod clrepr_python;
44
#[cfg(feature = "python")]
55
mod py_in_js;
66

7-
pub use clrepr::{CLRepr, CLReprKind, CLReprObject};
7+
pub use clrepr::{CLRepr, CLReprKind, CLReprObject, StringType};
88

99
#[cfg(feature = "python")]
1010
pub use clrepr_python::{CLReprPython, PythonRef};

packages/cubejs-backend-native/src/template/mj_value/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ pub fn to_minijinja_value(from: CLRepr) -> mjv::Value {
1010
CLRepr::Tuple(inner) | CLRepr::Array(inner) => {
1111
mjv::Value::from_seq_object(value::JinjaSequenceObject { inner })
1212
}
13-
CLRepr::String(v) => mjv::Value::from(v),
13+
CLRepr::String(v, mode) => match mode {
14+
StringType::Normal => mjv::Value::from(v),
15+
StringType::Safe => mjv::Value::from_safe_string(v),
16+
},
1417
CLRepr::Float(v) => mjv::Value::from(v),
1518
CLRepr::Int(v) => mjv::Value::from(v),
1619
CLRepr::Bool(v) => mjv::Value::from(v),

packages/cubejs-backend-native/src/template/mj_value/python.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,16 @@ pub fn from_minijinja_value(from: &mjv::Value) -> Result<CLRepr, mj::Error> {
2828
))
2929
}
3030
}
31-
mjv::ValueKind::String => {
32-
// TODO: Danger strings? Should we check from.is_safe()?
33-
Ok(CLRepr::String(
34-
from.as_str()
35-
.expect("ValueKind::String must return string from as_str()")
36-
.to_string(),
37-
))
38-
}
31+
mjv::ValueKind::String => Ok(CLRepr::String(
32+
from.as_str()
33+
.expect("ValueKind::String must return string from as_str()")
34+
.to_string(),
35+
if from.is_safe() {
36+
StringType::Safe
37+
} else {
38+
StringType::Normal
39+
},
40+
)),
3941
mjv::ValueKind::Seq => {
4042
let seq = if let Some(seq) = from.as_seq() {
4143
seq

0 commit comments

Comments
 (0)