update client
This commit is contained in:
84
client.go
84
client.go
@@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func Must(providers ...any) *Client {
|
||||
@@ -27,14 +25,7 @@ func New(providers ...any) (*Client, error) {
|
||||
case Provider:
|
||||
client.providers[idx] = current
|
||||
case Factory:
|
||||
client.providers[idx] = &provider{
|
||||
factory: func(ctx context.Context) (Provider, error) {
|
||||
return current(ctx, client)
|
||||
},
|
||||
mu: sync.Mutex{},
|
||||
done: 0,
|
||||
provider: nil,
|
||||
}
|
||||
client.providers[idx] = WrapFactory(current, client)
|
||||
default:
|
||||
return nil, fmt.Errorf("provier[%d]: %w %T", idx, ErrUnknowType, prov)
|
||||
}
|
||||
@@ -43,60 +34,6 @@ func New(providers ...any) (*Client, error) {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
type provider struct {
|
||||
mu sync.Mutex
|
||||
done uint32
|
||||
provider Provider
|
||||
factory func(ctx context.Context) (Provider, error)
|
||||
}
|
||||
|
||||
func (p *provider) Watch(ctx context.Context, callback WatchCallback, path ...string) error {
|
||||
if err := p.init(ctx); err != nil {
|
||||
return fmt.Errorf("init read:%w", err)
|
||||
}
|
||||
|
||||
watch, ok := p.provider.(WatchProvider)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := watch.Watch(ctx, callback, path...); err != nil {
|
||||
return fmt.Errorf("factory provider: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) Value(ctx context.Context, path ...string) (Value, error) {
|
||||
if err := p.init(ctx); err != nil {
|
||||
return nil, fmt.Errorf("init read:%w", err)
|
||||
}
|
||||
|
||||
variable, err := p.provider.Value(ctx, path...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("factory provider: %w", err)
|
||||
}
|
||||
|
||||
return variable, nil
|
||||
}
|
||||
|
||||
func (p *provider) init(ctx context.Context) error {
|
||||
if atomic.LoadUint32(&p.done) == 0 {
|
||||
if !p.mu.TryLock() {
|
||||
return fmt.Errorf("%w", ErrInitFactory)
|
||||
}
|
||||
defer atomic.StoreUint32(&p.done, 1)
|
||||
defer p.mu.Unlock()
|
||||
|
||||
var err error
|
||||
if p.provider, err = p.factory(ctx); err != nil {
|
||||
return fmt.Errorf("init provider factory:%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
providers []Provider
|
||||
}
|
||||
@@ -114,7 +51,7 @@ func (c *Client) Value(ctx context.Context, path ...string) (Value, error) {
|
||||
|
||||
for _, provider := range c.providers {
|
||||
value, err = provider.Value(ctx, path...)
|
||||
if err == nil || (!errors.Is(err, ErrValueNotFound) && !errors.Is(err, ErrInitFactory)) {
|
||||
if err == nil || (!errors.Is(err, ErrNotFound) && !errors.Is(err, ErrInitFactory)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -135,7 +72,7 @@ func (c *Client) Watch(ctx context.Context, callback WatchCallback, path ...stri
|
||||
|
||||
err := provider.Watch(ctx, callback, path...)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrValueNotFound) || errors.Is(err, ErrInitFactory) {
|
||||
if errors.Is(err, ErrNotFound) || errors.Is(err, ErrInitFactory) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -145,3 +82,18 @@ func (c *Client) Watch(ctx context.Context, callback WatchCallback, path ...stri
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Bind(ctx context.Context, data Variables) error {
|
||||
for idx, prov := range c.providers {
|
||||
provider, ok := prov.(BindProvider)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := provider.Bind(ctx, data); err != nil {
|
||||
return fmt.Errorf("bind[%d] %v:%w", idx, provider.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,14 +42,14 @@ func ExampleClient_Value() {
|
||||
|
||||
port, err := config.Value(ctx, "listen")
|
||||
if err != nil {
|
||||
log.Print("listen", err)
|
||||
log.Print("listen: ", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
hostValue, err := config.Value(ctx, "host")
|
||||
if err != nil {
|
||||
log.Print("host ", err)
|
||||
log.Print("host:", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
vault:
|
||||
container_name: vault
|
||||
image: vault:1.13.3
|
||||
cap_add:
|
||||
- IPC_LOCK
|
||||
@@ -10,8 +9,11 @@ services:
|
||||
environment:
|
||||
VAULT_DEV_ROOT_TOKEN_ID: "dev"
|
||||
etcd:
|
||||
image: bitnami/etcd:3.5.11
|
||||
container_name: etcd
|
||||
image: quay.io/coreos/etcd:v3.6.7
|
||||
environment:
|
||||
ALLOW_NONE_AUTHENTICATION: "yes"
|
||||
ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379"
|
||||
ETCD_ADVERTISE_CLIENT_URLS: "http://etcd:2379"
|
||||
ports:
|
||||
- "2379:2379"
|
||||
4
definition/defenition.go
Executable file → Normal file
4
definition/defenition.go
Executable file → Normal file
@@ -4,9 +4,9 @@ import (
|
||||
"gitoa.ru/go-4devs/config"
|
||||
)
|
||||
|
||||
func New() *Definition {
|
||||
func New(opts ...config.Option) *Definition {
|
||||
return &Definition{
|
||||
options: nil,
|
||||
options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
206
definition/generate/example/config_config.go
Normal file
206
definition/generate/example/config_config.go
Normal file
@@ -0,0 +1,206 @@
|
||||
// Code generated gitoa.ru/go-4devs/config DO NOT EDIT.
|
||||
package example
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
config "gitoa.ru/go-4devs/config"
|
||||
)
|
||||
|
||||
func WithInputConfigLog(log func(context.Context, string, ...any)) func(*InputConfig) {
|
||||
return func(ci *InputConfig) {
|
||||
ci.log = log
|
||||
}
|
||||
}
|
||||
|
||||
func NewInputConfig(prov config.Provider, opts ...func(*InputConfig)) InputConfig {
|
||||
i := InputConfig{
|
||||
Provider: prov,
|
||||
log: func(_ context.Context, format string, args ...any) {
|
||||
fmt.Printf(format, args...)
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&i)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
type InputConfig struct {
|
||||
config.Provider
|
||||
log func(context.Context, string, ...any)
|
||||
}
|
||||
|
||||
// readTest test string.
|
||||
func (i InputConfig) readTest(ctx context.Context) (v string, e error) {
|
||||
val, err := i.Value(ctx, "test")
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("read [%v]:%w", []string{"test"}, err)
|
||||
|
||||
}
|
||||
|
||||
return val.ParseString()
|
||||
|
||||
}
|
||||
|
||||
// ReadTest test string.
|
||||
func (i InputConfig) ReadTest() (string, error) {
|
||||
return i.readTest(context.Background())
|
||||
}
|
||||
|
||||
// Test test string.
|
||||
func (i InputConfig) Test() string {
|
||||
val, err := i.readTest(context.Background())
|
||||
if err != nil {
|
||||
i.log(context.Background(), "get [%v]: %v", []string{"test"}, err)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
type InputConfigUser struct {
|
||||
InputConfig
|
||||
}
|
||||
|
||||
// User configure user.
|
||||
func (i InputConfig) User() InputConfigUser {
|
||||
return InputConfigUser{i}
|
||||
}
|
||||
|
||||
// readName name.
|
||||
func (i InputConfigUser) readName(ctx context.Context) (v string, e error) {
|
||||
val, err := i.Value(ctx, "user", "name")
|
||||
if err != nil {
|
||||
i.log(context.Background(), "read [%v]: %v", []string{"user", "name"}, err)
|
||||
|
||||
return "4devs", nil
|
||||
}
|
||||
|
||||
return val.ParseString()
|
||||
|
||||
}
|
||||
|
||||
// ReadName name.
|
||||
func (i InputConfigUser) ReadName(ctx context.Context) (string, error) {
|
||||
return i.readName(ctx)
|
||||
}
|
||||
|
||||
// Name name.
|
||||
func (i InputConfigUser) Name(ctx context.Context) string {
|
||||
val, err := i.readName(ctx)
|
||||
if err != nil {
|
||||
i.log(ctx, "get [%v]: %v", []string{"user", "name"}, err)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
// readPass password.
|
||||
func (i InputConfigUser) readPass(ctx context.Context) (v string, e error) {
|
||||
val, err := i.Value(ctx, "user", "pass")
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("read [%v]:%w", []string{"user", "pass"}, err)
|
||||
|
||||
}
|
||||
|
||||
return val.ParseString()
|
||||
|
||||
}
|
||||
|
||||
// ReadPass password.
|
||||
func (i InputConfigUser) ReadPass(ctx context.Context) (string, error) {
|
||||
return i.readPass(ctx)
|
||||
}
|
||||
|
||||
// Pass password.
|
||||
func (i InputConfigUser) Pass(ctx context.Context) string {
|
||||
val, err := i.readPass(ctx)
|
||||
if err != nil {
|
||||
i.log(ctx, "get [%v]: %v", []string{"user", "pass"}, err)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
type InputConfigLog struct {
|
||||
InputConfig
|
||||
}
|
||||
|
||||
// Log configure logger.
|
||||
func (i InputConfig) Log() InputConfigLog {
|
||||
return InputConfigLog{i}
|
||||
}
|
||||
|
||||
// readLevel log level.
|
||||
func (i InputConfigLog) readLevel(ctx context.Context) (v Level, e error) {
|
||||
val, err := i.Value(ctx, "log", "level")
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("read [%v]:%w", []string{"log", "level"}, err)
|
||||
|
||||
}
|
||||
|
||||
pval, perr := val.ParseString()
|
||||
if perr != nil {
|
||||
return v, fmt.Errorf("read [%v]:%w", []string{"log", "level"}, perr)
|
||||
}
|
||||
|
||||
return v, v.UnmarshalText([]byte(pval))
|
||||
}
|
||||
|
||||
// ReadLevel log level.
|
||||
func (i InputConfigLog) ReadLevel(ctx context.Context) (Level, error) {
|
||||
return i.readLevel(ctx)
|
||||
}
|
||||
|
||||
// Level log level.
|
||||
func (i InputConfigLog) Level(ctx context.Context) Level {
|
||||
val, err := i.readLevel(ctx)
|
||||
if err != nil {
|
||||
i.log(ctx, "get [%v]: %v", []string{"log", "level"}, err)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
type InputConfigLogService struct {
|
||||
InputConfigLog
|
||||
service string
|
||||
}
|
||||
|
||||
// Service servise logger.
|
||||
func (i InputConfigLog) Service(key string) InputConfigLogService {
|
||||
return InputConfigLogService{i, key}
|
||||
}
|
||||
|
||||
// readLevel log level.
|
||||
func (i InputConfigLogService) readLevel(ctx context.Context) (v Level, e error) {
|
||||
val, err := i.Value(ctx, "log", i.service, "level")
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("read [%v]:%w", []string{"log", i.service, "level"}, err)
|
||||
|
||||
}
|
||||
|
||||
pval, perr := val.ParseString()
|
||||
if perr != nil {
|
||||
return v, fmt.Errorf("read [%v]:%w", []string{"log", i.service, "level"}, perr)
|
||||
}
|
||||
|
||||
return v, v.UnmarshalText([]byte(pval))
|
||||
}
|
||||
|
||||
// ReadLevel log level.
|
||||
func (i InputConfigLogService) ReadLevel(ctx context.Context) (Level, error) {
|
||||
return i.readLevel(ctx)
|
||||
}
|
||||
|
||||
// Level log level.
|
||||
func (i InputConfigLogService) Level(ctx context.Context) Level {
|
||||
val, err := i.readLevel(ctx)
|
||||
if err != nil {
|
||||
i.log(ctx, "get [%v]: %v", []string{"log", i.service, "level"}, err)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
59
definition/generate/example/generate_test.go
Normal file
59
definition/generate/example/generate_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package example_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gitoa.ru/go-4devs/config/definition"
|
||||
"gitoa.ru/go-4devs/config/definition/generate"
|
||||
"gitoa.ru/go-4devs/config/definition/generate/bootstrap"
|
||||
"gitoa.ru/go-4devs/config/definition/generate/example"
|
||||
)
|
||||
|
||||
func TestGenerate_Bootstrap(t *testing.T) {
|
||||
t.SkipNow()
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
options := definition.New()
|
||||
_ = example.Config(ctx, options)
|
||||
|
||||
cfg, _ := generate.NewGConfig("./config.go",
|
||||
generate.WithMethods("Config"),
|
||||
generate.WithFullPkg("gitoa.ru/go-4devs/config/definition/generate/example"),
|
||||
)
|
||||
|
||||
path, err := bootstrap.Bootstrap(ctx, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
os.Remove(path)
|
||||
|
||||
t.Log(path)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
func TestGenerate_Genereate(t *testing.T) {
|
||||
t.SkipNow()
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
options := definition.New()
|
||||
_ = example.Config(ctx, options)
|
||||
|
||||
cfg, _ := generate.NewGConfig("./config.go",
|
||||
generate.WithMethods("Config"),
|
||||
generate.WithFullPkg("gitoa.ru/go-4devs/config/definition/generate/example"),
|
||||
)
|
||||
|
||||
err := generate.Generate(ctx, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
t.FailNow()
|
||||
}
|
||||
0
definition/generate/pkg/imports.go
Executable file → Normal file
0
definition/generate/pkg/imports.go
Executable file → Normal file
@@ -1,8 +1,6 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"gitoa.ru/go-4devs/config/definition/generate/pkg"
|
||||
"gitoa.ru/go-4devs/config/definition/generate/view"
|
||||
)
|
||||
@@ -53,8 +51,6 @@ func (d ViewData) Value(name, val string) string {
|
||||
}
|
||||
|
||||
func (d ViewData) Default(name string) string {
|
||||
log.Print(d.View.Default())
|
||||
|
||||
return Data(d.View.Default(), name, d)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitoa.ru/go-4devs/config/definition/generate/pkg"
|
||||
"gitoa.ru/go-4devs/config/definition/key"
|
||||
"gitoa.ru/go-4devs/config/key"
|
||||
)
|
||||
|
||||
func Keys(keys []string, val string) string {
|
||||
|
||||
0
definition/generate/render/tpl/data/any.go.tpl
Executable file → Normal file
0
definition/generate/render/tpl/data/any.go.tpl
Executable file → Normal file
0
definition/generate/render/tpl/data/parse.go.tpl
Executable file → Normal file
0
definition/generate/render/tpl/data/parse.go.tpl
Executable file → Normal file
0
definition/generate/render/tpl/data/unmarshal_json.go.tpl
Executable file → Normal file
0
definition/generate/render/tpl/data/unmarshal_json.go.tpl
Executable file → Normal file
0
definition/generate/render/tpl/data/unmarshal_text.go.tpl
Executable file → Normal file
0
definition/generate/render/tpl/data/unmarshal_text.go.tpl
Executable file → Normal file
0
definition/generate/render/tpl/option/option.go.tpl
Executable file → Normal file
0
definition/generate/render/tpl/option/option.go.tpl
Executable file → Normal file
@@ -67,7 +67,7 @@ func newView(name string, get param.Params, in any, opts ...Option) View {
|
||||
kind: reflect.TypeOf(in),
|
||||
name: name,
|
||||
Params: get,
|
||||
dtype: option.DataType(get),
|
||||
dtype: param.Type(get),
|
||||
children: nil,
|
||||
keys: nil,
|
||||
parent: "",
|
||||
|
||||
0
definition/group/group.go
Executable file → Normal file
0
definition/group/group.go
Executable file → Normal file
@@ -22,7 +22,7 @@ func (o Error) Unwrap() error {
|
||||
return o.Err
|
||||
}
|
||||
|
||||
func Err(err error, key ...string) Error {
|
||||
func Err(err error, key []string) Error {
|
||||
return Error{
|
||||
Key: key,
|
||||
Err: err,
|
||||
|
||||
@@ -13,7 +13,6 @@ const (
|
||||
paramRequired
|
||||
paramSlice
|
||||
paramBool
|
||||
paramType
|
||||
paramPos
|
||||
paramShort
|
||||
)
|
||||
@@ -30,15 +29,17 @@ func ParamShort(fn param.Params) (string, bool) {
|
||||
return data, ok
|
||||
}
|
||||
|
||||
func HasShort(short string, fn param.Params) bool {
|
||||
data, ok := param.String(fn, paramShort)
|
||||
func HasShort(short string) param.Has {
|
||||
return func(fn param.Params) bool {
|
||||
data, ok := param.String(fn, paramShort)
|
||||
|
||||
return ok && data == short
|
||||
return ok && data == short
|
||||
}
|
||||
}
|
||||
|
||||
func WithType(in any) param.Option {
|
||||
return func(v param.Params) param.Params {
|
||||
out := param.With(v, paramType, in)
|
||||
out := param.WithType(in)(v)
|
||||
if _, ok := in.(bool); ok {
|
||||
return param.With(out, paramBool, ok)
|
||||
}
|
||||
@@ -117,12 +118,6 @@ func IsRequired(fn param.Params) bool {
|
||||
return ok && data
|
||||
}
|
||||
|
||||
func DataType(fn param.Params) any {
|
||||
param, _ := fn.Param(paramType)
|
||||
|
||||
return param
|
||||
}
|
||||
|
||||
func DataDescription(fn param.Params) string {
|
||||
data, _ := param.String(fn, paramDesc)
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package proto
|
||||
|
||||
import (
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/key"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/key"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
)
|
||||
|
||||
|
||||
21
error.go
21
error.go
@@ -1,11 +1,20 @@
|
||||
package config
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrValueNotFound = errors.New("value not found")
|
||||
ErrInvalidValue = errors.New("invalid value")
|
||||
ErrUnknowType = errors.New("unknow type")
|
||||
ErrInitFactory = errors.New("init factory")
|
||||
ErrStopWatch = errors.New("stop watch")
|
||||
ErrInvalidValue = errors.New("invalid value")
|
||||
ErrUnknowType = errors.New("unknow type")
|
||||
ErrInitFactory = errors.New("init factory")
|
||||
ErrStopWatch = errors.New("stop watch")
|
||||
ErrNotFound = errors.New("not found")
|
||||
ErrValueNotFound = fmt.Errorf("value %w", ErrNotFound)
|
||||
ErrToManyArgs = errors.New("to many args")
|
||||
ErrWrongType = errors.New("wrong type")
|
||||
ErrInvalidName = errors.New("ivalid name")
|
||||
ErrUnexpectedType = errors.New("unexpected type")
|
||||
ErrRequired = errors.New("required")
|
||||
)
|
||||
|
||||
97
factory.go
Normal file
97
factory.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func WrapFactory(fn Factory, prov Provider) *WrapProvider {
|
||||
return &WrapProvider{
|
||||
factory: func(ctx context.Context) (Provider, error) {
|
||||
return fn(ctx, prov)
|
||||
},
|
||||
mu: sync.Mutex{},
|
||||
done: 0,
|
||||
provider: nil,
|
||||
}
|
||||
}
|
||||
|
||||
type WrapProvider struct {
|
||||
mu sync.Mutex
|
||||
done uint32
|
||||
provider Provider
|
||||
factory func(ctx context.Context) (Provider, error)
|
||||
}
|
||||
|
||||
func (p *WrapProvider) Watch(ctx context.Context, callback WatchCallback, path ...string) error {
|
||||
if err := p.init(ctx); err != nil {
|
||||
return fmt.Errorf("init read:%w", err)
|
||||
}
|
||||
|
||||
watch, ok := p.provider.(WatchProvider)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := watch.Watch(ctx, callback, path...); err != nil {
|
||||
return fmt.Errorf("factory provider: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (p *WrapProvider) Value(ctx context.Context, path ...string) (Value, error) {
|
||||
if err := p.init(ctx); err != nil {
|
||||
return nil, fmt.Errorf("init read:%w", err)
|
||||
}
|
||||
|
||||
variable, err := p.provider.Value(ctx, path...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("factory provider: %w", err)
|
||||
}
|
||||
|
||||
return variable, nil
|
||||
}
|
||||
|
||||
func (p *WrapProvider) Name() string {
|
||||
if err := p.init(context.Background()); err != nil {
|
||||
return fmt.Sprintf("%T", p.provider)
|
||||
}
|
||||
|
||||
return p.provider.Name()
|
||||
}
|
||||
|
||||
func (p *WrapProvider) Bind(ctx context.Context, data Variables) error {
|
||||
if err := p.init(ctx); err != nil {
|
||||
return fmt.Errorf("init bind: %w", err)
|
||||
}
|
||||
|
||||
prov, ok := p.provider.(BindProvider)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if perr := prov.Bind(ctx, data); perr != nil {
|
||||
return fmt.Errorf("init bind provider: %w", perr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *WrapProvider) init(ctx context.Context) error {
|
||||
if atomic.LoadUint32(&p.done) == 0 {
|
||||
if !p.mu.TryLock() {
|
||||
return fmt.Errorf("%w", ErrInitFactory)
|
||||
}
|
||||
defer atomic.StoreUint32(&p.done, 1)
|
||||
defer p.mu.Unlock()
|
||||
|
||||
var err error
|
||||
if p.provider, err = p.factory(ctx); err != nil {
|
||||
return fmt.Errorf("init provider factory:%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
121
key/map.go
Normal file
121
key/map.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package key
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
prefixByPath = "byPath"
|
||||
)
|
||||
|
||||
func newMap() *Map {
|
||||
return &Map{
|
||||
idx: 0,
|
||||
wild: nil,
|
||||
children: nil,
|
||||
}
|
||||
}
|
||||
|
||||
type Map struct {
|
||||
idx int
|
||||
wild *Map
|
||||
children map[string]*Map
|
||||
}
|
||||
|
||||
func ByPath(name, sep string) []string {
|
||||
return []string{prefixByPath, name, sep}
|
||||
}
|
||||
|
||||
func (m *Map) Index(path []string) (int, bool) {
|
||||
if data, ok := m.find(path); ok {
|
||||
return data.idx, true
|
||||
}
|
||||
|
||||
if len(path) == 3 && path[0] == prefixByPath {
|
||||
data, ok := m.byPath(path[1], path[2])
|
||||
|
||||
return data.idx, ok
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (m *Map) Add(idx int, path []string) {
|
||||
m.add(path).idx = idx
|
||||
}
|
||||
|
||||
func (m *Map) add(path []string) *Map {
|
||||
name, path := path[0], path[1:]
|
||||
|
||||
if IsWild(name) {
|
||||
m.wild = newMap()
|
||||
|
||||
return m.wild.add(path)
|
||||
}
|
||||
|
||||
if m.children == nil {
|
||||
m.children = map[string]*Map{}
|
||||
}
|
||||
|
||||
if _, ok := m.children[name]; !ok {
|
||||
m.children[name] = newMap()
|
||||
}
|
||||
|
||||
if len(path) > 0 {
|
||||
return m.children[name].add(path)
|
||||
}
|
||||
|
||||
return m.children[name]
|
||||
}
|
||||
|
||||
func (m *Map) byPath(path, sep string) (*Map, bool) {
|
||||
for name := range m.children {
|
||||
if after, ok := strings.CutPrefix(path, name); ok {
|
||||
data := m.children[name]
|
||||
if len(after) == 0 {
|
||||
return data, true
|
||||
}
|
||||
|
||||
after, ok = strings.CutPrefix(after, sep)
|
||||
if !ok {
|
||||
return data, false
|
||||
}
|
||||
|
||||
if data.wild == nil {
|
||||
return data.byPath(after, sep)
|
||||
}
|
||||
|
||||
if idx := strings.Index(after, sep); idx != -1 {
|
||||
return data.wild.byPath(after[idx+1:], sep)
|
||||
}
|
||||
|
||||
return data, false
|
||||
}
|
||||
}
|
||||
|
||||
return m, false
|
||||
}
|
||||
|
||||
func (m *Map) find(path []string) (*Map, bool) {
|
||||
name := path[0]
|
||||
|
||||
last := len(path) == 1
|
||||
if !last {
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
if m.wild != nil {
|
||||
return m.wild.find(path)
|
||||
}
|
||||
|
||||
data, ok := m.children[name]
|
||||
if !ok {
|
||||
return data, false
|
||||
}
|
||||
|
||||
if last {
|
||||
return data, data.children == nil
|
||||
}
|
||||
|
||||
return data.find(path)
|
||||
}
|
||||
83
key/map_test.go
Normal file
83
key/map_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package key_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitoa.ru/go-4devs/config/key"
|
||||
)
|
||||
|
||||
func TestMap_ByPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
expID int = 1
|
||||
newID int = 42
|
||||
)
|
||||
|
||||
data := key.Map{}
|
||||
data.Add(expID, []string{"test", "data", "three"})
|
||||
data.Add(expID, []string{"test", "other"})
|
||||
data.Add(newID, []string{"new", "{data}", "test"})
|
||||
|
||||
idx, ok := data.Index(key.ByPath("test-other", "-"))
|
||||
if !ok {
|
||||
t.Error("key not found")
|
||||
}
|
||||
|
||||
if idx != expID {
|
||||
t.Errorf("idx exp:%v got:%v", expID, idx)
|
||||
}
|
||||
|
||||
if nidx, nok := data.Index(key.ByPath("new-service-test", "-")); !nok && nidx != newID {
|
||||
t.Errorf("idx exp:%v got:%v", newID, nidx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMap_Add(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
expID int = 1
|
||||
newID int = 42
|
||||
)
|
||||
|
||||
data := key.Map{}
|
||||
data.Add(expID, []string{"test", "data"})
|
||||
data.Add(expID, []string{"test", "other"})
|
||||
data.Add(newID, []string{"new"})
|
||||
|
||||
idx, ok := data.Index([]string{"test", "data"})
|
||||
if !ok {
|
||||
t.Error("key not found")
|
||||
}
|
||||
|
||||
if idx != expID {
|
||||
t.Errorf("idx exp:%v got:%v", expID, idx)
|
||||
}
|
||||
|
||||
if nidx, nok := data.Index([]string{"new"}); !nok && nidx != newID {
|
||||
t.Errorf("idx exp:%v got:%v", newID, nidx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMap_Wild(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
expID int = 1
|
||||
newID int = 42
|
||||
)
|
||||
|
||||
data := key.Map{}
|
||||
data.Add(expID, []string{"test", "{data}", "id"})
|
||||
data.Add(newID, []string{"new", "data"})
|
||||
|
||||
idx, ok := data.Index([]string{"test", "data", "id"})
|
||||
if !ok {
|
||||
t.Error("key not found")
|
||||
}
|
||||
|
||||
if idx != expID {
|
||||
t.Errorf("idx exp:%v got:%v", expID, idx)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package param
|
||||
|
||||
import "gitoa.ru/go-4devs/config"
|
||||
|
||||
func String(fn Params, key any) (string, bool) {
|
||||
val, ok := fn.Param(key)
|
||||
if !ok {
|
||||
@@ -24,17 +22,6 @@ func Bool(key any, fn Params) (bool, bool) {
|
||||
return data, ok
|
||||
}
|
||||
|
||||
func Value(key any, fn Params) (config.Value, bool) {
|
||||
data, ok := fn.Param(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
res, ok := data.(config.Value)
|
||||
|
||||
return res, ok
|
||||
}
|
||||
|
||||
func Uint64(key any, fn Params) (uint64, bool) {
|
||||
data, ok := fn.Param(key)
|
||||
if !ok {
|
||||
|
||||
@@ -4,6 +4,7 @@ type key int
|
||||
|
||||
const (
|
||||
paramTimeFormat key = iota + 1
|
||||
paramType
|
||||
)
|
||||
|
||||
func WithTimeFormat(format string) Option {
|
||||
@@ -15,3 +16,15 @@ func WithTimeFormat(format string) Option {
|
||||
func TimeFormat(fn Params) (string, bool) {
|
||||
return String(fn, paramTimeFormat)
|
||||
}
|
||||
|
||||
func WithType(in any) Option {
|
||||
return func(v Params) Params {
|
||||
return With(v, paramType, in)
|
||||
}
|
||||
}
|
||||
|
||||
func Type(fn Params) any {
|
||||
param, _ := fn.Param(paramType)
|
||||
|
||||
return param
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ var (
|
||||
)
|
||||
|
||||
type Option func(p Params) Params
|
||||
|
||||
type Has func(Params) bool
|
||||
type Params interface {
|
||||
Param(key any) (any, bool)
|
||||
}
|
||||
|
||||
26
provider.go
26
provider.go
@@ -1,14 +1,14 @@
|
||||
package config
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
Value(ctx context.Context, path ...string) (Value, error)
|
||||
}
|
||||
|
||||
type NamedProvider interface {
|
||||
Name() string
|
||||
Provider
|
||||
}
|
||||
|
||||
type WatchCallback func(ctx context.Context, oldVar, newVar Value) error
|
||||
@@ -33,10 +33,18 @@ type Options interface {
|
||||
Options() []Option
|
||||
}
|
||||
|
||||
type BindProvider interface {
|
||||
Provider
|
||||
|
||||
Bind(ctx context.Context, data Variables) error
|
||||
}
|
||||
|
||||
type Variables interface {
|
||||
ByName(name ...string) (Variable, error)
|
||||
ByParam(filter param.Has) (Variable, error)
|
||||
Variables() []Variable
|
||||
}
|
||||
|
||||
type Definition interface {
|
||||
Add(opts ...Option)
|
||||
}
|
||||
|
||||
type BindProvider interface {
|
||||
Bind(ctx context.Context, def Definition)
|
||||
}
|
||||
|
||||
59
provider/arg/option.go
Normal file
59
provider/arg/option.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package arg
|
||||
|
||||
import (
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
)
|
||||
|
||||
func Default(in any) param.Option {
|
||||
return option.Default(value.New(in))
|
||||
}
|
||||
|
||||
func Required(v param.Params) {
|
||||
option.Required(v)
|
||||
}
|
||||
|
||||
func Slice(v param.Params) {
|
||||
option.Slice(v)
|
||||
}
|
||||
|
||||
func String(name, description string, opts ...param.Option) option.Option {
|
||||
return option.String(name, description, append(opts, Argument)...)
|
||||
}
|
||||
|
||||
func Bool(name, description string, opts ...param.Option) option.Option {
|
||||
return option.Bool(name, description, append(opts, Argument)...)
|
||||
}
|
||||
|
||||
func Duration(name, description string, opts ...param.Option) option.Option {
|
||||
return option.Duration(name, description, append(opts, Argument)...)
|
||||
}
|
||||
|
||||
func Float64(name, description string, opts ...param.Option) option.Option {
|
||||
return option.Float64(name, description, append(opts, Argument)...)
|
||||
}
|
||||
|
||||
func Int(name, description string, opts ...param.Option) option.Option {
|
||||
return option.Int(name, description, append(opts, Argument)...)
|
||||
}
|
||||
|
||||
func Int64(name, description string, opts ...param.Option) option.Option {
|
||||
return option.Int64(name, description, append(opts, Argument)...)
|
||||
}
|
||||
|
||||
func Time(name, description string, opts ...param.Option) option.Option {
|
||||
return option.Time(name, description, append(opts, Argument)...)
|
||||
}
|
||||
|
||||
func Uint(name, description string, opts ...param.Option) option.Option {
|
||||
return option.Uint(name, description, append(opts, Argument)...)
|
||||
}
|
||||
|
||||
func Uint64(name, descriontion string, opts ...param.Option) option.Option {
|
||||
return option.Uint64(name, descriontion, append(opts, Argument)...)
|
||||
}
|
||||
|
||||
func Err(err error, key ...string) option.Error {
|
||||
return option.Err(err, key)
|
||||
}
|
||||
38
provider/arg/param.go
Normal file
38
provider/arg/param.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package arg
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
)
|
||||
|
||||
type keyParam int
|
||||
|
||||
const (
|
||||
paramArgument keyParam = iota + 1
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var argNum uint64
|
||||
|
||||
func Argument(v param.Params) param.Params {
|
||||
return param.With(v, paramArgument, atomic.AddUint64(&argNum, 1))
|
||||
}
|
||||
|
||||
func ParamArgument(fn param.Params) (uint64, bool) {
|
||||
return param.Uint64(paramArgument, fn)
|
||||
}
|
||||
|
||||
func PosArgument(in uint64) param.Has {
|
||||
return func(p param.Params) bool {
|
||||
idx, ok := ParamArgument(p)
|
||||
|
||||
return ok && idx == in
|
||||
}
|
||||
}
|
||||
|
||||
func HasArgument(fn param.Params) bool {
|
||||
_, ok := ParamArgument(fn)
|
||||
|
||||
return ok
|
||||
}
|
||||
@@ -2,86 +2,230 @@ package arg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/key"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
"gitoa.ru/go-4devs/config/provider/memory"
|
||||
)
|
||||
|
||||
const Name = "arg"
|
||||
const (
|
||||
doubleDash = `--`
|
||||
defaultLenLognOption = 2
|
||||
dash = `-`
|
||||
)
|
||||
|
||||
var _ config.Provider = (*Provider)(nil)
|
||||
func WithSkip(skip int) func(*Argv) {
|
||||
return func(ar *Argv) {
|
||||
res := 2
|
||||
|
||||
type Option func(*Provider)
|
||||
switch {
|
||||
case skip > 0 && len(os.Args) > skip:
|
||||
res = skip
|
||||
case skip > 0:
|
||||
res = len(os.Args)
|
||||
case len(os.Args) == 1:
|
||||
res = 1
|
||||
case len(os.Args) > 1 && os.Args[1][0] == '-':
|
||||
res = 1
|
||||
}
|
||||
|
||||
func WithKeyFactory(factory func(s ...string) string) Option {
|
||||
return func(p *Provider) { p.key = factory }
|
||||
ar.skip = res
|
||||
}
|
||||
}
|
||||
|
||||
func New(opts ...Option) *Provider {
|
||||
prov := Provider{
|
||||
key: func(s ...string) string {
|
||||
return strings.Join(s, "-")
|
||||
},
|
||||
args: make(map[string][]string, len(os.Args[1:])),
|
||||
name: Name,
|
||||
func New(opts ...func(*Argv)) *Argv {
|
||||
arg := &Argv{
|
||||
args: nil,
|
||||
pos: 0,
|
||||
Map: memory.Map{},
|
||||
skip: 1,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&prov)
|
||||
opt(arg)
|
||||
}
|
||||
|
||||
return &prov
|
||||
arg.args = os.Args[arg.skip:]
|
||||
|
||||
return arg
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
args map[string][]string
|
||||
key func(...string) string
|
||||
name string
|
||||
type Argv struct {
|
||||
memory.Map
|
||||
|
||||
args []string
|
||||
pos uint64
|
||||
skip int
|
||||
}
|
||||
|
||||
func (p *Provider) Name() string {
|
||||
return p.name
|
||||
}
|
||||
func (i *Argv) Value(ctx context.Context, key ...string) (config.Value, error) {
|
||||
if err := i.parse(); err != nil {
|
||||
return nil, fmt.Errorf("parse:%w", err)
|
||||
}
|
||||
|
||||
func (p *Provider) Value(_ context.Context, path ...string) (config.Value, error) {
|
||||
err := p.parse()
|
||||
data, err := i.Map.Value(ctx, key...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
name := p.key(path...)
|
||||
if val, ok := p.args[name]; ok {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (i *Argv) Bind(ctx context.Context, def config.Variables) error {
|
||||
options := true
|
||||
|
||||
for len(i.args) > 0 {
|
||||
var err error
|
||||
|
||||
arg := i.args[0]
|
||||
i.args = i.args[1:]
|
||||
|
||||
switch {
|
||||
case len(val) == 1:
|
||||
return value.JString(val[0]), nil
|
||||
default:
|
||||
data, jerr := json.Marshal(val)
|
||||
if jerr != nil {
|
||||
return nil, fmt.Errorf("failed load data:%w", jerr)
|
||||
case options && arg == doubleDash:
|
||||
options = false
|
||||
case options && len(arg) > 2 && arg[0:2] == doubleDash:
|
||||
err = i.parseLongOption(arg[2:], def)
|
||||
case options && arg[0:1] == "-":
|
||||
if len(arg) == 1 {
|
||||
return fmt.Errorf("%w: option name required given '-'", config.ErrInvalidName)
|
||||
}
|
||||
|
||||
return value.Decode(func(v any) error {
|
||||
err := json.Unmarshal(data, v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal:%w", err)
|
||||
}
|
||||
err = i.parseShortOption(arg[1:], def)
|
||||
default:
|
||||
err = i.parseArgument(arg, def)
|
||||
}
|
||||
|
||||
return nil
|
||||
}), nil
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%s:%w", p.Name(), config.ErrValueNotFound)
|
||||
if err := i.Map.Bind(ctx, def); err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Argv) parseLongOption(arg string, def config.Variables) error {
|
||||
var value *string
|
||||
|
||||
name := arg
|
||||
|
||||
if strings.Contains(arg, "=") {
|
||||
vals := strings.SplitN(arg, "=", defaultLenLognOption)
|
||||
name = vals[0]
|
||||
value = &vals[1]
|
||||
}
|
||||
|
||||
opt, err := def.ByName(key.ByPath(name, dash)...)
|
||||
if err != nil {
|
||||
return Err(err, name)
|
||||
}
|
||||
|
||||
return i.appendOption(value, opt)
|
||||
}
|
||||
|
||||
func (i *Argv) appendOption(data *string, opt config.Variable) error {
|
||||
if i.HasOption(opt.Key()...) && !option.IsSlice(opt) {
|
||||
return fmt.Errorf("%w: got: array, expect: %T", config.ErrUnexpectedType, param.Type(opt))
|
||||
}
|
||||
|
||||
var val string
|
||||
|
||||
switch {
|
||||
case data != nil:
|
||||
val = *data
|
||||
case option.IsBool(opt):
|
||||
val = "true"
|
||||
case len(i.args) > 0 && len(i.args[0]) > 0 && i.args[0][0:1] != "-":
|
||||
val = i.args[0]
|
||||
i.args = i.args[1:]
|
||||
default:
|
||||
return Err(config.ErrRequired, opt.Key()...)
|
||||
}
|
||||
|
||||
err := i.AppendOption(val, opt.Key()...)
|
||||
if err != nil {
|
||||
return Err(err, opt.Key()...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Argv) parseShortOption(arg string, def config.Variables) error {
|
||||
name := arg
|
||||
|
||||
var value string
|
||||
|
||||
if len(name) > 1 {
|
||||
name, value = arg[0:1], arg[1:]
|
||||
}
|
||||
|
||||
opt, err := def.ByParam(option.HasShort(name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
if option.IsBool(opt) && value != "" {
|
||||
err := i.parseShortOption(value, def)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value = ""
|
||||
}
|
||||
|
||||
if value == "" {
|
||||
return i.appendOption(nil, opt)
|
||||
}
|
||||
|
||||
return i.appendOption(&value, opt)
|
||||
}
|
||||
|
||||
func (i *Argv) parseArgument(arg string, def config.Variables) error {
|
||||
opt, err := def.ByParam(PosArgument(i.pos))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
i.pos++
|
||||
|
||||
if err := i.AppendOption(arg, opt.Key()...); err != nil {
|
||||
return Err(err, opt.Key()...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Argv) parse() error {
|
||||
if i.Len() > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, arg := range i.args {
|
||||
name, value, err := i.parseOne(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
if err := i.AppendOption(value, name); err != nil {
|
||||
return fmt.Errorf("append %v: %w", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseOne return name, value, error.
|
||||
//
|
||||
//nolint:cyclop
|
||||
func (p *Provider) parseOne(arg string) (string, string, error) {
|
||||
func (i *Argv) parseOne(arg string) (string, string, error) {
|
||||
if arg[0] != '-' {
|
||||
return "", "", nil
|
||||
}
|
||||
@@ -118,22 +262,3 @@ func (p *Provider) parseOne(arg string) (string, string, error) {
|
||||
|
||||
return name, val, nil
|
||||
}
|
||||
|
||||
func (p *Provider) parse() error {
|
||||
if len(p.args) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, arg := range os.Args[1:] {
|
||||
name, value, err := p.parseOne(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
p.args[name] = append(p.args[name], value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
42
provider/chain/chain.go
Normal file
42
provider/chain/chain.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
)
|
||||
|
||||
const Name = "chain"
|
||||
|
||||
func New(c ...config.Provider) config.BindProvider {
|
||||
return chain(c)
|
||||
}
|
||||
|
||||
type chain []config.Provider
|
||||
|
||||
func (c chain) Value(ctx context.Context, name ...string) (config.Value, error) {
|
||||
for _, in := range c {
|
||||
if val, err := in.Value(ctx, name...); err == nil {
|
||||
return val, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w", config.ErrNotFound)
|
||||
}
|
||||
|
||||
func (c chain) Bind(ctx context.Context, def config.Variables) error {
|
||||
for _, input := range c {
|
||||
if prov, ok := input.(config.BindProvider); ok {
|
||||
if err := prov.Bind(ctx, def); err != nil {
|
||||
return fmt.Errorf("%T:%w", input, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c chain) Name() string {
|
||||
return Name
|
||||
}
|
||||
2
provider/env/provider.go
vendored
2
provider/env/provider.go
vendored
@@ -52,5 +52,5 @@ func (p *Provider) Value(_ context.Context, path ...string) (config.Value, error
|
||||
return value.JString(val), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%v:%w", p.Name(), config.ErrValueNotFound)
|
||||
return nil, fmt.Errorf("%v:%w", p.Name(), config.ErrNotFound)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func NewEtcd(ctx context.Context) (*client.Client, error) {
|
||||
}
|
||||
|
||||
for name, val := range values {
|
||||
_, err = et.KV.Put(ctx, name, val)
|
||||
_, err = et.Put(ctx, name, val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module gitoa.ru/go-4devs/config/provider/etcd
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.8.4
|
||||
|
||||
@@ -3,6 +3,7 @@ package etcd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
@@ -34,6 +35,9 @@ func New(namespace, appName string, client Client) *Provider {
|
||||
},
|
||||
name: Name,
|
||||
prefix: namespace + Separator + appName,
|
||||
log: func(_ context.Context, format string, args ...any) {
|
||||
log.Printf(format, args...)
|
||||
},
|
||||
}
|
||||
|
||||
return &prov
|
||||
@@ -102,6 +106,7 @@ func (p *Provider) getEventKvs(events []*client.Event) ([]*pb.KeyValue, []*pb.Ke
|
||||
return kvs, old
|
||||
}
|
||||
|
||||
//nolint:nilnil
|
||||
func (p *Provider) resolve(key string, kvs []*pb.KeyValue) (config.Value, error) {
|
||||
for _, kv := range kvs {
|
||||
switch {
|
||||
|
||||
@@ -65,7 +65,7 @@ func ExampleClient_Watch() {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = etcdClient.KV.Put(ctx, "fdevs/config/example_db_dsn", "pgsql://user@pass:127.0.0.1:5432")
|
||||
_, err = etcdClient.Put(ctx, "fdevs/config/example_db_dsn", "pgsql://user@pass:127.0.0.1:5432")
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
|
||||
@@ -75,7 +75,7 @@ func ExampleClient_Watch() {
|
||||
defer func() {
|
||||
cancel()
|
||||
|
||||
if _, err = etcdClient.KV.Delete(context.Background(), "fdevs/config/example_db_dsn"); err != nil {
|
||||
if _, err = etcdClient.Delete(context.Background(), "fdevs/config/example_db_dsn"); err != nil {
|
||||
log.Print(err)
|
||||
|
||||
return
|
||||
@@ -107,7 +107,7 @@ func ExampleClient_Watch() {
|
||||
}
|
||||
|
||||
time.AfterFunc(time.Second, func() {
|
||||
if _, err := etcdClient.KV.Put(ctx, "fdevs/config/example_db_dsn", "mysql://localhost:5432"); err != nil {
|
||||
if _, err := etcdClient.Put(ctx, "fdevs/config/example_db_dsn", "mysql://localhost:5432"); err != nil {
|
||||
log.Print(err)
|
||||
|
||||
return
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestWatcher(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_, err = et.KV.Delete(context.Background(), "fdevs/config/test_watch")
|
||||
_, err = et.Delete(context.Background(), "fdevs/config/test_watch")
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestWatcher(t *testing.T) {
|
||||
wg.Add(6)
|
||||
|
||||
watch := func(cnt *int32) config.WatchCallback {
|
||||
return func(ctx context.Context, oldVar, newVar config.Value) error {
|
||||
return func(_ context.Context, oldVar, newVar config.Value) error {
|
||||
switch *cnt {
|
||||
case 0:
|
||||
assert.Equal(t, value(*cnt), newVar.String())
|
||||
@@ -77,7 +77,7 @@ func TestWatcher(t *testing.T) {
|
||||
case 2:
|
||||
_, perr := newVar.ParseString()
|
||||
require.NoError(t, perr)
|
||||
assert.Equal(t, "", newVar.String())
|
||||
assert.Empty(t, newVar.String())
|
||||
assert.Equal(t, value(*cnt-1), oldVar.String())
|
||||
default:
|
||||
t.Error("unexpected watch")
|
||||
@@ -96,11 +96,11 @@ func TestWatcher(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
time.AfterFunc(time.Second, func() {
|
||||
_, err = et.KV.Put(ctx, "fdevs/config/test_watch", value(0))
|
||||
_, err = et.Put(ctx, "fdevs/config/test_watch", value(0))
|
||||
require.NoError(t, err)
|
||||
_, err = et.KV.Put(ctx, "fdevs/config/test_watch", value(1))
|
||||
_, err = et.Put(ctx, "fdevs/config/test_watch", value(1))
|
||||
require.NoError(t, err)
|
||||
_, err = et.KV.Delete(ctx, "fdevs/config/test_watch")
|
||||
_, err = et.Delete(ctx, "fdevs/config/test_watch")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module gitoa.ru/go-4devs/config/provider/ini
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.8.4
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module gitoa.ru/go-4devs/config/provider/json
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/tidwall/gjson v1.17.0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package json
|
||||
package json //nolint:revive
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -25,6 +25,7 @@ func New(json []byte, opts ...Option) *Provider {
|
||||
return strings.Join(s, Separator)
|
||||
},
|
||||
data: json,
|
||||
name: Name,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
|
||||
44
provider/memory/def.go
Normal file
44
provider/memory/def.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
)
|
||||
|
||||
const NameDefault = "default"
|
||||
|
||||
var _ config.BindProvider = (*Default)(nil)
|
||||
|
||||
type Default struct {
|
||||
data Map
|
||||
name string
|
||||
}
|
||||
|
||||
func (a *Default) Value(ctx context.Context, key ...string) (config.Value, error) {
|
||||
if v, err := a.data.Value(ctx, key...); err == nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w", config.ErrNotFound)
|
||||
}
|
||||
|
||||
func (a *Default) Bind(_ context.Context, def config.Variables) error {
|
||||
for _, opt := range def.Variables() {
|
||||
if data, ok := option.DataDefaut(opt); ok {
|
||||
a.data.SetOption(data, opt.Key()...)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Default) Name() string {
|
||||
if a.name != "" {
|
||||
return a.name
|
||||
}
|
||||
|
||||
return NameDefault
|
||||
}
|
||||
92
provider/memory/map.go
Normal file
92
provider/memory/map.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/key"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
)
|
||||
|
||||
const NameMap = "map"
|
||||
|
||||
var _ config.BindProvider = (*Map)(nil)
|
||||
|
||||
type Map struct {
|
||||
mu sync.Mutex
|
||||
vals []config.Value
|
||||
idx key.Map
|
||||
name string
|
||||
}
|
||||
|
||||
func (m *Map) Len() int {
|
||||
return len(m.vals)
|
||||
}
|
||||
|
||||
func (m *Map) Value(_ context.Context, key ...string) (config.Value, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
idx, ok := m.idx.Index(key)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w", config.ErrNotFound)
|
||||
}
|
||||
|
||||
val := m.vals[idx]
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (m *Map) Bind(_ context.Context, _ config.Variables) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Map) Name() string {
|
||||
if m.name != "" {
|
||||
return m.name
|
||||
}
|
||||
|
||||
return NameMap
|
||||
}
|
||||
|
||||
func (m *Map) HasOption(key ...string) bool {
|
||||
_, ok := m.idx.Index(key)
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *Map) SetOption(val any, key ...string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if id, ok := m.idx.Index(key); ok {
|
||||
m.vals[id] = value.New(val)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
m.idx.Add(len(m.vals), key)
|
||||
m.vals = append(m.vals, value.New(val))
|
||||
}
|
||||
|
||||
func (m *Map) AppendOption(val string, keys ...string) error {
|
||||
id, ok := m.idx.Index(keys)
|
||||
if !ok {
|
||||
data := value.Strings{val}
|
||||
|
||||
m.SetOption(data, keys...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
old, tok := m.vals[id].(value.Strings)
|
||||
if !tok {
|
||||
return fmt.Errorf("%w:%T", config.ErrWrongType, m.vals[id])
|
||||
}
|
||||
|
||||
m.SetOption(old.Append(val), keys...)
|
||||
|
||||
return nil
|
||||
}
|
||||
44
provider/memory/validate.go
Normal file
44
provider/memory/validate.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/validator"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
)
|
||||
|
||||
var _ config.BindProvider = (*Map)(nil)
|
||||
|
||||
func Valid(in config.Provider) Validate {
|
||||
return Validate{
|
||||
Provider: in,
|
||||
name: "",
|
||||
}
|
||||
}
|
||||
|
||||
type Validate struct {
|
||||
config.Provider
|
||||
|
||||
name string
|
||||
}
|
||||
|
||||
func (a Validate) Bind(ctx context.Context, def config.Variables) error {
|
||||
for _, opt := range def.Variables() {
|
||||
if val, err := a.Value(ctx, opt.Key()...); err != nil && !value.IsEmpty(val) {
|
||||
if err := validator.Validate(opt.Key(), opt, val); err != nil {
|
||||
return option.Err(err, opt.Key())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (a Validate) Name() string {
|
||||
if a.name != "" {
|
||||
return a.name
|
||||
}
|
||||
|
||||
return a.Provider.Name()
|
||||
}
|
||||
@@ -1,15 +1,8 @@
|
||||
module gitoa.ru/go-4devs/config/provider/toml
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
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=
|
||||
|
||||
@@ -34,6 +34,7 @@ func configure(tree *toml.Tree, opts ...Option) *Provider {
|
||||
key: func(s []string) string {
|
||||
return strings.Join(s, Separator)
|
||||
},
|
||||
name: Name,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
|
||||
@@ -27,7 +27,7 @@ func (s Value) ParseInt() (int, error) {
|
||||
return int(v), nil
|
||||
}
|
||||
|
||||
func (s Value) Unmarshal(target interface{}) error {
|
||||
func (s Value) Unmarshal(target any) error {
|
||||
b, err := json.Marshal(s.Raw())
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", config.ErrInvalidValue, err)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module gitoa.ru/go-4devs/config/provider/vault
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/hashicorp/vault/api v1.11.0
|
||||
|
||||
@@ -68,19 +68,6 @@ func (p *Provider) Key(in []string) (string, string) {
|
||||
return p.prefix + Separator + path, val
|
||||
}
|
||||
|
||||
func (p *Provider) read(path, key string) (*api.Secret, error) {
|
||||
secret, err := p.client.Logical().Read(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read[%s:%s]:%w", path, key, err)
|
||||
}
|
||||
|
||||
if secret == nil && key != ValueName {
|
||||
return p.read(path+Separator+key, ValueName)
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func (p *Provider) Value(_ context.Context, key ...string) (config.Value, error) {
|
||||
path, field := p.Key(key)
|
||||
|
||||
@@ -98,7 +85,7 @@ func (p *Provider) Value(_ context.Context, key ...string) (config.Value, error)
|
||||
fmt.Errorf("%w: warn: %s, path:%s, field:%s, provider:%s", config.ErrValueNotFound, secret.Warnings, path, field, p.Name())
|
||||
}
|
||||
|
||||
data, ok := secret.Data["data"].(map[string]interface{})
|
||||
data, ok := secret.Data["data"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: path:%s, field:%s, provider:%s", config.ErrValueNotFound, path, field, p.Name())
|
||||
}
|
||||
@@ -118,3 +105,16 @@ func (p *Provider) Value(_ context.Context, key ...string) (config.Value, error)
|
||||
|
||||
return value.JBytes(md), nil
|
||||
}
|
||||
|
||||
func (p *Provider) read(path, key string) (*api.Secret, error) {
|
||||
secret, err := p.client.Logical().Read(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read[%s:%s]:%w", path, key, err)
|
||||
}
|
||||
|
||||
if secret == nil && key != ValueName {
|
||||
return p.read(path+Separator+key, ValueName)
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func NewVault() (*api.Client, error) {
|
||||
|
||||
cl.SetToken(tokenID)
|
||||
|
||||
values := map[string]map[string]interface{}{
|
||||
values := map[string]map[string]any{
|
||||
"database": {
|
||||
"duration": 1260000000000,
|
||||
"enabled": true,
|
||||
@@ -57,9 +57,9 @@ func NewVault() (*api.Client, error) {
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
func create(host, token, path string, data map[string]interface{}) error {
|
||||
func create(host, token, path string, data map[string]any) error {
|
||||
type Req struct {
|
||||
Data interface{} `json:"data"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
b, err := json.Marshal(Req{Data: data})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module gitoa.ru/go-4devs/config/provider/yaml
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
gitoa.ru/go-4devs/config v0.0.1
|
||||
|
||||
@@ -36,9 +36,9 @@ func New(yml []byte, opts ...Option) (*Provider, error) {
|
||||
}
|
||||
|
||||
func create(opts ...Option) *Provider {
|
||||
prov := Provider{
|
||||
name: Name,
|
||||
}
|
||||
var prov Provider
|
||||
|
||||
prov.name = Name
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&prov)
|
||||
@@ -65,6 +65,7 @@ func (p *Provider) Value(_ context.Context, path ...string) (config.Value, error
|
||||
func (p *Provider) With(data *yaml.Node) *Provider {
|
||||
return &Provider{
|
||||
data: node{Node: data},
|
||||
name: p.name,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +86,7 @@ func (n *node) read(name string, keys []string) (config.Value, error) {
|
||||
return value.Decode(val), nil
|
||||
}
|
||||
|
||||
func getData(node []*yaml.Node, keys []string) (func(interface{}) error, error) {
|
||||
func getData(node []*yaml.Node, keys []string) (func(any) error, error) {
|
||||
for idx := len(node) - 1; idx > 0; idx -= 2 {
|
||||
if node[idx-1].Value == keys[0] {
|
||||
if len(keys) > 1 {
|
||||
|
||||
@@ -67,8 +67,6 @@ func Time(value string) time.Time {
|
||||
}
|
||||
|
||||
// NewRead test data.
|
||||
//
|
||||
//nolint:cyclop
|
||||
func NewRead(expected any, key ...string) Read {
|
||||
return Read{
|
||||
Key: key,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package require
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -21,3 +22,12 @@ func NoErrorf(t *testing.T, err error, msg string, args ...any) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorIs(t *testing.T, err, ex error, msgAndArgs ...any) {
|
||||
t.Helper()
|
||||
|
||||
if errors.Is(ex, err) {
|
||||
t.Error(msgAndArgs...)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
19
validator/enum.go
Normal file
19
validator/enum.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
)
|
||||
|
||||
func Enum(enum ...string) Validator {
|
||||
return func(_ param.Params, in config.Value) error {
|
||||
val := in.String()
|
||||
if slices.Contains(enum, val) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return NewError(ErrInvalid, val, enum)
|
||||
}
|
||||
}
|
||||
30
validator/enum_test.go
Normal file
30
validator/enum_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/validator"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
)
|
||||
|
||||
func TestEnum(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
vars := option.String("test", "test")
|
||||
validValue := value.New("valid")
|
||||
invalidValue := value.New("invalid")
|
||||
|
||||
enum := validator.Enum("valid", "other", "three")
|
||||
|
||||
err := enum(vars, validValue)
|
||||
if err != nil {
|
||||
t.Errorf("expected valid value got err:%s", err)
|
||||
}
|
||||
|
||||
iErr := enum(vars, invalidValue)
|
||||
if !errors.Is(iErr, validator.ErrInvalid) {
|
||||
t.Errorf("expected err:%s, got: %s", validator.ErrInvalid, iErr)
|
||||
}
|
||||
}
|
||||
37
validator/error.go
Normal file
37
validator/error.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalid = errors.New("invalid value")
|
||||
ErrNotBlank = errors.New("not blank")
|
||||
)
|
||||
|
||||
func NewError(err error, value, expect any) Error {
|
||||
return Error{
|
||||
err: err,
|
||||
value: value,
|
||||
expect: expect,
|
||||
}
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
err error
|
||||
value any
|
||||
expect any
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("%s: expext: %s, given: %s", e.err, e.expect, e.value)
|
||||
}
|
||||
|
||||
func (e Error) Is(err error) bool {
|
||||
return errors.Is(e.err, err)
|
||||
}
|
||||
|
||||
func (e Error) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
83
validator/not_blank.go
Normal file
83
validator/not_blank.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
)
|
||||
|
||||
func NotBlank(fn param.Params, in config.Value) error {
|
||||
dataType := param.Type(fn)
|
||||
if option.IsSlice(fn) {
|
||||
return sliceByType(dataType, in)
|
||||
}
|
||||
|
||||
if notBlank(dataType, in) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrNotBlank
|
||||
}
|
||||
|
||||
func sliceByType(vType any, in config.Value) error {
|
||||
switch vType.(type) {
|
||||
case string:
|
||||
return sliceBy[string](in)
|
||||
case int:
|
||||
return sliceBy[int](in)
|
||||
case int64:
|
||||
return sliceBy[int64](in)
|
||||
case uint:
|
||||
return sliceBy[uint](in)
|
||||
case uint64:
|
||||
return sliceBy[uint64](in)
|
||||
case float64:
|
||||
return sliceBy[float64](in)
|
||||
case bool:
|
||||
return sliceBy[bool](in)
|
||||
case time.Duration:
|
||||
return sliceBy[time.Duration](in)
|
||||
case time.Time:
|
||||
return sliceBy[time.Time](in)
|
||||
default:
|
||||
return sliceBy[any](in)
|
||||
}
|
||||
}
|
||||
func sliceBy[T any](in config.Value) error {
|
||||
var data []T
|
||||
if err := in.Unmarshal(&data); err != nil {
|
||||
return fmt.Errorf("%w:%w", ErrNotBlank, err)
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w", ErrNotBlank)
|
||||
}
|
||||
|
||||
func notBlank(vType any, in config.Value) bool {
|
||||
switch vType.(type) {
|
||||
case int:
|
||||
return in.Int() != 0
|
||||
case int64:
|
||||
return in.Int64() != 0
|
||||
case uint:
|
||||
return in.Uint() != 0
|
||||
case uint64:
|
||||
return in.Uint64() != 0
|
||||
case float64:
|
||||
return in.Float64() != 0
|
||||
case time.Duration:
|
||||
return in.Duration() != 0
|
||||
case time.Time:
|
||||
return !in.Time().IsZero()
|
||||
case string:
|
||||
return len(in.String()) > 0
|
||||
default:
|
||||
return in.Any() != nil
|
||||
}
|
||||
}
|
||||
128
validator/not_blank_test.go
Normal file
128
validator/not_blank_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/validator"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
)
|
||||
|
||||
func TestNotBlank(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for name, ca := range casesNotBlank() {
|
||||
valid := validator.NotBlank
|
||||
|
||||
if err := valid(ca.vars, ca.value); err != nil {
|
||||
t.Errorf("case: %s, expected error <nil>, got: %s", name, err)
|
||||
}
|
||||
|
||||
if ca.empty == nil {
|
||||
ca.empty = value.EmptyValue()
|
||||
}
|
||||
|
||||
emptErr := valid(ca.vars, ca.empty)
|
||||
if emptErr == nil || !errors.Is(emptErr, validator.ErrNotBlank) {
|
||||
t.Errorf("case empty: %s, expect: %s, got:%v", name, validator.ErrNotBlank, emptErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type cases struct {
|
||||
vars config.Option
|
||||
value config.Value
|
||||
empty config.Value
|
||||
}
|
||||
|
||||
func casesNotBlank() map[string]cases {
|
||||
return map[string]cases{
|
||||
"any": {
|
||||
vars: option.New("any", "any", nil),
|
||||
value: value.New(float32(1)),
|
||||
},
|
||||
"array int": {
|
||||
vars: option.Int("int", "array int", option.Slice),
|
||||
value: value.New([]int{1}),
|
||||
empty: value.New([]int{}),
|
||||
},
|
||||
"array int64": {
|
||||
vars: option.Int64("int64", "array int64", option.Slice),
|
||||
value: value.New([]int64{1}),
|
||||
empty: value.New([]int64{}),
|
||||
},
|
||||
"array uint": {
|
||||
vars: option.Uint("uint", "array uint", option.Slice),
|
||||
value: value.New([]uint{1}),
|
||||
empty: value.New([]uint{}),
|
||||
},
|
||||
"array uint64": {
|
||||
vars: option.Uint64("uint64", "array uint64", option.Slice),
|
||||
value: value.New([]uint64{1}),
|
||||
empty: value.New([]uint64{}),
|
||||
},
|
||||
"array float64": {
|
||||
vars: option.Float64("float64", "array float64", option.Slice),
|
||||
value: value.New([]float64{0.2}),
|
||||
empty: value.New([]float64{}),
|
||||
},
|
||||
"array bool": {
|
||||
vars: option.Bool("bool", "array bool", option.Slice),
|
||||
value: value.New([]bool{true, false}),
|
||||
empty: value.New([]bool{}),
|
||||
},
|
||||
"array duration": {
|
||||
vars: option.Duration("duration", "array duration", option.Slice),
|
||||
value: value.New([]time.Duration{time.Second}),
|
||||
empty: value.New([]time.Duration{}),
|
||||
},
|
||||
"array time": {
|
||||
vars: option.Time("time", "array time", option.Slice),
|
||||
value: value.New([]time.Time{time.Now()}),
|
||||
empty: value.New([]time.Time{}),
|
||||
},
|
||||
"array string": {
|
||||
vars: option.String("string", "array string", option.Slice),
|
||||
value: value.New([]string{"value"}),
|
||||
empty: value.New([]string{}),
|
||||
},
|
||||
"int": {
|
||||
vars: option.Int("int", "int"),
|
||||
value: value.New(int(1)),
|
||||
},
|
||||
"int64": {
|
||||
vars: option.Int64("int64", "int64"),
|
||||
value: value.New(int64(2)),
|
||||
},
|
||||
"uint": {
|
||||
vars: option.Uint("uint", "uint"),
|
||||
value: value.New(uint(1)),
|
||||
empty: value.New([]uint{1}),
|
||||
},
|
||||
"uint64": {
|
||||
vars: option.Uint64("uint64", "uint64"),
|
||||
value: value.New(uint64(10)),
|
||||
},
|
||||
"float64": {
|
||||
vars: option.Float64("float64", "float64"),
|
||||
value: value.New(float64(.00001)),
|
||||
},
|
||||
"duration": {
|
||||
vars: option.Duration("duration", "duration"),
|
||||
value: value.New(time.Minute),
|
||||
empty: value.New("same string"),
|
||||
},
|
||||
"time": {
|
||||
vars: option.Time("time", "time"),
|
||||
value: value.New(time.Now()),
|
||||
},
|
||||
"string": {
|
||||
vars: option.String("string", "string"),
|
||||
value: value.New("string"),
|
||||
empty: value.New(""),
|
||||
},
|
||||
}
|
||||
}
|
||||
49
validator/valid.go
Normal file
49
validator/valid.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
)
|
||||
|
||||
func Chain(v ...Validator) Validator {
|
||||
return func(vr param.Params, in config.Value) error {
|
||||
for _, valid := range v {
|
||||
err := valid(vr, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const paramValid = "param.valid"
|
||||
|
||||
type Validator func(param.Params, config.Value) error
|
||||
|
||||
func Valid(in ...Validator) param.Option {
|
||||
return func(v param.Params) param.Params {
|
||||
return param.With(v, paramValid, in)
|
||||
}
|
||||
}
|
||||
|
||||
func Validate(key []string, fn param.Params, in config.Value) error {
|
||||
params, ok := fn.Param(paramValid)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
valids, _ := params.([]Validator)
|
||||
|
||||
for _, valid := range valids {
|
||||
err := valid(fn, in)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s:%w", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
33
validator/valid_test.go
Normal file
33
validator/valid_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/validator"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
)
|
||||
|
||||
func TestChain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
vars := option.String("test", "test")
|
||||
validValue := value.New("one")
|
||||
invalidValue := value.New([]string{"one"})
|
||||
|
||||
valid := validator.Chain(
|
||||
validator.NotBlank,
|
||||
validator.Enum("one", "two"),
|
||||
)
|
||||
|
||||
err := valid(vars, validValue)
|
||||
if err != nil {
|
||||
t.Errorf("expected valid value, got: %s", err)
|
||||
}
|
||||
|
||||
ierr := valid(vars, invalidValue)
|
||||
if !errors.Is(ierr, validator.ErrNotBlank) {
|
||||
t.Errorf("expected not blank, got:%s", ierr)
|
||||
}
|
||||
}
|
||||
1
value.go
1
value.go
@@ -25,6 +25,7 @@ type ReadValue interface {
|
||||
Bool() bool
|
||||
Duration() time.Duration
|
||||
Time() time.Time
|
||||
Any() any
|
||||
}
|
||||
|
||||
type ParseValue interface {
|
||||
|
||||
@@ -111,6 +111,10 @@ func (s Decode) Time() time.Time {
|
||||
return in
|
||||
}
|
||||
|
||||
func (s Decode) Any() any {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Decode) IsEquals(in config.Value) bool {
|
||||
return s.String() == in.String()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
)
|
||||
|
||||
var (
|
||||
emptyValue = Empty{Err: nil}
|
||||
)
|
||||
|
||||
func EmptyValue() config.Value {
|
||||
return emptyValue
|
||||
}
|
||||
|
||||
type Empty struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func IsEmpty(v config.Value) bool {
|
||||
return v == nil || v == emptyValue
|
||||
}
|
||||
|
||||
func (e Empty) Unmarshal(_ any) error {
|
||||
return e.Err
|
||||
}
|
||||
@@ -83,3 +98,13 @@ func (e Empty) Duration() time.Duration {
|
||||
func (e Empty) Time() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (e Empty) Any() any {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func (e Empty) IsEquals(v config.Value) bool {
|
||||
em, ok := v.(Empty)
|
||||
|
||||
return ok && errors.Is(em.Err, e.Err)
|
||||
}
|
||||
|
||||
123
value/float64.go
Normal file
123
value/float64.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
)
|
||||
|
||||
var _ config.Value = Float64(0)
|
||||
|
||||
type Float64 float64
|
||||
|
||||
func (f Float64) Any() any {
|
||||
return float64(f)
|
||||
}
|
||||
|
||||
func (f Float64) ParseString() (string, error) {
|
||||
return fmt.Sprint(float64(f)), nil
|
||||
}
|
||||
|
||||
func (f Float64) ParseInt() (int, error) {
|
||||
return int(f), nil
|
||||
}
|
||||
|
||||
func (f Float64) ParseInt64() (int64, error) {
|
||||
return int64(f), nil
|
||||
}
|
||||
|
||||
func (f Float64) ParseUint() (uint, error) {
|
||||
return uint(f), nil
|
||||
}
|
||||
|
||||
func (f Float64) ParseUint64() (uint64, error) {
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
func (f Float64) ParseFloat64() (float64, error) {
|
||||
return float64(f), nil
|
||||
}
|
||||
|
||||
func (f Float64) ParseBool() (bool, error) {
|
||||
return false, fmt.Errorf("float64:%w", config.ErrWrongType)
|
||||
}
|
||||
|
||||
func (f Float64) ParseDuration() (time.Duration, error) {
|
||||
return time.Duration(f), nil
|
||||
}
|
||||
|
||||
func (f Float64) ParseTime() (time.Time, error) {
|
||||
return time.Unix(0, int64(f*Float64(time.Second))), nil
|
||||
}
|
||||
|
||||
func (f Float64) Unmarshal(in any) error {
|
||||
v, ok := in.(*float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: expect *float64", config.ErrWrongType)
|
||||
}
|
||||
|
||||
*v = float64(f)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f Float64) IsEquals(val config.Value) bool {
|
||||
data, ok := val.(Float64)
|
||||
|
||||
return ok && data == f
|
||||
}
|
||||
|
||||
func (f Float64) String() string {
|
||||
data, _ := f.ParseString()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (f Float64) Int() int {
|
||||
data, _ := f.ParseInt()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (f Float64) Int64() int64 {
|
||||
data, _ := f.ParseInt64()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (f Float64) Uint() uint {
|
||||
data, _ := f.ParseUint()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (f Float64) Uint64() uint64 {
|
||||
data, _ := f.ParseUint64()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (f Float64) Float64() float64 {
|
||||
data, _ := f.ParseFloat64()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (f Float64) Bool() bool {
|
||||
data, _ := f.ParseBool()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (f Float64) Duration() time.Duration {
|
||||
data, _ := f.ParseDuration()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (f Float64) Time() time.Time {
|
||||
data, _ := f.ParseTime()
|
||||
|
||||
return data
|
||||
}
|
||||
51
value/float64_test.go
Normal file
51
value/float64_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package value_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"gitoa.ru/go-4devs/config/test/require"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
)
|
||||
|
||||
func TestFloat64_Unmarshal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := value.Float64(math.Pi)
|
||||
|
||||
var out float64
|
||||
|
||||
require.NoError(t, f.Unmarshal(&out))
|
||||
require.Equal(t, math.Pi, out)
|
||||
}
|
||||
|
||||
func TestFloat64_Any(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := value.Float64(math.Pi)
|
||||
|
||||
require.Equal(t, math.Pi, f.Any(), 0)
|
||||
}
|
||||
|
||||
func TestFloat64s_Unmarshal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data, err := json.Marshal([]float64{math.Pi, math.Sqrt2})
|
||||
require.NoError(t, err)
|
||||
|
||||
f := value.JBytes(data)
|
||||
|
||||
var out []float64
|
||||
|
||||
require.NoError(t, f.Unmarshal(&out))
|
||||
require.Equal(t, []float64{math.Pi, math.Sqrt2}, out)
|
||||
}
|
||||
|
||||
func TestFloat64s_Any(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := value.New([]float64{math.Pi, math.Sqrt2})
|
||||
|
||||
require.Equal(t, []float64{math.Pi, math.Sqrt2}, f.Any())
|
||||
}
|
||||
@@ -18,7 +18,7 @@ func ParseDuration(raw string) (time.Duration, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func ParseInt(s string) (int64, error) {
|
||||
func ParseInt64(s string) (int64, error) {
|
||||
i, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w: %w", config.ErrInvalidValue, err)
|
||||
@@ -27,13 +27,13 @@ func ParseInt(s string) (int64, error) {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func ParseUint(s string) (uint64, error) {
|
||||
func ParseUint(s string) (uint, error) {
|
||||
i, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w: %w", config.ErrInvalidValue, err)
|
||||
}
|
||||
|
||||
return i, nil
|
||||
return uint(i), nil
|
||||
}
|
||||
|
||||
func Atoi(s string) (int, error) {
|
||||
@@ -79,3 +79,12 @@ func JUnmarshal(b []byte, v any) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseUint64(s string) (uint64, error) {
|
||||
i, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w: %w", config.ErrInvalidValue, err)
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
@@ -23,20 +23,15 @@ func (s JBytes) ParseInt() (int, error) {
|
||||
}
|
||||
|
||||
func (s JBytes) ParseInt64() (int64, error) {
|
||||
return ParseInt(s.String())
|
||||
return ParseInt64(s.String())
|
||||
}
|
||||
|
||||
func (s JBytes) ParseUint() (uint, error) {
|
||||
u64, err := ParseUint(s.String())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint(u64), nil
|
||||
return ParseUint(s.String())
|
||||
}
|
||||
|
||||
func (s JBytes) ParseUint64() (uint64, error) {
|
||||
return ParseUint(s.String())
|
||||
return ParseUint64(s.String())
|
||||
}
|
||||
|
||||
func (s JBytes) ParseFloat64() (float64, error) {
|
||||
@@ -111,6 +106,10 @@ func (s JBytes) Time() time.Time {
|
||||
return in
|
||||
}
|
||||
|
||||
func (s JBytes) Any() any {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s JBytes) IsEquals(in config.Value) bool {
|
||||
return s.String() == in.String()
|
||||
}
|
||||
|
||||
@@ -23,20 +23,15 @@ func (s JString) ParseInt() (int, error) {
|
||||
}
|
||||
|
||||
func (s JString) ParseInt64() (int64, error) {
|
||||
return ParseInt(s.String())
|
||||
return ParseInt64(s.String())
|
||||
}
|
||||
|
||||
func (s JString) ParseUint() (uint, error) {
|
||||
u64, err := ParseUint(s.String())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint(u64), nil
|
||||
return ParseUint(s.String())
|
||||
}
|
||||
|
||||
func (s JString) ParseUint64() (uint64, error) {
|
||||
return ParseUint(s.String())
|
||||
return ParseUint64(s.String())
|
||||
}
|
||||
|
||||
func (s JString) ParseFloat64() (float64, error) {
|
||||
@@ -111,6 +106,10 @@ func (s JString) Time() time.Time {
|
||||
return in
|
||||
}
|
||||
|
||||
func (s JString) Any() any {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s JString) IsEquals(in config.Value) bool {
|
||||
return s.String() == in.String()
|
||||
}
|
||||
|
||||
161
value/string.go
Normal file
161
value/string.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
)
|
||||
|
||||
var _ config.Value = (String)("")
|
||||
|
||||
type String string
|
||||
|
||||
func (s String) ParseString() (string, error) {
|
||||
return string(s), nil
|
||||
}
|
||||
|
||||
func (s String) Unmarshal(in any) error {
|
||||
v, ok := in.(*string)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: expect *string", config.ErrWrongType)
|
||||
}
|
||||
|
||||
*v = string(s)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s String) Any() any {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s String) ParseInt() (int, error) {
|
||||
v, err := strconv.Atoi(string(s))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("string int:%w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (s String) Int64() int64 {
|
||||
out, _ := s.ParseInt64()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (s String) ParseInt64() (int64, error) {
|
||||
v, err := strconv.ParseInt(string(s), 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("string int64:%w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (s String) ParseUint() (uint, error) {
|
||||
uout, err := s.ParseUint64()
|
||||
|
||||
return uint(uout), err
|
||||
}
|
||||
|
||||
func (s String) ParseUint64() (uint64, error) {
|
||||
uout, err := strconv.ParseUint(string(s), 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("string uint:%w", err)
|
||||
}
|
||||
|
||||
return uout, nil
|
||||
}
|
||||
|
||||
func (s String) ParseFloat64() (float64, error) {
|
||||
fout, err := strconv.ParseFloat(string(s), 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("string float64:%w", err)
|
||||
}
|
||||
|
||||
return fout, nil
|
||||
}
|
||||
|
||||
func (s String) ParseBool() (bool, error) {
|
||||
v, err := strconv.ParseBool(string(s))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("string bool:%w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (s String) ParseDuration() (time.Duration, error) {
|
||||
v, err := time.ParseDuration(string(s))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("string duration:%w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (s String) ParseTime() (time.Time, error) {
|
||||
v, err := time.Parse(time.RFC3339, string(s))
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("string time:%w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (s String) IsEquals(val config.Value) bool {
|
||||
data, ok := val.(String)
|
||||
|
||||
return ok && data == s
|
||||
}
|
||||
|
||||
func (s String) String() string {
|
||||
data, _ := s.ParseString()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (s String) Int() int {
|
||||
data, _ := s.ParseInt()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (s String) Uint() uint {
|
||||
data, _ := s.ParseUint()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (s String) Uint64() uint64 {
|
||||
data, _ := s.ParseUint64()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (s String) Float64() float64 {
|
||||
data, _ := s.ParseFloat64()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (s String) Bool() bool {
|
||||
data, _ := s.ParseBool()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (s String) Duration() time.Duration {
|
||||
data, _ := s.ParseDuration()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (s String) Time() time.Time {
|
||||
data, _ := s.ParseTime()
|
||||
|
||||
return data
|
||||
}
|
||||
34
value/string_test.go
Normal file
34
value/string_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package value_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/test/require"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
)
|
||||
|
||||
func TestStringUnmarshal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
st := value.String("test")
|
||||
|
||||
data, err := json.Marshal([]string{"test1", "test2"})
|
||||
require.NoError(t, err)
|
||||
|
||||
sta := value.JBytes(data)
|
||||
|
||||
ac := ""
|
||||
require.NoError(t, st.Unmarshal(&ac))
|
||||
require.Equal(t, "test", ac)
|
||||
|
||||
aca := []string{}
|
||||
require.NoError(t, sta.Unmarshal(&aca))
|
||||
require.Equal(t, []string{"test1", "test2"}, aca)
|
||||
|
||||
require.ErrorIs(t, sta.Unmarshal(ac), config.ErrWrongType)
|
||||
require.ErrorIs(t, sta.Unmarshal(&ac), config.ErrWrongType)
|
||||
require.ErrorIs(t, st.Unmarshal(aca), config.ErrWrongType)
|
||||
require.ErrorIs(t, st.Unmarshal(&aca), config.ErrWrongType)
|
||||
}
|
||||
179
value/strings.go
Normal file
179
value/strings.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
)
|
||||
|
||||
type Strings []string
|
||||
|
||||
func (s Strings) Unmarshal(in any) error {
|
||||
data, jerr := json.Marshal([]string(s))
|
||||
if jerr != nil {
|
||||
return fmt.Errorf("failed load data:%w", jerr)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, in); err != nil {
|
||||
return fmt.Errorf("unmarshal:%w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Strings) ParseString() (string, error) {
|
||||
return s.String(), nil
|
||||
}
|
||||
|
||||
func (s Strings) ParseInt() (int, error) {
|
||||
data, err := Atoi(s.String())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("strings: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s Strings) ParseInt64() (int64, error) {
|
||||
data, err := ParseInt64(s.String())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("strings: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s Strings) ParseUint() (uint, error) {
|
||||
data, err := ParseUint(s.String())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("strings: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s Strings) ParseUint64() (uint64, error) {
|
||||
data, err := ParseUint64(s.String())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("strings: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s Strings) ParseFloat64() (float64, error) {
|
||||
data, err := ParseFloat(s.String())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("strings: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s Strings) ParseBool() (bool, error) {
|
||||
data, err := ParseBool(s.String())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("strings: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s Strings) ParseDuration() (time.Duration, error) {
|
||||
data, err := ParseDuration(s.String())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("strings: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s Strings) ParseTime() (time.Time, error) {
|
||||
data, err := ParseTime(s.String())
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("strings: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s Strings) String() string {
|
||||
if len(s) == 1 {
|
||||
return s[0]
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", []string(s))
|
||||
}
|
||||
|
||||
func (s Strings) Int() int {
|
||||
val, _ := s.ParseInt()
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (s Strings) Int64() int64 {
|
||||
val, _ := s.ParseInt64()
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (s Strings) Uint() uint {
|
||||
val, _ := s.ParseUint()
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (s Strings) Uint64() uint64 {
|
||||
val, _ := s.ParseUint64()
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (s Strings) Float64() float64 {
|
||||
val, _ := s.ParseFloat64()
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (s Strings) Bool() bool {
|
||||
val, _ := s.ParseBool()
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (s Strings) Duration() time.Duration {
|
||||
val, _ := s.ParseDuration()
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (s Strings) Time() time.Time {
|
||||
val, _ := s.ParseTime()
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (s Strings) Any() any {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Strings) Append(data string) Strings {
|
||||
return append(s, data)
|
||||
}
|
||||
|
||||
func (s Strings) IsEquals(in config.Value) bool {
|
||||
val, ok := in.(Strings)
|
||||
if !ok || len(s) != len(val) {
|
||||
return false
|
||||
}
|
||||
|
||||
for idx := range val {
|
||||
if s[idx] != val[idx] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
141
value/value.go
141
value/value.go
@@ -1,8 +1,8 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
@@ -10,6 +10,15 @@ import (
|
||||
|
||||
var _ config.Value = (*Value)(nil)
|
||||
|
||||
func New(data any) config.Value {
|
||||
switch val := data.(type) {
|
||||
case config.Value:
|
||||
return val
|
||||
default:
|
||||
return Value{Val: data}
|
||||
}
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
Val any
|
||||
}
|
||||
@@ -62,10 +71,6 @@ func (s Value) Duration() time.Duration {
|
||||
return v
|
||||
}
|
||||
|
||||
func (s Value) Raw() any {
|
||||
return s.Val
|
||||
}
|
||||
|
||||
func (s Value) Time() time.Time {
|
||||
v, _ := s.ParseTime()
|
||||
|
||||
@@ -73,20 +78,11 @@ func (s Value) Time() time.Time {
|
||||
}
|
||||
|
||||
func (s Value) Unmarshal(target any) error {
|
||||
if v, ok := s.Raw().([]byte); ok {
|
||||
err := json.Unmarshal(v, target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", config.ErrInvalidValue, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return config.ErrInvalidValue
|
||||
return typeAssert(s.Val, target)
|
||||
}
|
||||
|
||||
func (s Value) ParseInt() (int, error) {
|
||||
if r, ok := s.Raw().(int); ok {
|
||||
if r, ok := s.Any().(int); ok {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@@ -94,7 +90,7 @@ func (s Value) ParseInt() (int, error) {
|
||||
}
|
||||
|
||||
func (s Value) ParseInt64() (int64, error) {
|
||||
if r, ok := s.Raw().(int64); ok {
|
||||
if r, ok := s.Any().(int64); ok {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@@ -102,7 +98,7 @@ func (s Value) ParseInt64() (int64, error) {
|
||||
}
|
||||
|
||||
func (s Value) ParseUint() (uint, error) {
|
||||
if r, ok := s.Raw().(uint); ok {
|
||||
if r, ok := s.Any().(uint); ok {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@@ -110,7 +106,7 @@ func (s Value) ParseUint() (uint, error) {
|
||||
}
|
||||
|
||||
func (s Value) ParseUint64() (uint64, error) {
|
||||
if r, ok := s.Raw().(uint64); ok {
|
||||
if r, ok := s.Any().(uint64); ok {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@@ -118,7 +114,7 @@ func (s Value) ParseUint64() (uint64, error) {
|
||||
}
|
||||
|
||||
func (s Value) ParseFloat64() (float64, error) {
|
||||
if r, ok := s.Raw().(float64); ok {
|
||||
if r, ok := s.Any().(float64); ok {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@@ -126,7 +122,7 @@ func (s Value) ParseFloat64() (float64, error) {
|
||||
}
|
||||
|
||||
func (s Value) ParseString() (string, error) {
|
||||
if r, ok := s.Raw().(string); ok {
|
||||
if r, ok := s.Any().(string); ok {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@@ -134,7 +130,7 @@ func (s Value) ParseString() (string, error) {
|
||||
}
|
||||
|
||||
func (s Value) ParseBool() (bool, error) {
|
||||
if b, ok := s.Raw().(bool); ok {
|
||||
if b, ok := s.Any().(bool); ok {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
@@ -142,7 +138,7 @@ func (s Value) ParseBool() (bool, error) {
|
||||
}
|
||||
|
||||
func (s Value) ParseDuration() (time.Duration, error) {
|
||||
if b, ok := s.Raw().(time.Duration); ok {
|
||||
if b, ok := s.Any().(time.Duration); ok {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
@@ -150,7 +146,7 @@ func (s Value) ParseDuration() (time.Duration, error) {
|
||||
}
|
||||
|
||||
func (s Value) ParseTime() (time.Time, error) {
|
||||
if b, ok := s.Raw().(time.Time); ok {
|
||||
if b, ok := s.Any().(time.Time); ok {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
@@ -158,5 +154,100 @@ func (s Value) ParseTime() (time.Time, error) {
|
||||
}
|
||||
|
||||
func (s Value) IsEquals(in config.Value) bool {
|
||||
return s.String() == in.String()
|
||||
return s.Any() == in.Any()
|
||||
}
|
||||
|
||||
func (s Value) Any() any {
|
||||
return s.Val
|
||||
}
|
||||
|
||||
func typeAssert(source, target any) error {
|
||||
if source == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if directTypeAssert(source, target) {
|
||||
return nil
|
||||
}
|
||||
|
||||
valTarget := reflect.ValueOf(target)
|
||||
if !valTarget.IsValid() || valTarget.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("ptr target:%w", config.ErrInvalidValue)
|
||||
}
|
||||
|
||||
valTarget = valTarget.Elem()
|
||||
|
||||
if !valTarget.IsValid() {
|
||||
return fmt.Errorf("elem targer:%w", config.ErrInvalidValue)
|
||||
}
|
||||
|
||||
valSource := reflect.ValueOf(source)
|
||||
if !valSource.IsValid() {
|
||||
return fmt.Errorf("source:%w", config.ErrInvalidValue)
|
||||
}
|
||||
|
||||
valSource = deReference(valSource)
|
||||
if err := canSet(valSource, valTarget); err != nil {
|
||||
return fmt.Errorf("can set:%w", err)
|
||||
}
|
||||
|
||||
valTarget.Set(valSource)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func canSet(source, target reflect.Value) error {
|
||||
if source.Kind() != target.Kind() {
|
||||
return fmt.Errorf("source=%v target=%v:%w", source.Kind(), target.Kind(), config.ErrInvalidValue)
|
||||
}
|
||||
|
||||
if source.Kind() == reflect.Slice && source.Type().Elem().Kind() != target.Type().Elem().Kind() {
|
||||
return fmt.Errorf("slice source=%v, slice target=%v:%w",
|
||||
source.Type().Elem().Kind(), target.Type().Elem().Kind(), config.ErrInvalidValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func directTypeAssert(source, target any) bool {
|
||||
var ok bool
|
||||
|
||||
switch val := target.(type) {
|
||||
case *string:
|
||||
*val, ok = source.(string)
|
||||
case *[]byte:
|
||||
*val, ok = source.([]byte)
|
||||
case *int:
|
||||
*val, ok = source.(int)
|
||||
case *int64:
|
||||
*val, ok = source.(int64)
|
||||
case *uint:
|
||||
*val, ok = source.(uint)
|
||||
case *uint64:
|
||||
*val, ok = source.(uint64)
|
||||
case *bool:
|
||||
*val, ok = source.(bool)
|
||||
case *float64:
|
||||
*val, ok = source.(float64)
|
||||
case *time.Duration:
|
||||
*val, ok = source.(time.Duration)
|
||||
case *time.Time:
|
||||
*val, ok = source.(time.Time)
|
||||
case *[]string:
|
||||
*val, ok = source.([]string)
|
||||
case *map[string]string:
|
||||
*val, ok = source.(map[string]string)
|
||||
case *map[string]any:
|
||||
*val, ok = source.(map[string]any)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func deReference(v reflect.Value) reflect.Value {
|
||||
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && !v.IsNil() {
|
||||
return v.Elem()
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
122
variable.go
Normal file
122
variable.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitoa.ru/go-4devs/config/key"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
)
|
||||
|
||||
func NewVar(opt Option) Variable {
|
||||
return Variable{
|
||||
param: opt,
|
||||
names: []string{opt.Name()},
|
||||
}
|
||||
}
|
||||
|
||||
type Variable struct {
|
||||
names []string
|
||||
param param.Params
|
||||
}
|
||||
|
||||
func (v Variable) Param(key any) (any, bool) {
|
||||
return v.param.Param(key)
|
||||
}
|
||||
|
||||
func (v Variable) Key() []string {
|
||||
return v.names
|
||||
}
|
||||
|
||||
func (v Variable) Type() any {
|
||||
return param.Type(v)
|
||||
}
|
||||
|
||||
func (v Variable) With(opt Option) Variable {
|
||||
return Variable{
|
||||
names: append(v.Key(), opt.Name()),
|
||||
param: param.Chain(v.param, opt),
|
||||
}
|
||||
}
|
||||
|
||||
func NewVars(opts ...Option) Vars {
|
||||
vars := newVars(opts...)
|
||||
pos := key.Map{}
|
||||
|
||||
for idx, one := range vars {
|
||||
pos.Add(idx, one.Key())
|
||||
}
|
||||
|
||||
return Vars{
|
||||
vars: vars,
|
||||
pos: pos,
|
||||
}
|
||||
}
|
||||
|
||||
func newVars(opts ...Option) []Variable {
|
||||
vars := make([]Variable, 0, len(opts))
|
||||
for _, opt := range opts {
|
||||
one := NewVar(opt)
|
||||
switch data := opt.(type) {
|
||||
case Group:
|
||||
vars = append(vars, groupVars(one, data.Options()...)...)
|
||||
default:
|
||||
vars = append(vars, one)
|
||||
}
|
||||
}
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
func groupVars(parent Variable, opts ...Option) []Variable {
|
||||
vars := make([]Variable, 0, len(opts))
|
||||
for _, opt := range opts {
|
||||
switch data := opt.(type) {
|
||||
case Group:
|
||||
vars = append(vars, groupVars(parent.With(opt), data.Options()...)...)
|
||||
default:
|
||||
vars = append(vars, parent.With(opt))
|
||||
}
|
||||
}
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
type Vars struct {
|
||||
vars []Variable
|
||||
pos key.Map
|
||||
}
|
||||
|
||||
func (d Vars) Variables() []Variable {
|
||||
return d.vars
|
||||
}
|
||||
|
||||
func (d Vars) ByName(path ...string) (Variable, error) {
|
||||
if idx, ok := d.pos.Index(path); ok {
|
||||
return d.vars[idx], nil
|
||||
}
|
||||
|
||||
return Variable{}, ErrNotFound
|
||||
}
|
||||
|
||||
func (d Vars) ByParam(fn param.Has) (Variable, error) {
|
||||
opts := d.filter(fn)
|
||||
switch {
|
||||
case len(opts) == 0:
|
||||
return Variable{}, fmt.Errorf("%w:%T", ErrNotFound, fn)
|
||||
case len(opts) > 1:
|
||||
return Variable{}, fmt.Errorf("%w:%v", ErrToManyArgs, opts)
|
||||
default:
|
||||
return opts[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d Vars) filter(fn param.Has) []Variable {
|
||||
opts := make([]Variable, 0, len(d.vars))
|
||||
for idx := range d.vars {
|
||||
if fn(d.vars[idx]) {
|
||||
opts = append(opts, d.vars[idx])
|
||||
}
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
Reference in New Issue
Block a user