Repository: amp-labs/connectors
Pull request #1291 (2025-01-20 21:26:32)

feat: Static schema supports V1 and V2 Fields Metadata
# Background

There were recent changes to the format of the ObjectMetadata that is returned by ListObjectMetadata.

# Changes
To accommodate these updates, newly created static schema files may need to follow a revised structure. Each reference to the static file structure must now specify a generic type of either `ObjectFieldsV1` or `ObjectFieldsV2`.

* Created new type ObjectFields which is either ObjectFieldsV1(map[string]string) or ObjectFieldsV2 (map[string]common.FieldMetadata)
* Each connector script that parses OpenAPI was affected and now uses the existing `ObjectFieldsV1`.
* Files located under `.../<provider_name>/metadata` were previously named `scrapper.go`. While we are at it, renamed them to be `schemas.go`, because they do not perform scraping but instead reference a constant metadata structure. For context, originally first files were created using scrapping.

# Review
Main code changes are under `internal/staticschema/core.go` file.

# Tests
Ran each OpenAPI script. It produces the same output as before.

Cobalt0s: @RajatPawar I decided to add static schema file to Hubspot. It needs to follow a new ObjectMetedata schema. Could you have a look?

Patch:
diff --git a/internal/staticschema/convertors.go b/internal/staticschema/convertors.go
index 42bb04a2..04363109 100644
--- a/internal/staticschema/convertors.go
+++ b/internal/staticschema/convertors.go
@@ -1,18 +1,14 @@
 package staticschema
 
 import (
-	"errors"
 	"fmt"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/datautils"
 )
 
-var ErrObjectNotFound = errors.New("object not found")
-
 // Select will look for object names under the module and will return metadata result for those objects.
 // NOTE: empty module id is treated as root module.
-func (r *Metadata) Select(
+func (m *Metadata[F]) Select(
 	moduleID common.ModuleID, objectNames []string,
 ) (*common.ListObjectMetadataResult, error) {
 	if len(objectNames) == 0 {
@@ -22,7 +18,7 @@ func (r *Metadata) Select(
 	moduleID = moduleIdentifier(moduleID)
 
 	// Convert and return only listed objects
-	module, ok := r.Modules[moduleID]
+	module, ok := m.Modules[moduleID]
 	if !ok {
 		return nil, fmt.Errorf("%w: connector is using unknown module [%v]", common.ErrMissingModule, moduleID)
 	}
@@ -36,12 +32,9 @@ func (r *Metadata) Select(
 	for _, objectName := range objectNames {
 		if v, ok := module.Objects[objectName]; ok {
 			// move metadata from scrapper object to common object
-			list.Result[objectName] = common.ObjectMetadata{
-				DisplayName: v.DisplayName,
-				FieldsMap:   datautils.FromMap(v.FieldsMap).ShallowCopy(),
-			}
+			list.Result[objectName] = *v.getObjectMetadata()
 		} else {
-			return nil, fmt.Errorf("%w: unknown object [%v]", ErrObjectNotFound, objectName)
+			return nil, fmt.Errorf("%w: unknown object [%v]", common.ErrObjectNotSupported, objectName)
 		}
 	}
 
@@ -50,29 +43,20 @@ func (r *Metadata) Select(
 
 // SelectOne reads one object metadata from the static file.
 // NOTE: empty module id is treated as root module.
-func (r *Metadata) SelectOne(
+func (m *Metadata[F]) SelectOne(
 	moduleID common.ModuleID, objectName string,
 ) (*common.ObjectMetadata, error) {
 	moduleID = moduleIdentifier(moduleID)
 
 	// Convert and return only listed objects
-	module, ok := r.Modules[moduleID]
+	module, ok := m.Modules[moduleID]
 	if !ok {
 		return nil, fmt.Errorf("%w: connector is using unknown module [%v]", common.ErrMissingModule, moduleID)
 	}
 
-	mtd := common.ObjectMetadata{
-		FieldsMap: make(map[string]string),
-	}
-
 	if v, ok := module.Objects[objectName]; ok {
-		mtd = common.ObjectMetadata{
-			DisplayName: v.DisplayName,
-			FieldsMap:   v.FieldsMap,
-		}
-	} else {
-		return nil, fmt.Errorf("%w: unknown object [%v]", ErrObjectNotFound, objectName)
+		return v.getObjectMetadata(), nil
 	}
 
-	return &mtd, nil
+	return nil, fmt.Errorf("%w: unknown object [%v]", common.ErrObjectNotSupported, objectName)
 }
diff --git a/internal/staticschema/core.go b/internal/staticschema/core.go
index dd058723..34e6fea8 100644
--- a/internal/staticschema/core.go
+++ b/internal/staticschema/core.go
@@ -10,9 +10,9 @@ import (
 const RootModuleID common.ModuleID = "root"
 
 // NewMetadata constructs empty Metadata. To populate it with data use Metadata.Add method.
-func NewMetadata() *Metadata {
-	return &Metadata{
-		Modules: make(map[common.ModuleID]Module),
+func NewMetadata[F FieldMetadataMap]() *Metadata[F] {
+	return &Metadata[F]{
+		Modules: make(map[common.ModuleID]Module[F]),
 	}
 }
 
@@ -22,18 +22,18 @@ func NewMetadata() *Metadata {
 // Metadata.ObjectNames - registry of object names by modules.
 // Metadata.LookupURLPath - resolves URL given object name.
 // Metadata.Select - produces output suitable for connectors.ObjectMetadataConnector.
-type Metadata struct {
+type Metadata[F FieldMetadataMap] struct {
 	// Modules is a map of object names to object metadata
-	Modules map[common.ModuleID]Module `json:"modules"`
+	Modules map[common.ModuleID]Module[F] `json:"modules"`
 }
 
-type Module struct {
-	ID      common.ModuleID   `json:"id"`
-	Path    string            `json:"path"`
-	Objects map[string]Object `json:"objects"`
+type Module[F FieldMetadataMap] struct {
+	ID      common.ModuleID      `json:"id"`
+	Path    string               `json:"path"`
+	Objects map[string]Object[F] `json:"objects"`
 }
 
-type Object struct {
+type Object[F FieldMetadataMap] struct {
 	// Provider's display name for the object
 	DisplayName string `json:"displayName"`
 
@@ -44,41 +44,119 @@ type Object struct {
 	// A field name where the object is located within provider response.
 	ResponseKey string `json:"responseKey"`
 
-	// FieldsMap is a map of field names to field display names
-	FieldsMap map[string]string `json:"fields"`
+	// Fields is a map of field names to either field display names or extended field metadata.
+	Fields F `json:"fields"`
 
 	// DocsURL points to docs endpoint. Optional.
 	DocsURL *string `json:"docs,omitempty"`
 }
 
+type (
+	FieldMetadataMapV1 map[string]string
+	FieldMetadataMapV2 map[string]common.FieldMetadata
+
+	FieldMetadataMap interface {
+		FieldMetadataMapV1 | FieldMetadataMapV2
+	}
+)
+
+// All maps must be copies of the original map, which remains immutable.
+// The immutable map serves as a unified registry of metadata for the connector.
+// ListObjectMetadata returns a copy that consumers are allowed to modify as needed.
+func (o Object[F]) getObjectMetadata() *common.ObjectMetadata {
+	if fieldsMap, isV1 := (any(o.Fields)).(FieldMetadataMapV1); isV1 {
+		return &common.ObjectMetadata{
+			DisplayName: o.DisplayName,
+			FieldsMap:   datautils.FromMap(fieldsMap).ShallowCopy(),
+		}
+	}
+
+	fields, isV2 := (any(o.Fields)).(FieldMetadataMapV2)
+	if !isV2 {
+		// Unknown fieldsMap version.
+		return &common.ObjectMetadata{}
+	}
+
+	return common.NewObjectMetadata(
+		o.DisplayName,
+		datautils.FromMap(fields).ShallowCopy(),
+	)
+}
+
 // Add will appropriately store the data, abiding to data structure rules.
 // NOTE: empty module id is treated as root module.
-func (r *Metadata) Add(
+func (m *Metadata[F]) Add( // nolint:funlen
 	moduleID common.ModuleID,
-	objectName, objectDisplayName, fieldName, urlPath, responseKey string,
+	objectName, objectDisplayName, urlPath, responseKey string,
+	fieldMetadataMap F,
 	docsURL *string,
 ) {
 	moduleID = moduleIdentifier(moduleID)
 
-	module := r.getOrCreateModule(moduleID)
+	module := m.getOrCreateModule(moduleID)
 
-	data, ok := module.Objects[objectName]
-	if !ok {
-		data = Object{
+	object, objectExists := module.Objects[objectName]
+	defer func() {
+		module.Objects[objectName] = object
+	}()
+
+	if !objectExists {
+		object = Object[F]{
 			DisplayName: objectDisplayName,
 			URLPath:     urlPath,
 			ResponseKey: responseKey,
-			FieldsMap:   make(map[string]string),
+			Fields:      fieldMetadataMap,
 			DocsURL:     docsURL,
 		}
-		module.Objects[objectName] = data
+
+		return
 	}
 
-	data.FieldsMap[fieldName] = fieldName
+	// This code acts as a bridge between a concrete type and a generic type.
+	//
+	// Key points:
+	// - The range operation is not supported for expressions without a core type.
+	//   Therefore, the map is type-asserted to its concrete type.
+	// - For the concrete type, old and new values are copied into a joined map of that type.
+	// - Finally, the map is converted back to the generic type.
+	//
+	// Note:
+	// - This is a workaround for limitations in Go generics.
+	// - During compilation, only one execution path is valid for each concrete type.
+	//   The other paths are unreachable and conceptually invalid, as they pertain to entirely different types.
+	if presentFields, isV1 := (any(object.Fields)).(FieldMetadataMapV1); isV1 {
+		fieldsMap := make(FieldMetadataMapV1)
+		for k, v := range presentFields {
+			fieldsMap[k] = v
+		}
+
+		for k, v := range (any(fieldMetadataMap)).(FieldMetadataMapV1) { // nolint:forcetypeassert
+			fieldsMap[k] = v
+		}
+
+		object.Fields = any(fieldsMap).(F) // nolint:forcetypeassert
+
+		return
+	}
+
+	if presentFields, isV2 := (any(object.Fields)).(FieldMetadataMapV2); isV2 {
+		fieldsMap := make(FieldMetadataMapV2)
+		for k, v := range presentFields {
+			fieldsMap[k] = v
+		}
+
+		for k, v := range (any(fieldMetadataMap)).(FieldMetadataMapV2) { // nolint:forcetypeassert
+			fieldsMap[k] = v
+		}
+
+		object.Fields = any(fieldsMap).(F) // nolint:forcetypeassert
+
+		return
+	}
 }
 
-func (r *Metadata) refactorLongestCommonPath() {
-	for moduleID, module := range r.Modules {
+func (m *Metadata[F]) refactorLongestCommonPath() {
+	for moduleID, module := range m.Modules {
 		var (
 			commonPath string
 			isFirst    = true
@@ -102,30 +180,30 @@ func (r *Metadata) refactorLongestCommonPath() {
 
 		// CommonPath is now found.
 		module.withPath(commonPath)
-		r.Modules[moduleID] = module
+		m.Modules[moduleID] = module
 	}
 }
 
-func (r *Metadata) getOrCreateModule(moduleID common.ModuleID) Module {
-	module, ok := r.Modules[moduleID]
+func (m *Metadata[F]) getOrCreateModule(moduleID common.ModuleID) Module[F] {
+	module, ok := m.Modules[moduleID]
 	if !ok {
 		// new module
-		module = Module{
+		module = Module[F]{
 			ID:      moduleID,
 			Path:    "",
-			Objects: make(map[string]Object),
+			Objects: make(map[string]Object[F]),
 		}
-		r.Modules[moduleID] = module
+		m.Modules[moduleID] = module
 	}
 
 	return module
 }
 
 // ObjectNames provides a registry of object names grouped by module.
-func (r *Metadata) ObjectNames() datautils.UniqueLists[common.ModuleID, string] {
+func (m *Metadata[F]) ObjectNames() datautils.UniqueLists[common.ModuleID, string] {
 	moduleObjectNames := make(datautils.UniqueLists[common.ModuleID, string])
 
-	for key, value := range r.Modules {
+	for key, value := range m.Modules {
 		names := datautils.NewStringSet()
 		for name := range value.Objects {
 			names.AddOne(name)
@@ -144,30 +222,30 @@ func (r *Metadata) ObjectNames() datautils.UniqueLists[common.ModuleID, string]
 
 // LookupURLPath will give you the URL path for the object located under the module.
 // NOTE: empty module id is treated as root module.
-func (r *Metadata) LookupURLPath(moduleID common.ModuleID, objectName string) (string, error) {
+func (m *Metadata[F]) LookupURLPath(moduleID common.ModuleID, objectName string) (string, error) {
 	moduleID = moduleIdentifier(moduleID)
 
-	path := r.Modules[moduleID].Objects[objectName].URLPath
+	path := m.Modules[moduleID].Objects[objectName].URLPath
 	if len(path) == 0 {
 		return "", common.ErrResolvingURLPathForObject
 	}
 
-	fullPath := r.LookupModuleURLPath(moduleID) + path
+	fullPath := m.LookupModuleURLPath(moduleID) + path
 
 	return fullPath, nil
 }
 
-func (r *Metadata) LookupModuleURLPath(moduleID common.ModuleID) string {
+func (m *Metadata[F]) LookupModuleURLPath(moduleID common.ModuleID) string {
 	moduleID = moduleIdentifier(moduleID)
 
-	return r.Modules[moduleID].Path
+	return m.Modules[moduleID].Path
 }
 
 // ModuleRegistry returns the list of API modules from static schema.
-func (r *Metadata) ModuleRegistry() common.Modules {
-	result := make(common.Modules, len(r.Modules))
+func (m *Metadata[F]) ModuleRegistry() common.Modules {
+	result := make(common.Modules, len(m.Modules))
 
-	for id, module := range r.Modules {
+	for id, module := range m.Modules {
 		// Label and version is not differentiated and all is part of path.
 		result[id] = common.Module{
 			ID:      module.ID,
@@ -181,15 +259,15 @@ func (r *Metadata) ModuleRegistry() common.Modules {
 
 // LookupArrayFieldName will give you the field name which holds the array of objects in provider response.
 // Ex: CustomerSubscriptions is located under field name subscriptions => { "subscriptions": [{},{},{}] }.
-func (r *Metadata) LookupArrayFieldName(moduleID common.ModuleID, objectName string) string {
+func (m *Metadata[F]) LookupArrayFieldName(moduleID common.ModuleID, objectName string) string {
 	moduleID = moduleIdentifier(moduleID)
 
-	fieldName := r.Modules[moduleID].Objects[objectName].ResponseKey
+	fieldName := m.Modules[moduleID].Objects[objectName].ResponseKey
 
 	return fieldName
 }
 
-func (m *Module) withPath(path string) {
+func (m *Module[F]) withPath(path string) {
 	// Move last slash from module path to object. It looks better that way.
 	path, _ = strings.CutSuffix(path, "/")
 
diff --git a/internal/staticschema/files.go b/internal/staticschema/files.go
index a1e21d06..ce16ae0b 100644
--- a/internal/staticschema/files.go
+++ b/internal/staticschema/files.go
@@ -9,35 +9,35 @@ import (
 const SchemasFile = "schemas.json"
 
 // FileManager operates on schema.json file.
-type FileManager struct {
+type FileManager[F FieldMetadataMap] struct {
 	schemas []byte
 	locator fileconv.FileLocator
 	flush   fileconv.Flusher
 }
 
-func NewFileManager(schemas []byte, locator fileconv.FileLocator) *FileManager {
-	return &FileManager{
+func NewFileManager[F FieldMetadataMap](schemas []byte, locator fileconv.FileLocator) *FileManager[F] {
+	return &FileManager[F]{
 		schemas: schemas,
 		locator: locator,
 	}
 }
 
 // SaveSchemas is useful method when creating or updating static schema file.
-func (m FileManager) SaveSchemas(schemas *Metadata) error {
+func (m FileManager[F]) SaveSchemas(schemas *Metadata[F]) error {
 	schemas.refactorLongestCommonPath()
 
 	return m.flush.ToFile(m.locator.AbsPathTo(SchemasFile), schemas)
 }
 
 // MustLoadSchemas parses static schema file and returns Metadata for deep connector.
-func (m FileManager) MustLoadSchemas() *Metadata {
-	var result *Metadata
+func (m FileManager[F]) MustLoadSchemas() *Metadata[F] {
+	var result *Metadata[F]
 
 	err := json.Unmarshal(m.schemas, &result)
 	if err != nil {
 		// This error should never occur if schemas file is of correct format.
 		// If at least one test exists for the connector this will be caught at development time.
-		return &Metadata{}
+		return &Metadata[F]{}
 	}
 
 	return result
diff --git a/providers/constantcontact/metadata/scraping.go b/providers/constantcontact/metadata/schemas.go
similarity index 64%
rename from providers/constantcontact/metadata/scraping.go
rename to providers/constantcontact/metadata/schemas.go
index 8e928f4b..c22a8d57 100644
--- a/providers/constantcontact/metadata/scraping.go
+++ b/providers/constantcontact/metadata/schemas.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/providers/customerapp/metadata/scraping.go b/providers/customerapp/metadata/scraping.go
index 8e928f4b..c22a8d57 100644
--- a/providers/customerapp/metadata/scraping.go
+++ b/providers/customerapp/metadata/scraping.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/providers/gong/metadata/scraping.go b/providers/gong/metadata/schemas.go
similarity index 64%
rename from providers/gong/metadata/scraping.go
rename to providers/gong/metadata/schemas.go
index 8e928f4b..c22a8d57 100644
--- a/providers/gong/metadata/scraping.go
+++ b/providers/gong/metadata/schemas.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/providers/instantly/metadata/scraping.go b/providers/instantly/metadata/schemas.go
similarity index 64%
rename from providers/instantly/metadata/scraping.go
rename to providers/instantly/metadata/schemas.go
index 8e928f4b..c22a8d57 100644
--- a/providers/instantly/metadata/scraping.go
+++ b/providers/instantly/metadata/schemas.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/providers/intercom/metadata/scraping.go b/providers/intercom/metadata/schemas.go
similarity index 64%
rename from providers/intercom/metadata/scraping.go
rename to providers/intercom/metadata/schemas.go
index 8e928f4b..c22a8d57 100644
--- a/providers/intercom/metadata/scraping.go
+++ b/providers/intercom/metadata/schemas.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/providers/iterable/metadata/metadata.go b/providers/iterable/metadata/metadata.go
index 8e928f4b..c22a8d57 100644
--- a/providers/iterable/metadata/metadata.go
+++ b/providers/iterable/metadata/metadata.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/providers/keap/metadata/scraping.go b/providers/keap/metadata/scraping.go
index 8e928f4b..c22a8d57 100644
--- a/providers/keap/metadata/scraping.go
+++ b/providers/keap/metadata/scraping.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/providers/kit/metadata/scraping.go b/providers/kit/metadata/scraping.go
index 8e928f4b..c22a8d57 100644
--- a/providers/kit/metadata/scraping.go
+++ b/providers/kit/metadata/scraping.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/providers/klaviyo/metadata/metadata.go b/providers/klaviyo/metadata/metadata.go
index 8e928f4b..c22a8d57 100644
--- a/providers/klaviyo/metadata/metadata.go
+++ b/providers/klaviyo/metadata/metadata.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/providers/klaviyo/objectNames.go b/providers/klaviyo/objectNames.go
index 66bd87f7..fc1fc8f2 100644
--- a/providers/klaviyo/objectNames.go
+++ b/providers/klaviyo/objectNames.go
@@ -31,7 +31,7 @@ func init() {
 		for objectName, object := range module.Objects {
 		Search:
 			for _, preferredSinceField := range prioritySinceFieldsForRead {
-				for currentField := range object.FieldsMap {
+				for currentField := range object.Fields {
 					if preferredSinceField == currentField {
 						objectsNameToSinceFieldName[moduleID][objectName] = preferredSinceField
 
diff --git a/providers/marketo/metadata/file.go b/providers/marketo/metadata/file.go
deleted file mode 100644
index 0ebd600f..00000000
--- a/providers/marketo/metadata/file.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package metadata
-
-import (
-	_ "embed"
-
-	"github.com/amp-labs/connectors/tools/fileconv"
-	"github.com/amp-labs/connectors/tools/scrapper"
-)
-
-var (
-	//go:embed schemas.json
-	schemas []byte
-
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
-
-	// Schemas is cached Object schemas.
-	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
-)
diff --git a/providers/marketo/metadata/schemas.go b/providers/marketo/metadata/schemas.go
new file mode 100644
index 00000000..4cf4a87a
--- /dev/null
+++ b/providers/marketo/metadata/schemas.go
@@ -0,0 +1,20 @@
+package metadata
+
+import (
+	_ "embed"
+
+	"github.com/amp-labs/connectors/internal/staticschema"
+	"github.com/amp-labs/connectors/tools/fileconv"
+	"github.com/amp-labs/connectors/tools/scrapper"
+)
+
+var (
+	//go:embed schemas.json
+	schemas []byte
+
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
+
+	// Schemas is cached Object schemas.
+	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
+)
diff --git a/providers/pipedrive/metadata/scraping.go b/providers/pipedrive/metadata/scraping.go
index 8e928f4b..c22a8d57 100644
--- a/providers/pipedrive/metadata/scraping.go
+++ b/providers/pipedrive/metadata/scraping.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/providers/pipeliner/metadata/schemas.go b/providers/pipeliner/metadata/schemas.go
new file mode 100644
index 00000000..c22a8d57
--- /dev/null
+++ b/providers/pipeliner/metadata/schemas.go
@@ -0,0 +1,22 @@
+package metadata
+
+import (
+	_ "embed"
+
+	"github.com/amp-labs/connectors/internal/staticschema"
+	"github.com/amp-labs/connectors/tools/fileconv"
+	"github.com/amp-labs/connectors/tools/scrapper"
+)
+
+var (
+	// Static file containing a list of object metadata is embedded and can be served.
+	//
+	//go:embed schemas.json
+	schemas []byte
+
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
+
+	// Schemas is cached Object schemas.
+	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
+)
diff --git a/providers/pipeliner/metadata/scraping.go b/providers/pipeliner/metadata/scraping.go
deleted file mode 100644
index 8e928f4b..00000000
--- a/providers/pipeliner/metadata/scraping.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package metadata
-
-import (
-	_ "embed"
-
-	"github.com/amp-labs/connectors/tools/fileconv"
-	"github.com/amp-labs/connectors/tools/scrapper"
-)
-
-var (
-	// Static file containing a list of object metadata is embedded and can be served.
-	//
-	//go:embed schemas.json
-	schemas []byte
-
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
-
-	// Schemas is cached Object schemas.
-	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
-)
diff --git a/providers/salesloft/metadata/schemas.go b/providers/salesloft/metadata/schemas.go
new file mode 100644
index 00000000..c22a8d57
--- /dev/null
+++ b/providers/salesloft/metadata/schemas.go
@@ -0,0 +1,22 @@
+package metadata
+
+import (
+	_ "embed"
+
+	"github.com/amp-labs/connectors/internal/staticschema"
+	"github.com/amp-labs/connectors/tools/fileconv"
+	"github.com/amp-labs/connectors/tools/scrapper"
+)
+
+var (
+	// Static file containing a list of object metadata is embedded and can be served.
+	//
+	//go:embed schemas.json
+	schemas []byte
+
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
+
+	// Schemas is cached Object schemas.
+	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
+)
diff --git a/providers/salesloft/metadata/scrapping.go b/providers/salesloft/metadata/scrapping.go
deleted file mode 100644
index 8e928f4b..00000000
--- a/providers/salesloft/metadata/scrapping.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package metadata
-
-import (
-	_ "embed"
-
-	"github.com/amp-labs/connectors/tools/fileconv"
-	"github.com/amp-labs/connectors/tools/scrapper"
-)
-
-var (
-	// Static file containing a list of object metadata is embedded and can be served.
-	//
-	//go:embed schemas.json
-	schemas []byte
-
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
-
-	// Schemas is cached Object schemas.
-	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
-)
diff --git a/providers/smartlead/metadata/scraping.go b/providers/smartlead/metadata/scraping.go
index 8e928f4b..c22a8d57 100644
--- a/providers/smartlead/metadata/scraping.go
+++ b/providers/smartlead/metadata/scraping.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/providers/zendesksupport/metadata/scrapping.go b/providers/zendesksupport/metadata/scrapping.go
index 8e928f4b..c22a8d57 100644
--- a/providers/zendesksupport/metadata/scrapping.go
+++ b/providers/zendesksupport/metadata/scrapping.go
@@ -3,6 +3,7 @@ package metadata
 import (
 	_ "embed"
 
+	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/tools/fileconv"
 	"github.com/amp-labs/connectors/tools/scrapper"
 )
@@ -13,7 +14,8 @@ var (
 	//go:embed schemas.json
 	schemas []byte
 
-	FileManager = scrapper.NewMetadataFileManager(schemas, fileconv.NewSiblingFileLocator()) // nolint:gochecknoglobals
+	FileManager = scrapper.NewMetadataFileManager[staticschema.FieldMetadataMapV1]( // nolint:gochecknoglobals
+		schemas, fileconv.NewSiblingFileLocator())
 
 	// Schemas is cached Object schemas.
 	Schemas = FileManager.MustLoadSchemas() // nolint:gochecknoglobals
diff --git a/scripts/openapi/constantcontact/metadata/main.go b/scripts/openapi/constantcontact/metadata/main.go
index 855bfcb2..2dde18ff 100644
--- a/scripts/openapi/constantcontact/metadata/main.go
+++ b/scripts/openapi/constantcontact/metadata/main.go
@@ -70,7 +70,7 @@ func main() {
 	)
 	goutils.MustBeNil(err)
 
-	schemas := staticschema.NewMetadata()
+	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
@@ -82,8 +82,10 @@ func main() {
 		}
 
 		for _, field := range object.Fields {
-			schemas.Add("", object.ObjectName, object.DisplayName,
-				field, object.URLPath, object.ResponseKey, nil)
+			schemas.Add("", object.ObjectName, object.DisplayName, object.URLPath, object.ResponseKey,
+				staticschema.FieldMetadataMapV1{
+					field: field,
+				}, nil)
 		}
 
 		for _, queryParam := range object.QueryParams {
diff --git a/scripts/openapi/customerio/journeysapp/metadata/main.go b/scripts/openapi/customerio/journeysapp/metadata/main.go
index ed5877cc..751708bd 100644
--- a/scripts/openapi/customerio/journeysapp/metadata/main.go
+++ b/scripts/openapi/customerio/journeysapp/metadata/main.go
@@ -47,7 +47,7 @@ func main() {
 	)
 	goutils.MustBeNil(err)
 
-	schemas := staticschema.NewMetadata()
+	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
@@ -59,7 +59,10 @@ func main() {
 		}
 
 		for _, field := range object.Fields {
-			schemas.Add("", object.ObjectName, object.DisplayName, field, object.URLPath, object.ResponseKey, nil)
+			schemas.Add("", object.ObjectName, object.DisplayName, object.URLPath, object.ResponseKey,
+				staticschema.FieldMetadataMapV1{
+					field: field,
+				}, nil)
 		}
 
 		for _, queryParam := range object.QueryParams {
diff --git a/scripts/openapi/gong/metadata/main.go b/scripts/openapi/gong/metadata/main.go
index 1e2966eb..d7a35c2d 100644
--- a/scripts/openapi/gong/metadata/main.go
+++ b/scripts/openapi/gong/metadata/main.go
@@ -69,7 +69,7 @@ func main() {
 	)
 	goutils.MustBeNil(err)
 
-	schemas := staticschema.NewMetadata()
+	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
@@ -81,7 +81,10 @@ func main() {
 		}
 
 		for _, field := range object.Fields {
-			schemas.Add("", object.ObjectName, object.DisplayName, field, object.URLPath, object.ResponseKey, nil)
+			schemas.Add("", object.ObjectName, object.DisplayName, object.URLPath, object.ResponseKey,
+				staticschema.FieldMetadataMapV1{
+					field: field,
+				}, nil)
 		}
 
 		for _, queryParam := range object.QueryParams {
diff --git a/scripts/openapi/intercom/metadata/main.go b/scripts/openapi/intercom/metadata/main.go
index c9f0ef7e..9edf7539 100644
--- a/scripts/openapi/intercom/metadata/main.go
+++ b/scripts/openapi/intercom/metadata/main.go
@@ -69,7 +69,7 @@ func main() {
 
 	objects := searchObjects.Combine(readObjects)
 
-	schemas := staticschema.NewMetadata()
+	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
@@ -81,7 +81,10 @@ func main() {
 		}
 
 		for _, field := range object.Fields {
-			schemas.Add("", object.ObjectName, object.DisplayName, field, object.URLPath, object.ResponseKey, nil)
+			schemas.Add("", object.ObjectName, object.DisplayName, object.URLPath, object.ResponseKey,
+				staticschema.FieldMetadataMapV1{
+					field: field,
+				}, nil)
 		}
 
 		for _, queryParam := range object.QueryParams {
diff --git a/scripts/openapi/iterable/metadata/main.go b/scripts/openapi/iterable/metadata/main.go
index 9617387a..89854e1c 100644
--- a/scripts/openapi/iterable/metadata/main.go
+++ b/scripts/openapi/iterable/metadata/main.go
@@ -69,7 +69,7 @@ func main() {
 	)
 	goutils.MustBeNil(err)
 
-	schemas := staticschema.NewMetadata()
+	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
@@ -81,8 +81,10 @@ func main() {
 		}
 
 		for _, field := range object.Fields {
-			schemas.Add("", object.ObjectName, object.DisplayName,
-				field, object.URLPath, object.ResponseKey, nil)
+			schemas.Add("", object.ObjectName, object.DisplayName, object.URLPath, object.ResponseKey,
+				staticschema.FieldMetadataMapV1{
+					field: field,
+				}, nil)
 		}
 
 		for _, queryParam := range object.QueryParams {
diff --git a/scripts/openapi/keap/metadata/main.go b/scripts/openapi/keap/metadata/main.go
index bdeb2dbb..27c93125 100644
--- a/scripts/openapi/keap/metadata/main.go
+++ b/scripts/openapi/keap/metadata/main.go
@@ -16,7 +16,7 @@ import (
 )
 
 func main() {
-	schemas := staticschema.NewMetadata()
+	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 	registry := datautils.NamedLists[string]{}
 	lists := datautils.IndexedLists[common.ModuleID, api3.Schema]{}
 
@@ -33,8 +33,10 @@ func main() {
 			}
 
 			for _, field := range object.Fields {
-				schemas.Add(module, object.ObjectName, object.DisplayName, field,
-					object.URLPath, object.ResponseKey, nil)
+				schemas.Add(module, object.ObjectName, object.DisplayName, object.URLPath, object.ResponseKey,
+					staticschema.FieldMetadataMapV1{
+						field: field,
+					}, nil)
 			}
 
 			for _, queryParam := range object.QueryParams {
diff --git a/scripts/openapi/klaviyo/metadata/main.go b/scripts/openapi/klaviyo/metadata/main.go
index 53fb83a8..2271471e 100644
--- a/scripts/openapi/klaviyo/metadata/main.go
+++ b/scripts/openapi/klaviyo/metadata/main.go
@@ -47,7 +47,7 @@ func main() {
 	)
 	goutils.MustBeNil(err)
 
-	schemas := staticschema.NewMetadata()
+	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
@@ -59,8 +59,10 @@ func main() {
 		}
 
 		for _, field := range object.Fields {
-			schemas.Add(klaviyo.Module2024Oct15, object.ObjectName, object.DisplayName,
-				field, object.URLPath, object.ResponseKey, nil)
+			schemas.Add(klaviyo.Module2024Oct15, object.ObjectName, object.DisplayName, object.URLPath, object.ResponseKey,
+				staticschema.FieldMetadataMapV1{
+					field: field,
+				}, nil)
 		}
 
 		for _, queryParam := range object.QueryParams {
diff --git a/scripts/openapi/marketo/metadata/metadata.go b/scripts/openapi/marketo/metadata/metadata.go
index f7e58ace..e8976b25 100644
--- a/scripts/openapi/marketo/metadata/metadata.go
+++ b/scripts/openapi/marketo/metadata/metadata.go
@@ -33,7 +33,7 @@ func main() {
 	goutils.MustBeNil(err)
 
 	// Initializes an empty ObjectMetadata variable
-	objectMetadata := make(map[string]staticschema.Object)
+	objectMetadata := make(map[string]staticschema.Object[staticschema.FieldMetadataMapV1])
 
 	// Add Lead metadata details
 	objectMetadata = generateMetadata(ldef, docL, objectMetadata)
@@ -41,8 +41,8 @@ func main() {
 	// Adds Assets Metadata details to the same variable declared above.
 	objectMetadata = generateMetadata(def, docA, objectMetadata)
 
-	goutils.MustBeNil(metadata.FileManager.SaveSchemas(&staticschema.Metadata{
-		Modules: map[common.ModuleID]staticschema.Module{
+	goutils.MustBeNil(metadata.FileManager.SaveSchemas(&staticschema.Metadata[staticschema.FieldMetadataMapV1]{
+		Modules: map[common.ModuleID]staticschema.Module[staticschema.FieldMetadataMapV1]{
 			staticschema.RootModuleID: {
 				Objects: objectMetadata,
 			},
@@ -95,8 +95,8 @@ func cleanDefinitions(def string) string {
 }
 
 func generateMetadata(objDefs map[string]string,
-	doc openapi2.T, objectMetadata map[string]staticschema.Object,
-) map[string]staticschema.Object {
+	doc openapi2.T, objectMetadata map[string]staticschema.Object[staticschema.FieldMetadataMapV1],
+) map[string]staticschema.Object[staticschema.FieldMetadataMapV1] {
 	for obj, dfn := range objDefs {
 		schem := doc.Definitions[dfn].Value.Properties
 
@@ -112,10 +112,10 @@ func generateMetadata(objDefs map[string]string,
 			fields[k] = k
 		}
 
-		om := staticschema.Object{
+		om := staticschema.Object[staticschema.FieldMetadataMapV1]{
 			DisplayName: obj,
 			URLPath:     fmt.Sprintf("/%v", obj),
-			FieldsMap:   fields,
+			Fields:      fields,
 		}
 
 		objectMetadata[obj] = om
diff --git a/scripts/openapi/pipedrive/metadata/main.go b/scripts/openapi/pipedrive/metadata/main.go
index 56d6ad9d..6ec4b13a 100644
--- a/scripts/openapi/pipedrive/metadata/main.go
+++ b/scripts/openapi/pipedrive/metadata/main.go
@@ -75,7 +75,7 @@ func main() {
 		log.Fatalln(err)
 	}
 
-	schemas := staticschema.NewMetadata()
+	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
@@ -87,7 +87,10 @@ func main() {
 		}
 
 		for _, field := range object.Fields {
-			schemas.Add("", object.ObjectName, object.DisplayName, field, object.URLPath, object.ResponseKey, nil)
+			schemas.Add("", object.ObjectName, object.DisplayName, object.URLPath, object.ResponseKey,
+				staticschema.FieldMetadataMapV1{
+					field: field,
+				}, nil)
 		}
 
 		for _, queryParam := range object.QueryParams {
diff --git a/scripts/openapi/pipeliner/metadata/main.go b/scripts/openapi/pipeliner/metadata/main.go
index f8ea64e9..7f635fe8 100644
--- a/scripts/openapi/pipeliner/metadata/main.go
+++ b/scripts/openapi/pipeliner/metadata/main.go
@@ -45,7 +45,7 @@ func main() {
 	)
 	goutils.MustBeNil(err)
 
-	schemas := staticschema.NewMetadata()
+	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 	registry := datautils.NamedLists[string]{}
 
 	for _, object := range objects {
@@ -57,7 +57,10 @@ func main() {
 		}
 
 		for _, field := range object.Fields {
-			schemas.Add("", object.ObjectName, object.DisplayName, field, object.URLPath, object.ResponseKey, nil)
+			schemas.Add("", object.ObjectName, object.DisplayName, object.URLPath, object.ResponseKey,
+				staticschema.FieldMetadataMapV1{
+					field: field,
+				}, nil)
 		}
 
 		for _, queryParam := range object.QueryParams {
diff --git a/scripts/openapi/zendesksupport/metadata/main.go b/scripts/openapi/zendesksupport/metadata/main.go
index 22939040..877eedb1 100644
--- a/scripts/openapi/zendesksupport/metadata/main.go
+++ b/scripts/openapi/zendesksupport/metadata/main.go
@@ -16,7 +16,7 @@ import (
 )
 
 func main() {
-	schemas := staticschema.NewMetadata()
+	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 	registry := datautils.NamedLists[string]{}
 	lists := datautils.IndexedLists[common.ModuleID, api3.Schema]{}
 
@@ -33,8 +33,10 @@ func main() {
 			}
 
 			for _, field := range object.Fields {
-				schemas.Add(module, object.ObjectName, object.DisplayName,
-					field, object.URLPath, object.ResponseKey, nil)
+				schemas.Add(module, object.ObjectName, object.DisplayName, object.URLPath, object.ResponseKey,
+					staticschema.FieldMetadataMapV1{
+						field: field,
+					}, nil)
 			}
 
 			for _, queryParam := range object.QueryParams {
diff --git a/scripts/scrapper/salesloft/metadata/main.go b/scripts/scrapper/salesloft/metadata/main.go
index bc02b59e..41691147 100644
--- a/scripts/scrapper/salesloft/metadata/main.go
+++ b/scripts/scrapper/salesloft/metadata/main.go
@@ -69,7 +69,7 @@ func createSchemas() {
 	index, err := metadata.FileManager.LoadIndex()
 	goutils.MustBeNil(err)
 
-	schemas := staticschema.NewMetadata()
+	schemas := staticschema.NewMetadata[staticschema.FieldMetadataMapV1]()
 
 	filteredListDocs := getFilteredListDocs(index)
 	for i := range filteredListDocs { // nolint:varnamelen
@@ -89,7 +89,10 @@ func createSchemas() {
 						newDisplayName, isList := handleDisplayName(model.DisplayName)
 						if isList {
 							schemas.Add("", modelName,
-								newDisplayName, fieldName, fmt.Sprintf("/%v", modelName), "data", &model.URL)
+								newDisplayName, fmt.Sprintf("/%v", modelName), "data",
+								staticschema.FieldMetadataMapV1{
+									fieldName: fieldName,
+								}, &model.URL)
 						}
 					}
 				})
diff --git a/tools/scrapper/files.go b/tools/scrapper/files.go
index 6e93d3d6..8cc32bed 100644
--- a/tools/scrapper/files.go
+++ b/tools/scrapper/files.go
@@ -10,27 +10,29 @@ const (
 	QueryParamStatsFile = "queryParamStats.json"
 )
 
-type MetadataFileManager struct {
-	staticschema.FileManager
+type MetadataFileManager[F staticschema.FieldMetadataMap] struct {
+	staticschema.FileManager[F]
 
 	locator fileconv.FileLocator
 	flush   fileconv.Flusher
 }
 
-func NewMetadataFileManager(schemas []byte, locator fileconv.FileLocator) *MetadataFileManager {
-	return &MetadataFileManager{
-		FileManager: *staticschema.NewFileManager(schemas, locator),
+func NewMetadataFileManager[F staticschema.FieldMetadataMap](
+	schemas []byte, locator fileconv.FileLocator,
+) *MetadataFileManager[F] {
+	return &MetadataFileManager[F]{
+		FileManager: *staticschema.NewFileManager[F](schemas, locator),
 		locator:     locator,
 	}
 }
 
-func (m MetadataFileManager) SaveIndex(index *ModelURLRegistry) error {
+func (m MetadataFileManager[F]) SaveIndex(index *ModelURLRegistry) error {
 	index.Sort()
 
 	return m.flush.ToFile(m.locator.AbsPathTo(IndexFile), index)
 }
 
-func (m MetadataFileManager) LoadIndex() (*ModelURLRegistry, error) {
+func (m MetadataFileManager[F]) LoadIndex() (*ModelURLRegistry, error) {
 	var registry *ModelURLRegistry
 
 	err := LoadFile(m.locator.AbsPathTo(IndexFile), &registry)
@@ -41,6 +43,6 @@ func (m MetadataFileManager) LoadIndex() (*ModelURLRegistry, error) {
 	return registry, nil
 }
 
-func (m MetadataFileManager) SaveQueryParamStats(stats *QueryParamStats) error {
+func (m MetadataFileManager[F]) SaveQueryParamStats(stats *QueryParamStats) error {
 	return m.flush.ToFile(m.locator.AbsPathTo(QueryParamStatsFile), stats)
 }


Test patch:
diff --git a/providers/constantcontact/metadata_test.go b/providers/constantcontact/metadata_test.go
index 1e329cc0..51ccd70b 100644
--- a/providers/constantcontact/metadata_test.go
+++ b/providers/constantcontact/metadata_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
 )
@@ -24,7 +23,7 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Name:         "Unknown object requested",
 			Input:        []string{"butterflies"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:   "Successfully describe multiple objects with metadata",
diff --git a/providers/customerapp/metadata_test.go b/providers/customerapp/metadata_test.go
index f96ed252..e3da769c 100644
--- a/providers/customerapp/metadata_test.go
+++ b/providers/customerapp/metadata_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
 )
@@ -24,7 +23,7 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Name:         "Unknown object requested",
 			Input:        []string{"butterflies"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:   "Successfully describe multiple objects with metadata",
diff --git a/providers/gong/metadata_test.go b/providers/gong/metadata_test.go
index 33ee86f1..998ce9fd 100644
--- a/providers/gong/metadata_test.go
+++ b/providers/gong/metadata_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
 )
@@ -24,7 +23,7 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Name:         "Unknown object requested",
 			Input:        []string{"butterflies"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:       "Successfully describe one object with metadata",
diff --git a/providers/instantly/metadata_test.go b/providers/instantly/metadata_test.go
index 3904b461..ebc5dead 100644
--- a/providers/instantly/metadata_test.go
+++ b/providers/instantly/metadata_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
 )
@@ -24,7 +23,7 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Name:         "Unknown object requested",
 			Input:        []string{"butterflies"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:       "Successfully describe multiple objects with metadata",
diff --git a/providers/intercom/metadata_test.go b/providers/intercom/metadata_test.go
index 5167b530..b5a65517 100644
--- a/providers/intercom/metadata_test.go
+++ b/providers/intercom/metadata_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
 )
@@ -24,7 +23,7 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Name:         "Unknown object requested",
 			Input:        []string{"butterflies"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:   "Successfully describe one object with metadata",
diff --git a/providers/iterable/metadata_test.go b/providers/iterable/metadata_test.go
index c485c918..1a53b117 100644
--- a/providers/iterable/metadata_test.go
+++ b/providers/iterable/metadata_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
 )
@@ -24,7 +23,7 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Name:         "Unknown object requested",
 			Input:        []string{"butterflies"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:   "Successfully describe multiple objects with metadata",
diff --git a/providers/keap/metadata_test.go b/providers/keap/metadata_test.go
index 613e224e..f43f0cf7 100644
--- a/providers/keap/metadata_test.go
+++ b/providers/keap/metadata_test.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockcond"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
@@ -31,7 +30,7 @@ func TestListObjectMetadataV1(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Name:         "Unknown object requested",
 			Input:        []string{"butterflies"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:  "Successfully describe multiple objects with metadata",
diff --git a/providers/klaviyo/metadata_test.go b/providers/klaviyo/metadata_test.go
index e6d365b2..11a41639 100644
--- a/providers/klaviyo/metadata_test.go
+++ b/providers/klaviyo/metadata_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
 )
@@ -24,7 +23,7 @@ func TestListObjectMetadataV1(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Name:         "Unknown object requested",
 			Input:        []string{"butterflies"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:       "Successfully describe multiple objects with metadata",
diff --git a/providers/pipeliner/metadata_test.go b/providers/pipeliner/metadata_test.go
index b96edf34..576e3c2f 100644
--- a/providers/pipeliner/metadata_test.go
+++ b/providers/pipeliner/metadata_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
 )
@@ -24,7 +23,7 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Name:         "Unknown object requested",
 			Input:        []string{"butterflies"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:   "Successfully describe one object with metadata",
diff --git a/providers/salesloft/metadata_test.go b/providers/salesloft/metadata_test.go
index c03eb43c..fe5af1d4 100644
--- a/providers/salesloft/metadata_test.go
+++ b/providers/salesloft/metadata_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
 )
@@ -24,7 +23,7 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Name:         "Unknown object requested",
 			Input:        []string{"butterflies"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:   "Successfully describe one object with metadata",
diff --git a/providers/smartlead/metadata_test.go b/providers/smartlead/metadata_test.go
index e76e2157..2052316b 100644
--- a/providers/smartlead/metadata_test.go
+++ b/providers/smartlead/metadata_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
 )
@@ -24,7 +23,7 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Name:         "Unknown object requested",
 			Input:        []string{"butterflies"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:       "Successfully describe multiple objects with metadata",
diff --git a/providers/zendesksupport/metadata_test.go b/providers/zendesksupport/metadata_test.go
index 6cadfac4..68098318 100644
--- a/providers/zendesksupport/metadata_test.go
+++ b/providers/zendesksupport/metadata_test.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/staticschema"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
 	"github.com/amp-labs/connectors/test/utils/testroutines"
 )
@@ -26,7 +25,7 @@ func TestListObjectMetadataZendeskSupportModule(t *testing.T) { // nolint:funlen
 			Name:         "Object coming from different module is unknown",
 			Input:        []string{"articles"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:   "Successfully describe one object with metadata",
@@ -114,7 +113,7 @@ func TestListObjectMetadataHelpCenterModule(t *testing.T) { // nolint:funlen,goc
 			Name:         "Object coming from different module is unknown",
 			Input:        []string{"brands"},
 			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{staticschema.ErrObjectNotFound},
+			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
 			Name:   "Successfully describe one object with metadata",