Combining two if conditions into one
Asked Answered
G

2

5

The below works

{{- if hasKey (index $envAll.Values.policy) "type" }} 
{{- if has "two-wheeler" (index $envAll.Values.policy "type") }}
<code goes here>
{{- end }}
{{- end }}

while the below fails with "runtime error: invalid memory address or nil pointer dereference"

{{- if and (hasKey (index $envAll.Values.policy) "type") (has "two-wheeler" (index $envAll.Values.policy "type")) }}
<code goes here>
{{- end}}

There is no list by name "type" declared under $envAll.Values.policy.

In Go, if the right operand is evaluated conditionally, why does the last condition gets evaluated in the second code snippet? How do I solve it?

Edit (since it marked as duplicate): Unfortunately, I cannot use embedded {{ if }} like it is mentioned in the other post.

I simplified my problem above. I actually have to achieve this...

{{if or (and (condition A) (condition B)) (condition C)) }}
    <code goes here>
{{ end }}
Germane answered 1/4, 2019 at 23:3 Comment(4)
The and function in Go templates is not short-circuit evaluated (unlike the && operator in Go), all its arguments are evaluated always. So using 2 {{if}} is the right way to go here.Blackford
@icza, thank you. I edited my question. Could you remove the duplicate mark if you're convinced with my edit?Germane
It doesn't matter if the logic is something else: both or and and in templates evaluate all arguments, so the answer is still the same.Blackford
I'm looking for solution for my problem. The other answer does not solve my problem. As long as it is marked as duplicate, it won't get attention from others.Germane
B
9

Update: The following answer predates Go 1.18 where the argument evaluation for and and or template function changed to stop early if the results is know. The answer is only valid for prior Go versions.


You get an error when using the and function because the and function in Go templates is not short-circuit evaluated (unlike the && operator in Go), all its arguments are evaluated always. Read more about it here: Golang template and testing for Valid fields

So you have to use embedded {{if}} actions so the 2nd argument is only evaluated if the first is also true.

You edited the question and stated that your actual problem is this:

{{if or (and (condition A) (condition B)) (condition C)) }}
    <code goes here>
{{ end }}

This is how you can do it in templates only:

{{ $result := false }}
{{ if (conddition A )}}
    {{ if (condition B) }}
        {{ $result = true }}
    {{ end }}
{{ end }}
{{ if or $result (condition C) }}
    <code goes here>
{{ end }}

Another option is to pass the result of that logic to the template as a parameter.

If you can't or don't know the result before calling the template, yet another option is to register a custom function, and call this custom function from the template, and you can do short-circuit evaluation in Go code. For an example, see How to calculate something in html/template.

Blackford answered 2/4, 2019 at 14:57 Comment(3)
For reference, just add $ to variable result: $result. Otherwise, it fails with "function "result" not defined"Germane
@Germane Yes, you're right. I didn't try the example, just wrote it from memory. Fixed. Thanks.Blackford
It's worth noting that this behavior has changed in Go 1.18, and the and/or template functions are now short-circuit tip.golang.org/doc/go1.18#minor_library_changes (yes, it's described as a "minor library change")Aldaaldan
V
0

You could potentially use the Helm default function to avoid the second conditional.

It looks like your code is trying to test if .Values.policy.type.two-wheeler is present, with the caveat that the type layer may not exist at all. If there's no type, then .Values.policy.type evaluates to nil, and you can't do additional lookups in it.

The workaround, then, is to use default to substitute an empty dictionary for nil. Since it's then a dictionary you can do lookups in it, and since the default is empty testing for any specific thing will fail.

{{- $type := $envAll.Values.policy.type | default dict }}
{{- if has "two-wheeler" $type }}
<code goes here>
{{- end }}

You can put this into a one-liner if you want

{{- if has "two-wheeler" ($envAll.Values.policy.type | default dict) }}...{{ end }}

If you're actually going to use the value and not just test for its presence, another useful trick here can be to use the standard template with block instead of if. If with's conditional is "true" then it evaluates the block with . set to its value, and otherwise it skips the block (or runs an else block). In particular here, if a map value isn't present, then its lookup returns nil, which is "false" for conditional purposes (though note other things like 0 and empty string are "false" as well).

{{- $type := $envAll.Values.policy.type | default dict }}
{{- with $type.two-wheeler }}
{{-/* this references .Values.policy.type.two-wheeler.frontWheel */}}
frontWheel: {{ .frontWheel }}
backWheel: {{ .backWheel }}
{{- end }}
Virchow answered 9/7, 2022 at 10:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.