Skip to content

Commit c078a6d

Browse files
committed
Merge remote-tracking branch 'origin/master' into worktree/magnet/fixing-bytes-operations-handling-invalid-bytes
2 parents 51dd71e + 49ce97b commit c078a6d

5 files changed

Lines changed: 367 additions & 2 deletions

File tree

cel-spec

Submodule cel-spec added at 121e265

cel/src/context.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,23 @@ impl Default for Context<'_> {
222222
ctx.add_function("min", functions::min);
223223
ctx.add_function("startsWith", functions::starts_with);
224224
ctx.add_function("endsWith", functions::ends_with);
225+
ctx.add_function("isNaN", functions::is_nan);
226+
ctx.add_function("isInf", functions::is_inf);
227+
ctx.add_function("isFinite", functions::is_finite);
228+
ctx.add_function("ceil", functions::ceil);
229+
ctx.add_function("floor", functions::floor);
230+
ctx.add_function("trunc", functions::trunc);
231+
ctx.add_function("round", functions::round);
225232
ctx.add_function("string", functions::string);
226233
ctx.add_function("bytes", functions::bytes);
227234
ctx.add_function("double", functions::double);
228235
ctx.add_function("float", functions::float);
229236
ctx.add_function("int", functions::int);
230237
ctx.add_function("uint", functions::uint);
238+
ctx.add_function("list", functions::list);
239+
ctx.add_function("map", functions::map);
240+
ctx.add_function("null_type", functions::null_type);
241+
ctx.add_function("dyn", functions::dyn_conversion);
231242
ctx.add_function("optional.none", functions::optional_none);
232243
ctx.add_function("optional.of", functions::optional_of);
233244
ctx.add_function(

cel/src/functions.rs

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ pub fn size(ftx: &FunctionContext, This(this): This<Value>) -> Result<i64> {
7777
let size = match this {
7878
Value::List(l) => l.len(),
7979
Value::Map(m) => m.map.len(),
80+
Value::Struct(s) => s.fields.len(),
8081
Value::String(s) => s.len(),
8182
Value::Bytes(b) => b.len(),
8283
value => return Err(ftx.error(format!("cannot determine the size of {value:?}"))),
@@ -306,6 +307,37 @@ pub fn int(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
306307
})
307308
}
308309

310+
// Performs a type conversion to list.
311+
pub fn list(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
312+
Ok(match this {
313+
Value::List(v) => Value::List(v.clone()),
314+
v => return Err(ftx.error(format!("cannot convert {v:?} to list"))),
315+
})
316+
}
317+
318+
// Performs a type conversion to map.
319+
pub fn map(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
320+
Ok(match this {
321+
Value::Map(v) => Value::Map(v.clone()),
322+
v => return Err(ftx.error(format!("cannot convert {v:?} to map"))),
323+
})
324+
}
325+
326+
// Performs a type conversion to null_type.
327+
pub fn null_type(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
328+
Ok(match this {
329+
Value::Null => Value::Null,
330+
v => return Err(ftx.error(format!("cannot convert {v:?} to null_type"))),
331+
})
332+
}
333+
334+
// Performs a type conversion to dynamic type (dyn).
335+
// In CEL, dyn() is essentially an identity function that returns the value as-is,
336+
// indicating it should be treated as a dynamic type.
337+
pub fn dyn_conversion(_ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
338+
Ok(this)
339+
}
340+
309341
pub fn optional_none(ftx: &FunctionContext) -> Result<Value> {
310342
if ftx.this.is_some() || !ftx.args.is_empty() {
311343
return Err(ftx.error("unsupported function"));
@@ -622,6 +654,143 @@ pub mod time {
622654
}
623655
}
624656

657+
/// Returns true if the target value is NaN (Not a Number).
658+
///
659+
/// This function checks if a floating-point value is NaN. For non-float values,
660+
/// it returns false.
661+
///
662+
/// # Examples
663+
/// ```cel
664+
/// isNaN(0.0 / 0.0) == true
665+
/// isNaN(1.0 / 0.0) == false
666+
/// isNaN(1.0) == false
667+
/// ```
668+
pub fn is_nan(This(this): This<Value>) -> Result<bool> {
669+
Ok(match this {
670+
Value::Float(v) => v.is_nan(),
671+
_ => false,
672+
})
673+
}
674+
675+
/// Returns true if the target value is infinite (positive or negative infinity).
676+
///
677+
/// This function checks if a floating-point value is infinite. For non-float values,
678+
/// it returns false.
679+
///
680+
/// # Examples
681+
/// ```cel
682+
/// isInf(1.0 / 0.0) == true
683+
/// isInf(-1.0 / 0.0) == true
684+
/// isInf(1.0) == false
685+
/// ```
686+
pub fn is_inf(This(this): This<Value>) -> Result<bool> {
687+
Ok(match this {
688+
Value::Float(v) => v.is_infinite(),
689+
_ => false,
690+
})
691+
}
692+
693+
/// Returns true if the target value is finite (not NaN and not infinite).
694+
///
695+
/// This function checks if a value is finite. For integer types, always returns true.
696+
/// For floating-point values, returns true only if the value is neither NaN nor infinite.
697+
///
698+
/// # Examples
699+
/// ```cel
700+
/// isFinite(1.0) == true
701+
/// isFinite(1.0 / 0.0) == false
702+
/// isFinite(0.0 / 0.0) == false
703+
/// isFinite(42) == true
704+
/// ```
705+
pub fn is_finite(This(this): This<Value>) -> Result<bool> {
706+
Ok(match this {
707+
Value::Float(v) => v.is_finite(),
708+
Value::Int(_) | Value::UInt(_) => true,
709+
_ => false,
710+
})
711+
}
712+
713+
/// Returns the ceiling of the target value (rounds up to the nearest integer).
714+
///
715+
/// For float values, returns the smallest integer greater than or equal to the value.
716+
/// For integer values, returns the value unchanged.
717+
///
718+
/// # Examples
719+
/// ```cel
720+
/// ceil(1.2) == 2.0
721+
/// ceil(-1.2) == -1.0
722+
/// ceil(5) == 5.0
723+
/// ```
724+
pub fn ceil(This(this): This<Value>) -> Result<Value> {
725+
Ok(match this {
726+
Value::Float(v) => Value::Float(v.ceil()),
727+
Value::Int(v) => Value::Float(v as f64),
728+
Value::UInt(v) => Value::Float(v as f64),
729+
_ => return Err(ExecutionError::function_error("ceil", "argument must be numeric")),
730+
})
731+
}
732+
733+
/// Returns the floor of the target value (rounds down to the nearest integer).
734+
///
735+
/// For float values, returns the largest integer less than or equal to the value.
736+
/// For integer values, returns the value unchanged.
737+
///
738+
/// # Examples
739+
/// ```cel
740+
/// floor(1.8) == 1.0
741+
/// floor(-1.2) == -2.0
742+
/// floor(5) == 5.0
743+
/// ```
744+
pub fn floor(This(this): This<Value>) -> Result<Value> {
745+
Ok(match this {
746+
Value::Float(v) => Value::Float(v.floor()),
747+
Value::Int(v) => Value::Float(v as f64),
748+
Value::UInt(v) => Value::Float(v as f64),
749+
_ => return Err(ExecutionError::function_error("floor", "argument must be numeric")),
750+
})
751+
}
752+
753+
/// Truncates the target value toward zero (removes the fractional part).
754+
///
755+
/// For float values, returns the integer part by removing the fractional component.
756+
/// For integer values, returns the value unchanged.
757+
///
758+
/// # Examples
759+
/// ```cel
760+
/// trunc(1.8) == 1.0
761+
/// trunc(-1.8) == -1.0
762+
/// trunc(5) == 5.0
763+
/// ```
764+
pub fn trunc(This(this): This<Value>) -> Result<Value> {
765+
Ok(match this {
766+
Value::Float(v) => Value::Float(v.trunc()),
767+
Value::Int(v) => Value::Float(v as f64),
768+
Value::UInt(v) => Value::Float(v as f64),
769+
_ => return Err(ExecutionError::function_error("trunc", "argument must be numeric")),
770+
})
771+
}
772+
773+
/// Rounds the target value to the nearest integer.
774+
///
775+
/// For float values, returns the nearest integer using "round half away from zero" rounding.
776+
/// For integer values, returns the value unchanged.
777+
///
778+
/// # Examples
779+
/// ```cel
780+
/// round(1.4) == 1.0
781+
/// round(1.5) == 2.0
782+
/// round(-1.5) == -2.0
783+
/// round(5) == 5.0
784+
/// ```
785+
pub fn round(This(this): This<Value>) -> Result<Value> {
786+
Ok(match this {
787+
Value::Float(v) => Value::Float(v.round()),
788+
Value::Int(v) => Value::Float(v as f64),
789+
Value::UInt(v) => Value::Float(v as f64),
790+
_ => return Err(ExecutionError::function_error("round", "argument must be numeric")),
791+
})
792+
}
793+
625794
pub fn max(Arguments(args): Arguments) -> Result<Value> {
626795
// If items is a list of values, then operate on the list
627796
let items = if args.len() == 1 {
@@ -1163,6 +1332,44 @@ mod tests {
11631332
.for_each(assert_script);
11641333
}
11651334

1335+
#[test]
1336+
fn test_list() {
1337+
[
1338+
("list", "[1, 2, 3].list() == [1, 2, 3]"),
1339+
]
1340+
.iter()
1341+
.for_each(assert_script);
1342+
}
1343+
1344+
#[test]
1345+
fn test_map() {
1346+
[
1347+
("map", "{'a': 1, 'b': 2}.map() == {'a': 1, 'b': 2}"),
1348+
]
1349+
.iter()
1350+
.for_each(assert_script);
1351+
}
1352+
1353+
#[test]
1354+
fn test_null_type() {
1355+
[
1356+
("null", "null.null_type() == null"),
1357+
]
1358+
.iter()
1359+
.for_each(assert_script);
1360+
}
1361+
1362+
#[test]
1363+
fn test_dyn() {
1364+
[
1365+
("int", "10.dyn() == 10"),
1366+
("string", "'hello'.dyn() == 'hello'"),
1367+
("list", "[1, 2, 3].dyn() == [1, 2, 3]"),
1368+
]
1369+
.iter()
1370+
.for_each(assert_script);
1371+
}
1372+
11661373
#[test]
11671374
fn no_bool_coercion() {
11681375
[
@@ -1328,4 +1535,102 @@ mod tests {
13281535
}
13291536
}
13301537
}
1538+
1539+
#[test]
1540+
fn test_is_nan() {
1541+
[
1542+
("isNaN with NaN", "isNaN(0.0 / 0.0) == true"),
1543+
("isNaN with infinity", "isNaN(1.0 / 0.0) == false"),
1544+
("isNaN with normal float", "isNaN(1.0) == false"),
1545+
("isNaN with int", "isNaN(42) == false"),
1546+
("isNaN method call", "(0.0 / 0.0).isNaN() == true"),
1547+
]
1548+
.iter()
1549+
.for_each(assert_script);
1550+
}
1551+
1552+
#[test]
1553+
fn test_is_inf() {
1554+
[
1555+
("isInf with positive infinity", "isInf(1.0 / 0.0) == true"),
1556+
("isInf with negative infinity", "isInf(-1.0 / 0.0) == true"),
1557+
("isInf with normal float", "isInf(1.0) == false"),
1558+
("isInf with NaN", "isInf(0.0 / 0.0) == false"),
1559+
("isInf with int", "isInf(42) == false"),
1560+
("isInf method call", "(1.0 / 0.0).isInf() == true"),
1561+
]
1562+
.iter()
1563+
.for_each(assert_script);
1564+
}
1565+
1566+
#[test]
1567+
fn test_is_finite() {
1568+
[
1569+
("isFinite with normal float", "isFinite(1.0) == true"),
1570+
("isFinite with int", "isFinite(42) == true"),
1571+
("isFinite with uint", "isFinite(42u) == true"),
1572+
("isFinite with infinity", "isFinite(1.0 / 0.0) == false"),
1573+
("isFinite with NaN", "isFinite(0.0 / 0.0) == false"),
1574+
("isFinite method call", "1.0.isFinite() == true"),
1575+
]
1576+
.iter()
1577+
.for_each(assert_script);
1578+
}
1579+
1580+
#[test]
1581+
fn test_ceil() {
1582+
[
1583+
("ceil positive decimal", "ceil(1.2) == 2.0"),
1584+
("ceil negative decimal", "ceil(-1.2) == -1.0"),
1585+
("ceil int", "ceil(5) == 5.0"),
1586+
("ceil uint", "ceil(5u) == 5.0"),
1587+
("ceil whole number", "ceil(3.0) == 3.0"),
1588+
("ceil method call", "1.2.ceil() == 2.0"),
1589+
]
1590+
.iter()
1591+
.for_each(assert_script);
1592+
}
1593+
1594+
#[test]
1595+
fn test_floor() {
1596+
[
1597+
("floor positive decimal", "floor(1.8) == 1.0"),
1598+
("floor negative decimal", "floor(-1.2) == -2.0"),
1599+
("floor int", "floor(5) == 5.0"),
1600+
("floor uint", "floor(5u) == 5.0"),
1601+
("floor whole number", "floor(3.0) == 3.0"),
1602+
("floor method call", "1.8.floor() == 1.0"),
1603+
]
1604+
.iter()
1605+
.for_each(assert_script);
1606+
}
1607+
1608+
#[test]
1609+
fn test_trunc() {
1610+
[
1611+
("trunc positive decimal", "trunc(1.8) == 1.0"),
1612+
("trunc negative decimal", "trunc(-1.8) == -1.0"),
1613+
("trunc int", "trunc(5) == 5.0"),
1614+
("trunc uint", "trunc(5u) == 5.0"),
1615+
("trunc whole number", "trunc(3.0) == 3.0"),
1616+
("trunc method call", "1.8.trunc() == 1.0"),
1617+
]
1618+
.iter()
1619+
.for_each(assert_script);
1620+
}
1621+
1622+
#[test]
1623+
fn test_round() {
1624+
[
1625+
("round 1.4", "round(1.4) == 1.0"),
1626+
("round 1.5", "round(1.5) == 2.0"),
1627+
("round -1.5", "round(-1.5) == -2.0"),
1628+
("round int", "round(5) == 5.0"),
1629+
("round uint", "round(5u) == 5.0"),
1630+
("round whole number", "round(3.0) == 3.0"),
1631+
("round method call", "1.5.round() == 2.0"),
1632+
]
1633+
.iter()
1634+
.for_each(assert_script);
1635+
}
13311636
}

cel/src/json.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ impl Value {
4747
}
4848
serde_json::Value::Object(obj)
4949
}
50+
Value::Struct(ref s) => {
51+
let mut obj = serde_json::Map::new();
52+
for (k, v) in s.fields.iter() {
53+
obj.insert(k.clone(), v.json()?);
54+
}
55+
serde_json::Value::Object(obj)
56+
}
5057
Value::Int(i) => i.into(),
5158
Value::UInt(u) => u.into(),
5259
Value::Float(f) => f.into(),

0 commit comments

Comments
 (0)