Repository: cloudflare/cloudflare-go
Pull request #1265 (2023-04-13 02:27:50)

Add GetR2Bucket
## Description

- Adds `GetR2Bucket` for getting info for individual buckets
- Changes CreationTime to *time.Time

## Has your change been tested?

All tests pass

## Screenshots (if appropriate):

## 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)
- [X] 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. (soon™️)





Patch:
diff --git a/.changelog/1265.txt b/.changelog/1265.txt
new file mode 100644
index 000000000..f93fc420a
--- /dev/null
+++ b/.changelog/1265.txt
@@ -0,0 +1,7 @@
+```release-note:enhancement
+r2_bucket: add support for getting a bucket
+```
+
+```release-note:breaking-change
+r2_bucket: change creation time from string to *time.Time
+```
diff --git a/r2_bucket.go b/r2_bucket.go
index 08de630e1..046438f09 100644
--- a/r2_bucket.go
+++ b/r2_bucket.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"time"
 )
 
 var (
@@ -14,8 +15,9 @@ var (
 
 // R2Bucket defines a container for objects stored in R2 Storage.
 type R2Bucket struct {
-	Name         string `json:"name"`
-	CreationDate string `json:"creation_date,omitempty"`
+	Name         string     `json:"name"`
+	CreationDate *time.Time `json:"creation_date,omitempty"`
+	Location     string     `json:"location,omitempty"`
 }
 
 // R2Buckets represents the map of buckets response from
@@ -40,6 +42,16 @@ type ListR2BucketsParams struct {
 	Cursor     string `url:"cursor,omitempty"`
 }
 
+type CreateR2BucketParameters struct {
+	Name         string `json:"name,omitempty"`
+	LocationHint string `json:"locationHint,omitempty"`
+}
+
+type R2BucketResponse struct {
+	Result R2Bucket `json:"result"`
+	Response
+}
+
 // ListR2Buckets Lists R2 buckets.
 func (api *API) ListR2Buckets(ctx context.Context, rc *ResourceContainer, params ListR2BucketsParams) ([]R2Bucket, error) {
 	if rc.Identifier == "" {
@@ -61,26 +73,58 @@ func (api *API) ListR2Buckets(ctx context.Context, rc *ResourceContainer, params
 	return r2BucketListResponse.Result.Buckets, nil
 }
 
-type CreateR2BucketParameters struct {
-	Name string `json:"name,omitempty"`
-}
-
 // 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, params CreateR2BucketParameters) error {
+func (api *API) CreateR2Bucket(ctx context.Context, rc *ResourceContainer, params CreateR2BucketParameters) (R2Bucket, error) {
 	if rc.Identifier == "" {
-		return ErrMissingAccountID
+		return R2Bucket{}, ErrMissingAccountID
 	}
 
 	if params.Name == "" {
-		return ErrMissingBucketName
+		return R2Bucket{}, ErrMissingBucketName
 	}
 
 	uri := fmt.Sprintf("/accounts/%s/r2/buckets", rc.Identifier)
-	_, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
+	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
+	if err != nil {
+		return R2Bucket{}, err
+	}
 
-	return err
+	var r2BucketResponse R2BucketResponse
+	err = json.Unmarshal(res, &r2BucketResponse)
+	if err != nil {
+		return R2Bucket{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r2BucketResponse.Result, nil
+}
+
+// GetR2Bucket Gets an existing R2 bucket.
+//
+// API reference: https://api.cloudflare.com/#r2-bucket-get-bucket
+func (api *API) GetR2Bucket(ctx context.Context, rc *ResourceContainer, bucketName string) (R2Bucket, error) {
+	if rc.Identifier == "" {
+		return R2Bucket{}, ErrMissingAccountID
+	}
+
+	if bucketName == "" {
+		return R2Bucket{}, ErrMissingBucketName
+	}
+
+	uri := fmt.Sprintf("/accounts/%s/r2/buckets/%s", rc.Identifier, bucketName)
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return R2Bucket{}, err
+	}
+
+	var r2BucketResponse R2BucketResponse
+	err = json.Unmarshal(res, &r2BucketResponse)
+	if err != nil {
+		return R2Bucket{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r2BucketResponse.Result, nil
 }
 
 // DeleteR2Bucket Deletes an existing R2 bucket.


Test patch:
diff --git a/r2_bucket_test.go b/r2_bucket_test.go
index 42168838c..ac9b2f960 100644
--- a/r2_bucket_test.go
+++ b/r2_bucket_test.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"net/http"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -32,11 +33,11 @@ func TestR2_ListBuckets(t *testing.T) {
   }
 }`)
 	})
-
+	createDate, _ := time.Parse(time.RFC3339, "2022-06-24T19:58:49.477Z")
 	want := []R2Bucket{
 		{
 			Name:         "example-bucket",
-			CreationDate: "2022-06-24T19:58:49.477Z",
+			CreationDate: &createDate,
 		},
 	}
 	actual, err := client.ListR2Buckets(context.Background(), AccountIdentifier(testAccountID), ListR2BucketsParams{})
@@ -45,6 +46,49 @@ func TestR2_ListBuckets(t *testing.T) {
 	}
 }
 
+func TestR2_GetBucket(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.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": {
+			"name": "example-bucket",
+			"creation_date": "2022-06-24T19:58:49.477Z",
+			"location": "ENAM"
+	}
+}`)
+	})
+
+	_, err := client.GetR2Bucket(context.Background(), AccountIdentifier(""), "")
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingAccountID, err)
+	}
+
+	_, err = client.GetR2Bucket(context.Background(), AccountIdentifier(testAccountID), "")
+
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingBucketName, err)
+	}
+
+	createDate, _ := time.Parse(time.RFC3339, "2022-06-24T19:58:49.477Z")
+	want := R2Bucket{
+		Name:         testBucketName,
+		CreationDate: &createDate,
+		Location:     "ENAM",
+	}
+
+	actual, err := client.GetR2Bucket(context.Background(), AccountIdentifier(testAccountID), testBucketName)
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
 func TestR2_CreateBucket(t *testing.T) {
 	setup()
 	defer teardown()
@@ -56,22 +100,34 @@ func TestR2_CreateBucket(t *testing.T) {
   "success": true,
   "errors": [],
   "messages": [],
-  "result": {}
+  "result": {
+			"name": "example-bucket",
+			"creation_date": "2022-06-24T19:58:49.477Z",
+			"location": "ENAM"
+	}
 }`)
 	})
 
-	err := client.CreateR2Bucket(context.Background(), AccountIdentifier(""), CreateR2BucketParameters{})
+	_, err := client.CreateR2Bucket(context.Background(), AccountIdentifier(""), CreateR2BucketParameters{})
 	if assert.Error(t, err) {
 		assert.Equal(t, ErrMissingAccountID, err)
 	}
 
-	err = client.CreateR2Bucket(context.Background(), AccountIdentifier(testAccountID), CreateR2BucketParameters{Name: ""})
+	_, err = client.CreateR2Bucket(context.Background(), AccountIdentifier(testAccountID), CreateR2BucketParameters{Name: ""})
 	if assert.Error(t, err) {
 		assert.Equal(t, ErrMissingBucketName, err)
 	}
+	createDate, _ := time.Parse(time.RFC3339, "2022-06-24T19:58:49.477Z")
+	want := R2Bucket{
+		Name:         testBucketName,
+		CreationDate: &createDate,
+		Location:     "ENAM",
+	}
 
-	err = client.CreateR2Bucket(context.Background(), AccountIdentifier(testAccountID), CreateR2BucketParameters{Name: testBucketName})
-	assert.NoError(t, err)
+	actual, err := client.CreateR2Bucket(context.Background(), AccountIdentifier(testAccountID), CreateR2BucketParameters{Name: testBucketName, LocationHint: "ENAM"})
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
 }
 
 func TestR2_DeleteBucket(t *testing.T) {