Skip to content

Commit b6f1da0

Browse files
authored
Merge in v10 work (1BADragon#34)
1 parent eecbeec commit b6f1da0

File tree

18 files changed

+1067
-542
lines changed

18 files changed

+1067
-542
lines changed

.github/workflows/rust.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,34 @@ jobs:
1515

1616
steps:
1717
- uses: actions/checkout@v3
18+
1819
- name: Build
1920
run: cargo build --verbose
2021

2122
test_default:
2223
runs-on: ubuntu-latest
2324

2425
steps:
26+
- name: Install Protoc
27+
uses: arduino/setup-protoc@v3
28+
2529
- uses: actions/checkout@v3
30+
2631
- name: Run tests
2732
run: cargo test --verbose
33+
env:
34+
RSCEL_TEST_PROTO: 1
2835

2936
test_no_default:
3037
runs-on: ubuntu-latest
3138

3239
steps:
40+
- name: Install Protoc
41+
uses: arduino/setup-protoc@v3
42+
3343
- uses: actions/checkout@v3
44+
3445
- name: Run tests
3546
run: cargo test --verbose --no-default-features
47+
env:
48+
RSCEL_TEST_PROTO: 1

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rscel"
3-
version = "0.9.0"
3+
version = "0.10.0"
44
edition = "2021"
55
description = "Cel interpreter in rust"
66
license = "MIT"
@@ -24,6 +24,9 @@ wasm = [
2424
"console_error_panic_hook", "chrono/wasmbind"
2525
]
2626

27+
[build-dependencies]
28+
protobuf-codegen = "3.4.0"
29+
protoc-bin-vendored = "3.0.0"
2730

2831
[dependencies]
2932
test-case = "3.2.1"
@@ -34,6 +37,7 @@ serde_json = { version = "1.0.108", features = ["raw_value"] }
3437
chrono = { version = "0.4.31", features = ["serde"] }
3538
duration-str = "0.7.0"
3639
num = "0.4.1"
40+
protobuf = { version = "3.4.0" }
3741

3842
# Dependencies for python bindings
3943
pyo3 = { version = "0.20.0", optional = true, features = ["extension-module", "chrono"] }

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ default: build
66
build:
77
cargo build $(CARGO_ARGS)
88

9-
test:
9+
run-tests:
1010
cargo test $(CARGO_ARGS)
1111

12-
test-all: test
12+
run-all-tests: run-tests
1313
cargo test --no-default-features $(CARGO_ARGS)
1414

1515
.env:

README.md

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,9 @@ The design goals of this project were are as follows:
1010
* Sandbox'ed in such a way that only specific values can be bound
1111
* Can be used as a wasm depenedency (or other ffi)
1212

13-
While Google's CEL spec was designed around the protobuf message,
14-
I decided to focus on using the JSON message instead (CEL spec specifies
15-
how to convert from JSON to CEL types).
16-
1713
The basic example of how to use:
1814
```rust
19-
use rscel::{CelContext, BindContext, serde_json};
15+
use rscel::{CelContext, BindContext};
2016

2117
let mut ctx = CelContext::new();
2218
let mut exec_ctx = BindContext::new();
@@ -25,7 +21,37 @@ ctx.add_program_str("main", "foo + 3").unwrap();
2521
exec_ctx.bind_param("foo", 3.into()); // convert to CelValue
2622

2723
let res = ctx.exec("main", &exec_ctx).unwrap(); // CelValue::Int(6)
28-
assert!(TryInto::<i64>::try_into(res).unwrap() == 6);
24+
assert_eq!(res, 6.into());
25+
```
26+
27+
As of 0.10.0 binding protobuf messages from the protobuf crate is now available! Given
28+
the following protobuf message:
29+
```protobuf
30+
31+
message Point {
32+
int32 x = 1;
33+
int32 y = 2;
34+
}
35+
36+
```
37+
The following code can be used to evaluate a CEL expression on a Point message:
38+
39+
```rust
40+
use rscel::{CelContext, BindContext};
41+
42+
// currently rscel required protobuf messages to be in a box
43+
let p = Box::new(protos::Point::new());
44+
p.x = 4;
45+
p.y = 5;
46+
47+
let mut ctx = CelContext::new();
48+
let mut exec_ctx = BindContext::new();
49+
50+
ctx.add_program_str("main", "p.x + 3").unwrap();
51+
exec_ctx.bind_protobuf_msg("p", p);
52+
53+
assert_eq!(ctx.exec("main", &exec_ctx), 7.into());
54+
2955
```
3056

3157
## Current Benchmark Times

build.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use std::env;
2+
3+
fn main() {
4+
println!("cargo:rerun-if-env-changed=RSCEL_TEST_PROTO");
5+
6+
if let Ok(_) = env::var("RSCEL_TEST_PROTO") {
7+
println!("cargo:rustc-cfg=test_protos");
8+
protobuf_codegen::Codegen::new()
9+
.protoc()
10+
.include("test/protos")
11+
.inputs(["test/protos/test.proto"])
12+
.cargo_out_dir("test_protos")
13+
.run_from_script();
14+
}
15+
}

src/bindings/python/mod.rs

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use crate::{BindContext, CelContext, CelValue};
1+
use crate::{BindContext, CelContext, CelError, CelValue, CelValueDyn};
22

33
use chrono::{DateTime, Duration, Utc};
44
use pyo3::{
55
exceptions::{PyRuntimeError, PyValueError},
66
prelude::*,
77
types::{PyBool, PyBytes, PyDateTime, PyDelta, PyDict, PyFloat, PyInt, PyList, PyString},
88
};
9-
use std::collections::HashMap;
9+
use std::{collections::HashMap, sync::Arc};
1010

1111
mod celpycallable;
1212

@@ -143,6 +143,70 @@ fn rscel(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
143143
Ok(())
144144
}
145145

146+
impl CelValueDyn for PyObject {
147+
fn as_type(&self) -> CelValue {
148+
Python::with_gil(|py| {
149+
let inner = self.as_ref(py);
150+
let name = inner.get_type().name().unwrap();
151+
152+
CelValue::Type(format!("pyobj-{}", name))
153+
})
154+
}
155+
156+
fn access(&self, key: &str) -> crate::CelResult<CelValue> {
157+
Python::with_gil(|py| {
158+
let obj = self.as_ref(py);
159+
160+
match obj.getattr(key) {
161+
Ok(res) => match res.extract() {
162+
Ok(val) => Ok(val),
163+
Err(err) => Err(CelError::Misc(err.to_string())),
164+
},
165+
Err(err) => Err(CelError::Misc(err.to_string())),
166+
}
167+
})
168+
}
169+
170+
fn eq(&self, rhs: &CelValue) -> crate::CelResult<CelValue> {
171+
let lhs_type = self.as_type();
172+
let rhs_type = self.as_type();
173+
174+
if let CelValue::Dyn(rhs) = rhs {
175+
if let Some(rhs_obj) = rhs.any_ref().downcast_ref::<PyObject>() {
176+
return Python::with_gil(|py| {
177+
let lhs_obj = self.as_ref(py);
178+
let rhs_obj = rhs_obj.as_ref(py);
179+
180+
match lhs_obj.eq(rhs_obj) {
181+
Ok(res) => Ok(CelValue::from_bool(res)),
182+
Err(err) => Err(CelError::Misc(err.to_string())),
183+
}
184+
});
185+
}
186+
}
187+
188+
Err(CelError::invalid_op(&format!(
189+
"Invalid op == between {} and {}",
190+
lhs_type, rhs_type
191+
)))
192+
}
193+
194+
fn is_truthy(&self) -> bool {
195+
Python::with_gil(|py| {
196+
let inner = self.as_ref(py);
197+
198+
match inner.is_true() {
199+
Ok(res) => res,
200+
Err(_) => false, // this is just going to have to work. Basically is the equiv of calling bool(obj) and it throwing
201+
}
202+
})
203+
}
204+
205+
fn any_ref<'a>(&'a self) -> &'a dyn std::any::Any {
206+
self
207+
}
208+
}
209+
146210
impl<'source> FromPyObject<'source> for CelValue {
147211
fn extract(ob: &'source PyAny) -> PyResult<Self> {
148212
match ob.get_type().name() {
@@ -179,10 +243,7 @@ impl<'source> FromPyObject<'source> for CelValue {
179243
.into()),
180244
"timedelta" => Ok(ob.downcast::<PyDelta>()?.extract::<Duration>()?.into()),
181245
"NoneType" => Ok(CelValue::from_null()),
182-
other => Err(PyValueError::new_err(format!(
183-
"{} is not a compatable rscel type",
184-
other
185-
))),
246+
_ => Ok(CelValue::Dyn(Arc::<PyObject>::new(ob.into()))),
186247
},
187248
Err(_) => PyResult::Err(PyValueError::new_err(format!(
188249
"Failed to get type from {:?}",
@@ -217,6 +278,13 @@ impl ToPyObject for CelValue {
217278
TimeStamp(ts) => ts.to_object(py),
218279
Duration(d) => d.to_object(py),
219280
Null => py.None(),
281+
Dyn(d) => match d.any_ref().downcast_ref::<PyObject>() {
282+
Some(obj) => obj.clone(),
283+
// This *should* never happen. If this downcase were to fail that would
284+
// mean that the data in this dyn isn't a PyObject which should be impossible
285+
// for these bidnings
286+
None => py.None(),
287+
},
220288
_ => py.None(),
221289
}
222290
}

0 commit comments

Comments
 (0)