Repository: amp-labs/connectors
Pull request #585 (2024-06-21 21:41:33)

fix: Gong remove options, add error handler
# Changes

* Parse error in the format that gong utilises
* Method `count number of records` - counts records manually, because currentPageSize doesn't reflect this
* Removed Workspace and APIModule options
* Response parsing to use `jsonquery` for a shorter form, adapted tests respectively
* Tiny changes to connector gen template files
* Pull connector instantiation for testing that can be used by other 'Methods'

Making request using existing `Read` test returns output as expected.
![image](https://github.com/amp-labs/connectors/assets/60330780/a9654a31-8c48-4969-b670-fabf7f946f7b)



Patch:
diff --git a/common/parse.go b/common/parse.go
index bd937948..6ef0e6a1 100644
--- a/common/parse.go
+++ b/common/parse.go
@@ -6,7 +6,11 @@ import (
 	"github.com/spyzhov/ajson"
 )
 
-type NextPageFunc func(*ajson.Node) (string, error)
+type (
+	ListSizeFunc func(*ajson.Node) (int64, error)
+	NextPageFunc func(*ajson.Node) (string, error)
+	RecordsFunc  func(*ajson.Node) ([]map[string]any, error)
+)
 
 // ParseResult parses the response from a provider into a ReadResult. A 2xx return type is assumed.
 // The sizeFunc, recordsFunc, nextPageFunc, and marshalFunc are used to extract the relevant data from the response.
diff --git a/go.mod b/go.mod
index b799ab72..82aa294a 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.21.0
 
 require (
 	github.com/PuerkitoBio/goquery v1.9.2
-	github.com/antchfx/xmlquery v1.4.0
+	github.com/antchfx/xmlquery v1.4.1
 	github.com/brianvoe/gofakeit/v6 v6.28.0
 	github.com/gertd/go-pluralize v0.2.1
 	github.com/go-playground/validator v9.31.0+incompatible
@@ -21,7 +21,7 @@ require (
 
 require (
 	github.com/andybalholm/cascadia v1.3.2 // indirect
-	github.com/antchfx/xpath v1.3.0 // indirect
+	github.com/antchfx/xpath v1.3.1 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
diff --git a/go.sum b/go.sum
index 5640f3bd..258ad7e6 100644
--- a/go.sum
+++ b/go.sum
@@ -2,11 +2,11 @@ github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4
 github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
 github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
 github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
-github.com/antchfx/xmlquery v1.4.0 h1:xg2HkfcRK2TeTbdb0m1jxCYnvsPaGY/oeZWTGqX/0hA=
-github.com/antchfx/xmlquery v1.4.0/go.mod h1:Ax2aeaeDjfIw3CwXKDQ0GkwZ6QlxoChlIBP+mGnDFjI=
+github.com/antchfx/xmlquery v1.4.1 h1:YgpSwbeWvLp557YFTi8E3z6t6/hYjmFEtiEKbDfEbl0=
+github.com/antchfx/xmlquery v1.4.1/go.mod h1:lKezcT8ELGt8kW5L+ckFMTbgdR61/odpPgDv8Gvi1fI=
 github.com/antchfx/xpath v0.0.0-20170515025933-1f3266e77307/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
-github.com/antchfx/xpath v1.3.0 h1:nTMlzGAK3IJ0bPpME2urTuFL76o4A96iYvoKFHRXJgc=
-github.com/antchfx/xpath v1.3.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
+github.com/antchfx/xpath v1.3.1 h1:PNbFuUqHwWl0xRjvUPjJ95Agbmdj2uzzIwmQKgu4oCk=
+github.com/antchfx/xpath v1.3.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
 github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
 github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
 github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
diff --git a/gong/connector.go b/gong/connector.go
index 8caa830e..5d2ddb8f 100644
--- a/gong/connector.go
+++ b/gong/connector.go
@@ -1,9 +1,11 @@
 package gong
 
 import (
-	"net/http"
+	"strings"
 
 	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/interpreter"
+	"github.com/amp-labs/connectors/common/urlbuilder"
 	"github.com/amp-labs/connectors/providers"
 )
 
@@ -14,54 +16,53 @@ type Connector struct {
 	Client  *common.JSONHTTPClient
 }
 
-func WithCatalogSubstitutions(substitutions map[string]string) Option {
-	return func(params *gongParams) {
-		params.substitutions = substitutions
-	}
-}
-
 func NewConnector(opts ...Option) (conn *Connector, outErr error) {
 	defer common.PanicRecovery(func(cause error) {
 		outErr = cause
 		conn = nil
 	})
 
-	params := &gongParams{}
-	for _, opt := range opts {
-		opt(params)
-	}
-
-	var err error
-
-	params, err = params.prepare()
+	params, err := parameters{}.FromOptions(opts...)
 	if err != nil {
 		return nil, err
 	}
 
+	httpClient := params.Client.Caller
+	conn = &Connector{
+		Client: &common.JSONHTTPClient{
+			HTTPClient: httpClient,
+		},
+	}
+
 	// Read provider info
-	providerInfo, err := providers.ReadInfo(providers.Gong, &map[string]string{
-		"workspace": params.Workspace.Name,
-	})
+	providerInfo, err := providers.ReadInfo(conn.Provider(), nil)
 	if err != nil {
 		return nil, err
 	}
 
-	conn = &Connector{
-		Client:  params.client,
-		BaseURL: providerInfo.BaseURL,
-	}
-
+	// connector and its client must mirror base url and provide its own error parser
 	conn.setBaseURL(providerInfo.BaseURL)
-	conn.Client.HTTPClient.ErrorHandler = conn.interpretError
+	conn.Client.HTTPClient.ErrorHandler = interpreter.ErrorHandler{
+		JSON: conn.interpretJSONError,
+	}.Handle
 
 	return conn, nil
 }
 
+func (c *Connector) getURL(arg string) (*urlbuilder.URL, error) {
+	parts := []string{c.BaseURL, ApiVersion, arg}
+	filtered := make([]string, 0)
+
+	for _, part := range parts {
+		if len(part) != 0 {
+			filtered = append(filtered, part)
+		}
+	}
+
+	return constructURL(strings.Join(filtered, "/"))
+}
+
 func (c *Connector) setBaseURL(newURL string) {
 	c.BaseURL = newURL
 	c.Client.HTTPClient.Base = newURL
 }
-
-func (c *Connector) interpretError(res *http.Response, body []byte) error {
-	return common.InterpretError(res, body)
-}
diff --git a/gong/errors.go b/gong/errors.go
index e3039438..31e1ce89 100644
--- a/gong/errors.go
+++ b/gong/errors.go
@@ -1,15 +1,32 @@
 package gong
 
 import (
-	"errors"
-)
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
 
-var (
-	ErrMissingClient = errors.New("JSON http client not set")
-	ErrNotArray      = errors.New("results data is not an array")
-	ErrNotObject     = errors.New("record is not an object")
+	"github.com/amp-labs/connectors/common/interpreter"
 )
 
-func (c *Connector) HandleError(err error) error {
-	return err
+func (*Connector) interpretJSONError(res *http.Response, body []byte) error { //nolint:cyclop
+	var payload ResponseError
+	if err := json.Unmarshal(body, &payload); err != nil {
+		return fmt.Errorf("interpretJSONError general: %w %w", interpreter.ErrUnmarshal, err)
+	}
+
+	return payload.CombineErr(interpreter.DefaultStatusCodeMappingToErr(res, body))
+}
+
+type ResponseError struct {
+	RequestId string   `json:"requestId"`
+	Errors    []string `json:"errors"`
+}
+
+func (r ResponseError) CombineErr(base error) error {
+	if len(r.Errors) == 0 {
+		return base
+	}
+
+	return fmt.Errorf("%w: %v", base, strings.Join(r.Errors, ","))
 }
diff --git a/gong/params.go b/gong/params.go
index 447aad43..9c340888 100644
--- a/gong/params.go
+++ b/gong/params.go
@@ -10,16 +10,15 @@ import (
 	"golang.org/x/oauth2"
 )
 
-type gongParams struct {
-	client *common.JSONHTTPClient
-	paramsbuilder.Workspace
-	paramsbuilder.APIModule
-	substitutions map[string]string
-}
+// Option is a function which mutates the connector configuration.
+type Option func(params *parameters)
 
-type Option func(params *gongParams)
+type parameters struct {
+	paramsbuilder.Client
+}
 
-func (params *gongParams) FromOptions(opts ...Option) (*gongParams, error) {
+func (p parameters) FromOptions(opts ...Option) (*parameters, error) {
+	params := &p
 	for _, opt := range opts {
 		opt(params)
 	}
@@ -27,49 +26,22 @@ func (params *gongParams) FromOptions(opts ...Option) (*gongParams, error) {
 	return params, params.ValidateParams()
 }
 
-func (params *gongParams) ValidateParams() error {
+func (p parameters) ValidateParams() error {
 	return errors.Join(
-		params.Workspace.ValidateParams(),
+		p.Client.ValidateParams(),
 	)
 }
 
-func WithClient(ctx context.Context, client *http.Client, config *oauth2.Config, token *oauth2.Token,
+func WithClient(ctx context.Context, client *http.Client,
+	config *oauth2.Config, token *oauth2.Token, opts ...common.OAuthOption,
 ) Option {
-	return func(params *gongParams) {
-		oauthclient, err := common.NewOAuthHTTPClient(
-			ctx, common.WithOAuthClient(client),
-			common.WithOAuthConfig(config),
-			common.WithOAuthToken(token),
-		)
-		if err != nil {
-			panic(err)
-		}
-
-		WithAuthenticatedClient(oauthclient)(params)
-	}
-}
-
-func WithModule(module paramsbuilder.APIModule) Option {
-	return func(params *gongParams) {
-		params.APIModule = module
+	return func(params *parameters) {
+		params.WithClient(ctx, client, config, token, opts...)
 	}
 }
 
 func WithAuthenticatedClient(client common.AuthenticatedHTTPClient) Option {
-	return func(params *gongParams) {
-		params.client = &common.JSONHTTPClient{
-			HTTPClient: &common.HTTPClient{
-				Client:       client,
-				ErrorHandler: common.InterpretError,
-			},
-		}
+	return func(params *parameters) {
+		params.WithAuthenticatedClient(client)
 	}
 }
-
-func (params *gongParams) prepare() (*gongParams, error) {
-	if params.client == nil {
-		return nil, ErrMissingClient
-	}
-
-	return params, nil
-}
diff --git a/gong/parse.go b/gong/parse.go
index 49635348..367ec97b 100644
--- a/gong/parse.go
+++ b/gong/parse.go
@@ -2,83 +2,31 @@ package gong
 
 import (
 	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
-// getNextRecords returns the token or empty string if there are no more records.
-func getNextRecordsURL(node *ajson.Node) (string, error) {
-	recordsNode, err := node.GetKey("records")
-	if err != nil {
-		return "", err
-	}
-
-	if !recordsNode.HasKey("cursor") {
-		return "", nil
+func makeGetTotalSize(objectName string) common.ListSizeFunc {
+	return func(node *ajson.Node) (int64, error) {
+		return jsonquery.New(node).ArraySize(objectName)
 	}
-
-	cursorNode, err := recordsNode.GetKey("cursor")
-	if err != nil {
-		return "", err
-	}
-
-	nextPage := cursorNode.MustString()
-
-	return nextPage, nil
 }
 
-// getRecords returns the records from the response.
-func getRecords(node *ajson.Node, objectName string) ([]map[string]interface{}, error) {
-	records, err := node.GetKey(objectName)
-	if err != nil {
-		return nil, ErrNotArray
-	}
-
-	if !records.IsArray() {
-		return nil, ErrNotArray
-	}
-
-	arr := records.MustArray()
-	out := make([]map[string]interface{}, 0, len(arr))
-
-	for _, v := range arr {
-		if !v.IsObject() {
-			return nil, ErrNotObject
-		}
-
-		data, err := v.Unpack()
+func makeGetRecords(objectName string) common.RecordsFunc {
+	return func(node *ajson.Node) ([]map[string]any, error) {
+		// items are stored in array named after the API object
+		arr, err := jsonquery.New(node).Array(objectName, false)
 		if err != nil {
 			return nil, err
 		}
 
-		m, ok := data.(map[string]interface{})
-		if !ok {
-			return nil, ErrNotObject
-		}
-
-		out = append(out, m)
+		return jsonquery.Convertor.ArrayToMap(arr)
 	}
-
-	return out, nil
 }
 
-// getTotalSize returns the total number of records that match the query.
-func getTotalSize(node *ajson.Node) (int64, error) {
-	recordsNode, err := node.GetKey("records")
-	if err != nil {
-		return 0, common.ErrParseError
-	}
-
-	totalRecordsNode, err := recordsNode.GetKey("currentPageSize")
-	if err != nil {
-		return 0, common.ErrParseError
-	}
-
-	totalRecords, err := totalRecordsNode.GetNumeric()
-	if err != nil {
-		return 0, err
-	}
-
-	return int64(totalRecords), nil
+// getNextRecords returns the token or empty string if there are no more records.
+func getNextRecordsURL(node *ajson.Node) (string, error) {
+	return jsonquery.New(node, "records").StrWithDefault("cursor", "")
 }
 
 // getMarshaledData accepts a list of records and returns a list of structured data ([]ReadResultRow).
diff --git a/gong/read.go b/gong/read.go
index 0473439c..d3e532ec 100644
--- a/gong/read.go
+++ b/gong/read.go
@@ -2,43 +2,30 @@ package gong
 
 import (
 	"context"
-	"net/url"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/spyzhov/ajson"
 )
 
 func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common.ReadResult, error) {
-	var (
-		res *common.JSONHTTPResponse
-
-		fields []string
-	)
-
-	fullURL, err := url.JoinPath(c.BaseURL, ApiVersion, config.ObjectName)
+	url, err := c.getURL(config.ObjectName)
 	if err != nil {
 		return nil, err
 	}
 
 	if len(config.NextPage) != 0 { // not the first page, add a cursor
-		fullURL = fullURL + "?cursor=" + config.NextPage.String()
-	}
-
-	if config.Fields != nil {
-		fields = config.Fields
+		url.WithQueryParam("cursor", config.NextPage.String())
 	}
 
-	res, err = c.Client.Get(ctx, fullURL)
+	res, err := c.Client.Get(ctx, url.String())
 	if err != nil {
 		return nil, err
 	}
 
-	return common.ParseResult(res, getTotalSize,
-		func(node *ajson.Node) ([]map[string]interface{}, error) {
-			return getRecords(node, config.ObjectName)
-		},
+	return common.ParseResult(res,
+		makeGetTotalSize(config.ObjectName),
+		makeGetRecords(config.ObjectName),
 		getNextRecordsURL,
 		getMarshaledData,
-		fields,
+		config.Fields,
 	)
 }
diff --git a/gong/url.go b/gong/url.go
new file mode 100644
index 00000000..c1923d93
--- /dev/null
+++ b/gong/url.go
@@ -0,0 +1,18 @@
+package gong
+
+import "github.com/amp-labs/connectors/common/urlbuilder"
+
+var queryEncodingExceptions = map[string]string{ //nolint:gochecknoglobals
+	// none
+}
+
+func constructURL(base string) (*urlbuilder.URL, error) {
+	link, err := urlbuilder.New(base)
+	if err != nil {
+		return nil, err
+	}
+
+	link.AddEncodingExceptions(queryEncodingExceptions)
+
+	return link, nil
+}
diff --git a/providers/Makefile b/providers/Makefile
index 341fbda6..d2996543 100644
--- a/providers/Makefile
+++ b/providers/Makefile
@@ -1,5 +1,8 @@
 # Generate Go types from OpenAPI spec.
 
-.PHONY: gen
-gen:
-	oapi-codegen --config=oapi.config.yaml --o=types.gen.go types.yaml
+CATALOG_YAML = https://raw.githubusercontent.com/amp-labs/openapi/main/catalog/catalog.yaml
+
+
+.PHONY: gen/catalog
+gen/catalog:
+	oapi-codegen --config=oapi.config.yaml --o=types.gen.go $(CATALOG_YAML)
diff --git a/providers/domo.go b/providers/domo.go
index ac55a2d6..a338c33c 100644
--- a/providers/domo.go
+++ b/providers/domo.go
@@ -10,7 +10,7 @@ func init() {
 		Oauth2Opts: &Oauth2Opts{
 			GrantType:                 ClientCredentials,
 			TokenURL:                  "https://api.domo.com/oauth/token",
-			ExplicitScopesRequired:    false,
+			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
 			TokenMetadataFields: TokenMetadataFields{
 				ScopesField:      "scope",
diff --git a/providers/mailgun.go b/providers/mailgun.go
new file mode 100644
index 00000000..68a336b0
--- /dev/null
+++ b/providers/mailgun.go
@@ -0,0 +1,23 @@
+package providers
+
+const Mailgun Provider = "mailgun"
+
+func init() {
+	SetInfo(Mailgun, ProviderInfo{
+		AuthType: Basic,
+		BaseURL:  "https://api.mailgun.net",
+		Support: Support{
+			BulkWrite: BulkWriteSupport{
+				Insert: false,
+				Update: false,
+				Upsert: false,
+				Delete: false,
+			},
+			Proxy:     false,
+			Read:      false,
+			Subscribe: false,
+			Write:     false,
+		},
+		PostAuthInfoNeeded: false,
+	})
+}
diff --git a/providers/recurly.go b/providers/recurly.go
new file mode 100644
index 00000000..da232c9d
--- /dev/null
+++ b/providers/recurly.go
@@ -0,0 +1,23 @@
+package providers
+
+const Recurly Provider = "recurly"
+
+func init() {
+	SetInfo(Recurly, ProviderInfo{
+		AuthType: Basic,
+		BaseURL:  "https://v3.recurly.com",
+		Support: Support{
+			BulkWrite: BulkWriteSupport{
+				Insert: false,
+				Update: false,
+				Upsert: false,
+				Delete: false,
+			},
+			Proxy:     false,
+			Read:      false,
+			Subscribe: false,
+			Write:     false,
+		},
+		PostAuthInfoNeeded: false,
+	})
+}
diff --git a/providers/types.gen.go b/providers/types.gen.go
index 40ce49b2..ee968a67 100644
--- a/providers/types.gen.go
+++ b/providers/types.gen.go
@@ -1,6 +1,6 @@
 // Package providers provides primitives to interact with the openapi HTTP API.
 //
-// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT.
+// Code generated by github.com/deepmap/oapi-codegen version v1.16.3 DO NOT EDIT.
 package providers
 
 // Defines values for ApiKeyOptsType.
@@ -22,6 +22,7 @@ const (
 	AuthorizationCode Oauth2OptsGrantType = "authorizationCode"
 	ClientCredentials Oauth2OptsGrantType = "clientCredentials"
 	PKCE              Oauth2OptsGrantType = "PKCE"
+	Password          Oauth2OptsGrantType = "password"
 )
 
 // ApiKeyOpts defines model for ApiKeyOpts.
diff --git a/providers/types.yaml b/providers/types.yaml
deleted file mode 100644
index ab1468f1..00000000
--- a/providers/types.yaml
+++ /dev/null
@@ -1,190 +0,0 @@
-# ==================================
-# DO NOT EDIT THIS FILE unless you are sure of the dependencies as this
-# schema is being referenced in other specs.
-# ==================================
-
-# This file contains the schema for the provider catalog and helps generate
-# the Go types for the schema. The schema is used to create a provider catalog.
-
-openapi: 3.0.0
-
-info:
-  version: 1.0.0
-  title: Provider catalog
-
-components:
-  schemas:
-    AuthType:
-      type: string
-      enum: [oauth2, api_key, basic, none]
-      x-oapi-codegen-extra-tags:
-        validate: required
-
-    Support:
-      type: object
-      x-oapi-codegen-extra-tags:
-        validate: required
-      required:
-        - bulkWrite
-        - proxy
-        - read
-        - subscribe
-        - write
-      properties:
-        bulkWrite:
-          $ref: '#/components/schemas/BulkWriteSupport'
-        proxy:
-          type: boolean
-        read:
-          type: boolean
-        subscribe:
-          type: boolean
-        write:
-          type: boolean
-    BulkWriteSupport:
-      type: object
-      x-oapi-codegen-extra-tags:
-        validate: required
-      required:
-        - insert
-        - update
-        - upsert
-        - delete
-      properties:
-        insert:
-          type: boolean
-        update:
-          type: boolean
-        upsert:
-          type: boolean
-        delete:
-          type: boolean
-    TokenMetadataFields:
-      type: object
-      properties:
-        workspaceRefField:
-          type: string
-          example: account-id
-          x-go-type-skip-optional-pointer: true
-        consumerRefField:
-          type: string
-          example: user-id
-          x-go-type-skip-optional-pointer: true
-        scopesField:
-          type: string
-          example: scopes
-          x-go-type-skip-optional-pointer: true
-
-    Oauth2Opts:
-      type: object
-      required:
-          - grantType
-          - tokenURL
-          - explicitScopesRequired
-          - explicitWorkspaceRequired
-          - tokenMetadataFields
-      properties:
-        grantType:
-          type: string
-          enum: [authorizationCode, clientCredentials, PKCE]
-        authURL:
-          type: string
-          example: https://login.salesforce.com/services/oauth2/authorize
-          x-go-type-skip-optional-pointer: true
-        tokenURL:
-          type: string
-          example: https://login.salesforce.com/services/oauth2/token
-          x-oapi-codegen-extra-tags:
-            validate: required
-        explicitScopesRequired:
-          type: boolean
-          example: true
-        explicitWorkspaceRequired:
-          type: boolean
-          example: true
-        audience:
-          type: string
-          example: https://api.mparticle.com
-          x-go-type-skip-optional-pointer: true
-        tokenMetadataFields:
-          $ref: '#/components/schemas/TokenMetadataFields'
-
-    ApiKeyOpts:
-      type: object
-      required:
-        - type
-      properties:
-        type:
-          type: string
-          enum: [in-query, in-header]
-          x-oapi-codegen-extra-tags:
-            validate: required
-        queryParamName:
-          type: string
-          example: api_key
-          x-go-type-skip-optional-pointer: true
-        headerName:
-          type: string
-          example: X-Api-Key
-          x-go-type-skip-optional-pointer: true
-        valuePrefix:
-          type: string
-          example: "Bearer "
-          x-go-type-skip-optional-pointer: true
-        docsURL:
-          type: string
-          description: URL with more information about how to get or use an API key.
-          example: https://docs.example.com/api-key
-          x-go-type-skip-optional-pointer: true
-
-    ProviderOpts:
-        type: object
-        additionalProperties:
-          type: string
-
-    Provider:
-      type: string
-      example: salesforce
-
-    ProviderInfo:
-      type: object
-      required:
-        - name
-        - baseURL
-        - authType
-        - support
-        - providerOpts
-      properties:
-        name:
-          # If you're maintaining catalog.go, the name field will be auto-set for you,
-          # so you don't need to worry about it. Add it for completeness if you want.
-          type: string
-        authType:
-          $ref: '#/components/schemas/AuthType'
-        baseURL:
-          type: string
-          x-oapi-codegen-extra-tags:
-            validate: required
-        oauth2Opts:
-          $ref: '#/components/schemas/Oauth2Opts'
-        apiKeyOpts:
-          $ref: '#/components/schemas/ApiKeyOpts'
-        support:
-          $ref: '#/components/schemas/Support'
-        providerOpts:
-          $ref: '#/components/schemas/ProviderOpts'
-        displayName:
-          type: string
-          example: Zendesk Chat
-          description: The display name of the provider, if omitted, defaults to provider name.
-          x-go-type-skip-optional-pointer: true
-        postAuthInfoNeeded:
-          type: boolean
-          example: true
-          description: If true, we require additional information after auth to start making requests.
-          x-go-type-skip-optional-pointer: true
-
-    CatalogType:
-      type: object
-      additionalProperties:
-        $ref: '#/components/schemas/ProviderInfo'
diff --git a/providers/utils.go b/providers/utils.go
index fcf161b4..b228a0f1 100644
--- a/providers/utils.go
+++ b/providers/utils.go
@@ -263,6 +263,8 @@ func (i *ProviderInfo) NewClient(ctx context.Context, params *NewClientParams) (
 			return createOAuth2AuthCodeHTTPClient(ctx, params.Client, params.Debug, params.OAuth2AuthCodeCreds)
 		case ClientCredentials:
 			return createOAuth2ClientCredentialsHTTPClient(ctx, params.Client, params.Debug, params.OAuth2ClientCreds)
+		case Password:
+			return createOAuth2PasswordHTTPClient(ctx, params.Client, params.Debug, params.OAuth2AuthCodeCreds)
 		case PKCE:
 			return nil, fmt.Errorf("%w: %s", ErrClient, "PKCE grant type not supported")
 		default:
@@ -387,6 +389,17 @@ func createOAuth2ClientCredentialsHTTPClient( //nolint:ireturn
 	return oauthClient, nil
 }
 
+func createOAuth2PasswordHTTPClient(
+	ctx context.Context,
+	client *http.Client,
+	dbg bool,
+	cfg *OAuth2AuthCodeParams,
+) (common.AuthenticatedHTTPClient, error) {
+	// Refresh method works the same as with auth code method.
+	// Relies on access and refresh tokens created by Oauth2 password method.
+	return createOAuth2AuthCodeHTTPClient(ctx, client, dbg, cfg)
+}
+
 func createApiKeyHTTPClient( //nolint:ireturn
 	ctx context.Context,
 	client *http.Client,
diff --git a/scripts/oauth/token.go b/scripts/oauth/token.go
index a32ce078..bb210629 100644
--- a/scripts/oauth/token.go
+++ b/scripts/oauth/token.go
@@ -95,6 +95,16 @@ var readers = []utils.Reader{
 		JSONPath: "$['state']",
 		CredKey:  "State",
 	},
+	&utils.JSONReader{
+		FilePath: DefaultCredsFile,
+		JSONPath: "$['userName']",
+		CredKey:  "UserName",
+	},
+	&utils.JSONReader{
+		FilePath: DefaultCredsFile,
+		JSONPath: "$['password']",
+		CredKey:  "Password",
+	},
 }
 
 // OAuthApp is a simple OAuth app that can be used to get an OAuth token.
@@ -109,6 +119,7 @@ type OAuthApp struct {
 	Proto             string
 	SSLCert           string
 	SSLKey            string
+	PasswordParams    *providers.BasicParams
 }
 
 // ServeHTTP implements the http.Handler interface.
@@ -210,6 +221,18 @@ func (a *OAuthApp) Run() error {
 		fmt.Println("Expiry: " + tok.Expiry.String())
 		fmt.Println("Authorization: " + header)
 
+		return nil
+	} else if a.GrantType == providers.Password {
+		tok, err := a.Config.PasswordCredentialsToken(context.Background(), a.PasswordParams.User, a.PasswordParams.Pass)
+		if err != nil {
+			return err
+		}
+
+		header := tok.Type() + " " + tok.AccessToken
+		fmt.Println("Expiry: " + tok.Expiry.String())
+		fmt.Println("Authorization: " + header)
+		fmt.Println("Refresh Token: " + tok.RefreshToken)
+
 		return nil
 	} else {
 		slog.Info("starting OAuth app", "port", a.Port)
@@ -268,6 +291,10 @@ func setup() *OAuthApp {
 	callback := flag.String("callback", DefaultCallbackPath, "the full OAuth callback path (arbitrary)")
 	flag.Parse()
 
+	if err := registry.AddReaders(readers...); err != nil {
+		return nil
+	}
+
 	substitutions, err := registry.GetMap("Substitutions")
 	if err != nil {
 		slog.Warn("no substitutions, ensure that the provider info doesn't have any {{variables}}")
@@ -279,10 +306,6 @@ func setup() *OAuthApp {
 		substitutionsMap[key] = val.MustString()
 	}
 
-	if err = registry.AddReaders(readers...); err != nil {
-		return nil
-	}
-
 	provider := registry.MustString("Provider")
 
 	providerInfo, err := providers.ReadInfo(provider, &substitutionsMap)
@@ -384,6 +407,40 @@ func setup() *OAuthApp {
 			app.ClientCredsConfig.EndpointParams = url.Values{"audience": {aud}}
 		}
 
+		return app
+	case providers.Password:
+		state, err := registry.GetString("State")
+		if err != nil {
+			slog.Warn("no state attached, ensure that the provider doesn't require state")
+		}
+
+		username := registry.MustString("UserName")
+		password := registry.MustString("Password")
+
+		app := &OAuthApp{
+			GrantType: providers.Password,
+			Config: &oauth2.Config{
+				ClientID:     clientId,
+				ClientSecret: clientSecret,
+				Scopes:       oauthScopes,
+				Endpoint: oauth2.Endpoint{
+					TokenURL: providerInfo.Oauth2Opts.TokenURL,
+				},
+			},
+			PasswordParams: &providers.BasicParams{
+				User: username,
+				Pass: password,
+			},
+		}
+		if state != "" {
+			app.State = state
+		}
+
+		if providerInfo.Oauth2Opts.Audience != "" {
+			aud := providerInfo.Oauth2Opts.Audience
+			app.ClientCredsConfig.EndpointParams = url.Values{"audience": {aud}}
+		}
+
 		return app
 	default:
 		slog.Error("provider does not support authorization code or client credentials grant, not compatible with this script", "provider", provider)
diff --git a/scripts/proxy/proxy.go b/scripts/proxy/proxy.go
index b23f888d..de757712 100644
--- a/scripts/proxy/proxy.go
+++ b/scripts/proxy/proxy.go
@@ -167,6 +167,10 @@ func main() {
 			mainOAuth2ClientCreds(ctx, provider, substitutionsMap)
 		case providers.AuthorizationCode:
 			mainOAuth2AuthCode(ctx, provider, substitutionsMap)
+		case providers.Password:
+			// de facto, password grant acts as client credentials,
+			// even so access and refresh tokens were acquired differently.
+			mainOAuth2ClientCreds(ctx, provider, substitutionsMap)
 		default:
 			log.Fatalf("Unsupported OAuth2 grant type: %s", info.Oauth2Opts.GrantType)
 		}
@@ -179,48 +183,39 @@ func main() {
 	}
 }
 
-func mainOAuth2ClientCreds(ctx context.Context, provider string, substitutionsMap map[string]string) {
-	clientId := registry.MustString("ClientId")
-	clientSecret := registry.MustString("ClientSecret")
-
-	scopes, err := registry.GetString("Scopes")
-	if err != nil {
-		slog.Warn("no scopes attached, ensure that the provider doesn't require scopes")
-	}
-
-	oauthScopes := strings.Split(scopes, ",")
-
-	validateRequiredOAuth2Flags(provider, clientId, clientSecret)
-	startOAuthClientCredsProxy(ctx, provider, oauthScopes, clientId, clientSecret, substitutionsMap, DefaultPort)
+func mainOAuth2ClientCreds(ctx context.Context, provider string, substitutions map[string]string) {
+	params := createClientAuthParams(provider)
+	tokens := getTokensFromRegistry()
+	proxy := buildOAuth2AuthCodeProxy(ctx, provider, params.Scopes, params.ID, params.Secret, substitutions, tokens)
+	startProxy(ctx, proxy, DefaultPort)
 }
 
-func mainOAuth2AuthCode(ctx context.Context, provider string, substitutionsMap map[string]string) {
-	clientId := registry.MustString("ClientId")
-	clientSecret := registry.MustString("ClientSecret")
+func mainOAuth2AuthCode(ctx context.Context, provider string, substitutions map[string]string) {
+	params := createClientAuthParams(provider)
 	tokens := getTokensFromRegistry()
-
-	scopes, err := registry.GetString("Scopes")
-	if err != nil {
-		slog.Warn("no scopes attached, ensure that the provider doesn't require scopes")
-	}
-
-	oauthScopes := strings.Split(scopes, ",")
-
-	validateRequiredOAuth2Flags(provider, clientId, clientSecret)
-	startOAuthAuthCodeProxy(ctx, provider, oauthScopes, clientId, clientSecret, substitutionsMap, DefaultPort, tokens)
+	proxy := buildOAuth2AuthCodeProxy(ctx, provider, params.Scopes, params.ID, params.Secret, substitutions, tokens)
+	startProxy(ctx, proxy, DefaultPort)
 }
 
-func mainApiKey(ctx context.Context, provider string, substitutionsMap map[string]string) {
+func mainApiKey(ctx context.Context, provider string, substitutions map[string]string) {
 	apiKey := registry.MustString("ApiKey")
 	if apiKey == "" {
 		_, _ = fmt.Fprintln(os.Stderr, "api key from registry is empty")
 		os.Exit(1)
 	}
 
-	startApiKeyProxy(ctx, provider, apiKey, substitutionsMap, DefaultPort)
+	proxy := buildApiKeyProxy(ctx, provider, substitutions, apiKey)
+	startProxy(ctx, proxy, DefaultPort)
 }
 
-func mainBasic(ctx context.Context, provider string, substitutionsMap map[string]string) {
+func mainBasic(ctx context.Context, provider string, substitutions map[string]string) {
+	params := createBasicParams()
+
+	proxy := buildBasicAuthProxy(ctx, provider, substitutions, params.User, params.Pass)
+	startProxy(ctx, proxy, DefaultPort)
+}
+
+func createBasicParams() *providers.BasicParams {
 	user := registry.MustString("UserName")
 	pass := registry.MustString("Password")
 
@@ -236,7 +231,28 @@ func mainBasic(ctx context.Context, provider string, substitutionsMap map[string
 		slog.Warn("no password for basic authentication, ensure that it is not required")
 	}
 
-	startBasicAuthProxy(ctx, provider, user, pass, substitutionsMap, DefaultPort)
+	return &providers.BasicParams{
+		User: user,
+		Pass: pass,
+	}
+}
+
+func createClientAuthParams(provider string) *ClientAuthParams {
+	clientId := registry.MustString("ClientId")
+	clientSecret := registry.MustString("ClientSecret")
+
+	scopes, err := registry.GetString("Scopes")
+	if err != nil {
+		slog.Warn("no scopes attached, ensure that the provider doesn't require scopes")
+	}
+
+	validateRequiredOAuth2Flags(provider, clientId, clientSecret)
+
+	return &ClientAuthParams{
+		ID:     clientId,
+		Secret: clientSecret,
+		Scopes: strings.Split(scopes, ","),
+	}
 }
 
 // Some connectors may implement Refresh tokens, when it happens expiry must be provided alongside.
@@ -338,41 +354,7 @@ func listen(ctx context.Context, port int) error {
 	return nil
 }
 
-func startOAuthClientCredsProxy(ctx context.Context, provider string, scopes []string, clientId, clientSecret string, substitutions map[string]string, port int) {
-	proxy := buildOAuth2ClientCredentialsProxy(ctx, provider, scopes, clientId, clientSecret, substitutions)
-	http.Handle("/", proxy)
-
-	fmt.Printf("\nProxy server listening on :%d\n", port)
-
-	if err := listen(ctx, port); err != nil {
-		panic(err)
-	}
-}
-
-func startApiKeyProxy(ctx context.Context, provider string, apiKey string, substitutions map[string]string, port int) {
-	proxy := buildApiKeyProxy(ctx, provider, substitutions, apiKey)
-	http.Handle("/", proxy)
-
-	fmt.Printf("\nProxy server listening on :%d\n", port)
-
-	if err := listen(ctx, port); err != nil {
-		panic(err)
-	}
-}
-
-func startBasicAuthProxy(ctx context.Context, provider string, user, pass string, substitutions map[string]string, port int) {
-	proxy := buildBasicAuthProxy(ctx, provider, substitutions, user, pass)
-	http.Handle("/", proxy)
-
-	fmt.Printf("\nProxy server listening on :%d\n", port)
-
-	if err := listen(ctx, port); err != nil {
-		panic(err)
-	}
-}
-
-func startOAuthAuthCodeProxy(ctx context.Context, provider string, scopes []string, clientId, clientSecret string, substitutions map[string]string, port int, tokens *oauth2.Token) {
-	proxy := buildOAuth2AuthCodeProxy(ctx, provider, scopes, clientId, clientSecret, substitutions, tokens)
+func startProxy(ctx context.Context, proxy *Proxy, port int) {
 	http.Handle("/", proxy)
 
 	fmt.Printf("\nProxy server listening on :%d\n", port)
@@ -460,6 +442,12 @@ func configureOAuthClientCredentials(clientId, clientSecret string, scopes []str
 	return cfg
 }
 
+type ClientAuthParams struct {
+	ID     string
+	Secret string
+	Scopes []string
+}
+
 func configureOAuthAuthCode(clientId, clientSecret string, scopes []string, providerInfo *providers.ProviderInfo) *oauth2.Config {
 	return &oauth2.Config{
 		ClientID:     clientId,
diff --git a/tools/connectorgen/template/base/connector.go.tmpl b/tools/connectorgen/template/base/connector.go.tmpl
index 0a1a2651..a4e1546f 100644
--- a/tools/connectorgen/template/base/connector.go.tmpl
+++ b/tools/connectorgen/template/base/connector.go.tmpl
@@ -6,7 +6,6 @@ import (
 
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/common/interpreter"
-	"github.com/amp-labs/connectors/common/paramsbuilder"
 	"github.com/amp-labs/connectors/common/urlbuilder"
 	"github.com/amp-labs/connectors/providers"
 )


Test patch:
diff --git a/gong/read_test.go b/gong/read_test.go
index e27fad94..2b4b6145 100644
--- a/gong/read_test.go
+++ b/gong/read_test.go
@@ -10,6 +10,8 @@ import (
 	"testing"
 
 	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/interpreter"
+	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/test/utils/mockutils"
 	"github.com/go-test/deep"
 )
@@ -29,6 +31,24 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		expectedErrs     []error
 		expectedErrTypes []error
 	}{
+		{
+			name: "Mime response header expected",
+			server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.WriteHeader(http.StatusTeapot)
+			})),
+			expectedErrs: []error{interpreter.ErrMissingContentType},
+		},
+		{
+			name: "Incorrect key in payload",
+			server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.Header().Set("Content-Type", "application/json")
+				w.WriteHeader(http.StatusOK)
+				mockutils.WriteBody(w, `{
+					"garbage": {}
+				}`)
+			})),
+			expectedErrs: []error{jsonquery.ErrKeyNotFound},
+		},
 		{
 			name:  "Bad request handling test",
 			input: common.ReadParams{ObjectName: "calls"},
@@ -43,10 +63,9 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 				}`)
 			})),
 			expectedErrs: []error{
-				errors.New("HTTP status 400: caller error:"), // nolint:goerr113
+				common.ErrBadRequest, errors.New("Failed to verify cursor"), // nolint:goerr113
 			},
 		},
-
 		{
 			name:  "Records section is missing in the payload",
 			input: common.ReadParams{ObjectName: "calls"},
@@ -57,12 +76,11 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 					"value": []
 				}`)
 			})),
-
-			expectedErrs: []error{common.ErrParseError},
+			expectedErrs: []error{jsonquery.ErrKeyNotFound},
 		},
 
 		{
-			name:  "currentPageSize parameter is missing in the payload",
+			name:  "currentPageSize may be missing in payload",
 			input: common.ReadParams{ObjectName: "calls"},
 			server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
@@ -78,8 +96,11 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 			}		
 					`)
 			})),
-
-			expectedErrs: []error{common.ErrParseError},
+			expected: &common.ReadResult{
+				Data: []common.ReadResultRow{},
+				Done: true,
+			},
+			expectedErrs: nil,
 		},
 
 		{
@@ -150,7 +171,6 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 			},
 			expectedErrs: nil,
 		},
-
 		{
 			name:  "Incorrect data type in payload",
 			input: common.ReadParams{ObjectName: "calls"},
@@ -158,10 +178,10 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 				w.Header().Set("Content-Type", "application/json")
 				w.WriteHeader(http.StatusOK)
 				writeBody(w, `{
-					"values": {}
+					"calls": {}
 				}`)
 			})),
-			expectedErrs: []error{common.ErrParseError},
+			expectedErrs: []error{jsonquery.ErrNotArray},
 		},
 	}
 
diff --git a/test/gong/connector.go b/test/gong/connector.go
new file mode 100644
index 00000000..fdf19a83
--- /dev/null
+++ b/test/gong/connector.go
@@ -0,0 +1,55 @@
+package gong
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/amp-labs/connectors/gong"
+	testUtils "github.com/amp-labs/connectors/test/utils"
+	"github.com/amp-labs/connectors/utils"
+)
+
+func GetGongConnector(ctx context.Context, filePath string) *gong.Connector {
+	registry := utils.NewCredentialsRegistry()
+
+	readers := []utils.Reader{
+		&utils.JSONReader{
+			FilePath: filePath,
+			JSONPath: "$.clientId",
+			CredKey:  utils.ClientId,
+		},
+		&utils.JSONReader{
+			FilePath: filePath,
+			JSONPath: "$.clientSecret",
+			CredKey:  utils.ClientSecret,
+		},
+		&utils.JSONReader{
+			FilePath: filePath,
+			JSONPath: "$.accessToken",
+			CredKey:  utils.AccessToken,
+		},
+		&utils.JSONReader{
+			FilePath: filePath,
+			JSONPath: "$.refreshToken",
+			CredKey:  utils.RefreshToken,
+		},
+		&utils.JSONReader{
+			FilePath: filePath,
+			JSONPath: "$.provider",
+			CredKey:  utils.Provider,
+		},
+	}
+	_ = registry.AddReaders(readers...)
+
+	cfg := utils.GongOAuthConfigFromRegistry(registry)
+	tok := utils.GongOauthTokenFromRegistry(registry)
+
+	conn, err := gong.NewConnector(
+		gong.WithClient(ctx, http.DefaultClient, cfg, tok),
+	)
+	if err != nil {
+		testUtils.Fail("error creating connector", "error", err)
+	}
+
+	return conn
+}
diff --git a/test/gong/read/read.go b/test/gong/read/read.go
index f7db4417..9faa3fac 100644
--- a/test/gong/read/read.go
+++ b/test/gong/read/read.go
@@ -4,12 +4,10 @@ import (
 	"context"
 	"encoding/json"
 	"log/slog"
-	"net/http"
 	"os"
 
 	"github.com/amp-labs/connectors"
-	"github.com/amp-labs/connectors/gong"
-	"github.com/amp-labs/connectors/utils"
+	connTest "github.com/amp-labs/connectors/test/gong"
 	"github.com/joho/godotenv"
 )
 
@@ -17,55 +15,6 @@ const (
 	DefaultCredsFile = "creds.json"
 )
 
-func GetGongConnector(ctx context.Context, filePath string) *gong.Connector {
-	registry := utils.NewCredentialsRegistry()
-
-	readers := []utils.Reader{
-		&utils.JSONReader{
-			FilePath: filePath,
-			JSONPath: "$['clientId']",
-			CredKey:  "clientId",
-		},
-		&utils.JSONReader{
-			FilePath: filePath,
-			JSONPath: "$['clientSecret']",
-			CredKey:  "clientSecret",
-		},
-		&utils.JSONReader{
-			FilePath: filePath,
-			JSONPath: "$['refreshToken']",
-			CredKey:  "refreshToken",
-		},
-		&utils.JSONReader{
-			FilePath: filePath,
-			JSONPath: "$['accessToken']",
-			CredKey:  "accessToken",
-		},
-		&utils.JSONReader{
-			FilePath: filePath,
-			JSONPath: "$['provider']",
-			CredKey:  "provider",
-		},
-	}
-	registry.AddReaders(readers...)
-
-	cfg := utils.GongOAuthConfigFromRegistry(registry)
-	tok := utils.GongOauthTokenFromRegistry(registry)
-
-	conn, err := gong.NewConnector(
-		gong.WithClient(ctx, http.DefaultClient, cfg, tok),
-	)
-	if err != nil {
-		slog.Error("error creating gong connector", "error", err)
-	}
-
-	defer func() {
-		_ = conn.Close()
-	}()
-
-	return conn
-}
-
 func main() {
 	os.Exit(mainFn())
 }
@@ -82,14 +31,14 @@ func mainFn() int {
 		slog.Warn("No .env file provided")
 	}
 
-	gong := GetGongConnector(context.Background(), DefaultCredsFile)
+	conn := connTest.GetGongConnector(context.Background(), DefaultCredsFile)
 
 	config := connectors.ReadParams{
 		ObjectName: "calls", // could be calls, users
 		Fields:     []string{"url"},
 	}
 
-	result, err := gong.Read(context.Background(), config)
+	result, err := conn.Read(context.Background(), config)
 	if err != nil {
 		slog.Error("Error reading from Gong", "error", err)
 		return 1
diff --git a/tools/connectorgen/template/test/connector.go.tmpl b/tools/connectorgen/template/test/connector.go.tmpl
index ee5e8193..7feac7f5 100644
--- a/tools/connectorgen/template/test/connector.go.tmpl
+++ b/tools/connectorgen/template/test/connector.go.tmpl
@@ -4,7 +4,6 @@ import (
 	"context"
 	"net/http"
 
-	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/{{ .Package }}"
 	testUtils "github.com/amp-labs/connectors/test/utils"
 	"github.com/amp-labs/connectors/utils"
@@ -16,32 +15,32 @@ func Get{{ .Provider }}Connector(ctx context.Context, filePath string) *{{ .Pack
 	readers := []utils.Reader{
 		&utils.JSONReader{
 			FilePath: filePath,
-			JSONPath: "$.CLIENT_ID",
+			JSONPath: "$.clientId",
 			CredKey:  utils.ClientId,
 		},
 		&utils.JSONReader{
 			FilePath: filePath,
-			JSONPath: "$.CLIENT_SECRET",
+			JSONPath: "$.clientSecret",
 			CredKey:  utils.ClientSecret,
 		},
 		&utils.JSONReader{
 			FilePath: filePath,
-			JSONPath: "$.ACCESS_TOKEN",
+			JSONPath: "$.accessToken",
 			CredKey:  utils.AccessToken,
 		},
 		&utils.JSONReader{
 			FilePath: filePath,
-			JSONPath: "$.REFRESH_TOKEN",
+			JSONPath: "$.refreshToken",
 			CredKey:  utils.RefreshToken,
 		},
 		&utils.JSONReader{
 			FilePath: filePath,
-			JSONPath: "$.PROVIDER",
+			JSONPath: "$.provider",
 			CredKey:  utils.Provider,
 		},
 		&utils.JSONReader{
 			FilePath: filePath,
-			JSONPath: "$.WORKSPACE",
+			JSONPath: "$.workspace",
 			CredKey:  utils.WorkspaceRef,
 		},
 	}
@@ -53,7 +52,7 @@ func Get{{ .Provider }}Connector(ctx context.Context, filePath string) *{{ .Pack
 	workspace := registry.MustString(utils.WorkspaceRef)
 
 	// TODO provide required options
-	conn, err := connectors.{{ .Provider }}(
+	conn, err := {{ .Package }}.NewConnector(
 		{{ .Package }}.WithClient(ctx, http.DefaultClient, cfg, tok),
 		{{ .Package }}.WithWorkspace(workspace),
 	)