diff --git a/internal/credential/options.go b/internal/credential/options.go index a349c1e24..4f7483379 100644 --- a/internal/credential/options.go +++ b/internal/credential/options.go @@ -4,6 +4,11 @@ package credential import ( + "errors" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/pagination" + "github.com/hashicorp/boundary/internal/util" "github.com/hashicorp/boundary/internal/util/template" ) @@ -26,7 +31,11 @@ type Option func(*options) error // options = how options are represented type options struct { - WithTemplateData template.Data + WithTemplateData template.Data + WithReader db.Reader + WithWriter db.Writer + WithLimit int + WithStartPageAfterItem pagination.Item } func getDefaultOptions() *options { @@ -40,3 +49,41 @@ func WithTemplateData(with template.Data) Option { return nil } } + +// WithReaderWriter allows the caller to pass an inflight transaction to be used +// for all database operations. If WithReaderWriter(...) is used, then the +// caller is responsible for managing the transaction. +func WithReaderWriter(r db.Reader, w db.Writer) Option { + return func(o *options) error { + switch { + case util.IsNil(r): + return errors.New("nil reader") + case util.IsNil(w): + return errors.New("nil writer") + } + o.WithReader = r + o.WithWriter = w + return nil + } +} + +// WithLimit provides an option to provide a limit. +// If WithLimit == 0, then default limits are used for results. +func WithLimit(l int) Option { + return func(o *options) error { + if l < 0 { + return errors.New("limit must be at least 0") + } + o.WithLimit = l + return nil + } +} + +// WithStartPageAfterItem is used to paginate over the results. +// The next page will start after the provided item. +func WithStartPageAfterItem(item pagination.Item) Option { + return func(o *options) error { + o.WithStartPageAfterItem = item + return nil + } +} diff --git a/internal/credential/options_test.go b/internal/credential/options_test.go index d9332eac4..dbb2c55f6 100644 --- a/internal/credential/options_test.go +++ b/internal/credential/options_test.go @@ -5,20 +5,118 @@ package credential import ( "testing" + "time" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/pagination" "github.com/hashicorp/boundary/internal/util" "github.com/hashicorp/boundary/internal/util/template" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +type fakeWriter struct { + db.Writer +} + +type fakeReader struct { + db.Reader +} + +type fakeItem struct { + pagination.Item + publicId string + updateTime time.Time + createTime time.Time +} + +func (p *fakeItem) GetPublicId() string { + return p.publicId +} + +func (p *fakeItem) GetUpdateTime() *timestamp.Timestamp { + return timestamp.New(p.updateTime) +} + +func (p *fakeItem) GetCreateTime() *timestamp.Timestamp { + return timestamp.New(p.createTime) +} + func Test_GetOpts(t *testing.T) { t.Parallel() t.Run("WithTemplateData", func(t *testing.T) { + t.Parallel() opts := getDefaultOptions() assert.Empty(t, opts.WithTemplateData) opts, err := GetOpts(WithTemplateData(template.Data{User: template.User{Id: util.Pointer("foo")}})) require.NoError(t, err) assert.Equal(t, "foo", *opts.WithTemplateData.User.Id) }) + t.Run("WithLimit", func(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + opts := getDefaultOptions() + assert.Empty(t, opts.WithLimit) + opts, err := GetOpts(WithLimit(1)) + require.NoError(t, err) + assert.Equal(t, 1, opts.WithLimit) + }) + t.Run("negative limit", func(t *testing.T) { + t.Parallel() + opts := getDefaultOptions() + assert.Empty(t, opts.WithLimit) + _, err := GetOpts(WithLimit(-1)) + require.Error(t, err) + }) + }) + t.Run("WithReaderWriter", func(t *testing.T) { + t.Parallel() + t.Run("success", func(t *testing.T) { + t.Parallel() + opts := getDefaultOptions() + assert.Empty(t, opts.WithReader) + assert.Empty(t, opts.WithWriter) + r, w := &fakeReader{}, &fakeWriter{} + opts, err := GetOpts(WithReaderWriter(r, w)) + require.NoError(t, err) + assert.Equal(t, r, opts.WithReader) + assert.Equal(t, w, opts.WithWriter) + }) + t.Run("nil reader", func(t *testing.T) { + t.Parallel() + w := &fakeWriter{} + _, err := GetOpts(WithReaderWriter(nil, w)) + require.Error(t, err) + }) + t.Run("nil interface reader", func(t *testing.T) { + t.Parallel() + w := &fakeWriter{} + _, err := GetOpts(WithReaderWriter((*fakeReader)(nil), w)) + require.Error(t, err) + }) + t.Run("nil writer", func(t *testing.T) { + t.Parallel() + r := &fakeReader{} + _, err := GetOpts(WithReaderWriter(r, nil)) + require.Error(t, err) + }) + t.Run("nil interface writer", func(t *testing.T) { + t.Parallel() + r := &fakeReader{} + _, err := GetOpts(WithReaderWriter(r, (*fakeWriter)(nil))) + require.Error(t, err) + }) + }) + t.Run("WithStartPageAfterItem", func(t *testing.T) { + assert := assert.New(t) + updateTime := time.Now() + createTime := time.Now() + opts, err := GetOpts(WithStartPageAfterItem(&fakeItem{nil, "s_1", updateTime, createTime})) + require.NoError(t, err) + assert.Equal(opts.WithStartPageAfterItem.GetPublicId(), "s_1") + assert.Equal(opts.WithStartPageAfterItem.GetUpdateTime(), timestamp.New(updateTime)) + assert.Equal(opts.WithStartPageAfterItem.GetCreateTime(), timestamp.New(createTime)) + }) }