Repository: cloudflare/cloudflare-go
Pull request #1492 (2024-01-31 13:56:12)

Add Hyperdrive CRUD support
Add support for managing [Hyperdrive configs](https://developers.cloudflare.com/api/operations/list-hyperdrive)

## Description

Resolves https://github.com/cloudflare/cloudflare-go/issues/1491

Mainly wrote by copying the existing `queue.go`

## Has your change been tested?

- Tested `ListHyperdriveConfigs` manually
- Added tests

## 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 in [cloudflare/api-schemas](https://github.com/cloudflare/api-schemas) 
      and relies on stable APIs.



github-actions[bot]: Oops! It looks like no changelog entry is attached to this PR. Please include a release note as described in https://github.com/cloudflare/cloudflare-go/blob/master/docs/changelog-process.md.

Example: 

~~~
```release-note:TYPE
Release note
```
~~~

If you do not require a release note to be included, please add the `workflow/skip-changelog-entry` label.

Patch:
diff --git a/.changelog/1492.txt b/.changelog/1492.txt
new file mode 100644
index 000000000..f46c87416
--- /dev/null
+++ b/.changelog/1492.txt
@@ -0,0 +1,3 @@
+```release-note:enhancement
+hyperdrive: Add support for hyperdrive CRUD operations
+```
diff --git a/hyperdrive.go b/hyperdrive.go
new file mode 100644
index 000000000..346eb1ccb
--- /dev/null
+++ b/hyperdrive.go
@@ -0,0 +1,195 @@
+package cloudflare
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+
+	"github.com/goccy/go-json"
+)
+
+var (
+	ErrMissingHyperdriveConfigID       = errors.New("required hyperdrive config id is missing")
+	ErrMissingHyperdriveConfigName     = errors.New("required hyperdrive config name is missing")
+	ErrMissingHyperdriveConfigPassword = errors.New("required hyperdrive config password is missing")
+)
+
+type HyperdriveConfig struct {
+	ID      string                  `json:"id,omitempty"`
+	Name    string                  `json:"name,omitempty"`
+	Origin  HyperdriveConfigOrigin  `json:"origin,omitempty"`
+	Caching HyperdriveConfigCaching `json:"caching,omitempty"`
+}
+
+type HyperdriveConfigOrigin struct {
+	Database string `json:"database,omitempty"`
+	Host     string `json:"host,omitempty"`
+	Port     int    `json:"port,omitempty"`
+	Scheme   string `json:"scheme,omitempty"`
+	User     string `json:"user,omitempty"`
+}
+
+type HyperdriveConfigCaching struct {
+	Disabled             *bool `json:"disabled,omitempty"`
+	MaxAge               int   `json:"max_age,omitempty"`
+	StaleWhileRevalidate int   `json:"stale_while_revalidate,omitempty"`
+}
+
+type HyperdriveConfigListResponse struct {
+	Response
+	Result []HyperdriveConfig `json:"result"`
+}
+
+type CreateHyperdriveConfigParams struct {
+	Name     string                  `json:"name"`
+	Password string                  `json:"password"`
+	Origin   HyperdriveConfigOrigin  `json:"origin"`
+	Caching  HyperdriveConfigCaching `json:"caching,omitempty"`
+}
+
+type HyperdriveConfigResponse struct {
+	Response
+	Result HyperdriveConfig `json:"result"`
+}
+
+type UpdateHyperdriveConfigParams struct {
+	HyperdriveID string                  `json:"-"`
+	Name         string                  `json:"name"`
+	Password     string                  `json:"password"`
+	Origin       HyperdriveConfigOrigin  `json:"origin"`
+	Caching      HyperdriveConfigCaching `json:"caching,omitempty"`
+}
+
+type ListHyperdriveConfigParams struct{}
+
+// ListHyperdriveConfigs returns the Hyperdrive configs owned by an account.
+//
+// API reference: https://developers.cloudflare.com/api/operations/list-hyperdrive
+func (api *API) ListHyperdriveConfigs(ctx context.Context, rc *ResourceContainer, params ListHyperdriveConfigParams) ([]HyperdriveConfig, error) {
+	if rc.Identifier == "" {
+		return []HyperdriveConfig{}, ErrMissingAccountID
+	}
+
+	hResponse := HyperdriveConfigListResponse{}
+	uri := fmt.Sprintf("/accounts/%s/hyperdrive/configs", rc.Identifier)
+
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return []HyperdriveConfig{}, err
+	}
+
+	err = json.Unmarshal(res, &hResponse)
+	if err != nil {
+		return []HyperdriveConfig{}, fmt.Errorf("failed to unmarshal filters JSON data: %w", err)
+	}
+
+	return hResponse.Result, nil
+}
+
+// CreateHyperdriveConfig creates a new Hyperdrive config.
+//
+// API reference: https://developers.cloudflare.com/api/operations/create-hyperdrive
+func (api *API) CreateHyperdriveConfig(ctx context.Context, rc *ResourceContainer, params CreateHyperdriveConfigParams) (HyperdriveConfig, error) {
+	if rc.Identifier == "" {
+		return HyperdriveConfig{}, ErrMissingAccountID
+	}
+
+	if params.Name == "" {
+		return HyperdriveConfig{}, ErrMissingHyperdriveConfigName
+	}
+
+	if params.Password == "" {
+		return HyperdriveConfig{}, ErrMissingHyperdriveConfigPassword
+	}
+
+	uri := fmt.Sprintf("/accounts/%s/hyperdrive/configs", rc.Identifier)
+
+	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
+	if err != nil {
+		return HyperdriveConfig{}, err
+	}
+
+	var r HyperdriveConfigResponse
+	err = json.Unmarshal(res, &r)
+	if err != nil {
+		return HyperdriveConfig{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r.Result, nil
+}
+
+// DeleteHyperdriveConfig deletes a Hyperdrive config.
+//
+// API reference: https://developers.cloudflare.com/api/operations/delete-hyperdrive
+func (api *API) DeleteHyperdriveConfig(ctx context.Context, rc *ResourceContainer, hyperdriveID string) error {
+	if rc.Identifier == "" {
+		return ErrMissingAccountID
+	}
+	if hyperdriveID == "" {
+		return ErrMissingHyperdriveConfigID
+	}
+
+	uri := fmt.Sprintf("/accounts/%s/hyperdrive/configs/%s", rc.Identifier, hyperdriveID)
+	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
+	if err != nil {
+		return fmt.Errorf("%s: %w", errMakeRequestError, err)
+	}
+
+	return nil
+}
+
+// GetHyperdriveConfig returns a single Hyperdrive config based on the ID.
+//
+// API reference: https://developers.cloudflare.com/api/operations/get-hyperdrive
+func (api *API) GetHyperdriveConfig(ctx context.Context, rc *ResourceContainer, hyperdriveID string) (HyperdriveConfig, error) {
+	if rc.Identifier == "" {
+		return HyperdriveConfig{}, ErrMissingAccountID
+	}
+
+	if hyperdriveID == "" {
+		return HyperdriveConfig{}, ErrMissingHyperdriveConfigID
+	}
+
+	uri := fmt.Sprintf("/accounts/%s/hyperdrive/configs/%s", rc.Identifier, hyperdriveID)
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return HyperdriveConfig{}, err
+	}
+
+	var r HyperdriveConfigResponse
+	err = json.Unmarshal(res, &r)
+	if err != nil {
+		return HyperdriveConfig{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r.Result, nil
+}
+
+// UpdateHyperdriveConfig updates a Hyperdrive config.
+//
+// API reference: https://developers.cloudflare.com/api/operations/update-hyperdrive
+func (api *API) UpdateHyperdriveConfig(ctx context.Context, rc *ResourceContainer, params UpdateHyperdriveConfigParams) (HyperdriveConfig, error) {
+	if rc.Identifier == "" {
+		return HyperdriveConfig{}, ErrMissingAccountID
+	}
+
+	if params.HyperdriveID == "" {
+		return HyperdriveConfig{}, ErrMissingHyperdriveConfigID
+	}
+
+	uri := fmt.Sprintf("/accounts/%s/hyperdrive/configs/%s", rc.Identifier, params.HyperdriveID)
+
+	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params)
+	if err != nil {
+		return HyperdriveConfig{}, err
+	}
+
+	var r HyperdriveConfigResponse
+	err = json.Unmarshal(res, &r)
+	if err != nil {
+		return HyperdriveConfig{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r.Result, nil
+}


Test patch:
diff --git a/hyperdrive_test.go b/hyperdrive_test.go
new file mode 100644
index 000000000..633ce9cbb
--- /dev/null
+++ b/hyperdrive_test.go
@@ -0,0 +1,285 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	testHyperdriveConfigId   = "6b7efc370ea34ded8327fa20698dfe3a"
+	testHyperdriveConfigName = "example-hyperdrive"
+)
+
+func testHyperdriveConfig() HyperdriveConfig {
+	return HyperdriveConfig{
+		ID:   testHyperdriveConfigId,
+		Name: testHyperdriveConfigName,
+		Origin: HyperdriveConfigOrigin{
+			Database: "postgres",
+			Host:     "database.example.com",
+			Port:     5432,
+			Scheme:   "postgres",
+			User:     "postgres",
+		},
+		Caching: HyperdriveConfigCaching{
+			Disabled:             BoolPtr(false),
+			MaxAge:               30,
+			StaleWhileRevalidate: 15,
+		},
+	}
+}
+
+func TestHyperdriveConfig_List(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc(fmt.Sprintf("/accounts/%s/hyperdrive/configs", 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": [{
+				"id": "6b7efc370ea34ded8327fa20698dfe3a",
+				"caching": {
+					"disabled": false,
+					"max_age": 30,
+					"stale_while_revalidate": 15
+				},
+				"name": "example-hyperdrive",
+				"origin": {
+					"database": "postgres",
+					"host": "database.example.com",
+					"port": 5432,
+					"scheme": "postgres",
+					"user": "postgres"
+				}
+			}]
+		}`)
+	})
+
+	_, err := client.ListHyperdriveConfigs(context.Background(), AccountIdentifier(""), ListHyperdriveConfigParams{})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingAccountID, err)
+	}
+
+	result, err := client.ListHyperdriveConfigs(context.Background(), AccountIdentifier(testAccountID), ListHyperdriveConfigParams{})
+	if assert.NoError(t, err) {
+		assert.Equal(t, 1, len(result))
+		assert.Equal(t, testHyperdriveConfig(), result[0])
+	}
+}
+
+func TestHyperdriveConfig_Get(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc(fmt.Sprintf("/accounts/%s/hyperdrive/configs/%s", testAccountID, testHyperdriveConfigId), 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": "6b7efc370ea34ded8327fa20698dfe3a",
+				"caching": {
+					"disabled": false,
+					"max_age": 30,
+					"stale_while_revalidate": 15
+				},
+				"name": "example-hyperdrive",
+				"origin": {
+					"database": "postgres",
+					"host": "database.example.com",
+					"port": 5432,
+					"scheme": "postgres",
+					"user": "postgres"
+				}
+			}
+		}`)
+	})
+
+	_, err := client.GetHyperdriveConfig(context.Background(), AccountIdentifier(""), "")
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingAccountID, err)
+	}
+
+	_, err = client.GetHyperdriveConfig(context.Background(), AccountIdentifier(testAccountID), "")
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingHyperdriveConfigID, err)
+	}
+
+	result, err := client.GetHyperdriveConfig(context.Background(), AccountIdentifier(testAccountID), testHyperdriveConfigId)
+	if assert.NoError(t, err) {
+		assert.Equal(t, testHyperdriveConfig(), result)
+	}
+}
+
+func TestHyperdriveConfig_Create(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc(fmt.Sprintf("/accounts/%s/hyperdrive/configs", testAccountID), 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": "6b7efc370ea34ded8327fa20698dfe3a",
+				"caching": {
+					"disabled": false,
+					"max_age": 30,
+					"stale_while_revalidate": 15
+				},
+				"name": "example-hyperdrive",
+				"origin": {
+					"database": "postgres",
+					"host": "database.example.com",
+					"port": 5432,
+					"scheme": "postgres",
+					"user": "postgres"
+				}
+			}
+		}`)
+	})
+
+	_, err := client.CreateHyperdriveConfig(context.Background(), AccountIdentifier(""), CreateHyperdriveConfigParams{})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingAccountID, err)
+	}
+
+	_, err = client.CreateHyperdriveConfig(context.Background(), AccountIdentifier(testAccountID), CreateHyperdriveConfigParams{})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingHyperdriveConfigName, err)
+	}
+
+	_, err = client.CreateHyperdriveConfig(context.Background(), AccountIdentifier(testAccountID), CreateHyperdriveConfigParams{Name: "example-hyperdrive"})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingHyperdriveConfigPassword, err)
+	}
+
+	result, err := client.CreateHyperdriveConfig(context.Background(), AccountIdentifier(testAccountID), CreateHyperdriveConfigParams{
+		Name:     "example-hyperdrive",
+		Password: "password",
+		Origin: HyperdriveConfigOrigin{
+			Database: "postgres",
+			Host:     "database.example.com",
+			Port:     5432,
+			Scheme:   "postgres",
+			User:     "postgres",
+		},
+		Caching: HyperdriveConfigCaching{
+			Disabled:             BoolPtr(false),
+			MaxAge:               30,
+			StaleWhileRevalidate: 15,
+		},
+	})
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, testHyperdriveConfig(), result)
+	}
+}
+
+func TestHyperdriveConfig_Delete(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc(fmt.Sprintf("/accounts/%s/hyperdrive/configs/%s", testAccountID, testHyperdriveConfigId), 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": null
+		}`)
+	})
+	err := client.DeleteHyperdriveConfig(context.Background(), AccountIdentifier(""), "")
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingAccountID, err)
+	}
+
+	err = client.DeleteHyperdriveConfig(context.Background(), AccountIdentifier(testAccountID), "")
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingHyperdriveConfigID, err)
+	}
+
+	err = client.DeleteHyperdriveConfig(context.Background(), AccountIdentifier(testAccountID), testHyperdriveConfigId)
+	assert.NoError(t, err)
+}
+
+func TestHyperdriveConfig_Update(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc(fmt.Sprintf("/accounts/%s/hyperdrive/configs/%s", testAccountID, testHyperdriveConfigId), 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": {
+				"id": "6b7efc370ea34ded8327fa20698dfe3a",
+				"caching": {
+					"disabled": false,
+					"max_age": 30,
+					"stale_while_revalidate": 15
+				},
+				"name": "example-hyperdrive",
+				"origin": {
+					"database": "postgres",
+					"host": "database.example.com",
+					"port": 5432,
+					"scheme": "postgres",
+					"user": "postgres"
+				}
+			}
+		}`)
+	})
+
+	_, err := client.UpdateHyperdriveConfig(context.Background(), AccountIdentifier(""), UpdateHyperdriveConfigParams{})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingAccountID, err)
+	}
+
+	_, err = client.UpdateHyperdriveConfig(context.Background(), AccountIdentifier(testAccountID), UpdateHyperdriveConfigParams{})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingHyperdriveConfigID, err)
+	}
+
+	result, err := client.UpdateHyperdriveConfig(context.Background(), AccountIdentifier(testAccountID), UpdateHyperdriveConfigParams{
+		HyperdriveID: "6b7efc370ea34ded8327fa20698dfe3a",
+		Name:         "example-hyperdrive",
+		Password:     "password",
+		Origin: HyperdriveConfigOrigin{
+			Database: "postgres",
+			Host:     "database.example.com",
+			Port:     5432,
+			Scheme:   "postgres",
+			User:     "postgres",
+		},
+		Caching: HyperdriveConfigCaching{
+			Disabled:             BoolPtr(false),
+			MaxAge:               30,
+			StaleWhileRevalidate: 15,
+		},
+	})
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, testHyperdriveConfig(), result)
+	}
+}