Skip to content

Commit

Permalink
Merge pull request #1494 from hwrdprkns/cloudflare_images_variants
Browse files Browse the repository at this point in the history
Add CRUD support for Images Variants
  • Loading branch information
jacobbednarz committed Feb 5, 2024
2 parents 1821b1c + 1532c66 commit ecaae34
Show file tree
Hide file tree
Showing 5 changed files with 397 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/1494.txt
@@ -0,0 +1,3 @@
```release-note:enhancement
images_variants: Add support for Images Variants CRUD operations
```
163 changes: 163 additions & 0 deletions images_variants.go
@@ -0,0 +1,163 @@
package cloudflare

import (
"context"
"fmt"
"net/http"

"github.com/goccy/go-json"
)

type ImagesVariant struct {
ID string `json:"id,omitempty"`
NeverRequireSignedURLs *bool `json:"neverRequireSignedURLs,omitempty"`
Options ImagesVariantsOptions `json:"options,omitempty"`
}

type ImagesVariantsOptions struct {
Fit string `json:"fit,omitempty"`
Height int `json:"height,omitempty"`
Metadata string `json:"metadata,omitempty"`
Width int `json:"width,omitempty"`
}

type ListImageVariantsParams struct{}

type ListImagesVariantsResponse struct {
Result ListImageVariantsResult `json:"result,omitempty"`
Response
}

type ListImageVariantsResult struct {
ImagesVariants map[string]ImagesVariant `json:"variants,omitempty"`
}

type CreateImagesVariantParams struct {
ID string `json:"id,omitempty"`
NeverRequireSignedURLs *bool `json:"neverRequireSignedURLs,omitempty"`
Options ImagesVariantsOptions `json:"options,omitempty"`
}

type UpdateImagesVariantParams struct {
ID string `json:"-"`
NeverRequireSignedURLs *bool `json:"neverRequireSignedURLs,omitempty"`
Options ImagesVariantsOptions `json:"options,omitempty"`
}

type ImagesVariantResult struct {
Variant ImagesVariant `json:"variant,omitempty"`
}

type ImagesVariantResponse struct {
Result ImagesVariantResult `json:"result,omitempty"`
Response
}

// Lists existing variants.
//
// API Reference: https://developers.cloudflare.com/api/operations/cloudflare-images-variants-list-variants
func (api *API) ListImagesVariants(ctx context.Context, rc *ResourceContainer, params ListImageVariantsParams) (ListImageVariantsResult, error) {
if rc.Identifier == "" {
return ListImageVariantsResult{}, ErrMissingAccountID
}

baseURL := fmt.Sprintf("/accounts/%s/images/v1/variants", rc.Identifier)
res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil)
if err != nil {
return ListImageVariantsResult{}, err
}

var listImageVariantsResponse ListImagesVariantsResponse
err = json.Unmarshal(res, &listImageVariantsResponse)
if err != nil {
return ListImageVariantsResult{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return listImageVariantsResponse.Result, nil
}

// Fetch details for a single variant.
//
// API Reference: https://developers.cloudflare.com/api/operations/cloudflare-images-variants-variant-details
func (api *API) GetImagesVariant(ctx context.Context, rc *ResourceContainer, variantID string) (ImagesVariant, error) {
if rc.Identifier == "" {
return ImagesVariant{}, ErrMissingAccountID
}

baseURL := fmt.Sprintf("/accounts/%s/images/v1/variants/%s", rc.Identifier, variantID)
res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil)
if err != nil {
return ImagesVariant{}, err
}

var imagesVariantDetailResponse ImagesVariantResponse
err = json.Unmarshal(res, &imagesVariantDetailResponse)
if err != nil {
return ImagesVariant{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return imagesVariantDetailResponse.Result.Variant, nil
}

// Specify variants that allow you to resize images for different use cases.
//
// API Reference: https://developers.cloudflare.com/api/operations/cloudflare-images-variants-create-a-variant
func (api *API) CreateImagesVariant(ctx context.Context, rc *ResourceContainer, params CreateImagesVariantParams) (ImagesVariant, error) {
if rc.Identifier == "" {
return ImagesVariant{}, ErrMissingAccountID
}

baseURL := fmt.Sprintf("/accounts/%s/images/v1/variants", rc.Identifier)
res, err := api.makeRequestContext(ctx, http.MethodPost, baseURL, params)
if err != nil {
return ImagesVariant{}, err
}

var createImagesVariantResponse ImagesVariantResponse
err = json.Unmarshal(res, &createImagesVariantResponse)
if err != nil {
return ImagesVariant{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return createImagesVariantResponse.Result.Variant, nil
}

// Deleting a variant purges the cache for all images associated with the variant.
//
// API Reference: https://developers.cloudflare.com/api/operations/cloudflare-images-variants-variant-details
func (api *API) DeleteImagesVariant(ctx context.Context, rc *ResourceContainer, variantID string) error {
if rc.Identifier == "" {
return ErrMissingAccountID
}

baseURL := fmt.Sprintf("/accounts/%s/images/v1/variants/%s", rc.Identifier, variantID)
_, err := api.makeRequestContext(ctx, http.MethodDelete, baseURL, nil)
if err != nil {
return fmt.Errorf("%s: %w", errMakeRequestError, err)
}

return nil
}

// Updating a variant purges the cache for all images associated with the variant.
//
// API Reference: https://developers.cloudflare.com/api/operations/cloudflare-images-variants-variant-details
func (api *API) UpdateImagesVariant(ctx context.Context, rc *ResourceContainer, params UpdateImagesVariantParams) (ImagesVariant, error) {
if rc.Identifier == "" {
return ImagesVariant{}, ErrMissingAccountID
}

baseURL := fmt.Sprintf("/accounts/%s/images/v1/variants/%s", rc.Identifier, params.ID)
res, err := api.makeRequestContext(ctx, http.MethodPatch, baseURL, params)
if err != nil {
return ImagesVariant{}, err
}

var imagesVariantDetailResponse ImagesVariantResponse
err = json.Unmarshal(res, &imagesVariantDetailResponse)
if err != nil {
return ImagesVariant{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return imagesVariantDetailResponse.Result.Variant, nil
}
195 changes: 195 additions & 0 deletions images_variants_test.go
@@ -0,0 +1,195 @@
package cloudflare

import (
"context"
"fmt"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
)

const (
testImagesVariantID = "hero"
)

func TestImageVariants_List(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, loadFixture("images_variants", "single_list"))
}

mux.HandleFunc("/accounts/"+testAccountID+"/images/v1/variants", handler)

want := ListImageVariantsResult{
ImagesVariants: map[string]ImagesVariant{
"hero": {
ID: "hero",
NeverRequireSignedURLs: BoolPtr(true),
Options: ImagesVariantsOptions{
Fit: "scale-down",
Height: 768,
Width: 1366,
Metadata: "none",
},
},
},
}

got, err := client.ListImagesVariants(context.Background(), AccountIdentifier(testAccountID), ListImageVariantsParams{})
if assert.NoError(t, err) {
assert.Equal(t, want, got)
}
}

func TestImageVariants_Delete(t *testing.T) {
setup()
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodDelete, r.Method, "Expected method '%s', got %s", http.MethodDelete, r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"success": true,
"errors": [],
"messages": [],
"result": {}
}`)
}

url := fmt.Sprintf("/accounts/%s/images/v1/variants/%s", testAccountID, testImagesVariantID)
mux.HandleFunc(url, handler)

err := client.DeleteImagesVariant(context.Background(), AccountIdentifier(testAccountID), testImagesVariantID)
assert.NoError(t, err)
}

func TestImagesVariants_Get(t *testing.T) {
setup()
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method, "Expected method '%s', got %s", http.MethodGet, r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, loadFixture("images_variants", "single_full"))
}

url := fmt.Sprintf("/accounts/%s/images/v1/variants/%s", testAccountID, testImagesVariantID)
mux.HandleFunc(url, handler)

want := ImagesVariant{
ID: "hero",
NeverRequireSignedURLs: BoolPtr(true),
Options: ImagesVariantsOptions{
Fit: "scale-down",
Height: 768,
Width: 1366,
Metadata: "none",
},
}

got, err := client.GetImagesVariant(context.Background(), AccountIdentifier(testAccountID), testImagesVariantID)
if assert.NoError(t, err) {
assert.Equal(t, want, got)
}
}

func TestImagesVariants_Create(t *testing.T) {
setup()
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method, "Expected method '%s', got %s", http.MethodPost, r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, loadFixture("images_variants", "single_full"))
}

url := fmt.Sprintf("/accounts/%s/images/v1/variants", testAccountID)
mux.HandleFunc(url, handler)

want := ImagesVariant{
ID: "hero",
NeverRequireSignedURLs: BoolPtr(true),
Options: ImagesVariantsOptions{
Fit: "scale-down",
Height: 768,
Width: 1366,
Metadata: "none",
},
}

got, err := client.CreateImagesVariant(context.Background(), AccountIdentifier(testAccountID), CreateImagesVariantParams{
ID: testImagesVariantID,
NeverRequireSignedURLs: BoolPtr(true),
Options: ImagesVariantsOptions{
Fit: "scale-down",
Height: 768,
Width: 1366,
Metadata: "none",
},
})
if assert.NoError(t, err) {
assert.Equal(t, want, got)
}
}

func TestImagesVariants_Update(t *testing.T) {
setup()
defer teardown()

handler := func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPatch, r.Method, "Expected method '%s', got %s", http.MethodPatch, r.Method)
w.Header().Set("content-type", "application/json")
fmt.Fprint(w, loadFixture("images_variants", "single_full"))
}

url := fmt.Sprintf("/accounts/%s/images/v1/variants/%s", testAccountID, testImagesVariantID)
mux.HandleFunc(url, handler)

want := ImagesVariant{
ID: "hero",
NeverRequireSignedURLs: BoolPtr(true),
Options: ImagesVariantsOptions{
Fit: "scale-down",
Height: 768,
Width: 1366,
Metadata: "none",
},
}

got, err := client.UpdateImagesVariant(context.Background(), AccountIdentifier(testAccountID), UpdateImagesVariantParams{
ID: "hero",
NeverRequireSignedURLs: BoolPtr(true),
Options: ImagesVariantsOptions{
Fit: "scale-down",
Height: 768,
Width: 1366,
Metadata: "none",
},
})

if assert.NoError(t, err) {
assert.Equal(t, want, got)
}
}

func TestImageVariants_MissingAccountId(t *testing.T) {
_, err := client.ListImagesVariants(context.Background(), AccountIdentifier(""), ListImageVariantsParams{})
assert.Equal(t, ErrMissingAccountID, err)

_, err = client.GetImagesVariant(context.Background(), AccountIdentifier(""), testImagesVariantID)
assert.Equal(t, ErrMissingAccountID, err)

_, err = client.CreateImagesVariant(context.Background(), AccountIdentifier(""), CreateImagesVariantParams{})
assert.Equal(t, ErrMissingAccountID, err)

err = client.DeleteImagesVariant(context.Background(), AccountIdentifier(""), testImagesVariantID)
assert.Equal(t, ErrMissingAccountID, err)

_, err = client.UpdateImagesVariant(context.Background(), AccountIdentifier(""), UpdateImagesVariantParams{})
assert.Equal(t, ErrMissingAccountID, err)
}
17 changes: 17 additions & 0 deletions testdata/fixtures/images_variants/single_full.json
@@ -0,0 +1,17 @@
{
"errors": [],
"messages": [],
"result": {
"variant": {
"id": "hero",
"neverRequireSignedURLs": true,
"options": {
"fit": "scale-down",
"height": 768,
"metadata": "none",
"width": 1366
}
}
},
"success": true
}

0 comments on commit ecaae34

Please sign in to comment.