Repository: amp-labs/connectors
Pull request #1114 (2024-11-01 19:58:56)

feat: Package Goutils
# Background

Handy package primarily was created to hold data structures, but some general utility methods slipped in.

# Changes
I think it makes more sense to split this package in 2 distinct pieces.
This PR is branching first package: `goutils`. The other would then become a simple rename.
Both packages live under `internal` folder.

Package includes:
* Generics struct cloning
* Error checks (used by scripts, not appropriate for connectors)
* Pointer returners to primitive types.

These function are used for golang convenience, syntactic sugar.

Cobalt0s: * **#1115** <a href="https://app.graphite.dev/github/pr/amp-labs/connectors/1115?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#1114** <a href="https://app.graphite.dev/github/pr/amp-labs/connectors/1114?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: -->
jlimatampersand: > @Cobalt0s I think you have to update main to the latest and then run `git rebase main`, now that the datautils PR has been merged.
> 
> Also I'm not sure I like the name `goutils` (not sure why it's prefixed with `go`, since the whole codebase is in Go, so prefixing with `go` isn't adding any additional info). What about simply `utils` for now? Feel free to suggest other prefixes @jlimatampersand
> 
> I also add some comments on the functions that can be removed, our general philosophy is that utility functions should only be introduced if they save lines of code and are a common use case. Otherwise it adds to the learning curve for contributors who are developing new connectors (and makes existing connectors harder to read and reason about). After you remove the functions I suggest removing, the only ones left will be: MustBeNil and IsTrue, so it might be easier to collapse them into one file.

I think we can just have these be part of utils library which we already have. 
Cobalt0s: > you can rename the package to just `utils`.

Most methods from `utils` packages were catogarized into specialized packages to this date. I was trying to limit the usage of `utils` to avoid making it a God pacakge.

So far there are only 2 `utils` package:
![image](https://github.com/user-attachments/assets/4dab0367-6c6a-433f-adcb-5a38dd6f40c1)
One for testing scripts and the other for example scripts. For this context it makes sense.

On the other hand `connectors`, `providers` don't have utils pacakge. I suppose `goutils` rename to `utils` would fill the category.

NOTE: When I was working with `test` repo, the scripts there had imports of 2 utils which meant `alias importing` due to naming collision, therefore I would rather prefix utils with something by default.
Cobalt0s: Rebased and addressed some comments, used ideas after reading more about it.
First commit(important): https://github.com/amp-labs/connectors/pull/1114/commits/533b3bda6c80d971065c4a2e1897390f21d8716f
Second commit(repetative follow up): https://github.com/amp-labs/connectors/pull/1114/commits/e888c52cbbf4d8ebe9ef79afb97ee5976e9b2850

Patch:
diff --git a/common/handy/misc.go b/common/handy/misc.go
deleted file mode 100644
index 60c70d35..00000000
--- a/common/handy/misc.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package handy
-
-func Must(err error) {
-	if err != nil {
-		panic(err)
-	}
-}
diff --git a/common/handy/pointers.go b/common/handy/pointers.go
deleted file mode 100644
index 454ed4a7..00000000
--- a/common/handy/pointers.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package handy
-
-// Pointers is a collection of utility functions that help with go pointer referencing.
-var Pointers = pointers{} // nolint:gochecknoglobals
-
-type pointers struct{}
-
-// Str returns a pointer to the given string.
-func (pointers) Str(input string) *string {
-	return &input
-}
-
-// Bool returns a pointer to the given boolean.
-func (pointers) Bool(input bool) *bool {
-	return &input
-}
-
-// Int64 returns a pointer to the given integer.
-func (pointers) Int64(input int64) *int64 {
-	return &input
-}
-
-// IsTrue checks boolean pointer was set and is true.
-func (pointers) IsTrue(input *bool) bool {
-	if input == nil {
-		return false
-	}
-
-	return *input
-}
diff --git a/common/module.go b/common/module.go
index 21bef520..185065b7 100644
--- a/common/module.go
+++ b/common/module.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 var (
@@ -18,7 +18,7 @@ var (
 
 type ModuleID string
 
-type Modules = handy.Map[ModuleID, Module]
+type Modules = datautils.Map[ModuleID, Module]
 
 // Module represents a set of endpoints and functionality available by provider.
 // Single provider may support multiple modules, requiring the user to choose a module before making requests.
@@ -65,4 +65,4 @@ func (a Module) Path() string {
 //				return fieldName
 //			}),
 //		}
-type ModuleObjectNameToFieldName map[ModuleID]handy.DefaultMap[string, string]
+type ModuleObjectNameToFieldName map[ModuleID]datautils.DefaultMap[string, string]
diff --git a/common/paramsbuilder/module.go b/common/paramsbuilder/module.go
index 8648e899..f1fe0c11 100644
--- a/common/paramsbuilder/module.go
+++ b/common/paramsbuilder/module.go
@@ -2,7 +2,7 @@ package paramsbuilder
 
 import (
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 // Module represents a sub-product of a provider.
@@ -32,7 +32,7 @@ func (p *Module) ValidateParams() error {
 // This defines a list of module a user could switch to. Any out of scope module will be ignored.
 // If user supplied unsupported module, then it will use this fallback.
 func (p *Module) WithModule(moduleID common.ModuleID, supported common.Modules, fallbackID common.ModuleID) {
-	modules := handy.NewDefaultMap(supported,
+	modules := datautils.NewDefaultMap(supported,
 		func(unknownID common.ModuleID) common.Module {
 			// Unknown module ids will receive a fallback module.
 			return supported[fallbackID]
diff --git a/common/parse.go b/common/parse.go
index 5231f8fd..84429523 100644
--- a/common/parse.go
+++ b/common/parse.go
@@ -3,8 +3,8 @@ package common
 import (
 	"strings"
 
-	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/spyzhov/ajson"
 )
 
@@ -24,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 handy.Set[string],
+	fields datautils.Set[string],
 ) (*ReadResult, error) {
 	body, ok := resp.Body()
 	if !ok {
diff --git a/common/scanning/credscanning/fields.go b/common/scanning/credscanning/fields.go
index a4a8fe70..87cd0056 100644
--- a/common/scanning/credscanning/fields.go
+++ b/common/scanning/credscanning/fields.go
@@ -4,8 +4,8 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/common/scanning"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/providers"
 	"github.com/iancoleman/strcase"
 )
@@ -123,8 +123,8 @@ func (f Field) GetENVReader(providerName string) *scanning.EnvReader {
 
 func getFields(info providers.ProviderInfo,
 	withRequiredAccessToken, withRequiredWorkspace bool,
-) (handy.NamedLists[Field], error) {
-	lists := handy.NamedLists[Field]{}
+) (datautils.NamedLists[Field], error) {
+	lists := datautils.NamedLists[Field]{}
 	requiredType := "required"
 	optionalType := "optional"
 
diff --git a/common/scanning/credscanning/provider.go b/common/scanning/credscanning/provider.go
index 13542b76..6f75bdf0 100644
--- a/common/scanning/credscanning/provider.go
+++ b/common/scanning/credscanning/provider.go
@@ -7,8 +7,8 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/common/scanning"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/providers"
 )
 
@@ -65,7 +65,7 @@ func createProviderCreds(
 	}
 
 	registry := scanning.NewRegistry()
-	readers := handy.NamedLists[scanning.Reader]{}
+	readers := datautils.NamedLists[scanning.Reader]{}
 
 	// add readers for every field
 	for kind, fieldList := range fields {
@@ -95,7 +95,7 @@ func selectReader(field Field, filePath string, providerName string) scanning.Re
 	return field.GetENVReader(providerName)
 }
 
-func (r ProviderCredentials) loadValues(readers handy.NamedLists[scanning.Reader]) error {
+func (r ProviderCredentials) loadValues(readers datautils.NamedLists[scanning.Reader]) error {
 	// validate JSON file or ENV has all Required variables
 	missingKeys := make([]string, 0)
 
diff --git a/common/types.go b/common/types.go
index 1a25ed1e..112ae447 100644
--- a/common/types.go
+++ b/common/types.go
@@ -6,7 +6,7 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 var (
@@ -97,7 +97,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 handy.StringSet // required, at least one field needed
+	Fields datautils.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 fc8d8f7e..a6c3fe35 100644
--- a/connectors.go
+++ b/connectors.go
@@ -6,7 +6,7 @@ import (
 	"io"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/providers"
 )
 
@@ -101,4 +101,4 @@ type (
 	ErrorWithStatus = common.HTTPStatusError
 )
 
-var Fields = handy.NewStringSet // nolint:gochecknoglobals
+var Fields = datautils.NewStringSet // nolint:gochecknoglobals
diff --git a/common/handy/lists.go b/internal/datautils/lists.go
similarity index 98%
rename from common/handy/lists.go
rename to internal/datautils/lists.go
index 597be186..3077c93b 100644
--- a/common/handy/lists.go
+++ b/internal/datautils/lists.go
@@ -1,4 +1,4 @@
-package handy
+package datautils
 
 // IndexedLists is a dynamic list of identifiable slices.
 // Each slice is associated to the unique comparable object.
diff --git a/common/handy/maps.go b/internal/datautils/maps.go
similarity index 98%
rename from common/handy/maps.go
rename to internal/datautils/maps.go
index 9f214fa7..ce845a9b 100644
--- a/common/handy/maps.go
+++ b/internal/datautils/maps.go
@@ -1,4 +1,4 @@
-package handy
+package datautils
 
 // Map is a generic version of map with useful methods.
 // It can return Keys as a slice or a Set.
diff --git a/common/handy/set.go b/internal/datautils/set.go
similarity index 99%
rename from common/handy/set.go
rename to internal/datautils/set.go
index c891d60c..93cd6ae4 100644
--- a/common/handy/set.go
+++ b/internal/datautils/set.go
@@ -1,4 +1,4 @@
-package handy
+package datautils
 
 // NewStringSet and StringSet are Aliases.
 var NewStringSet = NewSet[string] // nolint:gochecknoglobals
diff --git a/common/handy/timing.go b/internal/datautils/timing.go
similarity index 96%
rename from common/handy/timing.go
rename to internal/datautils/timing.go
index 146dda8f..b3331a26 100644
--- a/common/handy/timing.go
+++ b/internal/datautils/timing.go
@@ -1,4 +1,4 @@
-package handy
+package datautils
 
 import (
 	"time"
diff --git a/common/handy/cloning.go b/internal/goutils/cloning.go
similarity index 96%
rename from common/handy/cloning.go
rename to internal/goutils/cloning.go
index 14bed077..560409d1 100644
--- a/common/handy/cloning.go
+++ b/internal/goutils/cloning.go
@@ -1,4 +1,4 @@
-package handy
+package goutils
 
 import (
 	"bytes"
diff --git a/internal/goutils/utils.go b/internal/goutils/utils.go
new file mode 100644
index 00000000..73a1f043
--- /dev/null
+++ b/internal/goutils/utils.go
@@ -0,0 +1,13 @@
+package goutils
+
+// Pointer returns a pointer to the given value.
+func Pointer[T any](input T) *T {
+	return &input
+}
+
+// MustBeNil - panics on non-empty error.
+func MustBeNil(err error) {
+	if err != nil {
+		panic(err)
+	}
+}
diff --git a/internal/staticschema/core.go b/internal/staticschema/core.go
index 406121ed..69b79828 100644
--- a/internal/staticschema/core.go
+++ b/internal/staticschema/core.go
@@ -4,7 +4,7 @@ import (
 	"strings"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 const RootModuleID common.ModuleID = "root"
@@ -118,11 +118,11 @@ func (r *Metadata) getOrCreateModule(moduleID common.ModuleID) Module {
 }
 
 // ObjectNames provides a registry of object names grouped by module.
-func (r *Metadata) ObjectNames() handy.UniqueLists[common.ModuleID, string] {
-	moduleObjectNames := make(handy.UniqueLists[common.ModuleID, string])
+func (r *Metadata) ObjectNames() datautils.UniqueLists[common.ModuleID, string] {
+	moduleObjectNames := make(datautils.UniqueLists[common.ModuleID, string])
 
 	for key, value := range r.Modules {
-		names := handy.NewStringSet()
+		names := datautils.NewStringSet()
 		for name := range value.Objects {
 			names.AddOne(name)
 		}
diff --git a/providers/attio/objectNames.go b/providers/attio/objectNames.go
index dd98ef22..176209ff 100644
--- a/providers/attio/objectNames.go
+++ b/providers/attio/objectNames.go
@@ -1,6 +1,6 @@
 package attio
 
-import "github.com/amp-labs/connectors/common/handy"
+import "github.com/amp-labs/connectors/internal/datautils"
 
 const (
 	objectNameObjects          = "objects"
@@ -11,7 +11,7 @@ const (
 	objectNameNotes            = "notes"
 )
 
-var supportedObjectsByRead = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByRead = datautils.NewSet( //nolint:gochecknoglobals
 	objectNameObjects,
 	objectNameLists,
 	objectNameWorkspacemembers,
@@ -20,7 +20,7 @@ var supportedObjectsByRead = handy.NewSet( //nolint:gochecknoglobals
 	objectNameNotes,
 )
 
-var supportedObjectsByWrite = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByWrite = datautils.NewSet( //nolint:gochecknoglobals
 	objectNameObjects,
 	objectNameLists,
 	objectNameTasks,
@@ -28,7 +28,7 @@ var supportedObjectsByWrite = handy.NewSet( //nolint:gochecknoglobals
 	objectNameWebhooks,
 )
 
-var supportLimitAndOffset = handy.NewSet( //nolint:gochecknoglobals
+var supportLimitAndOffset = datautils.NewSet( //nolint:gochecknoglobals
 	objectNameTasks,
 	objectNameNotes,
 	objectNameWebhooks,
diff --git a/providers/closecrm/search.go b/providers/closecrm/search.go
index dabc5d6e..247afc27 100644
--- a/providers/closecrm/search.go
+++ b/providers/closecrm/search.go
@@ -6,7 +6,7 @@ import (
 	"time"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 /*
@@ -49,7 +49,7 @@ func (c *Connector) Search(ctx context.Context, config SearchParams) (*common.Re
 		common.GetRecordsUnderJSONPath("data"),
 		getNextRecordCursor,
 		common.GetMarshaledData,
-		handy.NewStringSet(config.Fields...),
+		datautils.NewStringSet(config.Fields...),
 	)
 }
 
diff --git a/providers/customerapp/objectNames.go b/providers/customerapp/objectNames.go
index 287998c0..709102a4 100644
--- a/providers/customerapp/objectNames.go
+++ b/providers/customerapp/objectNames.go
@@ -1,7 +1,7 @@
 package customerapp
 
 import (
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/providers/customerapp/metadata"
 )
 
@@ -9,7 +9,7 @@ import (
 var supportedObjectsByRead = metadata.Schemas.ObjectNames() //nolint:gochecknoglobals
 
 // ObjectNameToResponseField maps ObjectName to the response field name which contains that object.
-var ObjectNameToResponseField = handy.NewDefaultMap(map[string]string{ //nolint:gochecknoglobals
+var ObjectNameToResponseField = datautils.NewDefaultMap(map[string]string{ //nolint:gochecknoglobals
 	"object_types":        "types",
 	"transactional":       "messages",
 	"subscription_topics": "topics",
diff --git a/providers/gong/objectNames.go b/providers/gong/objectNames.go
index e6c9758d..31ee7353 100644
--- a/providers/gong/objectNames.go
+++ b/providers/gong/objectNames.go
@@ -1,7 +1,7 @@
 package gong
 
 import (
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/providers/gong/metadata"
 )
 
@@ -12,6 +12,6 @@ const (
 // Supported object names can be found under schemas.json.
 var supportedObjectsByRead = metadata.Schemas.ObjectNames() //nolint:gochecknoglobals
 
-var supportedObjectsByWrite = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByWrite = datautils.NewSet( //nolint:gochecknoglobals
 	objectNameCalls,
 )
diff --git a/providers/gong/read.go b/providers/gong/read.go
index ed819dd7..3115632b 100644
--- a/providers/gong/read.go
+++ b/providers/gong/read.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common.ReadResult, error) {
@@ -29,7 +29,7 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common
 		// 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))
+		url.WithQueryParam("fromDateTime", datautils.Time.FormatRFC3339inUTC(config.Since))
 	}
 
 	res, err := c.Client.Get(ctx, url.String())
diff --git a/providers/hubspot/types.go b/providers/hubspot/types.go
index 03b97795..5fb61537 100644
--- a/providers/hubspot/types.go
+++ b/providers/hubspot/types.go
@@ -2,7 +2,7 @@ package hubspot
 
 import (
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 type SearchParams struct {
@@ -15,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 handy.Set[string] // optional
+	Fields datautils.Set[string] // optional
 }
 
 func (p SearchParams) ValidateParams() error {
diff --git a/providers/instantly/objectNames.go b/providers/instantly/objectNames.go
index 9da9e2e4..0adb97ff 100644
--- a/providers/instantly/objectNames.go
+++ b/providers/instantly/objectNames.go
@@ -1,6 +1,6 @@
 package instantly
 
-import "github.com/amp-labs/connectors/common/handy"
+import "github.com/amp-labs/connectors/internal/datautils"
 
 const (
 	objectNameCampaigns        = "campaigns"
@@ -12,7 +12,7 @@ const (
 	objectNameUniboxReplies    = "unibox-replies"
 )
 
-var supportedObjectsByRead = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByRead = datautils.NewSet( //nolint:gochecknoglobals
 	// Object Name	----------	API endpoint path
 	objectNameCampaigns, // campaign/list
 	objectNameAccounts,  // account/list
@@ -20,7 +20,7 @@ var supportedObjectsByRead = handy.NewSet( //nolint:gochecknoglobals
 	objectNameTags,      // custom-tag
 )
 
-var supportedObjectsByWrite = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByWrite = datautils.NewSet( //nolint:gochecknoglobals
 	// Object Name	----------	API endpoint path
 	objectNameTags,             // custom-tag
 	objectNameLeads,            // lead/add
@@ -28,7 +28,7 @@ var supportedObjectsByWrite = handy.NewSet( //nolint:gochecknoglobals
 	objectNameUniboxReplies,    // unibox/emails/reply
 )
 
-var supportedObjectsByDelete = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByDelete = datautils.NewSet( //nolint:gochecknoglobals
 	// Delete tag.
 	// https://developer.instantly.ai/tags/delete-a-tag
 	objectNameTags,
diff --git a/providers/instantly/write.go b/providers/instantly/write.go
index 84f6bd96..6aa618a2 100644
--- a/providers/instantly/write.go
+++ b/providers/instantly/write.go
@@ -4,9 +4,9 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/spyzhov/ajson"
 )
 
@@ -64,9 +64,9 @@ func (c *Connector) Write(
 
 var recordIdPaths = map[string]*string{ // nolint:gochecknoglobals
 	objectNameLeads:            nil, // ID is not returned for Leads.
-	objectNameBlocklistEntries: handy.Pointers.Str("blocklist_id"),
-	objectNameUniboxReplies:    handy.Pointers.Str("message_id"),
-	objectNameTags:             handy.Pointers.Str("id"),
+	objectNameBlocklistEntries: goutils.Pointer("blocklist_id"),
+	objectNameUniboxReplies:    goutils.Pointer("message_id"),
+	objectNameTags:             goutils.Pointer("id"),
 }
 
 func constructURLPathCreate(config common.WriteParams, url *urlbuilder.URL) {
diff --git a/providers/intercom/objectNames.go b/providers/intercom/objectNames.go
index 71ea39c3..8e8fae43 100644
--- a/providers/intercom/objectNames.go
+++ b/providers/intercom/objectNames.go
@@ -1,7 +1,7 @@
 package intercom
 
 import (
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/providers/intercom/metadata"
 )
 
@@ -13,7 +13,7 @@ const ticketsObjectName = "tickets"
 var supportedObjectsByRead = metadata.Schemas.ObjectNames() //nolint:gochecknoglobals
 
 // ObjectNameToResponseField maps ObjectName to the response field name which contains that object.
-var ObjectNameToResponseField = handy.NewDefaultMap(map[string]string{ //nolint:gochecknoglobals
+var ObjectNameToResponseField = datautils.NewDefaultMap(map[string]string{ //nolint:gochecknoglobals
 	"admins":        "admins",
 	"teams":         "teams",
 	"ticket_types":  "ticket_types",
@@ -29,7 +29,7 @@ var ObjectNameToResponseField = handy.NewDefaultMap(map[string]string{ //nolint:
 	},
 )
 
-var objectNameToURLPath = handy.NewDefaultMap(map[string]string{ //nolint:gochecknoglobals
+var objectNameToURLPath = datautils.NewDefaultMap(map[string]string{ //nolint:gochecknoglobals
 	"activity_logs": "/admins/activity_logs",
 	"collections":   "/help_center/collections",
 	"help_centers":  "/help_center/help_centers",
@@ -40,7 +40,7 @@ var objectNameToURLPath = handy.NewDefaultMap(map[string]string{ //nolint:gochec
 })
 
 // nolint:gomnd
-var incrementalSearchObjectPagination = handy.NewDefaultMap(map[string]int{ //nolint:gochecknoglobals
+var incrementalSearchObjectPagination = datautils.NewDefaultMap(map[string]int{ //nolint:gochecknoglobals
 	// https://developers.intercom.com/docs/references/rest-api/api.intercom.io/conversations/searchconversations
 	"conversations": 150,
 	// https://developers.intercom.com/docs/references/rest-api/api.intercom.io/contacts/searchcontacts
diff --git a/providers/pipedrive/objectNames.go b/providers/pipedrive/objectNames.go
index 3fc468b3..5b6a6396 100644
--- a/providers/pipedrive/objectNames.go
+++ b/providers/pipedrive/objectNames.go
@@ -1,11 +1,9 @@
 package pipedrive
 
-import (
-	"github.com/amp-labs/connectors/common/handy"
-)
+import "github.com/amp-labs/connectors/internal/datautils"
 
 // ObjectNameToResponseField maps ObjectName to the response field name which contains that object.
-var ObjectNameToResponseField = handy.NewDefaultMap(map[string]string{}, //nolint:gochecknoglobals
+var ObjectNameToResponseField = datautils.NewDefaultMap(map[string]string{}, //nolint:gochecknoglobals
 	func(key string) string {
 		return key
 	},
diff --git a/providers/salesforce/bulk-info.go b/providers/salesforce/bulk-info.go
index bdadf5c9..7e82094f 100644
--- a/providers/salesforce/bulk-info.go
+++ b/providers/salesforce/bulk-info.go
@@ -8,7 +8,7 @@ import (
 	"slices"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 type ListIngestJobsResult struct {
@@ -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.NewSetFromList(jobIDs)
+	pending := datautils.NewSetFromList(jobIDs)
 
 	// To keep track of pages
 	location := url.String()
diff --git a/providers/salesforce/read.go b/providers/salesforce/read.go
index ea88fdfa..0c7eeb74 100644
--- a/providers/salesforce/read.go
+++ b/providers/salesforce/read.go
@@ -4,8 +4,8 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 // Read reads data from Salesforce. By default, it will read all rows (backfill). However, if Since is set,
@@ -59,7 +59,7 @@ func makeSOQL(config common.ReadParams) *soqlBuilder {
 
 	// If Since is not set, then we're doing a backfill. We read all rows (in pages)
 	if !config.Since.IsZero() {
-		soql.Where("SystemModstamp > " + handy.Time.FormatRFC3339inUTC(config.Since))
+		soql.Where("SystemModstamp > " + datautils.Time.FormatRFC3339inUTC(config.Since))
 	}
 
 	if config.Deleted {
diff --git a/providers/smartlead/objectNames.go b/providers/smartlead/objectNames.go
index 4c1cb55d..8c1aa02f 100644
--- a/providers/smartlead/objectNames.go
+++ b/providers/smartlead/objectNames.go
@@ -1,7 +1,7 @@
 package smartlead
 
 import (
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/providers/smartlead/metadata"
 )
 
@@ -14,12 +14,12 @@ const (
 // Supported object names can be found under schemas.json.
 var supportedObjectsByRead = metadata.Schemas.ObjectNames() //nolint:gochecknoglobals
 
-var supportedObjectsByWrite = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByWrite = datautils.NewSet( //nolint:gochecknoglobals
 	objectNameCampaign,
 	objectNameEmailAccount,
 	objectNameClient,
 )
 
-var supportedObjectsByDelete = handy.NewSet( //nolint:gochecknoglobals
+var supportedObjectsByDelete = datautils.NewSet( //nolint:gochecknoglobals
 	objectNameCampaign,
 )
diff --git a/providers/types.gen.go b/providers/types.gen.go
index 20a8e773..5418e9bb 100644
--- a/providers/types.gen.go
+++ b/providers/types.gen.go
@@ -31,7 +31,7 @@ const (
 	Password          Oauth2OptsGrantType = "password"
 )
 
-// ApiKeyAsBasicOpts when this object is present, it means that this provider uses Basic Auth to actually collect an API key
+// ApiKeyAsBasicOpts when this object is present, it means that this provider uses Basic Auth to actually collect an API key.
 type ApiKeyAsBasicOpts struct {
 	// FieldUsed whether the API key should be used as the username or password.
 	FieldUsed ApiKeyAsBasicOptsFieldUsed `json:"fieldUsed,omitempty"`
diff --git a/providers/utils.go b/providers/utils.go
index f563604f..c5e076e4 100644
--- a/providers/utils.go
+++ b/providers/utils.go
@@ -8,8 +8,8 @@ import (
 	"net/http"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/common/substitutions/catalogreplacer"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/go-playground/validator"
 	"golang.org/x/oauth2"
 	"golang.org/x/oauth2/clientcredentials"
@@ -73,7 +73,7 @@ func (c CustomCatalog) ReadCatalog() (*CatalogWrapper, error) {
 		return nil, err
 	}
 
-	catalogCopy, err := handy.Clone[*CatalogWrapper](catalogInstance)
+	catalogCopy, err := goutils.Clone[*CatalogWrapper](catalogInstance)
 	if err != nil {
 		return nil, err
 	}
@@ -132,7 +132,7 @@ func (c CustomCatalog) ReadInfo(provider Provider, vars ...catalogreplacer.Catal
 	}
 
 	// Clone before modifying
-	providerInfo, err := handy.Clone[ProviderInfo](pInfo)
+	providerInfo, err := goutils.Clone[ProviderInfo](pInfo)
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/zendesksupport/objectNames.go b/providers/zendesksupport/objectNames.go
index fba5baa8..4722c8ea 100644
--- a/providers/zendesksupport/objectNames.go
+++ b/providers/zendesksupport/objectNames.go
@@ -2,7 +2,7 @@ package zendesksupport
 
 import (
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/providers/zendesksupport/metadata"
 )
 
@@ -11,7 +11,7 @@ var supportedObjectsByRead = metadata.Schemas.ObjectNames() //nolint:gochecknogl
 
 // ObjectNameToResponseField maps ObjectName to the response field name which contains that object.
 var ObjectNameToResponseField = common.ModuleObjectNameToFieldName{ //nolint:gochecknoglobals
-	ModuleTicketing: handy.NewDefaultMap(map[string]string{
+	ModuleTicketing: datautils.NewDefaultMap(map[string]string{
 		"ticket_audits":        "audits",
 		"search":               "results", // This is "/api/v2/search"
 		"satisfaction_reasons": "reasons",
@@ -20,7 +20,7 @@ var ObjectNameToResponseField = common.ModuleObjectNameToFieldName{ //nolint:goc
 			return objectName
 		},
 	),
-	ModuleHelpCenter: handy.NewDefaultMap(map[string]string{
+	ModuleHelpCenter: datautils.NewDefaultMap(map[string]string{
 		"articles":        "results",
 		"article_labels":  "labels",
 		"community_posts": "results",
diff --git a/scripts/openapi/customerio/journeysapp/metadata/main.go b/scripts/openapi/customerio/journeysapp/metadata/main.go
index bd16e589..bcb0b0c1 100644
--- a/scripts/openapi/customerio/journeysapp/metadata/main.go
+++ b/scripts/openapi/customerio/journeysapp/metadata/main.go
@@ -4,7 +4,8 @@ import (
 	"log"
 	"log/slog"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/providers/customerapp"
 	"github.com/amp-labs/connectors/providers/customerapp/metadata"
@@ -29,17 +30,17 @@ func main() {
 			api3.CamelCaseToSpaceSeparated,
 			api3.CapitalizeFirstLetterEveryWord,
 		))
-	must(err)
+	goutils.MustBeNil(err)
 
 	objects, err := explorer.ReadObjectsGet(
 		api3.NewDenyPathStrategy(ignoreEndpoints),
 		nil, displayNameOverride,
 		api3.CustomMappingObjectCheck(customerapp.ObjectNameToResponseField),
 	)
-	must(err)
+	goutils.MustBeNil(err)
 
 	schemas := staticschema.NewMetadata()
-	registry := handy.NamedLists[string]{}
+	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
 		if object.Problem != nil {
@@ -58,14 +59,8 @@ func main() {
 		}
 	}
 
-	must(metadata.FileManager.SaveSchemas(schemas))
-	must(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
+	goutils.MustBeNil(metadata.FileManager.SaveSchemas(schemas))
+	goutils.MustBeNil(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
 
 	log.Println("Completed.")
 }
-
-func must(err error) {
-	if err != nil {
-		panic(err)
-	}
-}
diff --git a/scripts/openapi/gong/metadata/main.go b/scripts/openapi/gong/metadata/main.go
index cb285135..4e0ff10f 100644
--- a/scripts/openapi/gong/metadata/main.go
+++ b/scripts/openapi/gong/metadata/main.go
@@ -3,7 +3,8 @@ package main
 import (
 	"log/slog"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/providers/gong/metadata"
 	"github.com/amp-labs/connectors/providers/gong/openapi"
@@ -16,7 +17,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("calls") // nolint:gochecknoglobals
+	queryParamFilterExceptions = datautils.NewSet("calls") // nolint:gochecknoglobals
 
 	ignoreEndpoints = []string{ // nolint:gochecknoglobals
 		"/v2/settings/scorecards",
@@ -60,16 +61,16 @@ func main() {
 				api3.OnlyOptionalQueryParameters(objectName, operation)
 		}),
 	)
-	must(err)
+	goutils.MustBeNil(err)
 
 	objects, err := explorer.ReadObjectsGet(
 		api3.NewDenyPathStrategy(ignoreEndpoints),
 		nil, nil, api3.IdenticalObjectLocator,
 	)
-	must(err)
+	goutils.MustBeNil(err)
 
 	schemas := staticschema.NewMetadata()
-	registry := handy.NamedLists[string]{}
+	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
 		if object.Problem != nil {
@@ -88,14 +89,8 @@ func main() {
 		}
 	}
 
-	must(metadata.FileManager.SaveSchemas(schemas))
-	must(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
+	goutils.MustBeNil(metadata.FileManager.SaveSchemas(schemas))
+	goutils.MustBeNil(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
 
 	slog.Info("Completed.")
 }
-
-func must(err error) {
-	if err != nil {
-		panic(err)
-	}
-}
diff --git a/scripts/openapi/intercom/metadata/main.go b/scripts/openapi/intercom/metadata/main.go
index da7bf123..a16e8e1d 100644
--- a/scripts/openapi/intercom/metadata/main.go
+++ b/scripts/openapi/intercom/metadata/main.go
@@ -7,7 +7,8 @@ package main
 import (
 	"log/slog"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/providers/intercom"
 	"github.com/amp-labs/connectors/providers/intercom/metadata"
@@ -50,26 +51,26 @@ var (
 
 func main() {
 	explorer, err := openapi.FileManager.GetExplorer()
-	handy.Must(err)
+	goutils.MustBeNil(err)
 
 	readObjects, err := explorer.ReadObjectsGet(
 		api3.NewDenyPathStrategy(ignoreEndpoints),
 		nil, displayNameOverride,
 		api3.CustomMappingObjectCheck(intercom.ObjectNameToResponseField),
 	)
-	handy.Must(err)
+	goutils.MustBeNil(err)
 
 	searchObjects, err := explorer.ReadObjectsPost(
 		api3.NewAllowPathStrategy(searchEndpoints),
 		searchObjectEndpoints, displayNameOverride,
 		api3.CustomMappingObjectCheck(intercom.ObjectNameToResponseField),
 	)
-	handy.Must(err)
+	goutils.MustBeNil(err)
 
 	objects := searchObjects.Combine(readObjects)
 
 	schemas := staticschema.NewMetadata()
-	registry := handy.NamedLists[string]{}
+	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
 		if object.Problem != nil {
@@ -88,8 +89,8 @@ func main() {
 		}
 	}
 
-	handy.Must(metadata.FileManager.SaveSchemas(schemas))
-	handy.Must(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
+	goutils.MustBeNil(metadata.FileManager.SaveSchemas(schemas))
+	goutils.MustBeNil(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
 
 	slog.Info("Completed.")
 }
diff --git a/scripts/openapi/marketo/metadata/metadata.go b/scripts/openapi/marketo/metadata/metadata.go
index c3a30880..d50ab42b 100644
--- a/scripts/openapi/marketo/metadata/metadata.go
+++ b/scripts/openapi/marketo/metadata/metadata.go
@@ -7,7 +7,7 @@ import (
 	"strings"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/providers/marketo/metadata"
 	"github.com/getkin/kin-openapi/openapi2"
@@ -25,12 +25,12 @@ func main() {
 	// 5 represents the amount of substrings that will be generated
 	// when path of interest is split using `/`
 	def, docA, err := constructDefinitions(assets, 5) //nolint:gomnd
-	handy.Must(err)
+	goutils.MustBeNil(err)
 
 	// 4 represents the amount of substrings that will be generated
 	// when path of interest is split using `/`
 	ldef, docL, err := constructDefinitions(leads, 4) //nolint:gomnd
-	handy.Must(err)
+	goutils.MustBeNil(err)
 
 	// Initializes an empty ObjectMetadata variable
 	objectMetadata := make(map[string]staticschema.Object)
@@ -41,7 +41,7 @@ func main() {
 	// Adds Assets Metadata details to the same variable declared above.
 	objectMetadata = generateMetadata(def, docA, objectMetadata)
 
-	handy.Must(metadata.FileManager.SaveSchemas(&staticschema.Metadata{
+	goutils.MustBeNil(metadata.FileManager.SaveSchemas(&staticschema.Metadata{
 		Modules: map[common.ModuleID]staticschema.Module{
 			staticschema.RootModuleID: {
 				Objects: objectMetadata,
diff --git a/scripts/openapi/pipedrive/metadata/main.go b/scripts/openapi/pipedrive/metadata/main.go
index 0ae0f700..4bee7981 100644
--- a/scripts/openapi/pipedrive/metadata/main.go
+++ b/scripts/openapi/pipedrive/metadata/main.go
@@ -4,7 +4,7 @@ import (
 	"log"
 	"log/slog"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/providers/pipedrive/metadata"
 	"github.com/amp-labs/connectors/providers/pipedrive/openapi"
@@ -76,7 +76,7 @@ func main() {
 	}
 
 	schemas := staticschema.NewMetadata()
-	registry := handy.NamedLists[string]{}
+	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
 		if object.Problem != nil {
diff --git a/scripts/openapi/pipeliner/metadata/main.go b/scripts/openapi/pipeliner/metadata/main.go
index 551a28df..b41add73 100644
--- a/scripts/openapi/pipeliner/metadata/main.go
+++ b/scripts/openapi/pipeliner/metadata/main.go
@@ -4,7 +4,8 @@ import (
 	"log"
 	"log/slog"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/providers/pipeliner/metadata"
 	"github.com/amp-labs/connectors/providers/pipeliner/openapi"
@@ -36,16 +37,16 @@ func main() {
 			api3.CamelCaseToSpaceSeparated,
 			api3.CapitalizeFirstLetterEveryWord,
 		))
-	must(err)
+	goutils.MustBeNil(err)
 
 	objects, err := explorer.ReadObjectsGet(
 		api3.NewDenyPathStrategy(ignoreEndpoints),
 		nil, displayNameOverride, api3.DataObjectLocator,
 	)
-	must(err)
+	goutils.MustBeNil(err)
 
 	schemas := staticschema.NewMetadata()
-	registry := handy.NamedLists[string]{}
+	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
 		if object.Problem != nil {
@@ -64,14 +65,8 @@ func main() {
 		}
 	}
 
-	must(metadata.FileManager.SaveSchemas(schemas))
-	must(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
+	goutils.MustBeNil(metadata.FileManager.SaveSchemas(schemas))
+	goutils.MustBeNil(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
 
 	log.Println("Completed.")
 }
-
-func must(err error) {
-	if err != nil {
-		panic(err)
-	}
-}
diff --git a/scripts/openapi/zendesksupport/metadata/helpcenter/objects.go b/scripts/openapi/zendesksupport/metadata/helpcenter/objects.go
index f311cfc9..11d4198f 100644
--- a/scripts/openapi/zendesksupport/metadata/helpcenter/objects.go
+++ b/scripts/openapi/zendesksupport/metadata/helpcenter/objects.go
@@ -1,7 +1,7 @@
 package helpcenter
 
 import (
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/providers/zendesksupport"
 	"github.com/amp-labs/connectors/providers/zendesksupport/openapi"
 	"github.com/amp-labs/connectors/tools/fileconv/api3"
@@ -26,14 +26,14 @@ func Objects() []api3.Schema {
 			api3.CapitalizeFirstLetterEveryWord,
 		),
 	)
-	handy.Must(err)
+	goutils.MustBeNil(err)
 
 	objects, err := explorer.ReadObjectsGet(
 		api3.NewDenyPathStrategy(ignoreEndpoints),
 		objectEndpoints, nil,
 		api3.CustomMappingObjectCheck(zendesksupport.ObjectNameToResponseField[zendesksupport.ModuleHelpCenter]),
 	)
-	handy.Must(err)
+	goutils.MustBeNil(err)
 
 	return objects
 }
diff --git a/scripts/openapi/zendesksupport/metadata/main.go b/scripts/openapi/zendesksupport/metadata/main.go
index 654e006a..3d2c78de 100644
--- a/scripts/openapi/zendesksupport/metadata/main.go
+++ b/scripts/openapi/zendesksupport/metadata/main.go
@@ -4,7 +4,8 @@ import (
 	"log/slog"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/providers/zendesksupport"
 	"github.com/amp-labs/connectors/providers/zendesksupport/metadata"
@@ -16,8 +17,8 @@ import (
 
 func main() {
 	schemas := staticschema.NewMetadata()
-	registry := handy.NamedLists[string]{}
-	lists := handy.IndexedLists[common.ModuleID, api3.Schema]{}
+	registry := datautils.NamedLists[string]{}
+	lists := datautils.IndexedLists[common.ModuleID, api3.Schema]{}
 
 	lists.Add(zendesksupport.ModuleTicketing, support.Objects()...)
 	lists.Add(zendesksupport.ModuleHelpCenter, helpcenter.Objects()...)
@@ -41,8 +42,8 @@ func main() {
 		}
 	}
 
-	handy.Must(metadata.FileManager.SaveSchemas(schemas))
-	handy.Must(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
+	goutils.MustBeNil(metadata.FileManager.SaveSchemas(schemas))
+	goutils.MustBeNil(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
 
 	slog.Info("Completed.")
 }
diff --git a/scripts/openapi/zendesksupport/metadata/support/objects.go b/scripts/openapi/zendesksupport/metadata/support/objects.go
index 3e10ecb8..b930a7bf 100644
--- a/scripts/openapi/zendesksupport/metadata/support/objects.go
+++ b/scripts/openapi/zendesksupport/metadata/support/objects.go
@@ -1,7 +1,7 @@
 package support
 
 import (
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/providers/zendesksupport"
 	"github.com/amp-labs/connectors/providers/zendesksupport/openapi"
 	"github.com/amp-labs/connectors/tools/fileconv/api3"
@@ -74,14 +74,14 @@ func Objects() []api3.Schema {
 			api3.CapitalizeFirstLetterEveryWord,
 		),
 	)
-	handy.Must(err)
+	goutils.MustBeNil(err)
 
 	objects, err := explorer.ReadObjectsGet(
 		api3.NewDenyPathStrategy(ignoreEndpoints),
 		nil, displayNameOverride,
 		api3.CustomMappingObjectCheck(zendesksupport.ObjectNameToResponseField[zendesksupport.ModuleTicketing]),
 	)
-	handy.Must(err)
+	goutils.MustBeNil(err)
 
 	return objects
 }
diff --git a/scripts/scrapper/salesloft/metadata/main.go b/scripts/scrapper/salesloft/metadata/main.go
index f976ae99..cdc3525f 100644
--- a/scripts/scrapper/salesloft/metadata/main.go
+++ b/scripts/scrapper/salesloft/metadata/main.go
@@ -8,8 +8,9 @@ import (
 	"strings"
 
 	"github.com/PuerkitoBio/goquery"
-	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/common/naming"
+	"github.com/amp-labs/connectors/internal/datautils"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/providers/salesloft/metadata"
 	"github.com/amp-labs/connectors/tools/scrapper"
@@ -61,12 +62,12 @@ func createIndex() {
 		log.Printf("Index completed %.2f%%\n", getPercentage(i, len(sections))) // nolint:forbidigo
 	}
 
-	must(metadata.FileManager.SaveIndex(registry))
+	goutils.MustBeNil(metadata.FileManager.SaveIndex(registry))
 }
 
 func createSchemas() {
 	index, err := metadata.FileManager.LoadIndex()
-	must(err)
+	goutils.MustBeNil(err)
 
 	schemas := staticschema.NewMetadata()
 
@@ -97,14 +98,14 @@ func createSchemas() {
 		log.Printf("Schemas completed %.2f%% [%v]\n", getPercentage(i, len(filteredListDocs)), modelName)
 	}
 
-	must(metadata.FileManager.SaveSchemas(schemas))
+	goutils.MustBeNil(metadata.FileManager.SaveSchemas(schemas))
 }
 
 func createQueryParamStats() {
 	index, err := metadata.FileManager.LoadIndex()
-	must(err)
+	goutils.MustBeNil(err)
 
-	registry := handy.NamedLists[string]{}
+	registry := datautils.NamedLists[string]{}
 
 	filteredListDocs := getFilteredListDocs(index)
 	numObjects := len(filteredListDocs)
@@ -122,7 +123,7 @@ func createQueryParamStats() {
 		log.Printf("Query param schemas completed %.2f%% [%v]\n", getPercentage(i, numObjects), modelName)
 	}
 
-	must(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
+	goutils.MustBeNil(metadata.FileManager.SaveQueryParamStats(scrapper.CalculateQueryParamStats(registry)))
 }
 
 /*
@@ -171,12 +172,6 @@ func getPercentage(i int, i2 int) float64 {
 	return (float64(i+1) / float64(i2)) * 100 // nolint:gomnd
 }
 
-func must(err error) {
-	if err != nil {
-		panic(err)
-	}
-}
-
 // List of exceptions:
 //   - Retrieve a list of Requests
 //   - Fetch current user
diff --git a/tools/fileconv/api3/models.go b/tools/fileconv/api3/models.go
index 07e73b91..795641af 100644
--- a/tools/fileconv/api3/models.go
+++ b/tools/fileconv/api3/models.go
@@ -7,7 +7,7 @@ import (
 	"net/http"
 	"strings"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/getkin/kin-openapi/openapi3"
 )
 
@@ -45,7 +45,7 @@ type Schema struct {
 type Schemas []Schema
 
 func (s Schemas) Combine(others Schemas) Schemas {
-	registry := handy.Map[string, Schema]{}
+	registry := datautils.Map[string, Schema]{}
 	for _, schema := range append(s, others...) {
 		_, found := registry[schema.ObjectName]
 
@@ -171,7 +171,7 @@ func extractFieldsFromArrayHolder(
 		}
 	}
 
-	if handy.Pointers.IsTrue(schema.AdditionalProperties.Has) {
+	if isBooleanTruthful(schema.AdditionalProperties.Has) {
 		// this schema is dynamic.
 		// the fields cannot be known.
 		return []string{}, nil
@@ -254,7 +254,7 @@ func extractSchema(operation *openapi3.Operation) *openapi3.Schema {
 }
 
 func extractFields(source *openapi3.Schema) ([]string, error) {
-	combined := make(handy.Set[string])
+	combined := make(datautils.Set[string])
 
 	if source.AnyOf != nil {
 		// this object can be represented by various definitions
@@ -340,3 +340,11 @@ func getSchemaType(schema *openapi3.Schema) definitionSchemaType {
 func createUnprocessableObjectError(objectName string) error {
 	return fmt.Errorf("%w: object %v", ErrUnprocessableObject, objectName)
 }
+
+func isBooleanTruthful(input *bool) bool {
+	if input == nil {
+		return false
+	}
+
+	return *input
+}
diff --git a/tools/fileconv/api3/parameters.go b/tools/fileconv/api3/parameters.go
index 3fc6757e..4027bf6f 100644
--- a/tools/fileconv/api3/parameters.go
+++ b/tools/fileconv/api3/parameters.go
@@ -1,8 +1,8 @@
 package api3
 
 import (
-	"github.com/amp-labs/connectors/common/handy"
 	"github.com/amp-labs/connectors/common/naming"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/iancoleman/strcase"
 )
@@ -36,7 +36,7 @@ func DataObjectLocator(objectName, fieldName string) bool {
 //
 // This can be understood as follows: orders, carts, coupons REST resources will be found under JSON response field
 // matching "it's name", while the rest will be located under "data" field.
-func CustomMappingObjectCheck(dict handy.DefaultMap[string, string]) ObjectArrayLocator {
+func CustomMappingObjectCheck(dict datautils.DefaultMap[string, string]) ObjectArrayLocator {
 	return func(objectName, fieldName string) bool {
 		expected := dict.Get(objectName)
 
diff --git a/tools/fileconv/api3/queries.go b/tools/fileconv/api3/queries.go
index 6c502c2e..07cf8aba 100644
--- a/tools/fileconv/api3/queries.go
+++ b/tools/fileconv/api3/queries.go
@@ -5,7 +5,7 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/getkin/kin-openapi/openapi3"
 )
 
@@ -100,7 +100,7 @@ func (e Explorer) ReadObjects(
 func (e Explorer) GetPathItems(
 	pathMatcher PathMatcher, endpointResources map[string]string,
 ) []PathItem {
-	items := handy.Map[string, PathItem]{}
+	items := datautils.Map[string, PathItem]{}
 
 	for path, pathObj := range e.schema.GetPaths() {
 		if !pathMatcher.IsPathMatching(path) {
diff --git a/tools/fileconv/api3/strategy.go b/tools/fileconv/api3/strategy.go
index d2e39a84..f8040d24 100644
--- a/tools/fileconv/api3/strategy.go
+++ b/tools/fileconv/api3/strategy.go
@@ -3,7 +3,7 @@ package api3
 import (
 	"strings"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 // NewAllowPathStrategy produces a path matching strategy that will accept only those paths that matched the list.
@@ -44,7 +44,7 @@ type PathMatcher interface {
 //   - prefix matching,
 //   - suffix matching.
 type StarRulePathResolver struct {
-	endpoints            handy.StringSet
+	endpoints            datautils.StringSet
 	prefixes             []string
 	suffixes             []string
 	pathMatchingCallback func(hasMatched bool) bool
@@ -55,7 +55,7 @@ func newStarRulePathResolver(
 	pathMatchingCallback func(matched bool) bool,
 ) *StarRulePathResolver {
 	result := &StarRulePathResolver{
-		endpoints:            handy.NewStringSet(),
+		endpoints:            datautils.NewStringSet(),
 		prefixes:             make([]string, 0),
 		suffixes:             make([]string, 0),
 		pathMatchingCallback: pathMatchingCallback,
diff --git a/tools/scrapper/models.go b/tools/scrapper/models.go
index fc6961b1..00d937ab 100644
--- a/tools/scrapper/models.go
+++ b/tools/scrapper/models.go
@@ -8,7 +8,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 type ModelDocLinks []ModelDocLink
@@ -81,8 +81,8 @@ 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.NamedLists[string]) *QueryParamStats {
-	objects := handy.NewStringSet()
+func CalculateQueryParamStats(queryParamRegistry datautils.NamedLists[string]) *QueryParamStats {
+	objects := datautils.NewStringSet()
 	for _, objectNames := range queryParamRegistry {
 		objects.Add(objectNames)
 	}


Test patch:
diff --git a/common/jsonquery/queries_test.go b/common/jsonquery/queries_test.go
index 6219758f..de847652 100644
--- a/common/jsonquery/queries_test.go
+++ b/common/jsonquery/queries_test.go
@@ -5,7 +5,7 @@ import (
 	"reflect"
 	"testing"
 
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/test/utils/testutils"
 	"github.com/spyzhov/ajson"
 )
@@ -86,7 +86,7 @@ func TestQueryInteger(t *testing.T) { // nolint:funlen
 			input: inType{
 				key: "count",
 			},
-			expected: handy.Pointers.Int64(38),
+			expected: goutils.Pointer[int64](38),
 		},
 		{
 			name: "Reaching for nested integer",
@@ -95,7 +95,7 @@ func TestQueryInteger(t *testing.T) { // nolint:funlen
 				optional: false,
 				zoom:     []string{"payload", "notes", "body"},
 			},
-			expected: handy.Pointers.Int64(359),
+			expected: goutils.Pointer[int64](359),
 		},
 		{
 			name: "Reaching for nested integer using self reference",
@@ -104,7 +104,7 @@ func TestQueryInteger(t *testing.T) { // nolint:funlen
 				optional: false,
 				zoom:     []string{"payload", "notes", "body", "amount"},
 			},
-			expected: handy.Pointers.Int64(359),
+			expected: goutils.Pointer[int64](359),
 		},
 		{
 			name: "Non existent zoom path is ok for optional integer",
@@ -181,7 +181,7 @@ func TestQueryString(t *testing.T) { // nolint:funlen
 			input: inType{
 				key: "text",
 			},
-			expected: handy.Pointers.Str("Hello World"),
+			expected: goutils.Pointer("Hello World"),
 		},
 		{
 			name: "Reaching for nested string",
@@ -190,7 +190,7 @@ func TestQueryString(t *testing.T) { // nolint:funlen
 				optional: false,
 				zoom:     []string{"payload", "notes", "body"},
 			},
-			expected: handy.Pointers.Str("Some notes"),
+			expected: goutils.Pointer("Some notes"),
 		},
 		{
 			name: "Reaching for nested string using self reference",
@@ -199,7 +199,7 @@ func TestQueryString(t *testing.T) { // nolint:funlen
 				optional: false,
 				zoom:     []string{"payload", "notes", "body", "text"},
 			},
-			expected: handy.Pointers.Str("Some notes"),
+			expected: goutils.Pointer("Some notes"),
 		},
 		{
 			name: "Non existent zoom path is ok for optional string",
@@ -276,7 +276,7 @@ func TestQueryBool(t *testing.T) { // nolint:funlen
 			input: inType{
 				key: "inProgress",
 			},
-			expected: handy.Pointers.Bool(false),
+			expected: goutils.Pointer(false),
 		},
 		{
 			name: "Reaching for nested bool",
@@ -285,7 +285,7 @@ func TestQueryBool(t *testing.T) { // nolint:funlen
 				optional: false,
 				zoom:     []string{"payload", "notes", "body"},
 			},
-			expected: handy.Pointers.Bool(true),
+			expected: goutils.Pointer(true),
 		},
 		{
 			name: "Reaching for nested bool using self reference",
@@ -294,7 +294,7 @@ func TestQueryBool(t *testing.T) { // nolint:funlen
 				optional: false,
 				zoom:     []string{"payload", "notes", "body", "purchased"},
 			},
-			expected: handy.Pointers.Bool(true),
+			expected: goutils.Pointer(true),
 		},
 		{
 			name: "Non existent zoom path is ok for optional bool",
diff --git a/common/handy/set_test.go b/internal/datautils/set_test.go
similarity index 98%
rename from common/handy/set_test.go
rename to internal/datautils/set_test.go
index 21e72b73..0d0a5d62 100644
--- a/common/handy/set_test.go
+++ b/internal/datautils/set_test.go
@@ -1,4 +1,4 @@
-package handy
+package datautils
 
 import (
 	"sort"
diff --git a/common/handy/timing_test.go b/internal/datautils/timing_test.go
similarity index 98%
rename from common/handy/timing_test.go
rename to internal/datautils/timing_test.go
index 57f73497..757ab640 100644
--- a/common/handy/timing_test.go
+++ b/internal/datautils/timing_test.go
@@ -1,4 +1,4 @@
-package handy
+package datautils
 
 import (
 	"testing"
diff --git a/test/customerio/journeysapp/metadata/main.go b/test/customerio/journeysapp/metadata/main.go
index 5fe06184..ce0570c8 100644
--- a/test/customerio/journeysapp/metadata/main.go
+++ b/test/customerio/journeysapp/metadata/main.go
@@ -7,7 +7,7 @@ import (
 	"syscall"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 	connTest "github.com/amp-labs/connectors/test/customerio/journeysapp"
 	"github.com/amp-labs/connectors/test/utils"
 )
@@ -36,7 +36,7 @@ func main() {
 
 	slog.Info("Read object using all fields from ListObjectMetadata")
 
-	requestFields := handy.Map[string, string](metadata.Result[objectName].FieldsMap).KeySet()
+	requestFields := datautils.Map[string, string](metadata.Result[objectName].FieldsMap).KeySet()
 
 	response, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
@@ -49,7 +49,7 @@ func main() {
 			utils.Fail("expected to read at least one record", "error", err)
 		}
 
-		givenFields := handy.Map[string, any](response.Data[0].Fields).KeySet()
+		givenFields := datautils.Map[string, any](response.Data[0].Fields).KeySet()
 
 		difference := givenFields.Diff(requestFields)
 		if len(difference) != 0 {
diff --git a/test/dynamicscrm/metadata/main.go b/test/dynamicscrm/metadata/main.go
index f9c5b552..19651df5 100644
--- a/test/dynamicscrm/metadata/main.go
+++ b/test/dynamicscrm/metadata/main.go
@@ -7,7 +7,7 @@ import (
 	"syscall"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/datautils"
 	connTest "github.com/amp-labs/connectors/test/dynamicscrm"
 	"github.com/amp-labs/connectors/test/utils"
 )
@@ -36,7 +36,7 @@ func main() {
 
 	fmt.Println("Read object using all fields from ListObjectMetadata")
 
-	requestFields := handy.Map[string, string](metadata.Result[objectName].FieldsMap).KeySet()
+	requestFields := datautils.Map[string, string](metadata.Result[objectName].FieldsMap).KeySet()
 
 	response, err := conn.Read(ctx, common.ReadParams{
 		ObjectName: objectName,
@@ -49,7 +49,7 @@ func main() {
 			utils.Fail("expected to read at least one record", "error", err)
 		}
 
-		givenFields := handy.Map[string, any](response.Data[0].Fields).KeySet()
+		givenFields := datautils.Map[string, any](response.Data[0].Fields).KeySet()
 
 		difference := givenFields.Diff(requestFields)
 		if len(difference) != 0 {
diff --git a/test/dynamicscrm/write-delete/main.go b/test/dynamicscrm/write-delete/main.go
index 38b18746..3dd0de84 100644
--- a/test/dynamicscrm/write-delete/main.go
+++ b/test/dynamicscrm/write-delete/main.go
@@ -8,7 +8,7 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/handy"
+	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/providers/dynamicscrm"
 	connTest "github.com/amp-labs/connectors/test/dynamicscrm"
 	"github.com/amp-labs/connectors/test/utils"
@@ -61,8 +61,8 @@ func main() {
 	leadID := fmt.Sprintf("%v", lead["leadid"])
 	fmt.Println("Updating some lead properties")
 	updateLead(ctx, conn, leadID, &LeadUploadPayload{
-		LastName:  handy.Pointers.Str(""),
-		FirstName: handy.Pointers.Str("Squidward"),
+		LastName:  goutils.Pointer(""),
+		FirstName: goutils.Pointer("Squidward"),
 	})
 	fmt.Println("View that lead has changed accordingly")