@@ -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+
309341pub 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+
625794pub 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}
0 commit comments