From b65617ebbb41e70e58c7af3c261ad5c78b980143 Mon Sep 17 00:00:00 2001 From: favonia Date: Wed, 4 Jan 2023 02:27:08 -0600 Subject: [PATCH 1/2] fix!: allow clearing comments from the Go API Currently, empty DNS record comments will be understood as "unset" or "unspecified", making it impossible to clear a comment. This commit fixes that. This is a breaking change, but since comments were introduced only in 0.58.0, chances are almost no applications depend on them yet. --- dns.go | 155 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 92 insertions(+), 63 deletions(-) diff --git a/dns.go b/dns.go index 473f9ae7de6..c7097e23710 100644 --- a/dns.go +++ b/dns.go @@ -10,25 +10,54 @@ import ( "golang.org/x/net/idna" ) +// DNSRecordComment represents a DNS record comment and implements a custom [encoding.Marshaler] interface, +// where empty comments will be serialized as "null". +type DNSRecordComment string + +func (c *DNSRecordComment) UnmarshalJSON(b []byte) error { + var s *string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + if s == nil { + // null + *c = "" + } else { + // string (which should be non-empty) + *c = DNSRecordComment(*s) + } + + return nil +} + +func (c DNSRecordComment) MarshalJSON() ([]byte, error) { + if c == "" { + return json.Marshal(nil) + } + + return json.Marshal(string(c)) +} + // DNSRecord represents a DNS record in a zone. type DNSRecord struct { - CreatedOn time.Time `json:"created_on,omitempty"` - ModifiedOn time.Time `json:"modified_on,omitempty"` - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Content string `json:"content,omitempty"` - Meta interface{} `json:"meta,omitempty"` - Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC - ID string `json:"id,omitempty"` - ZoneID string `json:"zone_id,omitempty"` - ZoneName string `json:"zone_name,omitempty"` - Priority *uint16 `json:"priority,omitempty"` - TTL int `json:"ttl,omitempty"` - Proxied *bool `json:"proxied,omitempty"` - Proxiable bool `json:"proxiable,omitempty"` - Locked bool `json:"locked,omitempty"` - Comment string `json:"comment,omitempty"` - Tags []string `json:"tags,omitempty"` + CreatedOn time.Time `json:"created_on,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Content string `json:"content,omitempty"` + Meta interface{} `json:"meta,omitempty"` + Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC + ID string `json:"id,omitempty"` + ZoneID string `json:"zone_id,omitempty"` + ZoneName string `json:"zone_name,omitempty"` + Priority *uint16 `json:"priority,omitempty"` + TTL int `json:"ttl,omitempty"` + Proxied *bool `json:"proxied,omitempty"` + Proxiable bool `json:"proxiable,omitempty"` + Locked bool `json:"locked,omitempty"` + Comment DNSRecordComment `json:"comment,omitempty"` + Tags []string `json:"tags,omitempty"` } // DNSRecordResponse represents the response from the DNS endpoint. @@ -46,40 +75,40 @@ const ( ) type ListDNSRecordsParams struct { - CreatedOn time.Time `json:"created_on,omitempty" url:"created_on,omitempty"` - ModifiedOn time.Time `json:"modified_on,omitempty" url:"modified_on,omitempty"` - Type string `json:"type,omitempty" url:"type,omitempty"` - Name string `json:"name,omitempty" url:"name,omitempty"` - Content string `json:"content,omitempty" url:"content,omitempty"` - Proxied *bool `json:"proxied,omitempty" url:"proxied,omitempty"` - Comment string `json:"comment,omitempty" url:"comment,omitempty"` - Tags []string `json:"tags,omitempty"` - Order string `url:"order,omitempty"` - Direction ListDirection `url:"direction,omitempty"` - Match string `url:"match,omitempty"` - Priority *uint16 `url:"-"` + CreatedOn time.Time `json:"created_on,omitempty" url:"created_on,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty" url:"modified_on,omitempty"` + Type string `json:"type,omitempty" url:"type,omitempty"` + Name string `json:"name,omitempty" url:"name,omitempty"` + Content string `json:"content,omitempty" url:"content,omitempty"` + Proxied *bool `json:"proxied,omitempty" url:"proxied,omitempty"` + Comment *DNSRecordComment `json:"comment,omitempty" url:"comment,omitempty"` + Tags []string `json:"tags,omitempty"` + Order string `url:"order,omitempty"` + Direction ListDirection `url:"direction,omitempty"` + Match string `url:"match,omitempty"` + Priority *uint16 `url:"-"` ResultInfo } type UpdateDNSRecordParams struct { - CreatedOn time.Time `json:"created_on,omitempty" url:"created_on,omitempty"` - ModifiedOn time.Time `json:"modified_on,omitempty" url:"modified_on,omitempty"` - Type string `json:"type,omitempty" url:"type,omitempty"` - Name string `json:"name,omitempty" url:"name,omitempty"` - Content string `json:"content,omitempty" url:"content,omitempty"` - Meta interface{} `json:"meta,omitempty"` - Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC - ID string `json:"id,omitempty"` - ZoneID string `json:"zone_id,omitempty"` - ZoneName string `json:"zone_name,omitempty"` - Priority *uint16 `json:"priority,omitempty"` - TTL int `json:"ttl,omitempty"` - Proxied *bool `json:"proxied,omitempty" url:"proxied,omitempty"` - Proxiable bool `json:"proxiable,omitempty"` - Locked bool `json:"locked,omitempty"` - Comment string `json:"comment,omitempty" url:"comment,omitempty"` - Tags []string `json:"tags,omitempty"` + CreatedOn time.Time `json:"created_on,omitempty" url:"created_on,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty" url:"modified_on,omitempty"` + Type string `json:"type,omitempty" url:"type,omitempty"` + Name string `json:"name,omitempty" url:"name,omitempty"` + Content string `json:"content,omitempty" url:"content,omitempty"` + Meta interface{} `json:"meta,omitempty"` + Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC + ID string `json:"id,omitempty"` + ZoneID string `json:"zone_id,omitempty"` + ZoneName string `json:"zone_name,omitempty"` + Priority *uint16 `json:"priority,omitempty"` + TTL int `json:"ttl,omitempty"` + Proxied *bool `json:"proxied,omitempty" url:"proxied,omitempty"` + Proxiable bool `json:"proxiable,omitempty"` + Locked bool `json:"locked,omitempty"` + Comment *DNSRecordComment `json:"comment,omitempty" url:"comment,omitempty"` + Tags []string `json:"tags,omitempty"` } // DNSListResponse represents the response from the list DNS records endpoint. @@ -109,23 +138,23 @@ func toUTS46ASCII(name string) string { } type CreateDNSRecordParams struct { - CreatedOn time.Time `json:"created_on,omitempty" url:"created_on,omitempty"` - ModifiedOn time.Time `json:"modified_on,omitempty" url:"modified_on,omitempty"` - Type string `json:"type,omitempty" url:"type,omitempty"` - Name string `json:"name,omitempty" url:"name,omitempty"` - Content string `json:"content,omitempty" url:"content,omitempty"` - Meta interface{} `json:"meta,omitempty"` - Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC - ID string `json:"id,omitempty"` - ZoneID string `json:"zone_id,omitempty"` - ZoneName string `json:"zone_name,omitempty"` - Priority *uint16 `json:"priority,omitempty"` - TTL int `json:"ttl,omitempty"` - Proxied *bool `json:"proxied,omitempty" url:"proxied,omitempty"` - Proxiable bool `json:"proxiable,omitempty"` - Locked bool `json:"locked,omitempty"` - Comment string `json:"comment,omitempty" url:"comment,omitempty"` - Tags []string `json:"tags,omitempty"` + CreatedOn time.Time `json:"created_on,omitempty" url:"created_on,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty" url:"modified_on,omitempty"` + Type string `json:"type,omitempty" url:"type,omitempty"` + Name string `json:"name,omitempty" url:"name,omitempty"` + Content string `json:"content,omitempty" url:"content,omitempty"` + Meta interface{} `json:"meta,omitempty"` + Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC + ID string `json:"id,omitempty"` + ZoneID string `json:"zone_id,omitempty"` + ZoneName string `json:"zone_name,omitempty"` + Priority *uint16 `json:"priority,omitempty"` + TTL int `json:"ttl,omitempty"` + Proxied *bool `json:"proxied,omitempty" url:"proxied,omitempty"` + Proxiable bool `json:"proxiable,omitempty"` + Locked bool `json:"locked,omitempty"` + Comment DNSRecordComment `json:"comment,omitempty" url:"comment,omitempty"` + Tags []string `json:"tags,omitempty"` } // CreateDNSRecord creates a DNS record for the zone identifier. From 321392d9dc226e5c3039b717b1d77fd3684d54de Mon Sep 17 00:00:00 2001 From: favonia Date: Thu, 5 Jan 2023 03:23:17 -0600 Subject: [PATCH 2/2] fix!: use pointer type for all DNS record comments --- dns.go | 68 ++++++++++++++++++++++++++--------------------------- dns_test.go | 3 ++- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/dns.go b/dns.go index c7097e23710..5a4c92e8bc8 100644 --- a/dns.go +++ b/dns.go @@ -41,23 +41,23 @@ func (c DNSRecordComment) MarshalJSON() ([]byte, error) { // DNSRecord represents a DNS record in a zone. type DNSRecord struct { - CreatedOn time.Time `json:"created_on,omitempty"` - ModifiedOn time.Time `json:"modified_on,omitempty"` - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Content string `json:"content,omitempty"` - Meta interface{} `json:"meta,omitempty"` - Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC - ID string `json:"id,omitempty"` - ZoneID string `json:"zone_id,omitempty"` - ZoneName string `json:"zone_name,omitempty"` - Priority *uint16 `json:"priority,omitempty"` - TTL int `json:"ttl,omitempty"` - Proxied *bool `json:"proxied,omitempty"` - Proxiable bool `json:"proxiable,omitempty"` - Locked bool `json:"locked,omitempty"` - Comment DNSRecordComment `json:"comment,omitempty"` - Tags []string `json:"tags,omitempty"` + CreatedOn time.Time `json:"created_on,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Content string `json:"content,omitempty"` + Meta interface{} `json:"meta,omitempty"` + Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC + ID string `json:"id,omitempty"` + ZoneID string `json:"zone_id,omitempty"` + ZoneName string `json:"zone_name,omitempty"` + Priority *uint16 `json:"priority,omitempty"` + TTL int `json:"ttl,omitempty"` + Proxied *bool `json:"proxied,omitempty"` + Proxiable bool `json:"proxiable,omitempty"` + Locked bool `json:"locked,omitempty"` + Comment *DNSRecordComment `json:"comment,omitempty"` + Tags []string `json:"tags,omitempty"` } // DNSRecordResponse represents the response from the DNS endpoint. @@ -138,23 +138,23 @@ func toUTS46ASCII(name string) string { } type CreateDNSRecordParams struct { - CreatedOn time.Time `json:"created_on,omitempty" url:"created_on,omitempty"` - ModifiedOn time.Time `json:"modified_on,omitempty" url:"modified_on,omitempty"` - Type string `json:"type,omitempty" url:"type,omitempty"` - Name string `json:"name,omitempty" url:"name,omitempty"` - Content string `json:"content,omitempty" url:"content,omitempty"` - Meta interface{} `json:"meta,omitempty"` - Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC - ID string `json:"id,omitempty"` - ZoneID string `json:"zone_id,omitempty"` - ZoneName string `json:"zone_name,omitempty"` - Priority *uint16 `json:"priority,omitempty"` - TTL int `json:"ttl,omitempty"` - Proxied *bool `json:"proxied,omitempty" url:"proxied,omitempty"` - Proxiable bool `json:"proxiable,omitempty"` - Locked bool `json:"locked,omitempty"` - Comment DNSRecordComment `json:"comment,omitempty" url:"comment,omitempty"` - Tags []string `json:"tags,omitempty"` + CreatedOn time.Time `json:"created_on,omitempty" url:"created_on,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty" url:"modified_on,omitempty"` + Type string `json:"type,omitempty" url:"type,omitempty"` + Name string `json:"name,omitempty" url:"name,omitempty"` + Content string `json:"content,omitempty" url:"content,omitempty"` + Meta interface{} `json:"meta,omitempty"` + Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC + ID string `json:"id,omitempty"` + ZoneID string `json:"zone_id,omitempty"` + ZoneName string `json:"zone_name,omitempty"` + Priority *uint16 `json:"priority,omitempty"` + TTL int `json:"ttl,omitempty"` + Proxied *bool `json:"proxied,omitempty" url:"proxied,omitempty"` + Proxiable bool `json:"proxiable,omitempty"` + Locked bool `json:"locked,omitempty"` + Comment *DNSRecordComment `json:"comment,omitempty" url:"comment,omitempty"` + Tags []string `json:"tags,omitempty"` } // CreateDNSRecord creates a DNS record for the zone identifier. diff --git a/dns_test.go b/dns_test.go index 08a55e4eb85..8538b0e0e3f 100644 --- a/dns_test.go +++ b/dns_test.go @@ -382,6 +382,7 @@ func TestDNSRecord(t *testing.T) { mux.HandleFunc("/zones/"+testZoneID+"/dns_records/"+dnsRecordID, handler) proxied := false + comment := DNSRecordComment("This is a comment") createdOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z") modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z") want := DNSRecord{ @@ -401,7 +402,7 @@ func TestDNSRecord(t *testing.T) { "auto_added": true, "source": "primary", }, - Comment: "This is a comment", + Comment: &comment, Tags: []string{"tag1", "tag2"}, }