diff --git a/docs/contributing/coding-style.md b/docs/contributing/coding-style.md index 9c072dd33e..45b96cf05d 100644 --- a/docs/contributing/coding-style.md +++ b/docs/contributing/coding-style.md @@ -451,20 +451,27 @@ The implementation may differ based on your use case. In Vald, the functional option pattern is widely used in Vald. You can refer to [this section](#Struct-initialization) for more details of the use case of this pattern. +We provide the following errors to describe the error to apply the option. + +| Error | Description | +|----|----| +| errors.ErrInvalidOption | Error to apply the option, and the error is ignorable | +| errors.ErrCriticalOption | Critical error to apply the option, the error cannot be ignored and should be handled | + We strongly recommend the following implementation to set the value using functional option. If an invalid value is set to the functional option, the `ErrInvalidOption` error defined in the [internal/errors/option.go](https://github.com/vdaas/vald/blob/master/internal/errors/option.go) should be returned. -The name argument (the first argument) of the `ErrInvalidOption` error should be the same as the functional option name without the `With` prefix. +The name argument (the first argument) of the `ErrInvalidOption` error should be the same as the functional option name without the `With` prefix. -For example, the functional option name `WithVersion` should return the error with the argument `name` as `version`. +For example, the functional option name `WithVersion` should return the error with the argument `name` as `version`. ```go func WithVersion(version string) Option { return func(c *client) error { if len(version) == 0 { - return errors.ErrInvalidOption("version", version) + return errors.NewErrInvalidOption("version", version) } c.version = version return nil @@ -478,11 +485,11 @@ We recommend the following implementation to parse the time string and set the t func WithTimeout(dur string) Option { func(c *client) error { if dur == "" { - return errors.ErrInvalidOption("timeout", dur) + return errors.NewErrInvalidOption("timeout", dur) } d, err := timeutil.Parse(dur) if err != nil { - return err + return errors.NewErrInvalidOption("timeout", dur, err) } c.timeout = d return nil @@ -496,7 +503,7 @@ We recommend the following implementation to append the value to the slice if th func WithHosts(hosts ...string) Option { return func(c *client) error { if len(hosts) == 0 { - return errors.ErrInvalidOption("hosts", hosts) + return errors.NewErrInvalidOption("hosts", hosts) } if c.hosts == nil { c.hosts = hosts @@ -508,16 +515,44 @@ func WithHosts(hosts ...string) Option { } ``` -We recommend the following implementation to apply the options. +If the functional option error is a critical error, we should return `ErrCriticalOption` error instead of `ErrInvalidOption`. + +```go +func WithConnectTimeout(dur string) Option { + return func(c *client) error { + if dur == "" { + return errors.NewErrInvalidOption("connectTimeout", dur) + } + d, err := timeutil.Parse(dur) + if err != nil { + return errors.NewErrCriticalOption("connectTimeout", dur, err) + } + + c.connectTimeout = d + return nil + } +} +``` + +In the caller side, we need to handle the error returned from the functional option. If the option failed to apply, an error wrapped with `ErrOptionFailed` defined in the [internal/errors/errors.go](https://github.com/vdaas/vald/blob/master/internal/errors/errors.go) should be returned. +We recommend the following implementation to apply the options. + ```go func New(opts ...Option) (Server, error) { srv := new(server) for _, opt := range opts { if err := opt(srv); err != nil { - return nil, errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + werr := errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + + e := new(errors.ErrCriticalOption) + if errors.As(err, &e) { + log.Error(werr) + return nil, werr + } + log.Warn(werr) } } } diff --git a/internal/db/nosql/cassandra/cassandra.go b/internal/db/nosql/cassandra/cassandra.go index b77ce408a6..05876a4186 100644 --- a/internal/db/nosql/cassandra/cassandra.go +++ b/internal/db/nosql/cassandra/cassandra.go @@ -26,6 +26,7 @@ import ( "github.com/scylladb/gocqlx" "github.com/scylladb/gocqlx/qb" "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/log" ) var ( @@ -50,29 +51,18 @@ type Cassandra interface { Query(stmt string, names []string) *Queryx } -type Session = gocql.Session -type Cmp = qb.Cmp -type BatchBuilder = qb.BatchBuilder -type InsertBuilder = qb.InsertBuilder -type DeleteBuilder = qb.DeleteBuilder -type UpdateBuilder = qb.UpdateBuilder -type Queryx = gocqlx.Queryx +type ( + Session = gocql.Session + Cmp = qb.Cmp + BatchBuilder = qb.BatchBuilder + InsertBuilder = qb.InsertBuilder + DeleteBuilder = qb.DeleteBuilder + UpdateBuilder = qb.UpdateBuilder + Queryx = gocqlx.Queryx +) -type client struct { - hosts []string - cqlVersion string - protoVersion int - timeout time.Duration - connectTimeout time.Duration - port int - keyspace string - numConns int - consistency gocql.Consistency - compressor gocql.Compressor - username string - password string - authProvider func(h *gocql.HostInfo) (gocql.Authenticator, error) - retryPolicy struct { +type ( + retryPolicy struct { numRetries int minDuration time.Duration maxDuration time.Duration @@ -93,42 +83,68 @@ type client struct { dcHost string whiteList []string } - socketKeepalive time.Duration - maxPreparedStmts int - maxRoutingKeyInfo int - pageSize int - serialConsistency gocql.SerialConsistency - tls *tls.Config - tlsCertPath string - tlsKeyPath string - tlsCAPath string - enableHostVerification bool - defaultTimestamp bool - reconnectInterval time.Duration - maxWaitSchemaAgreement time.Duration - ignorePeerAddr bool - disableInitialHostLookup bool - disableNodeStatusEvents bool - disableTopologyEvents bool - disableSchemaEvents bool - disableSkipMetadata bool - queryObserver QueryObserver - batchObserver BatchObserver - connectObserver ConnectObserver - frameHeaderObserver FrameHeaderObserver - defaultIdempotence bool - dialer gocql.Dialer - writeCoalesceWaitTime time.Duration + client struct { + hosts []string + cqlVersion string + protoVersion int + timeout time.Duration + connectTimeout time.Duration + port int + keyspace string + numConns int + consistency gocql.Consistency + compressor gocql.Compressor + username string + password string + authProvider func(h *gocql.HostInfo) (gocql.Authenticator, error) + retryPolicy retryPolicy + reconnectionPolicy reconnectionPolicy + poolConfig poolConfig + hostFilter hostFilter + socketKeepalive time.Duration + maxPreparedStmts int + maxRoutingKeyInfo int + pageSize int + serialConsistency gocql.SerialConsistency + tls *tls.Config + tlsCertPath string + tlsKeyPath string + tlsCAPath string + enableHostVerification bool + defaultTimestamp bool + reconnectInterval time.Duration + maxWaitSchemaAgreement time.Duration + ignorePeerAddr bool + disableInitialHostLookup bool + disableNodeStatusEvents bool + disableTopologyEvents bool + disableSchemaEvents bool + disableSkipMetadata bool + queryObserver QueryObserver + batchObserver BatchObserver + connectObserver ConnectObserver + frameHeaderObserver FrameHeaderObserver + defaultIdempotence bool + dialer gocql.Dialer + writeCoalesceWaitTime time.Duration - cluster *gocql.ClusterConfig - session *gocql.Session -} + cluster *gocql.ClusterConfig + session *gocql.Session + } +) func New(opts ...Option) (Cassandra, error) { c := new(client) for _, opt := range append(defaultOpts, opts...) { if err := opt(c); err != nil { - return nil, errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + werr := errors.ErrOptionFailed(err, reflect.ValueOf(opt)) + + e := new(errors.ErrCriticalOption) + if errors.As(err, &e) { + log.Error(werr) + return nil, werr + } + log.Warn(werr) } } diff --git a/internal/db/nosql/cassandra/cassandra_mock_test.go b/internal/db/nosql/cassandra/cassandra_mock_test.go new file mode 100644 index 0000000000..939c947766 --- /dev/null +++ b/internal/db/nosql/cassandra/cassandra_mock_test.go @@ -0,0 +1,29 @@ +// +// Copyright (C) 2019-2020 Vdaas.org Vald team ( kpango, rinx, kmrmt ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package cassandra + +import ( + "context" + "net" +) + +type DialerMock struct { + DialContextFunc func(ctx context.Context, network, addr string) (net.Conn, error) +} + +func (dm *DialerMock) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { + return dm.DialContextFunc(ctx, network, addr) +} diff --git a/internal/db/nosql/cassandra/option.go b/internal/db/nosql/cassandra/option.go index 57da8f9083..f475589040 100644 --- a/internal/db/nosql/cassandra/option.go +++ b/internal/db/nosql/cassandra/option.go @@ -19,6 +19,7 @@ package cassandra import ( "crypto/tls" + "math" "strings" "time" @@ -27,6 +28,10 @@ import ( "github.com/vdaas/vald/internal/timeutil" ) +// Option represents the functional option for client. +// It wraps the gocql.ClusterConfig to the function option implementation. +// Please refer to the following link for more information. +// https://pkg.go.dev/github.com/gocql/gocql?tab=doc#ClusterConfig type Option func(*client) error var ( @@ -60,10 +65,11 @@ var ( } ) +// WithHosts returns the option to set the hosts func WithHosts(hosts ...string) Option { return func(c *client) error { if len(hosts) == 0 { - return nil + return errors.NewErrInvalidOption("hosts", hosts) } if c.hosts == nil { c.hosts = hosts @@ -74,33 +80,44 @@ func WithHosts(hosts ...string) Option { } } +// WithDialer returns the option to set the dialer func WithDialer(der gocql.Dialer) Option { return func(c *client) error { - if der != nil { - c.dialer = der + if der == nil { + return errors.NewErrInvalidOption("dialer", der) } + c.dialer = der return nil } } +// WithCQLVersion returns the option to set the CQL version func WithCQLVersion(version string) Option { return func(c *client) error { + if len(version) == 0 { + return errors.NewErrInvalidOption("cqlVersion", version) + } c.cqlVersion = version return nil } } +// WithProtoVersion returns the option to set the proto version func WithProtoVersion(version int) Option { return func(c *client) error { + if version < 0 { + return errors.NewErrInvalidOption("protoVersion", version) + } c.protoVersion = version return nil } } +// WithTimeout returns the option to set the cassandra connect timeout time func WithTimeout(dur string) Option { return func(c *client) error { - if dur == "" { - return nil + if len(dur) == 0 { + return errors.NewErrInvalidOption("timeout", dur) } d, err := timeutil.Parse(dur) if err != nil { @@ -111,36 +128,50 @@ func WithTimeout(dur string) Option { } } +// WithConnectTimeout returns the option to set the cassandra initial connection timeout func WithConnectTimeout(dur string) Option { return func(c *client) error { - if dur == "" { - return nil + if len(dur) == 0 { + return errors.NewErrInvalidOption("connectTimeout", dur) } d, err := timeutil.Parse(dur) if err != nil { - return err + return errors.NewErrCriticalOption("connectTimeout", dur, err) } + c.connectTimeout = d return nil } } +// WithPort returns the option to set the port number func WithPort(port int) Option { return func(c *client) error { + if port <= 0 || port > math.MaxUint16 { + return errors.NewErrInvalidOption("port", port) + } c.port = port return nil } } +// WithKeyspace returns the option to set the keyspace func WithKeyspace(keyspace string) Option { return func(c *client) error { + if len(keyspace) == 0 { + return errors.NewErrInvalidOption("keyspace", keyspace) + } c.keyspace = keyspace return nil } } +// WithNumConns returns the option to set the number of connection per host func WithNumConns(numConns int) Option { return func(c *client) error { + if numConns < 0 { + return errors.NewErrInvalidOption("numConns", numConns) + } c.numConns = numConns return nil } diff --git a/internal/db/nosql/cassandra/option_test.go b/internal/db/nosql/cassandra/option_test.go index 2f3fc2f10a..335f1cf419 100644 --- a/internal/db/nosql/cassandra/option_test.go +++ b/internal/db/nosql/cassandra/option_test.go @@ -19,1026 +19,856 @@ package cassandra import ( "crypto/tls" + "reflect" "testing" + "time" "github.com/gocql/gocql" + "github.com/vdaas/vald/internal/errors" + "github.com/vdaas/vald/internal/test/comparator" "go.uber.org/goleak" ) +var ( + // Goroutine leak is detected by `fastime`, but it should be ignored in the test because it is an external package. + goleakIgnoreOptions = []goleak.Option{ + goleak.IgnoreTopFunction("github.com/kpango/fastime.(*Fastime).StartTimerD.func1"), + } + + // default comparator option for client + clientComparatorOpts = []comparator.Option{ + comparator.AllowUnexported(client{}), + comparator.Comparer(func(x, y retryPolicy) bool { + return reflect.DeepEqual(x, y) + }), + comparator.Comparer(func(x, y reconnectionPolicy) bool { + return reflect.DeepEqual(x, y) + }), + comparator.Comparer(func(x, y poolConfig) bool { + return reflect.DeepEqual(x, y) + }), + comparator.Comparer(func(x, y hostFilter) bool { + return reflect.DeepEqual(x, y) + }), + } +) + func TestWithHosts(t *testing.T) { - type T = interface{} + type T = client type args struct { hosts []string } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error + err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error - beforeFunc func(args) + name string + args args + want want + checkFunc func(want, *T, error) error + beforeFunc func(*T) afterFunc func(args) } + defaultCheckFunc := func(w want, obj *T, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got error = %v, want %v", err, w.err) + } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.c) - } - return nil - } - */ + if diff := comparator.Diff(obj, w.obj, clientComparatorOpts...); diff != "" { + return errors.New(diff) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - hosts: nil, - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - hosts: nil, - }, - want: want { - obj: new(T), - }, - } - }(), - */ + { + name: "set host success", + args: args{ + hosts: []string{"vald.vdaas.org"}, + }, + want: want{ + obj: &T{ + hosts: []string{"vald.vdaas.org"}, + }, + }, + }, + { + name: "return error if hosts is nil", + args: args{ + hosts: nil, + }, + want: want{ + obj: &T{ + hosts: nil, + }, + err: func() error { + var nilStr []string + return errors.NewErrInvalidOption("hosts", nilStr) + }(), + }, + }, + { + name: "set host twice success", + args: args{ + hosts: []string{"hosts1"}, + }, + beforeFunc: func(obj *T) { + _ = WithHosts("vald.vdaas.org")(obj) + }, + want: want{ + obj: &T{ + hosts: []string{"vald.vdaas.org", "hosts1"}, + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) - if test.beforeFunc != nil { - test.beforeFunc(test.args) - } + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.afterFunc != nil { defer test.afterFunc(test.args) } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithHosts(test.args.hosts...) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithHosts(test.args.hosts...) - obj := new(T) - got(obj) - if err := test.checkFunc(tt.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + got := WithHosts(test.args.hosts...) + obj := new(T) + if test.beforeFunc != nil { + test.beforeFunc(obj) + } + if err := test.checkFunc(test.want, obj, got(obj)); err != nil { + tt.Errorf("error = %v", err) + } }) } } func TestWithDialer(t *testing.T) { - type T = interface{} + type T = client type args struct { der gocql.Dialer } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error + err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + args args + want want + checkFunc func(want, *T, error) error beforeFunc func(args) afterFunc func(args) } - - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.c) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got error = %v, want %v", err, w.err) + } + if diff := comparator.Diff(obj, w.obj, clientComparatorOpts...); diff != "" { + return errors.New(diff) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - der: nil, - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - der: nil, - }, - want: want { - obj: new(T), - }, - } - }(), - */ + func() test { + dm := &DialerMock{} + return test{ + name: "set dialer success", + args: args{ + der: dm, + }, + want: want{ + obj: &T{ + dialer: dm, + }, + }, + } + }(), + { + name: "return error if dialer is nil", + args: args{ + der: nil, + }, + want: want{ + obj: &T{}, + err: errors.NewErrInvalidOption("dialer", nil), + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } if test.afterFunc != nil { defer test.afterFunc(test.args) } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithDialer(test.args.der) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithDialer(test.args.der) - obj := new(T) - got(obj) - if err := test.checkFunc(tt.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + got := WithDialer(test.args.der) + obj := new(T) + if err := test.checkFunc(test.want, obj, got(obj)); err != nil { + tt.Errorf("error = %v", err) + } }) } } func TestWithCQLVersion(t *testing.T) { - type T = interface{} + type T = client + type fields struct { + cqlVersion string + } type args struct { version string } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error + err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + fields fields + args args + want want + checkFunc func(want, *T, error) error beforeFunc func(args) afterFunc func(args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.c) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got error = %v, want %v", err, w.err) + } + if diff := comparator.Diff(obj, w.obj, clientComparatorOpts...); diff != "" { + return errors.New(diff) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - version: "", - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - version: "", - }, - want: want { - obj: new(T), - }, - } - }(), - */ + { + name: "set version success", + args: args{ + version: "1.0", + }, + want: want{ + obj: &T{ + cqlVersion: "1.0", + }, + }, + }, + { + name: "return error when version is empty", + args: args{ + version: "", + }, + fields: fields{ + cqlVersion: "1.0", + }, + want: want{ + obj: &T{ + cqlVersion: "1.0", + }, + err: errors.NewErrInvalidOption("cqlVersion", ""), + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } if test.afterFunc != nil { defer test.afterFunc(test.args) } - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithCQLVersion(test.args.version) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithCQLVersion(test.args.version) - obj := new(T) - got(obj) - if err := test.checkFunc(tt.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } + + got := WithCQLVersion(test.args.version) + obj := &T{ + cqlVersion: test.fields.cqlVersion, + } + if err := test.checkFunc(test.want, obj, got(obj)); err != nil { + tt.Errorf("error = %v", err) + } }) } } func TestWithProtoVersion(t *testing.T) { - type T = interface{} + type T = client type args struct { version int } + type fields struct { + protoVersion int + } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error + err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + args args + fields fields + want want + checkFunc func(want, *T, error) error beforeFunc func(args) afterFunc func(args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.c) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got error = %v, want %v", err, w.err) + } + if diff := comparator.Diff(obj, w.obj, clientComparatorOpts...); diff != "" { + return errors.New(diff) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - version: 0, - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - version: 0, - }, - want: want { - obj: new(T), - }, - } - }(), - */ + { + name: "set version success", + args: args{ + version: 1, + }, + want: want{ + obj: &T{ + protoVersion: 1, + }, + }, + }, + { + name: "return error when version < 0", + args: args{ + version: -1, + }, + fields: fields{ + protoVersion: 10, + }, + want: want{ + obj: &T{ + protoVersion: 10, + }, + err: errors.NewErrInvalidOption("protoVersion", -1), + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } if test.afterFunc != nil { defer test.afterFunc(test.args) } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithProtoVersion(test.args.version) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithProtoVersion(test.args.version) - obj := new(T) - got(obj) - if err := test.checkFunc(tt.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + got := WithProtoVersion(test.args.version) + obj := &T{ + protoVersion: test.fields.protoVersion, + } + if err := test.checkFunc(test.want, obj, got(obj)); err != nil { + tt.Errorf("error = %v", err) + } }) } } func TestWithTimeout(t *testing.T) { - type T = interface{} + type T = client type args struct { dur string } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error + err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + args args + want want + checkFunc func(want, *T, error) error beforeFunc func(args) afterFunc func(args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.c) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got error = %v, want %v", err, w.err) + } + if diff := comparator.Diff(obj, w.obj, clientComparatorOpts...); diff != "" { + return errors.New(diff) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - dur: "", - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - dur: "", - }, - want: want { - obj: new(T), - }, - } - }(), - */ + { + name: "set timeout success", + args: args{ + dur: "5s", + }, + want: want{ + obj: &T{ + timeout: 5 * time.Second, + }, + }, + }, + { + name: "set timeout success if the time format is invalid", + args: args{ + dur: "dummy", + }, + want: want{ + obj: &T{ + timeout: time.Minute, + }, + }, + }, + { + name: "return error if the time is empty", + args: args{ + dur: "", + }, + want: want{ + obj: &T{}, + err: errors.NewErrInvalidOption("timeout", ""), + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } if test.afterFunc != nil { defer test.afterFunc(test.args) } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithTimeout(test.args.dur) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithTimeout(test.args.dur) - obj := new(T) - got(obj) - if err := test.checkFunc(tt.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + got := WithTimeout(test.args.dur) + obj := new(T) + if err := test.checkFunc(test.want, obj, got(obj)); err != nil { + tt.Errorf("error = %v", err) + } }) } } func TestWithConnectTimeout(t *testing.T) { - type T = interface{} + type T = client type args struct { dur string } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error + err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + args args + want want + checkFunc func(want, *T, error) error beforeFunc func(args) afterFunc func(args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.c) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T, err error) error { + if (w.err == nil && err != nil) || (w.err != nil && err == nil) || + (err != nil && w.err.Error() != err.Error()) { + return errors.Errorf("got error = %v, want %v", err, w.err) + } + if diff := comparator.Diff(obj, w.obj, clientComparatorOpts...); diff != "" { + return errors.New(diff) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - dur: "", - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - dur: "", - }, - want: want { - obj: new(T), - }, - } - }(), - */ + { + name: "set timeout success", + args: args{ + dur: "5s", + }, + want: want{ + obj: &T{ + connectTimeout: 5 * time.Second, + }, + }, + }, + { + name: "return error if the time format is invalid", + args: args{ + dur: "dummy", + }, + want: want{ + err: errors.NewErrCriticalOption("connectTimeout", "dummy", errors.New("invalid timeout value: dummy :timeout parse error out put failed: time: invalid duration \"dummy\"")), + obj: &T{}, + }, + }, + { + name: "return error if the time is empty", + args: args{ + dur: "", + }, + want: want{ + obj: &T{}, + err: errors.NewErrInvalidOption("connectTimeout", ""), + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } if test.afterFunc != nil { defer test.afterFunc(test.args) } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithConnectTimeout(test.args.dur) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithConnectTimeout(test.args.dur) - obj := new(T) - got(obj) - if err := test.checkFunc(tt.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + got := WithConnectTimeout(test.args.dur) + obj := new(T) + if err := test.checkFunc(test.want, obj, got(obj)); err != nil { + tt.Errorf("error = %v", err) + } }) } } func TestWithPort(t *testing.T) { - type T = interface{} + type T = client type args struct { port int } + type fields struct { + port int + } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error + err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + args args + fields fields + want want + checkFunc func(want, *T, error) error beforeFunc func(args) afterFunc func(args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.c) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got error = %v, want %v", err, w.err) + } + if diff := comparator.Diff(obj, w.obj, clientComparatorOpts...); diff != "" { + return errors.New(diff) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - port: 0, - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - port: 0, - }, - want: want { - obj: new(T), - }, - } - }(), - */ + { + name: "set port success", + args: args{ + port: 8080, + }, + want: want{ + obj: &T{ + port: 8080, + }, + }, + }, + { + name: "set port success (boundary)", + args: args{ + port: 65535, + }, + want: want{ + obj: &T{ + port: 65535, + }, + }, + }, + { + name: "return error when port <= 0", + args: args{ + port: -1, + }, + fields: fields{ + port: 8080, + }, + want: want{ + err: errors.NewErrInvalidOption("port", -1), + obj: &T{ + port: 8080, + }, + }, + }, + { + name: "return error when port == 0", + args: args{ + port: 0, + }, + fields: fields{ + port: 8080, + }, + want: want{ + err: errors.NewErrInvalidOption("port", 0), + obj: &T{ + port: 8080, + }, + }, + }, + { + name: "return error when port > 65535", + args: args{ + port: 65536, + }, + fields: fields{ + port: 8080, + }, + want: want{ + err: errors.NewErrInvalidOption("port", 65536), + obj: &T{ + port: 8080, + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } if test.afterFunc != nil { defer test.afterFunc(test.args) } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithPort(test.args.port) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithPort(test.args.port) - obj := new(T) - got(obj) - if err := test.checkFunc(tt.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + got := WithPort(test.args.port) + obj := &T{ + port: test.fields.port, + } + if err := test.checkFunc(test.want, obj, got(obj)); err != nil { + tt.Errorf("error = %v", err) + } }) } } func TestWithKeyspace(t *testing.T) { - type T = interface{} + type T = client type args struct { keyspace string } + type fields struct { + keyspace string + } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error + err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + args args + fields fields + want want + checkFunc func(want, *T, error) error beforeFunc func(args) afterFunc func(args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.c) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got error = %v, want %v", err, w.err) + } + if diff := comparator.Diff(obj, w.obj, clientComparatorOpts...); diff != "" { + return errors.New(diff) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - keyspace: "", - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - keyspace: "", - }, - want: want { - obj: new(T), - }, - } - }(), - */ + { + name: "set keyspace success", + args: args{ + keyspace: "keyspace", + }, + want: want{ + obj: &T{ + keyspace: "keyspace", + }, + }, + }, + { + name: "return error when keyspace is empty", + args: args{ + keyspace: "", + }, + fields: fields{ + keyspace: "keyspace", + }, + want: want{ + obj: &T{ + keyspace: "keyspace", + }, + err: errors.NewErrInvalidOption("keyspace", ""), + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } if test.afterFunc != nil { defer test.afterFunc(test.args) } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithKeyspace(test.args.keyspace) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithKeyspace(test.args.keyspace) - obj := new(T) - got(obj) - if err := test.checkFunc(tt.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + got := WithKeyspace(test.args.keyspace) + obj := &T{ + keyspace: test.fields.keyspace, + } + if err := test.checkFunc(test.want, obj, got(obj)); err != nil { + tt.Errorf("error = %v", err) + } }) } } func TestWithNumConns(t *testing.T) { - type T = interface{} + type T = client type args struct { numConns int } + type fields struct { + numConns int + } type want struct { obj *T - // Uncomment this line if the option returns an error, otherwise delete it - // err error + err error } type test struct { - name string - args args - want want - // Use the first line if the option returns an error. otherwise use the second line - // checkFunc func(want, *T, error) error - // checkFunc func(want, *T) error + name string + args args + fields fields + want want + checkFunc func(want, *T, error) error beforeFunc func(args) afterFunc func(args) } - // Uncomment this block if the option returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T, err error) error { - if !errors.Is(err, w.err) { - return errors.Errorf("got_error: \"%#v\",\n\t\t\t\twant: \"%#v\"", err, w.err) - } - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.obj) - } - return nil - } - */ - - // Uncomment this block if the option do not returns an error, otherwise delete it - /* - defaultCheckFunc := func(w want, obj *T) error { - if !reflect.DeepEqual(obj, w.obj) { - return errors.Errorf("got: \"%#v\",\n\t\t\t\twant: \"%#v\"", obj, w.c) - } - return nil - } - */ + defaultCheckFunc := func(w want, obj *T, err error) error { + if !errors.Is(err, w.err) { + return errors.Errorf("got error = %v, want %v", err, w.err) + } + if diff := comparator.Diff(obj, w.obj, clientComparatorOpts...); diff != "" { + return errors.New(diff) + } + return nil + } tests := []test{ - // TODO test cases - /* - { - name: "test_case_1", - args: args { - numConns: 0, - }, - want: want { - obj: new(T), - }, - }, - */ - - // TODO test cases - /* - func() test { - return test { - name: "test_case_2", - args: args { - numConns: 0, - }, - want: want { - obj: new(T), - }, - } - }(), - */ + { + name: "set num conn success", + args: args{ + numConns: 100, + }, + want: want{ + obj: &T{ + numConns: 100, + }, + }, + }, + { + name: "return error when numConn < 0", + args: args{ + numConns: -1, + }, + fields: fields{ + numConns: 100, + }, + want: want{ + obj: &T{ + numConns: 100, + }, + err: errors.NewErrInvalidOption("numConns", -1), + }, + }, + { + name: "set numConn success when numConn = 0", + args: args{ + numConns: 0, + }, + fields: fields{ + numConns: 100, + }, + want: want{ + obj: &T{ + numConns: 0, + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { - defer goleak.VerifyNone(t) + defer goleak.VerifyNone(tt, goleakIgnoreOptions...) if test.beforeFunc != nil { test.beforeFunc(test.args) } if test.afterFunc != nil { defer test.afterFunc(test.args) } + if test.checkFunc == nil { + test.checkFunc = defaultCheckFunc + } - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - - got := WithNumConns(test.args.numConns) - obj := new(T) - if err := test.checkFunc(test.want, obj, got(obj)); err != nil { - tt.Errorf("error = %v", err) - } - */ - - // Uncomment this block if the option returns an error, otherwise delete it - /* - if test.checkFunc == nil { - test.checkFunc = defaultCheckFunc - } - got := WithNumConns(test.args.numConns) - obj := new(T) - got(obj) - if err := test.checkFunc(tt.want, obj); err != nil { - tt.Errorf("error = %v", err) - } - */ + got := WithNumConns(test.args.numConns) + obj := &T{ + numConns: test.fields.numConns, + } + if err := test.checkFunc(test.want, obj, got(obj)); err != nil { + tt.Errorf("error = %v", err) + } }) } } diff --git a/internal/db/storage/blob/s3/writer/option.go b/internal/db/storage/blob/s3/writer/option.go index a136d06af7..88d0bfd9e4 100644 --- a/internal/db/storage/blob/s3/writer/option.go +++ b/internal/db/storage/blob/s3/writer/option.go @@ -38,7 +38,7 @@ var ( func WithErrGroup(eg errgroup.Group) Option { return func(w *writer) error { if eg == nil { - return errors.ErrInvalidOption("errgroup", eg) + return errors.NewErrInvalidOption("errgroup", eg) } w.eg = eg return nil @@ -49,7 +49,7 @@ func WithErrGroup(eg errgroup.Group) Option { func WithService(s *s3.S3) Option { return func(w *writer) error { if s == nil { - return errors.ErrInvalidOption("service", s) + return errors.NewErrInvalidOption("service", s) } w.service = s return nil @@ -60,7 +60,7 @@ func WithService(s *s3.S3) Option { func WithBucket(bucket string) Option { return func(w *writer) error { if len(bucket) == 0 { - return errors.ErrInvalidOption("bucket", bucket) + return errors.NewErrInvalidOption("bucket", bucket) } w.bucket = bucket return nil @@ -71,7 +71,7 @@ func WithBucket(bucket string) Option { func WithKey(key string) Option { return func(w *writer) error { if len(key) == 0 { - return errors.ErrInvalidOption("key", key) + return errors.NewErrInvalidOption("key", key) } w.key = key return nil @@ -82,7 +82,7 @@ func WithKey(key string) Option { func WithContentType(ct string) Option { return func(w *writer) error { if len(ct) == 0 { - return errors.ErrInvalidOption("contentType", ct) + return errors.NewErrInvalidOption("contentType", ct) } w.contentType = ct return nil @@ -93,7 +93,7 @@ func WithContentType(ct string) Option { func WithMaxPartSize(max int64) Option { return func(w *writer) error { if max < s3manager.MinUploadPartSize { - return errors.ErrInvalidOption("maxPartSize", max) + return errors.NewErrInvalidOption("maxPartSize", max) } w.maxPartSize = max return nil diff --git a/internal/db/storage/blob/s3/writer/option_test.go b/internal/db/storage/blob/s3/writer/option_test.go index 46bbd5ab5b..541cd639cf 100644 --- a/internal/db/storage/blob/s3/writer/option_test.go +++ b/internal/db/storage/blob/s3/writer/option_test.go @@ -90,7 +90,7 @@ func TestWithErrGroup(t *testing.T) { obj: &T{ eg: errgroup.Get(), }, - err: errors.ErrInvalidOption("errgroup", nil), + err: errors.NewErrInvalidOption("errgroup", nil), }, }, } @@ -182,7 +182,7 @@ func TestWithService(t *testing.T) { obj: &T{ service: s, }, - err: errors.ErrInvalidOption("service", ss), + err: errors.NewErrInvalidOption("service", ss), }, } }(), @@ -260,7 +260,7 @@ func TestWithBucket(t *testing.T) { }, want: want{ obj: new(T), - err: errors.ErrInvalidOption("bucket", ""), + err: errors.NewErrInvalidOption("bucket", ""), }, }, } @@ -335,7 +335,7 @@ func TestWithKey(t *testing.T) { }, want: want{ obj: new(T), - err: errors.ErrInvalidOption("key", ""), + err: errors.NewErrInvalidOption("key", ""), }, }, } @@ -410,7 +410,7 @@ func TestWithMaxPartSize(t *testing.T) { }, want: want{ obj: new(T), - err: errors.ErrInvalidOption("maxPartSize", 10), + err: errors.NewErrInvalidOption("maxPartSize", 10), }, }, } @@ -485,7 +485,7 @@ func TestWithContentType(t *testing.T) { }, want: want{ obj: new(T), - err: errors.ErrInvalidOption("contentType", ""), + err: errors.NewErrInvalidOption("contentType", ""), }, }, } diff --git a/internal/errors/option.go b/internal/errors/option.go index bb56cdae16..cab7349b30 100644 --- a/internal/errors/option.go +++ b/internal/errors/option.go @@ -15,12 +15,85 @@ // package errors -var ( - // ErrInvalidOption returns invalid option error. - ErrInvalidOption = func(name string, val interface{}) error { - if val == nil { - return Errorf("invalid option. name: %s, val: nil", name) +// ErrInvalidOption represent the invalid option error +type ErrInvalidOption struct { + err error + origin error +} + +func NewErrInvalidOption(name string, val interface{}, errs ...error) error { + if len(errs) == 0 { + return &ErrInvalidOption{ + err: Errorf("invalid option, name: %s, val: %v", name, val), + } + } + var e error + for _, err := range errs { + if err == nil { + continue } - return Errorf("invalid option. name: %s, val: %#v", name, val) + + if e != nil { + e = Wrap(err, e.Error()) + } else { + e = err + } + } + + return &ErrInvalidOption{ + err: Wrapf(e, "invalid option, name: %s, val: %v", name, val), + origin: e, } -) +} + +func (e *ErrInvalidOption) Error() string { + return e.err.Error() +} + +func (e *ErrInvalidOption) Unwrap() error { + return e.origin +} + +/* + ErrCriticalOption +*/ + +// ErrCriticalOption represent the critical option error +type ErrCriticalOption struct { + err error + origin error +} + +func NewErrCriticalOption(name string, val interface{}, errs ...error) error { + if len(errs) == 0 { + return &ErrCriticalOption{ + err: Errorf("invalid critical option, name: %s, val: %v", name, val), + } + } + + var e error + for _, err := range errs { + if err == nil { + continue + } + + if e != nil { + e = Wrap(err, e.Error()) + } else { + e = err + } + } + + return &ErrCriticalOption{ + err: Wrapf(e, "invalid critical option, name: %s, val: %v", name, val), + origin: e, + } +} + +func (e *ErrCriticalOption) Error() string { + return e.err.Error() +} + +func (e *ErrCriticalOption) Unwrap() error { + return e.origin +}