config: add Validate func, small lint fixes

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2022-03-22 14:54:43 +03:00
parent 5527b16cd8
commit c64218d52c
15 changed files with 177 additions and 52 deletions

View File

@ -50,6 +50,7 @@ type Handler func(Event) error
// Events contains multiple events // Events contains multiple events
type Events []Event type Events []Event
// Ack try to ack all events and return
func (evs Events) Ack() error { func (evs Events) Ack() error {
var err error var err error
for _, ev := range evs { for _, ev := range evs {
@ -60,6 +61,7 @@ func (evs Events) Ack() error {
return nil return nil
} }
// SetError sets error on event
func (evs Events) SetError(err error) { func (evs Events) SetError(err error) {
for _, ev := range evs { for _, ev := range evs {
ev.SetError(err) ev.SetError(err)

View File

@ -4,11 +4,16 @@ package config // import "go.unistack.org/micro/v3/config"
import ( import (
"context" "context"
"errors" "errors"
"reflect"
"time" "time"
) )
type Validator interface {
Validate() error
}
// DefaultConfig default config // DefaultConfig default config
var DefaultConfig Config = NewConfig() var DefaultConfig = NewConfig()
// DefaultWatcherMinInterval default min interval for poll changes // DefaultWatcherMinInterval default min interval for poll changes
var DefaultWatcherMinInterval = 5 * time.Second var DefaultWatcherMinInterval = 5 * time.Second
@ -67,7 +72,59 @@ func Load(ctx context.Context, cs []Config, opts ...LoadOption) error {
return nil return nil
} }
// Validate runs Validate() error func for each struct field
func Validate(ctx context.Context, cfg interface{}) error {
if cfg == nil {
return nil
}
if v, ok := cfg.(Validator); ok {
if err := v.Validate(); err != nil {
return err
}
}
sv := reflect.ValueOf(cfg)
if sv.Kind() == reflect.Ptr {
sv = sv.Elem()
}
if sv.Kind() != reflect.Struct {
return nil
}
typ := sv.Type()
for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx)
val := sv.Field(idx)
if !val.IsValid() || len(fld.PkgPath) != 0 {
continue
}
if v, ok := val.Interface().(Validator); ok {
if err := v.Validate(); err != nil {
return err
}
}
switch val.Kind() {
case reflect.Ptr:
if reflect.Indirect(val).Kind() == reflect.Struct {
if err := Validate(ctx, val.Interface()); err != nil {
return err
}
}
case reflect.Struct:
if err := Validate(ctx, val.Interface()); err != nil {
return err
}
}
}
return nil
}
var ( var (
// DefaultAfterLoad default func that runs after config load
DefaultAfterLoad = func(ctx context.Context, c Config) error { DefaultAfterLoad = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterLoad { for _, fn := range c.Options().AfterLoad {
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
@ -79,7 +136,7 @@ var (
} }
return nil return nil
} }
// DefaultAfterSave default func that runs after config save
DefaultAfterSave = func(ctx context.Context, c Config) error { DefaultAfterSave = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterSave { for _, fn := range c.Options().AfterSave {
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
@ -91,7 +148,7 @@ var (
} }
return nil return nil
} }
// DefaultBeforeLoad default func that runs before config load
DefaultBeforeLoad = func(ctx context.Context, c Config) error { DefaultBeforeLoad = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeLoad { for _, fn := range c.Options().BeforeLoad {
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
@ -103,11 +160,11 @@ var (
} }
return nil return nil
} }
// DefaultBeforeSave default func that runs befora config save
DefaultBeforeSave = func(ctx context.Context, c Config) error { DefaultBeforeSave = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeSave { for _, fn := range c.Options().BeforeSave {
if err := fn(ctx, c); err != nil { if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s BeforeSavec err: %v", c.String(), err) c.Options().Logger.Errorf(ctx, "%s BeforeSave err: %v", c.String(), err)
if !c.Options().AllowFail { if !c.Options().AllowFail {
return err return err
} }

View File

@ -8,30 +8,46 @@ import (
"go.unistack.org/micro/v3/config" "go.unistack.org/micro/v3/config"
) )
type Cfg struct { type cfg struct {
StringValue string `default:"string_value"` StringValue string `default:"string_value"`
IgnoreValue string `json:"-"` IgnoreValue string `json:"-"`
StructValue struct { StructValue *cfgStructValue
StringValue string `default:"string_value"`
}
IntValue int `default:"99"` IntValue int `default:"99"`
} }
type cfgStructValue struct {
StringValue string `default:"string_value"`
}
func (c *cfg) Validate() error {
if c.IntValue != 10 {
return fmt.Errorf("invalid IntValue %d != %d", 10, c.IntValue)
}
return nil
}
func (c *cfgStructValue) Validate() error {
if c.StringValue != "string_value" {
return fmt.Errorf("invalid StringValue %s != %s", "string_value", c.StringValue)
}
return nil
}
func TestDefault(t *testing.T) { func TestDefault(t *testing.T) {
ctx := context.Background() ctx := context.Background()
conf := &Cfg{IntValue: 10} conf := &cfg{IntValue: 10}
blfn := func(ctx context.Context, cfg config.Config) error { blfn := func(_ context.Context, c config.Config) error {
nconf, ok := cfg.Options().Struct.(*Cfg) nconf, ok := c.Options().Struct.(*cfg)
if !ok { if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) return fmt.Errorf("failed to get Struct from options: %v", c.Options())
} }
nconf.StringValue = "before_load" nconf.StringValue = "before_load"
return nil return nil
} }
alfn := func(ctx context.Context, cfg config.Config) error { alfn := func(_ context.Context, c config.Config) error {
nconf, ok := cfg.Options().Struct.(*Cfg) nconf, ok := c.Options().Struct.(*cfg)
if !ok { if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options()) return fmt.Errorf("failed to get Struct from options: %v", c.Options())
} }
nconf.StringValue = "after_load" nconf.StringValue = "after_load"
return nil return nil
@ -50,3 +66,19 @@ func TestDefault(t *testing.T) {
_ = conf _ = conf
// t.Logf("%#+v\n", conf) // t.Logf("%#+v\n", conf)
} }
func TestValidate(t *testing.T) {
ctx := context.Background()
conf := &cfg{IntValue: 10}
cfg := config.NewConfig(config.Struct(conf))
if err := cfg.Init(); err != nil {
t.Fatal(err)
}
if err := cfg.Load(ctx); err != nil {
t.Fatal(err)
}
if err := config.Validate(ctx, conf); err != nil {
t.Fatal(err)
}
}

View File

@ -69,6 +69,7 @@ type LoadOptions struct {
Context context.Context Context context.Context
} }
// NewLoadOptions create LoadOptions struct with provided opts
func NewLoadOptions(opts ...LoadOption) LoadOptions { func NewLoadOptions(opts ...LoadOption) LoadOptions {
options := LoadOptions{} options := LoadOptions{}
for _, o := range opts { for _, o := range opts {
@ -221,8 +222,10 @@ type WatchOptions struct {
Coalesce bool Coalesce bool
} }
// WatchOption func signature
type WatchOption func(*WatchOptions) type WatchOption func(*WatchOptions)
// NewWatchOptions create WatchOptions struct with provided opts
func NewWatchOptions(opts ...WatchOption) WatchOptions { func NewWatchOptions(opts ...WatchOption) WatchOptions {
options := WatchOptions{ options := WatchOptions{
Context: context.Background(), Context: context.Background(),

View File

@ -11,7 +11,9 @@ import (
) )
var ( var (
// ErrStepNotExists returns when step not found
ErrStepNotExists = errors.New("step not exists") ErrStepNotExists = errors.New("step not exists")
// ErrMissingClient returns when client.Client is missing
ErrMissingClient = errors.New("client not set") ErrMissingClient = errors.New("client not set")
) )
@ -36,6 +38,7 @@ func (m *RawMessage) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// Message used to transfer data between steps
type Message struct { type Message struct {
Header metadata.Metadata Header metadata.Metadata
Body RawMessage Body RawMessage
@ -67,6 +70,7 @@ type Step interface {
Response() *Message Response() *Message
} }
// Status contains step current status
type Status int type Status int
func (status Status) String() string { func (status Status) String() string {
@ -74,15 +78,22 @@ func (status Status) String() string {
} }
const ( const (
// StatusPending step waiting to start
StatusPending Status = iota StatusPending Status = iota
// StatusRunning step is running
StatusRunning StatusRunning
// StatusFailure step competed with error
StatusFailure StatusFailure
// StatusSuccess step completed without error
StatusSuccess StatusSuccess
// StatusAborted step aborted while it running
StatusAborted StatusAborted
// StatusSuspend step suspended
StatusSuspend StatusSuspend
) )
var ( var (
// StatusString contains map status => string
StatusString = map[Status]string{ StatusString = map[Status]string{
StatusPending: "StatusPending", StatusPending: "StatusPending",
StatusRunning: "StatusRunning", StatusRunning: "StatusRunning",
@ -91,6 +102,7 @@ var (
StatusAborted: "StatusAborted", StatusAborted: "StatusAborted",
StatusSuspend: "StatusSuspend", StatusSuspend: "StatusSuspend",
} }
// StringStatus contains map string => status
StringStatus = map[string]Status{ StringStatus = map[string]Status{
"StatusPending": StatusPending, "StatusPending": StatusPending,
"StatusRunning": StatusRunning, "StatusRunning": StatusRunning,
@ -144,6 +156,7 @@ var (
atomicSteps atomic.Value atomicSteps atomic.Value
) )
// RegisterStep register own step with workflow
func RegisterStep(step Step) { func RegisterStep(step Step) {
flowMu.Lock() flowMu.Lock()
steps, _ := atomicSteps.Load().([]Step) steps, _ := atomicSteps.Load().([]Step)

View File

@ -91,7 +91,7 @@ func Store(s store.Store) Option {
} }
} }
// WorflowOption func signature // WorkflowOption func signature
type WorkflowOption func(*WorkflowOptions) type WorkflowOption func(*WorkflowOptions)
// WorkflowOptions holds workflow options // WorkflowOptions holds workflow options

View File

@ -11,6 +11,7 @@ import (
) )
var ( var (
// DefaultClientCallObserver called by wrapper in client Call
DefaultClientCallObserver = func(ctx context.Context, req client.Request, rsp interface{}, opts []client.CallOption, err error) []string { DefaultClientCallObserver = func(ctx context.Context, req client.Request, rsp interface{}, opts []client.CallOption, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()} labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil { if err != nil {
@ -19,6 +20,7 @@ var (
return labels return labels
} }
// DefaultClientStreamObserver called by wrapper in client Stream
DefaultClientStreamObserver = func(ctx context.Context, req client.Request, opts []client.CallOption, stream client.Stream, err error) []string { DefaultClientStreamObserver = func(ctx context.Context, req client.Request, opts []client.CallOption, stream client.Stream, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()} labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil { if err != nil {
@ -27,6 +29,7 @@ var (
return labels return labels
} }
// DefaultClientPublishObserver called by wrapper in client Publish
DefaultClientPublishObserver = func(ctx context.Context, msg client.Message, opts []client.PublishOption, err error) []string { DefaultClientPublishObserver = func(ctx context.Context, msg client.Message, opts []client.PublishOption, err error) []string {
labels := []string{"endpoint", msg.Topic()} labels := []string{"endpoint", msg.Topic()}
if err != nil { if err != nil {
@ -35,6 +38,7 @@ var (
return labels return labels
} }
// DefaultServerHandlerObserver called by wrapper in server Handler
DefaultServerHandlerObserver = func(ctx context.Context, req server.Request, rsp interface{}, err error) []string { DefaultServerHandlerObserver = func(ctx context.Context, req server.Request, rsp interface{}, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()} labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil { if err != nil {
@ -43,6 +47,7 @@ var (
return labels return labels
} }
// DefaultServerSubscriberObserver called by wrapper in server Subscriber
DefaultServerSubscriberObserver = func(ctx context.Context, msg server.Message, err error) []string { DefaultServerSubscriberObserver = func(ctx context.Context, msg server.Message, err error) []string {
labels := []string{"endpoint", msg.Topic()} labels := []string{"endpoint", msg.Topic()}
if err != nil { if err != nil {
@ -51,6 +56,7 @@ var (
return labels return labels
} }
// DefaultClientCallFuncObserver called by wrapper in client CallFunc
DefaultClientCallFuncObserver = func(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions, err error) []string { DefaultClientCallFuncObserver = func(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()} labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil { if err != nil {
@ -59,6 +65,7 @@ var (
return labels return labels
} }
// DefaultSkipEndpoints wrapper not called for this endpoints
DefaultSkipEndpoints = []string{"Meter.Metrics"} DefaultSkipEndpoints = []string{"Meter.Metrics"}
) )
@ -71,11 +78,17 @@ type lWrapper struct {
} }
type ( type (
// ClientCallObserver func signature
ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string
// ClientStreamObserver func signature
ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string
// ClientPublishObserver func signature
ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string
// ClientCallFuncObserver func signature
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string
// ServerHandlerObserver func signature
ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string
// ServerSubscriberObserver func signature
ServerSubscriberObserver func(context.Context, server.Message, error) []string ServerSubscriberObserver func(context.Context, server.Message, error) []string
) )

View File

@ -102,7 +102,7 @@ func (k byKey) Swap(i, j int) {
k[i*2+1], k[j*2+1] = k[j*2+1], k[i*2+1] k[i*2+1], k[j*2+1] = k[j*2+1], k[i*2+1]
} }
// BuildLables used to sort labels and delete duplicates. // BuildLabels used to sort labels and delete duplicates.
// Last value wins in case of duplicate label keys. // Last value wins in case of duplicate label keys.
func BuildLabels(labels ...string) []string { func BuildLabels(labels ...string) []string {
if len(labels)%2 == 1 { if len(labels)%2 == 1 {

View File

@ -104,7 +104,7 @@ func Meter(m meter.Meter) Option {
} }
} }
// SkipEndpoint add endpoint to skip // SkipEndoints add endpoint to skip
func SkipEndoints(eps ...string) Option { func SkipEndoints(eps ...string) Option {
return func(o *Options) { return func(o *Options) {
o.SkipEndpoints = append(o.SkipEndpoints, eps...) o.SkipEndpoints = append(o.SkipEndpoints, eps...)
@ -294,7 +294,7 @@ func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
} }
} }
// NewSubscribeWrapper create server subscribe wrapper // NewSubscriberWrapper create server subscribe wrapper
func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper { func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
handler := &wrapper{ handler := &wrapper{
opts: NewOptions(opts...), opts: NewOptions(opts...),

View File

@ -44,9 +44,8 @@ func NewOptions(opts ...Option) Options {
return options return options
} }
// nolint: golint,revive
// RegisterOptions holds options for register method // RegisterOptions holds options for register method
type RegisterOptions struct { type RegisterOptions struct { // nolint: golint,revive
Context context.Context Context context.Context
Domain string Domain string
TTL time.Duration TTL time.Duration
@ -197,33 +196,29 @@ func TLSConfig(t *tls.Config) Option {
} }
} }
// nolint: golint,revive
// RegisterAttempts specifies register atempts count // RegisterAttempts specifies register atempts count
func RegisterAttempts(t int) RegisterOption { func RegisterAttempts(t int) RegisterOption { // nolint: golint,revive
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
o.Attempts = t o.Attempts = t
} }
} }
// nolint: golint,revive
// RegisterTTL specifies register ttl // RegisterTTL specifies register ttl
func RegisterTTL(t time.Duration) RegisterOption { func RegisterTTL(t time.Duration) RegisterOption { // nolint: golint,revive
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
o.TTL = t o.TTL = t
} }
} }
// nolint: golint,revive
// RegisterContext sets the register context // RegisterContext sets the register context
func RegisterContext(ctx context.Context) RegisterOption { func RegisterContext(ctx context.Context) RegisterOption { // nolint: golint,revive
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
o.Context = ctx o.Context = ctx
} }
} }
// nolint: golint,revive
// RegisterDomain secifies register domain // RegisterDomain secifies register domain
func RegisterDomain(d string) RegisterOption { func RegisterDomain(d string) RegisterOption { // nolint: golint,revive
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
o.Domain = d o.Domain = d
} }

View File

@ -69,9 +69,8 @@ type Endpoint struct {
// Option func signature // Option func signature
type Option func(*Options) type Option func(*Options)
// nolint: golint,revive
// RegisterOption option is used to register service // RegisterOption option is used to register service
type RegisterOption func(*RegisterOptions) type RegisterOption func(*RegisterOptions) // nolint: golint,revive
// WatchOption option is used to watch service changes // WatchOption option is used to watch service changes
type WatchOption func(*WatchOptions) type WatchOption func(*WatchOptions)

View File

@ -2,12 +2,16 @@ package tracer
import "go.unistack.org/micro/v3/logger" import "go.unistack.org/micro/v3/logger"
// SpanOptions contains span option
type SpanOptions struct{} type SpanOptions struct{}
// SpanOption func signature
type SpanOption func(o *SpanOptions) type SpanOption func(o *SpanOptions)
// EventOptions contains event options
type EventOptions struct{} type EventOptions struct{}
// EventOption func signature
type EventOption func(o *EventOptions) type EventOption func(o *EventOptions)
// Options struct // Options struct
@ -18,7 +22,7 @@ type Options struct {
Name string Name string
} }
// Option func // Option func signature
type Option func(o *Options) type Option func(o *Options)
// Logger sets the logger // Logger sets the logger

View File

@ -9,18 +9,30 @@ import (
) )
const ( const (
// SplitToken used to detect path components
SplitToken = "." SplitToken = "."
// IndexCloseChar used to detect index end
IndexCloseChar = "]" IndexCloseChar = "]"
// IndexOpenChar used to detect index start
IndexOpenChar = "[" IndexOpenChar = "["
) )
var ( var (
ErrMalformedIndex = errors.New("Malformed index key") // ErrMalformedIndex returns when index key have invalid format
ErrInvalidIndexUsage = errors.New("Invalid index key usage") ErrMalformedIndex = errors.New("malformed index key")
ErrKeyNotFound = errors.New("Unable to find the key") // ErrInvalidIndexUsage returns when index key usage error
ErrBadJSONPath = errors.New("Bad path: must start with $ and have more then 2 chars") ErrInvalidIndexUsage = errors.New("invalid index key usage")
// ErrKeyNotFound returns when key not found
ErrKeyNotFound = errors.New("unable to find the key")
// ErrBadJSONPath returns when path have invalid syntax
ErrBadJSONPath = errors.New("bad path: must start with $ and have more then 2 chars")
) )
// Lookup performs a lookup into a value, using a path of keys. The key should
// match with a Field or a MapIndex. For slice you can use the syntax key[index]
// to access a specific index. If one key owns to a slice and an index is not
// specificied the rest of the path will be apllied to evaley value of the
// slice, and the value will be merged into a slice.
func Lookup(i interface{}, path string) (reflect.Value, error) { func Lookup(i interface{}, path string) (reflect.Value, error) {
if path == "" || path[0:1] != "$" { if path == "" || path[0:1] != "$" {
return reflect.Value{}, ErrBadJSONPath return reflect.Value{}, ErrBadJSONPath
@ -37,11 +49,6 @@ func Lookup(i interface{}, path string) (reflect.Value, error) {
return lookup(i, strings.Split(path[2:], SplitToken)...) return lookup(i, strings.Split(path[2:], SplitToken)...)
} }
// Lookup performs a lookup into a value, using a path of keys. The key should
// match with a Field or a MapIndex. For slice you can use the syntax key[index]
// to access a specific index. If one key owns to a slice and an index is not
// specificied the rest of the path will be apllied to evaley value of the
// slice, and the value will be merged into a slice.
func lookup(i interface{}, path ...string) (reflect.Value, error) { func lookup(i interface{}, path ...string) (reflect.Value, error) {
value := reflect.ValueOf(i) value := reflect.ValueOf(i)
var parent reflect.Value var parent reflect.Value

View File

@ -20,8 +20,8 @@ var bracketSplitter = regexp.MustCompile(`\[|\]`)
// StructField contains struct field path its value and field // StructField contains struct field path its value and field
type StructField struct { type StructField struct {
Path string
Value reflect.Value Value reflect.Value
Path string
Field reflect.StructField Field reflect.StructField
} }

View File

@ -11,13 +11,13 @@ import (
func TestStructfields(t *testing.T) { func TestStructfields(t *testing.T) {
type Config struct { type Config struct {
Wait time.Duration
Time time.Time Time time.Time
Nested *Config
Metadata map[string]int Metadata map[string]int
Broker string Broker string
Addr []string Addr []string
Wait time.Duration
Verbose bool Verbose bool
Nested *Config
} }
cfg := &Config{Nested: &Config{}} cfg := &Config{Nested: &Config{}}
fields, err := rutil.StructFields(cfg) fields, err := rutil.StructFields(cfg)