1use crate::compiler::conversion::Conversion;
2use crate::compiler::prelude::*;
3
4fn to_bool(value: Value) -> Resolved {
5 use Value::{Boolean, Bytes, Float, Integer, Null};
6
7 match value {
8 Boolean(_) => Ok(value),
9 Integer(v) => Ok(Boolean(v != 0)),
10 Float(v) => Ok(Boolean(v != 0.0)),
11 Null => Ok(Boolean(false)),
12 Bytes(v) => Conversion::Boolean
13 .convert(v)
14 .map_err(|e| e.to_string().into()),
15 v => Err(format!("unable to coerce {} into boolean", v.kind()).into()),
16 }
17}
18
19#[derive(Clone, Copy, Debug)]
20pub struct ToBool;
21
22impl Function for ToBool {
23 fn identifier(&self) -> &'static str {
24 "to_bool"
25 }
26
27 fn usage(&self) -> &'static str {
28 "Coerces the `value` into a boolean."
29 }
30
31 fn category(&self) -> &'static str {
32 Category::Coerce.as_ref()
33 }
34
35 fn internal_failure_reasons(&self) -> &'static [&'static str] {
36 &["`value` is not a supported boolean representation."]
37 }
38
39 fn return_kind(&self) -> u16 {
40 kind::BOOLEAN
41 }
42
43 fn return_rules(&self) -> &'static [&'static str] {
44 &[
45 "If `value` is `\"true\"`, `\"t\"`, `\"yes\"`, or `\"y\"`, `true` is returned.",
46 "If `value` is `\"false\"`, `\"f\"`, `\"no\"`, `\"n\"`, or `\"0\"`, `false` is returned.",
47 "If `value` is `0.0`, `false` is returned, otherwise `true` is returned.",
48 "If `value` is `0`, `false` is returned, otherwise `true` is returned.",
49 "If `value` is `null`, `false` is returned.",
50 "If `value` is a Boolean, it's returned unchanged.",
51 ]
52 }
53
54 fn parameters(&self) -> &'static [Parameter] {
55 const PARAMETERS: &[Parameter] = &[Parameter::required(
56 "value",
57 kind::ANY,
58 "The value to convert to a Boolean.",
59 )];
60 PARAMETERS
61 }
62
63 #[allow(clippy::too_many_lines)]
64 fn examples(&self) -> &'static [Example] {
65 &[
66 example! {
67 title: "Coerce to a Boolean (string)",
68 source: "to_bool!(\"yes\")",
69 result: Ok("true"),
70 },
71 example! {
72 title: "Coerce to a Boolean (float)",
73 source: "to_bool(0.0)",
74 result: Ok("false"),
75 },
76 example! {
77 title: "Coerce to a Boolean (int)",
78 source: "to_bool(0)",
79 result: Ok("false"),
80 },
81 example! {
82 title: "Coerce to a Boolean (null)",
83 source: "to_bool(null)",
84 result: Ok("false"),
85 },
86 example! {
87 title: "Coerce to a Boolean (Boolean)",
88 source: "to_bool(true)",
89 result: Ok("true"),
90 },
91 example! {
92 title: "Integer (other)",
93 source: "to_bool(2)",
94 result: Ok("true"),
95 },
96 example! {
97 title: "Float (other)",
98 source: "to_bool(5.6)",
99 result: Ok("true"),
100 },
101 example! {
102 title: "False",
103 source: "to_bool(false)",
104 result: Ok("false"),
105 },
106 example! {
107 title: "True string",
108 source: "to_bool!(s'true')",
109 result: Ok("true"),
110 },
111 example! {
112 title: "Y string",
113 source: "to_bool!(s'y')",
114 result: Ok("true"),
115 },
116 example! {
117 title: "Non-zero integer string",
118 source: "to_bool!(s'1')",
119 result: Ok("true"),
120 },
121 example! {
122 title: "False string",
123 source: "to_bool!(s'false')",
124 result: Ok("false"),
125 },
126 example! {
127 title: "No string",
128 source: "to_bool!(s'no')",
129 result: Ok("false"),
130 },
131 example! {
132 title: "N string",
133 source: "to_bool!(s'n')",
134 result: Ok("false"),
135 },
136 example! {
137 title: "Invalid string",
138 source: "to_bool!(s'foobar')",
139 result: Err(
140 r#"function call error for "to_bool" at (0:19): Invalid boolean value "foobar""#,
141 ),
142 },
143 example! {
144 title: "Timestamp",
145 source: "to_bool!(t'2020-01-01T00:00:00Z')",
146 result: Err(
147 r#"function call error for "to_bool" at (0:33): unable to coerce timestamp into boolean"#,
148 ),
149 },
150 example! {
151 title: "Array",
152 source: "to_bool!([])",
153 result: Err(
154 r#"function call error for "to_bool" at (0:12): unable to coerce array into boolean"#,
155 ),
156 },
157 example! {
158 title: "Object",
159 source: "to_bool!({})",
160 result: Err(
161 r#"function call error for "to_bool" at (0:12): unable to coerce object into boolean"#,
162 ),
163 },
164 example! {
165 title: "Regex",
166 source: "to_bool!(r'foo')",
167 result: Err(
168 r#"function call error for "to_bool" at (0:16): unable to coerce regex into boolean"#,
169 ),
170 },
171 ]
172 }
173
174 fn compile(
175 &self,
176 _state: &state::TypeState,
177 _ctx: &mut FunctionCompileContext,
178 arguments: ArgumentList,
179 ) -> Compiled {
180 let value = arguments.required("value");
181
182 Ok(ToBoolFn { value }.as_expr())
183 }
184}
185
186#[derive(Debug, Clone)]
187struct ToBoolFn {
188 value: Box<dyn Expression>,
189}
190
191impl FunctionExpression for ToBoolFn {
192 fn resolve(&self, ctx: &mut Context) -> Resolved {
193 let value = self.value.resolve(ctx)?;
194
195 to_bool(value)
196 }
197
198 fn type_def(&self, state: &state::TypeState) -> TypeDef {
199 let td = self.value.type_def(state);
200
201 TypeDef::boolean().maybe_fallible(
202 td.contains_bytes()
203 || td.contains_timestamp()
204 || td.contains_array()
205 || td.contains_object()
206 || td.contains_regex(),
207 )
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 test_function![
216 to_bool => ToBool;
217
218 string_true {
219 args: func_args![value: "true"],
220 want: Ok(true),
221 tdef: TypeDef::boolean().fallible(),
222 }
223
224 string_false {
225 args: func_args![value: "no"],
226 want: Ok(false),
227 tdef: TypeDef::boolean().fallible(),
228 }
229
230 string_error {
231 args: func_args![value: "cabbage"],
232 want: Err(r#"Invalid boolean value "cabbage""#),
233 tdef: TypeDef::boolean().fallible(),
234 }
235
236 number_true {
237 args: func_args![value: 20],
238 want: Ok(true),
239 tdef: TypeDef::boolean().infallible(),
240 }
241
242 number_false {
243 args: func_args![value: 0],
244 want: Ok(false),
245 tdef: TypeDef::boolean().infallible(),
246 }
247 ];
248}