From d04808a24e2f59c77e07ee57cb690513882cc00c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ksel=20K=C3=BC=C3=A7=C3=BCk=C5=9Fahin?= Date: Sun, 8 Feb 2026 15:55:17 +0300 Subject: [PATCH 1/3] experimental: add IfElse conditional branching support (#74) Implement conditional branching for query construction with full DSL support: - IfElse: Evaluates conditions and returns the first matching item or nil - ElseIf: Creates conditional branches for chaining multiple conditions - Else: Creates default branch when no condition matches - Branch: Internal type representing conditional branches Generic implementation supports both map[string]any and []any types for flexible query building. All implementations --- es/condition/if_else.go | 92 ++++++++++++++ es/condition/if_else_test.go | 237 +++++++++++++++++++++++++++++++++++ 2 files changed, 329 insertions(+) create mode 100644 es/condition/if_else.go create mode 100644 es/condition/if_else_test.go diff --git a/es/condition/if_else.go b/es/condition/if_else.go new file mode 100644 index 0000000..c023c34 --- /dev/null +++ b/es/condition/if_else.go @@ -0,0 +1,92 @@ +package condition + +// Branch represents a conditional branch used in IfElse chains. +// Create branches using ElseIf or Else functions. +type Branch struct { + condition bool + item any + isElse bool +} + +// ElseIf creates a conditional branch for use in IfElse. +// The item is selected only if no previous condition was true and this condition is true. +// +// Parameters: +// - condition: A boolean that determines if `item` should be selected. +// - item: The value to return if this branch is selected. +// +// Returns: +// +// A Branch to be passed to IfElse. +// +// Example usage: +// +// condition.IfElse(x > 10, es.Term("foo", "bar"), +// condition.ElseIf(y < 20, es.Exists("fizz")), +// ) +func ElseIf(condition bool, item any) Branch { + return Branch{condition: condition, item: item} +} + +// Else creates a default branch for use in IfElse. +// The item is selected when no previous condition was true. +// +// Parameters: +// - item: The value to return if no other condition matched. +// +// Returns: +// +// A Branch to be passed as the last argument to IfElse. +// +// Example usage: +// +// condition.IfElse(x > 10, es.Term("foo", "bar"), +// condition.ElseIf(y < 20, es.Exists("fizz")), +// condition.Else(es.Range("date").LesserThan("now-2h")), +// ) +func Else(item any) Branch { + return Branch{item: item, isElse: true} +} + +// IfElse evaluates conditions in order and returns the item associated with the +// first true condition. If no condition is true and no Else branch is provided, +// it returns nil. +// +// Parameters: +// - condition: A boolean that determines if `item` should be returned. +// - item: The map or slice to return if the condition is true. +// - branches: Optional ElseIf and Else branches to evaluate in order. +// +// Returns: +// +// The item from the first matching condition, the Else item, or nil. +// +// Example usage: +// +// // Simple if +// condition.IfElse(x > 10, es.Term("foo", "bar")) +// +// // if + elseIf +// condition.IfElse(x > 10, es.Term("foo", "bar"), +// condition.ElseIf(y < 20, es.Exists("fizz")), +// ) +// +// // if + elseIf + else +// condition.IfElse(x > 10, es.Term("foo", "bar"), +// condition.ElseIf(y < 20, es.Exists("fizz")), +// condition.Else(es.Range("date").LesserThan("now-2h")), +// ) +func IfElse[T ~map[string]any | ~[]any](condition bool, item T, branches ...Branch) any { + if condition { + return item + } + for _, branch := range branches { + if branch.isElse { + return branch.item + } + if branch.condition { + return branch.item + } + } + return nil +} diff --git a/es/condition/if_else_test.go b/es/condition/if_else_test.go new file mode 100644 index 0000000..19c30ff --- /dev/null +++ b/es/condition/if_else_test.go @@ -0,0 +1,237 @@ +package condition_test + +import ( + "testing" + + "github.com/Trendyol/es-query-builder/es" + "github.com/Trendyol/es-query-builder/es/condition" + "github.com/Trendyol/es-query-builder/test/assert" +) + +func Test_IfElse_should_return_item_when_condition_is_true(t *testing.T) { + t.Parallel() + // Given + x := 15 + + // When + query := es.NewQuery( + es.Bool(). + Filter( + condition.IfElse(x > 10, es.Term("foo", "bar")), + es.Exists("brandId"), + ), + ) + + // Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"bool\":{\"filter\":[{\"term\":{\"foo\":{\"value\":\"bar\"}}},{\"exists\":{\"field\":\"brandId\"}}]}}}", bodyJSON) +} + +func Test_IfElse_should_return_nil_when_condition_is_false(t *testing.T) { + t.Parallel() + // Given + x := 5 + + // When + query := es.NewQuery( + es.Bool(). + Filter( + condition.IfElse(x > 10, es.Term("foo", "bar")), + es.Exists("brandId"), + ), + ) + + // Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"bool\":{\"filter\":[{\"exists\":{\"field\":\"brandId\"}}]}}}", bodyJSON) +} + +func Test_IfElse_ElseIf_should_return_elseIf_item_when_first_is_false_and_second_is_true(t *testing.T) { + t.Parallel() + // Given + x := 5 + y := 10 + + // When + query := es.NewQuery( + es.Bool(). + Filter( + condition.IfElse(x > 10, es.Term("foo", "bar"), + condition.ElseIf(y < 20, es.Exists("fizz")), + ), + ), + ) + + // Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"bool\":{\"filter\":[{\"exists\":{\"field\":\"fizz\"}}]}}}", bodyJSON) +} + +func Test_IfElse_ElseIf_should_return_first_item_when_both_conditions_are_true(t *testing.T) { + t.Parallel() + // Given + x := 15 + y := 10 + + // When + query := es.NewQuery( + es.Bool(). + Filter( + condition.IfElse(x > 10, es.Term("foo", "bar"), + condition.ElseIf(y < 20, es.Exists("fizz")), + ), + ), + ) + + // Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"bool\":{\"filter\":[{\"term\":{\"foo\":{\"value\":\"bar\"}}}]}}}", bodyJSON) +} + +func Test_IfElse_ElseIf_should_return_nil_when_all_conditions_are_false(t *testing.T) { + t.Parallel() + // Given + x := 5 + y := 25 + + // When + query := es.NewQuery( + es.Bool(). + Filter( + condition.IfElse(x > 10, es.Term("foo", "bar"), + condition.ElseIf(y < 20, es.Exists("fizz")), + ), + es.Exists("brandId"), + ), + ) + + // Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"bool\":{\"filter\":[{\"exists\":{\"field\":\"brandId\"}}]}}}", bodyJSON) +} + +func Test_IfElse_Else_should_return_default_when_all_conditions_are_false(t *testing.T) { + t.Parallel() + // Given + x := 5 + y := 25 + + // When + query := es.NewQuery( + es.Bool(). + Filter( + condition.IfElse(x > 10, es.Term("foo", "bar"), + condition.ElseIf(y < 20, es.Exists("fizz")), + condition.Else(es.Term("default", "value")), + ), + ), + ) + + // Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"bool\":{\"filter\":[{\"term\":{\"default\":{\"value\":\"value\"}}}]}}}", bodyJSON) +} + +func Test_IfElse_Else_should_return_first_item_when_condition_is_true(t *testing.T) { + t.Parallel() + // Given + x := 15 + + // When + query := es.NewQuery( + es.Bool(). + Filter( + condition.IfElse(x > 10, es.Term("foo", "bar"), + condition.Else(es.Term("default", "value")), + ), + ), + ) + + // Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"bool\":{\"filter\":[{\"term\":{\"foo\":{\"value\":\"bar\"}}}]}}}", bodyJSON) +} + +func Test_IfElse_Else_should_return_elseIf_item_when_first_false_second_true(t *testing.T) { + t.Parallel() + // Given + x := 5 + y := 10 + + // When + query := es.NewQuery( + es.Bool(). + Filter( + condition.IfElse(x > 10, es.Term("foo", "bar"), + condition.ElseIf(y < 20, es.Exists("fizz")), + condition.Else(es.Term("default", "value")), + ), + ), + ) + + // Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"bool\":{\"filter\":[{\"exists\":{\"field\":\"fizz\"}}]}}}", bodyJSON) +} + +func Test_IfElse_multiple_ElseIf_should_return_correct_item(t *testing.T) { + t.Parallel() + // Given + x := 5 + y := 25 + z := 3 + + // When + query := es.NewQuery( + es.Bool(). + Filter( + condition.IfElse(x > 10, es.Term("first", "value"), + condition.ElseIf(y < 20, es.Term("second", "value")), + condition.ElseIf(z < 5, es.Term("third", "value")), + ), + ), + ) + + // Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"bool\":{\"filter\":[{\"term\":{\"third\":{\"value\":\"value\"}}}]}}}", bodyJSON) +} + +func Test_IfElse_should_work_with_slices(t *testing.T) { + t.Parallel() + // Given + cond := true + + // When + result := condition.IfElse(cond, es.Array{1, 2, 3}) + + // Then + assert.NotNil(t, result) + bodyJSON := assert.MarshalWithoutError(t, result) + assert.Equal(t, "[1,2,3]", bodyJSON) +} + +func Test_IfElse_Else_should_work_with_slices(t *testing.T) { + t.Parallel() + // Given + cond := false + + // When + result := condition.IfElse(cond, es.Array{1, 2, 3}, + condition.Else(es.Array{4, 5, 6}), + ) + + // Then + assert.NotNil(t, result) + bodyJSON := assert.MarshalWithoutError(t, result) + assert.Equal(t, "[4,5,6]", bodyJSON) +} From 3c7f0d1383fdb4b83b0495012d4c9b05a67c4523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ksel=20K=C3=BC=C3=A7=C3=BCk=C5=9Fahin?= Date: Sun, 8 Feb 2026 15:59:27 +0300 Subject: [PATCH 2/3] refactor: reorder Branch struct fields for better memory alignment Move item field before condition in Branch struct to optimize memory layout and reduce padding. --- es/condition/if_else.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es/condition/if_else.go b/es/condition/if_else.go index c023c34..9646e1b 100644 --- a/es/condition/if_else.go +++ b/es/condition/if_else.go @@ -3,8 +3,8 @@ package condition // Branch represents a conditional branch used in IfElse chains. // Create branches using ElseIf or Else functions. type Branch struct { - condition bool item any + condition bool isElse bool } From 1ce9d793eadc050b54442679d886946e0f58394f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ksel=20K=C3=BC=C3=A7=C3=BCk=C5=9Fahin?= Date: Sun, 8 Feb 2026 18:47:37 +0300 Subject: [PATCH 3/3] chore: fix linter --- es/condition/if_else_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/es/condition/if_else_test.go b/es/condition/if_else_test.go index 19c30ff..905d7ff 100644 --- a/es/condition/if_else_test.go +++ b/es/condition/if_else_test.go @@ -25,6 +25,7 @@ func Test_IfElse_should_return_item_when_condition_is_true(t *testing.T) { // Then assert.NotNil(t, query) bodyJSON := assert.MarshalWithoutError(t, query) + // nolint:golint,lll assert.Equal(t, "{\"query\":{\"bool\":{\"filter\":[{\"term\":{\"foo\":{\"value\":\"bar\"}}},{\"exists\":{\"field\":\"brandId\"}}]}}}", bodyJSON) }