Repository: cloudflare/cloudflare-go (go)

Issue:
Double-unmarshalling of `config` from cfd_tunnel/{{TUNNEL_ID}}/configurations is unnecessary
### Confirmation

- [X] My issue isn't already found on the issue tracker.
- [X] I have replicated my issue using the latest version of the library and it is still present.

### cloudflare-go version

0.46.0

### Go environment

Not relevant.

### Expected output

I was expecting to use `GetTunnelConfiguration` and `UpdateTunnelConfiguration`.
However, it seems like those methods are no longer compatible with the latest version of the API at https://api.cloudflare.com/#cloudflare-tunnel-configuration-properties

I traced the error down to the double-marshaling that was done in https://github.com/cloudflare/cloudflare-go/pull/948#issuecomment-1166701661.

### Actual output

Error unmarshalling.

`2022/08/16 12:38:25 Failed to get config for current tunnel: error unmarshalling the JSON response: json: cannot unmarshal object into Go struct field TunnelConfigurationStringifiedConfigResult.result.config of type string`

### Code demonstrating the issue

	// Get the current configuration
	currentConfig, err := api.GetTunnelConfiguration(ctx, cloudflare.AccountIdentifier(accountId), tunnelId)
	if err != nil {
		if _, ok := err.(*cloudflare.NotFoundError); ok {
			shouldCreateFromScratch = true
		} else {
			log.Fatal("Failed to get config for current tunnel: ", err)
		}
	}

### Steps to reproduce

1. Create a sample go project that uses v0.46.0 of the API.
2. Ensure that you have Cloudflare Tunnel with a Cloudflare Tunnel Configuration.
3. Attempt to get/update the tunnel configuration
4. Notice the serialization error

### References

https://github.com/cloudflare/cloudflare-go/pull/948#issuecomment-1166701661

Pull request description:
Remove double-unmarshalling
## Description

Fixes #1045 

## Has your change been tested?

Modified existing tests and ran `go test`.

## Screenshots (if appropriate):

## Types of changes

What sort of change does your code introduce/modify?

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:

- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.
- [x] This change is using publicly documented (api.cloudflare.com or developers.cloudflare.com) and stable APIs.

[1]: https://help.github.com/articles/closing-issues-using-keywords/


Patch:
diff --git a/.changelog/1046.txt b/.changelog/1046.txt
new file mode 100644
index 000000000..efe78ee9f
--- /dev/null
+++ b/.changelog/1046.txt
@@ -0,0 +1,3 @@
+```release-note:bug
+tunnel_configuration: Remove unnecessary double-unmarshalling due to changes in the API
+```
\ No newline at end of file
diff --git a/tunnel.go b/tunnel.go
index 8807a941d..3ed49f01b 100644
--- a/tunnel.go
+++ b/tunnel.go
@@ -68,21 +68,6 @@ type TunnelConnectionResponse struct {
 	Response
 }
 
-// TunnelConfigurationStringifiedConfigResult is the raw result from the API with
-// the `Config` value as a string. You probably don't want to use this anywhere
-// other than in the HTTP response unmarshaling. Use `TunnelConfigurationResult`
-// for the correct types.
-type TunnelConfigurationStringifiedConfigResult struct {
-	TunnelID string `json:"tunnel_id,omitempty"`
-	Config   string `json:"config,omitempty"`
-	Version  int    `json:"version,omitempty"`
-}
-
-type TunnelConfigurationStringifiedConfigResponse struct {
-	Result TunnelConfigurationStringifiedConfigResult `json:"result"`
-	Response
-}
-
 type TunnelConfigurationResult struct {
 	TunnelID string              `json:"tunnel_id,omitempty"`
 	Config   TunnelConfiguration `json:"config,omitempty"`
@@ -320,31 +305,20 @@ func (api *API) UpdateTunnelConfiguration(ctx context.Context, rc *ResourceConta
 	}
 
 	uri := fmt.Sprintf("/accounts/%s/cfd_tunnel/%s/configurations", rc.Identifier, params.TunnelID)
-	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params.Config)
+	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params)
 	if err != nil {
 		return TunnelConfigurationResult{}, err
 	}
 
-	// `config` comes back as a stringified representation of the configuration
-	// so we need to double unmarshal the result. First, `config` => the string
-	// struct field and if that is successful, then attempt putting it into a typed
-	// struct field.
-
-	var tunnelDetailsResponse TunnelConfigurationStringifiedConfigResponse
+	var tunnelDetailsResponse TunnelConfigurationResponse
 	err = json.Unmarshal(res, &tunnelDetailsResponse)
 	if err != nil {
 		return TunnelConfigurationResult{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
 	}
 
 	var tunnelDetails TunnelConfigurationResult
-	var tunnelConfig TunnelConfiguration
 
-	err = json.Unmarshal([]byte(tunnelDetailsResponse.Result.Config), &tunnelConfig)
-	if err != nil {
-		return TunnelConfigurationResult{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-
-	tunnelDetails.Config = tunnelConfig
+	tunnelDetails.Config = tunnelDetailsResponse.Result.Config
 	tunnelDetails.TunnelID = tunnelDetailsResponse.Result.TunnelID
 	tunnelDetails.Version = tunnelDetailsResponse.Result.Version
 
@@ -369,26 +343,15 @@ func (api *API) GetTunnelConfiguration(ctx context.Context, rc *ResourceContaine
 		return TunnelConfigurationResult{}, err
 	}
 
-	// `config` comes back as a stringified representation of the configuration
-	// so we need to double unmarshal the result. First, `config` => the string
-	// struct field and if that is successful, then attempt putting it into a typed
-	// struct field.
-
-	var tunnelDetailsResponse TunnelConfigurationStringifiedConfigResponse
+	var tunnelDetailsResponse TunnelConfigurationResponse
 	err = json.Unmarshal(res, &tunnelDetailsResponse)
 	if err != nil {
 		return TunnelConfigurationResult{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
 	}
 
 	var tunnelDetails TunnelConfigurationResult
-	var tunnelConfig TunnelConfiguration
-
-	err = json.Unmarshal([]byte(tunnelDetailsResponse.Result.Config), &tunnelConfig)
-	if err != nil {
-		return TunnelConfigurationResult{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
 
-	tunnelDetails.Config = tunnelConfig
+	tunnelDetails.Config = tunnelDetailsResponse.Result.Config
 	tunnelDetails.TunnelID = tunnelDetailsResponse.Result.TunnelID
 	tunnelDetails.Version = tunnelDetailsResponse.Result.Version
 


Test patch:
diff --git a/testdata/fixtures/tunnel/configuration.json b/testdata/fixtures/tunnel/configuration.json
index 86192c631..3267b8556 100644
--- a/testdata/fixtures/tunnel/configuration.json
+++ b/testdata/fixtures/tunnel/configuration.json
@@ -3,7 +3,14 @@
   "errors": [],
   "messages": [],
   "result": {
-    "config": "{\"warp-routing\": {\"enabled\": true},  \"originRequest\" : {\"connectTimeout\": 10}, \"ingress\" : [ {\"hostname\": \"test.example.com\", \"service\": \"https://localhost:8000\" } , {\"service\": \"http_status:404\"} ]}",
+    "config": {
+      "warp-routing": { "enabled": true },
+      "originRequest": { "connectTimeout": 10 },
+      "ingress": [
+        { "hostname": "test.example.com", "service": "https://localhost:8000" },
+        { "service": "http_status:404" }
+      ]
+    },
     "tunnel_id": "f174e90a-fafe-4643-bbbc-4a0ed4fc8415",
     "version": 5,
     "created_at": "2021-01-25T18:22:34.317854Z"