mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:37:32 +00:00
feat(eventstore): sdk (#39)
* sdk * fix(sdk): return correct error type * AppendEventError instead of Aggregater error * fix(tests): tests * fix(tests): wantErr to is error func
This commit is contained in:
@@ -33,7 +33,7 @@ func (es *eventstore) PushAggregates(ctx context.Context, aggregates ...*models.
|
||||
}
|
||||
for _, event := range aggregate.Events {
|
||||
if err = event.Validate(); err != nil {
|
||||
return err
|
||||
return errors.ThrowInvalidArgument(err, "EVENT-tzIhl", "validate event failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,12 +42,6 @@ func (es *eventstore) PushAggregates(ctx context.Context, aggregates ...*models.
|
||||
return err
|
||||
}
|
||||
|
||||
for _, aggregate := range aggregates {
|
||||
if aggregate.Appender != nil {
|
||||
aggregate.Appender(aggregate.Events...)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -24,11 +24,8 @@ type Aggregate struct {
|
||||
editorUser string
|
||||
resourceOwner string
|
||||
Events []*Event
|
||||
Appender appender
|
||||
}
|
||||
|
||||
type appender func(...*Event)
|
||||
|
||||
func (a *Aggregate) AppendEvent(typ EventType, payload interface{}) (*Aggregate, error) {
|
||||
if string(typ) == "" {
|
||||
return a, errors.ThrowInvalidArgument(nil, "MODEL-TGoCb", "no event type")
|
||||
@@ -81,8 +78,3 @@ func (a *Aggregate) Validate() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Aggregate) SetAppender(appendFn appender) *Aggregate {
|
||||
a.Appender = appendFn
|
||||
return a
|
||||
}
|
||||
|
36
internal/eventstore/sdk/append_event_error.go
Normal file
36
internal/eventstore/sdk/append_event_error.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
_ AppendEventError = (*appendEventError)(nil)
|
||||
_ errors.Error = (*appendEventError)(nil)
|
||||
)
|
||||
|
||||
type AppendEventError interface {
|
||||
error
|
||||
IsAppendEventError()
|
||||
}
|
||||
|
||||
type appendEventError struct {
|
||||
*errors.CaosError
|
||||
}
|
||||
|
||||
func ThrowAppendEventError(parent error, id, message string) error {
|
||||
return &appendEventError{errors.CreateCaosError(parent, id, message)}
|
||||
}
|
||||
|
||||
func ThrowAggregaterf(parent error, id, format string, a ...interface{}) error {
|
||||
return ThrowAppendEventError(parent, id, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (err *appendEventError) IsAppendEventError() {}
|
||||
|
||||
func IsAppendEventError(err error) bool {
|
||||
_, ok := err.(AppendEventError)
|
||||
return ok
|
||||
}
|
31
internal/eventstore/sdk/append_event_error_test.go
Normal file
31
internal/eventstore/sdk/append_event_error_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAppendEventError(t *testing.T) {
|
||||
var err interface{}
|
||||
err = new(appendEventError)
|
||||
_, ok := err.(*appendEventError)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestThrowAppendEventErrorf(t *testing.T) {
|
||||
err := ThrowAggregaterf(nil, "id", "msg")
|
||||
_, ok := err.(*appendEventError)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestIsAppendEventError(t *testing.T) {
|
||||
err := ThrowAppendEventError(nil, "id", "msg")
|
||||
ok := IsAppendEventError(err)
|
||||
assert.True(t, ok)
|
||||
|
||||
err = errors.New("i am found")
|
||||
ok = IsAppendEventError(err)
|
||||
assert.False(t, ok)
|
||||
}
|
72
internal/eventstore/sdk/sdk.go
Normal file
72
internal/eventstore/sdk/sdk.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type filterFunc func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error)
|
||||
type appendFunc func(...*es_models.Event) error
|
||||
type aggregateFunc func(context.Context) (*es_models.Aggregate, error)
|
||||
type pushFunc func(context.Context, ...*es_models.Aggregate) error
|
||||
|
||||
func Filter(ctx context.Context, filter filterFunc, appender appendFunc, query *es_models.SearchQuery) error {
|
||||
events, err := filter(ctx, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return errors.ThrowNotFound(nil, "EVENT-8due3", "no events found")
|
||||
}
|
||||
err = appender(events...)
|
||||
if err != nil{
|
||||
return ThrowAppendEventError(err, "SDK-awiWK", "appender failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Push creates the aggregates from aggregater
|
||||
// and pushes the aggregates to the given pushFunc
|
||||
// the given events are appended by the appender
|
||||
func Push(ctx context.Context, push pushFunc, appender appendFunc, aggregaters ...aggregateFunc) (err error) {
|
||||
if len(aggregaters) < 1 {
|
||||
return errors.ThrowPreconditionFailed(nil, "SDK-q9wjp", "no aggregaters passed")
|
||||
}
|
||||
|
||||
aggregates, err := makeAggregates(ctx, aggregaters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = push(ctx, aggregates...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
return appendAggregates(appender, aggregates)
|
||||
}
|
||||
|
||||
func appendAggregates(appender appendFunc, aggregates []*models.Aggregate) error {
|
||||
for _, aggregate := range aggregates {
|
||||
err := appender(aggregate.Events...)
|
||||
if err != nil {
|
||||
return ThrowAppendEventError(err, "SDK-o6kzK", "aggregator failed")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeAggregates(ctx context.Context, aggregaters []aggregateFunc) (aggregates []*models.Aggregate, err error) {
|
||||
aggregates = make([]*models.Aggregate, len(aggregaters))
|
||||
for i, aggregater := range aggregaters {
|
||||
aggregates[i], err = aggregater(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return aggregates, nil
|
||||
}
|
193
internal/eventstore/sdk/sdk_test.go
Normal file
193
internal/eventstore/sdk/sdk_test.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
type args struct {
|
||||
filter filterFunc
|
||||
appender appendFunc
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr func(error) bool
|
||||
}{
|
||||
{
|
||||
name: "filter error",
|
||||
args: args{
|
||||
filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "test-46VX2", "test error")
|
||||
},
|
||||
appender: nil,
|
||||
},
|
||||
wantErr: errors.IsInternal,
|
||||
},
|
||||
{
|
||||
name: "no events found",
|
||||
args: args{
|
||||
filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) {
|
||||
return []*es_models.Event{}, nil
|
||||
},
|
||||
appender: nil,
|
||||
},
|
||||
wantErr: errors.IsNotFound,
|
||||
},
|
||||
{
|
||||
name: "append fails",
|
||||
args: args{
|
||||
filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) {
|
||||
return []*es_models.Event{&es_models.Event{}}, nil
|
||||
},
|
||||
appender: func(...*es_models.Event) error {
|
||||
return errors.ThrowInvalidArgument(nil, "SDK-DhBzl", "test error")
|
||||
},
|
||||
},
|
||||
wantErr: IsAppendEventError,
|
||||
},
|
||||
{
|
||||
name: "filter correct",
|
||||
args: args{
|
||||
filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) {
|
||||
return []*es_models.Event{&es_models.Event{}}, nil
|
||||
},
|
||||
appender: func(...*es_models.Event) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := Filter(context.Background(), tt.args.filter, tt.args.appender, nil)
|
||||
if tt.wantErr == nil && err != nil {
|
||||
t.Errorf("no error expected %v", err)
|
||||
}
|
||||
if tt.wantErr != nil && !tt.wantErr(err) {
|
||||
t.Errorf("no error has wrong type %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPush(t *testing.T) {
|
||||
type args struct {
|
||||
push pushFunc
|
||||
appender appendFunc
|
||||
aggregaters []aggregateFunc
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr func(error) bool
|
||||
}{
|
||||
{
|
||||
name: "no aggregates",
|
||||
args: args{
|
||||
push: nil,
|
||||
appender: nil,
|
||||
aggregaters: nil,
|
||||
},
|
||||
wantErr: errors.IsPreconditionFailed,
|
||||
},
|
||||
{
|
||||
name: "aggregater fails",
|
||||
args: args{
|
||||
push: nil,
|
||||
appender: nil,
|
||||
aggregaters: []aggregateFunc{
|
||||
func(context.Context) (*es_models.Aggregate, error) {
|
||||
return nil, errors.ThrowInternal(nil, "SDK-Ec5x2", "test err")
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: errors.IsInternal,
|
||||
},
|
||||
{
|
||||
name: "push fails",
|
||||
args: args{
|
||||
push: func(context.Context, ...*es_models.Aggregate) error {
|
||||
return errors.ThrowInternal(nil, "SDK-0g4gW", "test error")
|
||||
},
|
||||
appender: nil,
|
||||
aggregaters: []aggregateFunc{
|
||||
func(context.Context) (*es_models.Aggregate, error) {
|
||||
return &es_models.Aggregate{}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: errors.IsInternal,
|
||||
},
|
||||
{
|
||||
name: "append aggregates fails",
|
||||
args: args{
|
||||
push: func(context.Context, ...*es_models.Aggregate) error {
|
||||
return nil
|
||||
},
|
||||
appender: func(...*es_models.Event) error {
|
||||
return errors.ThrowInvalidArgument(nil, "SDK-BDhcT", "test err")
|
||||
},
|
||||
aggregaters: []aggregateFunc{
|
||||
func(context.Context) (*es_models.Aggregate, error) {
|
||||
return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: IsAppendEventError,
|
||||
},
|
||||
{
|
||||
name: "correct one aggregate",
|
||||
args: args{
|
||||
push: func(context.Context, ...*es_models.Aggregate) error {
|
||||
return nil
|
||||
},
|
||||
appender: func(...*es_models.Event) error {
|
||||
return nil
|
||||
},
|
||||
aggregaters: []aggregateFunc{
|
||||
func(context.Context) (*es_models.Aggregate, error) {
|
||||
return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "correct multiple aggregate",
|
||||
args: args{
|
||||
push: func(context.Context, ...*es_models.Aggregate) error {
|
||||
return nil
|
||||
},
|
||||
appender: func(...*es_models.Event) error {
|
||||
return nil
|
||||
},
|
||||
aggregaters: []aggregateFunc{
|
||||
func(context.Context) (*es_models.Aggregate, error) {
|
||||
return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil
|
||||
},
|
||||
func(context.Context) (*es_models.Aggregate, error) {
|
||||
return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := Push(context.Background(), tt.args.push, tt.args.appender, tt.args.aggregaters...)
|
||||
if tt.wantErr == nil && err != nil {
|
||||
t.Errorf("no error expected %v", err)
|
||||
}
|
||||
if tt.wantErr != nil && !tt.wantErr(err) {
|
||||
t.Errorf("no error has wrong type %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user