mirror of
https://github.com/hanzoai/flags.git
synced 2026-10-30 18:36:27 +00:00
155 lines
5.2 KiB
Go
155 lines
5.2 KiB
Go
package flags
|
|
|
|
// The flag definition wire shapes — the PostHog-compatible `filters` JSON that
|
|
// the console UI, the SDKs and the evaluator all speak. Definitions live in
|
|
// cloud's per-org SQLite; this package only evaluates them.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// Def is one flag definition.
|
|
type Def struct {
|
|
Key string `json:"key"`
|
|
Active bool `json:"active"`
|
|
Deleted bool `json:"deleted"`
|
|
Filters Filters `json:"filters"`
|
|
// AggregationGroupTypeIndex, when set, aggregates the flag by a group type
|
|
// (its index) instead of by the person.
|
|
AggregationGroupTypeIndex *int `json:"aggregation_group_type_index,omitempty"`
|
|
}
|
|
|
|
// UnmarshalJSON decodes a definition, defaulting `active` to true when the key
|
|
// is absent — the wire contract a stored definition relies on.
|
|
func (d *Def) UnmarshalJSON(b []byte) error {
|
|
type plain Def // shed the method so encoding/json does the fields
|
|
v := plain{Active: true}
|
|
if err := json.Unmarshal(b, &v); err != nil {
|
|
return err
|
|
}
|
|
*d = Def(v)
|
|
return nil
|
|
}
|
|
|
|
type Filters struct {
|
|
Groups []ConditionGroup `json:"groups,omitempty"`
|
|
Multivariate *Multivariate `json:"multivariate,omitempty"`
|
|
// Payloads maps "true" -> the payload for a boolean match, or a variant key
|
|
// -> that variant's payload.
|
|
Payloads map[string]json.RawMessage `json:"payloads,omitempty"`
|
|
}
|
|
|
|
// ConditionGroup is one release condition: property filters plus a rollout.
|
|
type ConditionGroup struct {
|
|
Properties []PropertyFilter `json:"properties,omitempty"`
|
|
// RolloutPercentage nil means 100%.
|
|
RolloutPercentage *float64 `json:"rollout_percentage,omitempty"`
|
|
// Variant pins a specific variant when this group matches (it must exist in
|
|
// Multivariate to take effect).
|
|
Variant *string `json:"variant,omitempty"`
|
|
}
|
|
|
|
type Multivariate struct {
|
|
Variants []Variant `json:"variants,omitempty"`
|
|
}
|
|
|
|
type Variant struct {
|
|
Key string `json:"key"`
|
|
RolloutPercentage float64 `json:"rollout_percentage"`
|
|
}
|
|
|
|
// PropertyFilter is one property condition inside a condition group. Value is
|
|
// absent (nil) for the is_set / is_not_set operators, and for filters the API
|
|
// built without one.
|
|
type PropertyFilter struct {
|
|
Key string `json:"key"`
|
|
Value json.RawMessage `json:"value,omitempty"`
|
|
Operator *Operator `json:"operator,omitempty"`
|
|
Type PropertyType `json:"type"`
|
|
Negation *bool `json:"negation,omitempty"`
|
|
GroupTypeIndex *int `json:"group_type_index,omitempty"`
|
|
}
|
|
|
|
// Operator is the comparison a property filter applies.
|
|
type Operator string
|
|
|
|
const (
|
|
OpExact Operator = "exact"
|
|
OpIsNot Operator = "is_not"
|
|
OpIcontains Operator = "icontains"
|
|
OpNotIcontains Operator = "not_icontains"
|
|
OpRegex Operator = "regex"
|
|
OpNotRegex Operator = "not_regex"
|
|
OpGt Operator = "gt"
|
|
OpLt Operator = "lt"
|
|
OpGte Operator = "gte"
|
|
OpLte Operator = "lte"
|
|
OpSemverGt Operator = "semver_gt"
|
|
OpSemverGte Operator = "semver_gte"
|
|
OpSemverLt Operator = "semver_lt"
|
|
OpSemverLte Operator = "semver_lte"
|
|
OpSemverEq Operator = "semver_eq"
|
|
OpSemverNeq Operator = "semver_neq"
|
|
OpSemverTilde Operator = "semver_tilde"
|
|
OpSemverCaret Operator = "semver_caret"
|
|
OpSemverWildcard Operator = "semver_wildcard"
|
|
OpIsSet Operator = "is_set"
|
|
OpIsNotSet Operator = "is_not_set"
|
|
OpIsDateExact Operator = "is_date_exact"
|
|
OpIsDateAfter Operator = "is_date_after"
|
|
OpIsDateBefore Operator = "is_date_before"
|
|
OpIn Operator = "in"
|
|
OpNotIn Operator = "not_in"
|
|
OpFlagEvaluatesTo Operator = "flag_evaluates_to"
|
|
)
|
|
|
|
var operators = map[Operator]bool{
|
|
OpExact: true, OpIsNot: true, OpIcontains: true, OpNotIcontains: true,
|
|
OpRegex: true, OpNotRegex: true, OpGt: true, OpLt: true, OpGte: true, OpLte: true,
|
|
OpSemverGt: true, OpSemverGte: true, OpSemverLt: true, OpSemverLte: true,
|
|
OpSemverEq: true, OpSemverNeq: true, OpSemverTilde: true, OpSemverCaret: true,
|
|
OpSemverWildcard: true, OpIsSet: true, OpIsNotSet: true, OpIsDateExact: true,
|
|
OpIsDateAfter: true, OpIsDateBefore: true, OpIn: true, OpNotIn: true,
|
|
OpFlagEvaluatesTo: true,
|
|
}
|
|
|
|
// UnmarshalJSON rejects unknown operators so a definition carrying one fails to
|
|
// parse rather than silently never matching.
|
|
func (o *Operator) UnmarshalJSON(b []byte) error {
|
|
var s string
|
|
if err := json.Unmarshal(b, &s); err != nil {
|
|
return err
|
|
}
|
|
if !operators[Operator(s)] {
|
|
return fmt.Errorf("unknown variant `%s`", s)
|
|
}
|
|
*o = Operator(s)
|
|
return nil
|
|
}
|
|
|
|
// PropertyType is which bag a property filter reads from.
|
|
type PropertyType string
|
|
|
|
const (
|
|
TypePerson PropertyType = "person"
|
|
TypeCohort PropertyType = "cohort"
|
|
TypeGroup PropertyType = "group"
|
|
// TypeFlag compares against another flag's evaluation result.
|
|
TypeFlag PropertyType = "flag"
|
|
)
|
|
|
|
// UnmarshalJSON rejects unknown property types for the same reason as Operator.
|
|
func (p *PropertyType) UnmarshalJSON(b []byte) error {
|
|
var s string
|
|
if err := json.Unmarshal(b, &s); err != nil {
|
|
return err
|
|
}
|
|
switch PropertyType(s) {
|
|
case TypePerson, TypeCohort, TypeGroup, TypeFlag:
|
|
*p = PropertyType(s)
|
|
return nil
|
|
}
|
|
return fmt.Errorf("unknown variant `%s`", s)
|
|
}
|