Repository: cloudflare/cloudflare-go
Pull request #3458 (2024-10-21 16:02:01)

FLPROD-796: Create snippets api client
Adding client for snippets API to use in v4 terraform with tests


jacobbednarz: (ignore the changelog check, it's fine)

Patch:
diff --git a/.changelog/3458.txt b/.changelog/3458.txt
new file mode 100644
index 000000000..5d5e74468
--- /dev/null
+++ b/.changelog/3458.txt
@@ -0,0 +1,7 @@
+```release-note:enhancement
+snippets: add CRUD support
+```
+
+```release-note:enhancement
+snippets_rules: add CRUD support
+```
diff --git a/snippets.go b/snippets.go
new file mode 100644
index 000000000..870b8f73a
--- /dev/null
+++ b/snippets.go
@@ -0,0 +1,114 @@
+package cloudflare
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"mime/multipart"
+	"net/http"
+	"time"
+
+	"github.com/goccy/go-json"
+)
+
+type SnippetsResponse struct {
+	Response
+	Result []Snippet `json:"result"`
+}
+
+type Snippet struct {
+	CreatedOn   *time.Time `json:"created_on"`
+	ModifiedOn  *time.Time `json:"modified_on"`
+	SnippetName string     `json:"snippet_name"`
+}
+
+type SnippetFile struct {
+	FileName string `json:"file_name"`
+	Content  string `json:"content"`
+}
+
+type SnippetRequest struct {
+	SnippetName string        `json:"snippet_name"`
+	MainFile    string        `json:"main_file"`
+	Files       []SnippetFile `json:"files"`
+}
+
+func (api *API) ListZoneSnippets(ctx context.Context, rc *ResourceContainer) ([]Snippet, error) {
+	if rc.Identifier == "" {
+		return nil, ErrMissingZoneID
+	}
+
+	uri := buildURI(fmt.Sprintf("/zones/%s/snippets", rc.Identifier), nil)
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	result := SnippetsResponse{}
+	if err := json.Unmarshal(res, &result); err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return result.Result, nil
+}
+
+type SnippetMetadata struct {
+	MainModule string `json:"main_module"`
+}
+
+func snippetMultipartBody(request SnippetRequest) (string, *bytes.Buffer, error) {
+	body := new(bytes.Buffer)
+	mw := multipart.NewWriter(body)
+	defer mw.Close()
+
+	for _, file := range request.Files {
+		tp, err := mw.CreateFormFile(file.FileName, file.FileName)
+		if err != nil {
+			return "", nil, err
+		}
+		_, err = tp.Write([]byte(file.Content))
+		if err != nil {
+			return "", nil, err
+		}
+	}
+
+	tp, err := mw.CreateFormField("metadata")
+	if err != nil {
+		return "", nil, err
+	}
+
+	if err = json.NewEncoder(tp).Encode(SnippetMetadata{
+		MainModule: request.MainFile,
+	}); err != nil {
+		return "", nil, err
+	}
+
+	return mw.Boundary(), body, nil
+}
+
+func (api *API) UpdateZoneSnippet(ctx context.Context, rc *ResourceContainer, params SnippetRequest) ([]Snippet, error) {
+	if rc.Identifier == "" {
+		return nil, ErrMissingZoneID
+	}
+
+	uri := fmt.Sprintf("/zones/%s/snippets/%s", rc.Identifier, params.SnippetName)
+
+	boundary, body, err := snippetMultipartBody(params)
+	if err != nil {
+		return nil, err
+	}
+
+	res, err := api.makeRequestContextWithHeaders(ctx, http.MethodPut, uri, body, http.Header{
+		"Content-Type": []string{fmt.Sprintf("multipart/form-data; boundary=%s", boundary)},
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	result := SnippetsResponse{}
+	if err := json.Unmarshal(res, &result); err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return result.Result, nil
+}
diff --git a/snippets_rules.go b/snippets_rules.go
new file mode 100644
index 000000000..37a92d212
--- /dev/null
+++ b/snippets_rules.go
@@ -0,0 +1,66 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/goccy/go-json"
+)
+
+type SnippetsRulesResponse struct {
+	Response
+	Result []SnippetRule `json:"result"`
+}
+
+type SnippetRule struct {
+	ID          string `json:"id"`
+	Enabled     *bool  `json:"enabled,omitempty"`
+	Expression  string `json:"expression"`
+	SnippetName string `json:"snippet_name"`
+	Description string `json:"description"`
+}
+
+func (api *API) ListZoneSnippetsRules(ctx context.Context, rc *ResourceContainer) ([]SnippetRule, error) {
+	if rc.Identifier == "" {
+		return nil, ErrMissingZoneID
+	}
+
+	uri := buildURI(fmt.Sprintf("/zones/%s/snippets/rules", rc.Identifier), nil)
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	result := SnippetsRulesResponse{}
+	if err := json.Unmarshal(res, &result); err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return result.Result, nil
+}
+
+func (api *API) UpdateZoneSnippetsRules(ctx context.Context, rc *ResourceContainer, params []SnippetRule) ([]SnippetRule, error) {
+	if rc.Identifier == "" {
+		return nil, ErrMissingZoneID
+	}
+
+	uri := fmt.Sprintf("/zones/%s/snippets/rules", rc.Identifier)
+
+	payload, err := json.Marshal(params)
+	if err != nil {
+		return nil, err
+	}
+
+	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, payload)
+	if err != nil {
+		return nil, err
+	}
+
+	result := SnippetsRulesResponse{}
+	if err := json.Unmarshal(res, &result); err != nil {
+		return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return result.Result, nil
+}


Test patch:
diff --git a/snippets_rules_test.go b/snippets_rules_test.go
new file mode 100644
index 000000000..dea15b434
--- /dev/null
+++ b/snippets_rules_test.go
@@ -0,0 +1,131 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSnippetsRules(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.Fprint(w, `{
+      "result": [
+		{
+			"id": "some_id_1",
+			"expression": "true",
+			"enabled": true,
+			"description": "some description",
+			"snippet_name": "snippet_1"
+	  	},
+		{
+			"id": "some_id_2",
+			"expression": "true",
+			"enabled": true,
+			"description": "some description",
+			"snippet_name": "snippet_2"
+	  	}
+	  ],
+      "success": true,
+      "errors": [],
+      "messages": []
+    }`)
+	}
+	mux.HandleFunc("/zones/"+testZoneID+"/snippets/rules", handler)
+
+	want := []SnippetRule{
+		{
+			ID:          "some_id_1",
+			Expression:  "true",
+			Enabled:     BoolPtr(true),
+			Description: "some description",
+			SnippetName: "snippet_1",
+		},
+		{
+			ID:          "some_id_2",
+			Expression:  "true",
+			Enabled:     BoolPtr(true),
+			Description: "some description",
+			SnippetName: "snippet_2",
+		},
+	}
+
+	zoneActual, err := client.ListZoneSnippetsRules(context.Background(), ZoneIdentifier(testZoneID))
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, zoneActual)
+	}
+}
+
+func TestUpdateSnippetsRules(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.Fprint(w, `{
+      "result": [
+		{
+			"id": "some_id_1",
+			"expression": "true",
+			"enabled": false,
+			"description": "some description",
+			"snippet_name": "snippet_1"
+	  	},
+		{
+			"id": "some_id_2",
+			"expression": "true",
+			"enabled": false,
+			"description": "some description",
+			"snippet_name": "snippet_2"
+	  	}
+	  ],
+      "success": true,
+      "errors": [],
+      "messages": []
+    }`)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/snippets/rules", handler)
+	toUpdate := []SnippetRule{
+		{
+			Expression:  "true",
+			Enabled:     BoolPtr(false),
+			Description: "some description",
+			SnippetName: "snippet_1",
+		},
+		{
+			Expression:  "true",
+			Enabled:     BoolPtr(false),
+			Description: "some description",
+			SnippetName: "snippet_2",
+		},
+	}
+	want := []SnippetRule{
+		{
+			ID:          "some_id_1",
+			Expression:  "true",
+			Enabled:     BoolPtr(false),
+			Description: "some description",
+			SnippetName: "snippet_1",
+		},
+		{
+			ID:          "some_id_2",
+			Expression:  "true",
+			Enabled:     BoolPtr(false),
+			Description: "some description",
+			SnippetName: "snippet_2",
+		},
+	}
+	zoneActual, err := client.UpdateZoneSnippetsRules(context.Background(), ZoneIdentifier(testZoneID), toUpdate)
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, zoneActual)
+	}
+}
diff --git a/snippets_test.go b/snippets_test.go
new file mode 100644
index 000000000..627b3cb55
--- /dev/null
+++ b/snippets_test.go
@@ -0,0 +1,116 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSnippets(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.Fprint(w, `{
+      "result": [
+		{
+			"snippet_name": "some_id_1",
+			"created_on":"0001-01-01T00:00:00Z",
+			"modified_on":"0001-01-01T00:00:00Z"
+	  	},
+		{
+			"snippet_name": "some_id_2",
+			"created_on":"0001-01-01T00:00:00Z",
+			"modified_on":"0001-01-01T00:00:00Z"
+	  	}
+	  ],
+      "success": true,
+      "errors": [],
+      "messages": []
+    }`)
+	}
+	mux.HandleFunc("/zones/"+testZoneID+"/snippets", handler)
+
+	want := []Snippet{
+		{
+			SnippetName: "some_id_1",
+			CreatedOn:   &time.Time{},
+			ModifiedOn:  &time.Time{},
+		},
+		{
+			SnippetName: "some_id_2",
+			CreatedOn:   &time.Time{},
+			ModifiedOn:  &time.Time{},
+		},
+	}
+
+	zoneActual, err := client.ListZoneSnippets(context.Background(), ZoneIdentifier(testZoneID))
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, zoneActual)
+	}
+}
+
+func TestUpdateSnippets(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.Fprint(w, `{
+      "result": [
+		{
+			"snippet_name": "some_id_1",
+			"created_on":"0001-01-01T00:00:00Z",
+			"modified_on":"0001-01-01T00:00:00Z"
+	  	},
+		{
+			"snippet_name": "some_id_2",
+			"created_on":"0001-01-01T00:00:00Z",
+			"modified_on":"0001-01-01T00:00:00Z"
+	  	}
+	  ],
+      "success": true,
+      "errors": [],
+      "messages": []
+    }`)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/snippets/some_id_1", handler)
+	toUpdate := SnippetRequest{
+		SnippetName: "some_id_1",
+		MainFile:    "file1.js",
+		Files: []SnippetFile{
+			{
+				FileName: "file1.js",
+				Content:  "test1.json",
+			},
+			{
+				FileName: "file2.js",
+				Content:  "test2.json",
+			},
+		},
+	}
+	want := []Snippet{
+		{
+			SnippetName: "some_id_1",
+			CreatedOn:   &time.Time{},
+			ModifiedOn:  &time.Time{},
+		},
+		{
+			SnippetName: "some_id_2",
+			CreatedOn:   &time.Time{},
+			ModifiedOn:  &time.Time{},
+		},
+	}
+	zoneActual, err := client.UpdateZoneSnippet(context.Background(), ZoneIdentifier(testZoneID), toUpdate)
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, zoneActual)
+	}
+}