@@ -11,6 +11,8 @@ import (
1111 "github.com/cloudflare/terraform-provider-cloudflare/internal/customfield"
1212 "github.com/hashicorp/terraform-plugin-framework/attr"
1313 "github.com/hashicorp/terraform-plugin-framework/datasource"
14+ "github.com/hashicorp/terraform-plugin-framework/types"
15+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
1416)
1517
1618type DNSRecordsDataSource struct {
@@ -92,9 +94,113 @@ func (d *DNSRecordsDataSource) Read(ctx context.Context, req datasource.ReadRequ
9294 }
9395
9496 acc = acc [:min (len (acc ), maxItems )]
97+
98+ // Records returned in the same list may have differing concrete underlying
99+ // types for `data.flags` (e.g. CAA records carry a number, A records omit
100+ // the field and decode as a typeless dynamic null). Terraform's list
101+ // serialiser requires every element of a list to share an identical type,
102+ // so the differing per-record types crash the data source with
103+ // "inconsistent list element types". Normalise the dynamic flags so every
104+ // element exposes the same underlying type before constructing the list.
105+ // See https://github.com/cloudflare/terraform-provider-cloudflare/issues/7004
106+ normalizeFlagsTypes (ctx , acc )
107+
95108 result , diags := customfield .NewObjectListFromAttributes [DNSRecordsResultDataSourceModel ](ctx , acc )
96109 resp .Diagnostics .Append (diags ... )
97110 data .Result = result
98111
99112 resp .Diagnostics .Append (resp .State .Set (ctx , & data )... )
100113}
114+
115+ // normalizeFlagsTypes ensures every element's `data.flags` exposes the same
116+ // concrete underlying type. When any sibling carries a known concrete value, an
117+ // otherwise typeless dynamic null is rewrapped as a typed null of that type so
118+ // the resulting list has a uniform element type.
119+ func normalizeFlagsTypes (ctx context.Context , acc []attr.Value ) {
120+ if len (acc ) < 2 {
121+ return
122+ }
123+
124+ models := make ([]* DNSRecordsResultDataSourceModel , len (acc ))
125+
126+ var concreteType attr.Type
127+ for i , el := range acc {
128+ obj , ok := el .(customfield.NestedObject [DNSRecordsResultDataSourceModel ])
129+ if ! ok {
130+ return
131+ }
132+ m , diags := obj .Value (ctx )
133+ if diags .HasError () || m == nil {
134+ return
135+ }
136+ models [i ] = m
137+ if concreteType != nil {
138+ continue
139+ }
140+ data , diags := m .Data .Value (ctx )
141+ if diags .HasError () || data == nil {
142+ continue
143+ }
144+ dyn , _ := data .Flags .ToDynamicValue (ctx )
145+ if dyn .IsNull () || dyn .IsUnknown () {
146+ continue
147+ }
148+ if underlying := dyn .UnderlyingValue (); underlying != nil && ! underlying .IsNull () && ! underlying .IsUnknown () {
149+ concreteType = underlying .Type (ctx )
150+ }
151+ }
152+
153+ if concreteType == nil {
154+ return
155+ }
156+
157+ typedNull , err := makeTypedDynamicNull (concreteType )
158+ if err {
159+ return
160+ }
161+
162+ changed := false
163+ for _ , m := range models {
164+ data , diags := m .Data .Value (ctx )
165+ if diags .HasError () || data == nil {
166+ continue
167+ }
168+ dyn , _ := data .Flags .ToDynamicValue (ctx )
169+ if ! dyn .IsNull () {
170+ if underlying := dyn .UnderlyingValue (); underlying != nil && ! underlying .IsNull () {
171+ continue
172+ }
173+ }
174+ data .Flags = typedNull
175+ m .Data = customfield .NewObjectMust (ctx , data )
176+ changed = true
177+ }
178+
179+ if ! changed {
180+ return
181+ }
182+
183+ for i , m := range models {
184+ obj , diags := customfield .NewObject (ctx , m )
185+ if diags .HasError () {
186+ return
187+ }
188+ acc [i ] = obj
189+ }
190+ }
191+
192+ // makeTypedDynamicNull wraps a typed null of the given concrete type into a
193+ // NormalizedDynamicValue so that ToTerraformValue serialises a typed null
194+ // rather than the unconstrained DynamicPseudoType.
195+ func makeTypedDynamicNull (t attr.Type ) (customfield.NormalizedDynamicValue , bool ) {
196+ var inner attr.Value
197+ switch t .(type ) {
198+ case basetypes.Float64Type :
199+ inner = types .Float64Null ()
200+ case basetypes.StringType :
201+ inner = types .StringNull ()
202+ default :
203+ return customfield.NormalizedDynamicValue {}, true
204+ }
205+ return customfield .RawNormalizedDynamicValueFrom (inner ), false
206+ }
0 commit comments