1use crate::compiler::prelude::*;
2
3#[derive(Clone, Copy, Debug)]
4pub struct Exists;
5
6impl Function for Exists {
7 fn identifier(&self) -> &'static str {
8 "exists"
9 }
10
11 fn usage(&self) -> &'static str {
12 indoc! {"
13 Checks whether the `path` exists for the target.
14
15 This function distinguishes between a missing path
16 and a path with a `null` value. A regular path lookup,
17 such as `.foo`, cannot distinguish between the two cases
18 since it always returns `null` if the path doesn't exist.
19 "}
20 }
21
22 fn category(&self) -> &'static str {
23 Category::Path.as_ref()
24 }
25
26 fn return_kind(&self) -> u16 {
27 kind::BOOLEAN
28 }
29 fn parameters(&self) -> &'static [Parameter] {
30 const PARAMETERS: &[Parameter] = &[Parameter::required(
31 "field",
32 kind::ANY,
33 "The path of the field to check.",
34 )];
35 PARAMETERS
36 }
37
38 fn examples(&self) -> &'static [Example] {
39 &[
40 example! {
41 title: "Exists (field)",
42 source: indoc! {r#"
43 . = { "field": 1 }
44 exists(.field)
45 "#},
46 result: Ok("true"),
47 },
48 example! {
49 title: "Exists (array element)",
50 source: indoc! {r#"
51 . = { "array": [1, 2, 3] }
52 exists(.array[2])
53 "#},
54 result: Ok("true"),
55 },
56 example! {
57 title: "Does not exist (field)",
58 source: r#"exists({ "foo": "bar"}.baz)"#,
59 result: Ok("false"),
60 },
61 ]
62 }
63
64 fn compile(
65 &self,
66 _state: &state::TypeState,
67 _ctx: &mut FunctionCompileContext,
68 arguments: ArgumentList,
69 ) -> Compiled {
70 let query = arguments.required_query("field")?;
71
72 Ok(ExistsFn { query }.as_expr())
73 }
74}
75
76#[derive(Clone, Debug)]
77pub(crate) struct ExistsFn {
78 query: expression::Query,
79}
80
81fn exists(query: &expression::Query, ctx: &mut Context) -> Resolved {
82 let path = query.path();
83
84 if let Some(target_path) = query.external_path() {
85 return Ok(ctx
86 .target_mut()
87 .target_get(&target_path)
88 .ok()
89 .flatten()
90 .is_some()
91 .into());
92 }
93
94 if let Some(ident) = query.variable_ident() {
95 return match ctx.state().variable(ident) {
96 Some(value) => Ok(value.get(path).is_some().into()),
97 None => Ok(false.into()),
98 };
99 }
100
101 if let Some(expr) = query.expression_target() {
102 let value = expr.resolve(ctx)?;
103
104 return Ok(value.get(path).is_some().into());
105 }
106
107 Ok(false.into())
108}
109
110impl FunctionExpression for ExistsFn {
111 fn resolve(&self, ctx: &mut Context) -> Resolved {
112 exists(&self.query, ctx)
113 }
114
115 fn type_def(&self, _: &state::TypeState) -> TypeDef {
116 TypeDef::boolean().infallible()
117 }
118}