File size: 2,885 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
package patch

import (
	"fmt"

	"github.com/openmeterio/openmeter/openmeter/subscription"
	"github.com/openmeterio/openmeter/pkg/datetime"
)

type PatchRemoveItem struct {
	PhaseKey string
	ItemKey  string
}

func (r PatchRemoveItem) Op() subscription.PatchOperation {
	return subscription.PatchOperationRemove
}

func (r PatchRemoveItem) Path() subscription.SpecPath {
	return subscription.NewItemPath(r.PhaseKey, r.ItemKey)
}

func (r PatchRemoveItem) Validate() error {
	if err := r.Path().Validate(); err != nil {
		return err
	}

	if err := r.Op().Validate(); err != nil {
		return err
	}

	return nil
}

var _ subscription.Patch = PatchRemoveItem{}

// RemoveItem removes the last version for the provided key
func (r PatchRemoveItem) ApplyTo(spec *subscription.SubscriptionSpec, actx subscription.ApplyContext) error {
	phase, ok := spec.Phases[r.PhaseKey]
	if !ok {
		return &subscription.PatchValidationError{Msg: fmt.Sprintf("phase %s not found", r.PhaseKey)}
	}

	phaseStartTime, _ := phase.StartAfter.AddTo(spec.ActiveFrom)

	if items, exists := phase.ItemsByKey[r.ItemKey]; !exists || len(items) == 0 {
		return &subscription.PatchValidationError{Msg: fmt.Sprintf("items for key %s doesn't exists in phase %s", r.ItemKey, r.PhaseKey)}
	}

	// Checks we need:
	// 1. You cannot remove items from previous phases
	currentPhase, exists := spec.GetCurrentPhaseAt(actx.CurrentTime)
	if !exists {
		// either all phases are in the past or in the future
		// if all phases are in the past then no removal is possible
		//
		// If all phases are in the past then the selected one is also in the past
		if st, _ := phase.StartAfter.AddTo(spec.ActiveFrom); st.Before(actx.CurrentTime) {
			return &subscription.PatchForbiddenError{Msg: fmt.Sprintf("cannot remove item from phase %s which starts before current phase", r.PhaseKey)}
		}
	} else {
		currentPhaseStartTime, _ := currentPhase.StartAfter.AddTo(spec.ActiveFrom)
		if phaseStartTime.Before(currentPhaseStartTime) {
			return &subscription.PatchForbiddenError{Msg: fmt.Sprintf("cannot remove item from phase %s which starts before current phase", r.PhaseKey)}
		}
	}

	// Finally, lets try to remove the item
	if exists && currentPhase.PhaseKey == r.PhaseKey {
		// If it's removed from the current phase, we should set its end time to the current time, instead of deleting it (as we cannot falsify history)

		diff := datetime.ISODurationBetween(phaseStartTime, actx.CurrentTime)

		phase.ItemsByKey[r.ItemKey][len(phase.ItemsByKey[r.ItemKey])-1].ActiveToOverrideRelativeToPhaseStart = &diff
	} else {
		// Otherwise (if its a future phase), we can just remove it
		phase.ItemsByKey[r.ItemKey] = phase.ItemsByKey[r.ItemKey][:len(phase.ItemsByKey[r.ItemKey])-1]

		// And let's clean up the items array if it's empty
		if len(phase.ItemsByKey[r.ItemKey]) == 0 {
			delete(phase.ItemsByKey, r.ItemKey)
		}
	}

	return nil
}