feat: add command domain invariants
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
rvboxv1 "github.com/rvbox/rvbox/gen/go/rvbox/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidEventSequence = errors.New("event sequence must be nonzero")
|
||||
ErrEventSequenceGap = errors.New("event sequence gap")
|
||||
ErrConflictingDuplicate = errors.New("conflicting duplicate event")
|
||||
ErrMissingDuplicate = errors.New("duplicate event has no durable fingerprint")
|
||||
ErrInvalidTruncationRange = errors.New("invalid output truncation range")
|
||||
)
|
||||
|
||||
type EventFingerprint [sha256.Size]byte
|
||||
|
||||
func FingerprintBytes(value []byte) EventFingerprint { return sha256.Sum256(value) }
|
||||
|
||||
type EventSequenceDecision uint8
|
||||
|
||||
const (
|
||||
EventSequenceInvalid EventSequenceDecision = iota
|
||||
EventSequenceAppend
|
||||
EventSequenceDuplicate
|
||||
)
|
||||
|
||||
// ClassifyEventSequence admits exactly the next event, or an immutable replay
|
||||
// whose stored fingerprint is supplied. Wire event-sequence gaps are never
|
||||
// repaired by inventing events.
|
||||
func ClassifyEventSequence(last, incoming uint64, stored *EventFingerprint, candidate EventFingerprint) (EventSequenceDecision, error) {
|
||||
if incoming == 0 {
|
||||
return EventSequenceInvalid, ErrInvalidEventSequence
|
||||
}
|
||||
if last != ^uint64(0) && incoming == last+1 {
|
||||
return EventSequenceAppend, nil
|
||||
}
|
||||
if incoming > last {
|
||||
return EventSequenceInvalid, fmt.Errorf("%w: have %d, received %d", ErrEventSequenceGap, last, incoming)
|
||||
}
|
||||
if stored == nil {
|
||||
return EventSequenceInvalid, ErrMissingDuplicate
|
||||
}
|
||||
if *stored != candidate {
|
||||
return EventSequenceInvalid, ErrConflictingDuplicate
|
||||
}
|
||||
return EventSequenceDuplicate, nil
|
||||
}
|
||||
|
||||
// ValidateOutputTruncation checks the two allowed forms: both range endpoints
|
||||
// absent for pre-sequencing client loss, or a complete nonempty retained range.
|
||||
func ValidateOutputTruncation(value *rvboxv1.OutputTruncation) error {
|
||||
if value == nil {
|
||||
return ErrInvalidTruncationRange
|
||||
}
|
||||
first, last := value.FirstRemovedEventSeq, value.LastRemovedEventSeq
|
||||
if first == nil && last == nil {
|
||||
return nil
|
||||
}
|
||||
if first == nil || last == nil || *first == 0 || *last == 0 || *first > *last {
|
||||
return ErrInvalidTruncationRange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user