openmeter / subscription /patch /addphase.go
Leon4gr45's picture
Upload folder using huggingface_hub (part 6)
d6f631f verified
Raw
History Blame Contribute Delete
3.49 kB
package patch
import (
"fmt"
"github.com/openmeterio/openmeter/openmeter/subscription"
"github.com/openmeterio/openmeter/pkg/datetime"
)
type PatchAddPhase struct {
PhaseKey string
CreateInput subscription.CreateSubscriptionPhaseInput
}
func (a PatchAddPhase) Op() subscription.PatchOperation {
return subscription.PatchOperationAdd
}
func (a PatchAddPhase) Path() subscription.SpecPath {
return subscription.NewPhasePath(a.PhaseKey)
}
func (a PatchAddPhase) Value() subscription.CreateSubscriptionPhaseInput {
return a.CreateInput
}
func (a PatchAddPhase) ValueAsAny() any {
return a.CreateInput
}
func (a PatchAddPhase) Validate() error {
if err := a.Path().Validate(); err != nil {
return err
}
if err := a.Op().Validate(); err != nil {
return err
}
if err := a.CreateInput.Validate(); err != nil {
return err
}
return nil
}
var _ subscription.ValuePatch[subscription.CreateSubscriptionPhaseInput] = PatchAddPhase{}
func (a PatchAddPhase) ApplyTo(spec *subscription.SubscriptionSpec, actx subscription.ApplyContext) error {
if _, exists := spec.Phases[a.PhaseKey]; exists {
return &subscription.PatchConflictError{Msg: fmt.Sprintf("phase %s already exists", a.PhaseKey)}
}
// Checks we need:
vST, _ := a.Value().StartAfter.AddTo(spec.ActiveFrom)
// 2. You can only add a phase for future
if !vST.After(actx.CurrentTime) {
return &subscription.PatchForbiddenError{Msg: "cannot add phase in the past"}
}
// 3. You can only add a phase before the subscription ends
if spec.ActiveTo != nil && !vST.Before(*spec.ActiveTo) {
return &subscription.PatchForbiddenError{Msg: "cannot add phase after the subscription ends"}
}
// Let's apply the patch
// Let's get all later phases & make sure their start times is aligned based on the new phase's duration:
// 1. The very next phase should start based on the new phase's duration
// 2. All other phases should preserve their relative start times i.e. spacing
// To achieve this, we determine the difference between the next already scheduled phase's start and the duration, then add that difference to all later phases. Note that this difference is signed.
sortedPhases := spec.GetSortedPhases()
var diff datetime.ISODuration
for i := range sortedPhases {
p := sortedPhases[i]
// We use !.Before() cause we might insert the phase at the same time another one starts
if v, _ := p.StartAfter.AddTo(spec.ActiveFrom); !v.Before(vST) && diff.IsZero() {
tillNextPhase, err := p.StartAfter.Subtract(a.Value().StartAfter)
if err != nil {
return fmt.Errorf("failed to calculate difference between phases: %w", err)
}
diff, err = a.Value().Duration.Subtract(tillNextPhase)
if err != nil {
return fmt.Errorf("failed to calculate difference between phases: %w", err)
}
}
// Once we've reached the next phase lets increment the StartAfter by diff
if !diff.IsZero() {
sa, err := p.StartAfter.Add(diff)
if err != nil {
return fmt.Errorf("failed to adjust phase %s start time: %w", p.PhaseKey, err)
}
sortedPhases[i].StartAfter = sa
}
}
// And then let's add the new phase
spec.Phases[a.PhaseKey] = &subscription.SubscriptionPhaseSpec{
CreateSubscriptionPhasePlanInput: a.CreateInput.CreateSubscriptionPhasePlanInput,
CreateSubscriptionPhaseCustomerInput: a.CreateInput.CreateSubscriptionPhaseCustomerInput,
ItemsByKey: make(map[string][]*subscription.SubscriptionItemSpec),
}
return nil
}