diff --git a/.drone.yml b/.drone.yml index 19cc1d9..94b1481 100644 --- a/.drone.yml +++ b/.drone.yml @@ -72,3 +72,22 @@ steps: - cd provider/ini - golangci-lint run + +--- +kind: pipeline +type: docker +name: toml + +steps: +- name: test + image: golang + commands: + - cd provider/toml + - go test ./... + +- name: golangci-lint + image: golangci/golangci-lint:v1.55 + commands: + - cd provider/toml + - golangci-lint run + diff --git a/provider/toml/fixture/config.toml b/provider/toml/fixture/config.toml new file mode 100644 index 0000000..493429e --- /dev/null +++ b/provider/toml/fixture/config.toml @@ -0,0 +1,31 @@ +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +dob = 1979-05-27T07:32:00-08:00 # First class dates + +[database] +server = "192.168.1.1" +ports = [ 8001, 8001, 8002 ] +connection_max = 5000 +enabled = true + +[servers] + + # Indentation (tabs and/or spaces) is allowed but not required + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" + +[clients] +data = [ ["gamma", "delta"], [1, 2] ] + +# Line breaks are OK when inside arrays +hosts = [ + "alpha", + "omega" +] \ No newline at end of file diff --git a/provider/toml/go.mod b/provider/toml/go.mod new file mode 100644 index 0000000..3c7564e --- /dev/null +++ b/provider/toml/go.mod @@ -0,0 +1,15 @@ +module gitoa.ru/go-4devs/config/provider/toml + +go 1.21 + +require ( + github.com/pelletier/go-toml v1.9.5 + github.com/stretchr/testify v1.8.4 + gitoa.ru/go-4devs/config v0.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/provider/toml/go.sum b/provider/toml/go.sum new file mode 100644 index 0000000..24bfcee --- /dev/null +++ b/provider/toml/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gitoa.ru/go-4devs/config v0.0.1 h1:9KrOO09YbIMO8qL8aVn/G74DurGdOIW5y3O02bays4I= +gitoa.ru/go-4devs/config v0.0.1/go.mod h1:xfEC2Al9xnMLJUuekYs3KhJ5BIzWAseNwkMwbN6/xss= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/provider/toml/provider.go b/provider/toml/provider.go new file mode 100644 index 0000000..7a81d06 --- /dev/null +++ b/provider/toml/provider.go @@ -0,0 +1,71 @@ +package toml + +import ( + "context" + "fmt" + "strings" + + "github.com/pelletier/go-toml" + "gitoa.ru/go-4devs/config" + "gitoa.ru/go-4devs/config/value" +) + +const ( + Name = "toml" + Separator = "." +) + +var _ config.Provider = (*Provider)(nil) + +func NewFile(file string, opts ...Option) (*Provider, error) { + tree, err := toml.LoadFile(file) + if err != nil { + return nil, fmt.Errorf("toml: failed load file: %w", err) + } + + return configure(tree, opts...), nil +} + +type Option func(*Provider) + +func configure(tree *toml.Tree, opts ...Option) *Provider { + prov := &Provider{ + tree: tree, + key: func(s []string) string { + return strings.Join(s, Separator) + }, + } + + for _, opt := range opts { + opt(prov) + } + + return prov +} + +func New(data []byte, opts ...Option) (*Provider, error) { + tree, err := toml.LoadBytes(data) + if err != nil { + return nil, fmt.Errorf("toml failed load data: %w", err) + } + + return configure(tree, opts...), nil +} + +type Provider struct { + tree *toml.Tree + key func([]string) string + name string +} + +func (p *Provider) Name() string { + return p.name +} + +func (p *Provider) Value(_ context.Context, path ...string) (config.Value, error) { + if k := p.key(path); p.tree.Has(k) { + return Value{Value: value.Value{Val: p.tree.Get(k)}}, nil + } + + return nil, config.ErrValueNotFound +} diff --git a/provider/toml/provider_test.go b/provider/toml/provider_test.go new file mode 100644 index 0000000..1580b8d --- /dev/null +++ b/provider/toml/provider_test.go @@ -0,0 +1,36 @@ +package toml_test + +import ( + "embed" + "testing" + + "gitoa.ru/go-4devs/config/provider/toml" + "gitoa.ru/go-4devs/config/test" + "gitoa.ru/go-4devs/config/test/require" +) + +//go:embed fixture/* +var fixtures embed.FS + +func TestProvider(t *testing.T) { + t.Parallel() + + files, ferr := fixtures.ReadFile("fixture/config.toml") + require.NoError(t, ferr) + + prov, err := toml.New(files) + require.NoError(t, err) + + m := []int{} + + read := []test.Read{ + test.NewRead("192.168.1.1", "database.server"), + test.NewRead("TOML Example", "title"), + test.NewRead("10.0.0.1", "servers.alpha.ip"), + test.NewRead(true, "database.enabled"), + test.NewRead(5000, "database.connection_max"), + test.NewReadUnmarshal(&[]int{8001, 8001, 8002}, &m, "database", "ports"), + } + + test.Run(t, prov, read) +} diff --git a/provider/toml/value.go b/provider/toml/value.go new file mode 100644 index 0000000..7d2ab9d --- /dev/null +++ b/provider/toml/value.go @@ -0,0 +1,41 @@ +package toml + +import ( + "encoding/json" + "fmt" + + "gitoa.ru/go-4devs/config" + "gitoa.ru/go-4devs/config/value" +) + +type Value struct { + value.Value +} + +func (s Value) Int() int { + v, _ := s.ParseInt() + + return v +} + +func (s Value) ParseInt() (int, error) { + v, err := s.ParseInt64() + if err != nil { + return 0, fmt.Errorf("toml failed parce int: %w", err) + } + + return int(v), nil +} + +func (s Value) Unmarshal(target interface{}) error { + b, err := json.Marshal(s.Raw()) + if err != nil { + return fmt.Errorf("%w: %w", config.ErrInvalidValue, err) + } + + if err := json.Unmarshal(b, target); err != nil { + return fmt.Errorf("%w: %w", config.ErrInvalidValue, err) + } + + return nil +}