Repository: go-resty/resty
Pull request #625 (2023-03-09 09:03:29)

Remove usages of deprecated io/ioutil
io/ioutil has been deprecated since go1.16, and its functionality has been moved to io and os packages.

segevda: I don't see how a commit that adds no new statements (quite the opposite - it removes several statements) can lower the coverage. @jeevatkm please take a look :pray: 

Patch:
diff --git a/README.md b/README.md
index 8ec6518..bc3f924 100644
--- a/README.md
+++ b/README.md
@@ -265,7 +265,7 @@ resp, err := client.R().
       Post("https://myapp.com/login")
 
 // POST of raw bytes for file upload. For example: upload file to Dropbox
-fileBytes, _ := ioutil.ReadFile("/Users/jeeva/mydocument.pdf")
+fileBytes, _ := os.ReadFile("/Users/jeeva/mydocument.pdf")
 
 // See we are not setting content-type header, since go-resty automatically detects Content-Type for you
 resp, err := client.R().
@@ -369,13 +369,13 @@ import jsoniter "github.com/json-iterator/go"
 
 json := jsoniter.ConfigCompatibleWithStandardLibrary
 
-client := resty.New()
-client.JSONMarshal = json.Marshal
-client.JSONUnmarshal = json.Unmarshal
+client := resty.New().
+    SetJSONMarshaler(json.Marshal).
+    SetJSONUnmarshaler(json.Unmarshal)
 
 // similarly user could do for XML too with -
-client.XMLMarshal
-client.XMLUnmarshal
+client.SetXMLMarshaler(xml.Marshal).
+    SetXMLUnmarshaler(xml.Unmarshal)
 ```
 
 ### Multipart File(s) upload
@@ -383,8 +383,8 @@ client.XMLUnmarshal
 #### Using io.Reader
 
 ```go
-profileImgBytes, _ := ioutil.ReadFile("/Users/jeeva/test-img.png")
-notesBytes, _ := ioutil.ReadFile("/Users/jeeva/text-file.txt")
+profileImgBytes, _ := os.ReadFile("/Users/jeeva/test-img.png")
+notesBytes, _ := os.ReadFile("/Users/jeeva/text-file.txt")
 
 // Create a Resty Client
 client := resty.New()
diff --git a/client.go b/client.go
index e65e3ce..95c3a7d 100644
--- a/client.go
+++ b/client.go
@@ -14,10 +14,10 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math"
 	"net/http"
 	"net/url"
+	"os"
 	"reflect"
 	"regexp"
 	"strings"
@@ -126,29 +126,29 @@ type Client struct {
 	// value when `SetAuthToken` option is used.
 	HeaderAuthorizationKey string
 
-	jsonEscapeHTML     bool
-	setContentLength   bool
-	closeConnection    bool
-	notParseResponse   bool
-	trace              bool
-	debugBodySizeLimit int64
-	outputDirectory    string
-	scheme             string
-	log                Logger
-	httpClient         *http.Client
-	proxyURL           *url.URL
-	beforeRequest      []RequestMiddleware
-	udBeforeRequest    []RequestMiddleware
-  udBeforeRequestLock sync.RWMutex
-	preReqHook         PreRequestHook
-	successHooks       []SuccessHook
-	afterResponse      []ResponseMiddleware
-  afterResponseLock   sync.RWMutex
-	requestLog         RequestLogCallback
-	responseLog        ResponseLogCallback
-	errorHooks         []ErrorHook
-	invalidHooks       []ErrorHook
-	panicHooks         []ErrorHook
+	jsonEscapeHTML      bool
+	setContentLength    bool
+	closeConnection     bool
+	notParseResponse    bool
+	trace               bool
+	debugBodySizeLimit  int64
+	outputDirectory     string
+	scheme              string
+	log                 Logger
+	httpClient          *http.Client
+	proxyURL            *url.URL
+	beforeRequest       []RequestMiddleware
+	udBeforeRequest     []RequestMiddleware
+	udBeforeRequestLock sync.RWMutex
+	preReqHook          PreRequestHook
+	successHooks        []SuccessHook
+	afterResponse       []ResponseMiddleware
+	afterResponseLock   sync.RWMutex
+	requestLog          RequestLogCallback
+	responseLog         ResponseLogCallback
+	errorHooks          []ErrorHook
+	invalidHooks        []ErrorHook
+	panicHooks          []ErrorHook
 }
 
 // User type is to hold an username and password information
@@ -682,6 +682,34 @@ func (c *Client) SetRetryAfter(callback RetryAfterFunc) *Client {
 	return c
 }
 
+// SetJSONMarshaler method sets the JSON marshaler function to marshal the request body.
+// By default, Resty uses `encoding/json` package to marshal the request body.
+func (c *Client) SetJSONMarshaler(marshaler func(v interface{}) ([]byte, error)) *Client {
+	c.JSONMarshal = marshaler
+	return c
+}
+
+// SetJSONUnmarshaler method sets the JSON unmarshaler function to unmarshal the response body.
+// By default, Resty uses `encoding/json` package to unmarshal the response body.
+func (c *Client) SetJSONUnmarshaler(unmarshaler func(data []byte, v interface{}) error) *Client {
+	c.JSONUnmarshal = unmarshaler
+	return c
+}
+
+// SetXMLMarshaler method sets the XML marshaler function to marshal the request body.
+// By default, Resty uses `encoding/xml` package to marshal the request body.
+func (c *Client) SetXMLMarshaler(marshaler func(v interface{}) ([]byte, error)) *Client {
+	c.XMLMarshal = marshaler
+	return c
+}
+
+// SetXMLUnmarshaler method sets the XML unmarshaler function to unmarshal the response body.
+// By default, Resty uses `encoding/xml` package to unmarshal the response body.
+func (c *Client) SetXMLUnmarshaler(unmarshaler func(data []byte, v interface{}) error) *Client {
+	c.XMLUnmarshal = unmarshaler
+	return c
+}
+
 // AddRetryCondition method adds a retry condition function to array of functions
 // that are checked to determine if the request is retried. The request will
 // retry if any of the functions return true and error is nil.
@@ -797,7 +825,7 @@ func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
 //
 //	client.SetRootCertificate("/path/to/root/pemFile.pem")
 func (c *Client) SetRootCertificate(pemFilePath string) *Client {
-	rootPemData, err := ioutil.ReadFile(pemFilePath)
+	rootPemData, err := os.ReadFile(pemFilePath)
 	if err != nil {
 		c.log.Errorf("%v", err)
 		return c
@@ -1060,7 +1088,7 @@ func (c *Client) execute(req *Request) (*Response, error) {
 			}
 		}
 
-		if response.body, err = ioutil.ReadAll(body); err != nil {
+		if response.body, err = io.ReadAll(body); err != nil {
 			response.setReceivedAt()
 			return response, err
 		}
diff --git a/middleware.go b/middleware.go
index 4c5cb3c..537a628 100644
--- a/middleware.go
+++ b/middleware.go
@@ -9,7 +9,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"mime/multipart"
 	"net/http"
 	"net/url"
@@ -212,7 +211,7 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
 	// assign get body func for the underlying raw request instance
 	r.RawRequest.GetBody = func() (io.ReadCloser, error) {
 		if bodyCopy != nil {
-			return ioutil.NopCloser(bytes.NewReader(bodyCopy.Bytes())), nil
+			return io.NopCloser(bytes.NewReader(bodyCopy.Bytes())), nil
 		}
 		return nil, nil
 	}
@@ -525,14 +524,14 @@ func getBodyCopy(r *Request) (*bytes.Buffer, error) {
 	// Maybe body is `io.Reader`.
 	// Note: Resty user have to watchout for large body size of `io.Reader`
 	if r.RawRequest.Body != nil {
-		b, err := ioutil.ReadAll(r.RawRequest.Body)
+		b, err := io.ReadAll(r.RawRequest.Body)
 		if err != nil {
 			return nil, err
 		}
 
 		// Restore the Body
 		closeq(r.RawRequest.Body)
-		r.RawRequest.Body = ioutil.NopCloser(bytes.NewBuffer(b))
+		r.RawRequest.Body = io.NopCloser(bytes.NewBuffer(b))
 
 		// Return the Body bytes
 		return bytes.NewBuffer(b), nil


Test patch:
diff --git a/client_test.go b/client_test.go
index b640747..5baf78a 100644
--- a/client_test.go
+++ b/client_test.go
@@ -9,10 +9,11 @@ import (
 	"crypto/tls"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net"
 	"net/http"
 	"net/url"
+	"os"
 	"path/filepath"
 	"reflect"
 	"strconv"
@@ -205,7 +206,7 @@ func TestClientSetRootCertificateNotExists(t *testing.T) {
 
 func TestClientSetRootCertificateFromString(t *testing.T) {
 	client := dc()
-	rootPemData, err := ioutil.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
+	rootPemData, err := os.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
 	assertNil(t, err)
 
 	client.SetRootCertificateFromString(string(rootPemData))
@@ -218,9 +219,9 @@ func TestClientSetRootCertificateFromString(t *testing.T) {
 
 func TestClientSetRootCertificateFromStringErrorTls(t *testing.T) {
 	client := NewWithClient(&http.Client{})
-	client.outputLogTo(ioutil.Discard)
+	client.outputLogTo(io.Discard)
 
-	rootPemData, err := ioutil.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
+	rootPemData, err := os.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
 	assertNil(t, err)
 	rt := &CustomRoundTripper{}
 	client.SetTransport(rt)
@@ -427,7 +428,7 @@ func TestClientPreRequestHook(t *testing.T) {
 		// Reading Request `N` no of times
 		for i := 0; i < 5; i++ {
 			b, _ := r.GetBody()
-			rb, _ := ioutil.ReadAll(b)
+			rb, _ := io.ReadAll(b)
 			c.log.Debugf("%s %v", string(rb), len(rb))
 			assertEqual(t, true, len(rb) >= 45)
 		}
@@ -486,7 +487,7 @@ func TestClientAllowsGetMethodPayloadDisabled(t *testing.T) {
 
 func TestClientRoundTripper(t *testing.T) {
 	c := NewWithClient(&http.Client{})
-	c.outputLogTo(ioutil.Discard)
+	c.outputLogTo(io.Discard)
 
 	rt := &CustomRoundTripper{}
 	c.SetTransport(rt)
@@ -509,6 +510,39 @@ func TestClientNewRequest(t *testing.T) {
 	assertNotNil(t, request)
 }
 
+
+func TestClientSetJSONMarshaler(t *testing.T) {
+	m := func (v interface{}) ([]byte, error) { return nil,nil }
+	c := New().SetJSONMarshaler(m)
+	p1 := fmt.Sprintf("%p", c.JSONMarshal)
+	p2 := fmt.Sprintf("%p", m)
+	assertEqual(t, p1, p2) // functions can not be compared, we only can compare pointers
+}
+
+func TestClientSetJSONUnmarshaler(t *testing.T) {
+	m := func ([]byte, interface{}) error { return nil }
+	c := New().SetJSONUnmarshaler(m)
+	p1 := fmt.Sprintf("%p", c.JSONUnmarshal)
+	p2 := fmt.Sprintf("%p", m)
+	assertEqual(t, p1, p2) // functions can not be compared, we only can compare pointers
+}
+
+func TestClientSetXMLMarshaler(t *testing.T) {
+	m := func (v interface{}) ([]byte, error) { return nil,nil }
+	c := New().SetXMLMarshaler(m)
+	p1 := fmt.Sprintf("%p", c.XMLMarshal)
+	p2 := fmt.Sprintf("%p", m)
+	assertEqual(t, p1, p2) // functions can not be compared, we only can compare pointers
+}
+
+func TestClientSetXMLUnmarshaler(t *testing.T) {
+	m := func ([]byte, interface{}) error { return nil }
+	c := New().SetXMLUnmarshaler(m)
+	p1 := fmt.Sprintf("%p", c.XMLUnmarshal)
+	p2 := fmt.Sprintf("%p", m)
+	assertEqual(t, p1, p2) // functions can not be compared, we only can compare pointers
+}
+
 func TestDebugBodySizeLimit(t *testing.T) {
 	ts := createGetServer(t)
 	defer ts.Close()
@@ -749,7 +783,7 @@ func TestClientOnResponseError(t *testing.T) {
 					assertEqual(t, 1, hook6)
 				}
 			}()
-			c := New().outputLogTo(ioutil.Discard).
+			c := New().outputLogTo(io.Discard).
 				SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
 				SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF").
 				SetRetryCount(0).
diff --git a/example_test.go b/example_test.go
index 2d8d3f7..1a2aaeb 100644
--- a/example_test.go
+++ b/example_test.go
@@ -7,7 +7,6 @@ package resty_test
 import (
 	"crypto/tls"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net/http"
 	"os"
@@ -121,8 +120,7 @@ func Example_post() {
 func Example_dropboxUpload() {
 	// For example: upload file to Dropbox
 	// POST of raw bytes for file upload.
-	file, _ := os.Open("/Users/jeeva/mydocument.pdf")
-	fileBytes, _ := ioutil.ReadAll(file)
+	fileBytes, _ := os.ReadFile("/Users/jeeva/mydocument.pdf")
 
 	// Create a resty client
 	client := resty.New()
diff --git a/request_test.go b/request_test.go
index f7a3f4b..769a5e1 100644
--- a/request_test.go
+++ b/request_test.go
@@ -10,7 +10,6 @@ import (
 	"encoding/xml"
 	"errors"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/url"
@@ -84,7 +83,7 @@ func TestGetClientParamRequestParam(t *testing.T) {
 	c.SetQueryParam("client_param", "true").
 		SetQueryParams(map[string]string{"req_1": "jeeva", "req_3": "jeeva3"}).
 		SetDebug(true)
-	c.outputLogTo(ioutil.Discard)
+	c.outputLogTo(io.Discard)
 
 	resp, err := c.R().
 		SetQueryParams(map[string]string{"req_1": "req 1 value", "req_2": "req 2 value"}).
@@ -638,7 +637,7 @@ func TestFormData(t *testing.T) {
 	c.SetFormData(map[string]string{"zip_code": "00000", "city": "Los Angeles"}).
 		SetContentLength(true).
 		SetDebug(true)
-	c.outputLogTo(ioutil.Discard)
+	c.outputLogTo(io.Discard)
 
 	resp, err := c.R().
 		SetFormData(map[string]string{"first_name": "Jeevanandam", "last_name": "M", "zip_code": "00001"}).
@@ -660,7 +659,7 @@ func TestMultiValueFormData(t *testing.T) {
 
 	c := dc()
 	c.SetContentLength(true).SetDebug(true)
-	c.outputLogTo(ioutil.Discard)
+	c.outputLogTo(io.Discard)
 
 	resp, err := c.R().
 		SetQueryParamsFromValues(v).
@@ -680,7 +679,7 @@ func TestFormDataDisableWarn(t *testing.T) {
 		SetContentLength(true).
 		SetDebug(true).
 		SetDisableWarn(true)
-	c.outputLogTo(ioutil.Discard)
+	c.outputLogTo(io.Discard)
 
 	resp, err := c.R().
 		SetFormData(map[string]string{"first_name": "Jeevanandam", "last_name": "M", "zip_code": "00001"}).
@@ -781,8 +780,8 @@ func TestMultiPartIoReaderFiles(t *testing.T) {
 	defer cleanupFiles(".testdata/upload")
 
 	basePath := getTestDataPath()
-	profileImgBytes, _ := ioutil.ReadFile(filepath.Join(basePath, "test-img.png"))
-	notesBytes, _ := ioutil.ReadFile(filepath.Join(basePath, "text-file.txt"))
+	profileImgBytes, _ := os.ReadFile(filepath.Join(basePath, "test-img.png"))
+	notesBytes, _ := os.ReadFile(filepath.Join(basePath, "text-file.txt"))
 
 	// Just info values
 	file := File{
@@ -1033,7 +1032,7 @@ func TestPutJSONString(t *testing.T) {
 	})
 
 	client.SetDebug(true)
-	client.outputLogTo(ioutil.Discard)
+	client.outputLogTo(io.Discard)
 
 	resp, err := client.R().
 		SetHeaders(map[string]string{hdrContentTypeKey: "application/json; charset=utf-8", hdrAcceptKey: "application/json; charset=utf-8"}).
@@ -1202,9 +1201,7 @@ func TestRawFileUploadByBody(t *testing.T) {
 	ts := createFormPostServer(t)
 	defer ts.Close()
 
-	file, err := os.Open(filepath.Join(getTestDataPath(), "test-img.png"))
-	assertNil(t, err)
-	fileBytes, err := ioutil.ReadAll(file)
+	fileBytes, err := os.ReadFile(filepath.Join(getTestDataPath(), "test-img.png"))
 	assertNil(t, err)
 
 	resp, err := dclr().
@@ -1459,7 +1456,7 @@ func TestOutputFileWithBaseDirAndRelativePath(t *testing.T) {
 		SetRedirectPolicy(FlexibleRedirectPolicy(10)).
 		SetOutputDirectory(filepath.Join(getTestDataPath(), "dir-sample")).
 		SetDebug(true)
-	client.outputLogTo(ioutil.Discard)
+	client.outputLogTo(io.Discard)
 
 	resp, err := client.R().
 		SetOutput("go-resty/test-img-success.png").
@@ -1947,4 +1944,3 @@ func TestSetResultMustNotPanicOnNil(t *testing.T) {
 	}()
 	dc().R().SetResult(nil)
 }
-
diff --git a/resty_test.go b/resty_test.go
index 3671892..01d9a3b 100644
--- a/resty_test.go
+++ b/resty_test.go
@@ -11,7 +11,6 @@ import (
 	"encoding/xml"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -100,12 +99,12 @@ func createGetServer(t *testing.T) *httptest.Server {
 				time.Sleep(time.Second * 6)
 				_, _ = w.Write([]byte("TestClientTimeout page"))
 			case "/my-image.png":
-				fileBytes, _ := ioutil.ReadFile(filepath.Join(getTestDataPath(), "test-img.png"))
+				fileBytes, _ := os.ReadFile(filepath.Join(getTestDataPath(), "test-img.png"))
 				w.Header().Set("Content-Type", "image/png")
 				w.Header().Set("Content-Length", strconv.Itoa(len(fileBytes)))
 				_, _ = w.Write(fileBytes)
 			case "/get-method-payload-test":
-				body, err := ioutil.ReadAll(r.Body)
+				body, err := io.ReadAll(r.Body)
 				if err != nil {
 					t.Errorf("Error: could not read get body: %s", err.Error())
 				}
@@ -251,7 +250,7 @@ func createPostServer(t *testing.T) *httptest.Server {
 				// JSON
 				if IsJSONType(r.Header.Get(hdrContentTypeKey)) {
 					if r.URL.Query().Get("status") == "500" {
-						body, err := ioutil.ReadAll(r.Body)
+						body, err := io.ReadAll(r.Body)
 						if err != nil {
 							t.Errorf("Error: could not read post body: %s", err.Error())
 						}
@@ -290,13 +289,13 @@ func createPostServer(t *testing.T) *httptest.Server {
 				w.Header().Set(hdrLocationKey, "/login")
 				w.WriteHeader(http.StatusTemporaryRedirect)
 			case "/redirect-with-body":
-				body, _ := ioutil.ReadAll(r.Body)
+				body, _ := io.ReadAll(r.Body)
 				query := url.Values{}
 				query.Add("body", string(body))
 				w.Header().Set(hdrLocationKey, "/redirected-with-body?"+query.Encode())
 				w.WriteHeader(http.StatusTemporaryRedirect)
 			case "/redirected-with-body":
-				body, _ := ioutil.ReadAll(r.Body)
+				body, _ := io.ReadAll(r.Body)
 				assertEqual(t, r.URL.Query().Get("body"), string(body))
 				w.WriteHeader(http.StatusOK)
 			}
@@ -582,7 +581,7 @@ func createGenServer(t *testing.T) *httptest.Server {
 		}
 
 		if r.Method == "REPORT" && r.URL.Path == "/report" {
-			body, _ := ioutil.ReadAll(r.Body)
+			body, _ := io.ReadAll(r.Body)
 			if len(body) == 0 {
 				w.WriteHeader(http.StatusOK)
 			}
@@ -628,14 +627,14 @@ func createTestServer(fn func(w http.ResponseWriter, r *http.Request)) *httptest
 
 func dc() *Client {
 	c := New().
-		outputLogTo(ioutil.Discard)
+		outputLogTo(io.Discard)
 	return c
 }
 
 func dcl() *Client {
 	c := New().
 		SetDebug(true).
-		outputLogTo(ioutil.Discard)
+		outputLogTo(io.Discard)
 	return c
 }
 
@@ -646,7 +645,7 @@ func dcr() *Request {
 func dclr() *Request {
 	c := dc().
 		SetDebug(true).
-		outputLogTo(ioutil.Discard)
+		outputLogTo(io.Discard)
 	return c.R()
 }