| 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) |
|
|
| |
|
|
| |
| currentPhase, exists := spec.GetCurrentPhaseAt(actx.CurrentTime) |
| if !exists { |
| |
| |
| |
| 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 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 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 { |
| |
| if actx.CurrentTime.Before(phaseStartTime) { |
| return fmt.Errorf("current time is before the current phase start which is impossible") |
| } |
|
|
| |
| 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 { |
| |
| diff := datetime.ISODurationBetween(phaseStartTime, actx.CurrentTime) |
| a.CreateInput.ActiveFromOverrideRelativeToPhaseStart = &diff |
| } |
| } else if phaseStartTime.After(currentPhaseStartTime) { |
| |
| 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") |
| } |
| } |
|
|
| |
|
|
| if phase.ItemsByKey[a.ItemKey] == nil { |
| phase.ItemsByKey[a.ItemKey] = make([]*subscription.SubscriptionItemSpec, 0) |
| } |
|
|
| |
| hasCurrentItemAndShouldCloseCurrentItemForKey := false |
|
|
| if exists && currentPhase.PhaseKey == phase.PhaseKey { |
| if len(phase.ItemsByKey[a.ItemKey]) > 0 { |
| hasCurrentItemAndShouldCloseCurrentItemForKey = true |
| } |
| } |
|
|
| if hasCurrentItemAndShouldCloseCurrentItemForKey { |
| |
| 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 itemToClose.ActiveToOverrideRelativeToPhaseStart != nil { |
| itemToCloseEndTime, _ := itemToClose.ActiveToOverrideRelativeToPhaseStart.AddTo(phaseStartTime) |
|
|
| |
| 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)} |
| } |
| } |
|
|
| |
| itemToClose.ActiveToOverrideRelativeToPhaseStart = a.CreateInput.ActiveFromOverrideRelativeToPhaseStart |
| } |
|
|
| |
|
|
| phase.ItemsByKey[a.ItemKey] = append(phase.ItemsByKey[a.ItemKey], &a.CreateInput) |
| return nil |
| } |
|
|