Skip to main content

kcl_lib/std/
assert.rs

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