diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80f703f..64a3426 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: script: core.setFailed('[ERROR] Tag already exists.') - name: Setup Go version - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: '^1.20.7' diff --git a/CHANGES b/CHANGES index a2cb5e2..700c21f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +1.7.0 (Jan 21, 2026) +- Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs. +- Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs. +- Added support for rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK. +- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules. + 1.6.0 (Feb 5, 2025) - Added Health & Readiness endpoints. - Fixing vulnerabilities. diff --git a/external/commons/mocks/evaluator.go b/external/commons/mocks/evaluator.go index 0b3b84c..d598cf1 100644 --- a/external/commons/mocks/evaluator.go +++ b/external/commons/mocks/evaluator.go @@ -1,7 +1,7 @@ package mocks import ( - "github.com/splitio/go-split-commons/v6/engine/evaluator" + "github.com/splitio/go-split-commons/v9/engine/evaluator" "github.com/stretchr/testify/mock" ) diff --git a/external/commons/mocks/impmanager.go b/external/commons/mocks/impmanager.go index c5ee391..1f6cbdd 100644 --- a/external/commons/mocks/impmanager.go +++ b/external/commons/mocks/impmanager.go @@ -1,8 +1,8 @@ package mocks import ( - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/provisional" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/provisional" "github.com/stretchr/testify/mock" ) diff --git a/external/commons/mocks/imprecorder.go b/external/commons/mocks/imprecorder.go index 8a47d48..810ec72 100644 --- a/external/commons/mocks/imprecorder.go +++ b/external/commons/mocks/imprecorder.go @@ -1,8 +1,8 @@ package mocks import ( - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/service" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/service" "github.com/stretchr/testify/mock" ) diff --git a/external/commons/mocks/splitstorage.go b/external/commons/mocks/splitstorage.go index 937640b..97f909d 100644 --- a/external/commons/mocks/splitstorage.go +++ b/external/commons/mocks/splitstorage.go @@ -1,8 +1,8 @@ package mocks import ( - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/storage" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/storage" "github.com/splitio/go-toolkit/v5/datastructures/set" "github.com/stretchr/testify/mock" ) @@ -72,4 +72,14 @@ func (m *SplitStorageMock) LargeSegmentNames() *set.ThreadUnsafeSet { return m.Called().Get(0).(*set.ThreadUnsafeSet) } +func (m *SplitStorageMock) ReplaceAll(toAdd []dtos.SplitDTO, changeNumber int64) error { + args := m.Called(toAdd, changeNumber) + return args.Error(0) +} + +func (m *SplitStorageMock) RuleBasedSegmentNames() *set.ThreadUnsafeSet { + args := m.Called() + return args.Get(0).(*set.ThreadUnsafeSet) +} + var _ storage.SplitStorage = (*SplitStorageMock)(nil) diff --git a/go.mod b/go.mod index b094406..2f03549 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ module github.com/splitio/splitd -go 1.23.6 +go 1.24.0 require ( github.com/gin-gonic/gin v1.10.0 - github.com/splitio/go-split-commons/v6 v6.1.0 - github.com/splitio/go-toolkit/v5 v5.4.0 - github.com/stretchr/testify v1.9.0 + github.com/splitio/go-split-commons/v9 v9.1.0 + github.com/splitio/go-toolkit/v5 v5.4.1 + github.com/stretchr/testify v1.11.1 github.com/vmihailenco/msgpack/v5 v5.3.5 - golang.org/x/sync v0.10.0 + golang.org/x/sync v0.18.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -39,10 +39,10 @@ require ( github.com/ugorji/go/codec v1.2.12 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect google.golang.org/protobuf v1.34.1 // indirect ) diff --git a/go.sum b/go.sum index ffa9c93..449df85 100644 --- a/go.sum +++ b/go.sum @@ -51,10 +51,10 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6 github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/splitio/go-split-commons/v6 v6.1.0 h1:k3mwr12DF6gbEaV8XXU/tSAQlPkIEuzIgTEneYhGg2I= -github.com/splitio/go-split-commons/v6 v6.1.0/go.mod h1:D/XIY/9Hmfk9ivWsRsJVp439kEdmHbzUi3PKzQQDOXY= -github.com/splitio/go-toolkit/v5 v5.4.0 h1:g5WFpRhQomnXCmvfsNOWV4s5AuUrWIZ+amM68G8NBKM= -github.com/splitio/go-toolkit/v5 v5.4.0/go.mod h1:xYhUvV1gga9/1029Wbp5pjnR6Cy8nvBpjw99wAbsMko= +github.com/splitio/go-split-commons/v9 v9.1.0 h1:sfmPMuEDTtbIOJ+MeWNbfYl2/xKB/25d4/J95OUD+X0= +github.com/splitio/go-split-commons/v9 v9.1.0/go.mod h1:gJuaKo04Swlh4w9C1b2jBAqAdFxEd/Vpd8jnFINOeDY= +github.com/splitio/go-toolkit/v5 v5.4.1 h1:srTyvDBJZMUcJ/KiiQDMyjCuELVgTBh2TGRVn0sOXEE= +github.com/splitio/go-toolkit/v5 v5.4.1/go.mod h1:SifzysrOVDbzMcOE8zjX02+FG5az4FrR3Us/i5SeStw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -67,8 +67,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= @@ -82,20 +83,20 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/splitio/link/client/types/interfaces.go b/splitio/link/client/types/interfaces.go index 0d7ac20..4e11855 100644 --- a/splitio/link/client/types/interfaces.go +++ b/splitio/link/client/types/interfaces.go @@ -1,7 +1,7 @@ package types import ( - "github.com/splitio/go-split-commons/v6/dtos" + "github.com/splitio/go-split-commons/v9/dtos" "github.com/splitio/splitd/splitio/sdk" ) diff --git a/splitio/link/client/v1/impl.go b/splitio/link/client/v1/impl.go index 47cf7fb..fba91ce 100644 --- a/splitio/link/client/v1/impl.go +++ b/splitio/link/client/v1/impl.go @@ -3,7 +3,7 @@ package v1 import ( "fmt" - "github.com/splitio/go-split-commons/v6/dtos" + "github.com/splitio/go-split-commons/v9/dtos" "github.com/splitio/go-toolkit/v5/logging" "github.com/splitio/splitd/splitio" "github.com/splitio/splitd/splitio/common/lang" diff --git a/splitio/link/client/v1/impl_test.go b/splitio/link/client/v1/impl_test.go index f174400..2bdbee6 100644 --- a/splitio/link/client/v1/impl_test.go +++ b/splitio/link/client/v1/impl_test.go @@ -3,7 +3,7 @@ package v1 import ( "testing" - "github.com/splitio/go-split-commons/v6/dtos" + "github.com/splitio/go-split-commons/v9/dtos" "github.com/splitio/go-toolkit/v5/logging" "github.com/splitio/splitd/splitio/common/lang" v1 "github.com/splitio/splitd/splitio/link/protocol/v1" diff --git a/splitio/link/service/v1/clientmgr_test.go b/splitio/link/service/v1/clientmgr_test.go index 277c044..f9ee8e9 100644 --- a/splitio/link/service/v1/clientmgr_test.go +++ b/splitio/link/service/v1/clientmgr_test.go @@ -5,7 +5,7 @@ import ( "io" "testing" - "github.com/splitio/go-split-commons/v6/dtos" + "github.com/splitio/go-split-commons/v9/dtos" "github.com/splitio/go-toolkit/v5/logging" "github.com/splitio/splitd/splitio/common/lang" "github.com/splitio/splitd/splitio/link/protocol" diff --git a/splitio/sdk/conf/conf.go b/splitio/sdk/conf/conf.go index 1314186..f06d900 100644 --- a/splitio/sdk/conf/conf.go +++ b/splitio/sdk/conf/conf.go @@ -3,10 +3,10 @@ package conf import ( "time" - "github.com/splitio/go-split-commons/v6/conf" - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/flagsets" - "github.com/splitio/go-split-commons/v6/service/api/specs" + "github.com/splitio/go-split-commons/v9/conf" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/flagsets" + "github.com/splitio/go-split-commons/v9/service/api/specs" ) const ( @@ -15,14 +15,15 @@ const ( ) type Config struct { - LabelsEnabled bool - StreamingEnabled bool - Splits Splits - Segments Segments - Impressions Impressions - Events Events - URLs URLs - FlagSetsFilter []string + LabelsEnabled bool + StreamingEnabled bool + Splits Splits + Segments Segments + Impressions Impressions + Events Events + URLs URLs + FlagSetsFilter []string + FallbackTreatment dtos.FallbackTreatmentConfig } type Splits struct { @@ -81,6 +82,7 @@ func (c *Config) ToAdvancedConfig() *conf.AdvancedConfig { d.ImpressionsQueueSize = c.Impressions.QueueSize d.AuthSpecVersion = specs.FLAG_V1_1 d.FlagsSpecVersion = specs.FLAG_V1_1 + d.FallbackTreatment = c.FallbackTreatment return &d } diff --git a/splitio/sdk/helpers.go b/splitio/sdk/helpers.go index ffe973e..77ab56d 100644 --- a/splitio/sdk/helpers.go +++ b/splitio/sdk/helpers.go @@ -2,28 +2,32 @@ package sdk import ( "fmt" + "regexp" + "strings" sdkConf "github.com/splitio/splitd/splitio/sdk/conf" sss "github.com/splitio/splitd/splitio/sdk/storage" "github.com/splitio/splitd/splitio/sdk/workers" - "github.com/splitio/go-split-commons/v6/conf" - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/flagsets" - "github.com/splitio/go-split-commons/v6/healthcheck/application" - "github.com/splitio/go-split-commons/v6/provisional" - "github.com/splitio/go-split-commons/v6/provisional/strategy" - "github.com/splitio/go-split-commons/v6/service/api" - "github.com/splitio/go-split-commons/v6/storage" - "github.com/splitio/go-split-commons/v6/storage/filter" - "github.com/splitio/go-split-commons/v6/storage/inmemory" - "github.com/splitio/go-split-commons/v6/storage/inmemory/mutexmap" - "github.com/splitio/go-split-commons/v6/synchronizer" - "github.com/splitio/go-split-commons/v6/synchronizer/worker/impressionscount" - "github.com/splitio/go-split-commons/v6/synchronizer/worker/segment" - "github.com/splitio/go-split-commons/v6/synchronizer/worker/split" - "github.com/splitio/go-split-commons/v6/tasks" - "github.com/splitio/go-split-commons/v6/telemetry" + "github.com/splitio/go-split-commons/v9/conf" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/engine/grammar" + "github.com/splitio/go-split-commons/v9/flagsets" + "github.com/splitio/go-split-commons/v9/healthcheck/application" + "github.com/splitio/go-split-commons/v9/provisional" + "github.com/splitio/go-split-commons/v9/provisional/strategy" + "github.com/splitio/go-split-commons/v9/service/api" + "github.com/splitio/go-split-commons/v9/service/api/specs" + "github.com/splitio/go-split-commons/v9/storage" + "github.com/splitio/go-split-commons/v9/storage/filter" + "github.com/splitio/go-split-commons/v9/storage/inmemory" + "github.com/splitio/go-split-commons/v9/storage/inmemory/mutexmap" + "github.com/splitio/go-split-commons/v9/synchronizer" + "github.com/splitio/go-split-commons/v9/synchronizer/worker/impressionscount" + "github.com/splitio/go-split-commons/v9/synchronizer/worker/segment" + "github.com/splitio/go-split-commons/v9/synchronizer/worker/split" + "github.com/splitio/go-split-commons/v9/tasks" + "github.com/splitio/go-split-commons/v9/telemetry" "github.com/splitio/go-toolkit/v5/logging" ) @@ -36,6 +40,12 @@ const ( impressionsCountPeriodTaskInMemory = 1800 // 30 min impressionsCountPeriodTaskRedis = 300 // 5 min impressionsBulkSizeRedis = 100 + // Max Flag name length + MaxFlagNameLength = 100 + // Max Treatment length + MaxTreatmentLength = 100 + // Treatment regexp + TreatmentRegexp = "^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$" ) func setupWorkers( @@ -47,10 +57,11 @@ func setupWorkers( flagSetsFilter flagsets.FlagSetFilter, md dtos.Metadata, impComponents impComponents, + ruleBuilder grammar.RuleBuilder, ) *synchronizer.Workers { return &synchronizer.Workers{ - SplitUpdater: split.NewSplitUpdater(str.splits, api.SplitFetcher, logger, str.telemetry, hc, flagSetsFilter), - SegmentUpdater: segment.NewSegmentUpdater(str.splits, str.segments, api.SegmentFetcher, logger, str.telemetry, hc), + SplitUpdater: split.NewSplitUpdater(str.splits, str.ruleBasedSegments, api.SplitFetcher, logger, str.telemetry, hc, flagSetsFilter, ruleBuilder, false, specs.FLAG_V1_3), + SegmentUpdater: segment.NewSegmentUpdater(str.splits, str.segments, str.ruleBasedSegments, api.SegmentFetcher, logger, str.telemetry, hc), ImpressionRecorder: workers.NewImpressionsWorker(logger, str.telemetry, api.ImpressionRecorder, str.impressions, &cfg.Impressions), EventRecorder: workers.NewEventsWorker(logger, str.telemetry, api.EventRecorder, str.events, &cfg.Events), ImpressionsCountRecorder: impressionscount.NewRecorderSingle(impComponents.counter, api.ImpressionRecorder, md, logger, str.telemetry), @@ -132,11 +143,12 @@ func setupImpressionsComponents(c *sdkConf.Impressions, telemetry storage.Teleme } type storages struct { - splits storage.SplitStorage - segments storage.SegmentStorage - telemetry storage.TelemetryStorage - impressions *sss.ImpressionsStorage - events *sss.EventsStorage + splits storage.SplitStorage + segments storage.SegmentStorage + ruleBasedSegments storage.RuleBasedSegmentsStorage + telemetry storage.TelemetryStorage + impressions *sss.ImpressionsStorage + events *sss.EventsStorage } func setupStorages(cfg *sdkConf.Config, flagSetsFilter flagsets.FlagSetFilter) *storages { @@ -145,14 +157,67 @@ func setupStorages(cfg *sdkConf.Config, flagSetsFilter flagsets.FlagSetFilter) * eq, _ := sss.NewEventsQueue(cfg.Events.QueueSize) return &storages{ - splits: mutexmap.NewMMSplitStorage(flagSetsFilter), - segments: mutexmap.NewMMSegmentStorage(), - impressions: iq, - events: eq, - telemetry: ts, + splits: mutexmap.NewMMSplitStorage(flagSetsFilter), + segments: mutexmap.NewMMSegmentStorage(), + ruleBasedSegments: mutexmap.NewRuleBasedSegmentsStorage(), + impressions: iq, + events: eq, + telemetry: ts, } } +func SanitizeGlobalFallbackTreatment(global *dtos.FallbackTreatment, logger logging.LoggerInterface) *dtos.FallbackTreatment { + if global == nil { + return nil + } + if !isValidTreatment(global) { + logger.Error(fmt.Sprintf("Fallback treatments - Discarded global fallback: Invalid treatment (max %d chars and comply with %s)", MaxTreatmentLength, TreatmentRegexp)) + return nil + } + return &dtos.FallbackTreatment{ + Treatment: global.Treatment, + Config: global.Config, + } +} + +func isValidTreatment(fallbackTreatment *dtos.FallbackTreatment) bool { + if fallbackTreatment == nil || fallbackTreatment.Treatment == nil { + return false + } + value := *fallbackTreatment.Treatment + pattern := regexp.MustCompile(TreatmentRegexp) + return len(value) <= MaxTreatmentLength && pattern.MatchString(value) +} + +func SanitizeByFlagFallBackTreatment(byFlag map[string]dtos.FallbackTreatment, logger logging.LoggerInterface) map[string]dtos.FallbackTreatment { + sanitized := map[string]dtos.FallbackTreatment{} + if len(byFlag) == 0 { + return sanitized + } + for flagName, treatment := range byFlag { + if !isValidFlagName(&flagName) { + logger.Error(fmt.Sprintf("Fallback treatments - Discarded flag: Invalid flag name (max %d chars, no spaces)", MaxFlagNameLength)) + continue + } + if !isValidTreatment(&treatment) { + logger.Error(fmt.Sprintf("Fallback treatments - Discarded treatment for flag '%s': Invalid treatment (max %d chars and comply with %s)", flagName, MaxTreatmentLength, TreatmentRegexp)) + continue + } + sanitized[flagName] = dtos.FallbackTreatment{ + Treatment: treatment.Treatment, + Config: treatment.Config, + } + } + return sanitized +} + +func isValidFlagName(flagName *string) bool { + if flagName == nil { + return false + } + return len(*flagName) <= MaxFlagNameLength && !strings.Contains(*flagName, " ") +} + // Temporary for running without impressions/events/telemetry/etc type NoOpTask struct{} diff --git a/splitio/sdk/helpers_test.go b/splitio/sdk/helpers_test.go index 9d54b96..d83db88 100644 --- a/splitio/sdk/helpers_test.go +++ b/splitio/sdk/helpers_test.go @@ -3,7 +3,7 @@ package sdk import ( "testing" - "github.com/splitio/go-split-commons/v6/flagsets" + "github.com/splitio/go-split-commons/v9/flagsets" sdkConf "github.com/splitio/splitd/splitio/sdk/conf" "github.com/stretchr/testify/assert" ) diff --git a/splitio/sdk/integration_test.go b/splitio/sdk/integration_test.go index af620e6..97ccdaf 100644 --- a/splitio/sdk/integration_test.go +++ b/splitio/sdk/integration_test.go @@ -10,8 +10,8 @@ import ( "sync/atomic" "testing" - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/service/api/specs" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/service/api/specs" "github.com/splitio/go-toolkit/v5/logging" "github.com/splitio/splitd/splitio/sdk/conf" "github.com/splitio/splitd/splitio/sdk/types" @@ -50,14 +50,16 @@ func TestInstantiationAndGetTreatmentE2E(t *testing.T) { assert.Equal(t, "GET", r.Method) assert.Equal(t, "/splitChanges", r.URL.Path) - splitChanges := dtos.SplitChangesDTO{ - Splits: []dtos.SplitDTO{mockedSplit1, mockedSplit2, mockedSplit3}, - Since: 3, - Till: 3, + splitChanges := dtos.RuleChangesDTO{ + FeatureFlags: dtos.FeatureFlagsDTO{ + Splits: []dtos.SplitDTO{mockedSplit1, mockedSplit2, mockedSplit3}, + Since: 3, + Till: 3, + }, } assert.Equal(t, "-1", r.URL.Query().Get("since")) - assert.Equal(t, specs.FLAG_V1_1, r.URL.Query().Get("s")) + assert.Equal(t, specs.FLAG_V1_3, r.URL.Query().Get("s")) raw, err := json.Marshal(splitChanges) assert.Nil(t, err) @@ -102,6 +104,20 @@ func TestInstantiationAndGetTreatmentE2E(t *testing.T) { sdkConf.URLs.SDK = sdkServer.URL sdkConf.URLs.Telemetry = telemetryServer.URL sdkConf.StreamingEnabled = false + stringConfig := "flag1_config" + globalTreatment := "global_treatment" + flag1Treatment := "flag1_treatment" + sdkConf.FallbackTreatment = dtos.FallbackTreatmentConfig{ + GlobalFallbackTreatment: &dtos.FallbackTreatment{ + Treatment: &globalTreatment, + }, + ByFlagFallbackTreatment: map[string]dtos.FallbackTreatment{ + "flag1": { + Treatment: &flag1Treatment, + Config: &stringConfig, + }, + }, + } logger := logging.NewLogger(nil) client, err := New(logger, "someApikey", sdkConf) @@ -113,3 +129,529 @@ func TestInstantiationAndGetTreatmentE2E(t *testing.T) { assert.Nil(t, client.Shutdown()) assert.Equal(t, int32(1), atomic.LoadInt32(&eventsCalls)) } + +func TestInstantiationAndGetTreatmentE2EWithFallbackTreatment(t *testing.T) { + metricsInitCalled := 0 + mockedSplit1 := dtos.SplitDTO{ + Algo: 2, + ChangeNumber: 123, + DefaultTreatment: "default", + Killed: false, + Name: "split", + Seed: 1234, + Status: "ACTIVE", + TrafficAllocation: 1, + TrafficAllocationSeed: -1667452163, + TrafficTypeName: "tt1", + Conditions: []dtos.ConditionDTO{ + { + ConditionType: "ROLLOUT", + Label: "in segment all", + MatcherGroup: dtos.MatcherGroupDTO{ + Combiner: "AND", + Matchers: []dtos.MatcherDTO{{MatcherType: "ALL_KEYS"}}, + }, + Partitions: []dtos.PartitionDTO{{Size: 100, Treatment: "on"}}, + }, + }, + } + mockedSplit2 := dtos.SplitDTO{Name: "split2", Killed: true, Status: "ACTIVE"} + mockedSplit3 := dtos.SplitDTO{Name: "split3", Killed: true, Status: "INACTIVE"} + + sdkServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/splitChanges", r.URL.Path) + + splitChanges := dtos.RuleChangesDTO{ + FeatureFlags: dtos.FeatureFlagsDTO{ + Splits: []dtos.SplitDTO{mockedSplit1, mockedSplit2, mockedSplit3}, + Since: 3, + Till: 3, + }, + } + + assert.Equal(t, "-1", r.URL.Query().Get("since")) + assert.Equal(t, specs.FLAG_V1_3, r.URL.Query().Get("s")) + + raw, err := json.Marshal(splitChanges) + assert.Nil(t, err) + + w.Write(raw) + })) + defer sdkServer.Close() + + var eventsCalls int32 + eventsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.AddInt32(&eventsCalls, 1) + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/testImpressions/bulk", r.URL.Path) + + body, err := io.ReadAll(r.Body) + assert.Nil(t, err) + + var imps []dtos.ImpressionsDTO + assert.Nil(t, json.Unmarshal(body, &imps)) + + assert.Equal(t, "not_exist", imps[0].TestName) + assert.Equal(t, 1, len(imps[0].KeyImpressions)) + w.WriteHeader(200) + })) + defer eventsServer.Close() + + telemetryServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/metrics/config": + metricsInitCalled++ + rBody, _ := ioutil.ReadAll(r.Body) + var dataInPost dtos.Config + err := json.Unmarshal(rBody, &dataInPost) + assert.Nil(t, err) + } + fmt.Fprintln(w, "ok") + })) + defer telemetryServer.Close() + + sdkConf := conf.DefaultConfig() + sdkConf.URLs.Events = eventsServer.URL + sdkConf.URLs.SDK = sdkServer.URL + sdkConf.URLs.Telemetry = telemetryServer.URL + sdkConf.StreamingEnabled = false + stringConfig := "flag1_config" + globalTreatment := "global_treatment" + flag1Treatment := "flag1_treatment" + sdkConf.FallbackTreatment = dtos.FallbackTreatmentConfig{ + GlobalFallbackTreatment: &dtos.FallbackTreatment{ + Treatment: &globalTreatment, + }, + ByFlagFallbackTreatment: map[string]dtos.FallbackTreatment{ + "flag1": { + Treatment: &flag1Treatment, + Config: &stringConfig, + }, + }, + } + + logger := logging.NewLogger(nil) + client, err := New(logger, "someApikey", sdkConf) + assert.Nil(t, err) + + opts := dtos.EvaluationOptions{ + Properties: map[string]interface{}{ + "pleassssse": "holaaaaa", + }, + } + + res1, _ := client.Treatment(&types.ClientConfig{}, "aaaaaaklmnbv", nil, "not_exist", nil, client.WithEvaluationOptions(&opts)) + assert.Equal(t, "global_treatment", res1.Treatment) + assert.Equal(t, "{\"pleassssse\":\"holaaaaa\"}", res1.Impression.Properties) + + assert.Nil(t, client.Shutdown()) + assert.Equal(t, int32(1), atomic.LoadInt32(&eventsCalls)) +} + +func TestInstantiationAndGetTreatmentE2EWithPrerequistesNotAchive(t *testing.T) { + metricsInitCalled := 0 + mockedSplit1 := dtos.SplitDTO{ + Algo: 2, + ChangeNumber: 123, + DefaultTreatment: "default", + Killed: false, + Name: "split", + Seed: 1234, + Status: "ACTIVE", + TrafficAllocation: 1, + TrafficAllocationSeed: -1667452163, + TrafficTypeName: "tt1", + Prerequisites: []dtos.Prerequisite{ + { + FeatureFlagName: "ff1", + Treatments: []string{ + "off", + "v1", + }, + }, + }, + Conditions: []dtos.ConditionDTO{ + { + ConditionType: "ROLLOUT", + Label: "in segment all", + MatcherGroup: dtos.MatcherGroupDTO{ + Combiner: "AND", + Matchers: []dtos.MatcherDTO{{MatcherType: "ALL_KEYS"}}, + }, + Partitions: []dtos.PartitionDTO{{Size: 100, Treatment: "on"}}, + }, + }, + } + + mockedSplit2 := dtos.SplitDTO{ + Algo: 2, + ChangeNumber: 123, + DefaultTreatment: "off", + Killed: false, + Name: "ff1", + Seed: 1234, + Status: "ACTIVE", + TrafficAllocation: 1, + TrafficAllocationSeed: -1667452163, + TrafficTypeName: "tt1", + Conditions: []dtos.ConditionDTO{ + { + ConditionType: "ROLLOUT", + Label: "in segment all", + MatcherGroup: dtos.MatcherGroupDTO{ + Combiner: "AND", + Matchers: []dtos.MatcherDTO{{MatcherType: "ALL_KEYS"}}, + }, + Partitions: []dtos.PartitionDTO{{Size: 100, Treatment: "on"}}, + }, + }, + } + + sdkServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/splitChanges", r.URL.Path) + + splitChanges := dtos.RuleChangesDTO{ + FeatureFlags: dtos.FeatureFlagsDTO{ + Splits: []dtos.SplitDTO{mockedSplit1, mockedSplit2}, + Since: 3, + Till: 3, + }, + } + + assert.Equal(t, "-1", r.URL.Query().Get("since")) + assert.Equal(t, specs.FLAG_V1_3, r.URL.Query().Get("s")) + + raw, err := json.Marshal(splitChanges) + assert.Nil(t, err) + + w.Write(raw) + })) + defer sdkServer.Close() + + var eventsCalls int32 + eventsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.AddInt32(&eventsCalls, 1) + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/testImpressions/bulk", r.URL.Path) + + body, err := io.ReadAll(r.Body) + assert.Nil(t, err) + + var imps []dtos.ImpressionsDTO + assert.Nil(t, json.Unmarshal(body, &imps)) + + assert.Equal(t, "split", imps[0].TestName) + assert.Equal(t, 1, len(imps[0].KeyImpressions)) + w.WriteHeader(200) + })) + defer eventsServer.Close() + + telemetryServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/metrics/config": + metricsInitCalled++ + rBody, _ := ioutil.ReadAll(r.Body) + var dataInPost dtos.Config + err := json.Unmarshal(rBody, &dataInPost) + assert.Nil(t, err) + } + fmt.Fprintln(w, "ok") + })) + defer telemetryServer.Close() + + sdkConf := conf.DefaultConfig() + sdkConf.URLs.Events = eventsServer.URL + sdkConf.URLs.SDK = sdkServer.URL + sdkConf.URLs.Telemetry = telemetryServer.URL + sdkConf.StreamingEnabled = false + logger := logging.NewLogger(nil) + client, err := New(logger, "someApikey", sdkConf) + assert.Nil(t, err) + + res1, _ := client.Treatment(&types.ClientConfig{}, "aaaaaaklmnbv", nil, "split", nil) + assert.Equal(t, "default", res1.Treatment) + + assert.Nil(t, client.Shutdown()) + assert.Equal(t, int32(1), atomic.LoadInt32(&eventsCalls)) +} + +func TestInstantiationAndGetTreatmentE2EWithPrerequistesAchive(t *testing.T) { + metricsInitCalled := 0 + mockedSplit1 := dtos.SplitDTO{ + Algo: 2, + ChangeNumber: 123, + DefaultTreatment: "default", + Killed: false, + Name: "split", + Seed: 1234, + Status: "ACTIVE", + TrafficAllocation: 1, + TrafficAllocationSeed: -1667452163, + TrafficTypeName: "tt1", + Prerequisites: []dtos.Prerequisite{ + { + FeatureFlagName: "ff1", + Treatments: []string{ + "on", + "v1", + }, + }, + }, + Conditions: []dtos.ConditionDTO{ + { + ConditionType: "ROLLOUT", + Label: "in segment all", + MatcherGroup: dtos.MatcherGroupDTO{ + Combiner: "AND", + Matchers: []dtos.MatcherDTO{{MatcherType: "ALL_KEYS"}}, + }, + Partitions: []dtos.PartitionDTO{{Size: 100, Treatment: "on"}}, + }, + }, + } + + mockedSplit2 := dtos.SplitDTO{ + Algo: 2, + ChangeNumber: 123, + DefaultTreatment: "off", + Killed: false, + Name: "ff1", + Seed: 1234, + Status: "ACTIVE", + TrafficAllocation: 1, + TrafficAllocationSeed: -1667452163, + TrafficTypeName: "tt1", + Conditions: []dtos.ConditionDTO{ + { + ConditionType: "ROLLOUT", + Label: "in segment all", + MatcherGroup: dtos.MatcherGroupDTO{ + Combiner: "AND", + Matchers: []dtos.MatcherDTO{{MatcherType: "ALL_KEYS"}}, + }, + Partitions: []dtos.PartitionDTO{{Size: 100, Treatment: "on"}}, + }, + }, + } + + sdkServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/splitChanges", r.URL.Path) + + splitChanges := dtos.RuleChangesDTO{ + FeatureFlags: dtos.FeatureFlagsDTO{ + Splits: []dtos.SplitDTO{mockedSplit1, mockedSplit2}, + Since: 3, + Till: 3, + }, + } + + assert.Equal(t, "-1", r.URL.Query().Get("since")) + assert.Equal(t, specs.FLAG_V1_3, r.URL.Query().Get("s")) + + raw, err := json.Marshal(splitChanges) + assert.Nil(t, err) + + w.Write(raw) + })) + defer sdkServer.Close() + + var eventsCalls int32 + eventsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.AddInt32(&eventsCalls, 1) + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/testImpressions/bulk", r.URL.Path) + + body, err := io.ReadAll(r.Body) + assert.Nil(t, err) + + var imps []dtos.ImpressionsDTO + assert.Nil(t, json.Unmarshal(body, &imps)) + + assert.Equal(t, "split", imps[0].TestName) + assert.Equal(t, 1, len(imps[0].KeyImpressions)) + w.WriteHeader(200) + })) + defer eventsServer.Close() + + telemetryServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/metrics/config": + metricsInitCalled++ + rBody, _ := ioutil.ReadAll(r.Body) + var dataInPost dtos.Config + err := json.Unmarshal(rBody, &dataInPost) + assert.Nil(t, err) + } + fmt.Fprintln(w, "ok") + })) + defer telemetryServer.Close() + + sdkConf := conf.DefaultConfig() + sdkConf.URLs.Events = eventsServer.URL + sdkConf.URLs.SDK = sdkServer.URL + sdkConf.URLs.Telemetry = telemetryServer.URL + sdkConf.StreamingEnabled = false + logger := logging.NewLogger(nil) + client, err := New(logger, "someApikey", sdkConf) + assert.Nil(t, err) + + res1, _ := client.Treatment(&types.ClientConfig{}, "aaaaaaklmnbv", nil, "split", nil) + assert.Equal(t, "on", res1.Treatment) + + assert.Nil(t, client.Shutdown()) + assert.Equal(t, int32(1), atomic.LoadInt32(&eventsCalls)) +} + +func TestInstantiationAndGetTreatmentE2EWithRBS(t *testing.T) { + metricsInitCalled := 0 + mockedSplit1 := dtos.SplitDTO{ + Algo: 2, + ChangeNumber: 123, + DefaultTreatment: "default", + Killed: false, + Name: "split", + Seed: 1234, + Status: "ACTIVE", + TrafficAllocation: 1, + TrafficAllocationSeed: -1667452163, + TrafficTypeName: "tt1", + Conditions: []dtos.ConditionDTO{ + { + ConditionType: "ROLLOUT", + Label: "default rule", + MatcherGroup: dtos.MatcherGroupDTO{ + Combiner: "AND", + Matchers: []dtos.MatcherDTO{ + { + KeySelector: &dtos.KeySelectorDTO{ + TrafficType: "user", + }, + MatcherType: "IN_RULE_BASED_SEGMENT", + UserDefinedSegment: &dtos.UserDefinedSegmentMatcherDataDTO{ + SegmentName: "rbsegment1", + }, + Negate: false, + }, + }, + }, + Partitions: []dtos.PartitionDTO{ + { + Size: 100, + Treatment: "on", + }, + { + Size: 0, + Treatment: "off", + }, + }, + }, + }, + } + + semver := "3.4.5" + attribute := "version" + + rbsegment1 := dtos.RuleBasedSegmentDTO{ + Name: "rbsegment1", + Status: "ACTIVE", + Conditions: []dtos.RuleBasedConditionDTO{ + { + MatcherGroup: dtos.MatcherGroupDTO{ + Combiner: "AND", + Matchers: []dtos.MatcherDTO{ + { + KeySelector: &dtos.KeySelectorDTO{ + TrafficType: "user", + Attribute: &attribute, + }, + MatcherType: "EQUAL_TO_SEMVER", + String: &semver, + Whitelist: nil, + Negate: false, + }, + }, + }, + }, + }, + TrafficTypeName: "user", + } + + sdkServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/splitChanges", r.URL.Path) + + splitChanges := dtos.RuleChangesDTO{ + FeatureFlags: dtos.FeatureFlagsDTO{ + Splits: []dtos.SplitDTO{mockedSplit1}, + Since: 3, + Till: 3, + }, + RuleBasedSegments: dtos.RuleBasedSegmentsDTO{ + RuleBasedSegments: []dtos.RuleBasedSegmentDTO{rbsegment1}, + Since: 3, + Till: 3, + }, + } + + assert.Equal(t, "-1", r.URL.Query().Get("since")) + assert.Equal(t, specs.FLAG_V1_3, r.URL.Query().Get("s")) + + raw, err := json.Marshal(splitChanges) + assert.Nil(t, err) + + w.Write(raw) + })) + defer sdkServer.Close() + + var eventsCalls int32 + eventsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.AddInt32(&eventsCalls, 1) + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/testImpressions/bulk", r.URL.Path) + + body, err := io.ReadAll(r.Body) + assert.Nil(t, err) + + var imps []dtos.ImpressionsDTO + assert.Nil(t, json.Unmarshal(body, &imps)) + + assert.Equal(t, "split", imps[0].TestName) + assert.Equal(t, 1, len(imps[0].KeyImpressions)) + w.WriteHeader(200) + })) + defer eventsServer.Close() + + telemetryServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/metrics/config": + metricsInitCalled++ + rBody, _ := ioutil.ReadAll(r.Body) + var dataInPost dtos.Config + err := json.Unmarshal(rBody, &dataInPost) + assert.Nil(t, err) + } + fmt.Fprintln(w, "ok") + })) + defer telemetryServer.Close() + + sdkConf := conf.DefaultConfig() + sdkConf.URLs.Events = eventsServer.URL + sdkConf.URLs.SDK = sdkServer.URL + sdkConf.URLs.Telemetry = telemetryServer.URL + sdkConf.StreamingEnabled = false + + logger := logging.NewLogger(nil) + client, err := New(logger, "someApikey", sdkConf) + assert.Nil(t, err) + attributes := make(map[string]interface{}) + attributes["version"] = "3.4.5" + + res1, _ := client.Treatment(&types.ClientConfig{}, "aaaaaaklmnbv", nil, "split", attributes) + assert.Equal(t, "on", res1.Treatment) + + assert.Nil(t, client.Shutdown()) + assert.Equal(t, int32(1), atomic.LoadInt32(&eventsCalls)) +} diff --git a/splitio/sdk/mocks/sdk.go b/splitio/sdk/mocks/sdk.go index bffb988..dd6abb6 100644 --- a/splitio/sdk/mocks/sdk.go +++ b/splitio/sdk/mocks/sdk.go @@ -17,6 +17,7 @@ func (m *SDKMock) Treatment( bucketingKey *string, feature string, attributes map[string]interface{}, + optFns ...sdk.OptFn, ) (*sdk.EvaluationResult, error) { args := m.Called(md, key, bucketingKey, feature, attributes) return args.Get(0).(*sdk.EvaluationResult), args.Error(1) @@ -29,6 +30,7 @@ func (m *SDKMock) Treatments( bucketingKey *string, features []string, attributes map[string]interface{}, + optFns ...sdk.OptFn, ) (map[string]sdk.EvaluationResult, error) { args := m.Called(md, key, bucketingKey, features, attributes) return args.Get(0).(map[string]sdk.EvaluationResult), args.Error(1) @@ -41,6 +43,7 @@ func (m *SDKMock) TreatmentsByFlagSet( bucketingKey *string, flagSet string, attributes map[string]interface{}, + optFns ...sdk.OptFn, ) (map[string]sdk.EvaluationResult, error) { args := m.Called(md, key, bucketingKey, flagSet, attributes) return args.Get(0).(map[string]sdk.EvaluationResult), args.Error(1) @@ -53,6 +56,7 @@ func (m *SDKMock) TreatmentsByFlagSets( bucketingKey *string, flagSets []string, attributes map[string]interface{}, + optFns ...sdk.OptFn, ) (map[string]sdk.EvaluationResult, error) { args := m.Called(md, key, bucketingKey, flagSets, attributes) return args.Get(0).(map[string]sdk.EvaluationResult), args.Error(1) diff --git a/splitio/sdk/results.go b/splitio/sdk/results.go index 12d19b7..b7aaa69 100644 --- a/splitio/sdk/results.go +++ b/splitio/sdk/results.go @@ -1,6 +1,6 @@ package sdk -import "github.com/splitio/go-split-commons/v6/dtos" +import "github.com/splitio/go-split-commons/v9/dtos" type EvaluationResult struct { Treatment string diff --git a/splitio/sdk/sdk.go b/splitio/sdk/sdk.go index 06cc765..64febce 100644 --- a/splitio/sdk/sdk.go +++ b/splitio/sdk/sdk.go @@ -1,6 +1,7 @@ package sdk import ( + "encoding/json" "errors" "fmt" "time" @@ -9,15 +10,17 @@ import ( "github.com/splitio/splitd/splitio/sdk/storage" "github.com/splitio/splitd/splitio/sdk/types" - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/engine" - "github.com/splitio/go-split-commons/v6/engine/evaluator" - "github.com/splitio/go-split-commons/v6/flagsets" - "github.com/splitio/go-split-commons/v6/healthcheck/application" - "github.com/splitio/go-split-commons/v6/provisional" - "github.com/splitio/go-split-commons/v6/service/api" - commonStorage "github.com/splitio/go-split-commons/v6/storage" - "github.com/splitio/go-split-commons/v6/synchronizer" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/engine" + "github.com/splitio/go-split-commons/v9/engine/evaluator" + "github.com/splitio/go-split-commons/v9/engine/grammar" + "github.com/splitio/go-split-commons/v9/engine/grammar/constants" + "github.com/splitio/go-split-commons/v9/flagsets" + "github.com/splitio/go-split-commons/v9/healthcheck/application" + "github.com/splitio/go-split-commons/v9/provisional" + "github.com/splitio/go-split-commons/v9/service/api" + commonStorage "github.com/splitio/go-split-commons/v9/storage" + "github.com/splitio/go-split-commons/v9/synchronizer" "github.com/splitio/go-toolkit/v5/common" "github.com/splitio/go-toolkit/v5/logging" "github.com/splitio/splitd/splitio" @@ -33,13 +36,23 @@ var ( ErrSplitNotFound = errors.New("split not found") ) +var featureFlagsRules = []string{constants.MatcherTypeAllKeys, constants.MatcherTypeInSegment, constants.MatcherTypeWhitelist, constants.MatcherTypeEqualTo, constants.MatcherTypeGreaterThanOrEqualTo, constants.MatcherTypeLessThanOrEqualTo, constants.MatcherTypeBetween, + constants.MatcherTypeEqualToSet, constants.MatcherTypePartOfSet, constants.MatcherTypeContainsAllOfSet, constants.MatcherTypeContainsAnyOfSet, constants.MatcherTypeStartsWith, constants.MatcherTypeEndsWith, constants.MatcherTypeContainsString, constants.MatcherTypeInSplitTreatment, + constants.MatcherTypeEqualToBoolean, constants.MatcherTypeMatchesString, constants.MatcherEqualToSemver, constants.MatcherTypeGreaterThanOrEqualToSemver, constants.MatcherTypeLessThanOrEqualToSemver, constants.MatcherTypeBetweenSemver, constants.MatcherTypeInListSemver, + constants.MatcherTypeInRuleBasedSegment} + +var ruleBasedSegmentRules = []string{constants.MatcherTypeAllKeys, constants.MatcherTypeInSegment, constants.MatcherTypeWhitelist, constants.MatcherTypeEqualTo, constants.MatcherTypeGreaterThanOrEqualTo, constants.MatcherTypeLessThanOrEqualTo, constants.MatcherTypeBetween, + constants.MatcherTypeEqualToSet, constants.MatcherTypePartOfSet, constants.MatcherTypeContainsAllOfSet, constants.MatcherTypeContainsAnyOfSet, constants.MatcherTypeStartsWith, constants.MatcherTypeEndsWith, constants.MatcherTypeContainsString, + constants.MatcherTypeEqualToBoolean, constants.MatcherTypeMatchesString, constants.MatcherEqualToSemver, constants.MatcherTypeGreaterThanOrEqualToSemver, constants.MatcherTypeLessThanOrEqualToSemver, constants.MatcherTypeBetweenSemver, constants.MatcherTypeInListSemver, + constants.MatcherTypeInRuleBasedSegment} + type Attributes = map[string]interface{} type Interface interface { - Treatment(cfg *types.ClientConfig, key string, bucketingKey *string, feature string, attributes map[string]interface{}) (*EvaluationResult, error) - Treatments(cfg *types.ClientConfig, key string, bucketingKey *string, features []string, attributes map[string]interface{}) (map[string]EvaluationResult, error) - TreatmentsByFlagSet(cfg *types.ClientConfig, key string, bucketingKey *string, flagSet string, attributes map[string]interface{}) (map[string]EvaluationResult, error) - TreatmentsByFlagSets(cfg *types.ClientConfig, key string, bucketingKey *string, flagSets []string, attributes map[string]interface{}) (map[string]EvaluationResult, error) + Treatment(cfg *types.ClientConfig, key string, bucketingKey *string, feature string, attributes map[string]interface{}, optFns ...OptFn) (*EvaluationResult, error) + Treatments(cfg *types.ClientConfig, key string, bucketingKey *string, features []string, attributes map[string]interface{}, optFns ...OptFn) (map[string]EvaluationResult, error) + TreatmentsByFlagSet(cfg *types.ClientConfig, key string, bucketingKey *string, flagSet string, attributes map[string]interface{}, optFns ...OptFn) (map[string]EvaluationResult, error) + TreatmentsByFlagSets(cfg *types.ClientConfig, key string, bucketingKey *string, flagSets []string, attributes map[string]interface{}, optFns ...OptFn) (map[string]EvaluationResult, error) Track(cfg *types.ClientConfig, key string, trafficType string, eventType string, value *float64, properties map[string]interface{}) error SplitNames() ([]string, error) Splits() ([]SplitView, error) @@ -61,6 +74,22 @@ type Impl struct { validator Validator } +type options struct { + evaluationOptions *dtos.EvaluationOptions +} + +type OptFn = func(o *options) + +func (c *Impl) WithEvaluationOptions(e *dtos.EvaluationOptions) OptFn { + return func(o *options) { o.evaluationOptions = e } +} + +func defaultOpts() options { + return options{ + evaluationOptions: nil, + } +} + func New(logger logging.LoggerInterface, apikey string, c *conf.Config) (*Impl, error) { if warnings := c.Normalize(); len(warnings) > 0 { @@ -84,7 +113,10 @@ func New(logger logging.LoggerInterface, apikey string, c *conf.Config) (*Impl, queueFullChan := make(chan string, 2) splitApi := api.NewSplitAPI(apikey, *advCfg, logger, md) - workers := setupWorkers(logger, splitApi, stores, hc, c, flagSetsFilter, md, impc) + fallbackTreatmentCalculator := createFallbackTreatmentCalculator(&advCfg.FallbackTreatment, logger) + evaluator := evaluator.NewEvaluator(stores.splits, stores.segments, stores.ruleBasedSegments, nil, engine.NewEngine(logger), logger, featureFlagsRules, ruleBasedSegmentRules, fallbackTreatmentCalculator) + ruleBuilder := grammar.NewRuleBuilder(stores.segments, stores.ruleBasedSegments, nil, featureFlagsRules, ruleBasedSegmentRules, logger, evaluator) + workers := setupWorkers(logger, splitApi, stores, hc, c, flagSetsFilter, md, impc, ruleBuilder) tasks := setupTasks(c, logger, workers, impc) sync := synchronizer.NewSynchronizer(*advCfg, *tasks, *workers, logger, queueFullChan) @@ -105,7 +137,7 @@ func New(logger logging.LoggerInterface, apikey string, c *conf.Config) (*Impl, logger: logger, sm: manager, ss: sync, - ev: evaluator.NewEvaluator(stores.splits, stores.segments, engine.NewEngine(logger), logger), + ev: evaluator, is: stores.impressions, es: stores.events, iq: impc.manager, @@ -117,13 +149,14 @@ func New(logger logging.LoggerInterface, apikey string, c *conf.Config) (*Impl, } // Treatment implements Interface -func (i *Impl) Treatment(cfg *types.ClientConfig, key string, bk *string, feature string, attributes Attributes) (*EvaluationResult, error) { +func (i *Impl) Treatment(cfg *types.ClientConfig, key string, bk *string, feature string, attributes Attributes, optFns ...OptFn) (*EvaluationResult, error) { + options := getOptions(optFns...) res := i.ev.EvaluateFeature(key, bk, feature, attributes) if res == nil { return nil, fmt.Errorf("nil result") } - imp := i.handleImpression(key, bk, feature, res, cfg.Metadata) + imp := i.handleImpression(key, bk, feature, res, cfg.Metadata, serializeProperties(options.evaluationOptions)) return &EvaluationResult{ Treatment: res.Treatment, Impression: imp, @@ -131,9 +164,18 @@ func (i *Impl) Treatment(cfg *types.ClientConfig, key string, bk *string, featur }, nil } +func getOptions(optFns ...OptFn) options { + options := defaultOpts() + for _, optFn := range optFns { + optFn(&options) + } + return options +} + // Treatment implements Interface -func (i *Impl) Treatments(cfg *types.ClientConfig, key string, bk *string, features []string, attributes Attributes) (map[string]EvaluationResult, error) { +func (i *Impl) Treatments(cfg *types.ClientConfig, key string, bk *string, features []string, attributes Attributes, optFns ...OptFn) (map[string]EvaluationResult, error) { + options := getOptions(optFns...) res := i.ev.EvaluateFeatures(key, bk, features, attributes) toRet := make(map[string]EvaluationResult, len(res.Evaluations)) for _, feature := range features { @@ -146,7 +188,7 @@ func (i *Impl) Treatments(cfg *types.ClientConfig, key string, bk *string, featu var eres EvaluationResult eres.Treatment = curr.Treatment - eres.Impression = i.handleImpression(key, bk, feature, &curr, cfg.Metadata) + eres.Impression = i.handleImpression(key, bk, feature, &curr, cfg.Metadata, serializeProperties(options.evaluationOptions)) eres.Config = curr.Config toRet[feature] = eres } @@ -155,14 +197,15 @@ func (i *Impl) Treatments(cfg *types.ClientConfig, key string, bk *string, featu } // TreatmentsByFlagSet implements Interface -func (i *Impl) TreatmentsByFlagSet(cfg *types.ClientConfig, key string, bk *string, flagSet string, attributes Attributes) (map[string]EvaluationResult, error) { +func (i *Impl) TreatmentsByFlagSet(cfg *types.ClientConfig, key string, bk *string, flagSet string, attributes Attributes, optFns ...OptFn) (map[string]EvaluationResult, error) { + options := getOptions(optFns...) res := i.ev.EvaluateFeatureByFlagSets(key, bk, []string{flagSet}, attributes) toRet := make(map[string]EvaluationResult, len(res.Evaluations)) for feature, curr := range res.Evaluations { var eres EvaluationResult eres.Treatment = curr.Treatment - eres.Impression = i.handleImpression(key, bk, feature, &curr, cfg.Metadata) + eres.Impression = i.handleImpression(key, bk, feature, &curr, cfg.Metadata, serializeProperties(options.evaluationOptions)) eres.Config = curr.Config toRet[feature] = eres } @@ -171,14 +214,15 @@ func (i *Impl) TreatmentsByFlagSet(cfg *types.ClientConfig, key string, bk *stri } // TreatmentsByFlagSets implements Interface -func (i *Impl) TreatmentsByFlagSets(cfg *types.ClientConfig, key string, bk *string, flagSets []string, attributes Attributes) (map[string]EvaluationResult, error) { +func (i *Impl) TreatmentsByFlagSets(cfg *types.ClientConfig, key string, bk *string, flagSets []string, attributes Attributes, optFns ...OptFn) (map[string]EvaluationResult, error) { + options := getOptions(optFns...) res := i.ev.EvaluateFeatureByFlagSets(key, bk, flagSets, attributes) toRet := make(map[string]EvaluationResult, len(res.Evaluations)) for feature, curr := range res.Evaluations { var eres EvaluationResult eres.Treatment = curr.Treatment - eres.Impression = i.handleImpression(key, bk, feature, &curr, cfg.Metadata) + eres.Impression = i.handleImpression(key, bk, feature, &curr, cfg.Metadata, serializeProperties(options.evaluationOptions)) eres.Config = curr.Config toRet[feature] = eres } @@ -251,7 +295,7 @@ func (i *Impl) Shutdown() error { return nil } -func (i *Impl) handleImpression(key string, bk *string, f string, r *evaluator.Result, cm types.ClientMetadata) *dtos.Impression { +func (i *Impl) handleImpression(key string, bk *string, f string, r *evaluator.Result, cm types.ClientMetadata, properties string) *dtos.Impression { var label string if i.cfg.LabelsEnabled { label = r.Label @@ -266,6 +310,7 @@ func (i *Impl) handleImpression(key string, bk *string, f string, r *evaluator.R Treatment: r.Treatment, Time: timeMillis(), Disabled: r.ImpressionsDisabled, + Properties: properties, } forLog, _ := i.iq.Process([]dtos.Impression{imp}, false) @@ -313,4 +358,29 @@ func timeMillis() int64 { return time.Now().UTC().UnixMilli() } +func createFallbackTreatmentCalculator(fallbackTreatmentConfig *dtos.FallbackTreatmentConfig, logger logging.LoggerInterface) dtos.FallbackTreatmentCalculator { + fallbackTreatmentConf := dtos.FallbackTreatmentConfig{} + if fallbackTreatmentConfig != nil { + fallbackTreatmentConf.GlobalFallbackTreatment = SanitizeGlobalFallbackTreatment(fallbackTreatmentConfig.GlobalFallbackTreatment, logger) + fallbackTreatmentConf.ByFlagFallbackTreatment = SanitizeByFlagFallBackTreatment(fallbackTreatmentConfig.ByFlagFallbackTreatment, logger) + } + return dtos.NewFallbackTreatmentCalculatorImp(&fallbackTreatmentConf) +} + +func serializeProperties(opts *dtos.EvaluationOptions) string { + if opts == nil { + return "" + } + if len(opts.Properties) == 0 { + return "" + } + + properties, err := json.Marshal(opts.Properties) + if err != nil { + return "" + } + + return string(properties) +} + var _ Interface = (*Impl)(nil) diff --git a/splitio/sdk/sdk_test.go b/splitio/sdk/sdk_test.go index 0e6f1a4..1202487 100644 --- a/splitio/sdk/sdk_test.go +++ b/splitio/sdk/sdk_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/engine/evaluator" - "github.com/splitio/go-split-commons/v6/storage/inmemory" - "github.com/splitio/go-split-commons/v6/synchronizer" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/engine/evaluator" + "github.com/splitio/go-split-commons/v9/storage/inmemory" + "github.com/splitio/go-split-commons/v9/synchronizer" "github.com/splitio/go-toolkit/v5/logging" "github.com/splitio/splitd/external/commons/mocks" "github.com/splitio/splitd/splitio/common/lang" @@ -256,6 +256,88 @@ func TestTreatments(t *testing.T) { } +func TestTreatmentsWithImpressionProperties(t *testing.T) { + is, _ := storage.NewImpressionsQueue(100) + + ev := &mocks.EvaluatorMock{} + ev.On("EvaluateFeatures", "key1", (*string)(nil), []string{"f1", "f2", "f3"}, Attributes{"a": 1}). + Return(evaluator.Results{Evaluations: map[string]evaluator.Result{ + "f1": {Treatment: "on", Label: "label1", EvaluationTime: 1 * time.Millisecond, SplitChangeNumber: 123}, + "f2": {Treatment: "on", Label: "label2", EvaluationTime: 2 * time.Millisecond, SplitChangeNumber: 124}, + "f3": {Treatment: "on", Label: "label3", EvaluationTime: 3 * time.Millisecond, SplitChangeNumber: 125}, + }}). + Once() + + expectedImpressions := []dtos.Impression{ + {KeyName: "key1", BucketingKey: "", FeatureName: "f1", Treatment: "on", Label: "label1", ChangeNumber: 123, Properties: "{\"pleassssse\":\"holaaaaa\"}"}, + {KeyName: "key1", BucketingKey: "", FeatureName: "f2", Treatment: "on", Label: "label2", ChangeNumber: 124, Properties: "{\"pleassssse\":\"holaaaaa\"}"}, + {KeyName: "key1", BucketingKey: "", FeatureName: "f3", Treatment: "on", Label: "label3", ChangeNumber: 125, Properties: "{\"pleassssse\":\"holaaaaa\"}"}, + } + im := &mocks.ImpressionManagerMock{} + im.On("Process", mock.Anything). + Run(func(args mock.Arguments) { + assertImpEq(t, &expectedImpressions[0], &args.Get(0).([]dtos.Impression)[0]) + }). + Return([]dtos.Impression{expectedImpressions[0]}, []dtos.Impression{}). + Once() + im.On("Process", mock.Anything). + Run(func(args mock.Arguments) { + assertImpEq(t, &expectedImpressions[1], &args.Get(0).([]dtos.Impression)[0]) + }). + Return([]dtos.Impression{expectedImpressions[1]}, []dtos.Impression{}). + Once() + im.On("Process", mock.Anything). + Run(func(args mock.Arguments) { + assertImpEq(t, &expectedImpressions[2], &args.Get(0).([]dtos.Impression)[0]) + }). + Return([]dtos.Impression{expectedImpressions[2]}, []dtos.Impression{}). + Once() + + client := &Impl{ + logger: logging.NewLogger(nil), + is: is, + ev: ev, + iq: im, + cfg: conf.Config{LabelsEnabled: true}, + } + + opts := dtos.EvaluationOptions{ + Properties: map[string]interface{}{ + "pleassssse": "holaaaaa", + }, + } + res, err := client.Treatments( + &types.ClientConfig{Metadata: types.ClientMetadata{ID: "some", SdkVersion: "go-1.2.3"}}, + "key1", nil, []string{"f1", "f2", "f3"}, Attributes{"a": 1}, client.WithEvaluationOptions(&opts)) + assert.Nil(t, err) + assert.Nil(t, res["f1"].Config) + assert.Nil(t, res["f2"].Config) + assert.Nil(t, res["f3"].Config) + assertImpEq(t, &expectedImpressions[0], res["f1"].Impression) + assertImpEq(t, &expectedImpressions[1], res["f2"].Impression) + assertImpEq(t, &expectedImpressions[2], res["f3"].Impression) + + err = is.RangeAndClear(func(md types.ClientMetadata, st *storage.LockingQueue[dtos.Impression]) { + assert.Equal(t, types.ClientMetadata{ID: "some", SdkVersion: "go-1.2.3"}, md) + assert.Equal(t, 3, st.Len()) + + var imps []dtos.Impression + n, _ := st.Pop(3, &imps) + assert.Nil(t, nil) + assert.Equal(t, 3, n) + assert.Equal(t, 3, len(imps)) + assertImpEq(t, &expectedImpressions[0], &imps[0]) + assertImpEq(t, &expectedImpressions[1], &imps[1]) + assertImpEq(t, &expectedImpressions[2], &imps[2]) + n, err = st.Pop(1, &imps) + assert.Equal(t, 0, n) + assert.ErrorIs(t, err, storage.ErrQueueEmpty) + + }) + assert.Nil(t, err) + +} + func TestTreatmentsByFlagSet(t *testing.T) { is, _ := storage.NewImpressionsQueue(100) @@ -726,6 +808,7 @@ func assertImpEq(t *testing.T, i1, i2 *dtos.Impression) { assert.Equal(t, i1.Treatment, i2.Treatment) assert.Equal(t, i1.Label, i2.Label) assert.Equal(t, i1.ChangeNumber, i2.ChangeNumber) + assert.Equal(t, i1.Properties, i2.Properties) } func assertEventEq(t *testing.T, e1, e2 *dtos.EventDTO) { diff --git a/splitio/sdk/storage/multi_test.go b/splitio/sdk/storage/multi_test.go index 611f6cb..aa516bf 100644 --- a/splitio/sdk/storage/multi_test.go +++ b/splitio/sdk/storage/multi_test.go @@ -3,7 +3,7 @@ package storage import ( "testing" - "github.com/splitio/go-split-commons/v6/dtos" + "github.com/splitio/go-split-commons/v9/dtos" "github.com/splitio/splitd/splitio/sdk/types" "github.com/stretchr/testify/assert" diff --git a/splitio/sdk/storage/storages.go b/splitio/sdk/storage/storages.go index f979fa0..7124edc 100644 --- a/splitio/sdk/storage/storages.go +++ b/splitio/sdk/storage/storages.go @@ -3,7 +3,7 @@ package storage import ( "math" - "github.com/splitio/go-split-commons/v6/dtos" + "github.com/splitio/go-split-commons/v9/dtos" "github.com/splitio/splitd/splitio/sdk/types" ) diff --git a/splitio/sdk/validators.go b/splitio/sdk/validators.go index 93e53f7..888b337 100644 --- a/splitio/sdk/validators.go +++ b/splitio/sdk/validators.go @@ -4,7 +4,7 @@ import ( "errors" "strings" - "github.com/splitio/go-split-commons/v6/storage" + "github.com/splitio/go-split-commons/v9/storage" "github.com/splitio/go-toolkit/v5/logging" ) diff --git a/splitio/sdk/workers/events.go b/splitio/sdk/workers/events.go index 9537191..3cfc364 100644 --- a/splitio/sdk/workers/events.go +++ b/splitio/sdk/workers/events.go @@ -9,10 +9,10 @@ import ( "github.com/splitio/splitd/splitio/sdk/types" serrors "github.com/splitio/splitd/splitio/util/errors" - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/service" - "github.com/splitio/go-split-commons/v6/storage" - "github.com/splitio/go-split-commons/v6/synchronizer/worker/event" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/service" + "github.com/splitio/go-split-commons/v9/storage" + "github.com/splitio/go-split-commons/v9/synchronizer/worker/event" "github.com/splitio/go-toolkit/v5/logging" gtsync "github.com/splitio/go-toolkit/v5/sync" ) diff --git a/splitio/sdk/workers/events_test.go b/splitio/sdk/workers/events_test.go index 26e1033..1f709da 100644 --- a/splitio/sdk/workers/events_test.go +++ b/splitio/sdk/workers/events_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/service" - "github.com/splitio/go-split-commons/v6/storage/inmemory" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/service" + "github.com/splitio/go-split-commons/v9/storage/inmemory" "github.com/splitio/go-toolkit/v5/logging" "github.com/splitio/splitd/splitio/sdk/conf" sss "github.com/splitio/splitd/splitio/sdk/storage" diff --git a/splitio/sdk/workers/impressions.go b/splitio/sdk/workers/impressions.go index 8f3132b..b029251 100644 --- a/splitio/sdk/workers/impressions.go +++ b/splitio/sdk/workers/impressions.go @@ -9,10 +9,10 @@ import ( "github.com/splitio/splitd/splitio/sdk/types" serrors "github.com/splitio/splitd/splitio/util/errors" - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/service" - "github.com/splitio/go-split-commons/v6/storage" - "github.com/splitio/go-split-commons/v6/synchronizer/worker/impression" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/service" + "github.com/splitio/go-split-commons/v9/storage" + "github.com/splitio/go-split-commons/v9/synchronizer/worker/impression" "github.com/splitio/go-toolkit/v5/logging" gtsync "github.com/splitio/go-toolkit/v5/sync" ) diff --git a/splitio/sdk/workers/impressions_test.go b/splitio/sdk/workers/impressions_test.go index 6499bae..219d503 100644 --- a/splitio/sdk/workers/impressions_test.go +++ b/splitio/sdk/workers/impressions_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-split-commons/v6/service" - "github.com/splitio/go-split-commons/v6/storage/inmemory" + "github.com/splitio/go-split-commons/v9/dtos" + "github.com/splitio/go-split-commons/v9/service" + "github.com/splitio/go-split-commons/v9/storage/inmemory" "github.com/splitio/go-toolkit/v5/logging" "github.com/splitio/splitd/splitio/sdk/conf" sss "github.com/splitio/splitd/splitio/sdk/storage" diff --git a/splitio/version.go b/splitio/version.go index fff15bc..e8af325 100644 --- a/splitio/version.go +++ b/splitio/version.go @@ -1,3 +1,3 @@ package splitio -const Version = "1.6.3" +const Version = "1.7.0-rc"