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
import "errors"
import (
"errors"
"github.com/alcionai/corso/internal/common"
)
type accountProvider int
@ -15,39 +19,18 @@ var (
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
// and identifiers requried to set up or communicate with that provider.
type Account struct {
Provider accountProvider
Config config
Config map[string]string
}
// NewAccount aggregates all the supplied configurations into a single configuration
func NewAccount(p accountProvider, cfgs ...configurer) (Account, error) {
cs, err := unionConfigs(cfgs...)
func NewAccount(p accountProvider, cfgs ...common.StringConfigurer) (Account, error) {
cs, err := common.UnionStringConfigs(cfgs...)
return Account{
Provider: p,
Config: cs,
}, 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
}
func (c testConfig) Config() (config, error) {
return config{"expect": c.expect}, c.err
func (c testConfig) StringConfig() (map[string]string, error) {
return map[string]string{"expect": c.expect}, c.err
}
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"
)
func (c M365Config) Config() (config, error) {
cfg := config{
func (c M365Config) StringConfig() (map[string]string, error) {
cfg := map[string]string{
keyM365ClientID: c.ClientID,
keyM365ClientSecret: c.ClientSecret,
keyM365TenantID: c.TenantID,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,8 @@ package storage
import (
"errors"
"fmt"
"github.com/alcionai/corso/internal/common"
)
type storageProvider int
@ -18,43 +20,22 @@ var (
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
// requried to set up or communicate with that provider.
type Storage struct {
Provider storageProvider
Config config
Config map[string]string
}
// NewStorage aggregates all the supplied configurations into a single configuration.
func NewStorage(p storageProvider, cfgs ...configurer) (Storage, error) {
cs, err := unionConfigs(cfgs...)
func NewStorage(p storageProvider, cfgs ...common.StringConfigurer) (Storage, error) {
cs, err := common.UnionStringConfigs(cfgs...)
return Storage{
Provider: p,
Config: cs,
}, 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.
// If the value is nil or not a string, returns an empty string.
func orEmptyString(v any) string {

View File

@ -12,8 +12,8 @@ type testConfig struct {
err error
}
func (c testConfig) Config() (config, error) {
return config{"expect": c.expect}, c.err
func (c testConfig) StringConfig() (map[string]string, error) {
return map[string]string{"expect": c.expect}, c.err
}
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"])
})
}
}