From 1cdd63d37d7a6396949ce2de820f7e4cbe22dd92 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Wed, 16 Jul 2025 03:31:07 -0400 Subject: [PATCH 1/2] Add Length() function to all collection types --- types/basetypes/list_value.go | 5 +++++ types/basetypes/map_value.go | 5 +++++ types/basetypes/set_value.go | 5 +++++ types/basetypes/tuple_value.go | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/types/basetypes/list_value.go b/types/basetypes/list_value.go index d0ec03027..2c42a3178 100644 --- a/types/basetypes/list_value.go +++ b/types/basetypes/list_value.go @@ -173,6 +173,11 @@ func (l ListValue) Elements() []attr.Value { return result } +// Length returns the number of elements in the List. +func (l ListValue) Length() int { + return len(l.elements) +} + // ElementsAs populates `target` with the elements of the ListValue, throwing an // error if the elements cannot be stored in `target`. func (l ListValue) ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) diag.Diagnostics { diff --git a/types/basetypes/map_value.go b/types/basetypes/map_value.go index 7d819eca9..2525d4fa2 100644 --- a/types/basetypes/map_value.go +++ b/types/basetypes/map_value.go @@ -178,6 +178,11 @@ func (m MapValue) Elements() map[string]attr.Value { return result } +// Length returns the number of elements in the Map. +func (m MapValue) Length() int { + return len(m.elements) +} + // ElementsAs populates `target` with the elements of the MapValue, throwing an // error if the elements cannot be stored in `target`. func (m MapValue) ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) diag.Diagnostics { diff --git a/types/basetypes/set_value.go b/types/basetypes/set_value.go index 2064e8fb2..f48eae721 100644 --- a/types/basetypes/set_value.go +++ b/types/basetypes/set_value.go @@ -173,6 +173,11 @@ func (s SetValue) Elements() []attr.Value { return result } +// Length returns the number of elements in the Set. +func (s SetValue) Length() int { + return len(s.elements) +} + // ElementsAs populates `target` with the elements of the SetValue, throwing an // error if the elements cannot be stored in `target`. func (s SetValue) ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) diag.Diagnostics { diff --git a/types/basetypes/tuple_value.go b/types/basetypes/tuple_value.go index 5987d3824..cf7cd87b7 100644 --- a/types/basetypes/tuple_value.go +++ b/types/basetypes/tuple_value.go @@ -131,6 +131,11 @@ func (v TupleValue) Elements() []attr.Value { return result } +// Length returns the number of elements in the Tuple. +func (v TupleValue) Length() int { + return len(v.elements) +} + // ElementTypes returns the ordered list of element types for the Tuple. func (v TupleValue) ElementTypes(ctx context.Context) []attr.Type { return v.elementTypes From 8de6b0efa57145d1aa57c8686784381f4141162d Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Wed, 16 Jul 2025 04:03:10 -0400 Subject: [PATCH 2/2] Add tests --- types/basetypes/list_value_test.go | 45 +++++++++++++++++++++++++++ types/basetypes/map_value_test.go | 45 +++++++++++++++++++++++++++ types/basetypes/set_value_test.go | 47 +++++++++++++++++++++++++++++ types/basetypes/tuple_value_test.go | 45 +++++++++++++++++++++++++++ 4 files changed, 182 insertions(+) diff --git a/types/basetypes/list_value_test.go b/types/basetypes/list_value_test.go index 2532d4bd4..26d5fe814 100644 --- a/types/basetypes/list_value_test.go +++ b/types/basetypes/list_value_test.go @@ -841,6 +841,51 @@ func TestListValueType(t *testing.T) { } } +func TestListValueLength(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + input ListValue + expected int + }{ + "known-empty": { + input: NewListValueMust(StringType{}, []attr.Value{}), + expected: 0, + }, + "known-single": { + input: NewListValueMust(StringType{}, []attr.Value{NewStringValue("test")}), + expected: 1, + }, + "known-multiple": { + input: NewListValueMust(StringType{}, []attr.Value{ + NewStringValue("hello"), + NewStringValue("world"), + }), + expected: 2, + }, + "null": { + input: NewListNull(StringType{}), + expected: 0, + }, + "unknown": { + input: NewListUnknown(StringType{}), + expected: 0, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.input.Length() + + if got != testCase.expected { + t.Errorf("Expected %d, got %d", testCase.expected, got) + } + }) + } +} + func TestListTypeValidate(t *testing.T) { t.Parallel() diff --git a/types/basetypes/map_value_test.go b/types/basetypes/map_value_test.go index 293fff102..82b4d7895 100644 --- a/types/basetypes/map_value_test.go +++ b/types/basetypes/map_value_test.go @@ -856,6 +856,51 @@ func TestMapValueType(t *testing.T) { } } +func TestMapValueLength(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + input MapValue + expected int + }{ + "known-empty": { + input: NewMapValueMust(StringType{}, map[string]attr.Value{}), + expected: 0, + }, + "known-single": { + input: NewMapValueMust(StringType{}, map[string]attr.Value{"key": NewStringValue("test")}), + expected: 1, + }, + "known-multiple": { + input: NewMapValueMust(StringType{}, map[string]attr.Value{ + "key1": NewStringValue("hello"), + "key2": NewStringValue("world"), + }), + expected: 2, + }, + "null": { + input: NewMapNull(StringType{}), + expected: 0, + }, + "unknown": { + input: NewMapUnknown(StringType{}), + expected: 0, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.input.Length() + + if got != testCase.expected { + t.Errorf("Expected %d, got %d", testCase.expected, got) + } + }) + } +} + func TestMapTypeValidate(t *testing.T) { t.Parallel() diff --git a/types/basetypes/set_value_test.go b/types/basetypes/set_value_test.go index 5dbfa25a7..9dcd363dd 100644 --- a/types/basetypes/set_value_test.go +++ b/types/basetypes/set_value_test.go @@ -1092,3 +1092,50 @@ func TestSetValueType(t *testing.T) { }) } } + +func TestSetValueLength(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + input SetValue + expected int + }{ + "known-empty": { + input: NewSetValueMust(StringType{}, []attr.Value{}), + expected: 0, + }, + "known-single": { + input: NewSetValueMust(StringType{}, []attr.Value{NewStringValue("test")}), + expected: 1, + }, + "known-multiple": { + input: NewSetValueMust(StringType{}, []attr.Value{ + NewStringValue("hello"), + NewStringValue("world"), + }), + expected: 2, + }, + "null": { + input: NewSetNull(StringType{}), + expected: 0, + }, + "unknown": { + input: NewSetUnknown(StringType{}), + expected: 0, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.input.Length() + + if got != testCase.expected { + t.Errorf("Expected %d, got %d", testCase.expected, got) + } + }) + } +} + + diff --git a/types/basetypes/tuple_value_test.go b/types/basetypes/tuple_value_test.go index 5736ed262..caa09938d 100644 --- a/types/basetypes/tuple_value_test.go +++ b/types/basetypes/tuple_value_test.go @@ -732,3 +732,48 @@ func TestTupleValueToTerraformValue(t *testing.T) { }) } } + +func TestTupleValueLength(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + input TupleValue + expected int + }{ + "known-empty": { + input: NewTupleValueMust([]attr.Type{}, []attr.Value{}), + expected: 0, + }, + "known-single": { + input: NewTupleValueMust([]attr.Type{StringType{}}, []attr.Value{NewStringValue("test")}), + expected: 1, + }, + "known-multiple": { + input: NewTupleValueMust( + []attr.Type{StringType{}, BoolType{}}, + []attr.Value{NewStringValue("hello"), NewBoolValue(true)}, + ), + expected: 2, + }, + "null": { + input: NewTupleNull([]attr.Type{StringType{}, BoolType{}}), + expected: 0, + }, + "unknown": { + input: NewTupleUnknown([]attr.Type{StringType{}, BoolType{}}), + expected: 0, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.input.Length() + + if got != testCase.expected { + t.Errorf("Expected %d, got %d", testCase.expected, got) + } + }) + } +}