From 73d3323871314076123b98a7e1aec22175e5a11a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:51:28 +0000 Subject: [PATCH 1/2] Initial plan From 4867cbdc05fbf374e6e1a2834f91e8de63d08181 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:20:30 +0000 Subject: [PATCH 2/2] Refactor: extract duplicated list type handling into reusable helper function - Created EnsureTypedList helper in typeutils package - Replaced three instances of duplicated logic for handling untyped zero-value lists - Removed unused strings import from resource.go - All three instances (CategorizationFilters, Influencers, CustomRules) now use the centralized helper Co-authored-by: neiljbrookes <851324+neiljbrookes@users.noreply.github.com> --- .../ml/anomaly_detection_job/models_tf.go | 24 +++++-------------- .../ml/anomaly_detection_job/resource.go | 1 - internal/utils/typeutils/list.go | 20 ++++++++++++++++ 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/internal/elasticsearch/ml/anomaly_detection_job/models_tf.go b/internal/elasticsearch/ml/anomaly_detection_job/models_tf.go index 8f304f78d..0f58eecb4 100644 --- a/internal/elasticsearch/ml/anomaly_detection_job/models_tf.go +++ b/internal/elasticsearch/ml/anomaly_detection_job/models_tf.go @@ -362,23 +362,15 @@ func (tfModel *AnomalyDetectionJobTFModel) convertAnalysisConfigFromAPI(ctx cont var categorizationFiltersDiags diag.Diagnostics analysisConfigTF.CategorizationFilters, categorizationFiltersDiags = typeutils.NonEmptyListOrDefault(ctx, analysisConfigTF.CategorizationFilters, types.StringType, apiConfig.CategorizationFilters) diags.Append(categorizationFiltersDiags...) - // If the existing value was an untyped zero-value list (common during import), force a typed null list. - if analysisConfigTF.CategorizationFilters.ElementType(ctx) == nil { - analysisConfigTF.CategorizationFilters = types.ListNull(types.StringType) - } else if _, ok := analysisConfigTF.CategorizationFilters.ElementType(ctx).(basetypes.DynamicType); ok { - analysisConfigTF.CategorizationFilters = types.ListNull(types.StringType) - } + // Ensure the list is properly typed (handles untyped zero-value lists from import) + analysisConfigTF.CategorizationFilters = typeutils.EnsureTypedList(ctx, analysisConfigTF.CategorizationFilters, types.StringType) // Convert influencers var influencersDiags diag.Diagnostics analysisConfigTF.Influencers, influencersDiags = typeutils.NonEmptyListOrDefault(ctx, analysisConfigTF.Influencers, types.StringType, apiConfig.Influencers) diags.Append(influencersDiags...) - // If the existing value was an untyped zero-value list (common during import), force a typed null list. - if analysisConfigTF.Influencers.ElementType(ctx) == nil { - analysisConfigTF.Influencers = types.ListNull(types.StringType) - } else if _, ok := analysisConfigTF.Influencers.ElementType(ctx).(basetypes.DynamicType); ok { - analysisConfigTF.Influencers = types.ListNull(types.StringType) - } + // Ensure the list is properly typed (handles untyped zero-value lists from import) + analysisConfigTF.Influencers = typeutils.EnsureTypedList(ctx, analysisConfigTF.Influencers, types.StringType) // Convert detectors if len(apiConfig.Detectors) > 0 { @@ -449,12 +441,8 @@ func (tfModel *AnomalyDetectionJobTFModel) convertAnalysisConfigFromAPI(ctx cont var customRulesDiags diag.Diagnostics detectorsTF[i].CustomRules, customRulesDiags = typeutils.NonEmptyListOrDefault(ctx, originalDetector.CustomRules, types.ObjectType{AttrTypes: getCustomRuleAttrTypes()}, apiConfig.Detectors[i].CustomRules) diags.Append(customRulesDiags...) - // If the existing value was an untyped zero-value list (common during import), force a typed null list. - if detectorsTF[i].CustomRules.ElementType(ctx) == nil { - detectorsTF[i].CustomRules = types.ListNull(types.ObjectType{AttrTypes: getCustomRuleAttrTypes()}) - } else if _, ok := detectorsTF[i].CustomRules.ElementType(ctx).(basetypes.DynamicType); ok { - detectorsTF[i].CustomRules = types.ListNull(types.ObjectType{AttrTypes: getCustomRuleAttrTypes()}) - } + // Ensure the list is properly typed (handles untyped zero-value lists from import) + detectorsTF[i].CustomRules = typeutils.EnsureTypedList(ctx, detectorsTF[i].CustomRules, types.ObjectType{AttrTypes: getCustomRuleAttrTypes()}) } analysisConfigTF.Detectors = detectorsTF } diff --git a/internal/elasticsearch/ml/anomaly_detection_job/resource.go b/internal/elasticsearch/ml/anomaly_detection_job/resource.go index 9c45e6afb..a59ec7c9b 100644 --- a/internal/elasticsearch/ml/anomaly_detection_job/resource.go +++ b/internal/elasticsearch/ml/anomaly_detection_job/resource.go @@ -2,7 +2,6 @@ package anomaly_detection_job import ( "context" - "strings" "github.com/elastic/terraform-provider-elasticstack/internal/clients" fwdiags "github.com/hashicorp/terraform-plugin-framework/diag" diff --git a/internal/utils/typeutils/list.go b/internal/utils/typeutils/list.go index 0a3715000..87282b33a 100644 --- a/internal/utils/typeutils/list.go +++ b/internal/utils/typeutils/list.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) func NonEmptyListOrDefault[T any](ctx context.Context, original types.List, elemType attr.Type, slice []T) (types.List, diag.Diagnostics) { @@ -15,3 +16,22 @@ func NonEmptyListOrDefault[T any](ctx context.Context, original types.List, elem return types.ListValueFrom(ctx, elemType, slice) } + +// EnsureTypedList converts untyped zero-value lists to properly typed null lists. +// This is commonly needed during import operations where the framework may create +// untyped lists with DynamicPseudoType elements, which causes type conversion errors. +// If the list already has a proper type, it is returned unchanged. +func EnsureTypedList(ctx context.Context, list types.List, elemType attr.Type) types.List { + // Check if the list has no element type (nil) + if list.ElementType(ctx) == nil { + return types.ListNull(elemType) + } + + // Check if the list has a dynamic pseudo type + if _, ok := list.ElementType(ctx).(basetypes.DynamicType); ok { + return types.ListNull(elemType) + } + + // List is already properly typed, return as-is + return list +}