1use anyhow::Result;
4use kcl_derive_docs::stdlib;
5
6use super::args::TyF64;
7use crate::{
8 errors::{KclError, KclErrorDetails},
9 execution::{ExecState, KclValue},
10 std::Args,
11};
12
13async fn _assert(value: bool, message: &str, args: &Args) -> Result<(), KclError> {
14 if !value {
15 return Err(KclError::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")?;
25 let error = args.get_kw_arg_opt("error")?;
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")?;
34 let gt = args.get_kw_arg_opt("isGreaterThan")?;
35 let lt = args.get_kw_arg_opt("isLessThan")?;
36 let gte = args.get_kw_arg_opt("isGreaterThanOrEqual")?;
37 let lte = args.get_kw_arg_opt("isLessThanOrEqual")?;
38 let eq = args.get_kw_arg_opt("isEqualTo")?;
39 let tolerance = args.get_kw_arg_opt("tolerance")?;
40 let error = args.get_kw_arg_opt("error")?;
41 inner_assert(actual, gt, lt, gte, lte, eq, tolerance, error, &args).await?;
42 Ok(KclValue::none())
43}
44
45#[stdlib{
51 name = "assertIs",
52 unlabeled_first = true,
53 args = {
54 actual = { docs = "Value to check. If this is the boolean value true, assert passes. Otherwise it fails." },
55 error = { docs = "If the value was false, the program will terminate with this error message" },
56 }
57}]
58async fn inner_assert_is(actual: bool, error: Option<String>, args: &Args) -> Result<(), KclError> {
59 let error_msg = match &error {
60 Some(x) => x,
61 None => "should have been true, but it was not",
62 };
63 _assert(actual, error_msg, args).await
64}
65
66#[stdlib {
76 name = "assert",
77 unlabeled_first = true,
78 args = {
79 actual = { docs = "Value to check. It will be compared with one of the comparison arguments." },
80 is_greater_than = { docs = "Comparison argument. If given, checks the `actual` value is greater than this." },
81 is_less_than = { docs = "Comparison argument. If given, checks the `actual` value is less than this." },
82 is_greater_than_or_equal = { docs = "Comparison argument. If given, checks the `actual` value is greater than or equal to this." },
83 is_less_than_or_equal = { docs = "Comparison argument. If given, checks the `actual` value is less than or equal to this." },
84 is_equal_to = { docs = "Comparison argument. If given, checks the `actual` value is less than or equal to this.", include_in_snippet = true },
85 tolerance = { docs = "If `isEqualTo` is used, this is the tolerance to allow for the comparison. This tolerance is used because KCL's number system has some floating-point imprecision when used with very large decimal places." },
86 error = { docs = "If the value was false, the program will terminate with this error message" },
87 }
88}]
89#[allow(clippy::too_many_arguments)]
90async fn inner_assert(
91 actual: TyF64,
92 is_greater_than: Option<TyF64>,
93 is_less_than: Option<TyF64>,
94 is_greater_than_or_equal: Option<TyF64>,
95 is_less_than_or_equal: Option<TyF64>,
96 is_equal_to: Option<TyF64>,
97 tolerance: Option<TyF64>,
98 error: Option<String>,
99 args: &Args,
100) -> Result<(), KclError> {
101 let no_condition_given = [
103 &is_greater_than,
104 &is_less_than,
105 &is_greater_than_or_equal,
106 &is_less_than_or_equal,
107 &is_equal_to,
108 ]
109 .iter()
110 .all(|cond| cond.is_none());
111 if no_condition_given {
112 return Err(KclError::Type(KclErrorDetails::new(
113 "You must provide at least one condition in this assert (for example, isEqualTo)".to_owned(),
114 vec![args.source_range],
115 )));
116 }
117
118 if tolerance.is_some() && is_equal_to.is_none() {
119 return Err(KclError::Type(KclErrorDetails::new(
120 "The `tolerance` arg is only used with `isEqualTo`. Either remove `tolerance` or add an `isEqualTo` arg."
121 .to_owned(),
122 vec![args.source_range],
123 )));
124 }
125
126 let suffix = if let Some(err_string) = error {
127 format!(": {err_string}")
128 } else {
129 Default::default()
130 };
131 let actual = actual.n;
132
133 if let Some(exp) = is_greater_than {
135 let exp = exp.n;
136 _assert(
137 actual > exp,
138 &format!("Expected {actual} to be greater than {exp} but it wasn't{suffix}"),
139 args,
140 )
141 .await?;
142 }
143 if let Some(exp) = is_less_than {
144 let exp = exp.n;
145 _assert(
146 actual < exp,
147 &format!("Expected {actual} to be less than {exp} but it wasn't{suffix}"),
148 args,
149 )
150 .await?;
151 }
152 if let Some(exp) = is_greater_than_or_equal {
153 let exp = exp.n;
154 _assert(
155 actual >= exp,
156 &format!("Expected {actual} to be greater than or equal to {exp} but it wasn't{suffix}"),
157 args,
158 )
159 .await?;
160 }
161 if let Some(exp) = is_less_than_or_equal {
162 let exp = exp.n;
163 _assert(
164 actual <= exp,
165 &format!("Expected {actual} to be less than or equal to {exp} but it wasn't{suffix}"),
166 args,
167 )
168 .await?;
169 }
170 if let Some(exp) = is_equal_to {
171 let exp = exp.n;
172 let tolerance = tolerance.map(|e| e.n).unwrap_or(0.0000000001);
173 _assert(
174 (actual - exp).abs() < tolerance,
175 &format!("Expected {actual} to be equal to {exp} but it wasn't{suffix}"),
176 args,
177 )
178 .await?;
179 }
180 Ok(())
181}