move config unions to common code (#224)

* move config unions to common code

The configuration union handlers in Storage and Account
overlapped significantly in behavior.  Moving those helpers into
a common code folder was requested.  Although the behavior
was similar across the files, the types were not, requiring
the addition of generics to solve the need.
This commit is contained in:
Keepers 2022-06-23 12:16:20 -06:00 committed by GitHub
parent 2b65ff80f2
commit 0bd23ab2ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 119 additions and 142 deletions

View File

@ -0,0 +1,21 @@
package common
type StringConfigurer interface {
StringConfig() (map[string]string, error)
}
// UnionStringConfigs unions all provided configurers into a single
// map[string]string matching type.
func UnionStringConfigs(cfgs ...StringConfigurer) (map[string]string, error) {
union := map[string]string{}
for _, cfg := range cfgs {
c, err := cfg.StringConfig()
if err != nil {
return nil, err
}
for k, v := range c {
union[k] = v
}
}
return union, nil
}

View File

@ -0,0 +1,72 @@
package common_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/internal/common"
)
type CommonConfigsSuite struct {
suite.Suite
}
func TestCommonConfigsSuite(t *testing.T) {
suite.Run(t, new(CommonConfigsSuite))
}
const (
keyExpect = "expect"
keyExpect2 = "expect2"
)
type stringConfig struct {
expectA string
err error
}
func (c stringConfig) StringConfig() (map[string]string, error) {
return map[string]string{keyExpect: c.expectA}, c.err
}
type stringConfig2 struct {
expectB string
err error
}
func (c stringConfig2) StringConfig() (map[string]string, error) {
return map[string]string{keyExpect2: c.expectB}, c.err
}
func (suite *CommonConfigsSuite) TestUnionConfigs_string() {
table := []struct {
name string
ac stringConfig
bc stringConfig2
errCheck assert.ErrorAssertionFunc
}{
{"no error", stringConfig{keyExpect, nil}, stringConfig2{keyExpect2, nil}, assert.NoError},
{"tc error", stringConfig{keyExpect, assert.AnError}, stringConfig2{keyExpect2, nil}, assert.Error},
{"fc error", stringConfig{keyExpect, nil}, stringConfig2{keyExpect2, assert.AnError}, assert.Error},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
cs, err := common.UnionStringConfigs(test.ac, test.bc)
test.errCheck(t, err)
// remaining tests depend on error-free state
if test.ac.err != nil || test.bc.err != nil {
return
}
assert.Equalf(t,
test.ac.expectA,
cs[keyExpect],
"expected unioned config to have value [%s] at key [%s], got [%s]", test.ac.expectA, keyExpect, cs[keyExpect])
assert.Equalf(t,
test.bc.expectB,
cs[keyExpect2],
"expected unioned config to have value [%s] at key [%s], got [%s]", test.bc.expectB, keyExpect2, cs[keyExpect2])
})
}
}

View File

@ -1,6 +1,10 @@
package account package account
import "errors" import (
"errors"
"github.com/alcionai/corso/internal/common"
)
type accountProvider int type accountProvider int
@ -15,39 +19,18 @@ var (
errMissingRequired = errors.New("missing required storage configuration") errMissingRequired = errors.New("missing required storage configuration")
) )
type (
config map[string]string
configurer interface {
Config() (config, error)
}
)
// Account defines an account provider, along with any credentials // Account defines an account provider, along with any credentials
// and identifiers requried to set up or communicate with that provider. // and identifiers requried to set up or communicate with that provider.
type Account struct { type Account struct {
Provider accountProvider Provider accountProvider
Config config Config map[string]string
} }
// NewAccount aggregates all the supplied configurations into a single configuration // NewAccount aggregates all the supplied configurations into a single configuration
func NewAccount(p accountProvider, cfgs ...configurer) (Account, error) { func NewAccount(p accountProvider, cfgs ...common.StringConfigurer) (Account, error) {
cs, err := unionConfigs(cfgs...) cs, err := common.UnionStringConfigs(cfgs...)
return Account{ return Account{
Provider: p, Provider: p,
Config: cs, Config: cs,
}, err }, err
} }
func unionConfigs(cfgs ...configurer) (config, error) {
union := config{}
for _, cfg := range cfgs {
c, err := cfg.Config()
if err != nil {
return nil, err
}
for k, v := range c {
union[k] = v
}
}
return union, nil
}

View File

@ -12,8 +12,8 @@ type testConfig struct {
err error err error
} }
func (c testConfig) Config() (config, error) { func (c testConfig) StringConfig() (map[string]string, error) {
return config{"expect": c.expect}, c.err return map[string]string{"expect": c.expect}, c.err
} }
type AccountSuite struct { type AccountSuite struct {
@ -55,43 +55,3 @@ func (suite *AccountSuite) TestNewAccount() {
}) })
} }
} }
type fooConfig struct {
foo string
err error
}
func (c fooConfig) Config() (config, error) {
return config{"foo": c.foo}, c.err
}
func (suite *AccountSuite) TestUnionConfigs() {
table := []struct {
name string
tc testConfig
fc fooConfig
errCheck assert.ErrorAssertionFunc
}{
{"no error", testConfig{"test", nil}, fooConfig{"foo", nil}, assert.NoError},
{"tc error", testConfig{"test", assert.AnError}, fooConfig{"foo", nil}, assert.Error},
{"fc error", testConfig{"test", nil}, fooConfig{"foo", assert.AnError}, assert.Error},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
cs, err := unionConfigs(test.tc, test.fc)
test.errCheck(t, err)
// remaining tests depend on error-free state
if test.tc.err != nil || test.fc.err != nil {
return
}
assert.Equalf(t,
test.tc.expect,
cs["expect"],
"expected unioned config to have value [%s] at key [expect], got [%s]", test.tc.expect, cs["expect"])
assert.Equalf(t,
test.fc.foo,
cs["foo"],
"expected unioned config to have value [%s] at key [foo], got [%s]", test.fc.foo, cs["foo"])
})
}
}

View File

@ -24,8 +24,8 @@ const (
// (todo) TenantID = "TENANT_ID" // (todo) TenantID = "TENANT_ID"
) )
func (c M365Config) Config() (config, error) { func (c M365Config) StringConfig() (map[string]string, error) {
cfg := config{ cfg := map[string]string{
keyM365ClientID: c.ClientID, keyM365ClientID: c.ClientID,
keyM365ClientSecret: c.ClientSecret, keyM365ClientSecret: c.ClientSecret,
keyM365TenantID: c.TenantID, keyM365TenantID: c.TenantID,

View File

@ -29,7 +29,7 @@ var goodM365Config = account.M365Config{
func (suite *M365CfgSuite) TestM365Config_Config() { func (suite *M365CfgSuite) TestM365Config_Config() {
m365 := goodM365Config m365 := goodM365Config
c, err := m365.Config() c, err := m365.StringConfig()
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
table := []struct { table := []struct {

View File

@ -15,8 +15,8 @@ const (
keyCommonCorsoPassword = "common_corsoPassword" keyCommonCorsoPassword = "common_corsoPassword"
) )
func (c CommonConfig) Config() (config, error) { func (c CommonConfig) StringConfig() (map[string]string, error) {
cfg := config{ cfg := map[string]string{
keyCommonCorsoPassword: c.CorsoPassword, keyCommonCorsoPassword: c.CorsoPassword,
} }
return cfg, c.validate() return cfg, c.validate()

View File

@ -26,7 +26,7 @@ var goodCommonConfig = storage.CommonConfig{
func (suite *CommonCfgSuite) TestCommonConfig_Config() { func (suite *CommonCfgSuite) TestCommonConfig_Config() {
cfg := goodCommonConfig cfg := goodCommonConfig
c, err := cfg.Config() c, err := cfg.StringConfig()
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
table := []struct { table := []struct {

View File

@ -31,8 +31,8 @@ const (
Prefix = "prefix" Prefix = "prefix"
) )
func (c S3Config) Config() (config, error) { func (c S3Config) StringConfig() (map[string]string, error) {
cfg := config{ cfg := map[string]string{
keyS3AccessKey: c.AccessKey, keyS3AccessKey: c.AccessKey,
keyS3Bucket: c.Bucket, keyS3Bucket: c.Bucket,
keyS3Endpoint: c.Endpoint, keyS3Endpoint: c.Endpoint,

View File

@ -31,7 +31,7 @@ var goodS3Config = storage.S3Config{
func (suite *S3CfgSuite) TestS3Config_Config() { func (suite *S3CfgSuite) TestS3Config_Config() {
s3 := goodS3Config s3 := goodS3Config
c, err := s3.Config() c, err := s3.StringConfig()
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
table := []struct { table := []struct {

View File

@ -3,6 +3,8 @@ package storage
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/alcionai/corso/internal/common"
) )
type storageProvider int type storageProvider int
@ -18,43 +20,22 @@ var (
errMissingRequired = errors.New("missing required storage configuration") errMissingRequired = errors.New("missing required storage configuration")
) )
type (
config map[string]any
configurer interface {
Config() (config, error)
}
)
// Storage defines a storage provider, along with any configuration // Storage defines a storage provider, along with any configuration
// requried to set up or communicate with that provider. // requried to set up or communicate with that provider.
type Storage struct { type Storage struct {
Provider storageProvider Provider storageProvider
Config config Config map[string]string
} }
// NewStorage aggregates all the supplied configurations into a single configuration. // NewStorage aggregates all the supplied configurations into a single configuration.
func NewStorage(p storageProvider, cfgs ...configurer) (Storage, error) { func NewStorage(p storageProvider, cfgs ...common.StringConfigurer) (Storage, error) {
cs, err := unionConfigs(cfgs...) cs, err := common.UnionStringConfigs(cfgs...)
return Storage{ return Storage{
Provider: p, Provider: p,
Config: cs, Config: cs,
}, err }, err
} }
func unionConfigs(cfgs ...configurer) (config, error) {
union := config{}
for _, cfg := range cfgs {
c, err := cfg.Config()
if err != nil {
return nil, err
}
for k, v := range c {
union[k] = v
}
}
return union, nil
}
// Helper for parsing the values in a config object. // Helper for parsing the values in a config object.
// If the value is nil or not a string, returns an empty string. // If the value is nil or not a string, returns an empty string.
func orEmptyString(v any) string { func orEmptyString(v any) string {

View File

@ -12,8 +12,8 @@ type testConfig struct {
err error err error
} }
func (c testConfig) Config() (config, error) { func (c testConfig) StringConfig() (map[string]string, error) {
return config{"expect": c.expect}, c.err return map[string]string{"expect": c.expect}, c.err
} }
type StorageSuite struct { type StorageSuite struct {
@ -55,43 +55,3 @@ func (suite *StorageSuite) TestNewStorage() {
}) })
} }
} }
type fooConfig struct {
foo string
err error
}
func (c fooConfig) Config() (config, error) {
return config{"foo": c.foo}, c.err
}
func (suite *StorageSuite) TestUnionConfigs() {
table := []struct {
name string
tc testConfig
fc fooConfig
errCheck assert.ErrorAssertionFunc
}{
{"no error", testConfig{"test", nil}, fooConfig{"foo", nil}, assert.NoError},
{"tc error", testConfig{"test", assert.AnError}, fooConfig{"foo", nil}, assert.Error},
{"fc error", testConfig{"test", nil}, fooConfig{"foo", assert.AnError}, assert.Error},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
cs, err := unionConfigs(test.tc, test.fc)
test.errCheck(t, err)
// remaining tests depend on error-free state
if test.tc.err != nil || test.fc.err != nil {
return
}
assert.Equalf(t,
test.tc.expect,
cs["expect"],
"expected unioned config to have value [%s] at key [expect], got [%s]", test.tc.expect, cs["expect"])
assert.Equalf(t,
test.fc.foo,
cs["foo"],
"expected unioned config to have value [%s] at key [foo], got [%s]", test.fc.foo, cs["foo"])
})
}
}