Repository: cloudflare/cloudflare-go (go)

Issue:
Fix pagination for filter/firewall-rules API
### 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

latest

### Go environment

GO111MODULE=""
GOARCH="arm64"
GOBIN=""
GOCACHE="/Users/tjozsa/Library/Caches/go-build"
GOENV="/Users/tjozsa/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/tjozsa/go/pkg/mod"
GONOPROXY="code.cfops.it"
GONOSUMDB="code.cfops.it"
GOOS="darwin"
GOPATH="/Users/tjozsa/go"
GOPRIVATE="code.cfops.it"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/opt/homebrew/Cellar/go/1.18.5/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/opt/homebrew/Cellar/go/1.18.5/libexec/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.18.5"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/81/zyf3xcqd6wv1nh70f4pspr900000gp/T/go-build1439680234=/tmp/go-build -gno-record-gcc-switches -fno-common"

### Expected output

`api.FirewallRules` and `api.Filters` calling the public API only once with required params for paginations.

### Actual output

They submit at least two requests:
```
/zones/b1fbb152bbde3bd28919a7f4bdca841f/firewall/rules
/zones/b1fbb152bbde3bd28919a7f4bdca841f/firewall/rules?page=1&per_page=50
```

### Code demonstrating the issue

I've modified cloudflare-go a bit to present the problem I'm seeing. A simple printing out the `uri` we are calling in cloudflare.go `makeRequestContext`

```
func (api *API) makeRequestContext(ctx context.Context, method, uri string, params interface{}) ([]byte, error) {
	fmt.Println(uri)
	return api.makeRequestWithAuthType(ctx, method, uri, params, api.authType)
}
```

Then when I called in my example code:
```
fwRules, _, err := api.FirewallRules(context.Background(), cloudflare.ZoneIdentifier("b1fbb152bbde3bd28919a7f4bdca841f"), cloudflare.FirewallRuleListParams{})
```

stdout follows:
```
/zones/b1fbb152bbde3bd28919a7f4bdca841f/firewall/rules
/zones/b1fbb152bbde3bd28919a7f4bdca841f/firewall/rules?page=1&per_page=50
```



### Steps to reproduce

see code demonstration

### References

I found this while I was upgrading cf-terraforming to use the latest cloudflare-go. My tests were not passing, because they needed an additional interaction:
- https://github.com/cloudflare/cf-terraforming/blob/49cc60900f2562c287dfa68fec6d90352268b17f/testdata/cloudflare/cloudflare_filter.yaml#L4-L10
- https://github.com/cloudflare/cf-terraforming/blob/49cc60900f2562c287dfa68fec6d90352268b17f/testdata/cloudflare/cloudflare_filter.yaml#L43-L49




Pull request description:
filter/firewall-rules fix for double API calls
## Description

Recent pagination updates to these APIs are causing endpoints to be called twice. This PR aims to fix them.

## Has your change been tested?

Unit tests are passing, and I tried this version in cf-terraforming, where it's working as expected

## 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.
- [ ] 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/

Closes #1024

Patch:
diff --git a/.changelog/1016.txt b/.changelog/1016.txt
new file mode 100644
index 000000000..753e4646b
--- /dev/null
+++ b/.changelog/1016.txt
@@ -0,0 +1,7 @@
+```release-note:enhancement
+firewall_rule: fix double endpoint calls & moving towards common method signature
+```
+
+```release-note:enhancement
+filter: fix double endpoint calls & moving towards common method signature
+```
diff --git a/.changelog/1020.txt b/.changelog/1020.txt
deleted file mode 100644
index 051b9e79d..000000000
--- a/.changelog/1020.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:dependency
-provider: bumps github.com/golangci/golangci-lint from 1.47.3 to 1.48.0
-```
diff --git a/.changelog/1026.txt b/.changelog/1026.txt
deleted file mode 100644
index 3a43ce2de..000000000
--- a/.changelog/1026.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:enhancement
-workers-tail: Add in support for Workers tail API
-```
\ No newline at end of file
diff --git a/.changelog/1027.txt b/.changelog/1027.txt
deleted file mode 100644
index 4661228cd..000000000
--- a/.changelog/1027.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:enhancement
-workers-account-settings: Add in support for Workers account settings API
-```
\ No newline at end of file
diff --git a/.changelog/1028.txt b/.changelog/1028.txt
deleted file mode 100644
index f76d16ff3..000000000
--- a/.changelog/1028.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:enhancement
-r2: Add in support for creating and deleting R2 buckets
-```
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73a2af949..f6ef57eee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,16 +1,4 @@
-## 0.47.0 (Unreleased)
-
-ENHANCEMENTS:
-
-* r2: Add in support for creating and deleting R2 buckets ([#1028](https://github.com/cloudflare/cloudflare-go/issues/1028))
-* workers-account-settings: Add in support for Workers account settings API ([#1027](https://github.com/cloudflare/cloudflare-go/issues/1027))
-* workers-tail: Add in support for Workers tail API ([#1026](https://github.com/cloudflare/cloudflare-go/issues/1026))
-
-DEPENDENCIES:
-
-* provider: bumps github.com/golangci/golangci-lint from 1.47.3 to 1.48.0 ([#1020](https://github.com/cloudflare/cloudflare-go/issues/1020))
-
-## 0.46.0 (3rd August, 2022)
+## 0.46.0 (Unreleased)
 
 NOTES:
 
diff --git a/filter.go b/filter.go
index 2b342feb0..532bf5617 100644
--- a/filter.go
+++ b/filter.go
@@ -60,6 +60,26 @@ type FilterValidationExpressionMessage struct {
 	Message string `json:"message"`
 }
 
+// FilterCreateParams contains required and optional params
+// for creating a filter.
+type FilterCreateParams struct {
+	ID          string `json:"id,omitempty"`
+	Expression  string `json:"expression"`
+	Paused      bool   `json:"paused"`
+	Description string `json:"description"`
+	Ref         string `json:"ref,omitempty"`
+}
+
+// FilterUpdateParams contains required and optional params
+// for updating a filter.
+type FilterUpdateParams struct {
+	ID          string `json:"id"`
+	Expression  string `json:"expression"`
+	Paused      bool   `json:"paused"`
+	Description string `json:"description"`
+	Ref         string `json:"ref,omitempty"`
+}
+
 type FilterListParams struct {
 	ResultInfo
 }
@@ -84,62 +104,57 @@ func (api *API) Filter(ctx context.Context, rc *ResourceContainer, filterID stri
 	return filterResponse.Result, nil
 }
 
-// Filters returns all filters for a zone.
+// Filters returns filters for a zone.
 //
 // Automatically paginates all results unless `params.PerPage` and `params.Page`
 // is set.
 //
 // API reference: https://developers.cloudflare.com/firewall/api/cf-filters/get/#get-all-filters
 func (api *API) Filters(ctx context.Context, rc *ResourceContainer, params FilterListParams) ([]Filter, *ResultInfo, error) {
-	uri := buildURI(fmt.Sprintf("/zones/%s/filters", rc.Identifier), params)
-
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-	if err != nil {
-		return []Filter{}, &ResultInfo{}, err
-	}
-
-	var filtersResponse FiltersDetailResponse
-	err = json.Unmarshal(res, &filtersResponse)
-
-	if err != nil {
-		return []Filter{}, &ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	autoPaginate := true
+	if params.PerPage >= 1 || params.Page >= 1 {
+		autoPaginate = false
 	}
-
-	if params.PerPage < 1 && params.Page < 1 {
-		var filters []Filter
+	if params.PerPage < 1 {
 		params.PerPage = 50
+	}
+	if params.Page < 1 {
 		params.Page = 1
+	}
 
-		for !params.ResultInfo.Done() {
-			uri := buildURI(fmt.Sprintf("/zones/%s/filters", rc.Identifier), params)
+	var filters []Filter
+	var fResponse FiltersDetailResponse
+	for {
+		uri := buildURI(fmt.Sprintf("/zones/%s/filters", rc.Identifier), params)
 
-			res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-			if err != nil {
-				return []Filter{}, &ResultInfo{}, err
-			}
+		res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+		if err != nil {
+			return []Filter{}, &ResultInfo{}, err
+		}
+
+		err = json.Unmarshal(res, &fResponse)
+		if err != nil {
+			return []Filter{}, &ResultInfo{}, fmt.Errorf("failed to unmarshal filters JSON data: %w", err)
+		}
 
-			var fResponse FiltersDetailResponse
-			err = json.Unmarshal(res, &fResponse)
-			if err != nil {
-				return []Filter{}, &ResultInfo{}, fmt.Errorf("failed to unmarshal filters JSON data: %w", err)
-			}
+		filters = append(filters, fResponse.Result...)
+		params.ResultInfo = fResponse.ResultInfo.Next()
 
-			filters = append(filters, fResponse.Result...)
-			params.ResultInfo = fResponse.ResultInfo.Next()
+		if params.ResultInfo.Done() || !autoPaginate {
+			break
 		}
-		filtersResponse.Result = filters
 	}
 
-	return filtersResponse.Result, &filtersResponse.ResultInfo, nil
+	return filters, &fResponse.ResultInfo, nil
 }
 
 // CreateFilters creates new filters.
 //
 // API reference: https://developers.cloudflare.com/firewall/api/cf-filters/post/
-func (api *API) CreateFilters(ctx context.Context, rc *ResourceContainer, filters []Filter) ([]Filter, error) {
+func (api *API) CreateFilters(ctx context.Context, rc *ResourceContainer, params []FilterCreateParams) ([]Filter, error) {
 	uri := fmt.Sprintf("/zones/%s/filters", rc.Identifier)
 
-	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, filters)
+	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
 	if err != nil {
 		return []Filter{}, err
 	}
@@ -156,14 +171,14 @@ func (api *API) CreateFilters(ctx context.Context, rc *ResourceContainer, filter
 // UpdateFilter updates a single filter.
 //
 // API reference: https://developers.cloudflare.com/firewall/api/cf-filters/put/#update-a-single-filter
-func (api *API) UpdateFilter(ctx context.Context, rc *ResourceContainer, filter Filter) (Filter, error) {
-	if filter.ID == "" {
+func (api *API) UpdateFilter(ctx context.Context, rc *ResourceContainer, params FilterUpdateParams) (Filter, error) {
+	if params.ID == "" {
 		return Filter{}, fmt.Errorf("filter ID cannot be empty")
 	}
 
-	uri := fmt.Sprintf("/zones/%s/filters/%s", rc.Identifier, filter.ID)
+	uri := fmt.Sprintf("/zones/%s/filters/%s", rc.Identifier, params.ID)
 
-	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, filter)
+	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params)
 	if err != nil {
 		return Filter{}, err
 	}
@@ -180,8 +195,8 @@ func (api *API) UpdateFilter(ctx context.Context, rc *ResourceContainer, filter
 // UpdateFilters updates many filters at once.
 //
 // API reference: https://developers.cloudflare.com/firewall/api/cf-filters/put/#update-multiple-filters
-func (api *API) UpdateFilters(ctx context.Context, rc *ResourceContainer, filters []Filter) ([]Filter, error) {
-	for _, filter := range filters {
+func (api *API) UpdateFilters(ctx context.Context, rc *ResourceContainer, params []FilterUpdateParams) ([]Filter, error) {
+	for _, filter := range params {
 		if filter.ID == "" {
 			return []Filter{}, fmt.Errorf("filter ID cannot be empty")
 		}
@@ -189,7 +204,7 @@ func (api *API) UpdateFilters(ctx context.Context, rc *ResourceContainer, filter
 
 	uri := fmt.Sprintf("/zones/%s/filters", rc.Identifier)
 
-	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, filters)
+	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params)
 	if err != nil {
 		return []Filter{}, err
 	}
diff --git a/firewall_rules.go b/firewall_rules.go
index a0026b4a8..4d5636c37 100644
--- a/firewall_rules.go
+++ b/firewall_rules.go
@@ -18,6 +18,7 @@ type FirewallRule struct {
 	Priority    interface{} `json:"priority"`
 	Filter      Filter      `json:"filter"`
 	Products    []string    `json:"products,omitempty"`
+	Ref         string      `json:"ref,omitempty"`
 	CreatedOn   time.Time   `json:"created_on,omitempty"`
 	ModifiedOn  time.Time   `json:"modified_on,omitempty"`
 }
@@ -38,53 +39,78 @@ type FirewallRuleResponse struct {
 	Response
 }
 
+// FirewallRuleCreateParams contains required and optional params
+// for creating a firewall rule.
+type FirewallRuleCreateParams struct {
+	ID          string      `json:"id,omitempty"`
+	Paused      bool        `json:"paused"`
+	Description string      `json:"description"`
+	Action      string      `json:"action"`
+	Priority    interface{} `json:"priority"`
+	Filter      Filter      `json:"filter"`
+	Products    []string    `json:"products,omitempty"`
+	Ref         string      `json:"ref,omitempty"`
+}
+
+// FirewallRuleUpdateParams contains required and optional params
+// for updating a firewall rule.
+type FirewallRuleUpdateParams struct {
+	ID          string      `json:"id"`
+	Paused      bool        `json:"paused"`
+	Description string      `json:"description"`
+	Action      string      `json:"action"`
+	Priority    interface{} `json:"priority"`
+	Filter      Filter      `json:"filter"`
+	Products    []string    `json:"products,omitempty"`
+	Ref         string      `json:"ref,omitempty"`
+}
+
 type FirewallRuleListParams struct {
 	ResultInfo
 }
 
 // FirewallRules returns all firewall rules.
 //
+// Automatically paginates all results unless `params.PerPage` and `params.Page`
+// is set.
+//
 // API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/get/#get-all-rules
 func (api *API) FirewallRules(ctx context.Context, rc *ResourceContainer, params FirewallRuleListParams) ([]FirewallRule, *ResultInfo, error) {
-	uri := buildURI(fmt.Sprintf("/zones/%s/firewall/rules", rc.Identifier), params)
-
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-	if err != nil {
-		return []FirewallRule{}, &ResultInfo{}, err
+	autoPaginate := true
+	if params.PerPage >= 1 || params.Page >= 1 {
+		autoPaginate = false
 	}
-
-	var firewallDetailResponse FirewallRulesDetailResponse
-	err = json.Unmarshal(res, &firewallDetailResponse)
-	if err != nil {
-		return []FirewallRule{}, &ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
-	}
-
-	if params.PerPage < 1 && params.Page < 1 {
-		var firewallRules []FirewallRule
+	if params.PerPage < 1 {
 		params.PerPage = 50
+	}
+	if params.Page < 1 {
 		params.Page = 1
+	}
 
-		for !params.ResultInfo.Done() {
-			uri := buildURI(fmt.Sprintf("/zones/%s/firewall/rules", rc.Identifier), params)
+	var firewallRules []FirewallRule
+	var fResponse FirewallRulesDetailResponse
+	for {
+		uri := buildURI(fmt.Sprintf("/zones/%s/firewall/rules", rc.Identifier), params)
 
-			res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-			if err != nil {
-				return []FirewallRule{}, &ResultInfo{}, err
-			}
+		res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+		if err != nil {
+			return []FirewallRule{}, &ResultInfo{}, err
+		}
+
+		err = json.Unmarshal(res, &fResponse)
+		if err != nil {
+			return []FirewallRule{}, &ResultInfo{}, fmt.Errorf("failed to unmarshal filters JSON data: %w", err)
+		}
 
-			var fResponse FirewallRulesDetailResponse
-			err = json.Unmarshal(res, &fResponse)
-			if err != nil {
-				return []FirewallRule{}, &ResultInfo{}, fmt.Errorf("failed to unmarshal filters JSON data: %w", err)
-			}
+		firewallRules = append(firewallRules, fResponse.Result...)
+		params.ResultInfo = fResponse.ResultInfo.Next()
 
-			firewallRules = append(firewallRules, fResponse.Result...)
-			params.ResultInfo = fResponse.ResultInfo.Next()
+		if params.ResultInfo.Done() || !autoPaginate {
+			break
 		}
-		firewallDetailResponse.Result = firewallRules
 	}
 
-	return firewallDetailResponse.Result, &firewallDetailResponse.ResultInfo, nil
+	return firewallRules, &fResponse.ResultInfo, nil
 }
 
 // FirewallRule returns a single firewall rule based on the ID.
@@ -110,10 +136,10 @@ func (api *API) FirewallRule(ctx context.Context, rc *ResourceContainer, firewal
 // CreateFirewallRules creates new firewall rules.
 //
 // API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/post/
-func (api *API) CreateFirewallRules(ctx context.Context, rc *ResourceContainer, firewallRules []FirewallRule) ([]FirewallRule, error) {
+func (api *API) CreateFirewallRules(ctx context.Context, rc *ResourceContainer, params []FirewallRuleCreateParams) ([]FirewallRule, error) {
 	uri := fmt.Sprintf("/zones/%s/firewall/rules", rc.Identifier)
 
-	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, firewallRules)
+	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
 	if err != nil {
 		return []FirewallRule{}, err
 	}
@@ -130,14 +156,14 @@ func (api *API) CreateFirewallRules(ctx context.Context, rc *ResourceContainer,
 // UpdateFirewallRule updates a single firewall rule.
 //
 // API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/put/#update-a-single-rule
-func (api *API) UpdateFirewallRule(ctx context.Context, rc *ResourceContainer, firewallRule FirewallRule) (FirewallRule, error) {
-	if firewallRule.ID == "" {
+func (api *API) UpdateFirewallRule(ctx context.Context, rc *ResourceContainer, params FirewallRuleUpdateParams) (FirewallRule, error) {
+	if params.ID == "" {
 		return FirewallRule{}, fmt.Errorf("firewall rule ID cannot be empty")
 	}
 
-	uri := fmt.Sprintf("/zones/%s/firewall/rules/%s", rc.Identifier, firewallRule.ID)
+	uri := fmt.Sprintf("/zones/%s/firewall/rules/%s", rc.Identifier, params.ID)
 
-	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, firewallRule)
+	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params)
 	if err != nil {
 		return FirewallRule{}, err
 	}
@@ -154,8 +180,8 @@ func (api *API) UpdateFirewallRule(ctx context.Context, rc *ResourceContainer, f
 // UpdateFirewallRules updates a single firewall rule.
 //
 // API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/put/#update-multiple-rules
-func (api *API) UpdateFirewallRules(ctx context.Context, rc *ResourceContainer, firewallRules []FirewallRule) ([]FirewallRule, error) {
-	for _, firewallRule := range firewallRules {
+func (api *API) UpdateFirewallRules(ctx context.Context, rc *ResourceContainer, params []FirewallRuleUpdateParams) ([]FirewallRule, error) {
+	for _, firewallRule := range params {
 		if firewallRule.ID == "" {
 			return []FirewallRule{}, fmt.Errorf("firewall ID cannot be empty")
 		}
@@ -163,7 +189,7 @@ func (api *API) UpdateFirewallRules(ctx context.Context, rc *ResourceContainer,
 
 	uri := fmt.Sprintf("/zones/%s/firewall/rules", rc.Identifier)
 
-	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, firewallRules)
+	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params)
 	if err != nil {
 		return []FirewallRule{}, err
 	}
diff --git a/go.mod b/go.mod
index d3a03d072..f5ea54fcb 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/client9/misspell v0.3.4
 	github.com/cweill/gotests v1.6.0
 	github.com/go-delve/delve v1.9.0
-	github.com/golangci/golangci-lint v1.48.0
+	github.com/golangci/golangci-lint v1.47.3
 	github.com/google/go-querystring v1.1.0
 	github.com/hashicorp/go-changelog v0.0.0-20220419201213-5edfc0d651d8
 	github.com/hashicorp/go-retryablehttp v0.7.1
@@ -54,7 +54,6 @@ require (
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/posener/complete v1.2.3 // indirect
 	github.com/russross/blackfriday v1.6.0 // indirect
-	github.com/sashamelentyev/usestdlibvars v1.8.0 // indirect
 	github.com/shopspring/decimal v1.3.1 // indirect
 	github.com/xanzy/ssh-agent v0.3.0 // indirect
 	github.com/zclconf/go-cty v1.10.0 // indirect
@@ -89,7 +88,7 @@ require (
 	github.com/cilium/ebpf v0.7.0 // indirect
 	github.com/cosiner/argv v0.1.0 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
-	github.com/daixiang0/gci v0.6.2 // indirect
+	github.com/daixiang0/gci v0.5.0 // indirect
 	github.com/dave/dst v0.26.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/denis-tingaikin/go-header v0.4.3 // indirect
@@ -121,7 +120,7 @@ require (
 	github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect
 	github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect
 	github.com/golangci/misspell v0.3.5 // indirect
-	github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect
+	github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2 // indirect
 	github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
 	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/google/go-dap v0.6.0 // indirect
@@ -144,7 +143,7 @@ require (
 	github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
 	github.com/julz/importas v0.1.0 // indirect
 	github.com/karrick/godirwalk v1.12.0 // indirect
-	github.com/kisielk/errcheck v1.6.2 // indirect
+	github.com/kisielk/errcheck v1.6.1 // indirect
 	github.com/kisielk/gotool v1.0.0 // indirect
 	github.com/kulti/thelper v0.6.3 // indirect
 	github.com/kunwardeep/paralleltest v1.0.6 // indirect
diff --git a/go.sum b/go.sum
index 6ae87bca1..3c5575203 100644
--- a/go.sum
+++ b/go.sum
@@ -237,8 +237,8 @@ github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/cweill/gotests v1.6.0 h1:KJx+/p4EweijYzqPb4Y/8umDCip1Cv6hEVyOx0mE9W8=
 github.com/cweill/gotests v1.6.0/go.mod h1:CaRYbxQZGQOxXDvM9l0XJVV2Tjb2E5H53vq+reR2GrA=
-github.com/daixiang0/gci v0.6.2 h1:TXCP5RqjE/UupXO+p33MEhqdv7QxjKGw5MVkt9ATiMs=
-github.com/daixiang0/gci v0.6.2/go.mod h1:EpVfrztufwVgQRXjnX4zuNinEpLj5OmMjtu/+MB0V0c=
+github.com/daixiang0/gci v0.5.0 h1:3+Z8nb/4dhJQYjpEbG4wt5na+KFJJTZ++PVEq/MVKX4=
+github.com/daixiang0/gci v0.5.0/go.mod h1:EpVfrztufwVgQRXjnX4zuNinEpLj5OmMjtu/+MB0V0c=
 github.com/dave/dst v0.26.2 h1:lnxLAKI3tx7MgLNVDirFCsDTlTG9nKTk7GcptKcWSwY=
 github.com/dave/dst v0.26.2/go.mod h1:UMDJuIRPfyUCC78eFuB+SV/WI8oDeyFDvM/JR6NI3IU=
 github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ=
@@ -409,16 +409,16 @@ github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6
 github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ=
 github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=
 github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
-github.com/golangci/golangci-lint v1.48.0 h1:hRiBNk9iRqdAKMa06ntfEiLyza1/3IE9rHLNJaek4a8=
-github.com/golangci/golangci-lint v1.48.0/go.mod h1:5N+oxduCho+7yuccW69upg/O7cxjfR/d+IQeiNxGmKM=
+github.com/golangci/golangci-lint v1.47.3 h1:ri7A2DgtFpxgqcMSsU3qIT0IBm/SCdYgXlvmJx4szUU=
+github.com/golangci/golangci-lint v1.47.3/go.mod h1:IvT5xyPX1W8JUJJrV60gcMzgQe1ttW/38yAzn6LuHOk=
 github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA=
 github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
 github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
 github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
 github.com/golangci/misspell v0.3.5 h1:pLzmVdl3VxTOncgzHcvLOKirdvcx/TydsClUQXTehjo=
 github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
-github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 h1:DIPQnGy2Gv2FSA4B/hh8Q7xx3B7AIDk3DAMeHclH1vQ=
-github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6/go.mod h1:0AKcRCkMoKvUvlf89F6O7H2LYdhr1zBh736mBItOdRs=
+github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2 h1:SgM7GDZTxtTTQPU84heOxy34iG5Du7F2jcoZnvp+fXI=
+github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2/go.mod h1:LK+zW4MpyytAWQRz0M4xnzEk50lSvqDQKfx304apFkY=
 github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys=
 github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -691,8 +691,8 @@ github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZsc
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
-github.com/kisielk/errcheck v1.6.2 h1:uGQ9xI8/pgc9iOoCe7kWQgRE6SBTrCGmTSf0LrEtY7c=
-github.com/kisielk/errcheck v1.6.2/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
+github.com/kisielk/errcheck v1.6.1 h1:cErYo+J4SmEjdXZrVXGwLJCE2sB06s23LpkcyWNrT+s=
+github.com/kisielk/errcheck v1.6.1/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
 github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
@@ -978,8 +978,6 @@ github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43
 github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
 github.com/sanposhiho/wastedassign/v2 v2.0.6 h1:+6/hQIHKNJAUixEj6EmOngGIisyeI+T3335lYTyxRoA=
 github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI=
-github.com/sashamelentyev/usestdlibvars v1.8.0 h1:QnWP9IOEuRyYKH+IG0LlQIjuJlc0rfdo4K3/Zh3WRMw=
-github.com/sashamelentyev/usestdlibvars v1.8.0/go.mod h1:BFt7b5mSVHaaa26ZupiNRV2ODViQBxZZVhtAxAJRrjs=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
 github.com/securego/gosec/v2 v2.12.0 h1:CQWdW7ATFpvLSohMVsajscfyHJ5rsGmEXmsNcsDNmAg=
diff --git a/r2_bucket.go b/r2_bucket.go
deleted file mode 100644
index 63cb64a6b..000000000
--- a/r2_bucket.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"net/http"
-)
-
-var (
-	ErrMissingBucketName = errors.New("require bucket name missing")
-)
-
-// CreateR2Bucket Creates a new R2 bucket.
-//
-// API reference: https://api.cloudflare.com/#r2-bucket-create-bucket
-func (api *API) CreateR2Bucket(ctx context.Context, rc *ResourceContainer, bucketName string) error {
-	if rc.Identifier == "" {
-		return ErrMissingAccountID
-	}
-
-	if bucketName == "" {
-		return ErrMissingBucketName
-	}
-
-	uri := fmt.Sprintf("/accounts/%s/r2/buckets/%s", rc.Identifier, bucketName)
-	_, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil)
-
-	return err
-}
-
-// DeleteR2Bucket Deletes an existing R2 bucket.
-//
-// API reference: https://api.cloudflare.com/#r2-bucket-delete-bucket
-func (api *API) DeleteR2Bucket(ctx context.Context, rc *ResourceContainer, bucketName string) error {
-	if rc.Identifier == "" {
-		return ErrMissingAccountID
-	}
-
-	if bucketName == "" {
-		return ErrMissingBucketName
-	}
-
-	uri := fmt.Sprintf("/accounts/%s/r2/buckets/%s", rc.Identifier, bucketName)
-	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
-
-	return err
-}
diff --git a/workers_account_settings.go b/workers_account_settings.go
deleted file mode 100644
index 942db6dd0..000000000
--- a/workers_account_settings.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"encoding/json"
-	"fmt"
-	"net/http"
-)
-
-type WorkersAccountSettings struct {
-	DefaultUsageModel string `json:"default_usage_model,omitempty"`
-	GreenCompute      bool   `json:"green_compute,omitempty"`
-}
-
-type CreateWorkersAccountSettingsParameters struct {
-	DefaultUsageModel string `json:"default_usage_model,omitempty"`
-	GreenCompute      bool   `json:"green_compute,omitempty"`
-}
-
-type CreateWorkersAccountSettingsResponse struct {
-	Response
-	Result WorkersAccountSettings
-}
-
-type WorkersAccountSettingsParameters struct{}
-
-type WorkersAccountSettingsResponse struct {
-	Response
-	Result WorkersAccountSettings
-}
-
-// CreateWorkersAccountSettings sets the account settings for Workers.
-//
-// API reference: https://api.cloudflare.com/#worker-account-settings-create-worker-account-settings
-func (api *API) CreateWorkersAccountSettings(ctx context.Context, rc *ResourceContainer, params CreateWorkersAccountSettingsParameters) (WorkersAccountSettings, error) {
-	if rc.Identifier == "" {
-		return WorkersAccountSettings{}, ErrMissingAccountID
-	}
-
-	uri := fmt.Sprintf("/accounts/%s/workers/account-settings", rc.Identifier)
-	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params)
-	if err != nil {
-		return WorkersAccountSettings{}, err
-	}
-
-	var workersAccountSettingsResponse CreateWorkersAccountSettingsResponse
-	if err := json.Unmarshal(res, &workersAccountSettingsResponse); err != nil {
-		return WorkersAccountSettings{}, err
-	}
-
-	return workersAccountSettingsResponse.Result, nil
-}
-
-// WorkersAccountSettings returns the current account settings for Workers.
-//
-// API reference: https://api.cloudflare.com/#worker-account-settings-fetch-worker-account-settings
-func (api *API) WorkersAccountSettings(ctx context.Context, rc *ResourceContainer, params WorkersAccountSettingsParameters) (WorkersAccountSettings, error) {
-	if rc.Identifier == "" {
-		return WorkersAccountSettings{}, ErrMissingAccountID
-	}
-
-	uri := fmt.Sprintf("/accounts/%s/workers/account-settings", rc.Identifier)
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, params)
-	if err != nil {
-		return WorkersAccountSettings{}, err
-	}
-
-	var workersAccountSettingsResponse CreateWorkersAccountSettingsResponse
-	if err := json.Unmarshal(res, &workersAccountSettingsResponse); err != nil {
-		return WorkersAccountSettings{}, err
-	}
-
-	return workersAccountSettingsResponse.Result, nil
-}
diff --git a/workers_tail.go b/workers_tail.go
deleted file mode 100644
index 672f82191..000000000
--- a/workers_tail.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"net/http"
-	"time"
-)
-
-var (
-	ErrMissingScriptName = errors.New("required script name missing")
-	ErrMissingTailID     = errors.New("required tail id missing")
-)
-
-type WorkersTail struct {
-	ID        string     `json:"id"`
-	URL       string     `json:"url"`
-	ExpiresAt *time.Time `json:"expires_at"`
-}
-
-type StartWorkersTailResponse struct {
-	Response
-	Result WorkersTail
-}
-
-type ListWorkersTailParameters struct {
-	AccountID  string
-	ScriptName string
-}
-
-type ListWorkersTailResponse struct {
-	Response
-	Result WorkersTail
-}
-
-// StartWorkersTail Starts a tail that receives logs and exception from a Worker
-//
-// API reference: https://api.cloudflare.com/#worker-tail-logs-start-tail
-func (api *API) StartWorkersTail(ctx context.Context, rc *ResourceContainer, scriptName string) (WorkersTail, error) {
-	if rc.Identifier == "" {
-		return WorkersTail{}, ErrMissingAccountID
-	}
-	if scriptName == "" {
-		return WorkersTail{}, ErrMissingScriptName
-	}
-
-	uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/tails", rc.Identifier, scriptName)
-	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil)
-	if err != nil {
-		return WorkersTail{}, err
-	}
-
-	var workerstailResponse StartWorkersTailResponse
-	if err := json.Unmarshal(res, &workerstailResponse); err != nil {
-		return WorkersTail{}, err
-	}
-
-	return workerstailResponse.Result, nil
-}
-
-// ListWorkersTail Get list of tails currently deployed on a worker
-//
-// API reference: https://api.cloudflare.com/#worker-tail-logs-list-tails
-func (api *API) ListWorkersTail(ctx context.Context, rc *ResourceContainer, params ListWorkersTailParameters) (WorkersTail, error) {
-	if rc.Identifier == "" {
-		return WorkersTail{}, ErrMissingAccountID
-	}
-
-	if params.ScriptName == "" {
-		return WorkersTail{}, ErrMissingScriptName
-	}
-
-	uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/tails", rc.Identifier, params.ScriptName)
-	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
-	if err != nil {
-		return WorkersTail{}, err
-	}
-
-	var workerstailResponse ListWorkersTailResponse
-	if err := json.Unmarshal(res, &workerstailResponse); err != nil {
-		return WorkersTail{}, err
-	}
-
-	return workerstailResponse.Result, nil
-}
-
-// DeleteWorkersTail Deletes a tail from a Worker
-//
-// API reference: https://api.cloudflare.com/#worker-tail-logs-delete-tail
-func (api *API) DeleteWorkersTail(ctx context.Context, rc *ResourceContainer, scriptName, tailID string) error {
-	if rc.Identifier == "" {
-		return ErrMissingAccountID
-	}
-
-	if scriptName == "" {
-		return ErrMissingScriptName
-	}
-
-	if tailID == "" {
-		return ErrMissingTailID
-	}
-
-	uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/tails/%s", rc.Identifier, scriptName, tailID)
-	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index a7cfdd13f..5c307228f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -4,7 +4,7 @@ jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.17, 1.18, 1.19]
+        go-version: [1.17, 1.18]
         os: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
diff --git a/filter_test.go b/filter_test.go
index 3dda4adda..425801422 100644
--- a/filter_test.go
+++ b/filter_test.go
@@ -157,6 +157,15 @@ func TestCreateSingleFilter(t *testing.T) {
 	}
 
 	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/filters", handler)
+	params := []FilterCreateParams{
+		{
+			ID:          "b7ff25282d394be7b945e23c7106ce8a",
+			Paused:      false,
+			Description: "Login from office",
+			Expression:  "ip.src eq 127.0.0.1",
+		},
+	}
+
 	want := []Filter{
 		{
 			ID:          "b7ff25282d394be7b945e23c7106ce8a",
@@ -166,7 +175,7 @@ func TestCreateSingleFilter(t *testing.T) {
 		},
 	}
 
-	actual, err := client.CreateFilters(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), want)
+	actual, err := client.CreateFilters(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), params)
 
 	if assert.NoError(t, err) {
 		assert.Equal(t, want, actual)
@@ -203,6 +212,21 @@ func TestCreateMultipleFilters(t *testing.T) {
 	}
 
 	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/filters", handler)
+	params := []FilterCreateParams{
+		{
+			ID:          "b7ff25282d394be7b945e23c7106ce8a",
+			Paused:      false,
+			Description: "Login from office",
+			Expression:  "ip.src eq 127.0.0.1",
+		},
+		{
+			ID:          "b7ff25282d394be7b945e23c7106ce8a",
+			Paused:      false,
+			Description: "Login from second office",
+			Expression:  "ip.src eq 10.0.0.1",
+		},
+	}
+
 	want := []Filter{
 		{
 			ID:          "b7ff25282d394be7b945e23c7106ce8a",
@@ -218,7 +242,7 @@ func TestCreateMultipleFilters(t *testing.T) {
 		},
 	}
 
-	actual, err := client.CreateFilters(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), want)
+	actual, err := client.CreateFilters(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), params)
 
 	if assert.NoError(t, err) {
 		assert.Equal(t, want, actual)
@@ -247,6 +271,13 @@ func TestUpdateSingleFilter(t *testing.T) {
 	}
 
 	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/filters/60ee852f9cbb4802978d15600c7f3110", handler)
+	params := FilterUpdateParams{
+		ID:          "60ee852f9cbb4802978d15600c7f3110",
+		Paused:      false,
+		Description: "IP of example.org",
+		Expression:  "ip.src eq 93.184.216.0",
+	}
+
 	want := Filter{
 		ID:          "60ee852f9cbb4802978d15600c7f3110",
 		Paused:      false,
@@ -254,7 +285,7 @@ func TestUpdateSingleFilter(t *testing.T) {
 		Expression:  "ip.src eq 93.184.216.0",
 	}
 
-	actual, err := client.UpdateFilter(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), want)
+	actual, err := client.UpdateFilter(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), params)
 
 	if assert.NoError(t, err) {
 		assert.Equal(t, want, actual)
@@ -291,6 +322,21 @@ func TestUpdateMultipleFilters(t *testing.T) {
 	}
 
 	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/filters", handler)
+	params := []FilterUpdateParams{
+		{
+			ID:          "60ee852f9cbb4802978d15600c7f3110",
+			Paused:      false,
+			Description: "IP of example.org",
+			Expression:  "ip.src eq 93.184.216.0",
+		},
+		{
+			ID:          "c218c536b2bd406f958f278cf0fa8c0f",
+			Paused:      false,
+			Description: "IP of example.com",
+			Expression:  "ip.src ne 127.0.0.1",
+		},
+	}
+
 	want := []Filter{
 		{
 			ID:          "60ee852f9cbb4802978d15600c7f3110",
@@ -306,7 +352,7 @@ func TestUpdateMultipleFilters(t *testing.T) {
 		},
 	}
 
-	actual, err := client.UpdateFilters(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), want)
+	actual, err := client.UpdateFilters(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), params)
 
 	if assert.NoError(t, err) {
 		assert.Equal(t, want, actual)
diff --git a/firewall_rules_test.go b/firewall_rules_test.go
index 76c2b1c7d..9af55e41c 100644
--- a/firewall_rules_test.go
+++ b/firewall_rules_test.go
@@ -9,11 +9,6 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-var firewallRulePageOpts = PaginationOptions{
-	PerPage: 25,
-	Page:    1,
-}
-
 func TestFirewallRules(t *testing.T) {
 	setup()
 	defer teardown()
@@ -261,6 +256,22 @@ func TestCreateSingleFirewallRule(t *testing.T) {
 	}
 
 	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/firewall/rules", handler)
+	params := []FirewallRuleCreateParams{
+		{
+			ID:          "f2d427378e7542acb295380d352e2ebd",
+			Paused:      false,
+			Description: "do not challenge login from office",
+			Action:      "allow",
+			Priority:    nil,
+			Filter: Filter{
+				ID:          "b7ff25282d394be7b945e23c7106ce8a",
+				Expression:  "ip.src in {127.0.0.0/24}",
+				Paused:      false,
+				Description: "Login from office",
+			},
+		},
+	}
+
 	want := []FirewallRule{
 		{
 			ID:          "f2d427378e7542acb295380d352e2ebd",
@@ -277,7 +288,7 @@ func TestCreateSingleFirewallRule(t *testing.T) {
 		},
 	}
 
-	actual, err := client.CreateFirewallRules(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), want)
+	actual, err := client.CreateFirewallRules(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), params)
 
 	if assert.NoError(t, err) {
 		assert.Equal(t, want, actual)
@@ -328,6 +339,35 @@ func TestCreateMultipleFirewallRules(t *testing.T) {
 	}
 
 	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/firewall/rules", handler)
+	params := []FirewallRuleCreateParams{
+		{
+			ID:          "f2d427378e7542acb295380d352e2ebd",
+			Paused:      false,
+			Description: "do not challenge login from office",
+			Action:      "allow",
+			Priority:    nil,
+			Filter: Filter{
+				ID:          "b7ff25282d394be7b945e23c7106ce8a",
+				Expression:  "ip.src in {127.0.0.0/24}",
+				Paused:      false,
+				Description: "Login from office",
+			},
+		},
+		{
+			ID:          "cbf4b7a5a2a24e59a03044d6d44ceb09",
+			Paused:      false,
+			Description: "challenge login",
+			Action:      "challenge",
+			Priority:    nil,
+			Filter: Filter{
+				ID:          "c218c536b2bd406f958f278cf0fa8c0f",
+				Expression:  "(http.request.uri.path ~ \"^.*/wp-login.php$\")",
+				Paused:      false,
+				Description: "Login",
+			},
+		},
+	}
+
 	want := []FirewallRule{
 		{
 			ID:          "f2d427378e7542acb295380d352e2ebd",
@@ -357,7 +397,7 @@ func TestCreateMultipleFirewallRules(t *testing.T) {
 		},
 	}
 
-	actual, err := client.CreateFirewallRules(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), want)
+	actual, err := client.CreateFirewallRules(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), params)
 
 	if assert.NoError(t, err) {
 		assert.Equal(t, want, actual)
@@ -368,7 +408,7 @@ func TestUpdateFirewallRuleWithMissingID(t *testing.T) {
 	setup()
 	defer teardown()
 
-	want := FirewallRule{
+	want := FirewallRuleUpdateParams{
 		ID:          "",
 		Paused:      false,
 		Description: "challenge site",
@@ -415,6 +455,20 @@ func TestUpdateSingleFirewallRule(t *testing.T) {
 	}
 
 	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/firewall/rules/52161eb6af4241bb9d4b32394be72fdf", handler)
+	params := FirewallRuleUpdateParams{
+		ID:          "52161eb6af4241bb9d4b32394be72fdf",
+		Paused:      false,
+		Description: "challenge site",
+		Action:      "challenge",
+		Priority:    nil,
+		Filter: Filter{
+			ID:          "f2a64520581a4209aab12187a0081364",
+			Expression:  "not http.request.uri.path matches \"^/api/.*$\"",
+			Paused:      false,
+			Description: "not /api",
+		},
+	}
+
 	want := FirewallRule{
 		ID:          "52161eb6af4241bb9d4b32394be72fdf",
 		Paused:      false,
@@ -429,7 +483,7 @@ func TestUpdateSingleFirewallRule(t *testing.T) {
 		},
 	}
 
-	actual, err := client.UpdateFirewallRule(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), want)
+	actual, err := client.UpdateFirewallRule(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), params)
 
 	if assert.NoError(t, err) {
 		assert.Equal(t, want, actual)
@@ -480,6 +534,35 @@ func TestUpdateMultipleFirewallRules(t *testing.T) {
 	}
 
 	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/firewall/rules", handler)
+	params := []FirewallRuleUpdateParams{
+		{
+			ID:          "f2d427378e7542acb295380d352e2ebd",
+			Paused:      false,
+			Description: "do not challenge login from office",
+			Action:      "allow",
+			Priority:    nil,
+			Filter: Filter{
+				ID:          "b7ff25282d394be7b945e23c7106ce8a",
+				Expression:  "ip.src in {127.0.0.0/24}",
+				Paused:      false,
+				Description: "Login from office",
+			},
+		},
+		{
+			ID:          "cbf4b7a5a2a24e59a03044d6d44ceb09",
+			Paused:      false,
+			Description: "challenge login",
+			Action:      "challenge",
+			Priority:    nil,
+			Filter: Filter{
+				ID:          "c218c536b2bd406f958f278cf0fa8c0f",
+				Expression:  "(http.request.uri.path ~ \"^.*/wp-login.php$\")",
+				Paused:      false,
+				Description: "Login",
+			},
+		},
+	}
+
 	want := []FirewallRule{
 		{
 			ID:          "f2d427378e7542acb295380d352e2ebd",
@@ -509,7 +592,7 @@ func TestUpdateMultipleFirewallRules(t *testing.T) {
 		},
 	}
 
-	actual, err := client.UpdateFirewallRules(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), want)
+	actual, err := client.UpdateFirewallRules(context.Background(), ZoneIdentifier("d56084adb405e0b7e32c52321bf07be6"), params)
 
 	if assert.NoError(t, err) {
 		assert.Equal(t, want, actual)
diff --git a/r2_bucket_test.go b/r2_bucket_test.go
deleted file mode 100644
index 227f36e9c..000000000
--- a/r2_bucket_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-const testBucketName = "example-bucket"
-
-func TestR2_CreateBucket(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc(fmt.Sprintf("/accounts/%s/r2/buckets/%s", testAccountID, testBucketName), func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-  "success": true,
-  "errors": [],
-  "messages": [],
-  "result": {}
-}`)
-	})
-
-	err := client.CreateR2Bucket(context.Background(), AccountIdentifier(""), "")
-	if assert.Error(t, err) {
-		assert.Equal(t, ErrMissingAccountID, err)
-	}
-
-	err = client.CreateR2Bucket(context.Background(), AccountIdentifier(testAccountID), "")
-	if assert.Error(t, err) {
-		assert.Equal(t, ErrMissingBucketName, err)
-	}
-
-	err = client.CreateR2Bucket(context.Background(), AccountIdentifier(testAccountID), "example-bucket")
-	assert.NoError(t, err)
-}
-
-func TestR2_DeleteBucket(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc(fmt.Sprintf("/accounts/%s/r2/buckets/%s", testAccountID, testBucketName), func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-  "success": true,
-  "errors": [],
-  "messages": [],
-  "result": {}
-}`)
-	})
-
-	err := client.DeleteR2Bucket(context.Background(), AccountIdentifier(""), "")
-	if assert.Error(t, err) {
-		assert.Equal(t, ErrMissingAccountID, err)
-	}
-
-	err = client.DeleteR2Bucket(context.Background(), AccountIdentifier(testAccountID), "")
-	if assert.Error(t, err) {
-		assert.Equal(t, ErrMissingBucketName, err)
-	}
-
-	err = client.DeleteR2Bucket(context.Background(), AccountIdentifier(testAccountID), "example-bucket")
-	assert.NoError(t, err)
-}
diff --git a/workers_account_settings_test.go b/workers_account_settings_test.go
deleted file mode 100644
index 0bdd35fd3..000000000
--- a/workers_account_settings_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestWorkersAccountSettings_CreateAccountSettings(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc(fmt.Sprintf("/accounts/%s/workers/account-settings", testAccountID), func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-  "success": true,
-  "errors": [],
-  "messages": [],
-  "result": {
-    "default_usage_model": "unbound",
-	"green_compute": false
-  }
-}`)
-	})
-	res, err := client.CreateWorkersAccountSettings(context.Background(), AccountIdentifier(testAccountID), CreateWorkersAccountSettingsParameters{DefaultUsageModel: "unbound", GreenCompute: false})
-	want := WorkersAccountSettings{
-		DefaultUsageModel: "unbound",
-		GreenCompute:      false,
-	}
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, want, res)
-	}
-}
-
-func TestWorkersAccountSettings_GetAccountSettings(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc(fmt.Sprintf("/accounts/%s/workers/account-settings", testAccountID), func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-  "success": true,
-  "errors": [],
-  "messages": [],
-  "result": {
-    "default_usage_model": "unbound",
-	"green_compute": true
-  }
-}`)
-	})
-
-	res, err := client.WorkersAccountSettings(context.Background(), AccountIdentifier(testAccountID), WorkersAccountSettingsParameters{})
-	want := WorkersAccountSettings{
-		DefaultUsageModel: "unbound",
-		GreenCompute:      true,
-	}
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, want, res)
-	}
-}
diff --git a/workers_tail_test.go b/workers_tail_test.go
deleted file mode 100644
index ed57b4d80..000000000
--- a/workers_tail_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-package cloudflare
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/assert"
-)
-
-const (
-	testScriptName = "this-is_my_script-01"
-	testTailID     = "03dc9f77817b488fb26c5861ec18f791"
-)
-
-func TestWorkersTail_StartWorkersTail(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc(fmt.Sprintf("/accounts/%s/workers/scripts/%s/tails", testAccountID, testScriptName), func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-  "success": true,
-  "errors": [],
-  "messages": [],
-  "result": {
-    "id": "03dc9f77817b488fb26c5861ec18f791",
-    "url": "wss://tail.developers.workers.dev/03dc9f77817b488fb26c5861ec18f791",
-    "expires_at": "2021-08-20T19:15:51Z"
-  }
-}`)
-	})
-
-	_, err := client.StartWorkersTail(context.Background(), AccountIdentifier(testAccountID), "")
-	if assert.Error(t, err) {
-		assert.Equal(t, ErrMissingScriptName, err)
-	}
-
-	res, err := client.StartWorkersTail(context.Background(), AccountIdentifier(testAccountID), testScriptName)
-	expiresAt, _ := time.Parse(time.RFC3339, "2021-08-20T19:15:51Z")
-	want := WorkersTail{
-		ID:        "03dc9f77817b488fb26c5861ec18f791",
-		URL:       "wss://tail.developers.workers.dev/03dc9f77817b488fb26c5861ec18f791",
-		ExpiresAt: &expiresAt,
-	}
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, want, res)
-	}
-}
-
-func TestWorkersTail_ListWorkersTail(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc(fmt.Sprintf("/accounts/%s/workers/scripts/%s/tails", testAccountID, testScriptName), func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-  "success": true,
-  "errors": [],
-  "messages": [],
-  "result": {
-    "id": "03dc9f77817b488fb26c5861ec18f791",
-    "url": "wss://tail.developers.workers.dev/03dc9f77817b488fb26c5861ec18f791",
-    "expires_at": "2021-08-20T19:15:51Z"
-  }
-}`)
-	})
-
-	_, err := client.ListWorkersTail(context.Background(), AccountIdentifier(testAccountID), ListWorkersTailParameters{ScriptName: ""})
-	if assert.Error(t, err) {
-		assert.Equal(t, ErrMissingScriptName, err)
-	}
-
-	res, err := client.ListWorkersTail(context.Background(), AccountIdentifier(testAccountID), ListWorkersTailParameters{ScriptName: testScriptName})
-	expiresAt, _ := time.Parse(time.RFC3339, "2021-08-20T19:15:51Z")
-	want := WorkersTail{
-		ID:        "03dc9f77817b488fb26c5861ec18f791",
-		URL:       "wss://tail.developers.workers.dev/03dc9f77817b488fb26c5861ec18f791",
-		ExpiresAt: &expiresAt,
-	}
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, want, res)
-	}
-}
-
-func TestWorkersTail_DeleteWorkersTail(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc(fmt.Sprintf("/accounts/%s/workers/scripts/%s/tails/%s", testAccountID, testScriptName, testTailID), func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'POST', got %s", r.Method)
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-  "success": true,
-  "errors": [],
-  "messages": [],
-}`)
-	})
-
-	err := client.DeleteWorkersTail(context.Background(), AccountIdentifier(testAccountID), "", "")
-	if assert.Error(t, err) {
-		assert.Equal(t, ErrMissingScriptName, err)
-	}
-
-	err = client.DeleteWorkersTail(context.Background(), AccountIdentifier(testAccountID), testScriptName, "")
-	if assert.Error(t, err) {
-		assert.Equal(t, ErrMissingTailID, err)
-	}
-
-	err = client.DeleteWorkersTail(context.Background(), AccountIdentifier(testAccountID), testScriptName, testTailID)
-	assert.NoError(t, err)
-}