Repository: go-resty/resty (go)

Issue:
Clone or copy of existing client
I have a use case where my application should have a global HTTP client with sane defaults for tracing, logging and error handling and then some submodules which must apply highly specific settings for only their use case.

For that I would like to copy / clone an existing client which thus inherits all of the clients settings. In other libraries / languages this is also sometimes possible by having a child-parent relationship between clients.

What would be the best way to approach this with resty? Is there a function or common solutions for this use case?

Edit: If this is *not* a present feature but of interest to people, let's discuss how that could be implemented.

Pull request description:
Add Client.Clone function
This adds a crude cloning function. It can be used to have a main client with default values and then create copies from that, which must use custom values.
    
The added function is rather crude and may introduce the potential that, when adding a new Client field, we forget to add it to Clone.

Thus, we should discuss if this is the right way to go. Using code generation or relflection might be a more maintainable solution.

Merging this PR would close #773 

Patch:
diff --git a/client.go b/client.go
index 446ba85..006191c 100644
--- a/client.go
+++ b/client.go
@@ -142,11 +142,11 @@ type Client struct {
 	proxyURL            *url.URL
 	beforeRequest       []RequestMiddleware
 	udBeforeRequest     []RequestMiddleware
-	udBeforeRequestLock sync.RWMutex
+	udBeforeRequestLock *sync.RWMutex
 	preReqHook          PreRequestHook
 	successHooks        []SuccessHook
 	afterResponse       []ResponseMiddleware
-	afterResponseLock   sync.RWMutex
+	afterResponseLock   *sync.RWMutex
 	requestLog          RequestLogCallback
 	responseLog         ResponseLogCallback
 	errorHooks          []ErrorHook
@@ -1125,6 +1125,25 @@ func (c *Client) GetClient() *http.Client {
 	return c.httpClient
 }
 
+// Clone returns a clone of the original client.
+//
+// Be carefull when using this function:
+// - Interface values are not deeply cloned. Thus, both the original and the clone will use the
+// same value.
+// - This function is not safe for concurrent use. You should only use this when you are sure that
+// the client is not being used by any other goroutine.
+//
+// Since v2.12.0
+func (c *Client) Clone() *Client {
+	// dereference the pointer and copy the value
+	cc := *c
+
+	// lock values should not be copied - thus new values are used.
+	cc.afterResponseLock = &sync.RWMutex{}
+	cc.udBeforeRequestLock = &sync.RWMutex{}
+	return &cc
+}
+
 //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
 // Client Unexported methods
 //_______________________________________________________________________
@@ -1360,9 +1379,11 @@ func createClient(hc *http.Client) *Client {
 		XMLUnmarshal:           xml.Unmarshal,
 		HeaderAuthorizationKey: http.CanonicalHeaderKey("Authorization"),
 
-		jsonEscapeHTML:     true,
-		httpClient:         hc,
-		debugBodySizeLimit: math.MaxInt32,
+		jsonEscapeHTML:      true,
+		httpClient:          hc,
+		debugBodySizeLimit:  math.MaxInt32,
+		udBeforeRequestLock: &sync.RWMutex{},
+		afterResponseLock:   &sync.RWMutex{},
 	}
 
 	// Logger


Test patch:
diff --git a/client_test.go b/client_test.go
index 1f291d1..b91f2e9 100644
--- a/client_test.go
+++ b/client_test.go
@@ -1069,3 +1069,29 @@ func TestUnixSocket(t *testing.T) {
 	assertNil(t, err)
 	assertEqual(t, "Hello resty client from a server running on endpoint /hello!", res.String())
 }
+
+func TestClone(t *testing.T) {
+	parent := New()
+
+	// set a non-interface field
+	parent.SetBaseURL("http://localhost")
+
+	// set an interface field
+	parent.UserInfo = &User{
+		Username: "parent",
+	}
+
+	clone := parent.Clone()
+	// update value of non-interface type - change will only happen on clone
+	clone.SetBaseURL("https://local.host")
+	// update value of interface type - change will also happen on parent
+	clone.UserInfo.Username = "clone"
+
+	// asert non-interface type
+	assertEqual(t, "http://localhost", parent.BaseURL)
+	assertEqual(t, "https://local.host", clone.BaseURL)
+
+	// assert interface type
+	assertEqual(t, "clone", parent.UserInfo.Username)
+	assertEqual(t, "clone", clone.UserInfo.Username)
+}