Repository: cloudflare/cloudflare-go
Pull request #1102 (2022-10-07 20:41:17)

Add waiting room rules
## Description

Add methods for the management of Waiting Room Rules.

## Has your change been tested?

Unit tests have been created to confirm JSON marshaling works and the correct endpoints/methods are called.

## Types of changes

What sort of change does your code introduce/modify?

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] 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.
  - https://api.cloudflare.com/#waiting-room-list-waiting-room-rules


Patch:
diff --git a/.changelog/1102.txt b/.changelog/1102.txt
new file mode 100644
index 000000000..4a7d41aae
--- /dev/null
+++ b/.changelog/1102.txt
@@ -0,0 +1,3 @@
+```release-note:enhancement
+waiting_room: add support for waiting room rules
+```
\ No newline at end of file
diff --git a/waiting_room.go b/waiting_room.go
index e3e400e69..a60b5028e 100644
--- a/waiting_room.go
+++ b/waiting_room.go
@@ -3,11 +3,17 @@ package cloudflare
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"net/http"
 	"time"
 )
 
+var (
+	ErrMissingWaitingRoomID     = errors.New("missing required waiting room ID")
+	ErrMissingWaitingRoomRuleID = errors.New("missing required waiting room rule ID")
+)
+
 // WaitingRoom describes a WaitingRoom object.
 type WaitingRoom struct {
 	CreatedOn                  time.Time  `json:"created_on,omitempty"`
@@ -60,6 +66,16 @@ type WaitingRoomEvent struct {
 	ShuffleAtEventStart   bool       `json:"shuffle_at_event_start"`
 }
 
+type WaitingRoomRule struct {
+	ID          string     `json:"id,omitempty"`
+	Version     string     `json:"version,omitempty"`
+	Action      string     `json:"action"`
+	Expression  string     `json:"expression"`
+	Description string     `json:"description"`
+	LastUpdated *time.Time `json:"last_updated,omitempty"`
+	Enabled     *bool      `json:"enabled"`
+}
+
 // WaitingRoomPagePreviewURL describes a WaitingRoomPagePreviewURL object.
 type WaitingRoomPagePreviewURL struct {
 	PreviewURL string `json:"preview_url"`
@@ -106,6 +122,12 @@ type WaitingRoomEventsResponse struct {
 	Result []WaitingRoomEvent `json:"result"`
 }
 
+// WaitingRoomRulesResponse is the API response, containing an array of WaitingRoomRule.
+type WaitingRoomRulesResponse struct {
+	Response
+	Result []WaitingRoomRule `json:"result"`
+}
+
 // CreateWaitingRoom creates a new Waiting Room for a zone.
 //
 // API reference: https://api.cloudflare.com/#waiting-room-create-waiting-room
@@ -367,3 +389,146 @@ func (api *API) DeleteWaitingRoomEvent(ctx context.Context, zoneID string, waiti
 	}
 	return nil
 }
+
+type ListWaitingRoomRuleParams struct {
+	WaitingRoomID string
+}
+
+type CreateWaitingRoomRuleParams struct {
+	WaitingRoomID string
+	Rule          WaitingRoomRule
+}
+
+type ReplaceWaitingRoomRuleParams struct {
+	WaitingRoomID string
+	Rules         []WaitingRoomRule
+}
+
+type UpdateWaitingRoomRuleParams struct {
+	WaitingRoomID string
+	Rule          WaitingRoomRule
+}
+
+type DeleteWaitingRoomRuleParams struct {
+	WaitingRoomID string
+	RuleID        string
+}
+
+// ListWaitingRoomRules lists all rules for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-list-waiting-room-rules
+func (api *API) ListWaitingRoomRules(ctx context.Context, rc *ResourceContainer, params ListWaitingRoomRuleParams) ([]WaitingRoomRule, error) {
+	if params.WaitingRoomID == "" {
+		return nil, ErrMissingWaitingRoomID
+	}
+
+	uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules", rc.Identifier, params.WaitingRoomID)
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var r WaitingRoomRulesResponse
+	err = json.Unmarshal(res, &r)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r.Result, nil
+}
+
+// CreateWaitingRoomRule creates a new rule for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-create-waiting-room-rule
+func (api *API) CreateWaitingRoomRule(ctx context.Context, rc *ResourceContainer, params CreateWaitingRoomRuleParams) ([]WaitingRoomRule, error) {
+	if params.WaitingRoomID == "" {
+		return nil, ErrMissingWaitingRoomID
+	}
+
+	uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules", rc.Identifier, params.WaitingRoomID)
+	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params.Rule)
+	if err != nil {
+		return nil, err
+	}
+
+	var r WaitingRoomRulesResponse
+	err = json.Unmarshal(res, &r)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r.Result, nil
+}
+
+// ReplaceWaitingRoomRules replaces all rules for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-replace-waiting-room-rules
+func (api *API) ReplaceWaitingRoomRules(ctx context.Context, rc *ResourceContainer, params ReplaceWaitingRoomRuleParams) ([]WaitingRoomRule, error) {
+	if params.WaitingRoomID == "" {
+		return nil, ErrMissingWaitingRoomID
+	}
+
+	uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules", rc.Identifier, params.WaitingRoomID)
+	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params.Rules)
+	if err != nil {
+		return nil, err
+	}
+
+	var r WaitingRoomRulesResponse
+	err = json.Unmarshal(res, &r)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r.Result, nil
+}
+
+// UpdateWaitingRoomRule updates a rule for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-patch-waiting-room-rule
+func (api *API) UpdateWaitingRoomRule(ctx context.Context, rc *ResourceContainer, params UpdateWaitingRoomRuleParams) ([]WaitingRoomRule, error) {
+	if params.WaitingRoomID == "" {
+		return nil, ErrMissingWaitingRoomID
+	}
+
+	uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules/%s", rc.Identifier, params.WaitingRoomID, params.Rule.ID)
+	res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params.Rule)
+	if err != nil {
+		return nil, err
+	}
+
+	var r WaitingRoomRulesResponse
+	err = json.Unmarshal(res, &r)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r.Result, nil
+}
+
+// DeleteWaitingRoomRule deletes a rule for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-delete-waiting-room-rule
+func (api *API) DeleteWaitingRoomRule(ctx context.Context, rc *ResourceContainer, params DeleteWaitingRoomRuleParams) ([]WaitingRoomRule, error) {
+	if params.WaitingRoomID == "" {
+		return nil, ErrMissingWaitingRoomID
+	}
+
+	if params.RuleID == "" {
+		return nil, ErrMissingWaitingRoomRuleID
+	}
+
+	uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules/%s", rc.Identifier, params.WaitingRoomID, params.RuleID)
+	res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var r WaitingRoomRulesResponse
+	err = json.Unmarshal(res, &r)
+	if err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r.Result, nil
+}


Test patch:
diff --git a/waiting_room_test.go b/waiting_room_test.go
index 0018e3f42..902510305 100644
--- a/waiting_room_test.go
+++ b/waiting_room_test.go
@@ -13,6 +13,7 @@ import (
 
 var waitingRoomID = "699d98642c564d2e855e9661899b7252"
 var waitingRoomEventID = "25756b2dfe6e378a06b033b670413757"
+var waitingRoomRuleID = "25756b2dfe6e378a06b033b670413757"
 var testTimestampWaitingRoom = time.Now().UTC()
 var testTimestampWaitingRoomEvent = time.Now().UTC()
 var testTimestampWaitingRoomEventPrequeue = time.Now().UTC()
@@ -78,6 +79,18 @@ var waitingRoomStatusJSON = fmt.Sprintf(`
     }
    `, waitingRoomEventID)
 
+var waitingRoomRuleJSON = fmt.Sprintf(`
+{
+  "id": "%s",
+  "version": "1",
+  "description": "bypass ip",
+  "action": "bypass_waiting_room",
+  "expression": "ip.src in {1.2.3.4 5.6.7.8}",
+  "enabled": true,
+  "last_updated": "%s"
+}
+`, waitingRoomRuleID, testTimestampWaitingRoom.Format(time.RFC3339Nano))
+
 var waitingRoomPagePreviewJSON = `
     {
       "preview_url": "http://waitingrooms.dev/preview/35af8c12-6d68-4608-babb-b53435a5ddfb"
@@ -137,6 +150,16 @@ var waitingRoomPagePreview = WaitingRoomPagePreviewURL{
 	PreviewURL: "http://waitingrooms.dev/preview/35af8c12-6d68-4608-babb-b53435a5ddfb",
 }
 
+var waitingRoomRule = WaitingRoomRule{
+	ID:          waitingRoomRuleID,
+	Version:     "1",
+	Action:      "bypass_waiting_room",
+	Expression:  "ip.src in {1.2.3.4 5.6.7.8}",
+	Description: "bypass ip",
+	Enabled:     BoolPtr(true),
+	LastUpdated: &testTimestampWaitingRoom,
+}
+
 func TestListWaitingRooms(t *testing.T) {
 	setup()
 	defer teardown()
@@ -617,3 +640,148 @@ func TestDeleteWaitingRoomEvent(t *testing.T) {
 	err := client.DeleteWaitingRoomEvent(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", "25756b2dfe6e378a06b033b670413757")
 	assert.NoError(t, err)
 }
+
+func TestListWaitingRoomRules(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := 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": [
+			    %s
+			  ]
+			}
+		`, waitingRoomRuleJSON)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/rules", handler)
+	want := []WaitingRoomRule{waitingRoomRule}
+
+	actual, err := client.ListWaitingRoomRules(context.Background(), ZoneIdentifier(testZoneID), ListWaitingRoomRuleParams{WaitingRoomID: "699d98642c564d2e855e9661899b7252"})
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestCreateWaitingRoomRule(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := 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": [
+			    %s
+			  ]
+			}
+		`, waitingRoomRuleJSON)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/rules", handler)
+	want := []WaitingRoomRule{waitingRoomRule}
+
+	actual, err := client.CreateWaitingRoomRule(context.Background(), ZoneIdentifier(testZoneID), CreateWaitingRoomRuleParams{
+		WaitingRoomID: "699d98642c564d2e855e9661899b7252",
+		Rule:          waitingRoomRule,
+	})
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestUpdateWaitingRoomRule(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodPatch, r.Method, "Expected method 'PATCH', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			  "success": true,
+			  "errors": [],
+			  "messages": [],
+			  "result": [
+			    %s
+			  ]
+			}
+		`, waitingRoomRuleJSON)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/rules/"+waitingRoomRuleID, handler)
+	want := []WaitingRoomRule{waitingRoomRule}
+
+	actual, err := client.UpdateWaitingRoomRule(context.Background(), ZoneIdentifier(testZoneID), UpdateWaitingRoomRuleParams{
+		WaitingRoomID: "699d98642c564d2e855e9661899b7252",
+		Rule:          waitingRoomRule,
+	})
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestDeleteWaitingRoomRule(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := 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.Fprint(w, `{
+			  "success": true,
+			  "errors": [],
+			  "messages": [],
+			  "result": []
+			}
+		`)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/rules/"+waitingRoomRuleID, handler)
+	want := []WaitingRoomRule{}
+
+	actual, err := client.DeleteWaitingRoomRule(context.Background(), ZoneIdentifier(testZoneID), DeleteWaitingRoomRuleParams{
+		WaitingRoomID: "699d98642c564d2e855e9661899b7252",
+		RuleID:        waitingRoomRuleID,
+	})
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestReplaceWaitingRoomRules(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := 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": [
+			    %s
+			  ]
+			}
+		`, waitingRoomRuleJSON)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/rules", handler)
+	want := []WaitingRoomRule{waitingRoomRule}
+
+	actual, err := client.ReplaceWaitingRoomRules(context.Background(), ZoneIdentifier(testZoneID), ReplaceWaitingRoomRuleParams{
+		WaitingRoomID: "699d98642c564d2e855e9661899b7252",
+		Rules:         want,
+	})
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}