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 neq = args.get_kw_arg_opt("isNotEqualTo", &RuntimeType::num_any(), exec_state)?;
39 let tolerance = args.get_kw_arg_opt("tolerance", &RuntimeType::num_any(), exec_state)?;
40 let error = args.get_kw_arg_opt("error", &RuntimeType::string(), exec_state)?;
41 inner_assert(actual, gt, lt, gte, lte, eq, neq, tolerance, error, &args).await?;
42 Ok(KclValue::none())
43}
44
45async fn inner_assert_is(actual: bool, error: Option<String>, args: &Args) -> Result<(), KclError> {
46 let error_msg = match &error {
47 Some(x) => x,
48 None => "should have been true, but it was not",
49 };
50 _assert(actual, error_msg, args).await
51}
52
53#[allow(clippy::too_many_arguments)]
54async fn inner_assert(
55 actual: TyF64,
56 is_greater_than: Option<TyF64>,
57 is_less_than: Option<TyF64>,
58 is_greater_than_or_equal: Option<TyF64>,
59 is_less_than_or_equal: Option<TyF64>,
60 is_equal_to: Option<TyF64>,
61 is_not_equal_to: Option<TyF64>,
62 tolerance: Option<TyF64>,
63 error: Option<String>,
64 args: &Args,
65) -> Result<(), KclError> {
66 let no_condition_given = [
68 &is_greater_than,
69 &is_less_than,
70 &is_greater_than_or_equal,
71 &is_less_than_or_equal,
72 &is_equal_to,
73 &is_not_equal_to,
74 ]
75 .iter()
76 .all(|cond| cond.is_none());
77 if no_condition_given {
78 return Err(KclError::new_type(KclErrorDetails::new(
79 "You must provide at least one condition in this assert (for example, isEqualTo)".to_owned(),
80 vec![args.source_range],
81 )));
82 }
83
84 if tolerance.is_some() && is_equal_to.is_none() && is_not_equal_to.is_none() {
85 return Err(KclError::new_type(KclErrorDetails::new(
86 "The `tolerance` arg is only used with `isEqualTo` and `isNotEqualTo`. Either remove `tolerance` or add an `isEqualTo` or `isNotEqualTo` arg."
87 .to_owned(),
88 vec![args.source_range],
89 )));
90 }
91
92 let suffix = if let Some(err_string) = error {
93 format!(": {err_string}")
94 } else {
95 Default::default()
96 };
97 let actual = actual.n;
98
99 if let Some(exp) = is_greater_than {
101 let exp = exp.n;
102 _assert(
103 actual > exp,
104 &format!("Expected {actual} to be greater than {exp} but it wasn't{suffix}"),
105 args,
106 )
107 .await?;
108 }
109 if let Some(exp) = is_less_than {
110 let exp = exp.n;
111 _assert(
112 actual < exp,
113 &format!("Expected {actual} to be less than {exp} but it wasn't{suffix}"),
114 args,
115 )
116 .await?;
117 }
118 if let Some(exp) = is_greater_than_or_equal {
119 let exp = exp.n;
120 _assert(
121 actual >= exp,
122 &format!("Expected {actual} to be greater than or equal to {exp} but it wasn't{suffix}"),
123 args,
124 )
125 .await?;
126 }
127 if let Some(exp) = is_less_than_or_equal {
128 let exp = exp.n;
129 _assert(
130 actual <= exp,
131 &format!("Expected {actual} to be less than or equal to {exp} but it wasn't{suffix}"),
132 args,
133 )
134 .await?;
135 }
136 const DEFAULT_TOLERANCE: f64 = 0.0000000001;
137 if let Some(exp) = is_equal_to {
138 let exp = exp.n;
139 let tolerance = tolerance.as_ref().map(|e| e.n).unwrap_or(DEFAULT_TOLERANCE);
140 _assert(
141 (actual - exp).abs() < tolerance,
142 &format!("Expected {actual} to be equal to {exp} using tolerance {tolerance} but it wasn't{suffix}"),
143 args,
144 )
145 .await?;
146 }
147 if let Some(exp) = is_not_equal_to {
148 let exp = exp.n;
149 let tolerance = tolerance.as_ref().map(|e| e.n).unwrap_or(DEFAULT_TOLERANCE);
150 _assert(
151 (actual - exp).abs() >= tolerance,
152 &format!("Expected {actual} to not be equal to {exp} using tolerance {tolerance} but it was{suffix}"),
153 args,
154 )
155 .await?;
156 }
157 Ok(())
158}