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