Repository: amp-labs/connectors
Pull request #1045 (2024-09-18 23:00:03)

fix: Time formatting to respect Zone diff
# Background

While working on Gong found that calls and users objects support Since parameter but it must be in RFC3339 time format in UTC only.

# Changes

Gong - support incremental read.
Salesforce - SOQL relied on the same time format `2006-01-02T15:04:05Z`. It was modified next to Gong.

# Warning

This takes into account time zone differences and makes time universal. Salesforce Read is affected. If time was already passed in UTC then no changes will be noticed. Otherwise records could be omitted or duplicated depending on location of servers (likely duplicated).


Cobalt0s: * **#1036** <a href="https://app.graphite.dev/github/pr/amp-labs/connectors/1036?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#1035** <a href="https://app.graphite.dev/github/pr/amp-labs/connectors/1035?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#995** <a href="https://app.graphite.dev/github/pr/amp-labs/connectors/995?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#1045** <a href="https://app.graphite.dev/github/pr/amp-labs/connectors/1045?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈
* `main`

This stack of pull requests is managed by Graphite. <a href="https://stacking.dev/?utm_source=stack-comment">Learn more about stacking.</a>
<h2></h2>

 Join @Cobalt0s and the rest of your teammates on <a href="https://graphite.dev?utm-source=stack-comment"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="11px" height="11px"/> <b>Graphite</b></a>
  <!-- Current dependencies on/for this PR: -->

Patch:
diff --git a/common/handy/maps.go b/common/handy/maps.go
index d2fbb177..f8dfe5a3 100644
--- a/common/handy/maps.go
+++ b/common/handy/maps.go
@@ -14,7 +14,7 @@ func (m Map[K, V]) Keys() []K {
 }
 
 func (m Map[K, V]) KeySet() Set[K] {
-	return NewSet(m.Keys())
+	return NewSetFromList(m.Keys())
 }
 
 func (m Map[K, V]) Has(key K) bool {
diff --git a/common/handy/set.go b/common/handy/set.go
index b1920bae..c891d60c 100644
--- a/common/handy/set.go
+++ b/common/handy/set.go
@@ -1,11 +1,23 @@
 package handy
 
+// NewStringSet and StringSet are Aliases.
+var NewStringSet = NewSet[string] // nolint:gochecknoglobals
+type StringSet = Set[string]
+
 // Set data structure that can hold any type of data.
 type Set[T comparable] map[T]struct{}
 
-// NewSet creates a set from slice.
-func NewSet[V comparable](values []V) Set[V] {
-	result := make(Set[V])
+// NewSet creates a set from multiple values.
+func NewSet[V comparable](values ...V) Set[V] {
+	result := make(Set[V], len(values))
+	result.Add(values)
+
+	return result
+}
+
+// NewSetFromList creates a set from slice.
+func NewSetFromList[V comparable](values []V) Set[V] {
+	result := make(Set[V], len(values))
 	result.Add(values)
 
 	return result
@@ -25,9 +37,12 @@ func (s Set[T]) AddOne(value T) {
 
 // List returns unique set in a shape of a slice.
 func (s Set[T]) List() []T {
-	list := make([]T, 0)
+	list := make([]T, len(s))
+	index := 0
+
 	for v := range s {
-		list = append(list, v)
+		list[index] = v
+		index += 1
 	}
 
 	return list
diff --git a/common/handy/timing.go b/common/handy/timing.go
new file mode 100644
index 00000000..146dda8f
--- /dev/null
+++ b/common/handy/timing.go
@@ -0,0 +1,20 @@
+package handy
+
+import (
+	"time"
+)
+
+type timing struct{}
+
+var Time timing // nolint:gochecknoglobals
+
+// FormatRFC3339inUTC will convert time to UTC respecting the time zone difference
+// and only then apply time.RFC3339 format.
+func (timing) FormatRFC3339inUTC(input time.Time) string {
+	// Format cuts the zone, nanoseconds and other parts which are not used.
+	// Time zone should be preserved.
+	// Apply zone, keeping time difference.
+	utcTime := input.UTC()
+
+	return utcTime.Format("2006-01-02T15:04:05Z")
+}
diff --git a/common/parse.go b/common/parse.go
index 603d443a..ed21752f 100644
--- a/common/parse.go
+++ b/common/parse.go
@@ -3,6 +3,7 @@ package common
 import (
 	"strings"
 
+	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/spyzhov/ajson"
 )
@@ -23,7 +24,7 @@ func ParseResult(
 	recordsFunc func(*ajson.Node) ([]map[string]any, error),
 	nextPageFunc func(*ajson.Node) (string, error),
 	marshalFunc func([]map[string]any, []string) ([]ReadResultRow, error),
-	fields []string,
+	fields handy.Set[string],
 ) (*ReadResult, error) {
 	body, ok := resp.Body()
 	if !ok {
@@ -40,7 +41,7 @@ func ParseResult(
 		return nil, err
 	}
 
-	marshaledData, err := marshalFunc(records, fields)
+	marshaledData, err := marshalFunc(records, fields.List())
 	if err != nil {
 		return nil, err
 	}
diff --git a/common/types.go b/common/types.go
index af66f3db..736acfc0 100644
--- a/common/types.go
+++ b/common/types.go
@@ -5,6 +5,8 @@ import (
 	"errors"
 	"fmt"
 	"time"
+
+	"github.com/amp-labs/connectors/common/handy"
 )
 
 var (
@@ -92,7 +94,7 @@ type ReadParams struct {
 	// The name of the object we are reading, e.g. "Account"
 	ObjectName string // required
 	// The fields we are reading from the object, e.g. ["Id", "Name", "BillingCity"]
-	Fields []string // required, at least one field needed
+	Fields handy.StringSet // required, at least one field needed
 	// NextPage is an opaque token that can be used to get the next page of results.
 	NextPage NextPageToken // optional, only set this if you want to read the next page of results
 	// Since is a timestamp that can be used to get only records that have changed since that time.
diff --git a/connectors.go b/connectors.go
index 13fc6ffc..fc8d8f7e 100644
--- a/connectors.go
+++ b/connectors.go
@@ -6,6 +6,7 @@ import (
 	"io"
 
 	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/providers"
 )
 
@@ -99,3 +100,5 @@ type (
 
 	ErrorWithStatus = common.HTTPStatusError
 )
+
+var Fields = handy.NewStringSet // nolint:gochecknoglobals
diff --git a/examples/deep_connectors/salesforce/salesforce.go b/examples/deep_connectors/salesforce/salesforce.go
index 4446c623..7cf842b1 100644
--- a/examples/deep_connectors/salesforce/salesforce.go
+++ b/examples/deep_connectors/salesforce/salesforce.go
@@ -46,7 +46,7 @@ func salesforceDeepExample(ctx context.Context) error {
 	// Make a read request to Salesforce
 	result, err := conn.Read(ctx, connectors.ReadParams{
 		ObjectName: "Contact",
-		Fields:     []string{"FirstName", "LastName", "Email"},
+		Fields:     connectors.Fields("FirstName", "LastName", "Email"),
 	})
 	if err != nil {
 		return err
@@ -59,7 +59,7 @@ func salesforceDeepExample(ctx context.Context) error {
 	for !result.Done {
 		result, err = conn.Read(ctx, connectors.ReadParams{
 			ObjectName: "Contact",
-			Fields:     []string{"FirstName", "LastName", "Email"},
+			Fields:     connectors.Fields("FirstName", "LastName", "Email"),
 			NextPage:   result.NextPage,
 		})
 		if err != nil {
diff --git a/providers/dynamicscrm/read.go b/providers/dynamicscrm/read.go
index 16675163..8a4bf726 100644
--- a/providers/dynamicscrm/read.go
+++ b/providers/dynamicscrm/read.go
@@ -55,8 +55,9 @@ func (c *Connector) buildReadURL(config common.ReadParams) (*urlbuilder.URL, err
 		return nil, err
 	}
 
-	if len(config.Fields) != 0 {
-		url.WithQueryParam("$select", strings.Join(config.Fields, ","))
+	fields := config.Fields.List()
+	if len(fields) != 0 {
+		url.WithQueryParam("$select", strings.Join(fields, ","))
 	}
 
 	return url, nil
diff --git a/providers/gong/objectNames.go b/providers/gong/objectNames.go
index 26499953..93628d99 100644
--- a/providers/gong/objectNames.go
+++ b/providers/gong/objectNames.go
@@ -6,6 +6,6 @@ import (
 )
 
 // Supported object names can be found under schemas.json.
-var supportedObjectsByRead = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByRead = handy.NewSetFromList( //nolint:gochecknoglobals
 	metadata.Schemas.GetObjectNames(),
 )
diff --git a/providers/gong/read.go b/providers/gong/read.go
index d1db6340..60505394 100644
--- a/providers/gong/read.go
+++ b/providers/gong/read.go
@@ -4,6 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/handy"
 )
 
 func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common.ReadResult, error) {
@@ -24,6 +25,13 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common
 		url.WithQueryParam("cursor", config.NextPage.String())
 	}
 
+	if !config.Since.IsZero() {
+		// This time format is RFC3339 but in UTC only.
+		// See calls or users object for query parameter requirements.
+		// https://gong.app.gong.io/settings/api/documentation#get-/v2/calls
+		url.WithQueryParam("fromDateTime", handy.Time.FormatRFC3339inUTC(config.Since))
+	}
+
 	res, err := c.Client.Get(ctx, url.String())
 	if err != nil {
 		return nil, err
diff --git a/providers/hubspot/read.go b/providers/hubspot/read.go
index 36aa858f..69e4bc61 100644
--- a/providers/hubspot/read.go
+++ b/providers/hubspot/read.go
@@ -78,8 +78,9 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common
 func makeQueryValues(config common.ReadParams) string {
 	queryValues := url.Values{}
 
-	if len(config.Fields) != 0 {
-		queryValues.Add("properties", strings.Join(config.Fields, ","))
+	fields := config.Fields.List()
+	if len(fields) != 0 {
+		queryValues.Add("properties", strings.Join(fields, ","))
 	}
 
 	if config.Deleted {
diff --git a/providers/hubspot/search.go b/providers/hubspot/search.go
index 1b485095..99e044df 100644
--- a/providers/hubspot/search.go
+++ b/providers/hubspot/search.go
@@ -99,7 +99,7 @@ func makeFilterBody(config SearchParams) map[string]any {
 	}
 
 	if config.Fields != nil {
-		filterBody["properties"] = config.Fields
+		filterBody["properties"] = config.Fields.List()
 	}
 
 	return filterBody
diff --git a/providers/hubspot/types.go b/providers/hubspot/types.go
index 45553ac9..03b97795 100644
--- a/providers/hubspot/types.go
+++ b/providers/hubspot/types.go
@@ -1,6 +1,9 @@
 package hubspot
 
-import "github.com/amp-labs/connectors/common"
+import (
+	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/handy"
+)
 
 type SearchParams struct {
 	// The name of the object we are reading, e.g. "Account"
@@ -12,7 +15,7 @@ type SearchParams struct {
 	// FilterBy is the filter to apply to the search
 	FilterGroups []FilterGroup // optional
 	// Fields is the list of fields to return in the result.
-	Fields []string // optional
+	Fields handy.Set[string] // optional
 }
 
 func (p SearchParams) ValidateParams() error {
diff --git a/providers/instantly/objectNames.go b/providers/instantly/objectNames.go
index 35bceb35..9da9e2e4 100644
--- a/providers/instantly/objectNames.go
+++ b/providers/instantly/objectNames.go
@@ -12,24 +12,24 @@ const (
 	objectNameUniboxReplies    = "unibox-replies"
 )
 
-var supportedObjectsByRead = handy.NewSet([]string{ //nolint:gochecknoglobals
+var supportedObjectsByRead = handy.NewSet( //nolint:gochecknoglobals
 	// Object Name	----------	API endpoint path
 	objectNameCampaigns, // campaign/list
 	objectNameAccounts,  // account/list
 	objectNameEmails,    // unibox/emails
 	objectNameTags,      // custom-tag
-})
+)
 
-var supportedObjectsByWrite = handy.NewSet([]string{ //nolint:gochecknoglobals
+var supportedObjectsByWrite = handy.NewSet( //nolint:gochecknoglobals
 	// Object Name	----------	API endpoint path
 	objectNameTags,             // custom-tag
 	objectNameLeads,            // lead/add
 	objectNameBlocklistEntries, // blocklist/add/entries
 	objectNameUniboxReplies,    // unibox/emails/reply
-})
+)
 
-var supportedObjectsByDelete = handy.NewSet([]string{ //nolint:gochecknoglobals
+var supportedObjectsByDelete = handy.NewSet( //nolint:gochecknoglobals
 	// Delete tag.
 	// https://developer.instantly.ai/tags/delete-a-tag
 	objectNameTags,
-})
+)
diff --git a/providers/intercom/objectNames.go b/providers/intercom/objectNames.go
index c016caee..3ae5c7ce 100644
--- a/providers/intercom/objectNames.go
+++ b/providers/intercom/objectNames.go
@@ -6,7 +6,7 @@ import (
 )
 
 // Supported object names can be found under schemas.json.
-var supportedObjectsByRead = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByRead = handy.NewSetFromList( //nolint:gochecknoglobals
 	metadata.Schemas.GetObjectNames(),
 )
 
diff --git a/providers/pipeliner/objectNames.go b/providers/pipeliner/objectNames.go
index b2df4148..c5b640cb 100644
--- a/providers/pipeliner/objectNames.go
+++ b/providers/pipeliner/objectNames.go
@@ -6,6 +6,6 @@ import (
 )
 
 // Supported object names can be found under schemas.json.
-var supportedObjectsByRead = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByRead = handy.NewSetFromList( //nolint:gochecknoglobals
 	metadata.Schemas.GetObjectNames(),
 )
diff --git a/providers/salesforce/bulk-info.go b/providers/salesforce/bulk-info.go
index 4174fe75..0ad1b0a6 100644
--- a/providers/salesforce/bulk-info.go
+++ b/providers/salesforce/bulk-info.go
@@ -36,7 +36,7 @@ func (c *Connector) ListIngestJobsInfo(ctx context.Context, jobIds ...string) ([
 	// If we have jobIds, we create a set to keep track of the matches we need to find. Each time we get
 	// a match, we remove it from the set. If the set is empty, we can break the loop to save time and unnecessary
 	// pagination.
-	pending := handy.NewSet[string](jobIds)
+	pending := handy.NewSetFromList(jobIds)
 
 	// To keep track of pages
 	location := url.String()
diff --git a/providers/salesforce/read.go b/providers/salesforce/read.go
index 5b04db17..ea88fdfa 100644
--- a/providers/salesforce/read.go
+++ b/providers/salesforce/read.go
@@ -4,6 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/common/urlbuilder"
 )
 
@@ -54,11 +55,11 @@ func (c *Connector) buildReadURL(config common.ReadParams) (*urlbuilder.URL, err
 
 // makeSOQL returns the SOQL query for the desired read operation.
 func makeSOQL(config common.ReadParams) *soqlBuilder {
-	soql := (&soqlBuilder{}).SelectFields(config.Fields).From(config.ObjectName)
+	soql := (&soqlBuilder{}).SelectFields(config.Fields.List()).From(config.ObjectName)
 
 	// If Since is not set, then we're doing a backfill. We read all rows (in pages)
 	if !config.Since.IsZero() {
-		soql.Where("SystemModstamp > " + config.Since.Format("2006-01-02T15:04:05Z"))
+		soql.Where("SystemModstamp > " + handy.Time.FormatRFC3339inUTC(config.Since))
 	}
 
 	if config.Deleted {
diff --git a/providers/salesloft/objectNames.go b/providers/salesloft/objectNames.go
index 2647abe6..e2be6aad 100644
--- a/providers/salesloft/objectNames.go
+++ b/providers/salesloft/objectNames.go
@@ -6,6 +6,6 @@ import (
 )
 
 // Supported object names can be found under schemas.json.
-var supportedObjectsByRead = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByRead = handy.NewSetFromList( //nolint:gochecknoglobals
 	metadata.Schemas.GetObjectNames(),
 )
diff --git a/providers/smartlead/objectNames.go b/providers/smartlead/objectNames.go
index 34d2f656..55a9b55b 100644
--- a/providers/smartlead/objectNames.go
+++ b/providers/smartlead/objectNames.go
@@ -12,16 +12,16 @@ const (
 )
 
 // Supported object names can be found under schemas.json.
-var supportedObjectsByRead = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByRead = handy.NewSetFromList( //nolint:gochecknoglobals
 	metadata.Schemas.GetObjectNames(),
 )
 
-var supportedObjectsByWrite = handy.NewSet([]string{ //nolint:gochecknoglobals
+var supportedObjectsByWrite = handy.NewSet( //nolint:gochecknoglobals
 	objectNameCampaign,
 	objectNameEmailAccount,
 	objectNameClient,
-})
+)
 
-var supportedObjectsByDelete = handy.NewSet([]string{ //nolint:gochecknoglobals
+var supportedObjectsByDelete = handy.NewSet( //nolint:gochecknoglobals
 	objectNameCampaign,
-})
+)
diff --git a/providers/zendesksupport/objectNames.go b/providers/zendesksupport/objectNames.go
index 0d6cece4..8cadcb2c 100644
--- a/providers/zendesksupport/objectNames.go
+++ b/providers/zendesksupport/objectNames.go
@@ -6,7 +6,7 @@ import (
 )
 
 // Supported object names can be found under schemas.json.
-var supportedObjectsByRead = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByRead = handy.NewSetFromList( //nolint:gochecknoglobals
 	metadata.Schemas.GetObjectNames(),
 )
 
diff --git a/scripts/openapi/gong/metadata/main.go b/scripts/openapi/gong/metadata/main.go
index 0cce67b6..90377650 100644
--- a/scripts/openapi/gong/metadata/main.go
+++ b/scripts/openapi/gong/metadata/main.go
@@ -15,7 +15,7 @@ var (
 	// Even though OpenAPI docs and official documentation say that some query parameters are required
 	// in practice you still can make an API call without any specified.
 	// Must include "calls" object.
-	queryParamFilterExceptions = handy.NewSet([]string{"calls"}) // nolint:gochecknoglobals
+	queryParamFilterExceptions = handy.NewSet("calls") // nolint:gochecknoglobals
 
 	ignoreEndpoints = []string{ // nolint:gochecknoglobals
 		"/v2/settings/scorecards",
diff --git a/tools/fileconv/api3/strategy.go b/tools/fileconv/api3/strategy.go
index 6f84b019..0beffdcc 100644
--- a/tools/fileconv/api3/strategy.go
+++ b/tools/fileconv/api3/strategy.go
@@ -13,14 +13,14 @@ import (
 // Suffix:	*/batch		- ignores paths ending with batch
 // Prefix:	/v2/*		- ignores paths starting with v2.
 type ignorePathStrategy struct {
-	ignoreEndpoints handy.Set[string]
+	ignoreEndpoints handy.StringSet
 	prefixes        []string
 	suffixes        []string
 }
 
 func newIgnorePathStrategy(endpoints []string) *ignorePathStrategy {
 	result := &ignorePathStrategy{
-		ignoreEndpoints: handy.NewSet([]string{}),
+		ignoreEndpoints: handy.NewStringSet(),
 		prefixes:        make([]string, 0),
 		suffixes:        make([]string, 0),
 	}
diff --git a/tools/scrapper/models.go b/tools/scrapper/models.go
index fb1b4e45..35e2671a 100644
--- a/tools/scrapper/models.go
+++ b/tools/scrapper/models.go
@@ -130,7 +130,7 @@ type queryParamObjectStats struct {
 // CalculateQueryParamStats produces statistics on objects and their query parameters.
 // queryParamRegistry - holds query parameter name to the list of object names that use it.
 func CalculateQueryParamStats(queryParamRegistry handy.Lists[string]) *QueryParamStats {
-	objects := handy.NewSet([]string{})
+	objects := handy.NewStringSet()
 	for _, objectNames := range queryParamRegistry {
 		objects.Add(objectNames)
 	}


Test patch:
diff --git a/common/handy/set_test.go b/common/handy/set_test.go
new file mode 100644
index 00000000..21e72b73
--- /dev/null
+++ b/common/handy/set_test.go
@@ -0,0 +1,47 @@
+package handy
+
+import (
+	"sort"
+	"testing"
+
+	"github.com/amp-labs/connectors/test/utils/testutils"
+)
+
+func TestStringSet(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name     string
+		input    []string
+		expected []string
+	}{
+		{
+			name:     "Empty set",
+			input:    []string{},
+			expected: []string{},
+		},
+		{
+			name:     "Multiple elements",
+			input:    []string{"apple", "pineapple"},
+			expected: []string{"apple", "pineapple"},
+		},
+		{
+			name:     "Item repetitions",
+			input:    []string{"apple", "kiwi", "kiwi", "orange", "pineapple", "kiwi"},
+			expected: []string{"apple", "kiwi", "orange", "pineapple"},
+		},
+	}
+
+	for _, tt := range tests {
+		// nolint:varnamelen
+		tt := tt // rebind, omit loop side effects for parallel goroutine
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			set := NewStringSet(tt.input...)
+			output := set.List()
+			sort.Strings(output)
+			testutils.CheckOutputWithError(t, tt.name, tt.expected, nil, output, nil)
+		})
+	}
+}
diff --git a/common/handy/timing_test.go b/common/handy/timing_test.go
new file mode 100644
index 00000000..57f73497
--- /dev/null
+++ b/common/handy/timing_test.go
@@ -0,0 +1,60 @@
+package handy
+
+import (
+	"testing"
+	"time"
+
+	"github.com/amp-labs/connectors/test/utils/testutils"
+)
+
+func TestTimingFormatRFC3339inUTC(t *testing.T) {
+	t.Parallel()
+
+	const hour = 60 * 60
+
+	zoneUTC := time.FixedZone("UTC", 0)
+	zonePacific := time.FixedZone("UTC-8", -8*hour)
+	zoneEasternEu := time.FixedZone("UTC+2", 2*hour)
+
+	createTimeIn := func(zone *time.Location) time.Time {
+		return time.Date(2024, 9, 19, 4, 30, 45, 600, zone)
+	}
+
+	tests := []struct {
+		name     string
+		input    time.Time
+		expected string
+	}{
+		{
+			name:     "Time origin",
+			input:    time.Time{},
+			expected: "0001-01-01T00:00:00Z",
+		},
+		{
+			name:     "UTC time",
+			input:    createTimeIn(zoneUTC),
+			expected: "2024-09-19T04:30:45Z",
+		},
+		{
+			name:     "Pacific time",
+			input:    createTimeIn(zonePacific),
+			expected: "2024-09-19T12:30:45Z",
+		},
+		{
+			name:     "Eastern EU time",
+			input:    createTimeIn(zoneEasternEu),
+			expected: "2024-09-19T02:30:45Z",
+		},
+	}
+
+	for _, tt := range tests {
+		// nolint:varnamelen
+		tt := tt // rebind, omit loop side effects for parallel goroutine
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			output := Time.FormatRFC3339inUTC(tt.input)
+			testutils.CheckOutputWithError(t, tt.name, tt.expected, nil, output, nil)
+		})
+	}
+}
diff --git a/providers/atlassian/read_test.go b/providers/atlassian/read_test.go
index 63bcc824..ac63896f 100644
--- a/providers/atlassian/read_test.go
+++ b/providers/atlassian/read_test.go
@@ -40,13 +40,13 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:         "Mime response header expected",
-			Input:        common.ReadParams{ObjectName: "issues", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "issues", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{interpreter.ErrMissingContentType},
 		},
 		{
 			Name:  "Correct error message is understood from JSON response",
-			Input: common.ReadParams{ObjectName: "issues", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "issues", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusBadRequest)
@@ -59,7 +59,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Invalid path understood as not found error",
-			Input: common.ReadParams{ObjectName: "issues", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "issues", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusNotFound)
@@ -72,7 +72,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect key in payload",
-			Input: common.ReadParams{ObjectName: "issues", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "issues", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -84,7 +84,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect data type in payload",
-			Input: common.ReadParams{ObjectName: "issues", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "issues", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -96,7 +96,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Empty array produces no next page",
-			Input: common.ReadParams{ObjectName: "issues", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "issues", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -118,7 +118,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Issue must have fields property",
-			Input: common.ReadParams{ObjectName: "issues", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "issues", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -131,7 +131,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Issue must have id property",
-			Input: common.ReadParams{ObjectName: "issues", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "issues", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -144,7 +144,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Missing starting index produces no next page",
-			Input: common.ReadParams{ObjectName: "issues", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "issues", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -167,7 +167,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page is implied from start index and issues size",
-			Input: common.ReadParams{ObjectName: "issues", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "issues", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -193,7 +193,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Since rounds to minute time frame",
 			Input: common.ReadParams{
 				ObjectName: "issues",
-				Fields:     []string{"id"},
+				Fields:     connectors.Fields("id"),
 				Since:      time.Now().Add(-5 * time.Minute),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -222,7 +222,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Next page is propagated in query params",
 			Input: common.ReadParams{
 				ObjectName: "issues",
-				Fields:     []string{"id"},
+				Fields:     connectors.Fields("id"),
 				NextPage:   "17",
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -253,7 +253,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Successful list of rows",
 			Input: common.ReadParams{
 				ObjectName: "issues",
-				Fields:     []string{"id", "summary"},
+				Fields:     connectors.Fields("id", "summary"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
@@ -324,7 +324,10 @@ func TestReadWithoutMetadata(t *testing.T) {
 		t.Fatal("failed to create connector")
 	}
 
-	_, err = connector.Read(context.Background(), common.ReadParams{ObjectName: "issues", Fields: []string{"id"}})
+	_, err = connector.Read(context.Background(), common.ReadParams{
+		ObjectName: "issues",
+		Fields:     connectors.Fields("id"),
+	})
 	if !errors.Is(err, ErrMissingCloudId) {
 		t.Fatalf("expected Read method to complain about missing cloud id")
 	}
diff --git a/providers/dynamicscrm/read_test.go b/providers/dynamicscrm/read_test.go
index cf8c5125..dbacbd89 100644
--- a/providers/dynamicscrm/read_test.go
+++ b/providers/dynamicscrm/read_test.go
@@ -35,13 +35,13 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:         "Mime response header expected",
-			Input:        common.ReadParams{ObjectName: "contact", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "contact", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{interpreter.ErrMissingContentType},
 		},
 		{
 			Name:  "Correct error message is understood from JSON response",
-			Input: common.ReadParams{ObjectName: "contact", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "contact", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusBadRequest)
@@ -58,7 +58,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Incorrect key in payload",
-			Input: common.ReadParams{ObjectName: "contact", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "contact", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -70,7 +70,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Incorrect data type in payload",
-			Input: common.ReadParams{ObjectName: "contact", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "contact", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -82,7 +82,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Next page cursor may be missing in payload",
-			Input: common.ReadParams{ObjectName: "contact", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "contact", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -98,7 +98,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Successful read with chosen fields",
-			Input: common.ReadParams{ObjectName: "contact", Fields: []string{"fullname", "fax"}},
+			Input: common.ReadParams{ObjectName: "contact", Fields: connectors.Fields("fullname", "fax")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
diff --git a/providers/gong/read_test.go b/providers/gong/read_test.go
index 6ce82be9..d3fdb5c4 100644
--- a/providers/gong/read_test.go
+++ b/providers/gong/read_test.go
@@ -4,7 +4,9 @@ import (
 	"errors"
 	"net/http"
 	"net/http/httptest"
+	"net/url"
 	"testing"
+	"time"
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
@@ -36,19 +38,19 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:         "Unsupported object name",
-			Input:        common.ReadParams{ObjectName: "butterflies", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "butterflies", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{common.ErrOperationNotSupportedForObject},
 		},
 		{
 			Name:         "Mime response header expected",
-			Input:        common.ReadParams{ObjectName: "calls", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "calls", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{interpreter.ErrMissingContentType},
 		},
 		{
 			Name:  "Incorrect key in payload",
-			Input: common.ReadParams{ObjectName: "calls", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "calls", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -60,7 +62,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Bad request handling test",
-			Input: common.ReadParams{ObjectName: "calls", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "calls", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusBadRequest)
@@ -77,7 +79,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Records section is missing in the payload",
-			Input: common.ReadParams{ObjectName: "calls", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "calls", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -89,7 +91,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "currentPageSize may be missing in payload",
-			Input: common.ReadParams{ObjectName: "calls", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "calls", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -110,9 +112,34 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 			},
 			ExpectedErrs: nil,
 		},
+		{
+			Name: "Since parameter is reflected in query parameter",
+			Input: common.ReadParams{
+				ObjectName: "calls",
+				Fields:     []string{"id"},
+				Since: time.Date(2024, 9, 19, 4, 30, 45, 600,
+					time.FixedZone("UTC-8", -8*60*60)),
+			},
+			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.Header().Set("Content-Type", "application/json")
+				w.WriteHeader(http.StatusOK)
+				mockutils.RespondToQueryParameters(w, r, url.Values{
+					// Pacific time to UTC is achieved by adding 8 hours
+					"fromDateTime": []string{"2024-09-19T12:30:45Z"},
+				}, func() {
+					w.WriteHeader(http.StatusOK)
+					_, _ = w.Write(fakeServerResp)
+				})
+			})),
+			Comparator: func(serverURL string, actual, expected *common.ReadResult) bool {
+				return actual.Rows == expected.Rows
+			},
+			Expected:     &common.ReadResult{Rows: 2},
+			ExpectedErrs: nil,
+		},
 		{
 			Name:  "Successful read with 2 entries without cursor/next page",
-			Input: common.ReadParams{ObjectName: "calls", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "calls", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -150,7 +177,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 
 		{
 			Name:  "Successful read with 2 entries and cursor for next page",
-			Input: common.ReadParams{ObjectName: "calls", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "calls", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -188,7 +215,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Incorrect data type in payload",
-			Input: common.ReadParams{ObjectName: "calls", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "calls", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
diff --git a/providers/instantly/read_test.go b/providers/instantly/read_test.go
index eb2fa1d0..68e9ad58 100644
--- a/providers/instantly/read_test.go
+++ b/providers/instantly/read_test.go
@@ -40,13 +40,13 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:         "Mime response header expected",
-			Input:        common.ReadParams{ObjectName: "campaigns", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "campaigns", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{interpreter.ErrMissingContentType},
 		},
 		{
 			Name:     "Unknown object name is not supported",
-			Input:    common.ReadParams{ObjectName: "orders", Fields: []string{"id"}},
+			Input:    common.ReadParams{ObjectName: "orders", Fields: connectors.Fields("id")},
 			Server:   mockserver.Dummy(),
 			Expected: nil,
 			ExpectedErrs: []error{
@@ -55,7 +55,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Correct error message is understood from JSON response",
-			Input: common.ReadParams{ObjectName: "campaigns", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "campaigns", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusNotFound)
@@ -68,7 +68,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect key in payload",
-			Input: common.ReadParams{ObjectName: "emails", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "emails", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -80,7 +80,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect data type in payload",
-			Input: common.ReadParams{ObjectName: "emails", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "emails", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -95,7 +95,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Input: common.ReadParams{
 				ObjectName: "campaigns",
 				NextPage:   "test-placeholder?skip=700",
-				Fields:     []string{"id"},
+				Fields:     connectors.Fields("id"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
@@ -122,7 +122,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Current empty page signifies no next page",
 			Input: common.ReadParams{
 				ObjectName: "campaigns",
-				Fields:     []string{"id"},
+				Fields:     connectors.Fields("id"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
@@ -145,7 +145,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Read campaigns with chosen fields",
 			Input: common.ReadParams{
 				ObjectName: "campaigns",
-				Fields:     []string{"name"},
+				Fields:     connectors.Fields("name"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
@@ -184,7 +184,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Read tags with chosen fields",
 			Input: common.ReadParams{
 				ObjectName: "tags",
-				Fields:     []string{"label", "description"},
+				Fields:     connectors.Fields("label", "description"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/providers/intercom/read_test.go b/providers/intercom/read_test.go
index 7d4e3160..b6f947de 100644
--- a/providers/intercom/read_test.go
+++ b/providers/intercom/read_test.go
@@ -48,19 +48,19 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:         "Unsupported object name",
-			Input:        common.ReadParams{ObjectName: "butterflies", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "butterflies", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{common.ErrOperationNotSupportedForObject},
 		},
 		{
 			Name:         "Mime response header expected",
-			Input:        common.ReadParams{ObjectName: "contacts", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "contacts", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{interpreter.ErrMissingContentType},
 		},
 		{
 			Name:  "Correct error message is understood from JSON response",
-			Input: common.ReadParams{ObjectName: "contacts", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "contacts", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusNotFound)
@@ -72,7 +72,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect key in payload",
-			Input: common.ReadParams{ObjectName: "contacts", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "contacts", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -84,7 +84,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect data type in payload",
-			Input: common.ReadParams{ObjectName: "contacts", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "contacts", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -96,7 +96,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page cursor may be missing in payload",
-			Input: common.ReadParams{ObjectName: "contacts", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "contacts", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -114,7 +114,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "API version header is passed as server request",
-			Input: common.ReadParams{ObjectName: "articles", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "articles", Fields: connectors.Fields("id")},
 			// notes is not supported for now, but its payload is good for testing
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
@@ -132,7 +132,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page URL is resolved, when provided with a string",
-			Input: common.ReadParams{ObjectName: "articles", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "articles", Fields: connectors.Fields("id")},
 			// notes is not supported for now, but its payload is good for testing
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
@@ -149,7 +149,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page URL is inferred, when provided with an object",
-			Input: common.ReadParams{ObjectName: "contacts", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "contacts", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -167,7 +167,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page URL is empty, when provided with null object",
-			Input: common.ReadParams{ObjectName: "articles", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "articles", Fields: connectors.Fields("id")},
 			// notes is not supported for now, but its payload is good for testing
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
@@ -183,7 +183,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page URL is empty, when provided with missing object",
-			Input: common.ReadParams{ObjectName: "contacts", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "contacts", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -200,7 +200,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Successful read with chosen fields",
 			Input: common.ReadParams{
 				ObjectName: "contacts",
-				Fields:     []string{"email", "name"},
+				Fields:     connectors.Fields("email", "name"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
@@ -242,7 +242,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Successful read of named list",
 			Input: common.ReadParams{
 				ObjectName: "conversations",
-				Fields:     []string{"state"},
+				Fields:     connectors.Fields("state"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/providers/pipeliner/read_test.go b/providers/pipeliner/read_test.go
index c2c343c4..ade5c541 100644
--- a/providers/pipeliner/read_test.go
+++ b/providers/pipeliner/read_test.go
@@ -39,19 +39,19 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:         "Unsupported object name",
-			Input:        common.ReadParams{ObjectName: "butterflies", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "butterflies", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{common.ErrOperationNotSupportedForObject},
 		},
 		{
 			Name:         "Mime response header expected",
-			Input:        common.ReadParams{ObjectName: "Profiles", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "Profiles", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{interpreter.ErrMissingContentType},
 		},
 		{
 			Name:  "Correct error message is understood from JSON response",
-			Input: common.ReadParams{ObjectName: "Profiles", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "Profiles", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusNotFound)
@@ -64,7 +64,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect key in payload",
-			Input: common.ReadParams{ObjectName: "Profiles", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "Profiles", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -76,7 +76,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect data type in payload",
-			Input: common.ReadParams{ObjectName: "Profiles", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "Profiles", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -88,7 +88,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page cursor may be missing in payload",
-			Input: common.ReadParams{ObjectName: "Profiles", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "Profiles", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -107,7 +107,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page URL is inferred, when provided with an object",
-			Input: common.ReadParams{ObjectName: "Profiles", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "Profiles", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -123,7 +123,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page URL is empty, when provided with null object",
-			Input: common.ReadParams{ObjectName: "Profiles", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "Profiles", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -140,7 +140,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Successful read with chosen fields",
 			Input: common.ReadParams{
 				ObjectName: "Profiles",
-				Fields:     []string{"name", "owner_id"},
+				Fields:     connectors.Fields("name", "owner_id"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/providers/salesforce/read_test.go b/providers/salesforce/read_test.go
index 24f85d92..87d6c858 100644
--- a/providers/salesforce/read_test.go
+++ b/providers/salesforce/read_test.go
@@ -37,13 +37,13 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:         "Mime response header expected",
-			Input:        common.ReadParams{ObjectName: "leads", Fields: []string{"Name"}},
+			Input:        common.ReadParams{ObjectName: "leads", Fields: connectors.Fields("Name")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{interpreter.ErrMissingContentType},
 		},
 		{
 			Name:  "Correct error message is understood from JSON response",
-			Input: common.ReadParams{ObjectName: "leads", Fields: []string{"Name"}},
+			Input: common.ReadParams{ObjectName: "leads", Fields: connectors.Fields("Name")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusBadRequest)
@@ -55,7 +55,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect key in payload",
-			Input: common.ReadParams{ObjectName: "leads", Fields: []string{"Name"}},
+			Input: common.ReadParams{ObjectName: "leads", Fields: connectors.Fields("Name")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -67,7 +67,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect data type in payload",
-			Input: common.ReadParams{ObjectName: "leads", Fields: []string{"Name"}},
+			Input: common.ReadParams{ObjectName: "leads", Fields: connectors.Fields("Name")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -79,7 +79,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page cursor may be missing in payload",
-			Input: common.ReadParams{ObjectName: "leads", Fields: []string{"Name"}},
+			Input: common.ReadParams{ObjectName: "leads", Fields: connectors.Fields("Name")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -96,7 +96,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page URL is resolved, when provided with a string",
-			Input: common.ReadParams{ObjectName: "leads", Fields: []string{"City"}},
+			Input: common.ReadParams{ObjectName: "leads", Fields: connectors.Fields("City")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -114,7 +114,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Successful read with chosen fields",
 			Input: common.ReadParams{
 				ObjectName: "contacts",
-				Fields:     []string{"Department", "AssistantName"},
+				Fields:     connectors.Fields("Department", "AssistantName"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/providers/salesloft/read_test.go b/providers/salesloft/read_test.go
index 12ce5be7..d0e2405d 100644
--- a/providers/salesloft/read_test.go
+++ b/providers/salesloft/read_test.go
@@ -44,19 +44,19 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:         "Unsupported object name",
-			Input:        common.ReadParams{ObjectName: "butterflies", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "butterflies", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{common.ErrOperationNotSupportedForObject},
 		},
 		{
 			Name:         "Mime response header expected",
-			Input:        common.ReadParams{ObjectName: "users", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "users", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{interpreter.ErrMissingContentType},
 		},
 		{
 			Name:  "Correct error message is understood from JSON response",
-			Input: common.ReadParams{ObjectName: "users", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "users", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusNotFound)
@@ -70,7 +70,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect key in payload",
-			Input: common.ReadParams{ObjectName: "users", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "users", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -82,7 +82,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect data type in payload",
-			Input: common.ReadParams{ObjectName: "users", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "users", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -94,7 +94,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page cursor may be missing in payload",
-			Input: common.ReadParams{ObjectName: "users", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "users", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -108,7 +108,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page URL is correctly inferred",
-			Input: common.ReadParams{ObjectName: "people", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "people", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -125,7 +125,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Successful read with 25 entries, checking one row",
-			Input: common.ReadParams{ObjectName: "people", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "people", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -156,7 +156,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Successful read with chosen fields",
 			Input: common.ReadParams{
 				ObjectName: "people",
-				Fields:     []string{"email_address", "person_company_website"},
+				Fields:     connectors.Fields("email_address", "person_company_website"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
@@ -187,7 +187,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Listing Users without pagination payload",
 			Input: common.ReadParams{
 				ObjectName: "users",
-				Fields:     []string{"email", "guid"},
+				Fields:     connectors.Fields("email", "guid"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
@@ -218,7 +218,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Successful read accounts without since query",
-			Input: common.ReadParams{ObjectName: "accounts", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "accounts", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				mockutils.RespondToMissingQueryParameters(w, r, []string{"updated_at[gte]"}, func() {
@@ -239,7 +239,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Input: common.ReadParams{
 				ObjectName: "accounts",
 				Since:      accountsSince,
-				Fields:     []string{"id"},
+				Fields:     connectors.Fields("id"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/providers/smartlead/read_test.go b/providers/smartlead/read_test.go
index 3d68f21d..fa462376 100644
--- a/providers/smartlead/read_test.go
+++ b/providers/smartlead/read_test.go
@@ -38,19 +38,19 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:         "Unsupported object name",
-			Input:        common.ReadParams{ObjectName: "butterflies", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "butterflies", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{common.ErrOperationNotSupportedForObject},
 		},
 		{
 			Name:         "Mime response header expected",
-			Input:        common.ReadParams{ObjectName: "email-accounts", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "email-accounts", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{interpreter.ErrMissingContentType},
 		},
 		{
 			Name:  "Correct error message is understood from HTML response",
-			Input: common.ReadParams{ObjectName: "email-accounts", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "email-accounts", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "text/html")
 				w.WriteHeader(http.StatusBadRequest)
@@ -63,7 +63,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Incorrect data type in payload",
-			Input: common.ReadParams{ObjectName: "email-accounts", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "email-accounts", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -73,7 +73,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Empty read response",
-			Input: common.ReadParams{ObjectName: "campaigns", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "campaigns", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -89,7 +89,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Successful read with chosen fields",
-			Input: common.ReadParams{ObjectName: "campaigns", Fields: []string{"name", "status"}},
+			Input: common.ReadParams{ObjectName: "campaigns", Fields: connectors.Fields("name", "status")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
diff --git a/providers/zendesksupport/read_test.go b/providers/zendesksupport/read_test.go
index 7587e748..dfdad557 100644
--- a/providers/zendesksupport/read_test.go
+++ b/providers/zendesksupport/read_test.go
@@ -39,13 +39,13 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:         "Mime response header expected",
-			Input:        common.ReadParams{ObjectName: "triggers", Fields: []string{"id"}},
+			Input:        common.ReadParams{ObjectName: "triggers", Fields: connectors.Fields("id")},
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{interpreter.ErrMissingContentType},
 		},
 		{
 			Name:  "Correct error message is understood from JSON response",
-			Input: common.ReadParams{ObjectName: "triggers", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "triggers", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusNotFound)
@@ -57,7 +57,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Forbidden error code and response",
-			Input: common.ReadParams{ObjectName: "triggers", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "triggers", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusForbidden)
@@ -69,7 +69,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect key in payload",
-			Input: common.ReadParams{ObjectName: "triggers", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "triggers", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -81,7 +81,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Incorrect data type in payload",
-			Input: common.ReadParams{ObjectName: "triggers", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "triggers", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -93,7 +93,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page cursor may be missing in payload",
-			Input: common.ReadParams{ObjectName: "users", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "users", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -107,7 +107,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		},
 		{
 			Name:  "Next page URL is resolved, when provided with a string",
-			Input: common.ReadParams{ObjectName: "users", Fields: []string{"id"}},
+			Input: common.ReadParams{ObjectName: "users", Fields: connectors.Fields("id")},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
@@ -126,7 +126,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Name: "Successful read with chosen fields",
 			Input: common.ReadParams{
 				ObjectName: "tickets",
-				Fields:     []string{"type", "subject", "status"},
+				Fields:     connectors.Fields("type", "subject", "status"),
 			},
 			Server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/test/apollo/read/read.go b/test/apollo/read/read.go
index 4d3b47ba..1792ca5f 100644
--- a/test/apollo/read/read.go
+++ b/test/apollo/read/read.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"github.com/amp-labs/connectors"
 	"log"
 	"os"
 	"time"
@@ -43,7 +44,7 @@ func MainFn() int {
 func testReadOpportunitiesSearch(ctx context.Context, conn *ap.Connector) error {
 	params := common.ReadParams{
 		ObjectName: "opportunities",
-		Fields:     []string{"id"},
+		Fields:     connectors.Fields("id"),
 	}
 
 	res, err := conn.Read(ctx, params)
@@ -66,7 +67,7 @@ func testReadOpportunitiesSearch(ctx context.Context, conn *ap.Connector) error
 func testReadEmailAccounts(ctx context.Context, conn *ap.Connector) error {
 	params := common.ReadParams{
 		ObjectName: "email_accounts",
-		Fields:     []string{"user_id", "id", "email"},
+		Fields:     connectors.Fields("user_id", "id", "email"),
 		Since:      time.Now().Add(-1800 * time.Hour),
 	}
 
@@ -90,7 +91,7 @@ func testReadEmailAccounts(ctx context.Context, conn *ap.Connector) error {
 func testReadCustomFields(ctx context.Context, conn *ap.Connector) error {
 	params := common.ReadParams{
 		ObjectName: "typed_custom_fields",
-		Fields:     []string{"type", "id", "modality"},
+		Fields:     connectors.Fields("type", "id", "modality"),
 		Since:      time.Now().Add(-1800 * time.Hour),
 	}
 
diff --git a/test/apollo/search/search.go b/test/apollo/search/search.go
index c32372be..5eb03c61 100644
--- a/test/apollo/search/search.go
+++ b/test/apollo/search/search.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"github.com/amp-labs/connectors"
 	"log"
 	"os"
 
@@ -42,7 +43,7 @@ func MainFn() int {
 func testReadContactsSearch(ctx context.Context, conn *ap.Connector) error {
 	params := common.ReadParams{
 		ObjectName: "contacts",
-		Fields:     []string{"id"},
+		Fields:     connectors.Fields("id"),
 		// NextPage:   "2",
 	}
 
@@ -66,7 +67,7 @@ func testReadContactsSearch(ctx context.Context, conn *ap.Connector) error {
 func testReadOpportunitiesSearch(ctx context.Context, conn *ap.Connector) error {
 	params := common.ReadParams{
 		ObjectName: "opportunities",
-		Fields:     []string{"id"},
+		Fields:     connectors.Fields("id"),
 	}
 
 	res, err := conn.Search(ctx, params)
@@ -89,7 +90,7 @@ func testReadOpportunitiesSearch(ctx context.Context, conn *ap.Connector) error
 func testReadPeopleSearch(ctx context.Context, conn *ap.Connector) error {
 	params := common.ReadParams{
 		ObjectName: "mixed_people",
-		Fields:     []string{"id"},
+		Fields:     connectors.Fields("id"),
 	}
 
 	res, err := conn.Search(ctx, params)
diff --git a/test/atlassian/read/main.go b/test/atlassian/read/main.go
index 71c286dd..39a31d76 100644
--- a/test/atlassian/read/main.go
+++ b/test/atlassian/read/main.go
@@ -7,6 +7,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	connTest "github.com/amp-labs/connectors/test/atlassian"
 	"github.com/amp-labs/connectors/test/utils"
@@ -24,9 +25,7 @@ func main() {
 	defer utils.Close(conn)
 
 	res, err := conn.Read(ctx, common.ReadParams{
-		Fields: []string{
-			"id", "summary", "status",
-		},
+		Fields: connectors.Fields("id", "summary", "status"),
 		// Below is the example to get issues that were updated in the last 15 min.
 		// Since: time.Now().Add(-15 * time.Minute),
 	})
diff --git a/test/atlassian/write-delete/main.go b/test/atlassian/write-delete/main.go
index e4b86ab2..37a7088c 100644
--- a/test/atlassian/write-delete/main.go
+++ b/test/atlassian/write-delete/main.go
@@ -6,6 +6,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/providers/atlassian"
 	connTest "github.com/amp-labs/connectors/test/atlassian"
@@ -108,9 +109,7 @@ func searchIssue(res *common.ReadResult, key, value string) map[string]any {
 
 func readIssue(ctx context.Context, conn *atlassian.Connector) *common.ReadResult {
 	res, err := conn.Read(ctx, common.ReadParams{
-		Fields: []string{
-			"id", "fields",
-		},
+		Fields: connectors.Fields("id", "fields"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Atlassian", "error", err)
diff --git a/test/dynamicscrm/metadata/main.go b/test/dynamicscrm/metadata/main.go
index 555f37a9..f9c5b552 100644
--- a/test/dynamicscrm/metadata/main.go
+++ b/test/dynamicscrm/metadata/main.go
@@ -36,7 +36,7 @@ func main() {
 
 	fmt.Println("Read object using all fields from ListObjectMetadata")
 
-	requestFields := handy.Map[string, string](metadata.Result[objectName].FieldsMap).Keys()
+	requestFields := handy.Map[string, string](metadata.Result[objectName].FieldsMap).KeySet()
 
 	response, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
@@ -49,9 +49,9 @@ func main() {
 			utils.Fail("expected to read at least one record", "error", err)
 		}
 
-		givenFields := handy.Map[string, any](response.Data[0].Fields).Keys()
+		givenFields := handy.Map[string, any](response.Data[0].Fields).KeySet()
 
-		difference := handy.NewSet(givenFields).Diff(handy.NewSet(requestFields))
+		difference := givenFields.Diff(requestFields)
 		if len(difference) != 0 {
 			utils.Fail("connector read didn't match requested fields", "difference", difference)
 		}
diff --git a/test/dynamicscrm/read/main.go b/test/dynamicscrm/read/main.go
index ae2f3df2..832b3ff0 100644
--- a/test/dynamicscrm/read/main.go
+++ b/test/dynamicscrm/read/main.go
@@ -7,6 +7,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/providers/dynamicscrm"
 	connTest "github.com/amp-labs/connectors/test/dynamicscrm"
@@ -28,9 +29,7 @@ func main() {
 
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
-		Fields: []string{
-			"fullname", "emailaddress1", "fax",
-		},
+		Fields:     connectors.Fields("fullname", "emailaddress1", "fax"),
 	})
 	if err != nil {
 		utils.Fail("error reading from microsoft CRM", "error", err)
diff --git a/test/dynamicscrm/write-delete/main.go b/test/dynamicscrm/write-delete/main.go
index 7dcbf9a0..38b18746 100644
--- a/test/dynamicscrm/write-delete/main.go
+++ b/test/dynamicscrm/write-delete/main.go
@@ -6,6 +6,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/providers/dynamicscrm"
@@ -99,9 +100,9 @@ func searchLead(res *common.ReadResult, key, value string) map[string]any {
 func readLeads(ctx context.Context, conn *dynamicscrm.Connector) *common.ReadResult {
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
-		Fields: []string{
+		Fields: connectors.Fields(
 			"leadid", "lastname", "firstname", "companyname", "subject",
-		},
+		),
 	})
 	if err != nil {
 		utils.Fail("error reading from microsoft CRM", "error", err)
diff --git a/test/gong/metadata/main.go b/test/gong/metadata/main.go
index ab8ec605..ad9a69a4 100644
--- a/test/gong/metadata/main.go
+++ b/test/gong/metadata/main.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"context"
+	"github.com/amp-labs/connectors"
 	"log/slog"
 	"os/signal"
 	"syscall"
@@ -29,7 +30,7 @@ func main() {
 
 	response, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
-		Fields:     []string{"firstName", "lastName", "emailAddress"},
+		Fields:     connectors.Fields("firstName", "lastName", "emailAddress"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Gong", "error", err)
diff --git a/test/gong/read/read.go b/test/gong/read/read.go
index bb633de3..605d14cb 100644
--- a/test/gong/read/read.go
+++ b/test/gong/read/read.go
@@ -7,6 +7,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	connTest "github.com/amp-labs/connectors/test/gong"
 	"github.com/amp-labs/connectors/test/utils"
@@ -25,7 +26,7 @@ func main() {
 
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: "calls", // could be calls, users
-		Fields:     []string{"url"},
+		Fields:     connectors.Fields("url"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Gong", "error", err)
diff --git a/test/hubspot/write/main.go b/test/hubspot/write/main.go
index 091e5003..783a9805 100644
--- a/test/hubspot/write/main.go
+++ b/test/hubspot/write/main.go
@@ -8,6 +8,7 @@ import (
 	"syscall"
 	"time"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	connTest "github.com/amp-labs/connectors/test/hubspot"
 	"github.com/amp-labs/connectors/test/utils"
@@ -59,7 +60,7 @@ func main() {
 
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: "contacts",
-		Fields:     []string{"email", "phone", "company", "website", "lastname", "firstname"},
+		Fields:     connectors.Fields("email", "phone", "company", "website", "lastname", "firstname"),
 		NextPage:   "",
 		Since:      time.Now().Add(-5 * time.Minute),
 	})
diff --git a/test/instantly/read/main.go b/test/instantly/read/main.go
index 79735fd0..651e76da 100644
--- a/test/instantly/read/main.go
+++ b/test/instantly/read/main.go
@@ -3,6 +3,7 @@ package main
 import (
 	"context"
 	"fmt"
+	"github.com/amp-labs/connectors"
 	"log/slog"
 	"os"
 	"os/signal"
@@ -29,9 +30,9 @@ func main() {
 
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
-		Fields: []string{
+		Fields: connectors.Fields(
 			"name",
-		},
+		),
 	})
 	if err != nil {
 		utils.Fail("error reading from Instantly", "error", err)
diff --git a/test/instantly/write-delete/main.go b/test/instantly/write-delete/main.go
index 759594da..45708cbe 100644
--- a/test/instantly/write-delete/main.go
+++ b/test/instantly/write-delete/main.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"context"
+	"github.com/amp-labs/connectors"
 	"log/slog"
 	"os/signal"
 	"syscall"
@@ -80,9 +81,9 @@ func searchTags(res *common.ReadResult, key, value string) map[string]any {
 func readTags(ctx context.Context, conn *instantly.Connector) *common.ReadResult {
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
-		Fields: []string{
+		Fields: connectors.Fields(
 			"id", "view", "name",
-		},
+		),
 	})
 	if err != nil {
 		utils.Fail("error reading from Instantly", "error", err)
diff --git a/test/intercom/read/main.go b/test/intercom/read/main.go
index 7c86a6d7..4dd9399e 100644
--- a/test/intercom/read/main.go
+++ b/test/intercom/read/main.go
@@ -7,6 +7,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/providers/intercom"
 	msTest "github.com/amp-labs/connectors/test/intercom"
@@ -26,11 +27,11 @@ func main() {
 
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: "conversations",
-		Fields: []string{
+		Fields: connectors.Fields(
 			"id",
 			"state",
 			"type",
-		},
+		),
 	})
 	if err != nil {
 		utils.Fail("error reading from Intercom", "error", err)
diff --git a/test/intercom/write-delete/main.go b/test/intercom/write-delete/main.go
index 2b457665..12ebaaab 100644
--- a/test/intercom/write-delete/main.go
+++ b/test/intercom/write-delete/main.go
@@ -7,6 +7,7 @@ import (
 	"strconv"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/providers/intercom"
 	msTest "github.com/amp-labs/connectors/test/intercom"
@@ -73,9 +74,7 @@ func main() {
 func getAdminID(ctx context.Context, conn *intercom.Connector) string {
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: "admins",
-		Fields: []string{
-			"id",
-		},
+		Fields:     connectors.Fields("id"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Intercom", "error", err)
@@ -113,9 +112,7 @@ func searchArticles(res *common.ReadResult, key, value string) map[string]any {
 func readArticles(ctx context.Context, conn *intercom.Connector) *common.ReadResult {
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: "articles",
-		Fields: []string{
-			"id", "title", "description", "author_id",
-		},
+		Fields:     connectors.Fields("id", "title", "description", "author_id"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Intercom", "error", err)
diff --git a/test/marketo/read/read.go b/test/marketo/read/read.go
index 1da2df3b..92dfac86 100644
--- a/test/marketo/read/read.go
+++ b/test/marketo/read/read.go
@@ -8,6 +8,7 @@ import (
 	"os"
 	"time"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	mk "github.com/amp-labs/connectors/test/marketo"
 )
@@ -40,7 +41,7 @@ func testReadChannels(ctx context.Context) error {
 
 	params := common.ReadParams{
 		ObjectName: "channels",
-		Fields:     []string{"applicableProgramType", "id", "name"},
+		Fields:     connectors.Fields("applicableProgramType", "id", "name"),
 	}
 
 	res, err := conn.Read(ctx, params)
@@ -65,7 +66,7 @@ func testReadSmartCampaigns(ctx context.Context) error {
 
 	params := common.ReadParams{
 		ObjectName: "smartCampaigns",
-		Fields:     []string{"description", "id", "name"},
+		Fields:     connectors.Fields("description", "id", "name"),
 		Since:      time.Now().Add(-1800 * time.Hour),
 	}
 
@@ -91,7 +92,7 @@ func testReadCampaigns(ctx context.Context) error {
 
 	params := common.ReadParams{
 		ObjectName: "campaigns",
-		Fields:     []string{"createdAt", "id", "name"},
+		Fields:     connectors.Fields("createdAt", "id", "name"),
 		Since:      time.Now().Add(-1800 * time.Hour),
 	}
 
diff --git a/test/outreach/read/read.go b/test/outreach/read/read.go
index 4ef2544c..c5f5ac0b 100644
--- a/test/outreach/read/read.go
+++ b/test/outreach/read/read.go
@@ -46,7 +46,7 @@ func testReadSequences(ctx context.Context, conn connectors.ReadConnector) error
 	config := connectors.ReadParams{
 		ObjectName: "sequences",
 		Since:      time.Now().Add(-720 * time.Hour),
-		Fields:     []string{"openCount", "description", "id"},
+		Fields:     connectors.Fields("openCount", "description", "id"),
 	}
 
 	result, err := conn.Read(ctx, config)
@@ -70,7 +70,7 @@ func testReadMailings(ctx context.Context, conn connectors.ReadConnector) error
 	config := connectors.ReadParams{
 		ObjectName: "mailings",
 		Since:      time.Now().Add(-720 * time.Hour),
-		Fields:     []string{"bodyHtml", "errorReason", "id"},
+		Fields:     connectors.Fields("bodyHtml", "errorReason", "id"),
 	}
 
 	result, err := conn.Read(ctx, config)
@@ -94,7 +94,7 @@ func testReadProspects(ctx context.Context, conn connectors.ReadConnector) error
 	config := connectors.ReadParams{
 		ObjectName: "prospects",
 		Since:      time.Now().Add(-720 * time.Hour),
-		Fields:     []string{"addressCountry", "campaignName", "id"},
+		Fields:     connectors.Fields("addressCountry", "campaignName", "id"),
 	}
 
 	result, err := conn.Read(ctx, config)
diff --git a/test/pipeliner/read/main.go b/test/pipeliner/read/main.go
index b8e9ece1..e68bc8d3 100644
--- a/test/pipeliner/read/main.go
+++ b/test/pipeliner/read/main.go
@@ -7,6 +7,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/providers/pipeliner"
 	connTest "github.com/amp-labs/connectors/test/pipeliner"
@@ -28,9 +29,7 @@ func main() {
 
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
-		Fields: []string{
-			"id", "formatted_name", "account_position",
-		},
+		Fields:     connectors.Fields("id", "formatted_name", "account_position"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Pipeliner", "error", err)
diff --git a/test/pipeliner/write-delete/main.go b/test/pipeliner/write-delete/main.go
index 547d1e12..2e72ca2f 100644
--- a/test/pipeliner/write-delete/main.go
+++ b/test/pipeliner/write-delete/main.go
@@ -6,6 +6,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/providers/pipeliner"
 	connTest "github.com/amp-labs/connectors/test/pipeliner"
@@ -85,9 +86,7 @@ func searchNotes(res *common.ReadResult, key, value string) map[string]any {
 func readNotes(ctx context.Context, conn *pipeliner.Connector) *common.ReadResult {
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
-		Fields: []string{
-			"id", "view", "name",
-		},
+		Fields:     connectors.Fields("id", "view", "name"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Pipeliner", "error", err)
@@ -99,7 +98,7 @@ func readNotes(ctx context.Context, conn *pipeliner.Connector) *common.ReadResul
 func getFirstObjectID(ctx context.Context, conn *pipeliner.Connector, name string) string {
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: name,
-		Fields:     []string{"id"},
+		Fields:     connectors.Fields("id"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Pipeliner", "error", err)
diff --git a/test/salesforce/bulk/read/main.go b/test/salesforce/bulk/read/main.go
index c4573e77..e7987487 100644
--- a/test/salesforce/bulk/read/main.go
+++ b/test/salesforce/bulk/read/main.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"context"
+	"github.com/amp-labs/connectors"
 	"log/slog"
 	"os"
 	"os/signal"
@@ -26,7 +27,7 @@ func main() {
 
 	res, err := conn.BulkRead(ctx, common.ReadParams{
 		ObjectName: "Account",
-		Fields:     []string{"Id", "Name"},
+		Fields:     connectors.Fields("Id", "Name"),
 	})
 	if err != nil {
 		utils.Fail("error bulk reading from Salesforce", "error", err)
diff --git a/test/salesforce/metadata-read/main.go b/test/salesforce/metadata-read/main.go
index 42bd2f77..0cbd426a 100644
--- a/test/salesforce/metadata-read/main.go
+++ b/test/salesforce/metadata-read/main.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	connTest "github.com/amp-labs/connectors/test/salesforce"
 	"github.com/amp-labs/connectors/test/utils"
@@ -30,7 +31,7 @@ func main() {
 
 	response, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
-		Fields:     []string{"*"},
+		Fields:     connectors.Fields("*"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Salesforce", "error", err)
diff --git a/test/salesforce/read-write/read-write.go b/test/salesforce/read-write/read-write.go
index b0383da2..6e13015d 100644
--- a/test/salesforce/read-write/read-write.go
+++ b/test/salesforce/read-write/read-write.go
@@ -55,7 +55,7 @@ func testReadConnector(ctx context.Context, conn connectors.ReadConnector) error
 	// Read some data from Salesforce
 	res, err := conn.Read(ctx, connectors.ReadParams{
 		ObjectName: "Account",
-		Fields:     []string{"Id", "Name", "BillingCity", "IsDeleted"},
+		Fields:     connectors.Fields("Id", "Name", "BillingCity", "IsDeleted"),
 	})
 	if err != nil {
 		return fmt.Errorf("error reading from Salesforce: %w", err)
diff --git a/test/salesloft/read/main.go b/test/salesloft/read/main.go
index 6509c872..e96ce4bb 100644
--- a/test/salesloft/read/main.go
+++ b/test/salesloft/read/main.go
@@ -7,6 +7,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/providers/salesloft"
 	msTest "github.com/amp-labs/connectors/test/salesloft"
@@ -26,10 +27,7 @@ func main() {
 
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: "people",
-		Fields: []string{
-			"display_name",
-			"email_address",
-		},
+		Fields:     connectors.Fields("display_name", "email_address"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Salesloft", "error", err)
diff --git a/test/salesloft/write-delete/main.go b/test/salesloft/write-delete/main.go
index d7198d73..f668e378 100644
--- a/test/salesloft/write-delete/main.go
+++ b/test/salesloft/write-delete/main.go
@@ -6,6 +6,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/providers/salesloft"
 	msTest "github.com/amp-labs/connectors/test/salesloft"
@@ -86,9 +87,7 @@ func searchListView(res *common.ReadResult, key, value string) map[string]any {
 func readListViews(ctx context.Context, conn *salesloft.Connector) *common.ReadResult {
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: "saved_list_views",
-		Fields: []string{
-			"id", "view", "name",
-		},
+		Fields:     connectors.Fields("id", "view", "name"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Salesloft", "error", err)
diff --git a/test/smartlead/read/main.go b/test/smartlead/read/main.go
index e2930dba..fd0122e5 100644
--- a/test/smartlead/read/main.go
+++ b/test/smartlead/read/main.go
@@ -7,6 +7,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	connTest "github.com/amp-labs/connectors/test/smartlead"
 	"github.com/amp-labs/connectors/test/utils"
@@ -27,9 +28,7 @@ func main() {
 
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
-		Fields: []string{
-			"name", "status", "user_id",
-		},
+		Fields:     connectors.Fields("name", "status", "user_id"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Smartlead", "error", err)
diff --git a/test/zendesksupport/read/main.go b/test/zendesksupport/read/main.go
index b23fa8ec..7813163c 100644
--- a/test/zendesksupport/read/main.go
+++ b/test/zendesksupport/read/main.go
@@ -7,6 +7,7 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/providers/zendesksupport"
 	"github.com/amp-labs/connectors/test/utils"
@@ -28,9 +29,7 @@ func main() {
 
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
-		Fields: []string{
-			"name", "time_zone", "role",
-		},
+		Fields:     connectors.Fields("name", "time_zone", "role"),
 	})
 	if err != nil {
 		utils.Fail("error reading from Zendesk Support", "error", err)
diff --git a/test/zendesksupport/write-delete/main.go b/test/zendesksupport/write-delete/main.go
index 256c7c1c..29f0d5d6 100644
--- a/test/zendesksupport/write-delete/main.go
+++ b/test/zendesksupport/write-delete/main.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 	"syscall"
 
+	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/providers/zendesksupport"
 	"github.com/amp-labs/connectors/test/utils"
@@ -99,9 +100,7 @@ func searchBrands(res *common.ReadResult, key, value string) map[string]any {
 func readBrands(ctx context.Context, conn *zendesksupport.Connector) *common.ReadResult {
 	res, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
-		Fields: []string{
-			"id", "view", "name",
-		},
+		Fields:     connectors.Fields("id", "view", "name"),
 	})
 	if err != nil {
 		utils.Fail("error reading from ZendeskSupport", "error", err)