From b0ff791b8d20b06557ae17a237f738c961af6cc3 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Wed, 29 Mar 2023 10:08:07 +1100 Subject: [PATCH] zone: make settings more flexible For the life of this library, the settings have always been under the `/zones/:zone_id/settings` route which was an aggregate of all the settings. This was designed as a way to aggregate all of the settings in one place and had its place however, now we are breaking up the zone settings to their true paths and need to make the `Get` and `Update` zone settings compatible with this approach. This will enable teams to use their true paths instead of the prefixed or aggregate paths with no friction from the library. --- errors.go | 2 ++ zone.go | 63 ++++++++++++++++++++++++++------ zone_test.go | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 10 deletions(-) diff --git a/errors.go b/errors.go index 723c18bae3ab..85fdc3efacb3 100644 --- a/errors.go +++ b/errors.go @@ -31,6 +31,7 @@ const ( errInvalidResourceContainerAccess = "requested resource container (%q) is not supported for this endpoint" errRequiredAccountLevelResourceContainer = "this endpoint requires using an account level resource container and identifiers" + errRequiredZoneLevelResourceContainer = "this endpoint requires using a zone level resource container and identifiers" ) var ( @@ -43,6 +44,7 @@ var ( ErrMissingResourceIdentifier = errors.New(errMissingResourceIdentifier) ErrRequiredAccountLevelResourceContainer = errors.New(errRequiredAccountLevelResourceContainer) + ErrRequiredZoneLevelResourceContainer = errors.New(errRequiredZoneLevelResourceContainer) ) type ErrorType string diff --git a/zone.go b/zone.go index ba544c789666..a4716d4b3366 100644 --- a/zone.go +++ b/zone.go @@ -14,6 +14,11 @@ import ( "golang.org/x/net/idna" ) +var ( + // ErrMissingSettingName is for when identifier is required but missing. + ErrMissingSettingName = errors.New("zone setting name required but missing") +) + // Owner describes the resource owner. type Owner struct { ID string `json:"id"` @@ -300,6 +305,17 @@ type zoneSubscriptionRatePlanPayload struct { } `json:"rate_plan"` } +type GetZoneSettingParams struct { + Name string `json:"-"` + PathPrefix string `json:"-"` +} + +type UpdateZoneSettingParams struct { + Name string `json:"-"` + PathPrefix string `json:"-"` + Value interface{} `json:"value"` +} + // CreateZone creates a zone on an account. // // Setting jumpstart to true will attempt to automatically scan for existing @@ -897,11 +913,25 @@ func normalizeZoneName(name string) string { return name } -// ZoneSingleSetting returns information about specified setting to the specified zone. +// GetZoneSetting returns information about specified setting to the specified +// zone. // // API reference: https://api.cloudflare.com/#zone-settings-get-all-zone-settings -func (api *API) ZoneSingleSetting(ctx context.Context, zoneID, settingName string) (ZoneSetting, error) { - uri := fmt.Sprintf("/zones/%s/settings/%s", zoneID, settingName) +func (api *API) GetZoneSetting(ctx context.Context, rc *ResourceContainer, params GetZoneSettingParams) (ZoneSetting, error) { + if rc.Level != ZoneRouteLevel { + return ZoneSetting{}, ErrRequiredZoneLevelResourceContainer + } + + if rc.Identifier == "" { + return ZoneSetting{}, ErrMissingName + } + + pathPrefix := "settings" + if params.PathPrefix != "" { + pathPrefix = params.PathPrefix + } + + uri := fmt.Sprintf("/zones/%s/%s/%s", rc.Identifier, pathPrefix, params.Name) res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) if err != nil { return ZoneSetting{}, err @@ -914,23 +944,36 @@ func (api *API) ZoneSingleSetting(ctx context.Context, zoneID, settingName strin return r.Result, nil } -// UpdateZoneSingleSetting updates the specified setting for a given zone. +// UpdateZoneSetting updates the specified setting for a given zone. // // API reference: https://api.cloudflare.com/#zone-settings-edit-zone-settings-info -func (api *API) UpdateZoneSingleSetting(ctx context.Context, zoneID, settingName string, setting ZoneSetting) (*ZoneSettingSingleResponse, error) { - uri := fmt.Sprintf("/zones/%s/settings/%s", zoneID, settingName) - res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, setting) +func (api *API) UpdateZoneSetting(ctx context.Context, rc *ResourceContainer, params UpdateZoneSettingParams) (ZoneSetting, error) { + if rc.Level != ZoneRouteLevel { + return ZoneSetting{}, ErrRequiredZoneLevelResourceContainer + } + + if rc.Identifier == "" { + return ZoneSetting{}, ErrMissingName + } + + pathPrefix := "settings" + if params.PathPrefix != "" { + pathPrefix = params.PathPrefix + } + + uri := fmt.Sprintf("/zones/%s/%s/%s", rc.Identifier, pathPrefix, params.Name) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params) if err != nil { - return nil, err + return ZoneSetting{}, err } response := &ZoneSettingSingleResponse{} err = json.Unmarshal(res, &response) if err != nil { - return nil, fmt.Errorf("%s: %w", errUnmarshalError, err) + return ZoneSetting{}, fmt.Errorf("%s: %w", errUnmarshalError, err) } - return response, nil + return response.Result, nil } // ZoneExport returns the text BIND config for the given zone diff --git a/zone_test.go b/zone_test.go index 93a2bb6fd5be..6dd5bc4fccd1 100644 --- a/zone_test.go +++ b/zone_test.go @@ -1510,3 +1510,103 @@ func TestUpdateZoneSSLSettings(t *testing.T) { assert.Equal(t, s.ModifiedOn, "2014-01-01T05:20:00.12345Z") } } + +func TestGetZoneSetting(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.Fprintf(w, `{ + "result": { + "id": "ssl", + "value": "off", + "editable": true, + "modified_on": "2014-01-01T05:20:00.12345Z" + } + }`) + } + mux.HandleFunc("/zones/foo/settings/ssl", handler) + s, err := client.GetZoneSetting(context.Background(), ZoneIdentifier("foo"), GetZoneSettingParams{Name: "ssl"}) + if assert.NoError(t, err) { + assert.Equal(t, s.ID, "ssl") + assert.Equal(t, s.Value, "off") + assert.Equal(t, s.Editable, true) + assert.Equal(t, s.ModifiedOn, "2014-01-01T05:20:00.12345Z") + } +} + +func TestGetZoneSettingWithCustomPathPrefix(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.Fprintf(w, `{ + "result": { + "id": "ssl", + "value": "off", + "editable": true, + "modified_on": "2014-01-01T05:20:00.12345Z" + } + }`) + } + mux.HandleFunc("/zones/foo/my_custom_path/ssl", handler) + s, err := client.GetZoneSetting(context.Background(), ZoneIdentifier("foo"), GetZoneSettingParams{Name: "ssl", PathPrefix: "my_custom_path"}) + if assert.NoError(t, err) { + assert.Equal(t, s.ID, "ssl") + assert.Equal(t, s.Value, "off") + assert.Equal(t, s.Editable, true) + assert.Equal(t, s.ModifiedOn, "2014-01-01T05:20:00.12345Z") + } +} + +func TestUpdateZoneSetting(t *testing.T) { + setup() + defer teardown() + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPatch, r.Method, "Expected method 'PATCH', got %s", r.Method) + w.Header().Set("content-type", "application/json") + _, _ = fmt.Fprintf(w, `{ + "result": { + "id": "ssl", + "value": "off", + "editable": true, + "modified_on": "2014-01-01T05:20:00.12345Z" + } + }`) + } + mux.HandleFunc("/zones/foo/settings/ssl", handler) + s, err := client.UpdateZoneSetting(context.Background(), ZoneIdentifier("foo"), UpdateZoneSettingParams{Name: "ssl", Value: "off"}) + if assert.NoError(t, err) { + assert.Equal(t, s.ID, "ssl") + assert.Equal(t, s.Value, "off") + assert.Equal(t, s.Editable, true) + assert.Equal(t, s.ModifiedOn, "2014-01-01T05:20:00.12345Z") + } +} + +func TestUpdateZoneSettingWithCustomPathPrefix(t *testing.T) { + setup() + defer teardown() + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPatch, r.Method, "Expected method 'PATCH', got %s", r.Method) + w.Header().Set("content-type", "application/json") + _, _ = fmt.Fprintf(w, `{ + "result": { + "id": "ssl", + "value": "off", + "editable": true, + "modified_on": "2014-01-01T05:20:00.12345Z" + } + }`) + } + mux.HandleFunc("/zones/foo/my_custom_path/ssl", handler) + s, err := client.UpdateZoneSetting(context.Background(), ZoneIdentifier("foo"), UpdateZoneSettingParams{Name: "ssl", PathPrefix: "my_custom_path", Value: "off"}) + if assert.NoError(t, err) { + assert.Equal(t, s.ID, "ssl") + assert.Equal(t, s.Value, "off") + assert.Equal(t, s.Editable, true) + assert.Equal(t, s.ModifiedOn, "2014-01-01T05:20:00.12345Z") + } +}