From 3a3471e2108df4eccb388db4241b961381ba7c9e Mon Sep 17 00:00:00 2001 From: NoahLan <6995syu@163.com> Date: Wed, 21 Jun 2023 09:31:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0hook=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E5=8C=85=EF=BC=8C=E5=BF=AB=E9=80=9Fhook.On?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/orm/nent/hook/hook.go | 188 ++++++++++++++++++++++++ core/orm/nent/mixins/optimistic_lock.go | 32 ++-- 2 files changed, 203 insertions(+), 17 deletions(-) create mode 100644 core/orm/nent/hook/hook.go diff --git a/core/orm/nent/hook/hook.go b/core/orm/nent/hook/hook.go new file mode 100644 index 0000000..b77e9e1 --- /dev/null +++ b/core/orm/nent/hook/hook.go @@ -0,0 +1,188 @@ +package hook + +import ( + "context" + "entgo.io/ent" + "fmt" +) + +// Condition is a hook condition function. +type Condition func(context.Context, ent.Mutation) bool + +// And groups conditions with the AND operator. +func And(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + if !first(ctx, m) || !second(ctx, m) { + return false + } + for _, cond := range rest { + if !cond(ctx, m) { + return false + } + } + return true + } +} + +// Or groups conditions with the OR operator. +func Or(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + if first(ctx, m) || second(ctx, m) { + return true + } + for _, cond := range rest { + if cond(ctx, m) { + return true + } + } + return false + } +} + +// Not negates a given condition. +func Not(cond Condition) Condition { + return func(ctx context.Context, m ent.Mutation) bool { + return !cond(ctx, m) + } +} + +// HasOp is a condition testing mutation operation. +func HasOp(op ent.Op) Condition { + return func(_ context.Context, m ent.Mutation) bool { + return m.Op().Is(op) + } +} + +// HasAddedFields is a condition validating `.AddedField` on fields. +func HasAddedFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if _, exists := m.AddedField(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.AddedField(field); !exists { + return false + } + } + return true + } +} + +// HasClearedFields is a condition validating `.FieldCleared` on fields. +func HasClearedFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if exists := m.FieldCleared(field); !exists { + return false + } + for _, field := range fields { + if exists := m.FieldCleared(field); !exists { + return false + } + } + return true + } +} + +// HasFields is a condition validating `.Field` on fields. +func HasFields(field string, fields ...string) Condition { + return func(_ context.Context, m ent.Mutation) bool { + if _, exists := m.Field(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.Field(field); !exists { + return false + } + } + return true + } +} + +// If executes the given hook under condition. +// +// hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) +// +func If(hk ent.Hook, cond Condition) ent.Hook { + return func(next ent.Mutator) ent.Mutator { + return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if cond(ctx, m) { + return hk(next).Mutate(ctx, m) + } + return next.Mutate(ctx, m) + }) + } +} + +// On executes the given hook only for the given operation. +// +// hook.On(Log, ent.Delete|ent.Create) +// +func On(hk ent.Hook, op ent.Op) ent.Hook { + return If(hk, HasOp(op)) +} + +// Unless skips the given hook only for the given operation. +// +// hook.Unless(Log, ent.Update|ent.UpdateOne) +// +func Unless(hk ent.Hook, op ent.Op) ent.Hook { + return If(hk, Not(HasOp(op))) +} + +// FixedError is a hook returning a fixed error. +func FixedError(err error) ent.Hook { + return func(ent.Mutator) ent.Mutator { + return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) { + return nil, err + }) + } +} + +// Reject returns a hook that rejects all operations that match op. +// +// func (T) Hooks() []ent.Hook { +// return []ent.Hook{ +// Reject(ent.Delete|ent.Update), +// } +// } +// +func Reject(op ent.Op) ent.Hook { + hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) + return On(hk, op) +} + +// Chain acts as a list of hooks and is effectively immutable. +// Once created, it will always hold the same set of hooks in the same order. +type Chain struct { + hooks []ent.Hook +} + +// NewChain creates a new chain of hooks. +func NewChain(hooks ...ent.Hook) Chain { + return Chain{append([]ent.Hook(nil), hooks...)} +} + +// Hook chains the list of hooks and returns the final hook. +func (c Chain) Hook() ent.Hook { + return func(mutator ent.Mutator) ent.Mutator { + for i := len(c.hooks) - 1; i >= 0; i-- { + mutator = c.hooks[i](mutator) + } + return mutator + } +} + +// Append extends a chain, adding the specified hook +// as the last ones in the mutation flow. +func (c Chain) Append(hooks ...ent.Hook) Chain { + newHooks := make([]ent.Hook, 0, len(c.hooks)+len(hooks)) + newHooks = append(newHooks, c.hooks...) + newHooks = append(newHooks, hooks...) + return Chain{newHooks} +} + +// Extend extends a chain, adding the specified chain +// as the last ones in the mutation flow. +func (c Chain) Extend(chain Chain) Chain { + return c.Append(chain.hooks...) +} diff --git a/core/orm/nent/mixins/optimistic_lock.go b/core/orm/nent/mixins/optimistic_lock.go index fc8e9a5..d41e5ea 100644 --- a/core/orm/nent/mixins/optimistic_lock.go +++ b/core/orm/nent/mixins/optimistic_lock.go @@ -7,6 +7,7 @@ import ( "entgo.io/ent/schema/field" "entgo.io/ent/schema/mixin" "fmt" + "git.noahlan.cn/noahlan/ntool-biz/core/orm/nent/hook" ) // OptimisticLockMixin implements the ent.Mixin for sharing @@ -33,28 +34,25 @@ func WithVersion(parent context.Context, version int64) context.Context { // Hooks of the OptimisticLockMixin. func (d OptimisticLockMixin) Hooks() []ent.Hook { return []ent.Hook{ - func(next ent.Mutator) ent.Mutator { + hook.On(func(next ent.Mutator) ent.Mutator { return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { - if m.Op().Is(ent.OpUpdate | ent.OpUpdateOne) { - mx, ok := m.(interface { - AddVersion(i int64) - WhereP(...func(*sql.Selector)) - }) - if !ok { - return nil, fmt.Errorf("unexpected mutation type %T", m) - } + mx, ok := m.(interface { + AddVersion(i int64) + WhereP(...func(*sql.Selector)) + }) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } - // get pre-version for update condition | where version={pre-version} - if preVersion, preVersionOk := ctx.Value(optimisticLockKey{}).(int64); preVersionOk { - d.P(mx, preVersion) - } - // set version=version+1 - mx.AddVersion(1) - return next.Mutate(ctx, m) + // get pre-version for update condition | where version={pre-version} + if preVersion, preVersionOk := ctx.Value(optimisticLockKey{}).(int64); preVersionOk { + d.P(mx, preVersion) } + // set version=version+1 + mx.AddVersion(1) return next.Mutate(ctx, m) }) - }, + }, ent.OpUpdate|ent.OpUpdateOne), } }