Skip to content

Commit 331240a

Browse files
authored
Merge pull request #5901 from zakcutner/apijson
feat(apijson): add `decode_null_to_zero` tag option
2 parents 0007628 + f5b955b commit 331240a

File tree

8 files changed

+5347
-65
lines changed

8 files changed

+5347
-65
lines changed

internal/apijsoncustom/decoder.go

Lines changed: 1569 additions & 0 deletions
Large diffs are not rendered by default.

internal/apijsoncustom/encoder.go

Lines changed: 735 additions & 0 deletions
Large diffs are not rendered by default.

internal/apijsoncustom/json_test.go

Lines changed: 2869 additions & 0 deletions
Large diffs are not rendered by default.

internal/apijsoncustom/registry.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package apijsoncustom
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/tidwall/gjson"
7+
)
8+
9+
type UnionVariant struct {
10+
TypeFilter gjson.Type
11+
DiscriminatorValue interface{}
12+
Type reflect.Type
13+
}
14+
15+
var unionRegistry = map[reflect.Type]unionEntry{}
16+
17+
type unionEntry struct {
18+
discriminatorKey string
19+
variants []UnionVariant
20+
}
21+
22+
func RegisterUnion(typ reflect.Type, discriminator string, variants ...UnionVariant) {
23+
unionRegistry[typ] = unionEntry{
24+
discriminatorKey: discriminator,
25+
variants: variants,
26+
}
27+
}

internal/apijsoncustom/tag.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package apijsoncustom
2+
3+
import (
4+
"reflect"
5+
"strings"
6+
)
7+
8+
const jsonStructTag = "json"
9+
const formatStructTag = "format"
10+
11+
type parsedStructTag struct {
12+
name string
13+
extras bool
14+
metadata bool
15+
inline bool
16+
required bool
17+
optional bool
18+
computed bool
19+
computed_optional bool
20+
noRefresh bool
21+
// Don't skip this value, even if it's computed (no-op for computed optional fields)
22+
// If encodeStateForUnknown is set on a computed field, this flag should also be set;
23+
// otherwise this flag will have no effect
24+
// NOTE: won't work if update behavior is 'patch'
25+
forceEncode bool
26+
// If the value in the plan is unknown,
27+
// encode the value from the state instead
28+
// This is similar to the UseStateForUnknown plan modifier,
29+
// but it only impacts serialization of request bodies, not planning.
30+
// NOTE #1: only use this for computed/computed_optional values that may be changed by the server;
31+
// otherwise just use the UseStateForUnknown plan modifier
32+
// NOTE #2: won't work if update behavior is 'patch'
33+
encodeStateValueWhenPlanUnknown bool
34+
// decodeZeroValueWhenNull indicates whether null and omitted values should
35+
// be decoded as the zero value of the field type instead of leaving the
36+
// field unset.
37+
decodeZeroValueWhenNull bool
38+
}
39+
40+
func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) {
41+
raw, ok := field.Tag.Lookup(jsonStructTag)
42+
if !ok {
43+
return
44+
}
45+
parts := strings.Split(raw, ",")
46+
if len(parts) == 0 {
47+
return tag, false
48+
}
49+
tag.name = parts[0]
50+
for _, part := range parts[1:] {
51+
switch part {
52+
case "extras":
53+
tag.extras = true
54+
case "metadata":
55+
tag.metadata = true
56+
case "inline":
57+
tag.inline = true
58+
case "required":
59+
tag.required = true
60+
case "optional":
61+
tag.optional = true
62+
case "computed":
63+
tag.computed = true
64+
case "computed_optional":
65+
tag.computed_optional = true
66+
case "no_refresh":
67+
tag.noRefresh = true
68+
case "encode_state_for_unknown":
69+
tag.encodeStateValueWhenPlanUnknown = true
70+
case "decode_null_to_zero":
71+
tag.decodeZeroValueWhenNull = true
72+
case "force_encode":
73+
tag.forceEncode = true
74+
}
75+
}
76+
return
77+
}
78+
79+
func parseFormatStructTag(field reflect.StructField) (format string, ok bool) {
80+
format, ok = field.Tag.Lookup(formatStructTag)
81+
return
82+
}

internal/services/ruleset/data_source.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
"github.com/cloudflare/cloudflare-go/v5"
1212
"github.com/cloudflare/cloudflare-go/v5/option"
13-
"github.com/cloudflare/terraform-provider-cloudflare/internal/apijson"
13+
"github.com/cloudflare/terraform-provider-cloudflare/internal/apijsoncustom"
1414
"github.com/cloudflare/terraform-provider-cloudflare/internal/logging"
1515
"github.com/hashicorp/terraform-plugin-framework/datasource"
1616
)
@@ -77,7 +77,7 @@ func (d *RulesetDataSource) Read(ctx context.Context, req datasource.ReadRequest
7777
return
7878
}
7979
bytes, _ := io.ReadAll(res.Body)
80-
err = apijson.UnmarshalComputed(bytes, &env)
80+
err = apijsoncustom.UnmarshalComputed(bytes, &env)
8181
if err != nil {
8282
resp.Diagnostics.AddError("failed to deserialize http request", err.Error())
8383
return

internal/services/ruleset/model.go

Lines changed: 58 additions & 58 deletions
Large diffs are not rendered by default.

internal/services/ruleset/resource.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/cloudflare/cloudflare-go/v5"
1212
"github.com/cloudflare/cloudflare-go/v5/option"
1313
"github.com/cloudflare/cloudflare-go/v5/rulesets"
14-
"github.com/cloudflare/terraform-provider-cloudflare/internal/apijson"
14+
"github.com/cloudflare/terraform-provider-cloudflare/internal/apijsoncustom"
1515
"github.com/cloudflare/terraform-provider-cloudflare/internal/customfield"
1616
"github.com/cloudflare/terraform-provider-cloudflare/internal/importpath"
1717
"github.com/cloudflare/terraform-provider-cloudflare/internal/logging"
@@ -92,7 +92,7 @@ func (r *RulesetResource) Create(ctx context.Context, req resource.CreateRequest
9292
return
9393
}
9494
bytes, _ := io.ReadAll(res.Body)
95-
err = apijson.UnmarshalComputed(bytes, &env)
95+
err = apijsoncustom.UnmarshalComputed(bytes, &env)
9696
if err != nil {
9797
resp.Diagnostics.AddError("failed to deserialize http request", err.Error())
9898
return
@@ -143,7 +143,7 @@ func (r *RulesetResource) Update(ctx context.Context, req resource.UpdateRequest
143143
return
144144
}
145145
bytes, _ := io.ReadAll(res.Body)
146-
err = apijson.UnmarshalComputed(bytes, &env)
146+
err = apijsoncustom.UnmarshalComputed(bytes, &env)
147147
if err != nil {
148148
resp.Diagnostics.AddError("failed to deserialize http request", err.Error())
149149
return
@@ -189,7 +189,7 @@ func (r *RulesetResource) Read(ctx context.Context, req resource.ReadRequest, re
189189
return
190190
}
191191
bytes, _ := io.ReadAll(res.Body)
192-
err = apijson.Unmarshal(bytes, &env)
192+
err = apijsoncustom.Unmarshal(bytes, &env)
193193
if err != nil {
194194
resp.Diagnostics.AddError("failed to deserialize http request", err.Error())
195195
return
@@ -275,7 +275,7 @@ func (r *RulesetResource) ImportState(ctx context.Context, req resource.ImportSt
275275
return
276276
}
277277
bytes, _ := io.ReadAll(res.Body)
278-
err = apijson.Unmarshal(bytes, &env)
278+
err = apijsoncustom.Unmarshal(bytes, &env)
279279
if err != nil {
280280
resp.Diagnostics.AddError("failed to deserialize http request", err.Error())
281281
return

0 commit comments

Comments
 (0)