add yaml provider #5

Merged
andrey merged 1 commits from yaml into master 9 months ago
  1. 17
      .drone.yml
  2. 14
      provider/yaml/fixture/config.yaml
  3. 8
      provider/yaml/go.mod
  4. 6
      provider/yaml/go.sum
  5. 100
      provider/yaml/provider.go
  6. 32
      provider/yaml/provider_test.go
  7. 46
      provider/yaml/watch.go

17
.drone.yml

@ -35,3 +35,20 @@ steps:
commands:
- cd provider/json
- golangci-lint run
---
kind: pipeline
name: yaml
steps:
- name: test
image: golang
commands:
- cd provider/yaml
- go test ./...
- name: golangci-lint
image: golangci/golangci-lint:v1.55
commands:
- cd provider/yaml
- golangci-lint run

14
provider/yaml/fixture/config.yaml

@ -0,0 +1,14 @@
app:
title: yaml title
name:
var:
- test
bool_var: true
duration_var: 21m
empty_var:
url_var: "http://google.com/"
time_var: "2020-01-02T15:04:05Z"
cfg:
duration: 21m
enabled: true
type: yaml

8
provider/yaml/go.mod

@ -0,0 +1,8 @@
module gitoa.ru/go-4devs/config/provider/yaml
go 1.21
require (
gitoa.ru/go-4devs/config v0.0.1
gopkg.in/yaml.v3 v3.0.1
)

6
provider/yaml/go.sum

@ -0,0 +1,6 @@
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=

100
provider/yaml/provider.go

@ -0,0 +1,100 @@
package yaml
import (
"context"
"errors"
"fmt"
"os"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/value"
"gopkg.in/yaml.v3"
)
const (
Name = "yaml"
)
var _ config.Provider = (*Provider)(nil)
func NewFile(name string, opts ...Option) (*Provider, error) {
in, err := os.ReadFile(name)
if err != nil {
return nil, fmt.Errorf("yaml_file: read error: %w", err)
}
return New(in, opts...)
}
func New(yml []byte, opts ...Option) (*Provider, error) {
var data yaml.Node
if err := yaml.Unmarshal(yml, &data); err != nil {
return nil, fmt.Errorf("yaml: unmarshal err: %w", err)
}
return create(opts...).With(&data), nil
}
func create(opts ...Option) *Provider {
prov := Provider{
name: Name,
}
for _, opt := range opts {
opt(&prov)
}
return &prov
}
type Option func(*Provider)
type Provider struct {
data node
name string
}
func (p *Provider) Name() string {
return p.name
}
func (p *Provider) Value(_ context.Context, path ...string) (config.Value, error) {
return p.data.read(p.Name(), path)
}
func (p *Provider) With(data *yaml.Node) *Provider {
return &Provider{
data: node{Node: data},
}
}
type node struct {
*yaml.Node
}
func (n *node) read(name string, keys []string) (config.Value, error) {
val, err := getData(n.Node.Content[0].Content, keys)
if err != nil {
if errors.Is(err, config.ErrValueNotFound) {
return nil, fmt.Errorf("%w: %s", config.ErrValueNotFound, name)
}
return nil, fmt.Errorf("%w: %s", err, name)
}
return value.Decode(val), nil
}
func getData(node []*yaml.Node, keys []string) (func(interface{}) error, error) {
for idx := len(node) - 1; idx > 0; idx -= 2 {
if node[idx-1].Value == keys[0] {
if len(keys) > 1 {
return getData(node[idx].Content, keys[1:])
}
return node[idx].Decode, nil
}
}
return nil, config.ErrValueNotFound
}

32
provider/yaml/provider_test.go

@ -0,0 +1,32 @@
package yaml_test
import (
"embed"
"testing"
"time"
"gitoa.ru/go-4devs/config/provider/yaml"
"gitoa.ru/go-4devs/config/test"
"gitoa.ru/go-4devs/config/test/require"
)
//go:embed fixture/*
var fixture embed.FS
func TestProvider(t *testing.T) {
t.Parallel()
data, err := fixture.ReadFile("fixture/config.yaml")
require.NoError(t, err)
prov, err := yaml.New(data)
require.NoError(t, err)
read := []test.Read{
test.NewRead(21*time.Minute, "duration_var"),
test.NewRead(true, "app", "name", "bool_var"),
test.NewRead(test.Time("2020-01-02T15:04:05Z"), "time_var"),
test.NewReadConfig("cfg"),
}
test.Run(t, prov, read)
}

46
provider/yaml/watch.go

@ -0,0 +1,46 @@
package yaml
import (
"context"
"fmt"
"os"
"gitoa.ru/go-4devs/config"
"gopkg.in/yaml.v3"
)
const NameWatch = "yaml_watch"
func NewWatch(name string, opts ...Option) *Watch {
wath := Watch{
file: name,
prov: create(opts...),
name: NameWatch,
}
return &wath
}
type Watch struct {
file string
prov *Provider
name string
}
func (p *Watch) Name() string {
return p.name
}
func (p *Watch) Value(ctx context.Context, path ...string) (config.Value, error) {
in, err := os.ReadFile(p.file)
if err != nil {
return nil, fmt.Errorf("yaml_file: read error: %w", err)
}
var yNode yaml.Node
if err = yaml.Unmarshal(in, &yNode); err != nil {
return nil, fmt.Errorf("yaml_file: unmarshal error: %w", err)
}
return p.prov.With(&yNode).Value(ctx, path...)
}
Loading…
Cancel
Save