Skip to content

Commit 933f0a7

Browse files
authored
fix: remove omitempty on bool fields and fix GetMonitor response parsing (#10)
* fix: remove omitempty on bool fields and fix GetMonitor response parsing Two bugs fixed: 1. Bool fields (followRedirects, active, public) used `json:",omitempty"` which omits `false` from JSON serialization. The API never receives `false` and defaults to `true`. Removed omitempty from all bool fields across HTTP, TCP, and DNS monitor resources. 2. GetMonitor API response is wrapped in a `monitor` key: `{"monitor": {"http": {...}}}` but the provider expected `{"http": {...}}`. Added the wrapper struct so Read/Import correctly parse the response. * test: add tests for bool omitempty fix and GetMonitor response parsing
1 parent fe2d727 commit 933f0a7

7 files changed

Lines changed: 213 additions & 45 deletions

internal/monitor/dns_monitor_resource.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ type dnsMonitorAPIObject struct {
159159
Timeout jsonInt64 `json:"timeout,omitempty"`
160160
DegradedAt jsonInt64 `json:"degradedAt,omitempty"`
161161
Retry jsonInt64 `json:"retry,omitempty"`
162-
Active bool `json:"active,omitempty"`
163-
Public bool `json:"public,omitempty"`
162+
Active bool `json:"active"`
163+
Public bool `json:"public"`
164164
Description string `json:"description,omitempty"`
165165
Regions []string `json:"regions,omitempty"`
166166
RecordAssertions []apiRecordAssertion `json:"recordAssertions,omitempty"`
@@ -230,12 +230,12 @@ func (r *dnsMonitorResource) Read(ctx context.Context, req resource.ReadRequest,
230230
return
231231
}
232232

233-
if apiResp.DNS == nil {
233+
if apiResp.Monitor.DNS == nil {
234234
resp.State.RemoveResource(ctx)
235235
return
236236
}
237237

238-
resp.Diagnostics.Append(dnsAPIToModel(ctx, *apiResp.DNS, &data)...)
238+
resp.Diagnostics.Append(dnsAPIToModel(ctx, *apiResp.Monitor.DNS, &data)...)
239239
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
240240
}
241241

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package monitor
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
)
7+
8+
func TestDNSMonitorAPIObject_BoolFalseNotOmitted(t *testing.T) {
9+
obj := dnsMonitorAPIObject{
10+
Name: "test",
11+
URI: "example.com",
12+
Periodicity: "PERIODICITY_1M",
13+
Active: false,
14+
Public: false,
15+
}
16+
17+
data, err := json.Marshal(obj)
18+
if err != nil {
19+
t.Fatalf("unexpected marshal error: %v", err)
20+
}
21+
22+
var raw map[string]interface{}
23+
if err := json.Unmarshal(data, &raw); err != nil {
24+
t.Fatalf("unexpected unmarshal error: %v", err)
25+
}
26+
27+
for _, field := range []string{"active", "public"} {
28+
val, ok := raw[field]
29+
if !ok {
30+
t.Errorf("field %q omitted from JSON when false — omitempty must be removed", field)
31+
continue
32+
}
33+
if val != false {
34+
t.Errorf("field %q = %v, want false", field, val)
35+
}
36+
}
37+
}
38+
39+
func TestGetMonitorResponse_NestedMonitorWrapper_DNS(t *testing.T) {
40+
apiJSON := `{"monitor":{"dns":{"name":"DNS Check","uri":"example.com"}}}`
41+
42+
var resp getMonitorResponse
43+
if err := json.Unmarshal([]byte(apiJSON), &resp); err != nil {
44+
t.Fatalf("unexpected unmarshal error: %v", err)
45+
}
46+
47+
if resp.Monitor.DNS == nil {
48+
t.Fatal("expected monitor.dns to be parsed, got nil")
49+
}
50+
if resp.Monitor.DNS.Name != "DNS Check" {
51+
t.Errorf("name = %q, want %q", resp.Monitor.DNS.Name, "DNS Check")
52+
}
53+
}

internal/monitor/http_monitor_resource.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,9 @@ type httpMonitorAPIObject struct {
260260
Timeout jsonInt64 `json:"timeout,omitempty"`
261261
DegradedAt jsonInt64 `json:"degradedAt,omitempty"`
262262
Retry jsonInt64 `json:"retry,omitempty"`
263-
FollowRedirects bool `json:"followRedirects,omitempty"`
264-
Active bool `json:"active,omitempty"`
265-
Public bool `json:"public,omitempty"`
263+
FollowRedirects bool `json:"followRedirects"`
264+
Active bool `json:"active"`
265+
Public bool `json:"public"`
266266
Description string `json:"description,omitempty"`
267267
Regions []string `json:"regions,omitempty"`
268268
Headers []apiHeader `json:"headers,omitempty"`
@@ -297,12 +297,16 @@ type httpMonitorAPIResponse struct {
297297
Monitor httpMonitorAPIObject `json:"monitor"`
298298
}
299299

300-
type getMonitorResponse struct {
300+
type getMonitorResponseInner struct {
301301
HTTP *httpMonitorAPIObject `json:"http,omitempty"`
302302
TCP *tcpMonitorAPIObject `json:"tcp,omitempty"`
303303
DNS *dnsMonitorAPIObject `json:"dns,omitempty"`
304304
}
305305

306+
type getMonitorResponse struct {
307+
Monitor getMonitorResponseInner `json:"monitor"`
308+
}
309+
306310
func (r *httpMonitorResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
307311
var data httpMonitorModel
308312
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
@@ -345,12 +349,12 @@ func (r *httpMonitorResource) Read(ctx context.Context, req resource.ReadRequest
345349
return
346350
}
347351

348-
if apiResp.HTTP == nil {
352+
if apiResp.Monitor.HTTP == nil {
349353
resp.State.RemoveResource(ctx)
350354
return
351355
}
352356

353-
resp.Diagnostics.Append(httpAPIToModel(ctx, *apiResp.HTTP, &data)...)
357+
resp.Diagnostics.Append(httpAPIToModel(ctx, *apiResp.Monitor.HTTP, &data)...)
354358
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
355359
}
356360

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package monitor
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
)
7+
8+
func TestHTTPMonitorAPIObject_BoolFalseNotOmitted(t *testing.T) {
9+
obj := httpMonitorAPIObject{
10+
Name: "test",
11+
URL: "https://example.com",
12+
Periodicity: "PERIODICITY_1M",
13+
FollowRedirects: false,
14+
Active: false,
15+
Public: false,
16+
}
17+
18+
data, err := json.Marshal(obj)
19+
if err != nil {
20+
t.Fatalf("unexpected marshal error: %v", err)
21+
}
22+
23+
var raw map[string]interface{}
24+
if err := json.Unmarshal(data, &raw); err != nil {
25+
t.Fatalf("unexpected unmarshal error: %v", err)
26+
}
27+
28+
for _, field := range []string{"followRedirects", "active", "public"} {
29+
val, ok := raw[field]
30+
if !ok {
31+
t.Errorf("field %q omitted from JSON when false — omitempty must be removed", field)
32+
continue
33+
}
34+
if val != false {
35+
t.Errorf("field %q = %v, want false", field, val)
36+
}
37+
}
38+
}
39+
40+
func TestGetMonitorResponse_NestedMonitorWrapper(t *testing.T) {
41+
// Simulates the actual API v2 response format
42+
apiJSON := `{"monitor":{"http":{"name":"Hub UI","url":"https://hub.traefik.io","followRedirects":false}}}`
43+
44+
var resp getMonitorResponse
45+
if err := json.Unmarshal([]byte(apiJSON), &resp); err != nil {
46+
t.Fatalf("unexpected unmarshal error: %v", err)
47+
}
48+
49+
if resp.Monitor.HTTP == nil {
50+
t.Fatal("expected monitor.http to be parsed, got nil")
51+
}
52+
if resp.Monitor.HTTP.Name != "Hub UI" {
53+
t.Errorf("name = %q, want %q", resp.Monitor.HTTP.Name, "Hub UI")
54+
}
55+
if resp.Monitor.HTTP.FollowRedirects != false {
56+
t.Error("followRedirects = true, want false")
57+
}
58+
}

internal/monitor/monitor_data_source.go

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -85,42 +85,42 @@ func (d *monitorDataSource) Read(ctx context.Context, req datasource.ReadRequest
8585
}
8686

8787
switch {
88-
case apiResp.HTTP != nil:
88+
case apiResp.Monitor.HTTP != nil:
8989
data.Type = types.StringValue("http")
90-
data.Name = types.StringValue(apiResp.HTTP.Name)
91-
data.URL = types.StringValue(apiResp.HTTP.URL)
92-
data.Periodicity = types.StringValue(MapPeriodicityFromAPI(apiResp.HTTP.Periodicity))
93-
data.Method = types.StringValue(MapMethodFromAPI(apiResp.HTTP.Method))
94-
data.Active = types.BoolValue(apiResp.HTTP.Active)
95-
data.Public = types.BoolValue(apiResp.HTTP.Public)
96-
data.Description = types.StringValue(apiResp.HTTP.Description)
97-
data.Timeout = types.Int64Value(apiResp.HTTP.Timeout.Int64())
98-
if apiResp.HTTP.Status != "" {
99-
data.Status = types.StringValue(MapMonitorStatusFromAPI(apiResp.HTTP.Status))
90+
data.Name = types.StringValue(apiResp.Monitor.HTTP.Name)
91+
data.URL = types.StringValue(apiResp.Monitor.HTTP.URL)
92+
data.Periodicity = types.StringValue(MapPeriodicityFromAPI(apiResp.Monitor.HTTP.Periodicity))
93+
data.Method = types.StringValue(MapMethodFromAPI(apiResp.Monitor.HTTP.Method))
94+
data.Active = types.BoolValue(apiResp.Monitor.HTTP.Active)
95+
data.Public = types.BoolValue(apiResp.Monitor.HTTP.Public)
96+
data.Description = types.StringValue(apiResp.Monitor.HTTP.Description)
97+
data.Timeout = types.Int64Value(apiResp.Monitor.HTTP.Timeout.Int64())
98+
if apiResp.Monitor.HTTP.Status != "" {
99+
data.Status = types.StringValue(MapMonitorStatusFromAPI(apiResp.Monitor.HTTP.Status))
100100
}
101-
case apiResp.TCP != nil:
101+
case apiResp.Monitor.TCP != nil:
102102
data.Type = types.StringValue("tcp")
103-
data.Name = types.StringValue(apiResp.TCP.Name)
104-
data.URI = types.StringValue(apiResp.TCP.URI)
105-
data.Periodicity = types.StringValue(MapPeriodicityFromAPI(apiResp.TCP.Periodicity))
106-
data.Active = types.BoolValue(apiResp.TCP.Active)
107-
data.Public = types.BoolValue(apiResp.TCP.Public)
108-
data.Description = types.StringValue(apiResp.TCP.Description)
109-
data.Timeout = types.Int64Value(apiResp.TCP.Timeout.Int64())
110-
if apiResp.TCP.Status != "" {
111-
data.Status = types.StringValue(MapMonitorStatusFromAPI(apiResp.TCP.Status))
103+
data.Name = types.StringValue(apiResp.Monitor.TCP.Name)
104+
data.URI = types.StringValue(apiResp.Monitor.TCP.URI)
105+
data.Periodicity = types.StringValue(MapPeriodicityFromAPI(apiResp.Monitor.TCP.Periodicity))
106+
data.Active = types.BoolValue(apiResp.Monitor.TCP.Active)
107+
data.Public = types.BoolValue(apiResp.Monitor.TCP.Public)
108+
data.Description = types.StringValue(apiResp.Monitor.TCP.Description)
109+
data.Timeout = types.Int64Value(apiResp.Monitor.TCP.Timeout.Int64())
110+
if apiResp.Monitor.TCP.Status != "" {
111+
data.Status = types.StringValue(MapMonitorStatusFromAPI(apiResp.Monitor.TCP.Status))
112112
}
113-
case apiResp.DNS != nil:
113+
case apiResp.Monitor.DNS != nil:
114114
data.Type = types.StringValue("dns")
115-
data.Name = types.StringValue(apiResp.DNS.Name)
116-
data.URI = types.StringValue(apiResp.DNS.URI)
117-
data.Periodicity = types.StringValue(MapPeriodicityFromAPI(apiResp.DNS.Periodicity))
118-
data.Active = types.BoolValue(apiResp.DNS.Active)
119-
data.Public = types.BoolValue(apiResp.DNS.Public)
120-
data.Description = types.StringValue(apiResp.DNS.Description)
121-
data.Timeout = types.Int64Value(apiResp.DNS.Timeout.Int64())
122-
if apiResp.DNS.Status != "" {
123-
data.Status = types.StringValue(MapMonitorStatusFromAPI(apiResp.DNS.Status))
115+
data.Name = types.StringValue(apiResp.Monitor.DNS.Name)
116+
data.URI = types.StringValue(apiResp.Monitor.DNS.URI)
117+
data.Periodicity = types.StringValue(MapPeriodicityFromAPI(apiResp.Monitor.DNS.Periodicity))
118+
data.Active = types.BoolValue(apiResp.Monitor.DNS.Active)
119+
data.Public = types.BoolValue(apiResp.Monitor.DNS.Public)
120+
data.Description = types.StringValue(apiResp.Monitor.DNS.Description)
121+
data.Timeout = types.Int64Value(apiResp.Monitor.DNS.Timeout.Int64())
122+
if apiResp.Monitor.DNS.Status != "" {
123+
data.Status = types.StringValue(MapMonitorStatusFromAPI(apiResp.Monitor.DNS.Status))
124124
}
125125
default:
126126
resp.Diagnostics.AddError("Monitor not found", "No monitor returned for ID: "+data.ID.ValueString())

internal/monitor/tcp_monitor_resource.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ type tcpMonitorAPIObject struct {
130130
Timeout jsonInt64 `json:"timeout,omitempty"`
131131
DegradedAt jsonInt64 `json:"degradedAt,omitempty"`
132132
Retry jsonInt64 `json:"retry,omitempty"`
133-
Active bool `json:"active,omitempty"`
134-
Public bool `json:"public,omitempty"`
133+
Active bool `json:"active"`
134+
Public bool `json:"public"`
135135
Description string `json:"description,omitempty"`
136136
Regions []string `json:"regions,omitempty"`
137137
Status string `json:"status,omitempty"`
@@ -194,12 +194,12 @@ func (r *tcpMonitorResource) Read(ctx context.Context, req resource.ReadRequest,
194194
return
195195
}
196196

197-
if apiResp.TCP == nil {
197+
if apiResp.Monitor.TCP == nil {
198198
resp.State.RemoveResource(ctx)
199199
return
200200
}
201201

202-
resp.Diagnostics.Append(tcpAPIToModel(ctx, *apiResp.TCP, &data)...)
202+
resp.Diagnostics.Append(tcpAPIToModel(ctx, *apiResp.Monitor.TCP, &data)...)
203203
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
204204
}
205205

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package monitor
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
)
7+
8+
func TestTCPMonitorAPIObject_BoolFalseNotOmitted(t *testing.T) {
9+
obj := tcpMonitorAPIObject{
10+
Name: "test",
11+
URI: "example.com:443",
12+
Periodicity: "PERIODICITY_1M",
13+
Active: false,
14+
Public: false,
15+
}
16+
17+
data, err := json.Marshal(obj)
18+
if err != nil {
19+
t.Fatalf("unexpected marshal error: %v", err)
20+
}
21+
22+
var raw map[string]interface{}
23+
if err := json.Unmarshal(data, &raw); err != nil {
24+
t.Fatalf("unexpected unmarshal error: %v", err)
25+
}
26+
27+
for _, field := range []string{"active", "public"} {
28+
val, ok := raw[field]
29+
if !ok {
30+
t.Errorf("field %q omitted from JSON when false — omitempty must be removed", field)
31+
continue
32+
}
33+
if val != false {
34+
t.Errorf("field %q = %v, want false", field, val)
35+
}
36+
}
37+
}
38+
39+
func TestGetMonitorResponse_NestedMonitorWrapper_TCP(t *testing.T) {
40+
apiJSON := `{"monitor":{"tcp":{"name":"Orbula","uri":"v3.license.containous.cloud:443"}}}`
41+
42+
var resp getMonitorResponse
43+
if err := json.Unmarshal([]byte(apiJSON), &resp); err != nil {
44+
t.Fatalf("unexpected unmarshal error: %v", err)
45+
}
46+
47+
if resp.Monitor.TCP == nil {
48+
t.Fatal("expected monitor.tcp to be parsed, got nil")
49+
}
50+
if resp.Monitor.TCP.Name != "Orbula" {
51+
t.Errorf("name = %q, want %q", resp.Monitor.TCP.Name, "Orbula")
52+
}
53+
}

0 commit comments

Comments
 (0)