more
All checks were successful
Build and Publish / Metadata (push) Successful in 8s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 1m12s
Build and Publish / BuildAndDeployArm64 (push) Successful in 4m9s

This commit is contained in:
matst80
2025-10-14 12:30:12 +02:00
parent 7a79efbb9f
commit 0b9c14c231
9 changed files with 201 additions and 25 deletions

View File

@@ -6,8 +6,13 @@ import (
"github.com/gogo/protobuf/proto"
)
type MutationResult[V any] struct {
Result V `json:"result"`
Mutations []ApplyResult `json:"mutations,omitempty"`
}
type GrainPool[V any] interface {
Apply(id uint64, mutation ...proto.Message) (V, error)
Apply(id uint64, mutation ...proto.Message) (*MutationResult[V], error)
Get(id uint64) (V, error)
OwnerHost(id uint64) (Host, bool)
Hostname() string

View File

@@ -9,8 +9,14 @@ import (
"github.com/gogo/protobuf/proto"
)
type ApplyResult struct {
Type string `json:"type"`
Mutation proto.Message `json:"mutation"`
Error error `json:"error,omitempty"`
}
type MutationRegistry interface {
Apply(grain any, msg ...proto.Message) error
Apply(grain any, msg ...proto.Message) ([]ApplyResult, error)
RegisterMutations(handlers ...MutationHandler)
Create(typeName string) (proto.Message, bool)
GetTypeName(msg proto.Message) (string, bool)
@@ -145,12 +151,14 @@ func (r *ProtoMutationRegistry) Create(typeName string) (proto.Message, bool) {
// Returns updated grain if successful.
//
// If the mutation is not registered, returns (nil, ErrMutationNotRegistered).
func (r *ProtoMutationRegistry) Apply(grain any, msg ...proto.Message) error {
func (r *ProtoMutationRegistry) Apply(grain any, msg ...proto.Message) ([]ApplyResult, error) {
results := make([]ApplyResult, 0, len(msg))
if grain == nil {
return fmt.Errorf("nil grain")
return results, fmt.Errorf("nil grain")
}
if msg == nil {
return fmt.Errorf("nil mutation message")
return results, fmt.Errorf("nil mutation message")
}
for _, m := range msg {
@@ -159,18 +167,18 @@ func (r *ProtoMutationRegistry) Apply(grain any, msg ...proto.Message) error {
entry, ok := r.mutationRegistry[rt]
r.mutationRegistryMu.RUnlock()
if !ok {
return ErrMutationNotRegistered
}
if err := entry.Handle(grain, m); err != nil {
return err
results = append(results, ApplyResult{Error: ErrMutationNotRegistered, Type: rt.Name(), Mutation: m})
continue
}
err := entry.Handle(grain, m)
results = append(results, ApplyResult{Error: err, Type: rt.Name(), Mutation: m})
}
// if entry.updateTotals {
// grain.UpdateTotals()
// }
return nil
return results, nil
}
// RegisteredMutations returns metadata for all registered mutations (snapshot).

View File

@@ -78,7 +78,7 @@ func TestRegisteredMutationBasics(t *testing.T) {
// Apply happy path
state := &cartState{}
add := &messages.AddItem{ItemId: 42, Quantity: 3, Sku: "ABC"}
if err := reg.Apply(state, add); err != nil {
if _, err := reg.Apply(state, add); err != nil {
t.Fatalf("Apply returned error: %v", err)
}
if state.calls != 1 {
@@ -89,17 +89,17 @@ func TestRegisteredMutationBasics(t *testing.T) {
}
// Apply nil grain
if err := reg.Apply(nil, add); err == nil {
if _, err := reg.Apply(nil, add); err == nil {
t.Fatalf("expected error for nil grain")
}
// Apply nil message
if err := reg.Apply(state, nil); err == nil {
if _, err := reg.Apply(state, nil); err == nil {
t.Fatalf("expected error for nil mutation message")
}
// Apply unregistered message
if err := reg.Apply(state, &messages.Noop{}); !errors.Is(err, ErrMutationNotRegistered) {
if _, err := reg.Apply(state, &messages.Noop{}); !errors.Is(err, ErrMutationNotRegistered) {
t.Fatalf("expected ErrMutationNotRegistered, got %v", err)
}
}

View File

@@ -371,13 +371,15 @@ func (p *SimpleGrainPool[V]) getOrClaimGrain(id uint64) (Grain[V], error) {
// var ErrNotOwner = fmt.Errorf("not owner")
// Apply applies a mutation to a grain.
func (p *SimpleGrainPool[V]) Apply(id uint64, mutation ...proto.Message) (*V, error) {
func (p *SimpleGrainPool[V]) Apply(id uint64, mutation ...proto.Message) (*MutationResult[*V], error) {
grain, err := p.getOrClaimGrain(id)
if err != nil {
return nil, err
}
if applyErr := p.mutationRegistry.Apply(grain, mutation...); applyErr != nil {
return nil, applyErr
mutations, err := p.mutationRegistry.Apply(grain, mutation...)
if err != nil {
return nil, err
}
if p.storage != nil {
go func() {
@@ -386,7 +388,14 @@ func (p *SimpleGrainPool[V]) Apply(id uint64, mutation ...proto.Message) (*V, er
}
}()
}
return grain.GetCurrentState()
result, err := grain.GetCurrentState()
if err != nil {
return nil, err
}
return &MutationResult[*V]{
Result: result,
Mutations: mutations,
}, nil
}
// Get returns the current state of a grain.

View File

@@ -1,6 +1,9 @@
package voucher
import "math"
import (
"errors"
"math"
)
type Rule struct {
Type string `json:"type"`
@@ -20,8 +23,13 @@ type Service struct {
}
var ErrInvalidCode = errors.New("invalid vouchercode")
func (s *Service) GetVoucher(code string) (*Voucher, error) {
value := int64(math.Round(100 * math.Pow(10, 2)))
if code == "" {
return nil, ErrInvalidCode
}
return &Voucher{
Code: code,
Value: value,