openmeter / subscription /patch /additem.go
Leon4gr45's picture
Upload folder using huggingface_hub (part 6)
d6f631f verified
Raw
History Blame Contribute Delete
6.45 kB
package patch
import (
"fmt"
"github.com/openmeterio/openmeter/openmeter/subscription"
"github.com/openmeterio/openmeter/pkg/datetime"
"github.com/openmeterio/openmeter/pkg/models"
)
type PatchAddItem struct {
PhaseKey string
ItemKey string
CreateInput subscription.SubscriptionItemSpec
}
func (a PatchAddItem) Op() subscription.PatchOperation {
return subscription.PatchOperationAdd
}
func (a PatchAddItem) Path() subscription.SpecPath {
return subscription.NewItemPath(a.PhaseKey, a.ItemKey)
}
func (a PatchAddItem) Value() subscription.SubscriptionItemSpec {
return a.CreateInput
}
func (a PatchAddItem) 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 models.ErrorWithFieldPrefix(a.FieldDescriptor(), err)
}
return nil
}
func (a PatchAddItem) FieldDescriptor() *models.FieldDescriptor {
return models.NewFieldSelectorGroup(
models.NewFieldSelectorGroup(
models.NewFieldSelector("phases"),
models.NewFieldSelector(a.PhaseKey),
).WithAttributes(models.Attributes{
subscription.PhaseDescriptor: true,
}),
models.NewFieldSelector("items"),
models.NewFieldSelector(a.ItemKey),
)
}
func (a PatchAddItem) ValueAsAny() any {
return a.CreateInput
}
var _ subscription.ValuePatch[subscription.SubscriptionItemSpec] = PatchAddItem{}
func (a PatchAddItem) ApplyTo(spec *subscription.SubscriptionSpec, actx subscription.ApplyContext) error {
phase, ok := spec.Phases[a.PhaseKey]
if !ok {
return &subscription.PatchValidationError{Msg: fmt.Sprintf("phase %s not found", a.PhaseKey)}
}
phaseStartTime, _ := phase.StartAfter.AddTo(spec.ActiveFrom)
// Checks we need:
// 1. You cannot add items to previous phases
currentPhase, exists := spec.GetCurrentPhaseAt(actx.CurrentTime)
if !exists {
// If the current phase doesn't exist then either all phases are in the past or in the future
// If all phases are in the past then no addition 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 add item to phase %s which starts before current phase", a.PhaseKey)}
} else {
// If it's added to a future phase, the matching key for the phase has to be empty
if len(phase.ItemsByKey) > 0 {
return &subscription.PatchForbiddenError{Msg: fmt.Sprintf("cannot add item to future phase %s which already has items", a.PhaseKey)}
}
}
} else {
currentPhaseStartTime, _ := currentPhase.StartAfter.AddTo(spec.ActiveFrom)
// If the selected phase is before the current phase, it's forbidden
if phaseStartTime.Before(currentPhaseStartTime) {
return &subscription.PatchForbiddenError{Msg: fmt.Sprintf("cannot add item to phase %s which starts before current phase", a.PhaseKey)}
} else if phase.PhaseKey == currentPhase.PhaseKey {
// Sanity check
if actx.CurrentTime.Before(phaseStartTime) {
return fmt.Errorf("current time is before the current phase start which is impossible")
}
// 2. If it's added to the current phase, the specified start time cannot point to the past
if a.CreateInput.ActiveFromOverrideRelativeToPhaseStart != nil {
iST, _ := a.CreateInput.ActiveFromOverrideRelativeToPhaseStart.AddTo(phaseStartTime)
if iST.Before(actx.CurrentTime) {
return &subscription.PatchForbiddenError{Msg: fmt.Sprintf("cannot add item to phase %s which would become active in the past at %s", a.PhaseKey, iST)}
}
} else {
// 3. If it's added to the current phase, and start time is not specified, it will be set for the current time, as you cannot change the past
diff := datetime.ISODurationBetween(phaseStartTime, actx.CurrentTime)
a.CreateInput.ActiveFromOverrideRelativeToPhaseStart = &diff
}
} else if phaseStartTime.After(currentPhaseStartTime) {
// 4. If you're adding it to a future phase, the matching key for the phase has to be empty
if len(phase.ItemsByKey[a.ItemKey]) > 0 {
return &subscription.PatchForbiddenError{Msg: fmt.Sprintf("cannot add item to future phase %s which already has items", a.PhaseKey)}
}
} else {
return fmt.Errorf("didn't enter any logical branch")
}
}
// Finally, let's try to add it to the phase
if phase.ItemsByKey[a.ItemKey] == nil {
phase.ItemsByKey[a.ItemKey] = make([]*subscription.SubscriptionItemSpec, 0)
}
// If it's added to the current phase, we need to close the activity of any current item if present
hasCurrentItemAndShouldCloseCurrentItemForKey := false
if exists && currentPhase.PhaseKey == phase.PhaseKey {
if len(phase.ItemsByKey[a.ItemKey]) > 0 {
hasCurrentItemAndShouldCloseCurrentItemForKey = true
}
}
if hasCurrentItemAndShouldCloseCurrentItemForKey {
// Sanity check
if len(phase.ItemsByKey[a.ItemKey]) == 0 {
return fmt.Errorf("there should be an item to close")
}
itemToClose := phase.ItemsByKey[a.ItemKey][len(phase.ItemsByKey[a.ItemKey])-1]
// If it already has a scheduled end time, which is later than the time this new item should start, we should error.
// The user can circumvent this, by first issuing a delete for the item, and then adding a new one.
if itemToClose.ActiveToOverrideRelativeToPhaseStart != nil {
itemToCloseEndTime, _ := itemToClose.ActiveToOverrideRelativeToPhaseStart.AddTo(phaseStartTime)
// Sanity check
if a.CreateInput.ActiveFromOverrideRelativeToPhaseStart == nil {
return fmt.Errorf("ActiveFromOverrideRelativeToPhaseStart should already be set when adding after an already existing item for the current phase")
}
itemToAddStartTime, _ := a.CreateInput.ActiveFromOverrideRelativeToPhaseStart.AddTo(phaseStartTime)
if itemToCloseEndTime.After(itemToAddStartTime) {
return &subscription.PatchForbiddenError{Msg: fmt.Sprintf("cannot add item to phase %s which would overlap with a current item, you should delete first", a.PhaseKey)}
}
}
// Let's update the current item to close to actually close as the new item starts
itemToClose.ActiveToOverrideRelativeToPhaseStart = a.CreateInput.ActiveFromOverrideRelativeToPhaseStart
}
// Finally, we simply add it as the last Spec for its key in the phase
phase.ItemsByKey[a.ItemKey] = append(phase.ItemsByKey[a.ItemKey], &a.CreateInput)
return nil
}