From 0c8c31cae5d97635c3355f6ad3e104ee0b353574 Mon Sep 17 00:00:00 2001 From: Valera Shaitorov <shaitorov@perx.ru> Date: Mon, 17 Apr 2023 19:07:12 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20Roles=20middlewares?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/roles/middleware/caching_middleware.go | 80 +++++++ .../middleware/caching_middleware_test.go | 201 ++++++++++++++++ .../middleware/error_logging_middleware.go | 80 +++++++ pkg/roles/middleware/logging_middleware.go | 214 ++++++++++++++++++ pkg/roles/middleware/middleware.go | 28 +++ pkg/roles/middleware/recovering_middleware.go | 91 ++++++++ 6 files changed, 694 insertions(+) create mode 100644 pkg/roles/middleware/caching_middleware.go create mode 100644 pkg/roles/middleware/caching_middleware_test.go create mode 100644 pkg/roles/middleware/error_logging_middleware.go create mode 100644 pkg/roles/middleware/logging_middleware.go create mode 100644 pkg/roles/middleware/middleware.go create mode 100644 pkg/roles/middleware/recovering_middleware.go diff --git a/pkg/roles/middleware/caching_middleware.go b/pkg/roles/middleware/caching_middleware.go new file mode 100644 index 00000000..19bdfe6b --- /dev/null +++ b/pkg/roles/middleware/caching_middleware.go @@ -0,0 +1,80 @@ +package service + +import ( + "context" + "strings" + + "git.perx.ru/perxis/perxis-go/pkg/cache" + service "git.perx.ru/perxis/perxis-go/pkg/roles" +) + +func makeKey(ss ...string) string { + return strings.Join(ss, "-") +} + +func CachingMiddleware(cache *cache.Cache) Middleware { + return func(next service.Roles) service.Roles { + return &cachingMiddleware{ + cache: cache, + next: next, + } + } +} + +type cachingMiddleware struct { + cache *cache.Cache + next service.Roles +} + +func (m cachingMiddleware) Create(ctx context.Context, role *service.Role) (rl *service.Role, err error) { + rl, err = m.next.Create(ctx, role) + if err == nil { + m.cache.Remove(rl.SpaceID) + } + return rl, err +} + +func (m cachingMiddleware) Get(ctx context.Context, spaceId string, roleId string) (rl *service.Role, err error) { + key := makeKey(spaceId, roleId) + value, e := m.cache.Get(key) + if e == nil { + return value.(*service.Role), err + } + rl, err = m.next.Get(ctx, spaceId, roleId) + if err == nil { + m.cache.Set(key, rl) + } + return rl, err +} + +func (m cachingMiddleware) List(ctx context.Context, spaceId string) (roles []*service.Role, err error) { + value, e := m.cache.Get(spaceId) + if e == nil { + return value.([]*service.Role), err + } + roles, err = m.next.List(ctx, spaceId) + if err == nil { + m.cache.Set(spaceId, roles) + } + return roles, err +} + +func (m cachingMiddleware) Update(ctx context.Context, role *service.Role) (err error) { + err = m.next.Update(ctx, role) + if err == nil { + key := makeKey(role.SpaceID, role.ID) + m.cache.Remove(key) + m.cache.Remove(role.SpaceID) + } + return err +} + +func (m cachingMiddleware) Delete(ctx context.Context, spaceId string, roleId string) (err error) { + err = m.next.Delete(ctx, spaceId, roleId) + if err == nil { + key := makeKey(spaceId, roleId) + m.cache.Remove(key) + m.cache.Remove(spaceId) + } + return err +} diff --git a/pkg/roles/middleware/caching_middleware_test.go b/pkg/roles/middleware/caching_middleware_test.go new file mode 100644 index 00000000..bb5496b5 --- /dev/null +++ b/pkg/roles/middleware/caching_middleware_test.go @@ -0,0 +1,201 @@ +package service + +import ( + "context" + "testing" + "time" + + "git.perx.ru/perxis/perxis-go/pkg/cache" + "git.perx.ru/perxis/perxis-go/pkg/errors" + "git.perx.ru/perxis/perxis-go/pkg/roles" + rsmocks "git.perx.ru/perxis/perxis-go/pkg/roles/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestRolesCache(t *testing.T) { + + const ( + roleID = "roleID" + spaceID = "spaceID" + size = 5 + ttl = 20 * time.Millisecond + ) + + errNotFound := errors.NotFound(errors.New("not found")) + + ctx := context.Background() + + t.Run("Get from cache", func(t *testing.T) { + rl := &rsmocks.Roles{} + + svc := CachingMiddleware(cache.NewCache(size, ttl))(rl) + + rl.On("Get", mock.Anything, spaceID, roleID).Return(&roles.Role{ID: roleID, SpaceID: spaceID, Description: "Role"}, nil).Once() + + v1, err := svc.Get(ctx, spaceID, roleID) + require.NoError(t, err) + + v2, err := svc.Get(ctx, spaceID, roleID) + require.NoError(t, err) + assert.Same(t, v1, v2, "Ожидается при повторном запросе получение объекта из кэша.") + + rl.AssertExpectations(t) + }) + + t.Run("List from cache", func(t *testing.T) { + rl := &rsmocks.Roles{} + + svc := CachingMiddleware(cache.NewCache(size, ttl))(rl) + + rl.On("List", mock.Anything, spaceID).Return([]*roles.Role{{ID: roleID, SpaceID: spaceID, Description: "Role"}}, nil).Once() + + vl1, err := svc.List(ctx, spaceID) + require.NoError(t, err) + + vl2, err := svc.List(ctx, spaceID) + require.NoError(t, err) + assert.Same(t, vl1[0], vl2[0], "Ожидается при повторном запросе получение объектов из кэша.") + + rl.AssertExpectations(t) + }) + + t.Run("Invalidate cache", func(t *testing.T) { + t.Run("After Update", func(t *testing.T) { + rl := &rsmocks.Roles{} + + svc := CachingMiddleware(cache.NewCache(size, ttl))(rl) + + rl.On("Get", mock.Anything, spaceID, roleID).Return(&roles.Role{ID: roleID, SpaceID: spaceID, Description: "Role"}, nil).Once() + rl.On("List", mock.Anything, spaceID).Return([]*roles.Role{{ID: roleID, SpaceID: spaceID, Description: "Role"}}, nil).Once() + + v1, err := svc.Get(ctx, spaceID, roleID) + require.NoError(t, err) + + v2, err := svc.Get(ctx, spaceID, roleID) + require.NoError(t, err) + assert.Same(t, v1, v2, "Ожидается при повторном запросе получение объектов из кэша.") + + vl1, err := svc.List(ctx, spaceID) + require.NoError(t, err) + + vl2, err := svc.List(ctx, spaceID) + require.NoError(t, err) + assert.Same(t, vl1[0], vl2[0], "Ожидается при повторном запросе получение объектов из кэша.") + + rl.On("Update", mock.Anything, mock.Anything).Return(nil).Once() + + err = svc.Update(ctx, &roles.Role{ID: roleID, SpaceID: spaceID, Description: "RoleUPD"}) + require.NoError(t, err) + + rl.On("Get", mock.Anything, spaceID, roleID).Return(&roles.Role{ID: roleID, SpaceID: spaceID, Description: "RoleUPD"}, nil).Once() + rl.On("List", mock.Anything, spaceID).Return([]*roles.Role{{ID: roleID, SpaceID: spaceID, Description: "RoleUPD"}}, nil).Once() + + v3, err := svc.Get(ctx, spaceID, roleID) + require.NoError(t, err) + assert.NotSame(t, v2, v3, "Ожидается что кеш объекта был удален после его обновления и объект был запрошен из сервиса.") + + vl3, err := svc.List(ctx, spaceID) + require.NoError(t, err) + assert.NotSame(t, vl2[0], vl3[0], "Ожидается что кеш объектов был удален после обновления объекта.") + + rl.AssertExpectations(t) + }) + + t.Run("After Delete", func(t *testing.T) { + rl := &rsmocks.Roles{} + + svc := CachingMiddleware(cache.NewCache(size, ttl))(rl) + + rl.On("Get", mock.Anything, spaceID, roleID).Return(&roles.Role{ID: roleID, SpaceID: spaceID, Description: "Role"}, nil).Once() + rl.On("List", mock.Anything, spaceID).Return([]*roles.Role{{ID: roleID, SpaceID: spaceID, Description: "Role"}}, nil).Once() + + v1, err := svc.Get(ctx, spaceID, roleID) + require.NoError(t, err) + + v2, err := svc.Get(ctx, spaceID, roleID) + require.NoError(t, err) + assert.Same(t, v1, v2, "Ожидается при повторном запросе получение объекта из кэша.") + + vl1, err := svc.List(ctx, spaceID) + require.NoError(t, err) + + vl2, err := svc.List(ctx, spaceID) + require.NoError(t, err) + assert.Same(t, vl1[0], vl2[0], "Ожидается при повторном запросе получение объектов из кэша.") + + rl.On("Update", mock.Anything, mock.Anything).Return(nil).Once() + + err = svc.Update(ctx, &roles.Role{ID: roleID, SpaceID: spaceID, Description: "RoleUPD"}) + require.NoError(t, err) + + rl.On("Get", mock.Anything, spaceID, roleID).Return(nil, errNotFound).Once() + rl.On("List", mock.Anything, spaceID).Return(nil, errNotFound).Once() + + v3, err := svc.Get(ctx, spaceID, roleID) + require.Error(t, err) + assert.EqualError(t, err, "not found", "Ожидается что после удаления кеш объекта был удален и получена ошибка сервиса.") + assert.Nil(t, v3) + + vl3, err := svc.List(ctx, spaceID) + require.Error(t, err) + assert.EqualError(t, err, "not found", "Ожидается что после удаления кеш объекта был удален и получена ошибка сервиса.") + assert.Nil(t, vl3) + + rl.AssertExpectations(t) + }) + + t.Run("After Create", func(t *testing.T) { + rl := &rsmocks.Roles{} + + svc := CachingMiddleware(cache.NewCache(size, ttl))(rl) + + rl.On("List", mock.Anything, spaceID).Return([]*roles.Role{{ID: roleID, SpaceID: spaceID, Description: "Role"}}, nil).Once() + + vl1, err := svc.List(ctx, spaceID) + require.NoError(t, err) + + vl2, err := svc.List(ctx, spaceID) + require.NoError(t, err) + assert.Same(t, vl1[0], vl2[0], "Ожидается при повторном запросе получение объекта из кэша.") + + rl.On("Create", mock.Anything, mock.Anything).Return(&roles.Role{ID: "roleID2", SpaceID: spaceID, Description: "Role2"}, nil).Once() + + _, err = svc.Create(ctx, &roles.Role{ID: "roleID2", SpaceID: spaceID, Description: "Role2"}) + require.NoError(t, err) + + rl.On("List", mock.Anything, spaceID).Return([]*roles.Role{{ID: roleID, SpaceID: spaceID, Description: "Role"}, {ID: "roleID2", SpaceID: spaceID, Description: "Role2"}}, nil).Once() + + vl3, err := svc.List(ctx, spaceID) + require.NoError(t, err) + assert.Len(t, vl3, 2, "Ожидает что после создания нового объекта, кеш будет очищен и объекты запрошены заново из сервиса.") + + rl.AssertExpectations(t) + }) + + t.Run("After TTL expired", func(t *testing.T) { + rl := &rsmocks.Roles{} + + svc := CachingMiddleware(cache.NewCache(size, ttl))(rl) + + rl.On("Get", mock.Anything, spaceID, roleID).Return(&roles.Role{ID: roleID, SpaceID: spaceID, Description: "Role"}, nil).Once() + + v1, err := svc.Get(ctx, spaceID, roleID) + require.NoError(t, err) + + v2, err := svc.Get(ctx, spaceID, roleID) + require.NoError(t, err) + assert.Same(t, v1, v2, "Ожидается получение объекта из кэша.") + + time.Sleep(2 * ttl) + rl.On("Get", mock.Anything, spaceID, roleID).Return(&roles.Role{ID: roleID, SpaceID: spaceID, Description: "Role"}, nil).Once() + + v3, err := svc.Get(ctx, spaceID, roleID) + require.NoError(t, err) + assert.NotSame(t, v2, v3, "Ожидается что объект был удален из кеша и получен заново из сервиса.") + + rl.AssertExpectations(t) + }) + }) +} diff --git a/pkg/roles/middleware/error_logging_middleware.go b/pkg/roles/middleware/error_logging_middleware.go new file mode 100644 index 00000000..a6ff0af4 --- /dev/null +++ b/pkg/roles/middleware/error_logging_middleware.go @@ -0,0 +1,80 @@ +// Code generated by gowrap. DO NOT EDIT. +// template: ../../../assets/templates/middleware/error_log +// gowrap: http://github.com/hexdigest/gowrap + +package service + +//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/roles -i Roles -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l "" + +import ( + "context" + + "git.perx.ru/perxis/perxis-go/pkg/roles" + "go.uber.org/zap" +) + +// errorLoggingMiddleware implements roles.Roles that is instrumented with logging +type errorLoggingMiddleware struct { + logger *zap.Logger + next roles.Roles +} + +// ErrorLoggingMiddleware instruments an implementation of the roles.Roles with simple logging +func ErrorLoggingMiddleware(logger *zap.Logger) Middleware { + return func(next roles.Roles) roles.Roles { + return &errorLoggingMiddleware{ + next: next, + logger: logger, + } + } +} + +func (m *errorLoggingMiddleware) Create(ctx context.Context, role *roles.Role) (created *roles.Role, err error) { + logger := m.logger + defer func() { + if err != nil { + logger.Warn("response error", zap.Error(err)) + } + }() + return m.next.Create(ctx, role) +} + +func (m *errorLoggingMiddleware) Delete(ctx context.Context, spaceId string, roleId string) (err error) { + logger := m.logger + defer func() { + if err != nil { + logger.Warn("response error", zap.Error(err)) + } + }() + return m.next.Delete(ctx, spaceId, roleId) +} + +func (m *errorLoggingMiddleware) Get(ctx context.Context, spaceId string, roleId string) (role *roles.Role, err error) { + logger := m.logger + defer func() { + if err != nil { + logger.Warn("response error", zap.Error(err)) + } + }() + return m.next.Get(ctx, spaceId, roleId) +} + +func (m *errorLoggingMiddleware) List(ctx context.Context, spaceId string) (roles []*roles.Role, err error) { + logger := m.logger + defer func() { + if err != nil { + logger.Warn("response error", zap.Error(err)) + } + }() + return m.next.List(ctx, spaceId) +} + +func (m *errorLoggingMiddleware) Update(ctx context.Context, role *roles.Role) (err error) { + logger := m.logger + defer func() { + if err != nil { + logger.Warn("response error", zap.Error(err)) + } + }() + return m.next.Update(ctx, role) +} diff --git a/pkg/roles/middleware/logging_middleware.go b/pkg/roles/middleware/logging_middleware.go new file mode 100644 index 00000000..764a0136 --- /dev/null +++ b/pkg/roles/middleware/logging_middleware.go @@ -0,0 +1,214 @@ +// Code generated by gowrap. DO NOT EDIT. +// template: ../../../assets/templates/middleware/access_log +// gowrap: http://github.com/hexdigest/gowrap + +package service + +//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/roles -i Roles -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l "" + +import ( + "context" + "fmt" + "time" + + "git.perx.ru/perxis/perxis-go/pkg/auth" + "git.perx.ru/perxis/perxis-go/pkg/roles" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// loggingMiddleware implements roles.Roles that is instrumented with logging +type loggingMiddleware struct { + logger *zap.Logger + next roles.Roles +} + +// LoggingMiddleware instruments an implementation of the roles.Roles with simple logging +func LoggingMiddleware(logger *zap.Logger) Middleware { + return func(next roles.Roles) roles.Roles { + return &loggingMiddleware{ + next: next, + logger: logger, + } + } +} + +func (m *loggingMiddleware) Create(ctx context.Context, role *roles.Role) (created *roles.Role, err error) { + begin := time.Now() + var fields []zapcore.Field + for k, v := range map[string]interface{}{ + "ctx": ctx, + "role": role} { + if k == "ctx" { + fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx)))) + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Create.Request", fields...) + + created, err = m.next.Create(ctx, role) + + fields = []zapcore.Field{ + zap.Duration("time", time.Since(begin)), + zap.Error(err), + } + + for k, v := range map[string]interface{}{ + "created": created, + "err": err} { + if k == "err" { + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Create.Response", fields...) + + return created, err +} + +func (m *loggingMiddleware) Delete(ctx context.Context, spaceId string, roleId string) (err error) { + begin := time.Now() + var fields []zapcore.Field + for k, v := range map[string]interface{}{ + "ctx": ctx, + "spaceId": spaceId, + "roleId": roleId} { + if k == "ctx" { + fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx)))) + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Delete.Request", fields...) + + err = m.next.Delete(ctx, spaceId, roleId) + + fields = []zapcore.Field{ + zap.Duration("time", time.Since(begin)), + zap.Error(err), + } + + for k, v := range map[string]interface{}{ + "err": err} { + if k == "err" { + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Delete.Response", fields...) + + return err +} + +func (m *loggingMiddleware) Get(ctx context.Context, spaceId string, roleId string) (role *roles.Role, err error) { + begin := time.Now() + var fields []zapcore.Field + for k, v := range map[string]interface{}{ + "ctx": ctx, + "spaceId": spaceId, + "roleId": roleId} { + if k == "ctx" { + fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx)))) + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Get.Request", fields...) + + role, err = m.next.Get(ctx, spaceId, roleId) + + fields = []zapcore.Field{ + zap.Duration("time", time.Since(begin)), + zap.Error(err), + } + + for k, v := range map[string]interface{}{ + "role": role, + "err": err} { + if k == "err" { + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Get.Response", fields...) + + return role, err +} + +func (m *loggingMiddleware) List(ctx context.Context, spaceId string) (roles []*roles.Role, err error) { + begin := time.Now() + var fields []zapcore.Field + for k, v := range map[string]interface{}{ + "ctx": ctx, + "spaceId": spaceId} { + if k == "ctx" { + fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx)))) + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("List.Request", fields...) + + roles, err = m.next.List(ctx, spaceId) + + fields = []zapcore.Field{ + zap.Duration("time", time.Since(begin)), + zap.Error(err), + } + + for k, v := range map[string]interface{}{ + "roles": roles, + "err": err} { + if k == "err" { + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("List.Response", fields...) + + return roles, err +} + +func (m *loggingMiddleware) Update(ctx context.Context, role *roles.Role) (err error) { + begin := time.Now() + var fields []zapcore.Field + for k, v := range map[string]interface{}{ + "ctx": ctx, + "role": role} { + if k == "ctx" { + fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx)))) + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Update.Request", fields...) + + err = m.next.Update(ctx, role) + + fields = []zapcore.Field{ + zap.Duration("time", time.Since(begin)), + zap.Error(err), + } + + for k, v := range map[string]interface{}{ + "err": err} { + if k == "err" { + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Update.Response", fields...) + + return err +} diff --git a/pkg/roles/middleware/middleware.go b/pkg/roles/middleware/middleware.go new file mode 100644 index 00000000..0a519811 --- /dev/null +++ b/pkg/roles/middleware/middleware.go @@ -0,0 +1,28 @@ +// Code generated by gowrap. DO NOT EDIT. +// template: ../../../assets/templates/middleware/middleware +// gowrap: http://github.com/hexdigest/gowrap + +package service + +//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/roles -i Roles -t ../../../assets/templates/middleware/middleware -o middleware.go -l "" + +import ( + "git.perx.ru/perxis/perxis-go/pkg/roles" + "go.uber.org/zap" +) + +type Middleware func(roles.Roles) roles.Roles + +func WithLog(s roles.Roles, logger *zap.Logger, log_access bool) roles.Roles { + if logger == nil { + logger = zap.NewNop() + } + + logger = logger.Named("Roles") + s = ErrorLoggingMiddleware(logger)(s) + if log_access { + s = LoggingMiddleware(logger)(s) + } + s = RecoveringMiddleware(logger)(s) + return s +} diff --git a/pkg/roles/middleware/recovering_middleware.go b/pkg/roles/middleware/recovering_middleware.go new file mode 100644 index 00000000..0c0f023b --- /dev/null +++ b/pkg/roles/middleware/recovering_middleware.go @@ -0,0 +1,91 @@ +// Code generated by gowrap. DO NOT EDIT. +// template: ../../../assets/templates/middleware/recovery +// gowrap: http://github.com/hexdigest/gowrap + +package service + +//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/roles -i Roles -t ../../../assets/templates/middleware/recovery -o recovering_middleware.go -l "" + +import ( + "context" + "fmt" + + "git.perx.ru/perxis/perxis-go/pkg/roles" + "go.uber.org/zap" +) + +// recoveringMiddleware implements roles.Roles that is instrumented with logging +type recoveringMiddleware struct { + logger *zap.Logger + next roles.Roles +} + +// RecoveringMiddleware instruments an implementation of the roles.Roles with simple logging +func RecoveringMiddleware(logger *zap.Logger) Middleware { + return func(next roles.Roles) roles.Roles { + return &recoveringMiddleware{ + next: next, + logger: logger, + } + } +} + +func (m *recoveringMiddleware) Create(ctx context.Context, role *roles.Role) (created *roles.Role, err error) { + logger := m.logger + defer func() { + if r := recover(); r != nil { + logger.Error("panic", zap.Error(fmt.Errorf("%v", r))) + err = fmt.Errorf("%v", r) + } + }() + + return m.next.Create(ctx, role) +} + +func (m *recoveringMiddleware) Delete(ctx context.Context, spaceId string, roleId string) (err error) { + logger := m.logger + defer func() { + if r := recover(); r != nil { + logger.Error("panic", zap.Error(fmt.Errorf("%v", r))) + err = fmt.Errorf("%v", r) + } + }() + + return m.next.Delete(ctx, spaceId, roleId) +} + +func (m *recoveringMiddleware) Get(ctx context.Context, spaceId string, roleId string) (role *roles.Role, err error) { + logger := m.logger + defer func() { + if r := recover(); r != nil { + logger.Error("panic", zap.Error(fmt.Errorf("%v", r))) + err = fmt.Errorf("%v", r) + } + }() + + return m.next.Get(ctx, spaceId, roleId) +} + +func (m *recoveringMiddleware) List(ctx context.Context, spaceId string) (roles []*roles.Role, err error) { + logger := m.logger + defer func() { + if r := recover(); r != nil { + logger.Error("panic", zap.Error(fmt.Errorf("%v", r))) + err = fmt.Errorf("%v", r) + } + }() + + return m.next.List(ctx, spaceId) +} + +func (m *recoveringMiddleware) Update(ctx context.Context, role *roles.Role) (err error) { + logger := m.logger + defer func() { + if r := recover(); r != nil { + logger.Error("panic", zap.Error(fmt.Errorf("%v", r))) + err = fmt.Errorf("%v", r) + } + }() + + return m.next.Update(ctx, role) +} -- GitLab