diff --git a/.changelog/1494.txt b/.changelog/1494.txt new file mode 100644 index 00000000000..ad6abdd66ae --- /dev/null +++ b/.changelog/1494.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +images_variants: Add support for Images Variants CRUD operations +``` diff --git a/images_variants.go b/images_variants.go new file mode 100644 index 00000000000..2949551166e --- /dev/null +++ b/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 +} diff --git a/images_variants_test.go b/images_variants_test.go new file mode 100644 index 00000000000..994eaad2c75 --- /dev/null +++ b/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) +} diff --git a/testdata/fixtures/images_variants/single_full.json b/testdata/fixtures/images_variants/single_full.json new file mode 100644 index 00000000000..f8bc039b5c8 --- /dev/null +++ b/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 +} diff --git a/testdata/fixtures/images_variants/single_list.json b/testdata/fixtures/images_variants/single_list.json new file mode 100644 index 00000000000..1838aa6ce36 --- /dev/null +++ b/testdata/fixtures/images_variants/single_list.json @@ -0,0 +1,19 @@ +{ + "errors": [], + "messages": [], + "result": { + "variants": { + "hero": { + "id": "hero", + "neverRequireSignedURLs": true, + "options": { + "fit": "scale-down", + "height": 768, + "metadata": "none", + "width": 1366 + } + } + } + }, + "success": true +}