From 9e0c436197cda42f307fb9f762a739b7a43a287f Mon Sep 17 00:00:00 2001 From: Miroslav Cvetic Date: Wed, 1 Jul 2026 14:18:32 +0200 Subject: [PATCH 1/4] feat: add region support for API base URLs --- contentful.go | 22 ++++++++++++++++++++++ contentful_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/contentful.go b/contentful.go index 9a82974..17e7cd6 100644 --- a/contentful.go +++ b/contentful.go @@ -40,6 +40,28 @@ type Contentful struct { Webhooks *WebhooksService } +// Region identifies a Contentful infrastructure region. +type Region string + +const ( + RegionUS Region = "us" + RegionEU Region = "eu" +) + +// SetRegion configures all API base URLs for the given region. +// RegionUS and the empty string are no-ops; the client keeps its constructor defaults. +func (c *Contentful) SetRegion(region Region) *Contentful { + if region == "" || region == RegionUS { + return c + } + subdomain := "." + string(region) + c.BaseURL = strings.Replace(c.BaseURL, ".contentful.com", subdomain+".contentful.com", 1) + if c.UploadURL != "" { + c.UploadURL = strings.Replace(c.UploadURL, ".contentful.com", subdomain+".contentful.com", 1) + } + return c +} + type service struct { c *Contentful } diff --git a/contentful_test.go b/contentful_test.go index ed44b57..7e0ded4 100644 --- a/contentful_test.go +++ b/contentful_test.go @@ -166,6 +166,35 @@ func TestContentfulSetOrganization(t *testing.T) { assert.Equal(t, organizationID, cma.Headers["X-Contentful-Organization"]) } +func TestSetRegion(t *testing.T) { + t.Run("US is a no-op on CMA", func(t *testing.T) { + c := NewCMA(CMAToken) + c.SetRegion(RegionUS) + assert.Equal(t, "https://api.contentful.com", c.BaseURL) + assert.Equal(t, "https://upload.contentful.com", c.UploadURL) + }) + t.Run("EU sets CMA BaseURL and UploadURL", func(t *testing.T) { + c := NewCMA(CMAToken) + c.SetRegion(RegionEU) + assert.Equal(t, "https://api.eu.contentful.com", c.BaseURL) + assert.Equal(t, "https://upload.eu.contentful.com", c.UploadURL) + }) + t.Run("EU sets CDA BaseURL", func(t *testing.T) { + c := NewCDA(CDAToken) + c.SetRegion(RegionEU) + assert.Equal(t, "https://cdn.eu.contentful.com", c.BaseURL) + }) + t.Run("EU sets CPA BaseURL", func(t *testing.T) { + c := NewCPA(CPAToken) + c.SetRegion(RegionEU) + assert.Equal(t, "https://preview.eu.contentful.com", c.BaseURL) + }) + t.Run("SetRegion is chainable", func(t *testing.T) { + c := NewCMA(CMAToken).SetRegion(RegionEU) + assert.Equal(t, "https://api.eu.contentful.com", c.BaseURL) + }) +} + func TestContentfulSetClient(t *testing.T) { newClient := &http.Client{} cma := NewCMA(CMAToken) From 9b0e0c8f56c0049ae70f3caa51540f2ef9070403 Mon Sep 17 00:00:00 2001 From: Miroslav Cvetic Date: Wed, 1 Jul 2026 14:34:41 +0200 Subject: [PATCH 2/4] feat: move SetRegion method after the constructors --- contentful.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/contentful.go b/contentful.go index 17e7cd6..e12cb80 100644 --- a/contentful.go +++ b/contentful.go @@ -48,20 +48,6 @@ const ( RegionEU Region = "eu" ) -// SetRegion configures all API base URLs for the given region. -// RegionUS and the empty string are no-ops; the client keeps its constructor defaults. -func (c *Contentful) SetRegion(region Region) *Contentful { - if region == "" || region == RegionUS { - return c - } - subdomain := "." + string(region) - c.BaseURL = strings.Replace(c.BaseURL, ".contentful.com", subdomain+".contentful.com", 1) - if c.UploadURL != "" { - c.UploadURL = strings.Replace(c.UploadURL, ".contentful.com", subdomain+".contentful.com", 1) - } - return c -} - type service struct { c *Contentful } @@ -154,6 +140,20 @@ func NewCPA(token string) *Contentful { return c } +// SetRegion configures all API base URLs for the given region. +// RegionUS and the empty string are no-ops; the client keeps its constructor defaults. +func (c *Contentful) SetRegion(region Region) *Contentful { + if region == "" || region == RegionUS { + return c + } + subdomain := "." + string(region) + c.BaseURL = strings.Replace(c.BaseURL, ".contentful.com", subdomain+".contentful.com", 1) + if c.UploadURL != "" { + c.UploadURL = strings.Replace(c.UploadURL, ".contentful.com", subdomain+".contentful.com", 1) + } + return c +} + // SetOrganization sets the given organization id func (c *Contentful) SetOrganization(organizationID string) *Contentful { c.Headers["X-Contentful-Organization"] = organizationID From b625749a7b6d1a9d40bfb312ff2afd1ba20a00fa Mon Sep 17 00:00:00 2001 From: Miroslav Cvetic Date: Wed, 1 Jul 2026 15:32:11 +0200 Subject: [PATCH 3/4] feat: refactor region handling into separate file and add tests --- contentful.go | 8 -------- region.go | 22 ++++++++++++++++++++++ region_test.go | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 region.go create mode 100644 region_test.go diff --git a/contentful.go b/contentful.go index e12cb80..5179e15 100644 --- a/contentful.go +++ b/contentful.go @@ -40,14 +40,6 @@ type Contentful struct { Webhooks *WebhooksService } -// Region identifies a Contentful infrastructure region. -type Region string - -const ( - RegionUS Region = "us" - RegionEU Region = "eu" -) - type service struct { c *Contentful } diff --git a/region.go b/region.go new file mode 100644 index 0000000..96813e5 --- /dev/null +++ b/region.go @@ -0,0 +1,22 @@ +package contentful + +import "fmt" + +// Region identifies a Contentful infrastructure region. +type Region string + +const ( + RegionUS Region = "us" + RegionEU Region = "eu" +) + +// ParseRegion validates s against the known regions and returns the typed Region. +// An empty string is accepted and represents the default (US) region. +func ParseRegion(s string) (Region, error) { + switch Region(s) { + case "", RegionUS, RegionEU: + return Region(s), nil + default: + return "", fmt.Errorf("unknown region %q: supported values are %q and %q", s, RegionUS, RegionEU) + } +} diff --git a/region_test.go b/region_test.go new file mode 100644 index 0000000..5331bbb --- /dev/null +++ b/region_test.go @@ -0,0 +1,34 @@ +package contentful + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseRegion(t *testing.T) { + t.Run("empty string is valid", func(t *testing.T) { + r, err := ParseRegion("") + require.NoError(t, err) + assert.Equal(t, Region(""), r) + }) + t.Run("us is valid", func(t *testing.T) { + r, err := ParseRegion("us") + require.NoError(t, err) + assert.Equal(t, RegionUS, r) + }) + t.Run("eu is valid", func(t *testing.T) { + r, err := ParseRegion("eu") + require.NoError(t, err) + assert.Equal(t, RegionEU, r) + }) + t.Run("uppercase EU is rejected", func(t *testing.T) { + _, err := ParseRegion("EU") + require.Error(t, err) + }) + t.Run("unknown region is rejected", func(t *testing.T) { + _, err := ParseRegion("ap") + require.Error(t, err) + }) +} From b75ebeb0f4cc25d8a6d875889e463768bd9fcb7a Mon Sep 17 00:00:00 2001 From: Miroslav Cvetic Date: Wed, 1 Jul 2026 15:34:21 +0200 Subject: [PATCH 4/4] refactor: improve variable usage in ParseRegion function --- region.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/region.go b/region.go index 96813e5..fdc9942 100644 --- a/region.go +++ b/region.go @@ -13,9 +13,10 @@ const ( // ParseRegion validates s against the known regions and returns the typed Region. // An empty string is accepted and represents the default (US) region. func ParseRegion(s string) (Region, error) { - switch Region(s) { + r := Region(s) + switch r { case "", RegionUS, RegionEU: - return Region(s), nil + return r, nil default: return "", fmt.Errorf("unknown region %q: supported values are %q and %q", s, RegionUS, RegionEU) }