Skip to content

Commit 6e7cbe4

Browse files
authored
Plan-Addon RateCard Compatibility adjustments (#2708)
1 parent 703f335 commit 6e7cbe4

File tree

4 files changed

+50
-51
lines changed

4 files changed

+50
-51
lines changed

openmeter/productcatalog/addon.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,11 @@ func AddonWithCompatiblePrices() models.ValidatorFunc[Addon] {
284284
}
285285
}
286286
}
287+
288+
// Determines if an Addon RateCard will effect a given Plan RateCard
289+
// Right now we only support a single RateCard per addon effecting a single plan RateCard and we match them by key.
290+
func AddonRateCardMatcherForAGivenPlanRateCard(planRateCard RateCard) func(addonRateCard RateCard) bool {
291+
return func(addonRateCard RateCard) bool {
292+
return addonRateCard.Key() == planRateCard.Key()
293+
}
294+
}

openmeter/productcatalog/planaddon.go

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/samber/lo"
88

99
"github.com/openmeterio/openmeter/pkg/models"
10+
"github.com/openmeterio/openmeter/pkg/slicesx"
1011
)
1112

1213
type PlanAddonMeta struct {
@@ -108,11 +109,8 @@ func (c PlanAddon) Validate() error {
108109
} else {
109110
// Validate ratecards from plan phases and addon.
110111
for _, phase := range c.Plan.Phases[phaseIdx:] {
111-
// If ratecards can be merged then they are compatible.
112-
if err := phase.RateCards.Compatible(c.Addon.RateCards); err != nil {
113-
errs = append(errs,
114-
fmt.Errorf("invalid phase [phase.key=%s]: ratecards are not compatible: %w", phase.Key, err),
115-
)
112+
if err := c.validateRateCardsInPhase(phase.RateCards, c.Addon.RateCards); err != nil {
113+
errs = append(errs, fmt.Errorf("invalid phase [phase.key=%s]: ratecards are not compatible: %w", phase.Key, err))
116114
}
117115
}
118116
}
@@ -122,3 +120,33 @@ func (c PlanAddon) Validate() error {
122120

123121
return models.NewNillableGenericValidationError(errors.Join(errs...))
124122
}
123+
124+
func (c PlanAddon) validateRateCardsInPhase(phaseRateCards, addonRateCards RateCards) error {
125+
var errs []error
126+
127+
// We'll assume phaseRateCards and addonRateCards are otherwise valid by unique constraints and formal contents...
128+
for _, phaseRateCard := range phaseRateCards {
129+
affectingRateCards := lo.Filter(addonRateCards, slicesx.AsFilterIteratee(AddonRateCardMatcherForAGivenPlanRateCard(phaseRateCard)))
130+
131+
// No RateCards affect this plan RateCard
132+
if len(affectingRateCards) == 0 {
133+
continue
134+
}
135+
136+
// For now we only support a single RateCard per addon effecting a single plan RateCard.
137+
if len(affectingRateCards) > 1 {
138+
errs = append(errs, fmt.Errorf("multiple add-on ratecards affect plan ratecard [plan.ratecard.key=%s]: affecting add-on ratecard keys: %+v", phaseRateCard.Key(), lo.Map(affectingRateCards, func(item RateCard, index int) string {
139+
return item.Key()
140+
})))
141+
142+
continue
143+
}
144+
145+
// Finally, let's check that they are compatible
146+
if err := rateCardsCompatible(phaseRateCard, affectingRateCards[0]); err != nil {
147+
errs = append(errs, fmt.Errorf("plan ratecard is not compatible with add-on ratecard [plan.ratecard.key=%s add-on.ratecard.key=%s]: %w", phaseRateCard.Key(), affectingRateCards[0].Key(), err))
148+
}
149+
}
150+
151+
return models.NewNillableGenericValidationError(errors.Join(errs...))
152+
}

openmeter/productcatalog/ratecard.go

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -560,46 +560,6 @@ func (c RateCards) Validate() error {
560560
return models.NewNillableGenericValidationError(errors.Join(errs...))
561561
}
562562

563-
func (c RateCards) Compatible(overlays RateCards) error {
564-
if err := c.Validate(); err != nil {
565-
return err
566-
}
567-
568-
if err := overlays.Validate(); err != nil {
569-
return err
570-
}
571-
572-
var errs []error
573-
574-
m := make(map[string]rateCardWithOverlays)
575-
576-
// Collect ratecards by their keys
577-
for _, rc := range lo.Union(c, overlays) {
578-
_, ok := m[rc.Key()]
579-
if !ok {
580-
m[rc.Key()] = rateCardWithOverlays{base: rc}
581-
}
582-
583-
m[rc.Key()] = rateCardWithOverlays{
584-
base: m[rc.Key()].base,
585-
overlays: append(m[rc.Key()].overlays, rc),
586-
}
587-
}
588-
589-
for key, rc := range m {
590-
// Skip compatibility check
591-
if len(rc.overlays) == 0 {
592-
continue
593-
}
594-
595-
if err := rc.Validate(); err != nil {
596-
errs = append(errs, fmt.Errorf("incompatible ratecards [key=%s]: %w", key, err))
597-
}
598-
}
599-
600-
return models.NewNillableGenericValidationError(errors.Join(errs...))
601-
}
602-
603563
type rateCardWithOverlays struct {
604564
base RateCard
605565
overlays []RateCard
@@ -670,8 +630,8 @@ func rateCardsCompatible(r, v RateCard) error {
670630

671631
// Validate Entitlement
672632

673-
if rMeta.EntitlementTemplate != nil {
674-
if vMeta.EntitlementTemplate == nil || rMeta.EntitlementTemplate.Type() != vMeta.EntitlementTemplate.Type() {
633+
if rMeta.EntitlementTemplate != nil && vMeta.EntitlementTemplate != nil {
634+
if rMeta.EntitlementTemplate.Type() != vMeta.EntitlementTemplate.Type() {
675635
errs = append(errs, errors.New("incompatible entitlement template type"))
676636
} else {
677637
switch rMeta.EntitlementTemplate.Type() {
@@ -693,10 +653,6 @@ func rateCardsCompatible(r, v RateCard) error {
693653
rMetered.UsagePeriod.ISOString(), vMetered.UsagePeriod.ISOString()),
694654
)
695655
}
696-
697-
if lo.FromPtr(rMetered.IssueAfterReset) > lo.FromPtr(vMetered.IssueAfterReset) {
698-
errs = append(errs, errors.New("incompatible issue after reset for metered entitlement"))
699-
}
700656
case entitlement.EntitlementTypeBoolean:
701657
}
702658
}

pkg/slicesx/iteratee.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package slicesx
2+
3+
func AsFilterIteratee[T any](f func(T) bool) func(T, int) bool {
4+
return func(v T, _ int) bool {
5+
return f(v)
6+
}
7+
}

0 commit comments

Comments
 (0)