File size: 2,884 Bytes
16cdcb7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | package patch
import (
"fmt"
"github.com/openmeterio/openmeter/openmeter/subscription"
"github.com/openmeterio/openmeter/pkg/datetime"
)
type PatchStretchPhase struct {
PhaseKey string
// Signed duration
Duration datetime.ISODuration
}
func (p PatchStretchPhase) Op() subscription.PatchOperation {
return subscription.PatchOperationStretch
}
func (p PatchStretchPhase) Path() subscription.SpecPath {
return subscription.NewPhasePath(p.PhaseKey)
}
func (p PatchStretchPhase) Value() datetime.ISODuration {
return p.Duration
}
func (p PatchStretchPhase) ValueAsAny() any {
return p.Duration
}
func (p PatchStretchPhase) Validate() error {
if err := p.Path().Validate(); err != nil {
return err
}
if err := p.Op().Validate(); err != nil {
return err
}
if p.Duration.IsZero() {
return fmt.Errorf("duration cannot be zero")
}
return nil
}
var _ subscription.ValuePatch[datetime.ISODuration] = PatchStretchPhase{}
func (p PatchStretchPhase) ApplyTo(spec *subscription.SubscriptionSpec, actx subscription.ApplyContext) error {
phase, ok := spec.Phases[p.PhaseKey]
if !ok {
return fmt.Errorf("phase %s not found", p.PhaseKey)
}
sortedPhases := spec.GetSortedPhases()
// Checks we need:
pST, _ := phase.StartAfter.AddTo(spec.ActiveFrom)
// 2. You cannot extend past phases, only current or future ones
current, exists := spec.GetCurrentPhaseAt(actx.CurrentTime)
if exists {
cPST, _ := current.StartAfter.AddTo(spec.ActiveFrom)
if pST.Before(cPST) {
return &subscription.PatchForbiddenError{Msg: "cannot extend past phase"}
}
} else {
// If current phase doesn't exist then all phases are either in the past or in the future
// If they're all in the past then the by checking any we can see if it should fail or not
if pST.Before(actx.CurrentTime) {
return &subscription.PatchForbiddenError{Msg: "cannot extend past phase"}
}
}
if len(sortedPhases) < 2 {
return &subscription.PatchConflictError{Msg: "cannot stretch a single phase"}
}
reachedTargetPhase := false
for i, thisP := range sortedPhases {
if thisP.PhaseKey == p.PhaseKey {
reachedTargetPhase = true
continue
}
if reachedTargetPhase {
// Adding durtions in the semantic way (using ISO8601 format)
sa, err := thisP.StartAfter.Add(p.Duration)
if err != nil {
return &subscription.PatchValidationError{Msg: fmt.Sprintf("failed to extend phase %s: %s", thisP.PhaseKey, err)}
}
// before changing lets make sure the previous phase doesn't disappear
if i > 0 {
prev := sortedPhases[i-1]
prevStart, _ := prev.StartAfter.AddTo(spec.ActiveFrom)
newStart, _ := sa.AddTo(spec.ActiveFrom)
if !newStart.After(prevStart) {
return &subscription.PatchConflictError{Msg: fmt.Sprintf("phase %s would disappear due to stretching", prev.PhaseKey)}
}
}
sortedPhases[i].StartAfter = sa
}
}
return nil
}
|