diff --git a/.drone.yml b/.drone.yml index 130147d..21f4201 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,3 +1,4 @@ +--- kind: pipeline name: default @@ -11,10 +12,26 @@ steps: image: golang commands: # - go test -parallel 10 -race ./... - # - go test -race ./... - go test ./... - name: golangci-lint image: golangci/golangci-lint:v1.55 commands: - golangci-lint run + +--- +kind: pipeline +name: json + +steps: +- name: test + image: golang + commands: + - cd provider/json + - go test ./... + +- name: golangci-lint + image: golangci/golangci-lint:v1.55 + commands: + - cd provider/json + - golangci-lint run diff --git a/provider/json/fixture/config.json b/provider/json/fixture/config.json new file mode 100644 index 0000000..9faa3d5 --- /dev/null +++ b/provider/json/fixture/config.json @@ -0,0 +1,17 @@ +{ + "app": { + "name": { + "var": [ + "name" + ], + "title": "config title", + "timeout": "1m", + "success": true + } + }, + "cfg": { + "duration": 1260000000000, + "enabled": true, + "type":"json" + } +} \ No newline at end of file diff --git a/provider/json/go.mod b/provider/json/go.mod new file mode 100644 index 0000000..547124c --- /dev/null +++ b/provider/json/go.mod @@ -0,0 +1,13 @@ +module gitoa.ru/go-4devs/config/provider/json + +go 1.21.5 + +require ( + github.com/tidwall/gjson v1.17.0 + gitoa.ru/go-4devs/config v0.0.1 +) + +require ( + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect +) diff --git a/provider/json/go.sum b/provider/json/go.sum new file mode 100644 index 0000000..67e4933 --- /dev/null +++ b/provider/json/go.sum @@ -0,0 +1,10 @@ +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +gitoa.ru/go-4devs/config v0.0.0-20240125174937-085589a9383a h1:61iwpn1Ec4yIkBSvSz8knNENJuhj6v2rp6bfw1wkG0E= +gitoa.ru/go-4devs/config v0.0.0-20240125174937-085589a9383a/go.mod h1:3g2bwE2OTDyYwm33KN/Cqc8pEdGlWXnB8Ju3PsYNQr0= +gitoa.ru/go-4devs/config v0.0.1 h1:9KrOO09YbIMO8qL8aVn/G74DurGdOIW5y3O02bays4I= +gitoa.ru/go-4devs/config v0.0.1/go.mod h1:xfEC2Al9xnMLJUuekYs3KhJ5BIzWAseNwkMwbN6/xss= diff --git a/provider/json/provider.go b/provider/json/provider.go new file mode 100644 index 0000000..69eae81 --- /dev/null +++ b/provider/json/provider.go @@ -0,0 +1,65 @@ +package json + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/tidwall/gjson" + "gitoa.ru/go-4devs/config" + "gitoa.ru/go-4devs/config/value" +) + +const ( + Name = "json" + Separator = "." +) + +var _ config.Provider = (*Provider)(nil) + +func New(json []byte, opts ...Option) *Provider { + provider := Provider{ + key: func(s ...string) string { + return strings.Join(s, Separator) + }, + data: json, + } + + for _, opt := range opts { + opt(&provider) + } + + return &provider +} + +func NewFile(path string, opts ...Option) (*Provider, error) { + file, err := os.ReadFile(filepath.Clean(path)) + if err != nil { + return nil, fmt.Errorf("%w: unable to read config file %#q: file not found or unreadable", err, path) + } + + return New(file, opts...), nil +} + +type Option func(*Provider) + +type Provider struct { + data []byte + key func(...string) string + name string +} + +func (p *Provider) Name() string { + return p.name +} + +func (p *Provider) Value(ctx context.Context, path ...string) (config.Value, error) { + key := p.key(path...) + if val := gjson.GetBytes(p.data, key); val.Exists() { + return value.JString(val.String()), nil + } + + return nil, fmt.Errorf("%v:%w", p.Name(), config.ErrValueNotFound) +} diff --git a/provider/json/provider_example_test.go b/provider/json/provider_example_test.go new file mode 100644 index 0000000..612a1f0 --- /dev/null +++ b/provider/json/provider_example_test.go @@ -0,0 +1,55 @@ +package json_test + +import ( + "context" + "fmt" + "log" + + "gitoa.ru/go-4devs/config" + "gitoa.ru/go-4devs/config/provider/json" + "gitoa.ru/go-4devs/config/test" +) + +func ExampleClient_Value() { + ctx := context.Background() + + // read json config + jsonConfig, jerr := fixture.ReadFile("fixture/config.json") + if jerr != nil { + log.Printf("failed load file:%v", jerr) + + return + } + + config, err := config.New( + json.New(jsonConfig), + ) + if err != nil { + log.Print(err) + + return + } + + title, err := config.Value(ctx, "app.name.title") + if err != nil { + log.Print("app.name.title", err) + + return + } + + cfgValue, err := config.Value(ctx, "cfg") + if err != nil { + log.Print("cfg ", err) + + return + } + + cfg := test.Config{} + _ = cfgValue.Unmarshal(&cfg) + + fmt.Printf("title from json: %v\n", title.String()) + fmt.Printf("struct from json: %+v\n", cfg) + // Output: + // title from json: config title + // struct from json: {Duration:21m0s Enabled:true} +} diff --git a/provider/json/provider_test.go b/provider/json/provider_test.go new file mode 100644 index 0000000..0e0ab9e --- /dev/null +++ b/provider/json/provider_test.go @@ -0,0 +1,33 @@ +package json_test + +import ( + "embed" + "testing" + "time" + + "gitoa.ru/go-4devs/config/provider/json" + "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() + + js, err := fixture.ReadFile("fixture/config.json") + require.NoError(t, err) + + prov := json.New(js) + sl := []string{} + read := []test.Read{ + test.NewRead("config title", "app.name.title"), + test.NewRead(time.Minute, "app.name.timeout"), + test.NewReadUnmarshal(&[]string{"name"}, &sl, "app.name.var"), + test.NewReadConfig("cfg"), + test.NewRead(true, "app", "name", "success"), + } + + test.Run(t, prov, read) +}