File size: 2,481 Bytes
d6f631f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
package subscription

import (
	"fmt"
	"time"

	"github.com/samber/lo"

	"github.com/openmeterio/openmeter/pkg/models"
)

// The spec is the core object of Subscriptions being manipulated.
// Different resources like Patches, Addons, etc... can apply themselves to the spec.

type ApplyContext struct {
	CurrentTime time.Time
}

// Things can apply themselves to the spec
type AppliesToSpec interface {
	// This method should only ever be invoked by SubscriptionSpec (spec.ApplyX), so subsequent validations and logic are always guaranteed to run
	// FIXME(galexi): this can be enforced by making it private, but that will require rewriting all patches & addons
	ApplyTo(spec *SubscriptionSpec, actx ApplyContext) error
}

const (
	// Some errors are allowed during applying individual things to the spec, but still mean the Spec as a whole is invalid
	subscriptionPatchErrAttrNameAllowedDuringApplyingToSpecError = "allowed_during_applying_to_spec_error"
)

func AllowedDuringApplyingToSpecError() models.ValidationIssueOption {
	return models.WithAttribute(subscriptionPatchErrAttrNameAllowedDuringApplyingToSpecError, true)
}

func NewAppliesToSpec(fn func(spec *SubscriptionSpec, actx ApplyContext) error) AppliesToSpec {
	return &someAppliesToSpec{
		Fn: fn,
	}
}

var _ AppliesToSpec = &someAppliesToSpec{}

type someAppliesToSpec struct {
	Fn func(spec *SubscriptionSpec, actx ApplyContext) error
}

func (s *someAppliesToSpec) ApplyTo(spec *SubscriptionSpec, actx ApplyContext) error {
	return s.Fn(spec, actx)
}

// NewAggregateAppliesToSpec aggregates multiple applies to spec into a single applies to spec, and also validates the spec after applying all the patches
func NewAggregateAppliesToSpec(applieses []AppliesToSpec) AppliesToSpec {
	return NewAppliesToSpec(func(spec *SubscriptionSpec, actx ApplyContext) error {
		for i, applies := range applieses {
			if err := spec.Apply(applies, actx); err != nil {
				wrapError := func(err error) error {
					return models.ErrorWithComponent(models.ComponentName(fmt.Sprintf("patch at idx %d", i)), err)
				}

				issues, err := models.AsValidationIssues(err)
				if err != nil {
					return wrapError(err)
				}

				if lo.EveryBy(issues, func(issue models.ValidationIssue) bool {
					return IsValidationIssueWithBoolAttr(issue, subscriptionPatchErrAttrNameAllowedDuringApplyingToSpecError)
				}) {
					continue
				}

				// Otherwise we return with the error
				return wrapError(err)
			}
		}

		return nil
	})
}