File size: 3,960 Bytes
cee2387
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package ledger

import (
	"fmt"
	"time"

	"github.com/samber/mo"

	"github.com/openmeterio/openmeter/pkg/models"
	"github.com/openmeterio/openmeter/pkg/pagination/v2"
	"github.com/openmeterio/openmeter/pkg/timeutil"
)

type Query struct {
	Namespace string

	Cursor *pagination.Cursor

	Filters Filters
}

func (p Query) Validate() error {
	if p.Namespace == "" {
		return ErrLedgerQueryInvalid.WithAttrs(models.Attributes{
			"reason":    "namespace_required",
			"namespace": p.Namespace,
		})
	}

	if p.Cursor != nil {
		if err := p.Cursor.Validate(); err != nil {
			return ErrLedgerQueryInvalid.WithAttrs(models.Attributes{
				"reason": "cursor_invalid",
				"cursor": p.Cursor,
				"error":  err,
			})
		}
	}

	if p.Filters.TransactionID != nil && *p.Filters.TransactionID == "" {
		return ErrLedgerQueryInvalid.WithAttrs(models.Attributes{
			"reason":         "transaction_id_required",
			"transaction_id": *p.Filters.TransactionID,
		})
	}

	if p.Filters.AccountID != nil && *p.Filters.AccountID == "" {
		return ErrLedgerQueryInvalid.WithAttrs(models.Attributes{
			"reason":     "account_id_required",
			"account_id": *p.Filters.AccountID,
		})
	}

	if err := validateOptionalChargeIDFilter("source_charge_id", p.Filters.SourceChargeID); err != nil {
		return ErrLedgerQueryInvalid.WithAttrs(models.Attributes{
			"reason": "source_charge_id_invalid",
			"error":  err,
		})
	}

	if err := validateOptionalChargeIDFilter("spend_charge_id", p.Filters.SpendChargeID); err != nil {
		return ErrLedgerQueryInvalid.WithAttrs(models.Attributes{
			"reason": "spend_charge_id_invalid",
			"error":  err,
		})
	}

	if p.Filters.BookedAtPeriod != nil {
		if err := p.Filters.BookedAtPeriod.Validate(); err != nil {
			return ErrLedgerQueryInvalid.WithAttrs(models.Attributes{
				"reason":           "booked_at_period_invalid",
				"booked_at_period": p.Filters.BookedAtPeriod,
				"error":            err,
			})
		}
	}

	if p.Filters.After != nil {
		if err := p.Filters.After.Validate(); err != nil {
			return ErrLedgerQueryInvalid.WithAttrs(models.Attributes{
				"reason": "after_invalid",
				"after":  p.Filters.After,
				"error":  err,
			})
		}
	}

	if p.Filters.AsOf != nil && p.Filters.AsOf.IsZero() {
		return ErrLedgerQueryInvalid.WithAttrs(models.Attributes{
			"reason": "as_of_invalid",
			"as_of":  p.Filters.AsOf,
			"error":  fmt.Errorf("as_of must not be zero"),
		})
	}

	if p.Filters.After != nil && p.Filters.AsOf != nil {
		return ErrLedgerQueryInvalid.WithAttrs(models.Attributes{
			"reason": "after_as_of_both_set",
		})
	}

	if _, err := p.Filters.Route.Normalize(); err != nil {
		return ErrLedgerQueryInvalid.WithAttrs(models.Attributes{
			"reason": "route_invalid",
			"route":  p.Filters.Route,
			"error":  err,
		})
	}

	return nil
}

type Filters struct {
	// BookedAtPeriod is inclusive-exclusive... should it be? Maybe finally add period inclusivity params?
	BookedAtPeriod *timeutil.OpenPeriod
	After          *TransactionCursor
	AsOf           *time.Time
	TransactionID  *string
	// AccountID narrows the query to a single account via its sub-accounts.
	AccountID *string
	// SourceChargeID narrows entries by the creditpurchase charge that funded them.
	// Absent aggregates across source charges, Some(nil) selects source-less entries.
	SourceChargeID mo.Option[*string]
	// SpendChargeID narrows entries by the charge that consumed or accrued value.
	// Absent aggregates across spend charges, Some(nil) selects spend-less entries.
	SpendChargeID mo.Option[*string]
	// Route is a partial route filter. AccountID plus a full Route.Filter() identifies
	// one sub-account, but arbitrary RouteFilter values can match many sub-accounts.
	Route RouteFilter
}

func validateOptionalChargeIDFilter(name string, filter mo.Option[*string]) error {
	if filter.IsAbsent() {
		return nil
	}

	value, _ := filter.Get()
	if value != nil && *value == "" {
		return fmt.Errorf("%s must not be empty", name)
	}

	return nil
}