1use 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
29pub 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 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 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}