This is an automated email from the ASF dual-hosted git repository.

srijeet0406 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new b1791de5da Fix edge cases for DSR creation, update, and deletion 
(#7268)
b1791de5da is described below

commit b1791de5da52032ecf3fcc447e1b905b6d866d95
Author: ocket8888 <[email protected]>
AuthorDate: Wed Jan 11 10:05:36 2023 -0700

    Fix edge cases for DSR creation, update, and deletion (#7268)
    
    * Switch to non-pointer receivers on all upgrade/downgrade methods of DSes
    
    6 out of 8 upgrade/downgrade methods (2 of 4 downgrades and 4 of 4
    upgrades) already used a non-pointer receiver. Since the methods don't
    need to mutate the structure on which they are called, we can make life
    simpler by just using a regular receiver.
    
    * Fix Internal Server Error when creating a DSR in APIv3 without the 
required "deliveryservice" property
    
    * Fix creating and deleting DSRs in APIv3 returning APIv4.0 structures
    
    * Replace a dozen type-specific utility functions with two generic ones
    
    * Export copy utilities
    
    * Move copy utilities to the utils package
    
    * Add deprecation notices; everything should just use Ptr
    
    * foo
    
    * Add test coverage for the time pointer function
    
    * Switch tests to runnable examples
    
    * Add methods to coalesce DSes and DSRs from "nullable" v3.x structures to 
concrete structures
    
    * Add integration tests
    
    * Fix inaccurate godoc comment
    
    * typo
    
    * Remove bad test
    
    * Remove now-unnecessary ToConcrete methods
---
 lib/go-tc/copy.go                                  | 112 -----------
 lib/go-tc/copy_test.go                             | 166 ---------------
 lib/go-tc/deliveryservice_requests.go              |  45 +++--
 lib/go-tc/deliveryservices.go                      | 224 ++++++++++-----------
 lib/go-tc/users.go                                 | 132 ++++++------
 lib/go-util/ptr.go                                 | 109 ++++++++--
 lib/go-util/ptr_test.go                            |  42 +++-
 .../api/v3/deliveryservice_requests_test.go        |  45 ++++-
 .../deliveryservice/request/requests.go            |  10 +-
 .../deliveryservice/request/validate.go            |   3 +
 10 files changed, 394 insertions(+), 494 deletions(-)

diff --git a/lib/go-tc/copy.go b/lib/go-tc/copy.go
deleted file mode 100644
index 57152635d3..0000000000
--- a/lib/go-tc/copy.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package tc
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// copyStringIfNotNil makes a deep copy of s - unless it's nil, in which case 
it
-// just returns nil.
-func copyStringIfNotNil(s *string) *string {
-       if s == nil {
-               return nil
-       }
-       ret := new(string)
-       *ret = *s
-       return ret
-}
-
-// coalesceString coalesces a possibly nil pointer to a string to a concrete
-// string, using the provided default value in case `nil` is encountered.
-//
-// This can be thought of as roughly the inverse of
-// github.com/apache/trafficcontrol/lib/go-util.StrPtr.
-func coalesceString(s *string, def string) string {
-       if s == nil {
-               return def
-       }
-       return *s
-}
-
-// copyIntIfNotNil makes a deep copy of i - unless it's nil, in which case it
-// just returns nil.
-func copyIntIfNotNil(i *int) *int {
-       if i == nil {
-               return nil
-       }
-       ret := new(int)
-       *ret = *i
-       return ret
-}
-
-// coalesceInt coalesces a possibly nil pointer to an integer to a concrete
-// integer, using the provided default value in case `nil` is encountered.
-//
-// This can be thought of as roughly the inverse of
-// github.com/apache/trafficcontrol/lib/go-util.IntPtr.
-func coalesceInt(i *int, def int) int {
-       if i == nil {
-               return def
-       }
-       return *i
-}
-
-// copyBoolIfNotNil makes a deep copy of b - unless it's nil, in which case it
-// just returns nil.
-func copyBoolIfNotNil(b *bool) *bool {
-       if b == nil {
-               return nil
-       }
-       ret := new(bool)
-       *ret = *b
-       return ret
-}
-
-// coalesceBool coalesces a possibly nil pointer to a boolean to a concrete
-// boolean, using the provided default value in case `nil` is encountered.
-//
-// This can be thought of as roughly the inverse of
-// github.com/apache/trafficcontrol/lib/go-util.BoolPtr.
-func coalesceBool(b *bool, def bool) bool {
-       if b == nil {
-               return def
-       }
-       return *b
-}
-
-// copyFloatIfNotNil makes a deep copy of f - unless it's nil, in which case it
-// just returns nil.
-func copyFloatIfNotNil(f *float64) *float64 {
-       if f == nil {
-               return nil
-       }
-       ret := new(float64)
-       *ret = *f
-       return ret
-}
-
-// coalesceFloat coalesces a possibly nil pointer to a float64 to a concrete
-// float64, using the provided default value in case `nil` is encountered.
-//
-// This can be thought of as roughly the inverse of
-// github.com/apache/trafficcontrol/lib/go-util.FloatPtr.
-func coalesceFloat(f *float64, def float64) float64 {
-       if f == nil {
-               return def
-       }
-       return *f
-}
diff --git a/lib/go-tc/copy_test.go b/lib/go-tc/copy_test.go
deleted file mode 100644
index b4afb30b57..0000000000
--- a/lib/go-tc/copy_test.go
+++ /dev/null
@@ -1,166 +0,0 @@
-package tc
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import "testing"
-
-func TestCopyIntIfNotNil(t *testing.T) {
-       var i *int
-       copiedI := copyIntIfNotNil(i)
-       if copiedI != nil {
-               t.Errorf("Copying a nil int should've given nil, got: %d", 
*copiedI)
-       }
-       i = new(int)
-       *i = 9000
-       copiedI = copyIntIfNotNil(i)
-       if copiedI == nil {
-               t.Errorf("Copied pointer to %d was nil", *i)
-       } else {
-               if *copiedI != *i {
-                       t.Errorf("Incorrectly copied int pointer; expected: %d, 
got: %d", *i, *copiedI)
-               }
-               *i = 9001
-               if *copiedI == *i {
-                       t.Error("Expected copy to be 'deep' but modifying the 
original int changed the copy")
-               }
-       }
-}
-
-func TestCoalesceInt(t *testing.T) {
-       var i *int
-       copiedI := coalesceInt(i, 9000)
-       if copiedI != 9000 {
-               t.Errorf("Coalescing a nil int should've given the default 
value, got: %d", copiedI)
-       }
-       i = new(int)
-       *i = 9001
-       copiedI = coalesceInt(i, 9000)
-       if copiedI != 9001 {
-               t.Errorf("Coalescing a non-nil int should've given %d, got: 
%d", *i, copiedI)
-       }
-}
-
-func TestCopyFloatIfNotNil(t *testing.T) {
-       var f *float64
-       copiedF := copyFloatIfNotNil(f)
-       if copiedF != nil {
-               t.Errorf("Copying a nil float should've given nil, got: %f", 
*copiedF)
-       }
-       f = new(float64)
-       *f = 9000
-       copiedF = copyFloatIfNotNil(f)
-       if copiedF == nil {
-               t.Errorf("Copied pointer to %f was nil", *f)
-       } else {
-               if *copiedF != *f {
-                       t.Errorf("Incorrectly copied float64 pointer; expected: 
%f, got: %f", *f, *copiedF)
-               }
-               *f = 9001
-               if *copiedF == *f {
-                       t.Error("Expected copy to be 'deep' but modifying the 
original float changed the copy")
-               }
-       }
-}
-
-func TestCoalesceFloat(t *testing.T) {
-       var f *float64
-       copiedF := coalesceFloat(f, 9000)
-       if copiedF != 9000 {
-               t.Errorf("Coalescing a nil float should've given the default 
value, got: %f", copiedF)
-       }
-       f = new(float64)
-       *f = 9001
-       copiedF = coalesceFloat(f, 9000)
-       if copiedF != 9001 {
-               t.Errorf("Coalescing a non-nil float should've given %f, got: 
%f", *f, copiedF)
-       }
-}
-
-func TestCopyBoolIfNotNil(t *testing.T) {
-       var b *bool
-       copiedB := copyBoolIfNotNil(b)
-       if copiedB != nil {
-               t.Errorf("Copying a nil boolean should've given nil, got: %t", 
*copiedB)
-       }
-       b = new(bool)
-       *b = true
-       copiedB = copyBoolIfNotNil(b)
-       if copiedB == nil {
-               t.Errorf("Copied pointer to %t was nil", *b)
-       } else {
-               if *copiedB != *b {
-                       t.Errorf("Incorrectly copied bool pointer; expected: 
%t, got: %t", *b, *copiedB)
-               }
-               *b = false
-               if *copiedB == *b {
-                       t.Error("Expected copy to be 'deep' but modifying the 
original bool changed the copy")
-               }
-       }
-}
-
-func TestCoalesceBool(t *testing.T) {
-       var b *bool
-       copiedB := coalesceBool(b, true)
-       if copiedB != true {
-               t.Errorf("Coalescing a nil boolean should've given the default 
value, got: %t", copiedB)
-       }
-       b = new(bool)
-       *b = false
-       copiedB = coalesceBool(b, true)
-       if copiedB != false {
-               t.Errorf("Coalescing a non-nil bool should've given %t, got: 
%t", *b, copiedB)
-       }
-}
-
-func TestCopyStringIfNotNil(t *testing.T) {
-       var s *string
-       copiedS := copyStringIfNotNil(s)
-       if copiedS != nil {
-               t.Errorf("Copying a nil string should've given nil, got: %s", 
*copiedS)
-       }
-       s = new(string)
-       *s = "test string"
-       copiedS = copyStringIfNotNil(s)
-       if copiedS == nil {
-               t.Errorf("Copied pointer to '%s' was nil", *s)
-       } else {
-               if *copiedS != *s {
-                       t.Errorf("Incorrectly copied string pointer; expected: 
'%s', got: '%s'", *s, *copiedS)
-               }
-               *s = "a different test string"
-               if *copiedS == *s {
-                       t.Error("Expected copy to be 'deep' but modifying the 
original string changed the copy")
-               }
-       }
-}
-
-func TestCoalesceString(t *testing.T) {
-       var s *string
-       copiedS := coalesceString(s, "test")
-       if copiedS != "test" {
-               t.Errorf("Coalescing a nil string should've given the default 
value, got: %s", copiedS)
-       }
-       s = new(string)
-       *s = "quest"
-       copiedS = coalesceString(s, "test")
-       if copiedS != "quest" {
-               t.Errorf("Coalescing a non-nil string should've given %s, got: 
%s", *s, copiedS)
-       }
-}
diff --git a/lib/go-tc/deliveryservice_requests.go 
b/lib/go-tc/deliveryservice_requests.go
index 817cabc0c3..f335769256 100644
--- a/lib/go-tc/deliveryservice_requests.go
+++ b/lib/go-tc/deliveryservice_requests.go
@@ -401,6 +401,9 @@ func (o *OriginHeaders) UnmarshalJSON(data []byte) error {
 
 // DeliveryServiceRequest is used as part of the workflow to create,
 // modify, or delete a delivery service.
+//
+// Deprecated: Don't ever use this, even in legacy code if you can help it. It
+// shouldn't still exist already, but will nevertheless be removed soon.
 type DeliveryServiceRequest struct {
        AssigneeID      int             `json:"assigneeId,omitempty"`
        Assignee        string          `json:"assignee,omitempty"`
@@ -419,6 +422,8 @@ type DeliveryServiceRequest struct {
 
 // DeliveryServiceRequestNullable is used as part of the workflow to create,
 // modify, or delete a delivery service.
+//
+// Deprecated: This structure is only used in legacy API versions.
 type DeliveryServiceRequestNullable struct {
        AssigneeID      *int                        
`json:"assigneeId,omitempty" db:"assignee_id"`
        Assignee        *string                     `json:"assignee,omitempty"`
@@ -439,15 +444,15 @@ type DeliveryServiceRequestNullable struct {
 // Note that this function does a shallow copy of the requested and original 
Delivery Service structures.
 func (dsr DeliveryServiceRequestV41) Downgrade() DeliveryServiceRequestV40 {
        var dsrV40 DeliveryServiceRequestV40
-       dsrV40.Assignee = copyStringIfNotNil(dsr.Assignee)
-       dsrV40.AssigneeID = copyIntIfNotNil(dsr.AssigneeID)
+       dsrV40.Assignee = util.CopyIfNotNil(dsr.Assignee)
+       dsrV40.AssigneeID = util.CopyIfNotNil(dsr.AssigneeID)
        dsrV40.Author = dsr.Author
-       dsrV40.AuthorID = copyIntIfNotNil(dsr.AuthorID)
+       dsrV40.AuthorID = util.CopyIfNotNil(dsr.AuthorID)
        dsrV40.ChangeType = dsr.ChangeType
        dsrV40.CreatedAt = dsr.CreatedAt
-       dsrV40.ID = copyIntIfNotNil(dsr.ID)
+       dsrV40.ID = util.CopyIfNotNil(dsr.ID)
        dsrV40.LastEditedBy = dsr.LastEditedBy
-       dsrV40.LastEditedByID = copyIntIfNotNil(dsr.LastEditedByID)
+       dsrV40.LastEditedByID = util.CopyIfNotNil(dsr.LastEditedByID)
        dsrV40.LastUpdated = dsr.LastUpdated
        if dsr.Original != nil {
                dsrV40.Original = new(DeliveryServiceV40)
@@ -466,15 +471,15 @@ func (dsr DeliveryServiceRequestV41) Downgrade() 
DeliveryServiceRequestV40 {
 // Note that this function does a shallow copy of the requested and original 
Delivery Service structures.
 func (dsrV40 DeliveryServiceRequestV40) Upgrade() DeliveryServiceRequestV41 {
        var dsrV4 DeliveryServiceRequestV41
-       dsrV4.Assignee = copyStringIfNotNil(dsrV40.Assignee)
-       dsrV4.AssigneeID = copyIntIfNotNil(dsrV40.AssigneeID)
+       dsrV4.Assignee = util.CopyIfNotNil(dsrV40.Assignee)
+       dsrV4.AssigneeID = util.CopyIfNotNil(dsrV40.AssigneeID)
        dsrV4.Author = dsrV40.Author
-       dsrV4.AuthorID = copyIntIfNotNil(dsrV40.AuthorID)
+       dsrV4.AuthorID = util.CopyIfNotNil(dsrV40.AuthorID)
        dsrV4.ChangeType = dsrV40.ChangeType
        dsrV4.CreatedAt = dsrV40.CreatedAt
-       dsrV4.ID = copyIntIfNotNil(dsrV40.ID)
+       dsrV4.ID = util.CopyIfNotNil(dsrV40.ID)
        dsrV4.LastEditedBy = dsrV40.LastEditedBy
-       dsrV4.LastEditedByID = copyIntIfNotNil(dsrV40.LastEditedByID)
+       dsrV4.LastEditedByID = util.CopyIfNotNil(dsrV40.LastEditedByID)
        dsrV4.LastUpdated = dsrV40.LastUpdated
        if dsrV40.Original != nil {
                dsrV4.Original = new(DeliveryServiceV41)
@@ -1089,15 +1094,15 @@ type DeliveryServiceRequestV5 = 
DeliveryServiceRequestV50
 // also deep).
 func (dsr DeliveryServiceRequestV5) Downgrade() DeliveryServiceRequestV4 {
        downgraded := DeliveryServiceRequestV4{
-               Assignee:       copyStringIfNotNil(dsr.Assignee),
-               AssigneeID:     copyIntIfNotNil(dsr.AssigneeID),
+               Assignee:       util.CopyIfNotNil(dsr.Assignee),
+               AssigneeID:     util.CopyIfNotNil(dsr.AssigneeID),
                Author:         dsr.Author,
-               AuthorID:       copyIntIfNotNil(dsr.AuthorID),
+               AuthorID:       util.CopyIfNotNil(dsr.AuthorID),
                ChangeType:     dsr.ChangeType,
                CreatedAt:      dsr.CreatedAt,
-               ID:             copyIntIfNotNil(dsr.ID),
+               ID:             util.CopyIfNotNil(dsr.ID),
                LastEditedBy:   dsr.LastEditedBy,
-               LastEditedByID: copyIntIfNotNil(dsr.LastEditedByID),
+               LastEditedByID: util.CopyIfNotNil(dsr.LastEditedByID),
                LastUpdated:    dsr.LastUpdated,
                Status:         dsr.Status,
                XMLID:          dsr.XMLID,
@@ -1124,15 +1129,15 @@ func (dsr DeliveryServiceRequestV5) Downgrade() 
DeliveryServiceRequestV4 {
 // also deep).
 func (dsr DeliveryServiceRequestV4) Upgrade() DeliveryServiceRequestV5 {
        upgraded := DeliveryServiceRequestV5{
-               Assignee:       copyStringIfNotNil(dsr.Assignee),
-               AssigneeID:     copyIntIfNotNil(dsr.AssigneeID),
+               Assignee:       util.CopyIfNotNil(dsr.Assignee),
+               AssigneeID:     util.CopyIfNotNil(dsr.AssigneeID),
                Author:         dsr.Author,
-               AuthorID:       copyIntIfNotNil(dsr.AuthorID),
+               AuthorID:       util.CopyIfNotNil(dsr.AuthorID),
                ChangeType:     dsr.ChangeType,
                CreatedAt:      dsr.CreatedAt,
-               ID:             copyIntIfNotNil(dsr.ID),
+               ID:             util.CopyIfNotNil(dsr.ID),
                LastEditedBy:   dsr.LastEditedBy,
-               LastEditedByID: copyIntIfNotNil(dsr.LastEditedByID),
+               LastEditedByID: util.CopyIfNotNil(dsr.LastEditedByID),
                LastUpdated:    dsr.LastUpdated,
                Status:         dsr.Status,
                XMLID:          dsr.XMLID,
diff --git a/lib/go-tc/deliveryservices.go b/lib/go-tc/deliveryservices.go
index daf61e8f9b..2cdedb536a 100644
--- a/lib/go-tc/deliveryservices.go
+++ b/lib/go-tc/deliveryservices.go
@@ -121,7 +121,7 @@ type CreateDeliveryServiceNullableResponse struct {
        Alerts
 }
 
-// UpdateDeliveryServiceNullableResponse oughly models the structure of
+// UpdateDeliveryServiceNullableResponse roughly models the structure of
 // responses from Traffic Ops to PUT requests made to its
 // /deliveryservices/{{ID}} API endpoint.
 //
@@ -422,7 +422,7 @@ func tlsVersionsAlerts(versions []string, protocol int) 
Alerts {
 // to CDN operation, but can, in fact, work.
 func (ds DeliveryServiceV40) TLSVersionsAlerts() Alerts {
        vers := ds.TLSVersions
-       return tlsVersionsAlerts(vers, coalesceInt(ds.Protocol, 3))
+       return tlsVersionsAlerts(vers, util.Coalesce(ds.Protocol, 3))
 }
 
 // TLSVersionsAlerts generates warning-level alerts for the Delivery Service's
@@ -911,7 +911,7 @@ func (ds *DeliveryServiceV4) RemoveLD1AndLD2() 
DeliveryServiceV4 {
 }
 
 // DowngradeToV31 converts a 4.x DS to a 3.1 DS.
-func (ds *DeliveryServiceV4) DowngradeToV31() DeliveryServiceNullableV30 {
+func (ds DeliveryServiceV4) DowngradeToV31() DeliveryServiceNullableV30 {
        nullableFields := ds.DeliveryServiceNullableFieldsV11
        geoLimitCountries := ([]string)(ds.GeoLimitCountries)
        geo := strings.Join(geoLimitCountries, ",")
@@ -939,7 +939,7 @@ func (ds *DeliveryServiceV4) DowngradeToV31() 
DeliveryServiceNullableV30 {
 }
 
 // UpgradeToV4 converts the 3.x DS to a 4.x DS.
-func (ds *DeliveryServiceNullableV30) UpgradeToV4() DeliveryServiceV4 {
+func (ds DeliveryServiceNullableV30) UpgradeToV4() DeliveryServiceV4 {
        var geo GeoLimitCountriesType
        if ds.GeoLimitCountries != nil {
                str := *ds.GeoLimitCountries
@@ -1509,7 +1509,7 @@ type DeliveryServiceV5 = DeliveryServiceV50
 // it ONLY creates warnings based on conditions that are possibly detrimental
 // to CDN operation, but can, in fact, work.
 func (ds DeliveryServiceV5) TLSVersionsAlerts() Alerts {
-       return tlsVersionsAlerts(ds.TLSVersions, coalesceInt(ds.Protocol, 3))
+       return tlsVersionsAlerts(ds.TLSVersions, util.Coalesce(ds.Protocol, 3))
 }
 
 // Value implements the database/sql/driver.Valuer interface by marshaling the
@@ -1532,80 +1532,80 @@ func (ds DeliveryServiceV5) Downgrade() 
DeliveryServiceV4 {
        downgraded := DeliveryServiceV4{
                DeliveryServiceV40: DeliveryServiceV40{
                        DeliveryServiceFieldsV31: DeliveryServiceFieldsV31{
-                               MaxRequestHeaderBytes: 
copyIntIfNotNil(ds.MaxRequestHeaderBytes),
+                               MaxRequestHeaderBytes: 
util.CopyIfNotNil(ds.MaxRequestHeaderBytes),
                        },
                        DeliveryServiceFieldsV30: DeliveryServiceFieldsV30{
-                               FirstHeaderRewrite: 
copyStringIfNotNil(ds.FirstHeaderRewrite),
-                               InnerHeaderRewrite: 
copyStringIfNotNil(ds.InnerHeaderRewrite),
-                               LastHeaderRewrite:  
copyStringIfNotNil(ds.LastHeaderRewrite),
-                               ServiceCategory:    
copyStringIfNotNil(ds.ServiceCategory),
-                               Topology:           
copyStringIfNotNil(ds.Topology),
+                               FirstHeaderRewrite: 
util.CopyIfNotNil(ds.FirstHeaderRewrite),
+                               InnerHeaderRewrite: 
util.CopyIfNotNil(ds.InnerHeaderRewrite),
+                               LastHeaderRewrite:  
util.CopyIfNotNil(ds.LastHeaderRewrite),
+                               ServiceCategory:    
util.CopyIfNotNil(ds.ServiceCategory),
+                               Topology:           
util.CopyIfNotNil(ds.Topology),
                        },
                        DeliveryServiceFieldsV15: DeliveryServiceFieldsV15{
                                EcsEnabled:          ds.EcsEnabled,
-                               RangeSliceBlockSize: 
copyIntIfNotNil(ds.RangeSliceBlockSize),
+                               RangeSliceBlockSize: 
util.CopyIfNotNil(ds.RangeSliceBlockSize),
                        },
                        DeliveryServiceFieldsV14: DeliveryServiceFieldsV14{
                                ConsistentHashQueryParams: make([]string, 
len(ds.ConsistentHashQueryParams)),
-                               ConsistentHashRegex:       
copyStringIfNotNil(ds.ConsistentHashRegex),
-                               MaxOriginConnections:      
copyIntIfNotNil(ds.MaxOriginConnections),
+                               ConsistentHashRegex:       
util.CopyIfNotNil(ds.ConsistentHashRegex),
+                               MaxOriginConnections:      
util.CopyIfNotNil(ds.MaxOriginConnections),
                        },
                        DeliveryServiceFieldsV13: DeliveryServiceFieldsV13{
                                DeepCachingType:   new(DeepCachingType),
-                               FQPacingRate:      
copyIntIfNotNil(ds.FQPacingRate),
-                               SigningAlgorithm:  
copyStringIfNotNil(ds.SigningAlgorithm),
-                               Tenant:            
copyStringIfNotNil(ds.Tenant),
-                               TRResponseHeaders: 
copyStringIfNotNil(ds.TRResponseHeaders),
-                               TRRequestHeaders:  
copyStringIfNotNil(ds.TRRequestHeaders),
+                               FQPacingRate:      
util.CopyIfNotNil(ds.FQPacingRate),
+                               SigningAlgorithm:  
util.CopyIfNotNil(ds.SigningAlgorithm),
+                               Tenant:            util.CopyIfNotNil(ds.Tenant),
+                               TRResponseHeaders: 
util.CopyIfNotNil(ds.TRResponseHeaders),
+                               TRRequestHeaders:  
util.CopyIfNotNil(ds.TRRequestHeaders),
                        },
                        DeliveryServiceNullableFieldsV11: 
DeliveryServiceNullableFieldsV11{
                                Active:                   new(bool),
                                AnonymousBlockingEnabled: 
util.BoolPtr(ds.AnonymousBlockingEnabled),
-                               CCRDNSTTL:                
copyIntIfNotNil(ds.CCRDNSTTL),
+                               CCRDNSTTL:                
util.CopyIfNotNil(ds.CCRDNSTTL),
                                CDNID:                    util.IntPtr(ds.CDNID),
-                               CDNName:                  
copyStringIfNotNil(ds.CDNName),
-                               CheckPath:                
copyStringIfNotNil(ds.CheckPath),
+                               CDNName:                  
util.CopyIfNotNil(ds.CDNName),
+                               CheckPath:                
util.CopyIfNotNil(ds.CheckPath),
                                DisplayName:              
util.StrPtr(ds.DisplayName),
-                               DNSBypassCNAME:           
copyStringIfNotNil(ds.DNSBypassCNAME),
-                               DNSBypassIP:              
copyStringIfNotNil(ds.DNSBypassIP),
-                               DNSBypassIP6:             
copyStringIfNotNil(ds.DNSBypassIP6),
-                               DNSBypassTTL:             
copyIntIfNotNil(ds.DNSBypassTTL),
+                               DNSBypassCNAME:           
util.CopyIfNotNil(ds.DNSBypassCNAME),
+                               DNSBypassIP:              
util.CopyIfNotNil(ds.DNSBypassIP),
+                               DNSBypassIP6:             
util.CopyIfNotNil(ds.DNSBypassIP6),
+                               DNSBypassTTL:             
util.CopyIfNotNil(ds.DNSBypassTTL),
                                DSCP:                     util.IntPtr(ds.DSCP),
-                               EdgeHeaderRewrite:        
copyStringIfNotNil(ds.EdgeHeaderRewrite),
+                               EdgeHeaderRewrite:        
util.CopyIfNotNil(ds.EdgeHeaderRewrite),
                                GeoLimit:                 
util.IntPtr(ds.GeoLimit),
-                               GeoLimitRedirectURL:      
copyStringIfNotNil(ds.GeoLimitRedirectURL),
+                               GeoLimitRedirectURL:      
util.CopyIfNotNil(ds.GeoLimitRedirectURL),
                                GeoProvider:              
util.IntPtr(ds.GeoProvider),
-                               GlobalMaxMBPS:            
copyIntIfNotNil(ds.GlobalMaxMBPS),
-                               GlobalMaxTPS:             
copyIntIfNotNil(ds.GlobalMaxTPS),
-                               HTTPBypassFQDN:           
copyStringIfNotNil(ds.HTTPBypassFQDN),
-                               ID:                       
copyIntIfNotNil(ds.ID),
-                               InfoURL:                  
copyStringIfNotNil(ds.InfoURL),
-                               InitialDispersion:        
copyIntIfNotNil(ds.InitialDispersion),
-                               IPV6RoutingEnabled:       
copyBoolIfNotNil(ds.IPV6RoutingEnabled),
+                               GlobalMaxMBPS:            
util.CopyIfNotNil(ds.GlobalMaxMBPS),
+                               GlobalMaxTPS:             
util.CopyIfNotNil(ds.GlobalMaxTPS),
+                               HTTPBypassFQDN:           
util.CopyIfNotNil(ds.HTTPBypassFQDN),
+                               ID:                       
util.CopyIfNotNil(ds.ID),
+                               InfoURL:                  
util.CopyIfNotNil(ds.InfoURL),
+                               InitialDispersion:        
util.CopyIfNotNil(ds.InitialDispersion),
+                               IPV6RoutingEnabled:       
util.CopyIfNotNil(ds.IPV6RoutingEnabled),
                                LastUpdated:              
TimeNoModFromTime(ds.LastUpdated),
                                LogsEnabled:              
util.BoolPtr(ds.LogsEnabled),
                                LongDesc:                 
util.StrPtr(ds.LongDesc),
-                               MaxDNSAnswers:            
copyIntIfNotNil(ds.MaxDNSAnswers),
-                               MidHeaderRewrite:         
copyStringIfNotNil(ds.MidHeaderRewrite),
-                               MissLat:                  
copyFloatIfNotNil(ds.MissLat),
-                               MissLong:                 
copyFloatIfNotNil(ds.MissLong),
+                               MaxDNSAnswers:            
util.CopyIfNotNil(ds.MaxDNSAnswers),
+                               MidHeaderRewrite:         
util.CopyIfNotNil(ds.MidHeaderRewrite),
+                               MissLat:                  
util.CopyIfNotNil(ds.MissLat),
+                               MissLong:                 
util.CopyIfNotNil(ds.MissLong),
                                MultiSiteOrigin:          
util.BoolPtr(ds.MultiSiteOrigin),
-                               OriginShield:             
copyStringIfNotNil(ds.OriginShield),
-                               OrgServerFQDN:            
copyStringIfNotNil(ds.OrgServerFQDN),
-                               ProfileDesc:              
copyStringIfNotNil(ds.ProfileDesc),
-                               ProfileID:                
copyIntIfNotNil(ds.ProfileID),
-                               ProfileName:              
copyStringIfNotNil(ds.ProfileName),
-                               Protocol:                 
copyIntIfNotNil(ds.Protocol),
-                               QStringIgnore:            
copyIntIfNotNil(ds.QStringIgnore),
-                               RangeRequestHandling:     
copyIntIfNotNil(ds.RangeRequestHandling),
-                               RegexRemap:               
copyStringIfNotNil(ds.RegexRemap),
+                               OriginShield:             
util.CopyIfNotNil(ds.OriginShield),
+                               OrgServerFQDN:            
util.CopyIfNotNil(ds.OrgServerFQDN),
+                               ProfileDesc:              
util.CopyIfNotNil(ds.ProfileDesc),
+                               ProfileID:                
util.CopyIfNotNil(ds.ProfileID),
+                               ProfileName:              
util.CopyIfNotNil(ds.ProfileName),
+                               Protocol:                 
util.CopyIfNotNil(ds.Protocol),
+                               QStringIgnore:            
util.CopyIfNotNil(ds.QStringIgnore),
+                               RangeRequestHandling:     
util.CopyIfNotNil(ds.RangeRequestHandling),
+                               RegexRemap:               
util.CopyIfNotNil(ds.RegexRemap),
                                RegionalGeoBlocking:      
util.BoolPtr(ds.RegionalGeoBlocking),
-                               RemapText:                
copyStringIfNotNil(ds.RemapText),
+                               RemapText:                
util.CopyIfNotNil(ds.RemapText),
                                RoutingName:              
util.StrPtr(ds.RoutingName),
                                Signed:                   ds.Signed,
-                               SSLKeyVersion:            
copyIntIfNotNil(ds.SSLKeyVersion),
+                               SSLKeyVersion:            
util.CopyIfNotNil(ds.SSLKeyVersion),
                                TenantID:                 
util.IntPtr(ds.TenantID),
-                               Type:                     
(*DSType)(copyStringIfNotNil(ds.Type)),
+                               Type:                     
(*DSType)(util.CopyIfNotNil(ds.Type)),
                                TypeID:                   
util.IntPtr(ds.TypeID),
                                XMLID:                    util.StrPtr(ds.XMLID),
                        },
@@ -1642,76 +1642,76 @@ func (ds DeliveryServiceV5) Downgrade() 
DeliveryServiceV4 {
 // the latest minor version.
 func (ds DeliveryServiceV4) Upgrade() DeliveryServiceV5 {
        upgraded := DeliveryServiceV5{
-               AnonymousBlockingEnabled:  
coalesceBool(ds.AnonymousBlockingEnabled, false),
-               CCRDNSTTL:                 copyIntIfNotNil(ds.CCRDNSTTL),
-               CDNID:                     coalesceInt(ds.CDNID, -1),
-               CDNName:                   copyStringIfNotNil(ds.CDNName),
-               CheckPath:                 copyStringIfNotNil(ds.CheckPath),
+               AnonymousBlockingEnabled:  
util.CoalesceToDefault(ds.AnonymousBlockingEnabled),
+               CCRDNSTTL:                 util.CopyIfNotNil(ds.CCRDNSTTL),
+               CDNID:                     util.Coalesce(ds.CDNID, -1),
+               CDNName:                   util.CopyIfNotNil(ds.CDNName),
+               CheckPath:                 util.CopyIfNotNil(ds.CheckPath),
                ConsistentHashQueryParams: make([]string, 
len(ds.ConsistentHashQueryParams)),
-               ConsistentHashRegex:       
copyStringIfNotNil(ds.ConsistentHashRegex),
-               DeepCachingType:           
DeepCachingType(coalesceString((*string)(ds.DeepCachingType), "")),
-               DisplayName:               coalesceString(ds.DisplayName, ""),
-               DNSBypassCNAME:            
copyStringIfNotNil(ds.DNSBypassCNAME),
-               DNSBypassIP:               copyStringIfNotNil(ds.DNSBypassIP),
-               DNSBypassIP6:              copyStringIfNotNil(ds.DNSBypassIP6),
-               DNSBypassTTL:              copyIntIfNotNil(ds.DNSBypassTTL),
-               DSCP:                      coalesceInt(ds.DSCP, -1),
+               ConsistentHashRegex:       
util.CopyIfNotNil(ds.ConsistentHashRegex),
+               DeepCachingType:           
util.CoalesceToDefault(ds.DeepCachingType),
+               DisplayName:               
util.CoalesceToDefault(ds.DisplayName),
+               DNSBypassCNAME:            util.CopyIfNotNil(ds.DNSBypassCNAME),
+               DNSBypassIP:               util.CopyIfNotNil(ds.DNSBypassIP),
+               DNSBypassIP6:              util.CopyIfNotNil(ds.DNSBypassIP6),
+               DNSBypassTTL:              util.CopyIfNotNil(ds.DNSBypassTTL),
+               DSCP:                      util.Coalesce(ds.DSCP, -1),
                EcsEnabled:                ds.EcsEnabled,
-               EdgeHeaderRewrite:         
copyStringIfNotNil(ds.EdgeHeaderRewrite),
+               EdgeHeaderRewrite:         
util.CopyIfNotNil(ds.EdgeHeaderRewrite),
                ExampleURLs:               nil,
-               FirstHeaderRewrite:        
copyStringIfNotNil(ds.FirstHeaderRewrite),
-               FQPacingRate:              copyIntIfNotNil(ds.FQPacingRate),
-               GeoLimit:                  coalesceInt(ds.GeoLimit, -1),
+               FirstHeaderRewrite:        
util.CopyIfNotNil(ds.FirstHeaderRewrite),
+               FQPacingRate:              util.CopyIfNotNil(ds.FQPacingRate),
+               GeoLimit:                  util.Coalesce(ds.GeoLimit, -1),
                GeoLimitCountries:         make([]string, 
len(ds.GeoLimitCountries)),
-               GeoLimitRedirectURL:       
copyStringIfNotNil(ds.GeoLimitRedirectURL),
-               GeoProvider:               coalesceInt(ds.GeoProvider, -1),
-               GlobalMaxMBPS:             copyIntIfNotNil(ds.GlobalMaxMBPS),
-               GlobalMaxTPS:              copyIntIfNotNil(ds.GlobalMaxTPS),
-               HTTPBypassFQDN:            
copyStringIfNotNil(ds.HTTPBypassFQDN),
-               ID:                        copyIntIfNotNil(ds.ID),
-               InfoURL:                   copyStringIfNotNil(ds.InfoURL),
-               InitialDispersion:         
copyIntIfNotNil(ds.InitialDispersion),
-               InnerHeaderRewrite:        
copyStringIfNotNil(ds.InnerHeaderRewrite),
-               IPV6RoutingEnabled:        
copyBoolIfNotNil(ds.IPV6RoutingEnabled),
-               LastHeaderRewrite:         
copyStringIfNotNil(ds.LastHeaderRewrite),
-               LogsEnabled:               coalesceBool(ds.LogsEnabled, false),
-               LongDesc:                  coalesceString(ds.LongDesc, ""),
+               GeoLimitRedirectURL:       
util.CopyIfNotNil(ds.GeoLimitRedirectURL),
+               GeoProvider:               util.Coalesce(ds.GeoProvider, -1),
+               GlobalMaxMBPS:             util.CopyIfNotNil(ds.GlobalMaxMBPS),
+               GlobalMaxTPS:              util.CopyIfNotNil(ds.GlobalMaxTPS),
+               HTTPBypassFQDN:            util.CopyIfNotNil(ds.HTTPBypassFQDN),
+               ID:                        util.CopyIfNotNil(ds.ID),
+               InfoURL:                   util.CopyIfNotNil(ds.InfoURL),
+               InitialDispersion:         
util.CopyIfNotNil(ds.InitialDispersion),
+               InnerHeaderRewrite:        
util.CopyIfNotNil(ds.InnerHeaderRewrite),
+               IPV6RoutingEnabled:        
util.CopyIfNotNil(ds.IPV6RoutingEnabled),
+               LastHeaderRewrite:         
util.CopyIfNotNil(ds.LastHeaderRewrite),
+               LogsEnabled:               util.Coalesce(ds.LogsEnabled, false),
+               LongDesc:                  util.CoalesceToDefault(ds.LongDesc),
                MatchList:                 nil,
-               MaxDNSAnswers:             copyIntIfNotNil(ds.MaxDNSAnswers),
-               MaxOriginConnections:      
copyIntIfNotNil(ds.MaxOriginConnections),
-               MaxRequestHeaderBytes:     
copyIntIfNotNil(ds.MaxRequestHeaderBytes),
-               MidHeaderRewrite:          
copyStringIfNotNil(ds.MidHeaderRewrite),
-               MissLat:                   copyFloatIfNotNil(ds.MissLat),
-               MissLong:                  copyFloatIfNotNil(ds.MissLong),
-               MultiSiteOrigin:           coalesceBool(ds.MultiSiteOrigin, 
false),
-               OriginShield:              copyStringIfNotNil(ds.OriginShield),
-               OrgServerFQDN:             copyStringIfNotNil(ds.OrgServerFQDN),
-               ProfileDesc:               copyStringIfNotNil(ds.ProfileDesc),
-               ProfileID:                 copyIntIfNotNil(ds.ProfileID),
-               ProfileName:               copyStringIfNotNil(ds.ProfileName),
-               Protocol:                  copyIntIfNotNil(ds.Protocol),
-               QStringIgnore:             copyIntIfNotNil(ds.QStringIgnore),
-               RangeRequestHandling:      
copyIntIfNotNil(ds.RangeRequestHandling),
-               RangeSliceBlockSize:       
copyIntIfNotNil(ds.RangeSliceBlockSize),
-               RegexRemap:                copyStringIfNotNil(ds.RegexRemap),
+               MaxDNSAnswers:             util.CopyIfNotNil(ds.MaxDNSAnswers),
+               MaxOriginConnections:      
util.CopyIfNotNil(ds.MaxOriginConnections),
+               MaxRequestHeaderBytes:     
util.CopyIfNotNil(ds.MaxRequestHeaderBytes),
+               MidHeaderRewrite:          
util.CopyIfNotNil(ds.MidHeaderRewrite),
+               MissLat:                   util.CopyIfNotNil(ds.MissLat),
+               MissLong:                  util.CopyIfNotNil(ds.MissLong),
+               MultiSiteOrigin:           
util.CoalesceToDefault(ds.MultiSiteOrigin),
+               OriginShield:              util.CopyIfNotNil(ds.OriginShield),
+               OrgServerFQDN:             util.CopyIfNotNil(ds.OrgServerFQDN),
+               ProfileDesc:               util.CopyIfNotNil(ds.ProfileDesc),
+               ProfileID:                 util.CopyIfNotNil(ds.ProfileID),
+               ProfileName:               util.CopyIfNotNil(ds.ProfileName),
+               Protocol:                  util.CopyIfNotNil(ds.Protocol),
+               QStringIgnore:             util.CopyIfNotNil(ds.QStringIgnore),
+               RangeRequestHandling:      
util.CopyIfNotNil(ds.RangeRequestHandling),
+               RangeSliceBlockSize:       
util.CopyIfNotNil(ds.RangeSliceBlockSize),
+               RegexRemap:                util.CopyIfNotNil(ds.RegexRemap),
                Regional:                  ds.Regional,
-               RegionalGeoBlocking:       coalesceBool(ds.RegionalGeoBlocking, 
false),
-               RemapText:                 copyStringIfNotNil(ds.RemapText),
+               RegionalGeoBlocking:       
util.CoalesceToDefault(ds.RegionalGeoBlocking),
+               RemapText:                 util.CopyIfNotNil(ds.RemapText),
                RequiredCapabilities:      make([]string, 
len(ds.RequiredCapabilities)),
-               RoutingName:               coalesceString(ds.RoutingName, ""),
-               ServiceCategory:           
copyStringIfNotNil(ds.ServiceCategory),
+               RoutingName:               
util.CoalesceToDefault(ds.RoutingName),
+               ServiceCategory:           
util.CopyIfNotNil(ds.ServiceCategory),
                Signed:                    ds.Signed,
-               SigningAlgorithm:          
copyStringIfNotNil(ds.SigningAlgorithm),
-               SSLKeyVersion:             copyIntIfNotNil(ds.SSLKeyVersion),
-               Tenant:                    copyStringIfNotNil(ds.Tenant),
-               TenantID:                  coalesceInt(ds.TenantID, -1),
+               SigningAlgorithm:          
util.CopyIfNotNil(ds.SigningAlgorithm),
+               SSLKeyVersion:             util.CopyIfNotNil(ds.SSLKeyVersion),
+               Tenant:                    util.CopyIfNotNil(ds.Tenant),
+               TenantID:                  util.Coalesce(ds.TenantID, -1),
                TLSVersions:               make([]string, len(ds.TLSVersions)),
-               Topology:                  copyStringIfNotNil(ds.Topology),
-               TRResponseHeaders:         
copyStringIfNotNil(ds.TRResponseHeaders),
-               TRRequestHeaders:          
copyStringIfNotNil(ds.TRRequestHeaders),
-               Type:                      
copyStringIfNotNil((*string)(ds.Type)),
-               TypeID:                    coalesceInt(ds.TypeID, -1),
-               XMLID:                     coalesceString(ds.XMLID, ""),
+               Topology:                  util.CopyIfNotNil(ds.Topology),
+               TRResponseHeaders:         
util.CopyIfNotNil(ds.TRResponseHeaders),
+               TRRequestHeaders:          
util.CopyIfNotNil(ds.TRRequestHeaders),
+               Type:                      
(*string)(util.CopyIfNotNil(ds.Type)),
+               TypeID:                    util.Coalesce(ds.TypeID, -1),
+               XMLID:                     util.CoalesceToDefault(ds.XMLID),
        }
 
        if ds.Active == nil || !*ds.Active {
diff --git a/lib/go-tc/users.go b/lib/go-tc/users.go
index 18e997f090..e06179fa23 100644
--- a/lib/go-tc/users.go
+++ b/lib/go-tc/users.go
@@ -36,23 +36,23 @@ import (
 // Upgrade converts a User to a UserV4 (as seen in API versions 4.x).
 func (u User) Upgrade() UserV4 {
        var ret UserV4
-       ret.AddressLine1 = copyStringIfNotNil(u.AddressLine1)
-       ret.AddressLine2 = copyStringIfNotNil(u.AddressLine2)
-       ret.City = copyStringIfNotNil(u.City)
-       ret.Company = copyStringIfNotNil(u.Company)
-       ret.Country = copyStringIfNotNil(u.Country)
-       ret.Email = copyStringIfNotNil(u.Email)
-       ret.GID = copyIntIfNotNil(u.GID)
-       ret.ID = copyIntIfNotNil(u.ID)
-       ret.LocalPassword = copyStringIfNotNil(u.LocalPassword)
-       ret.PhoneNumber = copyStringIfNotNil(u.PhoneNumber)
-       ret.PostalCode = copyStringIfNotNil(u.PostalCode)
-       ret.PublicSSHKey = copyStringIfNotNil(u.PublicSSHKey)
-       ret.StateOrProvince = copyStringIfNotNil(u.StateOrProvince)
-       ret.Tenant = copyStringIfNotNil(u.Tenant)
-       ret.Token = copyStringIfNotNil(u.Token)
-       ret.UID = copyIntIfNotNil(u.UID)
-       ret.FullName = copyStringIfNotNil(u.FullName)
+       ret.AddressLine1 = util.CopyIfNotNil(u.AddressLine1)
+       ret.AddressLine2 = util.CopyIfNotNil(u.AddressLine2)
+       ret.City = util.CopyIfNotNil(u.City)
+       ret.Company = util.CopyIfNotNil(u.Company)
+       ret.Country = util.CopyIfNotNil(u.Country)
+       ret.Email = util.CopyIfNotNil(u.Email)
+       ret.GID = util.CopyIfNotNil(u.GID)
+       ret.ID = util.CopyIfNotNil(u.ID)
+       ret.LocalPassword = util.CopyIfNotNil(u.LocalPassword)
+       ret.PhoneNumber = util.CopyIfNotNil(u.PhoneNumber)
+       ret.PostalCode = util.CopyIfNotNil(u.PostalCode)
+       ret.PublicSSHKey = util.CopyIfNotNil(u.PublicSSHKey)
+       ret.StateOrProvince = util.CopyIfNotNil(u.StateOrProvince)
+       ret.Tenant = util.CopyIfNotNil(u.Tenant)
+       ret.Token = util.CopyIfNotNil(u.Token)
+       ret.UID = util.CopyIfNotNil(u.UID)
+       ret.FullName = util.CopyIfNotNil(u.FullName)
        if u.LastUpdated != nil {
                ret.LastUpdated = u.LastUpdated.Time
        }
@@ -91,26 +91,26 @@ func (u UserV4) Downgrade() User {
        ret.Username = new(string)
        *ret.Username = u.Username
 
-       ret.AddressLine1 = copyStringIfNotNil(u.AddressLine1)
-       ret.AddressLine2 = copyStringIfNotNil(u.AddressLine2)
-       ret.City = copyStringIfNotNil(u.City)
-       ret.Company = copyStringIfNotNil(u.Company)
-       ret.ConfirmLocalPassword = copyStringIfNotNil(u.LocalPassword)
-       ret.Country = copyStringIfNotNil(u.Country)
-       ret.Email = copyStringIfNotNil(u.Email)
-       ret.GID = copyIntIfNotNil(u.GID)
-       ret.ID = copyIntIfNotNil(u.ID)
-       ret.LocalPassword = copyStringIfNotNil(u.LocalPassword)
-       ret.PhoneNumber = copyStringIfNotNil(u.PhoneNumber)
-       ret.PostalCode = copyStringIfNotNil(u.PostalCode)
-       ret.PublicSSHKey = copyStringIfNotNil(u.PublicSSHKey)
+       ret.AddressLine1 = util.CopyIfNotNil(u.AddressLine1)
+       ret.AddressLine2 = util.CopyIfNotNil(u.AddressLine2)
+       ret.City = util.CopyIfNotNil(u.City)
+       ret.Company = util.CopyIfNotNil(u.Company)
+       ret.ConfirmLocalPassword = util.CopyIfNotNil(u.LocalPassword)
+       ret.Country = util.CopyIfNotNil(u.Country)
+       ret.Email = util.CopyIfNotNil(u.Email)
+       ret.GID = util.CopyIfNotNil(u.GID)
+       ret.ID = util.CopyIfNotNil(u.ID)
+       ret.LocalPassword = util.CopyIfNotNil(u.LocalPassword)
+       ret.PhoneNumber = util.CopyIfNotNil(u.PhoneNumber)
+       ret.PostalCode = util.CopyIfNotNil(u.PostalCode)
+       ret.PublicSSHKey = util.CopyIfNotNil(u.PublicSSHKey)
        if u.RegistrationSent != nil {
                ret.RegistrationSent = TimeNoModFromTime(*u.RegistrationSent)
        }
-       ret.StateOrProvince = copyStringIfNotNil(u.StateOrProvince)
-       ret.Tenant = copyStringIfNotNil(u.Tenant)
-       ret.Token = copyStringIfNotNil(u.Token)
-       ret.UID = copyIntIfNotNil(u.UID)
+       ret.StateOrProvince = util.CopyIfNotNil(u.StateOrProvince)
+       ret.Tenant = util.CopyIfNotNil(u.Tenant)
+       ret.Token = util.CopyIfNotNil(u.Token)
+       ret.UID = util.CopyIfNotNil(u.UID)
 
        return ret
 }
@@ -194,23 +194,23 @@ func (u UserV4) ToLegacyCurrentUser(roleID int, localUser 
bool) UserCurrent {
        *ret.UserName = u.Username
        ret.LocalUser = new(bool)
        *ret.LocalUser = localUser
-       ret.Token = copyStringIfNotNil(u.Token)
-       ret.AddressLine1 = copyStringIfNotNil(u.AddressLine1)
-       ret.AddressLine2 = copyStringIfNotNil(u.AddressLine2)
-       ret.City = copyStringIfNotNil(u.City)
-       ret.Company = copyStringIfNotNil(u.Company)
-       ret.Country = copyStringIfNotNil(u.Country)
-       ret.Email = copyStringIfNotNil(u.Email)
-       ret.GID = copyIntIfNotNil(u.GID)
-       ret.ID = copyIntIfNotNil(u.ID)
-       ret.UID = copyIntIfNotNil(u.UID)
-       ret.PhoneNumber = copyStringIfNotNil(u.PhoneNumber)
-       ret.PostalCode = copyStringIfNotNil(u.PostalCode)
-       ret.PublicSSHKey = copyStringIfNotNil(u.PublicSSHKey)
-       ret.StateOrProvince = copyStringIfNotNil(u.StateOrProvince)
-       ret.Tenant = copyStringIfNotNil(u.Tenant)
-       ret.Token = copyStringIfNotNil(u.Token)
-       ret.UID = copyIntIfNotNil(u.UID)
+       ret.Token = util.CopyIfNotNil(u.Token)
+       ret.AddressLine1 = util.CopyIfNotNil(u.AddressLine1)
+       ret.AddressLine2 = util.CopyIfNotNil(u.AddressLine2)
+       ret.City = util.CopyIfNotNil(u.City)
+       ret.Company = util.CopyIfNotNil(u.Company)
+       ret.Country = util.CopyIfNotNil(u.Country)
+       ret.Email = util.CopyIfNotNil(u.Email)
+       ret.GID = util.CopyIfNotNil(u.GID)
+       ret.ID = util.CopyIfNotNil(u.ID)
+       ret.UID = util.CopyIfNotNil(u.UID)
+       ret.PhoneNumber = util.CopyIfNotNil(u.PhoneNumber)
+       ret.PostalCode = util.CopyIfNotNil(u.PostalCode)
+       ret.PublicSSHKey = util.CopyIfNotNil(u.PublicSSHKey)
+       ret.StateOrProvince = util.CopyIfNotNil(u.StateOrProvince)
+       ret.Tenant = util.CopyIfNotNil(u.Tenant)
+       ret.Token = util.CopyIfNotNil(u.Token)
+       ret.UID = util.CopyIfNotNil(u.UID)
 
        return ret
 }
@@ -302,25 +302,25 @@ type CurrentUserUpdateRequestUser struct {
 // Fields not present in earlier API versions need to be passed explicitly
 func (u UserCurrent) Upgrade(registrationSent, lastAuthenticated *time.Time, 
ucdn string, changeLogCount int) UserV4 {
        var ret UserV4
-       ret.AddressLine1 = copyStringIfNotNil(u.AddressLine1)
-       ret.AddressLine2 = copyStringIfNotNil(u.AddressLine2)
+       ret.AddressLine1 = util.CopyIfNotNil(u.AddressLine1)
+       ret.AddressLine2 = util.CopyIfNotNil(u.AddressLine2)
        ret.ChangeLogCount = changeLogCount
-       ret.City = copyStringIfNotNil(u.City)
-       ret.Company = copyStringIfNotNil(u.Company)
-       ret.Country = copyStringIfNotNil(u.Country)
-       ret.Email = copyStringIfNotNil(u.Email)
-       ret.GID = copyIntIfNotNil(u.GID)
-       ret.ID = copyIntIfNotNil(u.ID)
+       ret.City = util.CopyIfNotNil(u.City)
+       ret.Company = util.CopyIfNotNil(u.Company)
+       ret.Country = util.CopyIfNotNil(u.Country)
+       ret.Email = util.CopyIfNotNil(u.Email)
+       ret.GID = util.CopyIfNotNil(u.GID)
+       ret.ID = util.CopyIfNotNil(u.ID)
        ret.LastAuthenticated = lastAuthenticated
-       ret.PhoneNumber = copyStringIfNotNil(u.PhoneNumber)
-       ret.PostalCode = copyStringIfNotNil(u.PostalCode)
-       ret.PublicSSHKey = copyStringIfNotNil(u.PublicSSHKey)
+       ret.PhoneNumber = util.CopyIfNotNil(u.PhoneNumber)
+       ret.PostalCode = util.CopyIfNotNil(u.PostalCode)
+       ret.PublicSSHKey = util.CopyIfNotNil(u.PublicSSHKey)
        ret.RegistrationSent = registrationSent
-       ret.StateOrProvince = copyStringIfNotNil(u.StateOrProvince)
-       ret.Tenant = copyStringIfNotNil(u.Tenant)
-       ret.Token = copyStringIfNotNil(u.Token)
+       ret.StateOrProvince = util.CopyIfNotNil(u.StateOrProvince)
+       ret.Tenant = util.CopyIfNotNil(u.Tenant)
+       ret.Token = util.CopyIfNotNil(u.Token)
        ret.UCDN = ucdn
-       ret.UID = copyIntIfNotNil(u.UID)
+       ret.UID = util.CopyIfNotNil(u.UID)
        ret.FullName = u.FullName
        if u.LastUpdated != nil {
                ret.LastUpdated = u.LastUpdated.Time
diff --git a/lib/go-util/ptr.go b/lib/go-util/ptr.go
index 6b33346d41..2517a77a91 100644
--- a/lib/go-util/ptr.go
+++ b/lib/go-util/ptr.go
@@ -1,3 +1,7 @@
+// Package util contains various miscellaneous utilities that are helpful
+// throughout components and aren't necessarily related to ATC data structures.
+package util
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -17,50 +21,129 @@
  * under the License.
  */
 
-package util
-
 import "time"
 
+// Ptr returns a pointer to the given value.
 func Ptr[T any](v T) *T {
        return &v
 }
 
+// StrPtr returns a pointer to the given string.
+//
+// Deprecated. This is exactly equivalent to just using Ptr, so duplicated
+// functionality like this function will likely be removed before too long.
 func StrPtr(str string) *string {
-       return &str
+       return Ptr(str)
 }
 
+// IntPtr returns a pointer to the given integer.
+//
+// Deprecated. This is exactly equivalent to just using Ptr, so duplicated
+// functionality like this function will likely be removed before too long.
 func IntPtr(i int) *int {
-       return &i
+       return Ptr(i)
 }
 
+// UIntPtr returns a pointer to the given unsigned integer.
+//
+// Deprecated. This is exactly equivalent to just using Ptr, so duplicated
+// functionality like this function will likely be removed before too long.
 func UIntPtr(u uint) *uint {
-       return &u
+       return Ptr(u)
 }
 
+// UInt64Ptr returns a pointer to the given 64-bit unsigned integer.
+//
+// Deprecated. This is exactly equivalent to just using Ptr, so duplicated
+// functionality like this function will likely be removed before too long.
 func UInt64Ptr(u uint64) *uint64 {
-       return &u
+       return Ptr(u)
 }
 
+// Uint64Ptr returns a pointer to the given 64-bit unsigned integer.
+//
+// Deprecated. This is just a common mis-casing of UInt64Ptr. These should not
+// both exist, and this one - being the less proper casing - is subject to
+// removal without warning, as its very existence is likely accidental.
+//
+// Deprecated. This is exactly equivalent to just using Ptr, so duplicated
+// functionality like this function will likely be removed before too long.
 func Uint64Ptr(u uint64) *uint64 {
-       return &u
+       return Ptr(u)
 }
 
+// Int64Ptr returns a pointer to the given 64-bit integer.
+//
+// Deprecated. This is exactly equivalent to just using Ptr, so duplicated
+// functionality like this function will likely be removed before too long.
 func Int64Ptr(i int64) *int64 {
-       return &i
+       return Ptr(i)
 }
 
+// BoolPtr returns a pointer to the given boolean.
+//
+// Deprecated. This is exactly equivalent to just using Ptr, so duplicated
+// functionality like this function will likely be removed before too long.
 func BoolPtr(b bool) *bool {
-       return &b
+       return Ptr(b)
 }
 
+// FloatPtr returns a pointer to the given 64-bit floating-point number.
+//
+// Deprecated. This is exactly equivalent to just using Ptr, so duplicated
+// functionality like this function will likely be removed before too long.
 func FloatPtr(f float64) *float64 {
-       return &f
+       return Ptr(f)
 }
 
-func InterfacePtr(i interface{}) *interface{} {
-       return &i
+// InterfacePtr returns a pointer to the given empty interface.
+//
+// Deprecated. This is exactly equivalent to just using Ptr, so duplicated
+// functionality like this function will likely be removed before too long.
+func InterfacePtr(i any) *any {
+       return Ptr(i)
 }
 
+// TimePtr returns a pointer to the given time.Time value.
+//
+// Deprecated. This is exactly equivalent to just using Ptr, so duplicated
+// functionality like this function will likely be removed before too long.
 func TimePtr(t time.Time) *time.Time {
-       return &t
+       return Ptr(t)
+}
+
+// Coalesce coalesces the given pointer to a concrete value. This is basically
+// the inverse operation of Ptr - it safely dereferences its input. If the
+// pointer is nil, def is returned as a default value.
+func Coalesce[T any](p *T, def T) T {
+       if p == nil {
+               return def
+       }
+       return *p
+}
+
+// CoalesceToDefault coalesces a pointer to the type to which it points. It
+// returns the "zero value" of its input's pointed-to type when the input is
+// nil. This is equivalent to:
+//
+//     var x T
+//     result := CoalesceToDefault(p, x)
+//
+// ... but can be done on one line without knowing the type of `p`.
+func CoalesceToDefault[T any](p *T) T {
+       var ret T
+       if p != nil {
+               ret = *p
+       }
+       return ret
+}
+
+// CopyIfNotNil makes a deep copy of p - unless it's nil, in which case it just
+// returns nil.
+func CopyIfNotNil[T any](p *T) *T {
+       if p == nil {
+               return nil
+       }
+       q := *p
+       return &q
 }
diff --git a/lib/go-util/ptr_test.go b/lib/go-util/ptr_test.go
index 2cc769919d..70b1113099 100644
--- a/lib/go-util/ptr_test.go
+++ b/lib/go-util/ptr_test.go
@@ -19,7 +19,10 @@ package util
  * under the License.
  */
 
-import "fmt"
+import (
+       "fmt"
+       "time"
+)
 
 func ExamplePtr() {
        ptr := Ptr("testquest")
@@ -80,3 +83,40 @@ func ExampleInterfacePtr() {
        fmt.Println(*ptr)
        // Output: (1+2i)
 }
+
+func ExampleTimePtr() {
+       ptr := TimePtr(time.Time{})
+       fmt.Println(*ptr)
+       // Output: 0001-01-01 00:00:00 +0000 UTC
+}
+
+func ExampleCopyIfNotNil() {
+       var i *int
+       fmt.Println(CopyIfNotNil(i))
+
+       s := Ptr("9000")
+       fmt.Println(*CopyIfNotNil(s))
+
+       // Output: <nil>
+       // 9000
+}
+
+func ExampleCoalesce() {
+       var i *int
+       fmt.Println(Coalesce(i, 9000))
+
+       s := Ptr("testquest")
+       fmt.Println(Coalesce(s, "9001"))
+       // Output: 9000
+       // testquest
+}
+
+func ExampleCoalesceToDefault() {
+       var i *int
+       fmt.Println(CoalesceToDefault(i))
+
+       s := Ptr("testquest")
+       fmt.Println(CoalesceToDefault(s))
+       // Output: 0
+       // testquest
+}
diff --git a/traffic_ops/testing/api/v3/deliveryservice_requests_test.go 
b/traffic_ops/testing/api/v3/deliveryservice_requests_test.go
index 2e43843fa9..fadf5e3fdc 100644
--- a/traffic_ops/testing/api/v3/deliveryservice_requests_test.go
+++ b/traffic_ops/testing/api/v3/deliveryservice_requests_test.go
@@ -114,7 +114,7 @@ func TestDeliveryServiceRequests(t *testing.T) {
                                        },
                                        Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
                                },
-                               "BAD REQUEST when MISSING REQUIRED FIELDS": {
+                               "BAD REQUEST when MISSING REQUIRED Delivery 
Service FIELDS": {
                                        ClientSession: TOSession,
                                        RequestBody: map[string]interface{}{
                                                "changeType": "create",
@@ -126,6 +126,49 @@ func TestDeliveryServiceRequests(t *testing.T) {
                                        },
                                        Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
                                },
+                               "bad request when missing Delivery Service 
definition": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]any{
+                                               "changeType": "create",
+                                               "status":     "draft",
+                                       },
+                               },
+                               "bad request when missing status": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "changeType": "create",
+                                               "deliveryService": 
generateDeliveryService(t, map[string]interface{}{
+                                                       "ccrDnsTtl":          
30,
+                                                       "deepCachingType":    
"NEVER",
+                                                       "initialDispersion":  3,
+                                                       "ipv6RoutingEnabled": 
true,
+                                                       "longDesc":           
"long desc",
+                                                       "orgServerFqdn":      
"http://example.test";,
+                                                       "profileName":        
nil,
+                                                       "tenant":             
"root",
+                                                       "xmlId":              
"test-ds2",
+                                               }),
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "bad request when missing change type": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "deliveryService": 
generateDeliveryService(t, map[string]interface{}{
+                                                       "ccrDnsTtl":          
30,
+                                                       "deepCachingType":    
"NEVER",
+                                                       "initialDispersion":  3,
+                                                       "ipv6RoutingEnabled": 
true,
+                                                       "longDesc":           
"long desc",
+                                                       "orgServerFqdn":      
"http://example.test";,
+                                                       "profileName":        
nil,
+                                                       "tenant":             
"root",
+                                                       "xmlId":              
"test-ds2",
+                                               }),
+                                               "status": "draft",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
                                "BAD REQUEST when VALIDATION RULES ARE NOT 
FOLLOWED": {
                                        ClientSession: TOSession,
                                        RequestBody: map[string]interface{}{
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
index 533625aab9..cc0e80a6b3 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
@@ -620,7 +620,7 @@ func createLegacy(w http.ResponseWriter, r *http.Request, 
inf *api.APIInfo) (res
                return
        }
 
-       api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Delivery Service request 
created", upgraded.Downgrade().Downgrade())
+       api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Delivery Service request 
created", upgraded.Downgrade().Downgrade().Downgrade())
 
        result.Successful = true
        result.Assignee = dsr.Assignee
@@ -749,9 +749,13 @@ func Delete(w http.ResponseWriter, r *http.Request) {
        case 5:
                resp = dsr
        case 4:
-               resp = dsr.Downgrade()
+               if inf.Version.Minor >= 1 {
+                       resp = dsr.Downgrade()
+               } else {
+                       resp = dsr.Downgrade().Downgrade()
+               }
        case 3:
-               resp = dsr.Downgrade().Downgrade()
+               resp = dsr.Downgrade().Downgrade().Downgrade()
        }
 
        api.WriteRespAlertObj(w, r, tc.SuccessLevel, fmt.Sprintf("Delivery 
Service Request #%d deleted", inf.IntParams["id"]), resp)
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
index 557baf3fbb..7b5883a3d8 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
@@ -67,6 +67,9 @@ func validateLegacy(dsr tc.DeliveryServiceRequestNullable, tx 
*sql.Tx) (error, e
                "status":          validation.Validate(dsr.Status, 
validation.Required, validation.By(validTransition)),
        }
        errs := tovalidate.ToErrors(errMap)
+       if len(errs) > 0 {
+               return util.JoinErrs(errs), nil
+       }
        // ensure the deliveryservice requested is valid
        upgraded := dsr.DeliveryService.UpgradeToV4().Upgrade()
        userErr, sysErr := deliveryservice.Validate(tx, &upgraded)

Reply via email to