2024-07-26 15:14:51 -04:00
//! Standard library assert functions.
use anyhow ::Result ;
2025-03-01 13:59:01 -08:00
use kcl_derive_docs ::stdlib ;
2024-07-26 15:14:51 -04:00
2025-04-26 21:21:26 -07:00
use super ::args ::TyF64 ;
2024-07-26 15:14:51 -04:00
use crate ::{
errors ::{ KclError , KclErrorDetails } ,
2025-02-21 12:36:21 +13:00
execution ::{ ExecState , KclValue } ,
2024-07-26 15:14:51 -04:00
std ::Args ,
} ;
async fn _assert ( value : bool , message : & str , args : & Args ) -> Result < ( ) , KclError > {
if ! value {
2025-05-19 14:13:10 -04:00
return Err ( KclError ::Type ( KclErrorDetails ::new (
format! ( " assert failed: {} " , message ) ,
vec! [ args . source_range ] ,
) ) ) ;
2024-07-26 15:14:51 -04:00
}
Ok ( ( ) )
}
2025-04-22 12:44:52 -05:00
pub async fn assert_is ( _exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
let actual = args . get_unlabeled_kw_arg ( " actual " ) ? ;
let error = args . get_kw_arg_opt ( " error " ) ? ;
inner_assert_is ( actual , error , & args ) . await ? ;
Ok ( KclValue ::none ( ) )
}
2024-07-26 15:14:51 -04:00
/// Check that the provided value is true, or raise a [KclError]
/// with the provided description.
2024-09-16 15:10:33 -04:00
pub async fn assert ( _exec_state : & mut ExecState , args : Args ) -> Result < KclValue , KclError > {
2025-04-22 12:44:52 -05:00
let actual = args . get_unlabeled_kw_arg ( " actual " ) ? ;
let gt = args . get_kw_arg_opt ( " isGreaterThan " ) ? ;
let lt = args . get_kw_arg_opt ( " isLessThan " ) ? ;
let gte = args . get_kw_arg_opt ( " isGreaterThanOrEqual " ) ? ;
let lte = args . get_kw_arg_opt ( " isLessThanOrEqual " ) ? ;
let eq = args . get_kw_arg_opt ( " isEqualTo " ) ? ;
let tolerance = args . get_kw_arg_opt ( " tolerance " ) ? ;
let error = args . get_kw_arg_opt ( " error " ) ? ;
inner_assert ( actual , gt , lt , gte , lte , eq , tolerance , error , & args ) . await ? ;
2025-02-21 12:36:21 +13:00
Ok ( KclValue ::none ( ) )
2024-07-26 15:14:51 -04:00
}
2025-04-22 12:44:52 -05:00
/// Asserts that a value is the boolean value true.
2024-07-26 15:14:51 -04:00
/// ```no_run
2025-04-22 12:44:52 -05:00
/// kclIsFun = true
/// assertIs(kclIsFun)
2024-07-26 15:14:51 -04:00
/// ```
2025-04-22 12:44:52 -05:00
#[ stdlib{
name = " assertIs " ,
keywords = true ,
unlabeled_first = true ,
args = {
actual = { docs = " Value to check. If this is the boolean value true, assert passes. Otherwise it fails. " } ,
error = { docs = " If the value was false, the program will terminate with this error message " } ,
}
2024-07-26 15:14:51 -04:00
} ]
2025-04-22 12:44:52 -05:00
async fn inner_assert_is ( actual : bool , error : Option < String > , args : & Args ) -> Result < ( ) , KclError > {
let error_msg = match & error {
Some ( x ) = > x ,
None = > " should have been true, but it was not " ,
} ;
_assert ( actual , error_msg , args ) . await
2024-07-26 15:14:51 -04:00
}
2025-04-22 12:44:52 -05:00
/// Check a value meets some expected conditions at runtime. Program terminates with an error if conditions aren't met.
/// If you provide multiple conditions, they will all be checked and all must be met.
2024-07-26 15:14:51 -04:00
///
/// ```no_run
2025-04-22 12:44:52 -05:00
/// n = 10
/// assert(n, isEqualTo = 10)
/// assert(n, isGreaterThanOrEqual = 0, isLessThan = 100, error = "number should be between 0 and 100")
/// assert(1.0000000000012, isEqualTo = 1, tolerance = 0.0001, error = "number should be almost exactly 1")
2024-07-26 15:14:51 -04:00
/// ```
#[ stdlib {
2025-04-22 12:44:52 -05:00
name = " assert " ,
keywords = true ,
unlabeled_first = true ,
args = {
actual = { docs = " Value to check. It will be compared with one of the comparison arguments. " } ,
is_greater_than = { docs = " Comparison argument. If given, checks the `actual` value is greater than this. " } ,
is_less_than = { docs = " Comparison argument. If given, checks the `actual` value is less than this. " } ,
is_greater_than_or_equal = { docs = " Comparison argument. If given, checks the `actual` value is greater than or equal to this. " } ,
is_less_than_or_equal = { docs = " Comparison argument. If given, checks the `actual` value is less than or equal to this. " } ,
is_equal_to = { docs = " Comparison argument. If given, checks the `actual` value is less than or equal to this. " , include_in_snippet = true } ,
tolerance = { docs = " If `isEqualTo` is used, this is the tolerance to allow for the comparison. This tolerance is used because KCL's number system has some floating-point imprecision when used with very large decimal places. " } ,
error = { docs = " If the value was false, the program will terminate with this error message " } ,
}
2024-08-05 11:31:58 -05:00
} ]
2025-04-22 12:44:52 -05:00
#[ allow(clippy::too_many_arguments) ]
async fn inner_assert (
actual : TyF64 ,
is_greater_than : Option < TyF64 > ,
is_less_than : Option < TyF64 > ,
is_greater_than_or_equal : Option < TyF64 > ,
is_less_than_or_equal : Option < TyF64 > ,
is_equal_to : Option < TyF64 > ,
tolerance : Option < TyF64 > ,
error : Option < String > ,
args : & Args ,
) -> Result < ( ) , KclError > {
// Validate the args
let no_condition_given = [
& is_greater_than ,
& is_less_than ,
& is_greater_than_or_equal ,
& is_less_than_or_equal ,
& is_equal_to ,
]
. iter ( )
. all ( | cond | cond . is_none ( ) ) ;
if no_condition_given {
2025-05-19 14:13:10 -04:00
return Err ( KclError ::Type ( KclErrorDetails ::new (
" You must provide at least one condition in this assert (for example, isEqualTo) " . to_owned ( ) ,
vec! [ args . source_range ] ,
) ) ) ;
2024-09-16 12:43:49 -04:00
}
2024-08-05 11:31:58 -05:00
2025-04-22 12:44:52 -05:00
if tolerance . is_some ( ) & & is_equal_to . is_none ( ) {
2025-05-19 14:13:10 -04:00
return Err ( KclError ::Type ( KclErrorDetails ::new (
" The `tolerance` arg is only used with `isEqualTo`. Either remove `tolerance` or add an `isEqualTo` arg. "
. to_owned ( ) ,
vec! [ args . source_range ] ,
) ) ) ;
2025-04-22 12:44:52 -05:00
}
2024-07-26 15:14:51 -04:00
2025-04-22 12:44:52 -05:00
let suffix = if let Some ( err_string ) = error {
format! ( " : {err_string} " )
} else {
Default ::default ( )
} ;
let actual = actual . n ;
// Run the checks.
if let Some ( exp ) = is_greater_than {
let exp = exp . n ;
_assert (
actual > exp ,
& format! ( " Expected {actual} to be greater than {exp} but it wasn't {suffix} " ) ,
args ,
)
. await ? ;
}
if let Some ( exp ) = is_less_than {
let exp = exp . n ;
_assert (
actual < exp ,
& format! ( " Expected {actual} to be less than {exp} but it wasn't {suffix} " ) ,
args ,
)
. await ? ;
}
if let Some ( exp ) = is_greater_than_or_equal {
let exp = exp . n ;
_assert (
actual > = exp ,
& format! ( " Expected {actual} to be greater than or equal to {exp} but it wasn't {suffix} " ) ,
args ,
)
. await ? ;
}
if let Some ( exp ) = is_less_than_or_equal {
let exp = exp . n ;
_assert (
actual < = exp ,
& format! ( " Expected {actual} to be less than or equal to {exp} but it wasn't {suffix} " ) ,
args ,
)
. await ? ;
}
if let Some ( exp ) = is_equal_to {
let exp = exp . n ;
let tolerance = tolerance . map ( | e | e . n ) . unwrap_or ( 0.0000000001 ) ;
_assert (
( actual - exp ) . abs ( ) < tolerance ,
& format! ( " Expected {actual} to be equal to {exp} but it wasn't {suffix} " ) ,
args ,
)
. await ? ;
}
Ok ( ( ) )
2024-07-26 15:14:51 -04:00
}