kcl_lib/std/
assert.rs

1//! Standard library assert functions.
2
3use anyhow::Result;
4
5use super::args::TyF64;
6use crate::{
7    errors::{KclError, KclErrorDetails},
8    execution::{ExecState, KclValue, types::RuntimeType},
9    std::Args,
10};
11
12async fn _assert(value: bool, message: &str, args: &Args) -> Result<(), KclError> {
13    if !value {
14        return Err(KclError::new_type(KclErrorDetails::new(
15            format!("assert failed: {message}"),
16            vec![args.source_range],
17        )));
18    }
19    Ok(())
20}
21
22pub async fn assert_is(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
23    let actual = args.get_unlabeled_kw_arg("actual", &RuntimeType::bool(), exec_state)?;
24    let error = args.get_kw_arg_opt("error", &RuntimeType::string(), exec_state)?;
25    inner_assert_is(actual, error, &args).await?;
26    Ok(KclValue::none())
27}
28
29/// Check that the provided value is true, or raise a [KclError]
30/// with the provided description.
31pub async fn assert(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
32    let actual = args.get_unlabeled_kw_arg("actual", &RuntimeType::num_any(), exec_state)?;
33    let gt = args.get_kw_arg_opt("isGreaterThan", &RuntimeType::num_any(), exec_state)?;
34    let lt = args.get_kw_arg_opt("isLessThan", &RuntimeType::num_any(), exec_state)?;
35    let gte = args.get_kw_arg_opt("isGreaterThanOrEqual", &RuntimeType::num_any(), exec_state)?;
36    let lte = args.get_kw_arg_opt("isLessThanOrEqual", &RuntimeType::num_any(), exec_state)?;
37    let eq = args.get_kw_arg_opt("isEqualTo", &RuntimeType::num_any(), exec_state)?;
38    let tolerance = args.get_kw_arg_opt("tolerance", &RuntimeType::num_any(), exec_state)?;
39    let error = args.get_kw_arg_opt("error", &RuntimeType::string(), exec_state)?;
40    inner_assert(actual, gt, lt, gte, lte, eq, tolerance, error, &args).await?;
41    Ok(KclValue::none())
42}
43
44async fn inner_assert_is(actual: bool, error: Option<String>, args: &Args) -> Result<(), KclError> {
45    let error_msg = match &error {
46        Some(x) => x,
47        None => "should have been true, but it was not",
48    };
49    _assert(actual, error_msg, args).await
50}
51
52#[allow(clippy::too_many_arguments)]
53async fn inner_assert(
54    actual: TyF64,
55    is_greater_than: Option<TyF64>,
56    is_less_than: Option<TyF64>,
57    is_greater_than_or_equal: Option<TyF64>,
58    is_less_than_or_equal: Option<TyF64>,
59    is_equal_to: Option<TyF64>,
60    tolerance: Option<TyF64>,
61    error: Option<String>,
62    args: &Args,
63) -> Result<(), KclError> {
64    // Validate the args
65    let no_condition_given = [
66        &is_greater_than,
67        &is_less_than,
68        &is_greater_than_or_equal,
69        &is_less_than_or_equal,
70        &is_equal_to,
71    ]
72    .iter()
73    .all(|cond| cond.is_none());
74    if no_condition_given {
75        return Err(KclError::new_type(KclErrorDetails::new(
76            "You must provide at least one condition in this assert (for example, isEqualTo)".to_owned(),
77            vec![args.source_range],
78        )));
79    }
80
81    if tolerance.is_some() && is_equal_to.is_none() {
82        return Err(KclError::new_type(KclErrorDetails::new(
83            "The `tolerance` arg is only used with `isEqualTo`. Either remove `tolerance` or add an `isEqualTo` arg."
84                .to_owned(),
85            vec![args.source_range],
86        )));
87    }
88
89    let suffix = if let Some(err_string) = error {
90        format!(": {err_string}")
91    } else {
92        Default::default()
93    };
94    let actual = actual.n;
95
96    // Run the checks.
97    if let Some(exp) = is_greater_than {
98        let exp = exp.n;
99        _assert(
100            actual > exp,
101            &format!("Expected {actual} to be greater than {exp} but it wasn't{suffix}"),
102            args,
103        )
104        .await?;
105    }
106    if let Some(exp) = is_less_than {
107        let exp = exp.n;
108        _assert(
109            actual < exp,
110            &format!("Expected {actual} to be less than {exp} but it wasn't{suffix}"),
111            args,
112        )
113        .await?;
114    }
115    if let Some(exp) = is_greater_than_or_equal {
116        let exp = exp.n;
117        _assert(
118            actual >= exp,
119            &format!("Expected {actual} to be greater than or equal to {exp} but it wasn't{suffix}"),
120            args,
121        )
122        .await?;
123    }
124    if let Some(exp) = is_less_than_or_equal {
125        let exp = exp.n;
126        _assert(
127            actual <= exp,
128            &format!("Expected {actual} to be less than or equal to {exp} but it wasn't{suffix}"),
129            args,
130        )
131        .await?;
132    }
133    if let Some(exp) = is_equal_to {
134        let exp = exp.n;
135        let tolerance = tolerance.map(|e| e.n).unwrap_or(0.0000000001);
136        _assert(
137            (actual - exp).abs() < tolerance,
138            &format!("Expected {actual} to be equal to {exp} but it wasn't{suffix}"),
139            args,
140        )
141        .await?;
142    }
143    Ok(())
144}