Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CRUD support for Images Variants #1494

Merged
3 changes: 3 additions & 0 deletions .changelog/1494.txt
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"errors": [],
"messages": [],
"result": {
"variant": {
"id": "hero",
"neverRequireSignedURLs": true,
"options": {
"fit": "scale-down",
"height": 768,
"metadata": "none",
"width": 1366
}
}
},
"success": true
}