| package entitlementdriver |
|
|
| import ( |
| "encoding/json" |
| "errors" |
| "fmt" |
| "strings" |
| "time" |
|
|
| "github.com/samber/lo" |
|
|
| "github.com/openmeterio/openmeter/api" |
| "github.com/openmeterio/openmeter/openmeter/entitlement" |
| booleanentitlement "github.com/openmeterio/openmeter/openmeter/entitlement/boolean" |
| meteredentitlement "github.com/openmeterio/openmeter/openmeter/entitlement/metered" |
| staticentitlement "github.com/openmeterio/openmeter/openmeter/entitlement/static" |
| "github.com/openmeterio/openmeter/openmeter/streaming" |
| "github.com/openmeterio/openmeter/pkg/clock" |
| "github.com/openmeterio/openmeter/pkg/convert" |
| "github.com/openmeterio/openmeter/pkg/datetime" |
| "github.com/openmeterio/openmeter/pkg/defaultx" |
| "github.com/openmeterio/openmeter/pkg/timeutil" |
| ) |
|
|
| type parser struct{} |
|
|
| var Parser = parser{} |
|
|
| func (parser) ToMetered(e *entitlement.EntitlementWithCustomer) (*api.EntitlementMetered, error) { |
| metered, err := meteredentitlement.ParseFromGenericEntitlement(&e.Entitlement) |
| if err != nil { |
| return nil, err |
| } |
|
|
| var subjKey string |
| if e.Customer.UsageAttribution != nil { |
| subjKey, err = e.Customer.UsageAttribution.GetFirstSubjectKey() |
| if err != nil { |
| subjKey = "" |
| } |
| } |
|
|
| return &api.EntitlementMetered{ |
| ActiveFrom: metered.ActiveFromTime(), |
| ActiveTo: metered.ActiveToTime(), |
| CreatedAt: metered.CreatedAt, |
| DeletedAt: metered.DeletedAt, |
| FeatureId: metered.FeatureID, |
| FeatureKey: metered.FeatureKey, |
| Id: metered.ID, |
| IsSoftLimit: convert.ToPointer(metered.IsSoftLimit), |
| IsUnlimited: convert.ToPointer(false), |
| IssueAfterReset: convert.SafeDeRef(metered.IssueAfterReset, func(i meteredentitlement.IssueAfterReset) *float64 { |
| return &i.Amount |
| }), |
| IssueAfterResetPriority: convert.SafeDeRef(metered.IssueAfterReset, func(i meteredentitlement.IssueAfterReset) *uint8 { |
| return convert.SafeDeRef(i.Priority, func(p uint8) *uint8 { |
| return convert.ToPointer(p) |
| }) |
| }), |
| MeasureUsageFrom: metered.MeasureUsageFrom, |
| Metadata: convert.MapToPointer(metered.Metadata), |
| Annotations: lo.EmptyableToPtr(api.Annotations(metered.Annotations)), |
| SubjectKey: subjKey, |
| Type: api.EntitlementMeteredType(metered.EntitlementType), |
| UpdatedAt: metered.UpdatedAt, |
| UsagePeriod: *mapUsagePeriod(e.UsagePeriod), |
| CurrentUsagePeriod: *mapPeriod(e.CurrentUsagePeriod), |
| LastReset: metered.LastReset, |
| PreserveOverageAtReset: convert.ToPointer(metered.PreserveOverageAtReset), |
| }, nil |
| } |
|
|
| func (parser) ToStatic(e *entitlement.EntitlementWithCustomer) (*api.EntitlementStatic, error) { |
| static, err := staticentitlement.ParseFromGenericEntitlement(&e.Entitlement) |
| if err != nil { |
| return nil, err |
| } |
|
|
| var subjKey string |
| if e.Customer.UsageAttribution != nil { |
| subjKey, err = e.Customer.UsageAttribution.GetFirstSubjectKey() |
| if err != nil { |
| subjKey = "" |
| } |
| } |
|
|
| apiRes := &api.EntitlementStatic{ |
| ActiveFrom: static.ActiveFromTime(), |
| ActiveTo: static.ActiveToTime(), |
| CreatedAt: static.CreatedAt, |
| DeletedAt: static.DeletedAt, |
| FeatureId: static.FeatureID, |
| FeatureKey: static.FeatureKey, |
| Id: static.ID, |
| Metadata: convert.MapToPointer(static.Metadata), |
| Annotations: lo.EmptyableToPtr(api.Annotations(static.Annotations)), |
| SubjectKey: subjKey, |
| Type: api.EntitlementStaticType(static.EntitlementType), |
| UpdatedAt: static.UpdatedAt, |
| CurrentUsagePeriod: mapPeriod(static.CurrentUsagePeriod), |
| UsagePeriod: mapUsagePeriod(e.UsagePeriod), |
| } |
|
|
| apiRes.Config, err = json.Marshal(static.Config) |
| if err != nil { |
| return nil, fmt.Errorf("failed to marshal static entitlement config: %w", err) |
| } |
|
|
| return apiRes, nil |
| } |
|
|
| func (parser) ToBoolean(e *entitlement.EntitlementWithCustomer) (*api.EntitlementBoolean, error) { |
| boolean, err := booleanentitlement.ParseFromGenericEntitlement(&e.Entitlement) |
| if err != nil { |
| return nil, err |
| } |
|
|
| var subjKey string |
| if e.Customer.UsageAttribution != nil { |
| subjKey, err = e.Customer.UsageAttribution.GetFirstSubjectKey() |
| if err != nil { |
| subjKey = "" |
| } |
| } |
|
|
| apiRes := &api.EntitlementBoolean{ |
| ActiveFrom: boolean.ActiveFromTime(), |
| ActiveTo: boolean.ActiveToTime(), |
| CreatedAt: boolean.CreatedAt, |
| DeletedAt: boolean.DeletedAt, |
| FeatureId: boolean.FeatureID, |
| FeatureKey: boolean.FeatureKey, |
| Id: boolean.ID, |
| Metadata: convert.MapToPointer(boolean.Metadata), |
| Annotations: lo.EmptyableToPtr(api.Annotations(boolean.Annotations)), |
| SubjectKey: subjKey, |
| Type: api.EntitlementBooleanType(boolean.EntitlementType), |
| UpdatedAt: boolean.UpdatedAt, |
| CurrentUsagePeriod: mapPeriod(boolean.CurrentUsagePeriod), |
| UsagePeriod: mapUsagePeriod(e.UsagePeriod), |
| } |
|
|
| return apiRes, nil |
| } |
|
|
| func (p parser) ToAPIGeneric(e *entitlement.EntitlementWithCustomer) (*api.Entitlement, error) { |
| res := &api.Entitlement{} |
| switch e.EntitlementType { |
| case entitlement.EntitlementTypeMetered: |
| c, err := p.ToMetered(e) |
| if err != nil { |
| return nil, err |
| } |
| err = res.FromEntitlementMetered(*c) |
| if err != nil { |
| return nil, err |
| } |
| return res, nil |
| case entitlement.EntitlementTypeStatic: |
| c, err := p.ToStatic(e) |
| if err != nil { |
| return nil, err |
| } |
| err = res.FromEntitlementStatic(*c) |
| if err != nil { |
| return nil, err |
| } |
| return res, nil |
| case entitlement.EntitlementTypeBoolean: |
| c, err := p.ToBoolean(e) |
| if err != nil { |
| return nil, err |
| } |
| err = res.FromEntitlementBoolean(*c) |
| if err != nil { |
| return nil, err |
| } |
| return res, nil |
| default: |
| return nil, fmt.Errorf("unsupported entitlement type: %s", e.EntitlementType) |
| } |
| } |
|
|
| func MapEntitlementValueToAPI(entitlementValue entitlement.EntitlementValue) (api.EntitlementValue, error) { |
| switch ent := entitlementValue.(type) { |
| case *meteredentitlement.MeteredEntitlementValue: |
| return api.EntitlementValue{ |
| HasAccess: ent.HasAccess(), |
| Balance: &ent.Balance, |
| Usage: &ent.UsageInPeriod, |
| Overage: &ent.Overage, |
| TotalAvailableGrantAmount: &ent.TotalAvailableGrantAmount, |
| }, nil |
| case *staticentitlement.StaticEntitlementValue: |
| var config *string |
| if len(ent.Config) > 0 { |
| config = lo.ToPtr(ent.Config) |
| } |
|
|
| return api.EntitlementValue{ |
| HasAccess: ent.HasAccess(), |
| Config: config, |
| }, nil |
| case *booleanentitlement.BooleanEntitlementValue: |
| return api.EntitlementValue{ |
| HasAccess: ent.HasAccess(), |
| }, nil |
| case *entitlement.NoAccessValue: |
| return api.EntitlementValue{ |
| HasAccess: false, |
| }, nil |
| default: |
| return api.EntitlementValue{}, errors.New("unknown entitlement type") |
| } |
| } |
|
|
| func mapUsagePeriod(u *entitlement.UsagePeriod) *api.RecurringPeriod { |
| if u == nil { |
| return nil |
| } |
|
|
| origi := u.GetOriginalValueAsUsagePeriodInput().GetValue() |
|
|
| return &api.RecurringPeriod{ |
| Anchor: origi.Anchor, |
| Interval: MapRecurrenceToAPI(origi.Interval), |
| IntervalISO: origi.Interval.ISOString().String(), |
| } |
| } |
|
|
| func mapPeriod(u *timeutil.ClosedPeriod) *api.Period { |
| if u == nil { |
| return nil |
| } |
| return &api.Period{ |
| From: u.From, |
| To: u.To, |
| } |
| } |
|
|
| func ParseAPICreateInput(inp *api.EntitlementCreateInputs, ns string, usageAttribution streaming.CustomerUsageAttribution) (entitlement.CreateEntitlementInputs, error) { |
| request := entitlement.CreateEntitlementInputs{} |
| if inp == nil { |
| return request, errors.New("input is nil") |
| } |
|
|
| value, err := inp.ValueByDiscriminator() |
| if err != nil { |
| return request, err |
| } |
|
|
| switch v := value.(type) { |
| case api.EntitlementMeteredCreateInputs: |
| iv, err := MapAPIPeriodIntervalToRecurrence(v.UsagePeriod.Interval) |
| if err != nil { |
| return request, fmt.Errorf("failed to map interval: %w", err) |
| } |
|
|
| request = entitlement.CreateEntitlementInputs{ |
| Namespace: ns, |
| FeatureID: v.FeatureId, |
| FeatureKey: v.FeatureKey, |
| UsageAttribution: usageAttribution, |
| EntitlementType: entitlement.EntitlementTypeMetered, |
| IsSoftLimit: v.IsSoftLimit, |
| IssueAfterReset: v.IssueAfterReset, |
| IssueAfterResetPriority: v.IssueAfterResetPriority, |
| UsagePeriod: lo.ToPtr(timeutil.AsTimed(func(r timeutil.Recurrence) time.Time { |
| return defaultx.WithDefault(v.UsagePeriod.Anchor, clock.Now()) |
| })(timeutil.Recurrence{ |
| Anchor: defaultx.WithDefault(v.UsagePeriod.Anchor, clock.Now()), |
| Interval: iv, |
| })), |
| PreserveOverageAtReset: v.PreserveOverageAtReset, |
| } |
| if v.Metadata != nil { |
| request.Metadata = *v.Metadata |
| } |
| if v.MeasureUsageFrom != nil { |
| measureUsageFrom := &entitlement.MeasureUsageFromInput{} |
| apiTime, err := v.MeasureUsageFrom.AsMeasureUsageFromTime() |
| if err == nil { |
| err := measureUsageFrom.FromTime(apiTime) |
| if err != nil { |
| return request, err |
| } |
| } else { |
| apiEnum, err := v.MeasureUsageFrom.AsMeasureUsageFromPreset() |
| if err != nil { |
| return request, err |
| } |
|
|
| |
| if request.UsagePeriod == nil { |
| return request, errors.New("usage period is required for enum measure usage from") |
| } |
|
|
| cPer, err := request.UsagePeriod.GetValue().GetPeriodAt(clock.Now()) |
| if err != nil { |
| return request, err |
| } |
|
|
| err = measureUsageFrom.FromEnum(entitlement.MeasureUsageFromEnum(apiEnum), cPer, clock.Now()) |
| if err != nil { |
| return request, err |
| } |
| } |
| request.MeasureUsageFrom = measureUsageFrom |
| } |
| case api.EntitlementStaticCreateInputs: |
| request = entitlement.CreateEntitlementInputs{ |
| Namespace: ns, |
| FeatureID: v.FeatureId, |
| FeatureKey: v.FeatureKey, |
| UsageAttribution: usageAttribution, |
| EntitlementType: entitlement.EntitlementTypeStatic, |
| } |
|
|
| if len(v.Config) > 0 { |
| var config string |
|
|
| err = json.Unmarshal(v.Config, &config) |
| if err != nil { |
| return request, fmt.Errorf("failed to unmarshal static entitlement config: %w", err) |
| } |
|
|
| request.Config = lo.ToPtr(config) |
| } |
|
|
| if v.UsagePeriod != nil { |
| iv, err := MapAPIPeriodIntervalToRecurrence(v.UsagePeriod.Interval) |
| if err != nil { |
| return request, fmt.Errorf("failed to map interval: %w", err) |
| } |
|
|
| request.UsagePeriod = lo.ToPtr(timeutil.AsTimed(func(r timeutil.Recurrence) time.Time { |
| return defaultx.WithDefault(v.UsagePeriod.Anchor, clock.Now()) |
| })(timeutil.Recurrence{ |
| Anchor: defaultx.WithDefault(v.UsagePeriod.Anchor, clock.Now()), |
| Interval: iv, |
| })) |
| } |
| if v.Metadata != nil { |
| request.Metadata = *v.Metadata |
| } |
| case api.EntitlementBooleanCreateInputs: |
| request = entitlement.CreateEntitlementInputs{ |
| Namespace: ns, |
| FeatureID: v.FeatureId, |
| FeatureKey: v.FeatureKey, |
| UsageAttribution: usageAttribution, |
| EntitlementType: entitlement.EntitlementTypeBoolean, |
| } |
| if v.UsagePeriod != nil { |
| iv, err := MapAPIPeriodIntervalToRecurrence(v.UsagePeriod.Interval) |
| if err != nil { |
| return request, fmt.Errorf("failed to map interval: %w", err) |
| } |
|
|
| request.UsagePeriod = lo.ToPtr(timeutil.AsTimed(func(r timeutil.Recurrence) time.Time { |
| return defaultx.WithDefault(v.UsagePeriod.Anchor, clock.Now()) |
| })(timeutil.Recurrence{ |
| Anchor: defaultx.WithDefault(v.UsagePeriod.Anchor, clock.Now()), |
| Interval: iv, |
| })) |
| } |
| if v.Metadata != nil { |
| request.Metadata = *v.Metadata |
| } |
| default: |
| return request, errors.New("unknown entitlement type") |
| } |
|
|
| |
| request.ActiveFrom = nil |
| request.ActiveTo = nil |
|
|
| return request, nil |
| } |
|
|
| func MapAPIPeriodIntervalToRecurrence(interval api.RecurringPeriodInterval) (timeutil.RecurrenceInterval, error) { |
| str, err := interval.AsRecurringPeriodInterval0() |
| if err != nil { |
| return timeutil.RecurrenceInterval{}, err |
| } |
|
|
| switch s := strings.ToUpper(str); s { |
| case string(api.RecurringPeriodIntervalEnumDAY): |
| return timeutil.RecurrencePeriodDaily, nil |
| case string(api.RecurringPeriodIntervalEnumWEEK): |
| return timeutil.RecurrencePeriodWeek, nil |
| case string(api.RecurringPeriodIntervalEnumMONTH): |
| return timeutil.RecurrencePeriodMonth, nil |
| case string(api.RecurringPeriodIntervalEnumYEAR): |
| return timeutil.RecurrencePeriodYear, nil |
| default: |
| p, err := datetime.ISODurationString(s).Parse() |
|
|
| return timeutil.RecurrenceInterval{ISODuration: p}, err |
| } |
| } |
|
|
| func MapRecurrenceToAPI(r timeutil.RecurrenceInterval) api.RecurringPeriodInterval { |
| |
| |
| |
| |
| |
| |
| normalised := r.Normalise(false) |
|
|
| apiInt := &api.RecurringPeriodInterval{} |
|
|
| if d, err := normalised.Subtract(timeutil.RecurrencePeriodDaily.ISODuration); err == nil && d.IsZero() { |
| _ = apiInt.FromRecurringPeriodIntervalEnum(api.RecurringPeriodIntervalEnumDAY) |
| } else if w, err := normalised.Subtract(timeutil.RecurrencePeriodWeek.ISODuration); err == nil && w.IsZero() { |
| _ = apiInt.FromRecurringPeriodIntervalEnum(api.RecurringPeriodIntervalEnumWEEK) |
| } else if m, err := normalised.Subtract(timeutil.RecurrencePeriodMonth.ISODuration); err == nil && m.IsZero() { |
| _ = apiInt.FromRecurringPeriodIntervalEnum(api.RecurringPeriodIntervalEnumMONTH) |
| } else if y, err := normalised.Subtract(timeutil.RecurrencePeriodYear.ISODuration); err == nil && y.IsZero() { |
| _ = apiInt.FromRecurringPeriodIntervalEnum(api.RecurringPeriodIntervalEnumYEAR) |
| } else { |
| _ = apiInt.FromRecurringPeriodInterval0(r.ISOString().String()) |
| } |
|
|
| return *apiInt |
| } |
|
|