diff --git a/atlas/map.go b/atlas/map.go index f64380b10..d61f4f4ae 100644 --- a/atlas/map.go +++ b/atlas/map.go @@ -14,6 +14,7 @@ import ( "github.com/go-spatial/tegola" "github.com/go-spatial/tegola/basic" "github.com/go-spatial/tegola/internal/convert" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/mvt" "github.com/go-spatial/tegola/provider" "github.com/go-spatial/tegola/provider/debug" @@ -60,7 +61,7 @@ func (m Map) AddDebugLayers() Map { m.Layers = layers // setup a debug provider - debugProvider, _ := debug.NewTileProvider(map[string]interface{}{}) + debugProvider, _ := debug.NewTileProvider(dict.Dict{}) m.Layers = append(layers, []Layer{ { diff --git a/cache/cache.go b/cache/cache.go index 569e20514..fc63db9eb 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/go-spatial/tegola" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/maths" ) @@ -125,7 +126,7 @@ func (k Key) String() string { // InitFunc initilize a cache given a config map. // The InitFunc should validate the config map, and report any errors. // This is called by the For function. -type InitFunc func(map[string]interface{}) (Interface, error) +type InitFunc func(dict.Dicter) (Interface, error) var cache map[string]InitFunc @@ -154,7 +155,7 @@ func Registered() (c []string) { } // For function returns a configed cache of the given type, provided the correct config map. -func For(cacheType string, config map[string]interface{}) (Interface, error) { +func For(cacheType string, config dict.Dicter) (Interface, error) { if cache == nil { return nil, fmt.Errorf("No cache backends registered.") } diff --git a/cache/file/file.go b/cache/file/file.go index 6b59691e2..fa1aefa08 100644 --- a/cache/file/file.go +++ b/cache/file/file.go @@ -8,7 +8,7 @@ import ( "github.com/go-spatial/tegola" "github.com/go-spatial/tegola/cache" - "github.com/go-spatial/tegola/util/dict" + "github.com/go-spatial/tegola/internal/dict" ) var ( @@ -31,24 +31,21 @@ func init() { // basepath (string): a path to where the cache will be written // max_zoom (int): max zoom to use the cache. beyond this zoom cache Set() calls will be ignored // -func New(config map[string]interface{}) (cache.Interface, error) { +func New(config dict.Dicter) (cache.Interface, error) { var err error // new filecache fc := Cache{} - // parse the config - c := dict.M(config) - defaultMaxZoom := uint(tegola.MaxZ) - maxZoom, err := c.Uint(ConfigKeyMaxZoom, &defaultMaxZoom) + maxZoom, err := config.Uint(ConfigKeyMaxZoom, &defaultMaxZoom) if err != nil { return nil, err } fc.MaxZoom = maxZoom - fc.Basepath, err = c.String(ConfigKeyBasepath, nil) + fc.Basepath, err = config.String(ConfigKeyBasepath, nil) if err != nil { return nil, ErrMissingBasepath } diff --git a/cache/file/file_test.go b/cache/file/file_test.go index 675393725..8f9a37d18 100644 --- a/cache/file/file_test.go +++ b/cache/file/file_test.go @@ -8,11 +8,12 @@ import ( "github.com/go-spatial/tegola" "github.com/go-spatial/tegola/cache" "github.com/go-spatial/tegola/cache/file" + "github.com/go-spatial/tegola/internal/dict" ) func TestNew(t *testing.T) { type tcase struct { - config map[string]interface{} + config dict.Dict expected *file.Cache err error } @@ -27,7 +28,7 @@ func TestNew(t *testing.T) { // correct error returned return } - t.Errorf("%v", err) + t.Errorf("unexpected error %v", err) return } @@ -70,7 +71,7 @@ func TestNew(t *testing.T) { "max_zoom": "foo", }, expected: nil, - err: fmt.Errorf("max_zoom value needs to be of type uint. Value is of type string"), + err: fmt.Errorf(`config: value mapped to "max_zoom" is string not uint`), }, } @@ -84,7 +85,7 @@ func TestNew(t *testing.T) { func TestSetGetPurge(t *testing.T) { type tcase struct { - config map[string]interface{} + config dict.Dict key cache.Key expected []byte } @@ -150,7 +151,7 @@ func TestSetGetPurge(t *testing.T) { func TestSetOverwrite(t *testing.T) { type tcase struct { - config map[string]interface{} + config dict.Dict key cache.Key bytes1 []byte bytes2 []byte @@ -227,7 +228,7 @@ func TestSetOverwrite(t *testing.T) { func TestMaxZoom(t *testing.T) { type tcase struct { - config map[string]interface{} + config dict.Dict key cache.Key bytes []byte expectedHit bool diff --git a/cache/redis/redis.go b/cache/redis/redis.go index afefa57bb..a3ec6febd 100644 --- a/cache/redis/redis.go +++ b/cache/redis/redis.go @@ -8,7 +8,7 @@ import ( "github.com/go-spatial/tegola" "github.com/go-spatial/tegola/cache" - "github.com/go-spatial/tegola/util/dict" + "github.com/go-spatial/tegola/internal/dict" ) const CacheType = "redis" @@ -25,7 +25,7 @@ func init() { cache.Register(CacheType, New) } -func New(config map[string]interface{}) (rcache cache.Interface, err error) { +func New(config dict.Dicter) (rcache cache.Interface, err error) { // default values defaultNetwork := "tcp" @@ -34,7 +34,7 @@ func New(config map[string]interface{}) (rcache cache.Interface, err error) { defaultDB := 0 defaultMaxZoom := uint(tegola.MaxZ) - c := dict.M(config) + c := config network, err := c.String(ConfigKeyNetwork, &defaultNetwork) if err != nil { diff --git a/cache/redis/redis_test.go b/cache/redis/redis_test.go index dcc07dade..37f9e3ceb 100644 --- a/cache/redis/redis_test.go +++ b/cache/redis/redis_test.go @@ -1,12 +1,15 @@ package redis_test import ( + "net" + "os" "reflect" - "strings" + "syscall" "testing" "github.com/go-spatial/tegola/cache" "github.com/go-spatial/tegola/cache/redis" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/internal/ttools" ) @@ -18,12 +21,52 @@ const TESTENV = "RUN_REDIS_TESTS" func TestNew(t *testing.T) { ttools.ShouldSkip(t, TESTENV) - type tc struct { - config map[string]interface{} - errMatch string + type tcase struct { + config dict.Dict + expectedErr error } - testcases := map[string]tc{ + fn := func(t *testing.T, tc tcase) { + t.Parallel() + + _, err := redis.New(tc.config) + if tc.expectedErr != nil { + if err == nil { + t.Errorf("expected err %v, got nil", tc.expectedErr.Error()) + return + } + + // check error types + if reflect.TypeOf(err) != reflect.TypeOf(tc.expectedErr) { + t.Errorf("invalid error type. expected %T, got %T", tc.expectedErr, err) + return + } + + switch e := err.(type) { + case *net.OpError: + expectedErr := tc.expectedErr.(*net.OpError) + + if reflect.TypeOf(e.Err) != reflect.TypeOf(expectedErr.Err) { + t.Errorf("invalid error type. expected %T, got %T", expectedErr.Err, e.Err) + return + } + default: + // check error messages + if err.Error() != tc.expectedErr.Error() { + t.Errorf("invalid error. expected %v, got %v", tc.expectedErr, err.Error()) + return + } + } + + return + } + if err != nil { + t.Errorf("unexpected err: %v", err) + return + } + } + + tests := map[string]tcase{ "explicit config": { config: map[string]interface{}{ "network": "tcp", @@ -32,42 +75,53 @@ func TestNew(t *testing.T) { "db": 0, "max_zoom": uint(10), }, - errMatch: "", }, "implicit config": { - config: map[string]interface{}{}, - errMatch: "", + config: map[string]interface{}{}, }, "bad address": { config: map[string]interface{}{ "address": "127.0.0.1:6000", }, - errMatch: "connection refused", + expectedErr: &net.OpError{ + Op: "dial", + Net: "tcp", + Addr: &net.TCPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 6000, + }, + Err: &os.SyscallError{ + Err: syscall.ECONNREFUSED, + }, + }, }, "bad max_zoom": { config: map[string]interface{}{ "max_zoom": "2", }, - errMatch: "max_zoom value needs to be of type uint. Value is of type string", + expectedErr: dict.ErrKeyType{ + Key: "max_zoom", + Value: "2", + T: reflect.TypeOf(uint(0)), + }, }, "bad max_zoom 2": { config: map[string]interface{}{ "max_zoom": -2, }, - errMatch: "max_zoom value needs to be of type uint. Value is of type int", + expectedErr: dict.ErrKeyType{ + Key: "max_zoom", + Value: -2, + T: reflect.TypeOf(uint(0)), + }, }, } - for i, tc := range testcases { - _, err := redis.New(tc.config) - if err != nil { - if tc.errMatch != "" && strings.Contains(err.Error(), tc.errMatch) { - // correct error returned - continue - } - t.Errorf("[%v] unexpected err, expected to find %q in %q", i, tc.errMatch, err) - continue - } + for name, tc := range tests { + tc := tc + t.Run(name, func(t *testing.T) { + fn(t, tc) + }) } } @@ -75,7 +129,7 @@ func TestSetGetPurge(t *testing.T) { ttools.ShouldSkip(t, TESTENV) type tc struct { - config map[string]interface{} + config dict.Dict key cache.Key expectedData []byte expectedHit bool @@ -150,7 +204,7 @@ func TestSetGetPurge(t *testing.T) { func TestSetOverwrite(t *testing.T) { ttools.ShouldSkip(t, TESTENV) type tc struct { - config map[string]interface{} + config dict.Dict key cache.Key bytes1 []byte bytes2 []byte @@ -217,7 +271,7 @@ func TestSetOverwrite(t *testing.T) { func TestMaxZoom(t *testing.T) { ttools.ShouldSkip(t, TESTENV) type tcase struct { - config map[string]interface{} + config dict.Dict key cache.Key bytes []byte expectedHit bool diff --git a/cache/s3/s3.go b/cache/s3/s3.go index ebbe22aff..450e1d624 100644 --- a/cache/s3/s3.go +++ b/cache/s3/s3.go @@ -15,7 +15,7 @@ import ( "github.com/go-spatial/tegola" "github.com/go-spatial/tegola/cache" - "github.com/go-spatial/tegola/util/dict" + "github.com/go-spatial/tegola/internal/dict" ) var ( @@ -63,24 +63,21 @@ func init() { // endpoint (string): the endpoint where the S3 compliant backend is located. only necessary for non-AWS deployments. defaults to '' // access_control_list (string): the S3 access control to set on the file when putting the file. defaults to ''. -func New(config map[string]interface{}) (cache.Interface, error) { +func New(config dict.Dicter) (cache.Interface, error) { var err error s3cache := Cache{} - // parse the config - c := dict.M(config) - // the config map's underlying value is int defaultMaxZoom := uint(tegola.MaxZ) - maxZoom, err := c.Uint(ConfigKeyMaxZoom, &defaultMaxZoom) + maxZoom, err := config.Uint(ConfigKeyMaxZoom, &defaultMaxZoom) if err != nil { return nil, err } s3cache.MaxZoom = maxZoom - s3cache.Bucket, err = c.String(ConfigKeyBucket, nil) + s3cache.Bucket, err = config.String(ConfigKeyBucket, nil) if err != nil { return nil, ErrMissingBucket } @@ -90,7 +87,7 @@ func New(config map[string]interface{}) (cache.Interface, error) { // basepath basepath := "" - s3cache.Basepath, err = c.String(ConfigKeyBasepath, &basepath) + s3cache.Basepath, err = config.String(ConfigKeyBasepath, &basepath) if err != nil { return nil, err } @@ -100,18 +97,18 @@ func New(config map[string]interface{}) (cache.Interface, error) { if region == "" { region = DefaultRegion } - region, err = c.String(ConfigKeyRegion, ®ion) + region, err = config.String(ConfigKeyRegion, ®ion) if err != nil { return nil, err } accessKey := "" - accessKey, err = c.String(ConfigKeyAWSAccessKeyID, &accessKey) + accessKey, err = config.String(ConfigKeyAWSAccessKeyID, &accessKey) if err != nil { return nil, err } secretKey := "" - secretKey, err = c.String(ConfigKeyAWSSecretKey, &secretKey) + secretKey, err = config.String(ConfigKeyAWSSecretKey, &secretKey) if err != nil { return nil, err } @@ -125,7 +122,8 @@ func New(config map[string]interface{}) (cache.Interface, error) { if endpoint == "" { endpoint = DefaultEndpoint } - endpoint, err = c.String(ConfigKeyEndpoint, &endpoint) + + endpoint, err = config.String(ConfigKeyEndpoint, &endpoint) if err != nil { return nil, err } @@ -151,7 +149,7 @@ func New(config map[string]interface{}) (cache.Interface, error) { // check for control_access_list env var acl := os.Getenv("AWS_ACL") - acl, err = c.String(ConfigKeyACL, &acl) + acl, err = config.String(ConfigKeyACL, &acl) if err != nil { return nil, err } diff --git a/cache/s3/s3_test.go b/cache/s3/s3_test.go index 2a8cae7b4..5e76e62b7 100644 --- a/cache/s3/s3_test.go +++ b/cache/s3/s3_test.go @@ -8,6 +8,7 @@ import ( "github.com/go-spatial/tegola/cache" "github.com/go-spatial/tegola/cache/s3" + "github.com/go-spatial/tegola/internal/dict" ) func TestNew(t *testing.T) { @@ -16,7 +17,7 @@ func TestNew(t *testing.T) { } type tcase struct { - config map[string]interface{} + config dict.Dict err error } @@ -83,7 +84,7 @@ func TestSetGetPurge(t *testing.T) { } type tcase struct { - config map[string]interface{} + config dict.Dict key cache.Key expected []byte } @@ -156,7 +157,7 @@ func TestSetOverwrite(t *testing.T) { } type tcase struct { - config map[string]interface{} + config dict.Dict key cache.Key bytes1 []byte bytes2 []byte @@ -238,7 +239,7 @@ func TestMaxZoom(t *testing.T) { } type tcase struct { - config map[string]interface{} + config dict.Dict key cache.Key bytes []byte expectedHit bool diff --git a/cmd/internal/register/caches.go b/cmd/internal/register/caches.go index 8050ec56b..f8b5313cb 100644 --- a/cmd/internal/register/caches.go +++ b/cmd/internal/register/caches.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/go-spatial/tegola/cache" + "github.com/go-spatial/tegola/internal/dict" ) var ( @@ -12,16 +13,17 @@ var ( ) // Cache registers cache backends -func Cache(config map[string]interface{}) (cache.Interface, error) { - // lookup our cache type - t, ok := config["type"] - if !ok { - return nil, ErrCacheTypeMissing - } - - cType, ok := t.(string) - if !ok { - return nil, ErrCacheTypeInvalid +func Cache(config dict.Dicter) (cache.Interface, error) { + cType, err := config.String("type", nil) + if err != nil { + switch err.(type) { + case dict.ErrKeyRequired: + return nil, ErrCacheTypeMissing + case dict.ErrKeyType: + return nil, ErrCacheTypeInvalid + default: + return nil, err + } } // register the provider diff --git a/cmd/internal/register/caches_test.go b/cmd/internal/register/caches_test.go index bc68fb741..d1fc70bb5 100644 --- a/cmd/internal/register/caches_test.go +++ b/cmd/internal/register/caches_test.go @@ -4,11 +4,12 @@ import ( "testing" "github.com/go-spatial/tegola/cmd/internal/register" + "github.com/go-spatial/tegola/internal/dict" ) func TestCaches(t *testing.T) { type tcase struct { - config map[string]interface{} + config dict.Dict expectedErr error } @@ -30,12 +31,12 @@ func TestCaches(t *testing.T) { tests := map[string]tcase{ "missing type": { - config: map[string]interface{}{}, + config: dict.Dict{}, expectedErr: register.ErrCacheTypeMissing, }, "type is not string": { - config: map[string]interface{}{ + config: dict.Dict{ "type": 1, }, expectedErr: register.ErrCacheTypeInvalid, diff --git a/cmd/internal/register/maps_test.go b/cmd/internal/register/maps_test.go index e66f0c63a..7d9d4d47b 100644 --- a/cmd/internal/register/maps_test.go +++ b/cmd/internal/register/maps_test.go @@ -6,20 +6,27 @@ import ( "github.com/go-spatial/tegola/atlas" "github.com/go-spatial/tegola/cmd/internal/register" "github.com/go-spatial/tegola/config" + "github.com/go-spatial/tegola/internal/dict" ) func TestMaps(t *testing.T) { type tcase struct { atlas atlas.Atlas maps []config.Map - providers []map[string]interface{} + providers []dict.Dict expectedErr error } fn := func(t *testing.T, tc tcase) { var err error - providers, err := register.Providers(tc.providers) + // convert []dict.Dict -> []dict.Dicter + provArr := make([]dict.Dicter, len(tc.providers)) + for i := range provArr { + provArr[i] = tc.providers[i] + } + + providers, err := register.Providers(provArr) if err != nil { t.Errorf("unexpected err: %v", err) return @@ -50,7 +57,7 @@ func TestMaps(t *testing.T) { }, }, }, - providers: []map[string]interface{}{ + providers: []dict.Dict{ { "name": "test", "type": "debug", @@ -87,7 +94,7 @@ func TestMaps(t *testing.T) { }, }, }, - providers: []map[string]interface{}{ + providers: []dict.Dict{ { "name": "test", "type": "debug", @@ -111,7 +118,7 @@ func TestMaps(t *testing.T) { }, }, }, - providers: []map[string]interface{}{ + providers: []dict.Dict{ { "name": "test", "type": "debug", @@ -123,7 +130,7 @@ func TestMaps(t *testing.T) { }, "success": { maps: []config.Map{}, - providers: []map[string]interface{}{ + providers: []dict.Dict{ { "name": "test", "type": "debug", diff --git a/cmd/internal/register/providers.go b/cmd/internal/register/providers.go index 4a234dc94..0dce52264 100644 --- a/cmd/internal/register/providers.go +++ b/cmd/internal/register/providers.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/provider" ) @@ -12,65 +13,61 @@ var ( ErrProviderNameInvalid = errors.New("register: provider 'name' value must be a string") ) -type ErrProviderAlreadyRegistered struct { - ProviderName string -} +type ErrProviderAlreadyRegistered string func (e ErrProviderAlreadyRegistered) Error() string { - return fmt.Sprintf("register: provider (%v) already registered", e.ProviderName) + return fmt.Sprintf("register: provider (%v) already registered", string(e)) } -type ErrProviderTypeMissing struct { - ProviderName string -} +type ErrProviderTypeMissing string func (e ErrProviderTypeMissing) Error() string { - return fmt.Sprintf("register: provider 'type' parameter missing for provider (%v)", e.ProviderName) + return fmt.Sprintf("register: provider 'type' parameter missing for provider (%v)", string(e)) } -type ErrProviderTypeInvalid struct { - ProviderName string -} +type ErrProviderTypeInvalid string func (e ErrProviderTypeInvalid) Error() string { - return fmt.Sprintf("register: provider 'type' must be a string for provider (%v)", e.ProviderName) + return fmt.Sprintf("register: provider 'type' must be a string for provider (%v)", string(e)) } // Providers registers data provider backends -func Providers(providers []map[string]interface{}) (map[string]provider.Tiler, error) { - var err error - +func Providers(providers []dict.Dicter) (map[string]provider.Tiler, error) { // holder for registered providers registeredProviders := map[string]provider.Tiler{} // iterate providers for _, p := range providers { // lookup our proivder name - n, ok := p["name"] - if !ok { - return registeredProviders, ErrProviderNameMissing - } - - pname, found := n.(string) - if !found { - return registeredProviders, ErrProviderNameInvalid + pname, err := p.String("name", nil) + if err != nil { + switch err.(type) { + case dict.ErrKeyRequired: + return registeredProviders, ErrProviderNameMissing + case dict.ErrKeyType: + return registeredProviders, ErrProviderNameInvalid + default: + return registeredProviders, err + } } // check if a proivder with this name is alrady registered - _, ok = registeredProviders[pname] + _, ok := registeredProviders[pname] if ok { - return registeredProviders, ErrProviderAlreadyRegistered{pname} + return registeredProviders, ErrProviderAlreadyRegistered(pname) } // lookup our provider type - t, ok := p["type"] - if !ok { - return registeredProviders, ErrProviderTypeMissing{pname} - } - - ptype, found := t.(string) - if !found { - return registeredProviders, ErrProviderTypeInvalid{pname} + ptype, err := p.String("type", nil) + if err != nil { + switch err.(type) { + case dict.ErrKeyRequired: + return registeredProviders, ErrProviderTypeMissing(pname) + case dict.ErrKeyType: + return registeredProviders, ErrProviderTypeInvalid(pname) + default: + return registeredProviders, err + } } // register the provider @@ -83,5 +80,5 @@ func Providers(providers []map[string]interface{}) (map[string]provider.Tiler, e registeredProviders[pname] = prov } - return registeredProviders, err + return registeredProviders, nil } diff --git a/cmd/internal/register/providers_test.go b/cmd/internal/register/providers_test.go index 8739abe84..76d94ef67 100644 --- a/cmd/internal/register/providers_test.go +++ b/cmd/internal/register/providers_test.go @@ -4,18 +4,25 @@ import ( "testing" "github.com/go-spatial/tegola/cmd/internal/register" + "github.com/go-spatial/tegola/internal/dict" ) func TestProviders(t *testing.T) { type tcase struct { - config []map[string]interface{} + config []dict.Dict expectedErr error } fn := func(t *testing.T, tc tcase) { var err error - _, err = register.Providers(tc.config) + // convert []dict.Dict -> []dict.Dicter + provArr := make([]dict.Dicter, len(tc.config)) + for i := range provArr { + provArr[i] = tc.config[i] + } + + _, err = register.Providers(provArr) if tc.expectedErr != nil { if err.Error() != tc.expectedErr.Error() { t.Errorf("invalid error. expected: %v, got %v", tc.expectedErr, err.Error()) @@ -30,7 +37,7 @@ func TestProviders(t *testing.T) { tests := map[string]tcase{ "missing name": { - config: []map[string]interface{}{ + config: []dict.Dict{ { "type": "postgis", }, @@ -38,7 +45,7 @@ func TestProviders(t *testing.T) { expectedErr: register.ErrProviderNameMissing, }, "name is not string": { - config: []map[string]interface{}{ + config: []dict.Dict{ { "name": 1, }, @@ -46,24 +53,24 @@ func TestProviders(t *testing.T) { expectedErr: register.ErrProviderNameInvalid, }, "missing type": { - config: []map[string]interface{}{ + config: []dict.Dict{ { "name": "test", }, }, - expectedErr: register.ErrProviderTypeMissing{"test"}, + expectedErr: register.ErrProviderTypeMissing("test"), }, "invalid type": { - config: []map[string]interface{}{ + config: []dict.Dict{ { "name": "test", "type": 1, }, }, - expectedErr: register.ErrProviderTypeInvalid{"test"}, + expectedErr: register.ErrProviderTypeInvalid("test"), }, "already registered": { - config: []map[string]interface{}{ + config: []dict.Dict{ { "name": "test", "type": "debug", @@ -73,10 +80,10 @@ func TestProviders(t *testing.T) { "type": "debug", }, }, - expectedErr: register.ErrProviderAlreadyRegistered{"test"}, + expectedErr: register.ErrProviderAlreadyRegistered("test"), }, "success": { - config: []map[string]interface{}{ + config: []dict.Dict{ { "name": "test", "type": "debug", diff --git a/cmd/tegola/cmd/root.go b/cmd/tegola/cmd/root.go index 468da0957..47859af84 100644 --- a/cmd/tegola/cmd/root.go +++ b/cmd/tegola/cmd/root.go @@ -10,6 +10,7 @@ import ( "github.com/go-spatial/tegola/atlas" "github.com/go-spatial/tegola/cmd/internal/register" "github.com/go-spatial/tegola/config" + "github.com/go-spatial/tegola/internal/dict" ) var ( @@ -64,7 +65,13 @@ func initConfig() { } // init our providers - providers, err := register.Providers(conf.Providers) + // but first convert []env.Map -> []dict.Dicter + provArr := make([]dict.Dicter, len(conf.Providers)) + for i := range provArr { + provArr[i] = conf.Providers[i] + } + + providers, err := register.Providers(provArr) if err != nil { log.Fatal(err) } diff --git a/cmd/tegola_lambda/main.go b/cmd/tegola_lambda/main.go index 941883f05..0dae1bd0f 100644 --- a/cmd/tegola_lambda/main.go +++ b/cmd/tegola_lambda/main.go @@ -11,6 +11,7 @@ import ( "github.com/go-spatial/tegola/atlas" "github.com/go-spatial/tegola/cmd/internal/register" "github.com/go-spatial/tegola/config" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/server" ) @@ -43,8 +44,15 @@ func main() { log.Fatal(err) } + // init our providers + // but first convert []env.Map -> []dict.Dicter + provArr := make([]dict.Dicter, len(conf.Providers)) + for i := range provArr { + provArr[i] = conf.Providers[i] + } + // register the providers - providers, err := register.Providers(conf.Providers) + providers, err := register.Providers(provArr) if err != nil { log.Fatal(err) } diff --git a/cmd/xyz2svg/cmd/draw.go b/cmd/xyz2svg/cmd/draw.go index 2a9a14d60..704d65003 100644 --- a/cmd/xyz2svg/cmd/draw.go +++ b/cmd/xyz2svg/cmd/draw.go @@ -18,6 +18,7 @@ import ( "github.com/go-spatial/tegola/config" "github.com/go-spatial/tegola/draw/svg" "github.com/go-spatial/tegola/internal/convert" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/maths/validate" "github.com/go-spatial/tegola/mvt" "github.com/go-spatial/tegola/provider" @@ -95,11 +96,19 @@ func drawCommand(cmd *cobra.Command, args []string) { format: drawOutputFilenameFormat, basedir: drawOutputBaseDir, } - providers, err := register.Providers(config.Providers) + + // convert []env.Map -> []dict.Dicter + provArr := make([]dict.Dicter, len(config.Providers)) + for i := range provArr { + provArr[i] = config.Providers[i] + } + + // register providers + providers, err := register.Providers(provArr) if err != nil { - fmt.Fprintf(os.Stderr, "Error loading providers in config(%v): %v\n", configFilename, err) - os.Exit(1) + log.Fatalf("Error loading providers in config(%v): %v\n", configFilename, err) } + prv, lyr := splitProviderLayer(providerString) var allprvs []string for name := range providers { diff --git a/cmd/xyz2svg/cmd/root.go b/cmd/xyz2svg/cmd/root.go index 35f287fe4..69d84a294 100644 --- a/cmd/xyz2svg/cmd/root.go +++ b/cmd/xyz2svg/cmd/root.go @@ -1,14 +1,14 @@ package cmd import ( - "errors" "fmt" "strconv" "strings" - "github.com/go-spatial/tegola/provider" "github.com/spf13/cobra" + "github.com/go-spatial/tegola/provider" + _ "github.com/go-spatial/tegola/provider/postgis" ) @@ -44,51 +44,6 @@ func init() { Root.AddCommand(drawCmd) } -//initProviders will return a map of registered providers in the config file. -func initProviders(providers []map[string]interface{}) (prvs map[string]provider.Tiler, err error) { - - prvs = make(map[string]provider.Tiler) - - // iterate providers - for _, p := range providers { - // lookup our provider name - n, ok := p["name"] - if !ok { - return prvs, errors.New("missing 'name' parameter for provider") - } - - pname, found := n.(string) - if !found { - return prvs, fmt.Errorf("'name' or provider must be of type string") - } - - // check if a proivder with this name is alrady registered - if _, ok := prvs[pname]; ok { - return prvs, fmt.Errorf("provider (%v) already registered!", pname) - } - - // lookup our provider type - t, ok := p["type"] - if !ok { - return prvs, fmt.Errorf("missing 'type' parameter for provider (%v)", pname) - } - - ptype, found := t.(string) - if !found { - return prvs, fmt.Errorf("'type' for provider (%v) must be a string", pname) - } - - // register the provider - prov, err := provider.For(ptype, p) - if err != nil { - return prvs, err - } - // add the provider to our map of registered providers - prvs[pname] = prov - } - return prvs, err -} - //parseTileString will convert a z/x/y formatted string into a the three components. func parseTileString(str string) (uint, uint, uint, error) { parts := strings.Split(str, "/") @@ -96,19 +51,20 @@ func parseTileString(str string) (uint, uint, uint, error) { return 0, 0, 0, fmt.Errorf("invalid zxy value “%v”; expected format “z/x/y”", str) } attr := [3]string{"z", "x", "y"} + var vals [3]uint var placeholder uint64 var err error - for i := range attr { + for i := range attr { placeholder, err = strconv.ParseUint(parts[i], 10, 64) if err != nil { return 0, 0, 0, fmt.Errorf("invalid %v value (%v); should be a positive integer.", attr[i], vals[i]) } vals[i] = uint(placeholder) } - return vals[0], vals[1], vals[2], nil + return vals[0], vals[1], vals[2], nil } //splitProviderLayer will convert a “$provider.$layer” formatted string into a the two components. diff --git a/config/config.go b/config/config.go index ceb1c06dd..b693bcf80 100644 --- a/config/config.go +++ b/config/config.go @@ -14,7 +14,7 @@ import ( "github.com/BurntSushi/toml" "github.com/go-spatial/tegola" - "github.com/go-spatial/tegola/config/env" + "github.com/go-spatial/tegola/internal/env" "github.com/go-spatial/tegola/internal/log" ) @@ -26,10 +26,10 @@ type Config struct { // If this is an empty string, it means that the location was unknown. This is the case if // the Parse() function is used directly. LocationName string - Webserver Webserver `toml:"webserver"` - Cache map[string]interface{} `toml:"cache"` + Webserver Webserver `toml:"webserver"` + Cache env.Dict `toml:"cache"` // Map of providers. - Providers []map[string]interface{} + Providers []env.Dict Maps []Map } diff --git a/config/config_test.go b/config/config_test.go index 3d65fbabf..6a0dc92b1 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,14 +1,14 @@ package config_test import ( + "os" "reflect" + "strconv" "strings" "testing" "github.com/go-spatial/tegola/config" - "github.com/go-spatial/tegola/config/env" - "os" - "strconv" + "github.com/go-spatial/tegola/internal/env" ) const ( @@ -137,11 +137,11 @@ func TestParse(t *testing.T) { Port: ":8080", CORSAllowedOrigin: "tegola.io", }, - Cache: map[string]interface{}{ + Cache: env.Dict{ "type": "file", "basepath": "/tmp/tegola-cache", }, - Providers: []map[string]interface{}{ + Providers: []env.Dict{ { "name": "provider1", "type": "postgis", @@ -244,7 +244,7 @@ func TestParse(t *testing.T) { HostName: ENV_TEST_HOST_CONCAT, Port: ENV_TEST_PORT, }, - Providers: []map[string]interface{}{ + Providers: []env.Dict{ { "name": "provider1", "type": "postgis", @@ -346,7 +346,7 @@ func TestValidate(t *testing.T) { Webserver: config.Webserver{ Port: ":8080", }, - Providers: []map[string]interface{}{ + Providers: []env.Dict{ { "name": "provider1", "type": "postgis", @@ -410,7 +410,7 @@ func TestValidate(t *testing.T) { }, "2": { config: config.Config{ - Providers: []map[string]interface{}{ + Providers: []env.Dict{ { "name": "provider1", "type": "postgis", @@ -480,7 +480,7 @@ func TestValidate(t *testing.T) { Webserver: config.Webserver{ Port: ":8080", }, - Providers: []map[string]interface{}{ + Providers: []env.Dict{ { "name": "provider1", "type": "postgis", @@ -563,7 +563,7 @@ func TestValidate(t *testing.T) { Webserver: config.Webserver{ Port: ":8080", }, - Providers: []map[string]interface{}{ + Providers: []env.Dict{ { "name": "provider1", "type": "postgis", @@ -632,7 +632,7 @@ func TestValidate(t *testing.T) { Webserver: config.Webserver{ Port: ":8080", }, - Providers: []map[string]interface{}{ + Providers: []env.Dict{ { "name": "provider1", "type": "postgis", diff --git a/config/env/types.go b/config/env/types.go deleted file mode 100644 index 6f723e116..000000000 --- a/config/env/types.go +++ /dev/null @@ -1,213 +0,0 @@ -package env - -import ( - "fmt" - "os" - "regexp" - "strconv" - "strings" -) - -// matches a variable surrounded by curly braces with leading dollar sign. -// ex: ${MY_VAR} -var regex = regexp.MustCompile(`\${[A-Z]+[A-Z1-9_]*}`) - -// EnvironmentError corresponds with a missing environment variable -type EnvironmentError struct { - varName string -} - -func (ee EnvironmentError) Error() string { - return fmt.Sprintf("environment variable %q not found", ee.varName) -} - -// TypeError corresponds with an incorrect type passed to UnmarshalTOML -type TypeError struct { - v interface{} -} - -func (te *TypeError) Error() string { - return fmt.Sprintf("type %t could not be converted", te.v) -} - -// replaceEnvVars replaces environment variable placeholders in reader stream with values -func replaceEnvVar(in string) (string, error) { - // loop through all environment variable matches - for locs := regex.FindStringIndex(in); locs != nil; locs = regex.FindStringIndex(in) { - - // extract match from the input string - match := in[locs[0]:locs[1]] - - // trim the leading '${' and trailing '}' - varName := match[2 : len(match)-1] - - // get env var - envVar, ok := os.LookupEnv(varName) - if !ok { - return "", &EnvironmentError{varName: varName} - } - - // update the input string with the env values - in = strings.Replace(in, match, envVar, -1) - } - - return in, nil -} - -type Bool bool - -func BoolPtr(v Bool) *Bool { - return &v -} - -func (t *Bool) UnmarshalTOML(v interface{}) error { - var boolVal bool - var err error - - switch val := v.(type) { - case string: - val, err = replaceEnvVar(val) - if err != nil { - return err - } - - boolVal, err = strconv.ParseBool(val) - if err != nil { - return err - } - case bool: - boolVal = val - default: - return &TypeError{v} - } - - *t = Bool(boolVal) - return nil -} - -type String string - -func StringPtr(v String) *String { - return &v -} - -func (t *String) UnmarshalTOML(v interface{}) error { - var stringVal string - var err error - - switch val := v.(type) { - case string: - stringVal, err = replaceEnvVar(val) - if err != nil { - return err - } - default: - return &TypeError{v} - } - - *t = String(stringVal) - return nil -} - -type Int int - -func IntPtr(v Int) *Int { - return &v -} - -func (t *Int) UnmarshalTOML(v interface{}) error { - var intVal int64 - var err error - - switch val := v.(type) { - case string: - val, err = replaceEnvVar(val) - if err != nil { - return err - } - intVal, err = strconv.ParseInt(val, 10, 64) - if err != nil { - return err - } - case int: - intVal = int64(val) - case int64: - intVal = val - case uint: - intVal = int64(val) - case uint64: - intVal = int64(val) - default: - return &TypeError{v} - } - - *t = Int(intVal) - return nil -} - -type Uint uint - -func UintPtr(v Uint) *Uint { - return &v -} - -func (t *Uint) UnmarshalTOML(v interface{}) error { - var uintVal uint64 - var err error - - switch val := v.(type) { - case string: - val, err = replaceEnvVar(val) - if err != nil { - return err - } - uintVal, err = strconv.ParseUint(val, 10, 64) - if err != nil { - return err - } - case int: - uintVal = uint64(val) - case int64: - uintVal = uint64(val) - case uint: - uintVal = uint64(val) - case uint64: - uintVal = val - default: - return &TypeError{v} - } - - *t = Uint(uintVal) - return nil -} - -type Float float64 - -func FloatPtr(v Float) *Float { - return &v -} -func (t *Float) UnmarshalTOML(v interface{}) error { - var floatVal float64 - var err error - - switch val := v.(type) { - case string: - val, err = replaceEnvVar(val) - if err != nil { - return err - } - floatVal, err = strconv.ParseFloat(val, 64) - if err != nil { - return err - } - case float64: - floatVal = val - case float32: - floatVal = float64(val) - default: - return &TypeError{v} - } - - *t = Float(floatVal) - return nil -} diff --git a/config/env/types_internal_test.go b/config/env/types_internal_test.go deleted file mode 100644 index 29d4ca6b1..000000000 --- a/config/env/types_internal_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package env - -import ( - "os" - "testing" -) - -func TestReplaceEnvVar(t *testing.T) { - type tcase struct { - envVar map[string]string - in string - expected string - expectedErr error - } - - fn := func(t *testing.T, tc tcase) { - t.Parallel() - // set the environment vars - for i := range tc.envVar { - os.Setenv(i, tc.envVar[i]) - } - - // issue replace - out, err := replaceEnvVar(tc.in) - if err != nil { - if tc.expectedErr.Error() != err.Error() { - t.Errorf("expected %v, got %v", tc.expected, err) - return - } - } - - if tc.expected != out { - t.Errorf("expected %v, got %v", tc.expected, out) - return - } - } - - tests := map[string]tcase{ - "success": { - envVar: map[string]string{ - "FOO": "bar", - }, - in: "${FOO}", - expected: "bar", - }, - "env var not found": { - envVar: map[string]string{}, - in: "${BAZ}", - expectedErr: EnvironmentError{"BAZ"}, - }, - } - - for name, tc := range tests { - tc := tc - t.Run(name, func(t *testing.T) { fn(t, tc) }) - } -} diff --git a/internal/dict/dict.go b/internal/dict/dict.go new file mode 100644 index 000000000..3412802ce --- /dev/null +++ b/internal/dict/dict.go @@ -0,0 +1,204 @@ +package dict + +import "reflect" + +// Dict is a pass-through implementation of the Dicter interface +type Dict map[string]interface{} + +func (d Dict) String(key string, def *string) (r string, err error) { + v, ok := d[key] + if !ok { + if def == nil { + return r, ErrKeyRequired(key) + } else { + return *def, nil + } + } + + r, ok = v.(string) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(r)} + } + + return r, nil +} + +func (d Dict) StringSlice(key string) (r []string, err error) { + v, ok := d[key] + if !ok { + return r, nil + } + + r, ok = v.([]string) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(r)} + } + + return r, nil +} + +func (d Dict) Bool(key string, def *bool) (r bool, err error) { + v, ok := d[key] + if !ok { + if def == nil { + return r, nil + } else { + return *def, nil + } + } + + r, ok = v.(bool) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(r)} + } + + return r, nil +} + +func (d Dict) BoolSlice(key string) (r []bool, err error) { + v, ok := d[key] + if !ok { + return r, nil + } + + r, ok = v.([]bool) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(r)} + } + + return r, nil +} + +func (d Dict) Int(key string, def *int) (r int, err error) { + v, ok := d[key] + if !ok { + if def == nil { + return r, ErrKeyRequired(key) + } else { + return *def, nil + } + } + + r, ok = v.(int) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(r)} + } + + return r, nil +} + +func (d Dict) IntSlice(key string) (r []int, err error) { + v, ok := d[key] + if !ok { + return r, nil + } + + r, ok = v.([]int) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(r)} + } + + return r, nil +} + +func (d Dict) Uint(key string, def *uint) (r uint, err error) { + v, ok := d[key] + if !ok { + if def == nil { + return r, nil + } else { + return *def, nil + } + } + + r, ok = v.(uint) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(r)} + } + + return r, nil +} + +func (d Dict) UintSlice(key string) (r []uint, err error) { + v, ok := d[key] + if !ok { + return r, nil + } + + r, ok = v.([]uint) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(r)} + } + + return r, nil +} + +func (d Dict) Float(key string, def *float64) (r float64, err error) { + v, ok := d[key] + if !ok { + if def == nil { + return r, ErrKeyRequired(key) + } else { + return *def, nil + } + } + + r, ok = v.(float64) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(r)} + } + + return r, nil +} + +func (d Dict) FloatSlice(key string) (r []float64, err error) { + v, ok := d[key] + if !ok { + return r, nil + } + + r, ok = v.([]float64) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(r)} + } + + return r, nil +} + +func (d Dict) Map(key string) (r Dicter, err error) { + v, ok := d[key] + if !ok { + return Dict{}, nil + } + + r, ok = v.(Dict) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(r)} + } + + return r, nil +} + +func (d Dict) MapSlice(key string) (r []Dicter, err error) { + v, ok := d[key] + if !ok { + return r, nil + } + + darr, ok := v.([]map[string]interface{}) + if !ok { + return r, ErrKeyType{Key: key, Value: v, T: reflect.TypeOf(darr)} + } + + r = make([]Dicter, len(darr)) + for k := range darr { + r[k] = Dicter(Dict(darr[k])) + } + + return r, nil +} + +func (d Dict) Interface(key string) (r interface{}, ok bool) { + r, ok = d[key] + return r, ok +} diff --git a/internal/dict/dict_test.go b/internal/dict/dict_test.go new file mode 100644 index 000000000..2670b0237 --- /dev/null +++ b/internal/dict/dict_test.go @@ -0,0 +1,93 @@ +package dict_test + +import ( + "reflect" + "testing" + + "github.com/go-spatial/tegola/internal/dict" +) + +func TestDict(t *testing.T) { + + type tcase struct { + dict dict.Dict + key string + expected interface{} + expectedErr error + } + + fn := func(t *testing.T, tc tcase) { + + var val interface{} + var err error + + switch tc.expected.(type) { + case string: + val, err = tc.dict.String(tc.key, nil) + case bool: + val, err = tc.dict.Bool(tc.key, nil) + case int: + val, err = tc.dict.Int(tc.key, nil) + case uint: + val, err = tc.dict.Uint(tc.key, nil) + case float32, float64: + val, err = tc.dict.Float(tc.key, nil) + default: + t.Errorf("invalid type: %T", tc.expected) + return + } + + if tc.expectedErr != nil { + if err == nil { + t.Errorf("expected err %v, got nil", tc.expectedErr.Error()) + return + } + + // compare error messages + if tc.expectedErr.Error() != err.Error() { + t.Errorf("invalid error. expected %v, got %v", tc.expectedErr, err) + return + } + + return + } + if err != nil { + t.Errorf("unexpected err: %v", err) + return + } + + if !reflect.DeepEqual(val, tc.expected) { + t.Errorf("expected %v, got %v", tc.expected, val) + return + } + } + + tests := map[string]tcase{ + "string": { + dict: dict.Dict{ + "host": "foo", + }, + key: "host", + expected: "foo", + }, + "string error": { + dict: dict.Dict{ + "host": 1, + }, + key: "host", + expected: "", + expectedErr: dict.ErrKeyType{ + Key: "host", + Value: 1, + T: reflect.TypeOf(""), + }, + }, + } + + for name, tc := range tests { + tc := tc + t.Run(name, func(t *testing.T) { + fn(t, tc) + }) + } +} diff --git a/internal/dict/types.go b/internal/dict/types.go new file mode 100644 index 000000000..5f24073da --- /dev/null +++ b/internal/dict/types.go @@ -0,0 +1,51 @@ +package dict + +import ( + "fmt" + "reflect" +) + +// Dicter is an abstraction over map[string]interface{} +// with the intent of allowing manipulation between the +// underlying data and requests for that data. +type Dicter interface { + String(key string, Default *string) (string, error) + StringSlice(key string) ([]string, error) + + Bool(key string, Default *bool) (bool, error) + BoolSlice(key string) ([]bool, error) + + Int(key string, Default *int) (int, error) + IntSlice(key string) ([]int, error) + + Uint(key string, Default *uint) (uint, error) + UintSlice(key string) ([]uint, error) + + Float(key string, Default *float64) (float64, error) + FloatSlice(key string) ([]float64, error) + + Map(key string) (Dicter, error) + MapSlice(key string) ([]Dicter, error) + + Interface(key string) (v interface{}, ok bool) +} + +// ErrKeyRequired is used to communicate a map miss and Dicter implementations should set the value as the missed key +type ErrKeyRequired string + +func (err ErrKeyRequired) Error() string { + return fmt.Sprintf("config: required Key %q not found", string(err)) +} + +// ErrKeyType is used to communicate the value requested cannot be converted/coerced/manipulated +// according to the method call. +// TODO: rename to ErrType +type ErrKeyType struct { + Key string + Value interface{} + T reflect.Type +} + +func (err ErrKeyType) Error() string { + return fmt.Sprintf("config: value mapped to %q is %T not %s", err.Key, err.Value, err.T.String()) +} diff --git a/internal/env/dict.go b/internal/env/dict.go new file mode 100644 index 000000000..058a93ace --- /dev/null +++ b/internal/env/dict.go @@ -0,0 +1,450 @@ +/* This file was generated using gen.pl and go fmt. */ + +// dict is a helper function that allow one to easily get concreate values out of a map[string]interface{} +package env + +import ( + "fmt" + "reflect" + + "github.com/go-spatial/tegola/internal/dict" +) + +type Dict map[string]interface{} + +// Dict is to obtain a map[string]interface{} that has already been cast to a M type. +func (d Dict) Dict(key string) (v Dict, err error) { + var val interface{} + var dv Dict + var ok bool + if val, ok = d[key]; !ok { + return v, fmt.Errorf("%v value is required.", key) + } + + if dv, ok = val.(Dict); !ok { + return v, fmt.Errorf("%v value needs to be of type map[string]interface{}.", key) + } + return dv, nil +} + +// String returns the value as a string type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. +func (d Dict) String(key string, def *string) (v string, err error) { + var val interface{} + var ok bool + + if val, ok = d[key]; !ok || val == nil { + if def != nil { + return *def, nil + } + return v, dict.ErrKeyRequired(key) + } + + ptr, err := ParseString(val) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + return *ptr, nil +} + +func (d Dict) StringSlice(key string) (v []string, err error) { + + val, ok := d[key] + if !ok { + return v, dict.ErrKeyRequired(key) + } + + switch val.(type) { + case string: + v, err = ParseStringSlice(val.(string)) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + + case []string: + v = val.([]string) + case []interface{}: + // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate + // type first, and then into the this type. + var iv []interface{} + if iv, ok = val.([]interface{}); !ok { + // Could not convert to the generic type, so we don't have the correct thing. + return v, dict.ErrKeyType{key, val, reflect.TypeOf(iv)} + } + + v = make([]string, len(iv)) + for k := range iv { + if iv[k] == nil { + v[k] = "" + } else { + ptr, err := ParseString(val) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + + v[k] = *ptr + } + } + } + + return v, nil +} + +// Bool returns the value as a string type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. +func (d Dict) Bool(key string, def *bool) (v bool, err error) { + var val interface{} + var ok bool + + if val, ok = d[key]; !ok || val == nil { + if def != nil { + return *def, nil + } + return v, dict.ErrKeyRequired(key) + } + + ptr, err := ParseBool(val) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + + return *ptr, nil +} + +func (d Dict) BoolSlice(key string) (v []bool, err error) { + + val, ok := d[key] + if !ok { + return v, dict.ErrKeyRequired(key) + } + + switch val.(type) { + case string: + v, err = ParseBoolSlice(val.(string)) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + case []bool: + v = val.([]bool) + case []interface{}: + // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate + // type first, and then into the this type. + var iv []interface{} + if iv, ok = val.([]interface{}); !ok { + // Could not convert to the generic type, so we don't have the correct thing. + return v, dict.ErrKeyType{key, val, reflect.TypeOf(iv)} + } + + v = make([]bool, len(iv)) + for k := range iv { + if iv[k] == nil { + iv[k] = false + } else { + ptr, err := ParseBool(val) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + + v[k] = *ptr + } + } + } + + return v, nil +} + +// Int returns the value as a int type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. +func (d Dict) Int(key string, def *int) (v int, err error) { + var val interface{} + var ok bool + if val, ok = d[key]; !ok || val == nil { + if def != nil { + return *def, nil + } + return v, dict.ErrKeyRequired(key) + } + + ptr, err := ParseInt(val) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + + return *ptr, nil +} + +func (d Dict) IntSlice(key string) (v []int, err error) { + + val, ok := d[key] + if !ok { + return v, dict.ErrKeyRequired(key) + } + + switch val.(type) { + case string: + v, err = ParseIntSlice(val.(string)) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + case []int: + v = val.([]int) + case []interface{}: + // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate + // type first, and then into the this type. + var iv []interface{} + if iv, ok = val.([]interface{}); !ok { + // Could not convert to the generic type, so we don't have the correct thing. + return v, dict.ErrKeyType{key, val, reflect.TypeOf(iv)} + } + v = make([]int, len(iv)) + for k := range iv { + if iv[k] == nil { + iv[k] = 0 + } else { + ptr, err := ParseInt(val) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + + v[k] = *ptr + } + + } + } + + return v, nil +} + +// Uint returns the value as a uint type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. +func (d Dict) Uint(key string, def *uint) (v uint, err error) { + var val interface{} + var ok bool + if val, ok = d[key]; !ok || val == nil { + if def != nil { + return *def, nil + } + return v, dict.ErrKeyRequired(key) + } + + ptr, err := ParseUint(val) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + + return *ptr, nil +} + +func (d Dict) UintSlice(key string) (v []uint, err error) { + + val, ok := d[key] + if !ok { + return v, dict.ErrKeyRequired(key) + } + + switch val.(type) { + case string: + v, err = ParseUintSlice(val.(string)) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + case []uint: + v = val.([]uint) + case []interface{}: + // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate + // type first, and then into the this type. + var iv []interface{} + if iv, ok = val.([]interface{}); !ok { + // Could not convert to the generic type, so we don't have the correct thing. + return v, &ErrType{val} + } + for k := range iv { + if iv[k] == nil { + iv[k] = 0 + } else { + ptr, err := ParseUint(val) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + + v[k] = *ptr + } + } + } + + return v, nil +} + +// Float returns the value as a uint type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. +func (d Dict) Float(key string, def *float64) (v float64, err error) { + var val interface{} + var ok bool + if val, ok = d[key]; !ok || val == nil { + if def != nil { + return *def, nil + } + return v, dict.ErrKeyRequired(key) + } + + ptr, err := ParseFloat(val) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + + return *ptr, nil +} + +func (d Dict) FloatSlice(key string) (v []float64, err error) { + val, ok := d[key] + if !ok { + return v, dict.ErrKeyRequired(key) + } + + switch val.(type) { + case string: + v, err = ParseFloatSlice(val.(string)) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + case []float64: + v = val.([]float64) + case []interface{}: + // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate + // type first, and then into the this type. + var iv []interface{} + if iv, ok = val.([]interface{}); !ok { + // Could not convert to the generic type, so we don't have the correct thing. + return v, &ErrType{val} + } + for k := range iv { + if iv[k] == nil { + iv[k] = 0 + } else { + ptr, err := ParseFloat(val) + if err != nil { + switch err.(type) { + case ErrEnvVar: + return v, err + default: + return v, dict.ErrKeyType{Key: key, Value: val, T: reflect.TypeOf(v)} + } + } + + v[k] = *ptr + } + } + } + + return v, nil +} + +func (d Dict) Map(key string) (r dict.Dicter, err error) { + v, ok := d[key] + if !ok { + // TODO(@ear7h): Revise this behavior, replicated from util/dict.Map + return Dict{}, nil + } + + r, ok = v.(Dict) + if !ok { + switch err.(type) { + case ErrEnvVar: + return r, err + default: + return r, dict.ErrKeyType{Key: key, Value: r, T: reflect.TypeOf(v)} + } + } + + return r, nil +} + +func (d Dict) MapSlice(key string) (r []dict.Dicter, err error) { + v, ok := d[key] + if !ok { + // TODO(@ear7h): Revise this behavior, replicated from util/dict.Map + return r, nil + } + + arr, ok := v.([]map[string]interface{}) + if !ok { + return r, dict.ErrKeyType{key, v, reflect.TypeOf(arr)} + } + + r = make([]dict.Dicter, len(arr)) + for k := range arr { + r[k] = dict.Dicter(Dict(arr[k])) + } + + return r, nil +} + +func (d Dict) Interface(key string) (v interface{}, ok bool) { + v, ok = d[key] + return v, ok +} diff --git a/internal/env/dict_test.go b/internal/env/dict_test.go new file mode 100644 index 000000000..b23147aeb --- /dev/null +++ b/internal/env/dict_test.go @@ -0,0 +1,384 @@ +package env_test + +import ( + "os" + "reflect" + "testing" + + "github.com/go-spatial/tegola/internal/env" +) + +func TestDict(t *testing.T) { + + type tcase struct { + dict env.Dict + key string + envVars map[string]string + expected interface{} + expectedErr error + } + + fn := func(t *testing.T, tc tcase) { + // setup our env vars + for k, v := range tc.envVars { + os.Setenv(k, v) + } + + // clean up env vars + defer (func() { + for k, _ := range tc.envVars { + os.Unsetenv(k) + } + })() + + var val interface{} + var err error + + switch tc.expected.(type) { + case string: + val, err = tc.dict.String(tc.key, nil) + case []string: + val, err = tc.dict.StringSlice(tc.key) + case bool: + val, err = tc.dict.Bool(tc.key, nil) + case []bool: + val, err = tc.dict.BoolSlice(tc.key) + case int: + val, err = tc.dict.Int(tc.key, nil) + case []int: + val, err = tc.dict.IntSlice(tc.key) + case uint: + val, err = tc.dict.Uint(tc.key, nil) + case []uint: + val, err = tc.dict.UintSlice(tc.key) + case float32, float64: + val, err = tc.dict.Float(tc.key, nil) + case []float64: + val, err = tc.dict.FloatSlice(tc.key) + case nil: + // ignore, used for checking errors + default: + t.Errorf("invalid type: %T", tc.expected) + return + } + + if tc.expectedErr != nil { + if err == nil { + t.Errorf("expected err %v, got nil", tc.expectedErr.Error()) + return + } + + // compare error messages + if tc.expectedErr.Error() != err.Error() { + t.Errorf("invalid error. expected %v, got %v", tc.expectedErr, err) + return + } + + return + } + if err != nil { + t.Errorf("unexpected err: %v", err) + return + } + + if !reflect.DeepEqual(val, tc.expected) { + t.Errorf("expected %v, got %v", tc.expected, val) + return + } + } + + tests := map[string]tcase{ + "string": { + dict: env.Dict{ + "string": "${TEST_STRING}", + }, + envVars: map[string]string{ + "TEST_STRING": "foo", + }, + key: "string", + expected: "foo", + }, + "string no env": { + dict: env.Dict{ + "string": "foo", + }, + key: "string", + expected: "foo", + }, + "string env not set": { + dict: env.Dict{ + "string": "${TEST_STRING}", + }, + key: "string", + expected: "", + expectedErr: env.ErrEnvVar("TEST_STRING"), + }, + "string slice": { + dict: env.Dict{ + "string_slice": "${TEST_STRING}", + }, + envVars: map[string]string{ + "TEST_STRING": "foo, bar", + }, + key: "string_slice", + expected: []string{"foo", "bar"}, + }, + "string slice no env": { + dict: env.Dict{ + "string_slice": []string{"foo", "bar", "baz"}, + }, + key: "string_slice", + expected: []string{"foo", "bar", "baz"}, + }, + "string slice concat no env": { + dict: env.Dict{ + "string_slice": "foo, bar, baz", + }, + key: "string_slice", + expected: []string{"foo", "bar", "baz"}, + }, + "string slice env not set": { + dict: env.Dict{ + "string_slice": "${TEST_STRING}", + }, + key: "string_slice", + expected: []string{""}, + expectedErr: env.ErrEnvVar("TEST_STRING"), + }, + "bool": { + dict: env.Dict{ + "bool": "${TEST_BOOL}", + }, + envVars: map[string]string{ + "TEST_BOOL": "true", + }, + key: "bool", + expected: true, + }, + "bool no env": { + dict: env.Dict{ + "bool": true, + }, + key: "bool", + expected: true, + }, + "bool env not set": { + dict: env.Dict{ + "bool": "${TEST_BOOL}", + }, + key: "bool", + expected: true, + expectedErr: env.ErrEnvVar("TEST_BOOL"), + }, + "bool slice": { + dict: env.Dict{ + "bool_slice": "${TEST_BOOL}", + }, + envVars: map[string]string{ + "TEST_BOOL": "true, false", + }, + key: "bool_slice", + expected: []bool{true, false}, + }, + "bool slice no env": { + dict: env.Dict{ + "bool_slice": []bool{true, false, true}, + }, + key: "bool_slice", + expected: []bool{true, false, true}, + }, + "bool slice concat no env": { + dict: env.Dict{ + "bool_slice": "true, false, true", + }, + key: "bool_slice", + expected: []bool{true, false, true}, + }, + "bool slice env not set": { + dict: env.Dict{ + "bool_slice": "${TEST_BOOL}", + }, + key: "bool_slice", + expected: []bool{true}, + expectedErr: env.ErrEnvVar("TEST_BOOL"), + }, + "int": { + dict: env.Dict{ + "int": "${TEST_INT}", + }, + envVars: map[string]string{ + "TEST_INT": "-1", + }, + key: "int", + expected: -1, + }, + "int no env": { + dict: env.Dict{ + "int": -1, + }, + key: "int", + expected: -1, + }, + "int env not set": { + dict: env.Dict{ + "int": "${TEST_INT}", + }, + key: "int", + expected: -1, + expectedErr: env.ErrEnvVar("TEST_INT"), + }, + "int slice": { + dict: env.Dict{ + "int_slice": "${TEST_INT_SLICE}", + }, + envVars: map[string]string{ + "TEST_INT_SLICE": "123, -324", + }, + key: "int_slice", + expected: []int{123, -324}, + }, + "int slice no env": { + dict: env.Dict{ + "int_slice": []int{43, -23, 12}, + }, + key: "int_slice", + expected: []int{43, -23, 12}, + }, + "int slice concat no env": { + dict: env.Dict{ + "int_slice": "43, -23, 12", + }, + key: "int_slice", + expected: []int{43, -23, 12}, + }, + "int slice env not set": { + dict: env.Dict{ + "int_slice": "${TEST_INT_SLICE}", + }, + key: "int_slice", + expected: []int{0}, + expectedErr: env.ErrEnvVar("TEST_INT_SLICE"), + }, + "uint": { + dict: env.Dict{ + "uint": "${TEST_UINT}", + }, + envVars: map[string]string{ + "TEST_UINT": "1", + }, + key: "uint", + expected: uint(1), + }, + "uint no env": { + dict: env.Dict{ + "uint": uint(1), + }, + key: "uint", + expected: uint(1), + }, + "uint env not set": { + dict: env.Dict{ + "uint": "${TEST_UINT}", + }, + key: "uint", + expected: uint(1), + expectedErr: env.ErrEnvVar("TEST_UINT"), + }, + "uint slice": { + dict: env.Dict{ + "uint_slice": "${TEST_UINT_SLICE}", + }, + envVars: map[string]string{ + "TEST_UINT_SLICE": "123, 324", + }, + key: "uint_slice", + expected: []uint{123, 324}, + }, + "uint slice no env": { + dict: env.Dict{ + "uint_slice": []uint{43, 23, 12}, + }, + key: "uint_slice", + expected: []uint{43, 23, 12}, + }, + "uint slice concat no env": { + dict: env.Dict{ + "uint_slice": "43, 23, 12", + }, + key: "uint_slice", + expected: []uint{43, 23, 12}, + }, + "uint slice env not set": { + dict: env.Dict{ + "uint_slice": "${TEST_UINT_SLICE}", + }, + key: "uint_slice", + expected: []uint{0}, + expectedErr: env.ErrEnvVar("TEST_UINT_SLICE"), + }, + "float": { + dict: env.Dict{ + "float": "${TEST_FLOAT}", + }, + envVars: map[string]string{ + "TEST_FLOAT": "1.0", + }, + key: "float", + expected: 1.0, + }, + "float no env": { + dict: env.Dict{ + "float": 1.0, + }, + key: "float", + expected: 1.0, + }, + "float env not set": { + dict: env.Dict{ + "float": "${TEST_FLOAT}", + }, + key: "float", + expected: 1.0, + expectedErr: env.ErrEnvVar("TEST_FLOAT"), + }, + "float slice": { + dict: env.Dict{ + "float_slice": "${TEST_FLOAT_SLICE}", + }, + envVars: map[string]string{ + "TEST_FLOAT_SLICE": "123.0, 324.0", + }, + key: "float_slice", + expected: []float64{123.0, 324.0}, + }, + "float slice no env": { + dict: env.Dict{ + "float_slice": []float64{43.0, 23.0, 12.0}, + }, + key: "float_slice", + expected: []float64{43.0, 23.0, 12.0}, + }, + "float slice concat no env": { + dict: env.Dict{ + "float_slice": "43.0, 23.0, 12.0", + }, + key: "float_slice", + expected: []float64{43.0, 23.0, 12.0}, + }, + "float slice env not set": { + dict: env.Dict{ + "float_slice": "${TEST_FLOAT_SLICE}", + }, + key: "float_slice", + expected: []float64{0.0}, + expectedErr: env.ErrEnvVar("TEST_FLOAT_SLICE"), + }, + } + + for name, tc := range tests { + tc := tc + t.Run(name, func(t *testing.T) { + fn(t, tc) + }) + } +} diff --git a/internal/env/parse.go b/internal/env/parse.go new file mode 100644 index 000000000..c7d998665 --- /dev/null +++ b/internal/env/parse.go @@ -0,0 +1,239 @@ +package env + +import ( + "strconv" + "strings" + + "github.com/go-spatial/tegola/internal/p" +) + +func ParseString(v interface{}) (*string, error) { + if v == nil { + return nil, nil + } + + switch val := v.(type) { + case string: + val, err := replaceEnvVar(val) + if err != nil { + return nil, err + } + return &val, nil + default: + return nil, ErrType{v} + } +} + +func ParseStringSlice(val string) ([]string, error) { + // replace the env vars + str, err := replaceEnvVar(val) + if err != nil { + return nil, err + } + + // split + vals := strings.Split(str, ",") + + // trim space + for i, v := range vals { + vals[i] = strings.TrimSpace(v) + } + + return vals, nil +} + +func ParseBool(v interface{}) (*bool, error) { + if v == nil { + return nil, nil + } + + switch val := v.(type) { + case bool: + return &val, nil + case string: + val, err := replaceEnvVar(val) + if err != nil { + return nil, err + } + + b, err := strconv.ParseBool(val) + return &b, err + default: + return nil, ErrType{v} + } +} + +func ParseBoolSlice(val string) ([]bool, error) { + // replace the env vars + str, err := replaceEnvVar(val) + if err != nil { + return nil, err + } + + // break our string up + vals := strings.Split(str, ",") + bools := make([]bool, 0, len(vals)) + for i := range vals { + // trim space and parse + b, err := strconv.ParseBool(strings.TrimSpace(vals[i])) + if err != nil { + return bools, err + } + + bools = append(bools, b) + } + + return bools, nil +} + +func ParseInt(v interface{}) (*int, error) { + if v == nil { + return nil, nil + } + + switch val := v.(type) { + case int: + return &val, nil + case int64: + i := int(val) + return &i, nil + case string: + val, err := replaceEnvVar(val) + if err != nil { + return nil, err + } + + i, err := strconv.Atoi(val) + return &i, err + default: + return nil, ErrType{v} + } +} + +func ParseIntSlice(val string) ([]int, error) { + // replace the env vars + str, err := replaceEnvVar(val) + if err != nil { + return nil, err + } + + // break our string up + vals := strings.Split(str, ",") + ints := make([]int, 0, len(vals)) + for i := range vals { + // trim space and parse + b, err := strconv.Atoi(strings.TrimSpace(vals[i])) + if err != nil { + return ints, err + } + + ints = append(ints, b) + } + + return ints, nil +} + +func ParseUint(v interface{}) (*uint, error) { + if v == nil { + return nil, nil + } + + switch val := v.(type) { + case uint: + return &val, nil + case uint64: + ui := uint(val) + return &ui, nil + case int: + if val < 0 { + return nil, ErrType{v} + } + return p.Uint(uint(val)), nil + case int64: + if val < 0 { + return nil, ErrType{v} + } + return p.Uint(uint(val)), nil + case string: + val, err := replaceEnvVar(val) + if err != nil { + return nil, err + } + + ui64, err := strconv.ParseUint(val, 10, 64) + ui := uint(ui64) + return &ui, err + default: + return nil, ErrType{v} + } +} + +func ParseUintSlice(val string) ([]uint, error) { + // replace the env vars + str, err := replaceEnvVar(val) + if err != nil { + return nil, err + } + + // break our string up + vals := strings.Split(str, ",") + uints := make([]uint, 0, len(vals)) + for i := range vals { + // trim space and parse + u, err := strconv.ParseUint(strings.TrimSpace(vals[i]), 10, 64) + if err != nil { + return uints, err + } + + uints = append(uints, uint(u)) + } + + return uints, nil +} + +func ParseFloat(v interface{}) (*float64, error) { + if v == nil { + return nil, nil + } + + switch val := v.(type) { + case float64: + return &val, nil + case float32: + f := float64(val) + return &f, nil + case string: + val, err := replaceEnvVar(val) + if err != nil { + return nil, err + } + + flt, err := strconv.ParseFloat(val, 64) + return &flt, err + default: + return nil, ErrType{v} + } +} + +func ParseFloatSlice(val string) ([]float64, error) { + // replace the env vars + str, err := replaceEnvVar(val) + if err != nil { + return nil, err + } + + // break our string up + vals := strings.Split(str, ",") + floats := make([]float64, 0, len(vals)) + for i := range vals { + // trim space and parse + f, err := strconv.ParseFloat(strings.TrimSpace(vals[i]), 64) + if err != nil { + return floats, err + } + + floats = append(floats, f) + } + + return floats, nil +} diff --git a/internal/env/types.go b/internal/env/types.go new file mode 100644 index 000000000..0c8b5deaf --- /dev/null +++ b/internal/env/types.go @@ -0,0 +1,149 @@ +package env + +import ( + "fmt" + "os" + "regexp" + "strings" +) + +// evnVarRegex matches a variable surrounded by curly braces with leading dollar sign. +// ex: ${MY_VAR} +var envVarRegex = regexp.MustCompile(`\${[A-Z]+[A-Z1-9_]*}`) + +// ErrEnvVar corresponds with a missing environment variable +type ErrEnvVar string + +func (e ErrEnvVar) Error() string { + return fmt.Sprintf("environment variable %q not found", string(e)) +} + +// ErrType corresponds with an incorrect type passed to UnmarshalTOML +type ErrType struct { + v interface{} +} + +func (te ErrType) Error() string { + return fmt.Sprintf("type %t could not be converted", te.v) +} + +// replaceEnvVars replaces environment variable placeholders in reader stream with values +func replaceEnvVar(in string) (string, error) { + // loop through all environment variable matches + for locs := envVarRegex.FindStringIndex(in); locs != nil; locs = envVarRegex.FindStringIndex(in) { + + // extract match from the input string + match := in[locs[0]:locs[1]] + + // trim the leading '${' and trailing '}' + varName := match[2 : len(match)-1] + + // get env var + envVar, ok := os.LookupEnv(varName) + if !ok { + return "", ErrEnvVar(varName) + } + + // update the input string with the env values + in = strings.Replace(in, match, envVar, -1) + } + + return in, nil +} + +//TODO(@ear7h): implement UnmarshalJSON for types + +type Bool bool + +func BoolPtr(v Bool) *Bool { + return &v +} + +func (t *Bool) UnmarshalTOML(v interface{}) error { + var b *bool + var err error + + b, err = ParseBool(v) + if err != nil { + return err + } + + *t = Bool(*b) + return nil +} + +type String string + +func StringPtr(v String) *String { + return &v +} + +func (t *String) UnmarshalTOML(v interface{}) error { + var s *string + var err error + + s, err = ParseString(v) + if err != nil { + return err + } + + *t = String(*s) + return nil +} + +type Int int + +func IntPtr(v Int) *Int { + return &v +} + +func (t *Int) UnmarshalTOML(v interface{}) error { + var i *int + var err error + + i, err = ParseInt(v) + if err != nil { + return err + } + + *t = Int(*i) + return nil +} + +type Uint uint + +func UintPtr(v Uint) *Uint { + return &v +} + +func (t *Uint) UnmarshalTOML(v interface{}) error { + var ui *uint + var err error + + ui, err = ParseUint(v) + if err != nil { + return err + } + + *t = Uint(*ui) + return nil +} + +type Float float64 + +func FloatPtr(v Float) *Float { + return &v +} + +func (t *Float) UnmarshalTOML(v interface{}) error { + var f *float64 + var err error + + f, err = ParseFloat(v) + if err != nil { + return err + } + + *t = Float(*f) + return nil +} diff --git a/internal/env/types_internal_test.go b/internal/env/types_internal_test.go new file mode 100644 index 000000000..07033d9c1 --- /dev/null +++ b/internal/env/types_internal_test.go @@ -0,0 +1,75 @@ +package env + +import ( + "os" + "testing" +) + +func TestReplaceEnvVar(t *testing.T) { + type tcase struct { + envVars map[string]string + in string + expected string + expectedErr error + } + + fn := func(t *testing.T, tc tcase) { + // setup our env vars + for k, v := range tc.envVars { + os.Setenv(k, v) + } + + // clean up env vars + defer (func() { + for k, _ := range tc.envVars { + os.Unsetenv(k) + } + })() + + out, err := replaceEnvVar(tc.in) + if tc.expectedErr != nil { + if err == nil { + t.Errorf("expected err %v, got nil", tc.expectedErr.Error()) + return + } + + // compare error messages + if tc.expectedErr.Error() != err.Error() { + t.Errorf("invalid error. expected %v, got %v", tc.expectedErr, err) + return + } + + return + } + if err != nil { + t.Errorf("unexpected err: %v", err) + return + } + + if out != tc.expected { + t.Errorf("expected %v, got %v", tc.expected, out) + return + } + } + + tests := map[string]tcase{ + "env": { + envVars: map[string]string{ + "TEST_STRING": "foo", + }, + in: "${TEST_STRING}", + expected: "foo", + }, + "env missing": { + in: "${TEST_STRING}", + expectedErr: ErrEnvVar("TEST_STRING"), + }, + } + + for name, tc := range tests { + tc := tc + t.Run(name, func(t *testing.T) { + fn(t, tc) + }) + } +} diff --git a/mvt/fuzz.go b/mvt/fuzz.go index 52a7b6237..c5b32e112 100644 --- a/mvt/fuzz.go +++ b/mvt/fuzz.go @@ -1,7 +1,6 @@ package mvt - func Fuzz(data []byte) int { - + return 0 } diff --git a/provider/debug/debug.go b/provider/debug/debug.go index 8e42a874b..2805af54a 100644 --- a/provider/debug/debug.go +++ b/provider/debug/debug.go @@ -9,6 +9,7 @@ import ( "github.com/go-spatial/geom" "github.com/go-spatial/tegola" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/provider" ) @@ -24,7 +25,7 @@ func init() { } // NewProvider Setups a debug provider. there are not currently any config params supported -func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { +func NewTileProvider(config dict.Dicter) (provider.Tiler, error) { return &Provider{}, nil } diff --git a/provider/gpkg/cgo_test.go b/provider/gpkg/cgo_test.go index f35942072..ccf9e15a9 100644 --- a/provider/gpkg/cgo_test.go +++ b/provider/gpkg/cgo_test.go @@ -5,12 +5,13 @@ package gpkg import ( "testing" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/provider" ) // This is a test to just see that the init function is doing something. func TestNewProviderStartup(t *testing.T) { - _, err := NewTileProvider(nil) + _, err := NewTileProvider(dict.Dict{}) if err == provider.ErrUnsupported { t.Fatalf("supported, expected any but unsupported got %v", err) } diff --git a/provider/gpkg/gpkg_register.go b/provider/gpkg/gpkg_register.go index 6088d131d..e54a8fd2b 100644 --- a/provider/gpkg/gpkg_register.go +++ b/provider/gpkg/gpkg_register.go @@ -15,9 +15,9 @@ import ( "github.com/go-spatial/geom" "github.com/go-spatial/tegola" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/internal/log" "github.com/go-spatial/tegola/provider" - "github.com/go-spatial/tegola/util/dict" ) func init() { @@ -180,11 +180,10 @@ func featureTableMetaData(gpkg *sql.DB) (map[string]featureTableDetails, error) return geomTableDetails, nil } -func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { - // parse our config - m := dict.M(config) +func NewTileProvider(config dict.Dicter) (provider.Tiler, error) { + log.Infof("%v", config) - filepath, err := m.String(ConfigKeyFilePath, nil) + filepath, err := config.String(ConfigKeyFilePath, nil) if err != nil { return nil, err } @@ -213,15 +212,13 @@ func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { db: db, } - layers, ok := config[ConfigKeyLayers].([]map[string]interface{}) - if !ok { - return nil, fmt.Errorf("expected %v to be a []map[string]interface{}", ConfigKeyLayers) + layers, err := config.MapSlice(ConfigKeyLayers) + if err != nil { + return nil, err } lyrsSeen := make(map[string]int) - for i, v := range layers { - - layerConf := dict.M(v) + for i, layerConf := range layers { layerName, err := layerConf.String(ConfigKeyLayerName, nil) if err != nil { @@ -237,12 +234,22 @@ func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { } lyrsSeen[layerName] = i - if layerConf[ConfigKeyTableName] == nil && layerConf[ConfigKeySQL] == nil { + // ensure only one of sql or tablename exist + _, errTable := layerConf.String(ConfigKeyTableName, nil) + if _, ok := errTable.(dict.ErrKeyRequired); errTable != nil && !ok { + return nil, err + } + _, errSQL := layerConf.String(ConfigKeySQL, nil) + if _, ok := errSQL.(dict.ErrKeyRequired); errSQL != nil && !ok { + return nil, err + } + // err != nil <-> key != exists + if errTable != nil && errSQL != nil { return nil, errors.New("'tablename' or 'sql' is required for a feature's config") } - - if layerConf[ConfigKeyTableName] != nil && layerConf[ConfigKeySQL] != nil { - return nil, errors.New("'tablename' or 'sql' is required for a feature's config. you have both") + // err == nil <-> key == exists + if errTable == nil && errSQL == nil { + return nil, errors.New("'tablename' or 'sql' is required for a feature's config") } idFieldname := DefaultIDFieldName @@ -252,8 +259,8 @@ func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { } tagFieldnames, err := layerConf.StringSlice(ConfigKeyFields) - if err != nil { - return nil, fmt.Errorf("for layer (%v) %v %v field had the following error: %v", i, layerName, ConfigKeyFields, err) + if err != nil { // empty slices are okay + return nil, fmt.Errorf("for layer (%v) %v, %q field had the following error: %v", i, layerName, ConfigKeyFields, err) } // layer container. will be added to the provider after it's configured @@ -261,7 +268,7 @@ func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { name: layerName, } - if layerConf[ConfigKeyTableName] != nil { + if errTable == nil { // layerConf[ConfigKeyTableName] exists tablename, err := layerConf.String(ConfigKeyTableName, &idFieldname) if err != nil { return nil, fmt.Errorf("for layer (%v) %v : %v", i, layerName, err) @@ -275,7 +282,7 @@ func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { layer.srid = geomTableDetails[tablename].srid layer.bbox = *geomTableDetails[tablename].bbox - } else { + } else { // layerConf[ConfigKeySQL] exists var customSQL string customSQL, err = layerConf.String(ConfigKeySQL, &customSQL) if err != nil { diff --git a/provider/gpkg/gpkg_register_internal_test.go b/provider/gpkg/gpkg_register_internal_test.go index e61f88d14..0f0aa961d 100644 --- a/provider/gpkg/gpkg_register_internal_test.go +++ b/provider/gpkg/gpkg_register_internal_test.go @@ -10,6 +10,8 @@ import ( "github.com/go-spatial/geom" "github.com/go-spatial/geom/cmp" + "github.com/go-spatial/tegola/internal/dict" + _ "github.com/mattn/go-sqlite3" ) @@ -1021,7 +1023,7 @@ func TestFeatureTableMetaData(t *testing.T) { func TestCleanup(t *testing.T) { type tcase struct { - config map[string]interface{} + config dict.Dict } fn := func(t *testing.T, tc tcase) { diff --git a/provider/gpkg/gpkg_test.go b/provider/gpkg/gpkg_test.go index ab5da61c9..775b5cc25 100644 --- a/provider/gpkg/gpkg_test.go +++ b/provider/gpkg/gpkg_test.go @@ -13,6 +13,7 @@ import ( "github.com/go-spatial/geom" "github.com/go-spatial/tegola" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/provider" "github.com/go-spatial/tegola/provider/gpkg" ) @@ -174,7 +175,7 @@ func TestAutoConfig(t *testing.T) { func TestNewTileProvider(t *testing.T) { type tcase struct { - config map[string]interface{} + config dict.Dict expectedLayerCount int expectedErr error } @@ -253,7 +254,7 @@ func (t *MockTile) ZXY() (uint, uint, uint) { return t.Z, t.X, t.Y } func TestTileFeatures(t *testing.T) { type tcase struct { - config map[string]interface{} + config dict.Dict layerName string tile MockTile expectedFeatureCount int @@ -387,7 +388,7 @@ func TestTileFeatures(t *testing.T) { func TestConfigs(t *testing.T) { type tcase struct { - config map[string]interface{} + config dict.Dict tile MockTile layerName string expectedTags map[uint64]map[string]interface{} @@ -431,7 +432,7 @@ func TestConfigs(t *testing.T) { tests := map[string]tcase{ "expecting tags": { - config: map[string]interface{}{ + config: dict.Dict{ "filepath": GPKGAthensFilePath, "layers": []map[string]interface{}{ {"name": "a_points", "tablename": "amenities_points", "id_fieldname": "fid", "fields": []string{"amenity", "religion", "tourism", "shop"}}, @@ -463,7 +464,7 @@ func TestConfigs(t *testing.T) { }, }, "no tags provided": { - config: map[string]interface{}{ + config: dict.Dict{ "filepath": GPKGAthensFilePath, "layers": []map[string]interface{}{ {"name": "a_points", "tablename": "amenities_points", "id_fieldname": "fid", "fields": []string{"amenity", "religion", "tourism", "shop"}}, @@ -484,7 +485,7 @@ func TestConfigs(t *testing.T) { }, }, "simple sql": { - config: map[string]interface{}{ + config: dict.Dict{ "filepath": GPKGAthensFilePath, "layers": []map[string]interface{}{ { @@ -517,7 +518,7 @@ func TestConfigs(t *testing.T) { }, }, "complex sql": { - config: map[string]interface{}{ + config: dict.Dict{ "filepath": GPKGAthensFilePath, "layers": []map[string]interface{}{ { @@ -567,7 +568,7 @@ func TestConfigs(t *testing.T) { func TestOpenNonExistantFile(t *testing.T) { type tcase struct { - config map[string]interface{} + config dict.Dict err error } const ( @@ -578,14 +579,14 @@ func TestOpenNonExistantFile(t *testing.T) { tests := map[string]tcase{ "empty": tcase{ - config: map[string]interface{}{ + config: dict.Dict{ gpkg.ConfigKeyFilePath: "", }, err: gpkg.ErrInvalidFilePath{FilePath: ""}, }, "nonexistance": tcase{ // should not exists - config: map[string]interface{}{ + config: dict.Dict{ gpkg.ConfigKeyFilePath: NONEXISTANTFILE, }, err: gpkg.ErrInvalidFilePath{FilePath: NONEXISTANTFILE}, diff --git a/provider/postgis/postgis.go b/provider/postgis/postgis.go index f5c2d253d..595013c22 100644 --- a/provider/postgis/postgis.go +++ b/provider/postgis/postgis.go @@ -1,6 +1,7 @@ package postgis import ( + "context" "fmt" "log" "os" @@ -9,14 +10,13 @@ import ( "github.com/jackc/pgx" - "context" - "github.com/go-spatial/geom" "github.com/go-spatial/geom/encoding/wkb" "github.com/go-spatial/geom/slippy" "github.com/go-spatial/tegola" "github.com/go-spatial/tegola/provider" - "github.com/go-spatial/tegola/util/dict" + + "github.com/go-spatial/tegola/internal/dict" ) const Name = "postgis" @@ -91,42 +91,40 @@ func init() { // !BBOX! - [Required] will be replaced with the bounding box of the tile before the query is sent to the database. // !ZOOM! - [Optional] will be replaced with the "Z" (zoom) value of the requested tile. // -func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { - // Validate the config to make sure it has the values I care about and the types for those values. - c := dict.M(config) +func NewTileProvider(config dict.Dicter) (provider.Tiler, error) { - host, err := c.String(ConfigKeyHost, nil) + host, err := config.String(ConfigKeyHost, nil) if err != nil { return nil, err } - db, err := c.String(ConfigKeyDB, nil) + db, err := config.String(ConfigKeyDB, nil) if err != nil { return nil, err } - user, err := c.String(ConfigKeyUser, nil) + user, err := config.String(ConfigKeyUser, nil) if err != nil { return nil, err } - password, err := c.String(ConfigKeyPassword, nil) + password, err := config.String(ConfigKeyPassword, nil) if err != nil { return nil, err } - port := int64(DefaultPort) - if port, err = c.Int64(ConfigKeyPort, &port); err != nil { + port := DefaultPort + if port, err = config.Int(ConfigKeyPort, &port); err != nil { return nil, err } - maxcon := int64(DefaultMaxConn) - if maxcon, err = c.Int64(ConfigKeyMaxConn, &maxcon); err != nil { + maxcon := DefaultMaxConn + if maxcon, err = config.Int(ConfigKeyMaxConn, &maxcon); err != nil { return nil, err } - var srid = int64(DefaultSRID) - if srid, err = c.Int64(ConfigKeySRID, &srid); err != nil { + var srid = DefaultSRID + if srid, err = config.Int(ConfigKeySRID, &srid); err != nil { return nil, err } @@ -148,18 +146,17 @@ func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { return nil, fmt.Errorf("Failed while creating connection pool: %v", err) } - layers, ok := c[ConfigKeyLayers].([]map[string]interface{}) - if !ok { - return nil, fmt.Errorf("Expected %v to be a []map[string]interface{}", ConfigKeyLayers) + layers, err := config.MapSlice(ConfigKeyLayers) + if err != nil { + return nil, err } lyrs := make(map[string]Layer) lyrsSeen := make(map[string]int) - for i, v := range layers { - vc := dict.M(v) + for i, layer := range layers { - lname, err := vc.String(ConfigKeyLayerName, nil) + lname, err := layer.String(ConfigKeyLayerName, nil) if err != nil { return nil, fmt.Errorf("For layer (%v) we got the following error trying to get the layer's name field: %v", i, err) } @@ -171,19 +168,19 @@ func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { p.firstlayer = lname } - fields, err := vc.StringSlice(ConfigKeyFields) + fields, err := layer.StringSlice(ConfigKeyFields) if err != nil { return nil, fmt.Errorf("For layer (%v) %v %v field had the following error: %v", i, lname, ConfigKeyFields, err) } geomfld := "geom" - geomfld, err = vc.String(ConfigKeyGeomField, &geomfld) + geomfld, err = layer.String(ConfigKeyGeomField, &geomfld) if err != nil { return nil, fmt.Errorf("For layer (%v) %v : %v", i, lname, err) } idfld := "gid" - idfld, err = vc.String(ConfigKeyGeomIDField, &idfld) + idfld, err = layer.String(ConfigKeyGeomIDField, &idfld) if err != nil { return nil, fmt.Errorf("For layer (%v) %v : %v", i, lname, err) } @@ -192,13 +189,13 @@ func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { } var tblName string - tblName, err = vc.String(ConfigKeyTablename, &lname) + tblName, err = layer.String(ConfigKeyTablename, &lname) if err != nil { return nil, fmt.Errorf("for %v layer(%v) %v has an error: %v", i, lname, ConfigKeyTablename, err) } var sql string - sql, err = vc.String(ConfigKeySQL, &sql) + sql, err = layer.String(ConfigKeySQL, &sql) if err != nil { return nil, fmt.Errorf("for %v layer(%v) %v has an error: %v", i, lname, ConfigKeySQL, err) } @@ -208,7 +205,7 @@ func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { } var lsrid = srid - if lsrid, err = vc.Int64(ConfigKeySRID, &lsrid); err != nil { + if lsrid, err = layer.Int(ConfigKeySRID, &lsrid); err != nil { return nil, err } @@ -218,6 +215,7 @@ func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { geomField: geomfld, srid: uint64(lsrid), } + if sql != "" { // make sure that the sql has a !BBOX! token if !strings.Contains(sql, bboxToken) { diff --git a/provider/postgis/postgis_internal_test.go b/provider/postgis/postgis_internal_test.go index 36ae3f022..7cbe22d18 100644 --- a/provider/postgis/postgis_internal_test.go +++ b/provider/postgis/postgis_internal_test.go @@ -7,26 +7,27 @@ import ( "testing" "github.com/go-spatial/geom" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/internal/ttools" ) // TESTENV is the environment variable that must be set to "yes" to run postgis tests. const TESTENV = "RUN_POSTGIS_TESTS" -func GetTestPort(t *testing.T) int64 { +func GetTestPort(t *testing.T) int { ttools.ShouldSkip(t, TESTENV) - port, err := strconv.ParseInt(os.Getenv("PGPORT"), 10, 64) + port, err := strconv.ParseInt(os.Getenv("PGPORT"), 10, 32) if err != nil { t.Skipf("err parsing PGPORT: %v", err) } - return port + return int(port) } func TestLayerGeomType(t *testing.T) { port := GetTestPort(t) type tcase struct { - config map[string]interface{} + config dict.Dict layerName string geom geom.Geometry } diff --git a/provider/postgis/postgis_test.go b/provider/postgis/postgis_test.go index dfae82904..cf906ca4c 100644 --- a/provider/postgis/postgis_test.go +++ b/provider/postgis/postgis_test.go @@ -8,6 +8,7 @@ import ( "github.com/go-spatial/geom/slippy" "github.com/go-spatial/tegola" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/provider" "github.com/go-spatial/tegola/provider/postgis" ) @@ -16,7 +17,7 @@ func TestNewTileProvider(t *testing.T) { port := postgis.GetTestPort(t) type tcase struct { - config map[string]interface{} + config dict.Dict } fn := func(t *testing.T, tc tcase) { @@ -29,7 +30,7 @@ func TestNewTileProvider(t *testing.T) { tests := map[string]tcase{ "1": { - config: map[string]interface{}{ + config: dict.Dict{ postgis.ConfigKeyHost: os.Getenv("PGHOST"), postgis.ConfigKeyPort: port, postgis.ConfigKeyDB: os.Getenv("PGDATABASE"), @@ -55,7 +56,7 @@ func TestTileFeatures(t *testing.T) { port := postgis.GetTestPort(t) type tcase struct { - config map[string]interface{} + config dict.Dict tile *slippy.Tile expectedFeatureCount int } @@ -91,7 +92,7 @@ func TestTileFeatures(t *testing.T) { tests := map[string]tcase{ "land query": { - config: map[string]interface{}{ + config: dict.Dict{ postgis.ConfigKeyHost: os.Getenv("PGHOST"), postgis.ConfigKeyPort: port, postgis.ConfigKeyDB: os.Getenv("PGDATABASE"), @@ -108,7 +109,7 @@ func TestTileFeatures(t *testing.T) { expectedFeatureCount: 4032, }, "scalerank test": { - config: map[string]interface{}{ + config: dict.Dict{ postgis.ConfigKeyHost: os.Getenv("PGHOST"), postgis.ConfigKeyPort: port, postgis.ConfigKeyDB: os.Getenv("PGDATABASE"), @@ -125,7 +126,7 @@ func TestTileFeatures(t *testing.T) { expectedFeatureCount: 98, }, "decode numeric(x,x) types": { - config: map[string]interface{}{ + config: dict.Dict{ postgis.ConfigKeyHost: os.Getenv("PGHOST"), postgis.ConfigKeyPort: port, postgis.ConfigKeyDB: os.Getenv("PGDATABASE"), diff --git a/provider/provider.go b/provider/provider.go index 1bf13f806..5037dfc71 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/go-spatial/geom" + "github.com/go-spatial/tegola/internal/dict" "github.com/go-spatial/tegola/internal/log" ) @@ -33,7 +34,7 @@ type LayerInfo interface { } // InitFunc initilize a provider given a config map. The init function should validate the config map, and report any errors. This is called by the For function. -type InitFunc func(map[string]interface{}) (Tiler, error) +type InitFunc func(dicter dict.Dicter) (Tiler, error) // CleanupFunc is called to when the system is shuting down, this allows the provider to cleanup. type CleanupFunc func() @@ -78,7 +79,7 @@ func Drivers() (l []string) { } // For function returns a configured provider of the given type, provided the correct config map. -func For(name string, config map[string]interface{}) (Tiler, error) { +func For(name string, config dict.Dicter) (Tiler, error) { if providers == nil { return nil, fmt.Errorf("no providers registered") } diff --git a/provider/test/provider.go b/provider/test/provider.go index 5da937a9f..f36f9bb76 100644 --- a/provider/test/provider.go +++ b/provider/test/provider.go @@ -6,6 +6,8 @@ import ( "github.com/go-spatial/geom" "github.com/go-spatial/tegola" "github.com/go-spatial/tegola/provider" + + "github.com/go-spatial/tegola/internal/dict" ) const Name = "test" @@ -17,7 +19,7 @@ func init() { } // NewProvider setups a test provider. there are not currently any config params supported -func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { +func NewTileProvider(config dict.Dicter) (provider.Tiler, error) { Count++ return &TileProvider{}, nil } diff --git a/server/bindata/bindata.go b/server/bindata/bindata.go index d5bc3f21a..eace9ce88 100644 --- a/server/bindata/bindata.go +++ b/server/bindata/bindata.go @@ -287,14 +287,14 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "static/.gitignore": staticGitignore, - "static/css/tegola.css": staticCssTegolaCss, - "static/favicon.ico": staticFaviconIco, - "static/index.html": staticIndexHtml, - "static/js/tegola.js": staticJsTegolaJs, + "static/.gitignore": staticGitignore, + "static/css/tegola.css": staticCssTegolaCss, + "static/favicon.ico": staticFaviconIco, + "static/index.html": staticIndexHtml, + "static/js/tegola.js": staticJsTegolaJs, "static/lib/mapbox-gl-js/v0.41.0/mapbox-gl.css": staticLibMapboxGlJsV0410MapboxGlCss, - "static/lib/mapbox-gl-js/v0.41.0/mapbox-gl.js": staticLibMapboxGlJsV0410MapboxGlJs, - "static/lib/vue/v2.3.0/vue.min.js": staticLibVueV230VueMinJs, + "static/lib/mapbox-gl-js/v0.41.0/mapbox-gl.js": staticLibMapboxGlJsV0410MapboxGlJs, + "static/lib/vue/v2.3.0/vue.min.js": staticLibVueV230VueMinJs, } // AssetDir returns the file names below a certain @@ -336,6 +336,7 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } + var _bintree = &bintree{nil, map[string]*bintree{ "static": &bintree{nil, map[string]*bintree{ ".gitignore": &bintree{staticGitignore, map[string]*bintree{}}, @@ -343,7 +344,7 @@ var _bintree = &bintree{nil, map[string]*bintree{ "tegola.css": &bintree{staticCssTegolaCss, map[string]*bintree{}}, }}, "favicon.ico": &bintree{staticFaviconIco, map[string]*bintree{}}, - "index.html": &bintree{staticIndexHtml, map[string]*bintree{}}, + "index.html": &bintree{staticIndexHtml, map[string]*bintree{}}, "js": &bintree{nil, map[string]*bintree{ "tegola.js": &bintree{staticJsTegolaJs, map[string]*bintree{}}, }}, @@ -351,7 +352,7 @@ var _bintree = &bintree{nil, map[string]*bintree{ "mapbox-gl-js": &bintree{nil, map[string]*bintree{ "v0.41.0": &bintree{nil, map[string]*bintree{ "mapbox-gl.css": &bintree{staticLibMapboxGlJsV0410MapboxGlCss, map[string]*bintree{}}, - "mapbox-gl.js": &bintree{staticLibMapboxGlJsV0410MapboxGlJs, map[string]*bintree{}}, + "mapbox-gl.js": &bintree{staticLibMapboxGlJsV0410MapboxGlJs, map[string]*bintree{}}, }}, }}, "vue": &bintree{nil, map[string]*bintree{ @@ -409,4 +410,3 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } - diff --git a/util/dict/gen.go b/util/dict/gen.go deleted file mode 100644 index 8aa709a06..000000000 --- a/util/dict/gen.go +++ /dev/null @@ -1,2 +0,0 @@ -//go:generate ./gen.sh -package dict diff --git a/util/dict/gen.pl b/util/dict/gen.pl deleted file mode 100755 index f1c9ac90f..000000000 --- a/util/dict/gen.pl +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/perl -use strict; -use warnings; -use 5.10.0; - -my @types = qw(string int uint); -push @types, "int$_", "uint$_" for (qw(8 16 32 64)); - -say < map.go && goimports -w map.go \ No newline at end of file diff --git a/util/dict/map.go b/util/dict/map.go deleted file mode 100644 index 86b30dfba..000000000 --- a/util/dict/map.go +++ /dev/null @@ -1,539 +0,0 @@ -/* This file was generated using gen.pl and go fmt. */ -// dict is a helper function that allow one to easily get concreate values out of a map[string]interface{} -package dict - -import "fmt" - -type M map[string]interface{} - -// Dict is to obtain a map[string]interface{} that has already been cast to a M type. -func (m M) Dict(key string) (v M, err error) { - var val interface{} - var mv map[string]interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, fmt.Errorf("%v value is required.", key) - } - - if mv, ok = val.(map[string]interface{}); !ok { - return v, fmt.Errorf("%v value needs to be of type map[string]interface{}.", key) - } - return M(mv), nil -} - -// String returns the value as a string type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. -func (m M) String(key string, def *string) (v string, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok || val == nil { - if def != nil { - return *def, nil - } - return v, fmt.Errorf("%v value is required.", key) - } - - switch placeholder := val.(type) { - case string: - v = placeholder - case *string: - v = *placeholder - default: - return v, fmt.Errorf("%v value needs to be of type string. Value is of type %T", key, val) - } - return v, nil -} - -func (m M) StringSlice(key string) (v []string, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, nil - } - if v, ok = val.([]string); !ok { - // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate - // type first, and then into the this type. - var iv []interface{} - if iv, ok = val.([]interface{}); !ok { - // Could not convert to the generic type, so we don't have the correct thing. - return v, fmt.Errorf("%v value needs to be of type []string. Value is of type %T", key, val) - } - for _, value := range iv { - vt, ok := value.(string) - if !ok { - return v, fmt.Errorf("%v value needs to be of type []string. Value is of type %T", key, val) - } - v = append(v, vt) - } - } - return v, nil -} - -// Int returns the value as a int type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. -func (m M) Int(key string, def *int) (v int, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok || val == nil { - if def != nil { - return *def, nil - } - return v, fmt.Errorf("%v value is required.", key) - } - - switch placeholder := val.(type) { - case int: - v = placeholder - case *int: - v = *placeholder - default: - return v, fmt.Errorf("%v value needs to be of type int. Value is of type %T", key, val) - } - return v, nil -} - -func (m M) IntSlice(key string) (v []int, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, nil - } - if v, ok = val.([]int); !ok { - // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate - // type first, and then into the this type. - var iv []interface{} - if iv, ok = val.([]interface{}); !ok { - // Could not convert to the generic type, so we don't have the correct thing. - return v, fmt.Errorf("%v value needs to be of type []int. Value is of type %T", key, val) - } - for _, value := range iv { - vt, ok := value.(int) - if !ok { - return v, fmt.Errorf("%v value needs to be of type []int. Value is of type %T", key, val) - } - v = append(v, vt) - } - } - return v, nil -} - -// Uint returns the value as a uint type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. -func (m M) Uint(key string, def *uint) (v uint, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok || val == nil { - if def != nil { - return *def, nil - } - return v, fmt.Errorf("%v value is required.", key) - } - - switch placeholder := val.(type) { - case uint: - v = placeholder - case *uint: - v = *placeholder - default: - return v, fmt.Errorf("%v value needs to be of type uint. Value is of type %T", key, val) - } - return v, nil -} - -func (m M) UintSlice(key string) (v []uint, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, nil - } - if v, ok = val.([]uint); !ok { - // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate - // type first, and then into the this type. - var iv []interface{} - if iv, ok = val.([]interface{}); !ok { - // Could not convert to the generic type, so we don't have the correct thing. - return v, fmt.Errorf("%v value needs to be of type []uint. Value is of type %T", key, val) - } - for _, value := range iv { - vt, ok := value.(uint) - if !ok { - return v, fmt.Errorf("%v value needs to be of type []uint. Value is of type %T", key, val) - } - v = append(v, vt) - } - } - return v, nil -} - -// Int8 returns the value as a int8 type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. -func (m M) Int8(key string, def *int8) (v int8, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok || val == nil { - if def != nil { - return *def, nil - } - return v, fmt.Errorf("%v value is required.", key) - } - - switch placeholder := val.(type) { - case int8: - v = placeholder - case *int8: - v = *placeholder - default: - return v, fmt.Errorf("%v value needs to be of type int8. Value is of type %T", key, val) - } - return v, nil -} - -func (m M) Int8Slice(key string) (v []int8, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, nil - } - if v, ok = val.([]int8); !ok { - // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate - // type first, and then into the this type. - var iv []interface{} - if iv, ok = val.([]interface{}); !ok { - // Could not convert to the generic type, so we don't have the correct thing. - return v, fmt.Errorf("%v value needs to be of type []int8. Value is of type %T", key, val) - } - for _, value := range iv { - vt, ok := value.(int8) - if !ok { - return v, fmt.Errorf("%v value needs to be of type []int8. Value is of type %T", key, val) - } - v = append(v, vt) - } - } - return v, nil -} - -// Uint8 returns the value as a uint8 type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. -func (m M) Uint8(key string, def *uint8) (v uint8, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok || val == nil { - if def != nil { - return *def, nil - } - return v, fmt.Errorf("%v value is required.", key) - } - - switch placeholder := val.(type) { - case uint8: - v = placeholder - case *uint8: - v = *placeholder - default: - return v, fmt.Errorf("%v value needs to be of type uint8. Value is of type %T", key, val) - } - return v, nil -} - -func (m M) Uint8Slice(key string) (v []uint8, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, nil - } - if v, ok = val.([]uint8); !ok { - // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate - // type first, and then into the this type. - var iv []interface{} - if iv, ok = val.([]interface{}); !ok { - // Could not convert to the generic type, so we don't have the correct thing. - return v, fmt.Errorf("%v value needs to be of type []uint8. Value is of type %T", key, val) - } - for _, value := range iv { - vt, ok := value.(uint8) - if !ok { - return v, fmt.Errorf("%v value needs to be of type []uint8. Value is of type %T", key, val) - } - v = append(v, vt) - } - } - return v, nil -} - -// Int16 returns the value as a int16 type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. -func (m M) Int16(key string, def *int16) (v int16, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok || val == nil { - if def != nil { - return *def, nil - } - return v, fmt.Errorf("%v value is required.", key) - } - - switch placeholder := val.(type) { - case int16: - v = placeholder - case *int16: - v = *placeholder - default: - return v, fmt.Errorf("%v value needs to be of type int16. Value is of type %T", key, val) - } - return v, nil -} - -func (m M) Int16Slice(key string) (v []int16, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, nil - } - if v, ok = val.([]int16); !ok { - // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate - // type first, and then into the this type. - var iv []interface{} - if iv, ok = val.([]interface{}); !ok { - // Could not convert to the generic type, so we don't have the correct thing. - return v, fmt.Errorf("%v value needs to be of type []int16. Value is of type %T", key, val) - } - for _, value := range iv { - vt, ok := value.(int16) - if !ok { - return v, fmt.Errorf("%v value needs to be of type []int16. Value is of type %T", key, val) - } - v = append(v, vt) - } - } - return v, nil -} - -// Uint16 returns the value as a uint16 type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. -func (m M) Uint16(key string, def *uint16) (v uint16, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok || val == nil { - if def != nil { - return *def, nil - } - return v, fmt.Errorf("%v value is required.", key) - } - - switch placeholder := val.(type) { - case uint16: - v = placeholder - case *uint16: - v = *placeholder - default: - return v, fmt.Errorf("%v value needs to be of type uint16. Value is of type %T", key, val) - } - return v, nil -} - -func (m M) Uint16Slice(key string) (v []uint16, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, nil - } - if v, ok = val.([]uint16); !ok { - // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate - // type first, and then into the this type. - var iv []interface{} - if iv, ok = val.([]interface{}); !ok { - // Could not convert to the generic type, so we don't have the correct thing. - return v, fmt.Errorf("%v value needs to be of type []uint16. Value is of type %T", key, val) - } - for _, value := range iv { - vt, ok := value.(uint16) - if !ok { - return v, fmt.Errorf("%v value needs to be of type []uint16. Value is of type %T", key, val) - } - v = append(v, vt) - } - } - return v, nil -} - -// Int32 returns the value as a int32 type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. -func (m M) Int32(key string, def *int32) (v int32, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok || val == nil { - if def != nil { - return *def, nil - } - return v, fmt.Errorf("%v value is required.", key) - } - - switch placeholder := val.(type) { - case int32: - v = placeholder - case *int32: - v = *placeholder - default: - return v, fmt.Errorf("%v value needs to be of type int32. Value is of type %T", key, val) - } - return v, nil -} - -func (m M) Int32Slice(key string) (v []int32, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, nil - } - if v, ok = val.([]int32); !ok { - // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate - // type first, and then into the this type. - var iv []interface{} - if iv, ok = val.([]interface{}); !ok { - // Could not convert to the generic type, so we don't have the correct thing. - return v, fmt.Errorf("%v value needs to be of type []int32. Value is of type %T", key, val) - } - for _, value := range iv { - vt, ok := value.(int32) - if !ok { - return v, fmt.Errorf("%v value needs to be of type []int32. Value is of type %T", key, val) - } - v = append(v, vt) - } - } - return v, nil -} - -// Uint32 returns the value as a uint32 type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. -func (m M) Uint32(key string, def *uint32) (v uint32, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok || val == nil { - if def != nil { - return *def, nil - } - return v, fmt.Errorf("%v value is required.", key) - } - - switch placeholder := val.(type) { - case uint32: - v = placeholder - case *uint32: - v = *placeholder - default: - return v, fmt.Errorf("%v value needs to be of type uint32. Value is of type %T", key, val) - } - return v, nil -} - -func (m M) Uint32Slice(key string) (v []uint32, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, nil - } - if v, ok = val.([]uint32); !ok { - // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate - // type first, and then into the this type. - var iv []interface{} - if iv, ok = val.([]interface{}); !ok { - // Could not convert to the generic type, so we don't have the correct thing. - return v, fmt.Errorf("%v value needs to be of type []uint32. Value is of type %T", key, val) - } - for _, value := range iv { - vt, ok := value.(uint32) - if !ok { - return v, fmt.Errorf("%v value needs to be of type []uint32. Value is of type %T", key, val) - } - v = append(v, vt) - } - } - return v, nil -} - -// Int64 returns the value as a int64 type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. -func (m M) Int64(key string, def *int64) (v int64, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok || val == nil { - if def != nil { - return *def, nil - } - return v, fmt.Errorf("%v value is required.", key) - } - - switch placeholder := val.(type) { - case int64: - v = placeholder - case *int64: - v = *placeholder - default: - return v, fmt.Errorf("%v value needs to be of type int64. Value is of type %T", key, val) - } - return v, nil -} - -func (m M) Int64Slice(key string) (v []int64, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, nil - } - if v, ok = val.([]int64); !ok { - // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate - // type first, and then into the this type. - var iv []interface{} - if iv, ok = val.([]interface{}); !ok { - // Could not convert to the generic type, so we don't have the correct thing. - return v, fmt.Errorf("%v value needs to be of type []int64. Value is of type %T", key, val) - } - for _, value := range iv { - vt, ok := value.(int64) - if !ok { - return v, fmt.Errorf("%v value needs to be of type []int64. Value is of type %T", key, val) - } - v = append(v, vt) - } - } - return v, nil -} - -// Uint64 returns the value as a uint64 type, if it is unable to convert the value it will error. If the default value is not provided, and it can not find the value, it will return the zero value, and an error. -func (m M) Uint64(key string, def *uint64) (v uint64, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok || val == nil { - if def != nil { - return *def, nil - } - return v, fmt.Errorf("%v value is required.", key) - } - - switch placeholder := val.(type) { - case uint64: - v = placeholder - case *uint64: - v = *placeholder - default: - return v, fmt.Errorf("%v value needs to be of type uint64. Value is of type %T", key, val) - } - return v, nil -} - -func (m M) Uint64Slice(key string) (v []uint64, err error) { - var val interface{} - var ok bool - if val, ok = m[key]; !ok { - return v, nil - } - if v, ok = val.([]uint64); !ok { - // It's possible that the value is of type []interface and not of our type, so we need to convert each element to the appropriate - // type first, and then into the this type. - var iv []interface{} - if iv, ok = val.([]interface{}); !ok { - // Could not convert to the generic type, so we don't have the correct thing. - return v, fmt.Errorf("%v value needs to be of type []uint64. Value is of type %T", key, val) - } - for _, value := range iv { - vt, ok := value.(uint64) - if !ok { - return v, fmt.Errorf("%v value needs to be of type []uint64. Value is of type %T", key, val) - } - v = append(v, vt) - } - } - return v, nil -} diff --git a/util/dict/map_test.go b/util/dict/map_test.go deleted file mode 100644 index 332e0843c..000000000 --- a/util/dict/map_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package dict - -import ( - "fmt" - "testing" -) - -func TestMPrimatives(t *testing.T) { - // go does not allow pointer literals - fooString := "an eloquent quote" - fooInt := 8675309 - - testcases := map[string]struct { - m M - test func(m M) (interface{}, error) - expected interface{} - err error - }{ - "string 1": { - m: M{"key1": "robpike"}, - test: func(m M) (interface{}, error) { - def := "default" - return m.String("key1", &def) - }, - expected: "robpike", - err: nil, - }, - "string 2": { - m: M{}, - test: func(m M) (interface{}, error) { - def := "default" - return m.String("key1", &def) - }, - expected: "default", - err: nil, - }, - "string ptr 1": { - m: M{"key1": &fooString}, - test: func(m M) (interface{}, error) { - def := "default" - return m.String("key1", &def) - }, - expected: fooString, - err: nil, - }, - "int 1": { - m: M{"key1": 1970}, - test: func(m M) (interface{}, error) { - def := 2018 - return m.Int("key1", &def) - }, - expected: 1970, - err: nil, - }, - "int ptr 1": { - m: M{"key1": &fooInt}, - test: func(m M) (interface{}, error) { - def := 2018 - return m.Int("key1", &def) - }, - expected: fooInt, - err: nil, - }, - "error 1": { - m: M{"key1": "stringy"}, - test: func(m M) (interface{}, error) { - def := 42 - return m.Int("key1", &def) - }, - expected: 0, - err: fmt.Errorf("key1 value needs to be of type int. Value is of type string"), - }, - } - - for k, tc := range testcases { - res, err := tc.test(tc.m) - if err != nil && tc.err == nil { - t.Fatalf("[%v] unexpected error %v", k, err) - } - - if err == nil && tc.err != nil { - t.Fatalf("[%v] unexpected return value %v, expected error %v", k, res, tc.err) - } - - if tc.err != nil { - if tc.err.Error() != err.Error() { - t.Fatalf("[%v] unexpected error %v, expected %v", k, err, tc.err) - } - continue - } - - if res != tc.expected { - t.Fatalf("[%v] incorrect return value expected %v, got %v", k, tc.expected, res) - } - } -}