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 keywords = true,
53 unlabeled_first = true,
54 args = {
55 actual = { docs = "Value to check. If this is the boolean value true, assert passes. Otherwise it fails." },
56 error = { docs = "If the value was false, the program will terminate with this error message" },
57 }
58}]
59async fn inner_assert_is(actual: bool, error: Option<String>, args: &Args) -> Result<(), KclError> {
60 let error_msg = match &error {
61 Some(x) => x,
62 None => "should have been true, but it was not",
63 };
64 _assert(actual, error_msg, args).await
65}
66
67#[stdlib {
77 name = "assert",
78 keywords = true,
79 unlabeled_first = true,
80 args = {
81 actual = { docs = "Value to check. It will be compared with one of the comparison arguments." },
82 is_greater_than = { docs = "Comparison argument. If given, checks the `actual` value is greater than this." },
83 is_less_than = { docs = "Comparison argument. If given, checks the `actual` value is less than this." },
84 is_greater_than_or_equal = { docs = "Comparison argument. If given, checks the `actual` value is greater than or equal to this." },
85 is_less_than_or_equal = { docs = "Comparison argument. If given, checks the `actual` value is less than or equal to this." },
86 is_equal_to = { docs = "Comparison argument. If given, checks the `actual` value is less than or equal to this.", include_in_snippet = true },
87 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." },
88 error = { docs = "If the value was false, the program will terminate with this error message" },
89 }
90}]
91#[allow(clippy::too_many_arguments)]
92async fn inner_assert(
93 actual: TyF64,
94 is_greater_than: Option<TyF64>,
95 is_less_than: Option<TyF64>,
96 is_greater_than_or_equal: Option<TyF64>,
97 is_less_than_or_equal: Option<TyF64>,
98 is_equal_to: Option<TyF64>,
99 tolerance: Option<TyF64>,
100 error: Option<String>,
101 args: &Args,
102) -> Result<(), KclError> {
103 let no_condition_given = [
105 &is_greater_than,
106 &is_less_than,
107 &is_greater_than_or_equal,
108 &is_less_than_or_equal,
109 &is_equal_to,
110 ]
111 .iter()
112 .all(|cond| cond.is_none());
113 if no_condition_given {
114 return Err(KclError::Type(KclErrorDetails::new(
115 "You must provide at least one condition in this assert (for example, isEqualTo)".to_owned(),
116 vec![args.source_range],
117 )));
118 }
119
120 if tolerance.is_some() && is_equal_to.is_none() {
121 return Err(KclError::Type(KclErrorDetails::new(
122 "The `tolerance` arg is only used with `isEqualTo`. Either remove `tolerance` or add an `isEqualTo` arg."
123 .to_owned(),
124 vec![args.source_range],
125 )));
126 }
127
128 let suffix = if let Some(err_string) = error {
129 format!(": {err_string}")
130 } else {
131 Default::default()
132 };
133 let actual = actual.n;
134
135 if let Some(exp) = is_greater_than {
137 let exp = exp.n;
138 _assert(
139 actual > exp,
140 &format!("Expected {actual} to be greater than {exp} but it wasn't{suffix}"),
141 args,
142 )
143 .await?;
144 }
145 if let Some(exp) = is_less_than {
146 let exp = exp.n;
147 _assert(
148 actual < exp,
149 &format!("Expected {actual} to be less than {exp} but it wasn't{suffix}"),
150 args,
151 )
152 .await?;
153 }
154 if let Some(exp) = is_greater_than_or_equal {
155 let exp = exp.n;
156 _assert(
157 actual >= exp,
158 &format!("Expected {actual} to be greater than or equal to {exp} but it wasn't{suffix}"),
159 args,
160 )
161 .await?;
162 }
163 if let Some(exp) = is_less_than_or_equal {
164 let exp = exp.n;
165 _assert(
166 actual <= exp,
167 &format!("Expected {actual} to be less than or equal to {exp} but it wasn't{suffix}"),
168 args,
169 )
170 .await?;
171 }
172 if let Some(exp) = is_equal_to {
173 let exp = exp.n;
174 let tolerance = tolerance.map(|e| e.n).unwrap_or(0.0000000001);
175 _assert(
176 (actual - exp).abs() < tolerance,
177 &format!("Expected {actual} to be equal to {exp} but it wasn't{suffix}"),
178 args,
179 )
180 .await?;
181 }
182 Ok(())
183}