MM-28859 Add feature flag managment system using split.io and remove viper. (#15954)

* Add feature flag managment system using split.io and remove viper.

* Fixing tests.

* Attempt to fix postgres tests.

* Fix watch filepath for advanced logging.

* Review fixes.

* Some error wrapping.

* Remove unessisary store interface.

* Desanitize SplitKey

* Simplify.

* Review feedback.

* Rename split mlog adatper to split logger.

* fsInner

* Style.

* Restore oldcfg test.

* Downgrading non-actionable feature flag errors to warnings.

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Christopher Speller
2020-10-29 15:54:39 -07:00
коммит произвёл GitHub
родитель 8bb772638c
Коммит 1aadd36644
423 изменённых файлов: 37646 добавлений и 20257 удалений

27
vendor/github.com/splitio/go-client/v6/CONTRIBUTORS-GUIDE.md сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,27 @@
# Contributing to the Split GO SDK
Split SDK is an open source project and we welcome feedback and contribution. The information below describes how to build the project with your changes, run the tests, and send the Pull Request(PR).
## Development
### Development process
1. Fork the repository and create a topic branch from `development` branch. Please use a descriptive name for your branch.
2. While developing, use descriptive messages in your commits. Avoid short or meaningless sentences like "fix bug".
3. Make sure to add tests for both positive and negative cases.
4. <if applicable> Run the linter script of the project and fix any issues you find.
5. Run the build script and make sure it runs with no errors.
6. Run all tests and make sure there are no failures.
7. `git push` your changes to GitHub within your topic branch.
8. Open a Pull Request(PR) from your forked repo and into the `development` branch of the original repository.
9. When creating your PR, please fill out all the fields of the PR template, as applicable, for the project.
10. Check for conflicts once the pull request is created to make sure your PR can be merged cleanly into `development`.
11. Keep an eye out for any feedback or comments from Split's SDK team.
### Running tests
To run test you can execute the command `go test ./...` on the root folder.
# Contact
If you have any other questions or need to contact us directly in a private manner send us a note at sdks@split.io.

13
vendor/github.com/splitio/go-client/v6/LICENSE.txt сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,13 @@
Copyright © 2020 Split Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

16
vendor/github.com/splitio/go-client/v6/splitio/client/bucketingkey.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,16 @@
package client
// Key struct to be used when supplying two keys. One for matching purposes and another one
// for hashing.
type Key struct {
MatchingKey string
BucketingKey string
}
// NewKey instantiates a new key
func NewKey(matchingKey string, bucketingKey string) *Key {
return &Key{
MatchingKey: matchingKey,
BucketingKey: bucketingKey,
}
}

390
vendor/github.com/splitio/go-client/v6/splitio/client/client.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,390 @@
package client
import (
"errors"
"runtime/debug"
"time"
"github.com/splitio/go-client/v6/splitio/conf"
"github.com/splitio/go-client/v6/splitio/engine/evaluator"
"github.com/splitio/go-client/v6/splitio/engine/evaluator/impressionlabels"
impressionlistener "github.com/splitio/go-client/v6/splitio/impressionListener"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/provisional"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-split-commons/v2/util"
"github.com/splitio/go-toolkit/v3/logging"
)
// SplitClient is the entry-point of the split SDK.
type SplitClient struct {
logger logging.LoggerInterface
evaluator evaluator.Interface
impressions storage.ImpressionStorageProducer
metrics storage.MetricsStorageProducer
events storage.EventStorageProducer
validator inputValidation
factory *SplitFactory
impressionListener *impressionlistener.WrapperImpressionListener
impressionManager provisional.ImpressionManager
}
// TreatmentResult struct that includes the Treatment evaluation with the corresponding Config
type TreatmentResult struct {
Treatment string `json:"treatment"`
Config *string `json:"config"`
}
// getEvaluationResult calls evaluation for one particular split
func (c *SplitClient) getEvaluationResult(
matchingKey string,
bucketingKey *string,
feature string,
attributes map[string]interface{},
operation string,
) *evaluator.Result {
if c.isReady() {
return c.evaluator.EvaluateFeature(matchingKey, bucketingKey, feature, attributes)
}
c.logger.Warning(operation + ": the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
return &evaluator.Result{
Treatment: evaluator.Control,
Label: impressionlabels.ClientNotReady,
Config: nil,
}
}
// getEvaluationsResult calls evaluation for multiple treatments at once
func (c *SplitClient) getEvaluationsResult(
matchingKey string,
bucketingKey *string,
features []string,
attributes map[string]interface{},
operation string,
) evaluator.Results {
if c.isReady() {
return c.evaluator.EvaluateFeatures(matchingKey, bucketingKey, features, attributes)
}
c.logger.Warning(operation + ": the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
result := evaluator.Results{
EvaluationTimeNs: 0,
Evaluations: make(map[string]evaluator.Result),
}
for _, feature := range features {
result.Evaluations[feature] = evaluator.Result{
Treatment: evaluator.Control,
Label: impressionlabels.ClientNotReady,
Config: nil,
}
}
return result
}
// createImpression creates impression to be stored and used by listener
func (c *SplitClient) createImpression(
feature string,
bucketingKey *string,
evaluationLabel string,
matchingKey string,
treatment string,
changeNumber int64,
) dtos.Impression {
var label string
if c.factory.cfg.LabelsEnabled {
label = evaluationLabel
}
impressionBucketingKey := ""
if bucketingKey != nil {
impressionBucketingKey = *bucketingKey
}
return dtos.Impression{
FeatureName: feature,
BucketingKey: impressionBucketingKey,
ChangeNumber: changeNumber,
KeyName: matchingKey,
Label: label,
Treatment: treatment,
Time: time.Now().UTC().UnixNano() / int64(time.Millisecond), // Convert standard timestamp to java's ms timestamps
}
}
// storeData stores impression, runs listener and stores metrics
func (c *SplitClient) storeData(impressions []dtos.Impression, attributes map[string]interface{}, metricsLabel string, evaluationTimeNs int64) {
// Store impression
if c.impressions != nil {
forLog, forListener := c.impressionManager.ProcessImpressions(impressions)
c.impressions.LogImpressions(forLog)
// Custom Impression Listener
if c.impressionListener != nil {
c.impressionListener.SendDataToClient(forListener, attributes)
}
} else {
c.logger.Warning("No impression storage set in client. Not sending impressions!")
}
// Store latency
if c.metrics != nil {
bucket := util.Bucket(evaluationTimeNs)
c.metrics.IncLatency(metricsLabel, bucket)
} else {
c.logger.Warning("No metrics storage set in client. Not sending latencies!")
}
}
// doTreatmentCall retrieves treatments of an specific feature with configurations object if it is present
// for a certain key and set of attributes
func (c *SplitClient) doTreatmentCall(
key interface{},
feature string,
attributes map[string]interface{},
operation string,
metricsLabel string,
) (t TreatmentResult) {
controlTreatment := TreatmentResult{
Treatment: evaluator.Control,
Config: nil,
}
// Set up a guard deferred function to recover if the SDK starts panicking
defer func() {
if r := recover(); r != nil {
// At this point we'll only trust that the logger isn't panicking trust
// that the logger isn't panicking
c.logger.Error(
"SDK is panicking with the following error", r, "\n",
string(debug.Stack()), "\n",
"Returning CONTROL", "\n")
t = controlTreatment
}
}()
if c.isDestroyed() {
c.logger.Error("Client has already been destroyed - no calls possible")
return controlTreatment
}
matchingKey, bucketingKey, err := c.validator.ValidateTreatmentKey(key, operation)
if err != nil {
c.logger.Error(err.Error())
return controlTreatment
}
feature, err = c.validator.ValidateFeatureName(feature, operation)
if err != nil {
c.logger.Error(err.Error())
return controlTreatment
}
evaluationResult := c.getEvaluationResult(matchingKey, bucketingKey, feature, attributes, operation)
if !c.validator.IsSplitFound(evaluationResult.Label, feature, operation) {
return controlTreatment
}
c.storeData(
[]dtos.Impression{c.createImpression(feature, bucketingKey, evaluationResult.Label, matchingKey, evaluationResult.Treatment, evaluationResult.SplitChangeNumber)},
attributes,
metricsLabel,
evaluationResult.EvaluationTimeNs,
)
return TreatmentResult{
Treatment: evaluationResult.Treatment,
Config: evaluationResult.Config,
}
}
// Treatment implements the main functionality of split. Retrieve treatments of a specific feature
// for a certain key and set of attributes
func (c *SplitClient) Treatment(key interface{}, feature string, attributes map[string]interface{}) string {
return c.doTreatmentCall(key, feature, attributes, "Treatment", "sdk.getTreatment").Treatment
}
// TreatmentWithConfig implements the main functionality of split. Retrieves the treatment of a specific feature with
// the corresponding configuration if it is present
func (c *SplitClient) TreatmentWithConfig(key interface{}, feature string, attributes map[string]interface{}) TreatmentResult {
return c.doTreatmentCall(key, feature, attributes, "TreatmentWithConfig", "sdk.getTreatmentWithConfig")
}
// Generates control treatments
func (c *SplitClient) generateControlTreatments(features []string, operation string) map[string]TreatmentResult {
treatments := make(map[string]TreatmentResult)
filtered, err := c.validator.ValidateFeatureNames(features, operation)
if err != nil {
return treatments
}
for _, feature := range filtered {
treatments[feature] = TreatmentResult{
Treatment: evaluator.Control,
Config: nil,
}
}
return treatments
}
// doTreatmentsCall retrieves treatments of an specific array of features with configurations object if it is present
// for a certain key and set of attributes
func (c *SplitClient) doTreatmentsCall(
key interface{},
features []string,
attributes map[string]interface{},
operation string,
metricsLabel string,
) (t map[string]TreatmentResult) {
treatments := make(map[string]TreatmentResult)
// Set up a guard deferred function to recover if the SDK starts panicking
defer func() {
if r := recover(); r != nil {
// At this point we'll only trust that the logger isn't panicking trust
// that the logger isn't panicking
c.logger.Error(
"SDK is panicking with the following error", r, "\n",
string(debug.Stack()), "\n")
t = treatments
}
}()
if c.isDestroyed() {
c.logger.Error("Client has already been destroyed - no calls possible")
return c.generateControlTreatments(features, operation)
}
matchingKey, bucketingKey, err := c.validator.ValidateTreatmentKey(key, operation)
if err != nil {
c.logger.Error(err.Error())
return c.generateControlTreatments(features, operation)
}
filteredFeatures, err := c.validator.ValidateFeatureNames(features, operation)
if err != nil {
c.logger.Error(err.Error())
return map[string]TreatmentResult{}
}
var bulkImpressions []dtos.Impression
evaluationsResult := c.getEvaluationsResult(matchingKey, bucketingKey, filteredFeatures, attributes, operation)
for feature, evaluation := range evaluationsResult.Evaluations {
if !c.validator.IsSplitFound(evaluation.Label, feature, operation) {
treatments[feature] = TreatmentResult{
Treatment: evaluator.Control,
Config: nil,
}
} else {
bulkImpressions = append(bulkImpressions, c.createImpression(feature, bucketingKey, evaluation.Label, matchingKey, evaluation.Treatment, evaluation.SplitChangeNumber))
treatments[feature] = TreatmentResult{
Treatment: evaluation.Treatment,
Config: evaluation.Config,
}
}
}
c.storeData(bulkImpressions, attributes, metricsLabel, evaluationsResult.EvaluationTimeNs)
return treatments
}
// Treatments evaluates multiple featers for a single user and set of attributes at once
func (c *SplitClient) Treatments(key interface{}, features []string, attributes map[string]interface{}) map[string]string {
treatments := map[string]string{}
result := c.doTreatmentsCall(key, features, attributes, "Treatments", "sdk.getTreatments")
for feature, treatmentResult := range result {
treatments[feature] = treatmentResult.Treatment
}
return treatments
}
// TreatmentsWithConfig evaluates multiple featers for a single user and set of attributes at once and returns configurations
func (c *SplitClient) TreatmentsWithConfig(key interface{}, features []string, attributes map[string]interface{}) map[string]TreatmentResult {
return c.doTreatmentsCall(key, features, attributes, "TreatmentsWithConfig", "sdk.getTreatmentsWithConfig")
}
// isDestroyed returns true if the client has been destroyed
func (c *SplitClient) isDestroyed() bool {
return c.factory.IsDestroyed()
}
// isReady returns true if the client is ready
func (c *SplitClient) isReady() bool {
return c.factory.IsReady()
}
// Destroy the client and the underlying factory.
func (c *SplitClient) Destroy() {
if !c.isDestroyed() {
c.factory.Destroy()
}
}
// Track an event and its custom value
func (c *SplitClient) Track(
key string,
trafficType string,
eventType string,
value interface{},
properties map[string]interface{},
) (ret error) {
defer func() {
if r := recover(); r != nil {
// At this point we'll only trust that the logger isn't panicking
c.logger.Error(
"SDK is panicking with the following error", r, "\n",
string(debug.Stack()), "\n",
)
ret = errors.New("Track is panicking. Please check logs")
}
return
}()
if c.isDestroyed() {
c.logger.Error("Client has already been destroyed - no calls possible")
return errors.New("Client has already been destroyed - no calls possible")
}
if !c.isReady() {
c.logger.Warning("Track: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
}
key, trafficType, eventType, value, err := c.validator.ValidateTrackInputs(
key,
trafficType,
eventType,
value,
c.isReady() && c.factory.apikey != conf.Localhost,
)
if err != nil {
c.logger.Error(err.Error())
return err
}
properties, size, err := c.validator.validateTrackProperties(properties)
if err != nil {
return err
}
err = c.events.Push(dtos.EventDTO{
Key: key,
TrafficTypeName: trafficType,
EventTypeID: eventType,
Value: value,
Timestamp: time.Now().UTC().UnixNano() / int64(time.Millisecond), // Convert standard timestamp to java's ms timestamps
Properties: properties,
}, size)
if err != nil {
c.logger.Error("Error tracking event", err.Error())
return err
}
return nil
}
// BlockUntilReady Calls BlockUntilReady on factory to block client on readiness
func (c *SplitClient) BlockUntilReady(timer int) error {
return c.factory.BlockUntilReady(timer)
}

463
vendor/github.com/splitio/go-client/v6/splitio/client/factory.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,463 @@
// Package client contains implementations of the Split SDK client and the factory used
// to instantiate it.
package client
import (
"errors"
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/splitio/go-client/v6/splitio"
"github.com/splitio/go-client/v6/splitio/conf"
"github.com/splitio/go-client/v6/splitio/engine"
"github.com/splitio/go-client/v6/splitio/engine/evaluator"
impressionlistener "github.com/splitio/go-client/v6/splitio/impressionListener"
config "github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/provisional"
"github.com/splitio/go-split-commons/v2/service"
"github.com/splitio/go-split-commons/v2/service/local"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-split-commons/v2/storage/mutexmap"
"github.com/splitio/go-split-commons/v2/storage/mutexqueue"
"github.com/splitio/go-split-commons/v2/storage/redis"
"github.com/splitio/go-split-commons/v2/synchronizer"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/event"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/impression"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/impressionscount"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/metric"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/segment"
"github.com/splitio/go-split-commons/v2/synchronizer/worker/split"
"github.com/splitio/go-split-commons/v2/tasks"
"github.com/splitio/go-toolkit/v3/logging"
)
const (
sdkStatusDestroyed = iota
sdkStatusInitializing
sdkStatusReady
sdkInitializationFailed = -1
)
type sdkStorages struct {
splits storage.SplitStorageConsumer
segments storage.SegmentStorageConsumer
impressions storage.ImpressionStorageProducer
events storage.EventStorageProducer
telemetry storage.MetricsStorageProducer
}
// SplitFactory struct is responsible for instantiating and storing instances of client and manager.
type SplitFactory struct {
metadata dtos.Metadata
storages sdkStorages
apikey string
status atomic.Value
readinessSubscriptors map[int]chan int
operationMode string
mutex sync.Mutex
cfg *conf.SplitSdkConfig
impressionListener *impressionlistener.WrapperImpressionListener
logger logging.LoggerInterface
syncManager *synchronizer.Manager
impressionManager provisional.ImpressionManager
}
// Client returns the split client instantiated by the factory
func (f *SplitFactory) Client() *SplitClient {
return &SplitClient{
logger: f.logger,
evaluator: evaluator.NewEvaluator(f.storages.splits, f.storages.segments, engine.NewEngine(f.logger), f.logger),
impressions: f.storages.impressions,
metrics: f.storages.telemetry,
events: f.storages.events,
validator: inputValidation{
logger: f.logger,
splitStorage: f.storages.splits,
},
factory: f,
impressionListener: f.impressionListener,
impressionManager: f.impressionManager,
}
}
// Manager returns the split manager instantiated by the factory
func (f *SplitFactory) Manager() *SplitManager {
return &SplitManager{
splitStorage: f.storages.splits,
validator: inputValidation{logger: f.logger},
logger: f.logger,
factory: f,
}
}
// IsDestroyed returns true if tbe client has been destroyed
func (f *SplitFactory) IsDestroyed() bool {
return f.status.Load() == sdkStatusDestroyed
}
// IsReady returns true if the factory is ready
func (f *SplitFactory) IsReady() bool {
return f.status.Load() == sdkStatusReady
}
// initializates task for localhost mode
func (f *SplitFactory) initializationLocalhost(readyChannel chan int) {
f.syncManager.Start()
<-readyChannel
f.broadcastReadiness(sdkStatusReady)
}
// initializates tasks for in-memory mode
func (f *SplitFactory) initializationInMemory(readyChannel chan int) {
go f.syncManager.Start()
msg := <-readyChannel
switch msg {
case synchronizer.Ready:
// Broadcast ready status for SDK
f.broadcastReadiness(sdkStatusReady)
default:
f.broadcastReadiness(sdkInitializationFailed)
}
}
// broadcastReadiness broadcasts message to all the subscriptors
func (f *SplitFactory) broadcastReadiness(status int) {
f.mutex.Lock()
defer f.mutex.Unlock()
if f.status.Load() == sdkStatusInitializing && status == sdkStatusReady {
f.status.Store(sdkStatusReady)
}
for _, subscriptor := range f.readinessSubscriptors {
subscriptor <- status
}
}
// subscribes listener
func (f *SplitFactory) subscribe(name int, subscriptor chan int) {
f.mutex.Lock()
defer f.mutex.Unlock()
f.readinessSubscriptors[name] = subscriptor
}
// removes a particular subscriptor from the list
func (f *SplitFactory) unsubscribe(name int, subscriptor chan int) {
f.mutex.Lock()
defer f.mutex.Unlock()
_, ok := f.readinessSubscriptors[name]
if ok {
delete(f.readinessSubscriptors, name)
}
}
// BlockUntilReady blocks client or manager until the SDK is ready, error occurs or times out
func (f *SplitFactory) BlockUntilReady(timer int) error {
if f.IsReady() {
return nil
}
if timer <= 0 {
return errors.New("SDK Initialization: timer must be positive number")
}
if f.IsDestroyed() {
return errors.New("SDK Initialization: Client is destroyed")
}
block := make(chan int, 1)
f.mutex.Lock()
subscriptorName := len(f.readinessSubscriptors)
f.mutex.Unlock()
defer func() {
// Unsubscription will happen only if a block channel has been created
if block != nil {
f.unsubscribe(subscriptorName, block)
close(block)
}
}()
f.subscribe(subscriptorName, block)
select {
case status := <-block:
switch status {
case sdkStatusReady:
break
case sdkInitializationFailed:
return errors.New("SDK Initialization failed")
}
case <-time.After(time.Second * time.Duration(timer)):
return fmt.Errorf("SDK Initialization: time of %d exceeded", timer)
}
return nil
}
// Destroy stops all async tasks and clears all storages
func (f *SplitFactory) Destroy() {
if !f.IsDestroyed() {
removeInstanceFromTracker(f.apikey)
}
f.status.Store(sdkStatusDestroyed)
if f.cfg.OperationMode == conf.RedisConsumer {
return
}
f.syncManager.Stop()
}
// setupLogger sets up the logger according to the parameters submitted by the sdk user
func setupLogger(cfg *conf.SplitSdkConfig) logging.LoggerInterface {
var logger logging.LoggerInterface
if cfg.Logger != nil {
// If a custom logger is supplied, use it.
logger = cfg.Logger
} else {
logger = logging.NewLogger(&cfg.LoggerConfig)
}
return logger
}
func setupInMemoryFactory(
apikey string,
cfg *conf.SplitSdkConfig,
logger logging.LoggerInterface,
metadata dtos.Metadata,
) (*SplitFactory, error) {
advanced := conf.NormalizeSDKConf(cfg.Advanced)
if strings.TrimSpace(cfg.SplitSyncProxyURL) != "" {
advanced.StreamingEnabled = false
}
/*
err := api.ValidateApikey(apikey, *advanced)
if err != nil {
return nil, err
}
*/
inMememoryFullQueue := make(chan string, 2) // Size 2: So that it's able to accept one event from each resource simultaneously.
splitsStorage := mutexmap.NewMMSplitStorage()
segmentsStorage := mutexmap.NewMMSegmentStorage()
impressionsStorage := mutexqueue.NewMQImpressionsStorage(cfg.Advanced.ImpressionsQueueSize, inMememoryFullQueue, logger)
telemetryStorage := mutexmap.NewMMMetricsStorage()
eventsStorage := mutexqueue.NewMQEventsStorage(cfg.Advanced.EventsQueueSize, inMememoryFullQueue, logger)
metricsWrapper := storage.NewMetricWrapper(telemetryStorage, nil, logger)
managerConfig := config.ManagerConfig{
ImpressionsMode: cfg.ImpressionsMode,
OperationMode: cfg.OperationMode,
ListenerEnabled: cfg.Advanced.ImpressionListener != nil,
}
splitAPI := service.NewSplitAPI(apikey, advanced, logger, metadata)
workers := synchronizer.Workers{
SplitFetcher: split.NewSplitFetcher(splitsStorage, splitAPI.SplitFetcher, metricsWrapper, logger),
SegmentFetcher: segment.NewSegmentFetcher(splitsStorage, segmentsStorage, splitAPI.SegmentFetcher, metricsWrapper, logger),
EventRecorder: event.NewEventRecorderSingle(eventsStorage, splitAPI.EventRecorder, metricsWrapper, logger, metadata),
ImpressionRecorder: impression.NewRecorderSingle(impressionsStorage, splitAPI.ImpressionRecorder, metricsWrapper, logger, metadata, managerConfig),
TelemetryRecorder: metric.NewRecorderSingle(telemetryStorage, splitAPI.MetricRecorder, metadata),
}
splitTasks := synchronizer.SplitTasks{
SplitSyncTask: tasks.NewFetchSplitsTask(workers.SplitFetcher, cfg.TaskPeriods.SplitSync, logger),
SegmentSyncTask: tasks.NewFetchSegmentsTask(workers.SegmentFetcher, cfg.TaskPeriods.SegmentSync, advanced.SegmentWorkers, advanced.SegmentQueueSize, logger),
EventSyncTask: tasks.NewRecordEventsTask(workers.EventRecorder, advanced.EventsBulkSize, cfg.TaskPeriods.EventsSync, logger),
ImpressionSyncTask: tasks.NewRecordImpressionsTask(workers.ImpressionRecorder, cfg.TaskPeriods.ImpressionSync, logger, advanced.ImpressionsBulkSize),
TelemetrySyncTask: tasks.NewRecordTelemetryTask(workers.TelemetryRecorder, cfg.TaskPeriods.LatencySync, logger),
}
var impressionsCounter *provisional.ImpressionsCounter
if cfg.ImpressionsMode == config.ImpressionsModeOptimized {
impressionsCounter = provisional.NewImpressionsCounter()
workers.ImpressionsCountRecorder = impressionscount.NewRecorderSingle(impressionsCounter, splitAPI.ImpressionRecorder, metadata, logger)
splitTasks.ImpressionsCountSyncTask = tasks.NewRecordImpressionsCountTask(workers.ImpressionsCountRecorder, logger)
}
impressionManager, err := provisional.NewImpressionManager(managerConfig, impressionsCounter)
if err != nil {
return nil, err
}
syncImpl := synchronizer.NewSynchronizer(
advanced,
splitTasks,
workers,
logger,
inMememoryFullQueue,
)
readyChannel := make(chan int, 1)
syncManager, err := synchronizer.NewSynchronizerManager(
syncImpl,
logger,
advanced,
splitAPI.AuthClient,
splitsStorage,
readyChannel,
)
if err != nil {
return nil, err
}
splitFactory := SplitFactory{
apikey: apikey,
cfg: cfg,
metadata: metadata,
logger: logger,
operationMode: conf.InMemoryStandAlone,
storages: sdkStorages{
splits: splitsStorage,
events: eventsStorage,
impressions: impressionsStorage,
segments: segmentsStorage,
telemetry: telemetryStorage,
},
readinessSubscriptors: make(map[int]chan int),
syncManager: syncManager,
}
splitFactory.status.Store(sdkStatusInitializing)
splitFactory.impressionManager = impressionManager
go splitFactory.initializationInMemory(readyChannel)
return &splitFactory, nil
}
func setupRedisFactory(apikey string, cfg *conf.SplitSdkConfig, logger logging.LoggerInterface, metadata dtos.Metadata) (*SplitFactory, error) {
redisClient, err := redis.NewRedisClient(&cfg.Redis, logger)
if err != nil {
logger.Error("Failed to instantiate redis client.")
return nil, err
}
storages := sdkStorages{
splits: redis.NewSplitStorage(redisClient, logger),
segments: redis.NewSegmentStorage(redisClient, logger),
impressions: redis.NewImpressionStorage(redisClient, metadata, logger),
telemetry: redis.NewMetricsStorage(redisClient, metadata, logger),
events: redis.NewEventsStorage(redisClient, metadata, logger),
}
factory := &SplitFactory{
apikey: apikey,
cfg: cfg,
metadata: metadata,
logger: logger,
operationMode: conf.RedisConsumer,
storages: storages,
readinessSubscriptors: make(map[int]chan int),
}
impressionManager, err := provisional.NewImpressionManager(config.ManagerConfig{
OperationMode: cfg.OperationMode,
ImpressionsMode: cfg.ImpressionsMode,
ListenerEnabled: cfg.Advanced.ImpressionListener != nil,
}, nil)
if err != nil {
return nil, err
}
factory.impressionManager = impressionManager
factory.status.Store(sdkStatusReady)
return factory, nil
}
func setupLocalhostFactory(
apikey string,
cfg *conf.SplitSdkConfig,
logger logging.LoggerInterface,
metadata dtos.Metadata,
) (*SplitFactory, error) {
splitStorage := mutexmap.NewMMSplitStorage()
splitPeriod := cfg.TaskPeriods.SplitSync
readyChannel := make(chan int, 1)
syncManager, err := synchronizer.NewSynchronizerManager(
synchronizer.NewLocal(
splitPeriod,
&service.SplitAPI{
SplitFetcher: local.NewFileSplitFetcher(cfg.SplitFile, logger),
},
splitStorage,
logger,
),
logger,
config.AdvancedConfig{},
nil,
splitStorage,
readyChannel,
)
if err != nil {
return nil, err
}
splitFactory := &SplitFactory{
apikey: apikey,
cfg: cfg,
metadata: metadata,
logger: logger,
storages: sdkStorages{
splits: splitStorage,
impressions: mutexqueue.NewMQImpressionsStorage(cfg.Advanced.ImpressionsQueueSize, make(chan string, 1), logger),
telemetry: mutexmap.NewMMMetricsStorage(),
events: mutexqueue.NewMQEventsStorage(cfg.Advanced.EventsQueueSize, make(chan string, 1), logger),
segments: mutexmap.NewMMSegmentStorage(),
},
readinessSubscriptors: make(map[int]chan int),
syncManager: syncManager,
}
splitFactory.status.Store(sdkStatusInitializing)
impressionManager, err := provisional.NewImpressionManager(config.ManagerConfig{
OperationMode: cfg.OperationMode,
ImpressionsMode: cfg.ImpressionsMode,
ListenerEnabled: cfg.Advanced.ImpressionListener != nil,
}, nil)
if err != nil {
return nil, err
}
splitFactory.impressionManager = impressionManager
// Call fetching tasks as goroutine
go splitFactory.initializationLocalhost(readyChannel)
return splitFactory, nil
}
// newFactory instantiates a new SplitFactory object. Accepts a SplitSdkConfig struct as an argument,
// which will be used to instantiate both the client and the manager
func newFactory(apikey string, cfg *conf.SplitSdkConfig, logger logging.LoggerInterface) (*SplitFactory, error) {
metadata := dtos.Metadata{
SDKVersion: "go-" + splitio.Version,
MachineIP: cfg.IPAddress,
MachineName: cfg.InstanceName,
}
var splitFactory *SplitFactory
var err error
switch cfg.OperationMode {
case conf.InMemoryStandAlone:
splitFactory, err = setupInMemoryFactory(apikey, cfg, logger, metadata)
case conf.RedisConsumer:
splitFactory, err = setupRedisFactory(apikey, cfg, logger, metadata)
case conf.Localhost:
splitFactory, err = setupLocalhostFactory(apikey, cfg, logger, metadata)
default:
err = fmt.Errorf("Invalid operation mode \"%s\"", cfg.OperationMode)
}
if err != nil {
return nil, err
}
if cfg.Advanced.ImpressionListener != nil {
splitFactory.impressionListener = impressionlistener.NewImpressionListenerWrapper(
cfg.Advanced.ImpressionListener,
metadata,
)
}
return splitFactory, nil
}

72
vendor/github.com/splitio/go-client/v6/splitio/client/factory_tracker.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,72 @@
package client
import (
"fmt"
"sync"
"github.com/splitio/go-client/v6/splitio/conf"
"github.com/splitio/go-toolkit/v3/logging"
)
// factoryInstances factory tracker instantiations
var factoryInstances = make(map[string]int64)
var mutex = &sync.Mutex{}
func setFactory(apikey string, logger logging.LoggerInterface) {
mutex.Lock()
defer mutex.Unlock()
counter, exists := factoryInstances[apikey]
if !exists {
if len(factoryInstances) > 0 {
logger.Warning("Factory Instantiation: You already have an instance of the Split factory. Make sure you definitely want " +
"this additional instance. We recommend keeping only one instance of the factory at all times (Singleton pattern) and " +
"reusing it throughout your application.")
}
factoryInstances[apikey] = 1
} else {
if counter == 1 {
logger.Warning("Factory Instantiation: You already have 1 factory with this API Key. We recommend keeping only one instance of the factory " +
"at all times (Singleton pattern) and reusing it throughout your application.")
} else {
logger.Warning(fmt.Sprintf("Factory Instantiation: You already have %d factories with this API Key.", counter) +
" We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application.")
}
factoryInstances[apikey]++
}
}
// removeInstanceFromTracker decrease the instance of factory track
func removeInstanceFromTracker(apikey string) {
mutex.Lock()
defer mutex.Unlock()
counter, exists := factoryInstances[apikey]
if exists {
if counter == 1 {
delete(factoryInstances, apikey)
} else {
factoryInstances[apikey]--
}
}
}
// NewSplitFactory instantiates a new SplitFactory object. Accepts a SplitSdkConfig struct as an argument,
// which will be used to instantiate both the client and the manager
func NewSplitFactory(apikey string, cfg *conf.SplitSdkConfig) (*SplitFactory, error) {
if cfg == nil {
cfg = conf.Default()
}
logger := setupLogger(cfg)
err := conf.Normalize(apikey, cfg)
if err != nil {
logger.Error(err.Error())
return nil, err
}
splitFactory, err := newFactory(apikey, cfg, logger)
setFactory(apikey, logger)
return splitFactory, err
}

294
vendor/github.com/splitio/go-client/v6/splitio/client/input_validator.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,294 @@
package client
import (
"errors"
"fmt"
"math"
"regexp"
"strconv"
"strings"
"github.com/splitio/go-client/v6/splitio/engine/evaluator/impressionlabels"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-toolkit/v3/datastructures/set"
"github.com/splitio/go-toolkit/v3/logging"
)
// InputValidation struct is responsible for cheking any input of treatment and
// track methods.
// MaxLength constant to check the length of the splits
const MaxLength = 250
// MaxEventLength constant to limit the event size
const MaxEventLength = 32768
// RegExpEventType constant that EventType must match
const RegExpEventType = "^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$"
type inputValidation struct {
logger logging.LoggerInterface
splitStorage storage.SplitStorageConsumer
}
func parseIfNumeric(value interface{}, operation string) (string, error) {
f, float := value.(float64)
i, integer := value.(int)
i32, integer32 := value.(int32)
i64, integer64 := value.(int64)
if float {
if math.IsNaN(f) || math.IsInf(f, -1) || math.IsInf(f, 1) || math.IsInf(f, 0) {
return "", errors.New(operation + ": you passed an invalid key, key must be a non-empty string")
}
return strconv.FormatFloat(f, 'f', -1, 64), nil
}
if integer {
return strconv.Itoa(i), nil
}
if integer32 {
return strconv.FormatInt(int64(i32), 10), nil
}
if integer64 {
return strconv.FormatInt(i64, 10), nil
}
return "", errors.New(operation + ": you passed an invalid key, key must be a non-empty string")
}
func (i *inputValidation) checkWhitespaces(value string, operation string) string {
trimmed := strings.TrimSpace(value)
if strings.TrimSpace(value) != value {
i.logger.Warning(fmt.Sprintf(operation+": split name '%s' has extra whitespace, trimming", value))
}
return trimmed
}
func checkIsEmptyString(value string, name string, operation string) error {
if strings.TrimSpace(value) == "" {
return errors.New(operation + ": you passed an empty " + name + ", " + name + " must be a non-empty string")
}
return nil
}
func checkIsNotValidLength(value string, name string, operation string) error {
if len(value) > MaxLength {
return errors.New(operation + ": " + name + " too long - must be " + strconv.Itoa(MaxLength) + " characters or less")
}
return nil
}
func checkIsValidString(value string, name string, operation string) error {
err := checkIsEmptyString(value, name, operation)
if err != nil {
return err
}
return checkIsNotValidLength(value, name, operation)
}
func checkValidKeyObject(matchingKey string, bucketingKey *string, operation string) (string, *string, error) {
if bucketingKey == nil {
return "", nil, errors.New(operation + ": you passed a nil bucketingKey, bucketingKey must be a non-empty string")
}
err := checkIsValidString(matchingKey, "matchingKey", operation)
if err != nil {
return "", nil, err
}
err = checkIsValidString(*bucketingKey, "bucketingKey", operation)
if err != nil {
return "", nil, err
}
return matchingKey, bucketingKey, nil
}
// ValidateTreatmentKey implements the validation for Treatment call
func (i *inputValidation) ValidateTreatmentKey(key interface{}, operation string) (string, *string, error) {
if key == nil {
return "", nil, errors.New(operation + ": you passed a nil key, key must be a non-empty string")
}
okey, ok := key.(*Key)
if ok {
return checkValidKeyObject(okey.MatchingKey, &okey.BucketingKey, operation)
}
var sMatchingKey string
var err error
sMatchingKey, ok = key.(string)
if !ok {
sMatchingKey, err = parseIfNumeric(key, operation)
if err != nil {
return "", nil, err
}
i.logger.Warning(fmt.Sprintf(operation+": key %s is not of type string, converting", key))
}
err = checkIsValidString(sMatchingKey, "key", operation)
if err != nil {
return "", nil, err
}
return sMatchingKey, nil, nil
}
// ValidateFeatureName implements the validation for FetureName
func (i *inputValidation) ValidateFeatureName(featureName string, operation string) (string, error) {
err := checkIsEmptyString(featureName, "featureName", operation)
if err != nil {
return "", err
}
return i.checkWhitespaces(featureName, operation), nil
}
func checkEventType(eventType string) error {
err := checkIsEmptyString(eventType, "event type", "Track")
if err != nil {
return err
}
var r = regexp.MustCompile(RegExpEventType)
if !r.MatchString(eventType) {
return errors.New("Track: you passed " + eventType + ", event name must adhere to " +
"the regular expression " + RegExpEventType + ". This means an event " +
"name must be alphanumeric, cannot be more than 80 characters long, and can " +
"only include a dash, underscore, period, or colon as separators of " +
"alphanumeric characters")
}
return nil
}
func (i *inputValidation) checkTrafficType(trafficType string, shouldValidateExistence bool) (string, error) {
err := checkIsEmptyString(trafficType, "traffic type", "Track")
if err != nil {
return "", err
}
toLower := strings.ToLower(trafficType)
if toLower != trafficType {
i.logger.Warning("Track: traffic type should be all lowercase - converting string to lowercase")
}
if shouldValidateExistence && !i.splitStorage.TrafficTypeExists(toLower) {
i.logger.Warning("Track: traffic type " + toLower + " does not have any corresponding Splits in this environment, " +
"make sure youre tracking your events to a valid traffic type defined in the Split console")
}
return toLower, nil
}
func checkValue(value interface{}) error {
if value == nil {
return nil
}
_, float := value.(float64)
_, integer := value.(int)
_, integer32 := value.(int32)
_, integer64 := value.(int64)
if float || integer || integer32 || integer64 {
return nil
}
return errors.New("Track: value must be a number")
}
// ValidateTrackInputs implements the validation for Track call
func (i *inputValidation) ValidateTrackInputs(
key string,
trafficType string,
eventType string,
value interface{},
shouldValidateExistence bool,
) (string, string, string, interface{}, error) {
err := checkIsValidString(key, "key", "Track")
if err != nil {
return "", trafficType, eventType, value, err
}
err = checkEventType(eventType)
if err != nil {
return key, trafficType, "", value, err
}
trafficType, err = i.checkTrafficType(trafficType, shouldValidateExistence)
if err != nil {
return key, "", eventType, value, err
}
err = checkValue(value)
if err != nil {
return key, trafficType, eventType, nil, err
}
return key, trafficType, eventType, value, nil
}
// ValidateManagerInputs implements the validation for Track call
func (i *inputValidation) ValidateManagerInputs(feature string) error {
return checkIsEmptyString(feature, "split name", "Split")
}
// ValidateFeatureNames implements the validation for Treatments call
func (i *inputValidation) ValidateFeatureNames(features []string, operation string) ([]string, error) {
var featuresSet = set.NewSet()
if len(features) == 0 {
return []string{}, errors.New(operation + ": features must be a non-empty array")
}
for _, feature := range features {
f, err := i.ValidateFeatureName(feature, operation)
if err != nil {
i.logger.Error(err.Error())
} else {
featuresSet.Add(f)
}
}
if featuresSet.IsEmpty() {
return []string{}, errors.New(operation + ": features must be a non-empty array")
}
f := make([]string, featuresSet.Size())
for i, v := range featuresSet.List() {
s, ok := v.(string)
if ok {
f[i] = s
}
}
return f, nil
}
func (i *inputValidation) validateTrackProperties(properties map[string]interface{}) (map[string]interface{}, int, error) {
if len(properties) == 0 {
return nil, 0, nil
}
if len(properties) > 300 {
i.logger.Warning("Track: Event has more than 300 properties. Some of them will be trimmed when processed")
}
processed := make(map[string]interface{})
size := 1024 // Average event size is ~750 bytes. Using 1kbyte as a starting point.
for name, value := range properties {
size += len(name)
switch value.(type) {
case int, int32, int64, uint, uint32, uint64, float32, float64, bool, nil:
processed[name] = value
case string:
asStr := value.(string)
size += len(asStr)
processed[name] = value
default:
i.logger.Warning("Property %s is of invalid type. Setting value to nil")
processed[name] = nil
}
if size > MaxEventLength {
i.logger.Error(
"The maximum size allowed for the properties is 32kb. Event not queued",
)
return nil, size, errors.New("The maximum size allowed for the properties is 32kb. Event not queued")
}
}
return processed, size, nil
}
func (i *inputValidation) IsSplitFound(label string, feature string, operation string) bool {
if label == impressionlabels.SplitNotFound {
i.logger.Error(fmt.Sprintf(operation+": you passed %s that does not exist in this environment, please double check what Splits exist in the web console.", feature))
return false
}
return true
}

115
vendor/github.com/splitio/go-client/v6/splitio/client/manager.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,115 @@
package client
import (
"fmt"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-toolkit/v3/logging"
)
// SplitManager provides information of the currently stored splits
type SplitManager struct {
splitStorage storage.SplitStorageConsumer
validator inputValidation
logger logging.LoggerInterface
factory *SplitFactory
}
// SplitView is a partial representation of a currently stored split
type SplitView struct {
Name string `json:"name"`
TrafficType string `json:"trafficType"`
Killed bool `json:"killed"`
Treatments []string `json:"treatments"`
ChangeNumber int64 `json:"changeNumber"`
Configs map[string]string `json:"configs"`
}
func newSplitView(splitDto *dtos.SplitDTO) *SplitView {
treatments := make([]string, 0)
for _, condition := range splitDto.Conditions {
for _, partition := range condition.Partitions {
treatments = append(treatments, partition.Treatment)
}
}
return &SplitView{
ChangeNumber: splitDto.ChangeNumber,
Killed: splitDto.Killed,
Name: splitDto.Name,
TrafficType: splitDto.TrafficTypeName,
Treatments: treatments,
Configs: splitDto.Configurations,
}
}
// SplitNames returns a list with the name of all the currently stored splits
func (m *SplitManager) SplitNames() []string {
if m.isDestroyed() {
m.logger.Error("Client has already been destroyed - no calls possible")
return []string{}
}
if !m.isReady() {
m.logger.Warning("splitNames: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
}
return m.splitStorage.SplitNames()
}
// Splits returns a list of a partial view of every currently stored split
func (m *SplitManager) Splits() []SplitView {
if m.isDestroyed() {
m.logger.Error("Client has already been destroyed - no calls possible")
return []SplitView{}
}
if !m.isReady() {
m.logger.Warning("splits: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
}
splitViews := make([]SplitView, 0)
splits := m.splitStorage.All()
for _, split := range splits {
splitViews = append(splitViews, *newSplitView(&split))
}
return splitViews
}
// Split returns a partial view of a particular split
func (m *SplitManager) Split(feature string) *SplitView {
if m.isDestroyed() {
m.logger.Error("Client has already been destroyed - no calls possible")
return nil
}
if !m.isReady() {
m.logger.Warning("split: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method")
}
err := m.validator.ValidateManagerInputs(feature)
if err != nil {
m.logger.Error(err.Error())
return nil
}
split := m.splitStorage.Split(feature)
if split != nil {
return newSplitView(split)
}
m.logger.Error(fmt.Sprintf("Split: you passed %s that does not exist in this environment, please double check what Splits exist in the web console.", feature))
return nil
}
// BlockUntilReady Calls BlockUntilReady on factory to block manager on readiness
func (m *SplitManager) BlockUntilReady(timer int) error {
return m.factory.BlockUntilReady(timer)
}
func (m *SplitManager) isDestroyed() bool {
return m.factory.IsDestroyed()
}
func (m *SplitManager) isReady() bool {
return m.factory.IsReady()
}

22
vendor/github.com/splitio/go-client/v6/splitio/conf/defaults.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,22 @@
package conf
const (
defaultHTTPTimeout = 30
defaultTaskPeriod = 60
defaultRedisHost = "localhost"
defaultRedisPort = 6379
defaultRedisDb = 0
defaultSegmentQueueSize = 500
defaultSegmentWorkers = 10
defaultImpressionSyncOptimized = 300
defaultImpressionSyncDebug = 60
)
const (
minSplitSync = 5
minSegmentSync = 30
minImpressionSync = 1
minImpressionSyncOptimized = 60
minEventSync = 1
minTelemetrySync = 30
)

259
vendor/github.com/splitio/go-client/v6/splitio/conf/sdkconf.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,259 @@
// Package conf contains configuration structures used to setup the SDK
package conf
import (
"errors"
"fmt"
"math"
"os/user"
"path"
"strings"
impressionlistener "github.com/splitio/go-client/v6/splitio/impressionListener"
"github.com/splitio/go-split-commons/v2/conf"
"github.com/splitio/go-toolkit/v3/datastructures/set"
"github.com/splitio/go-toolkit/v3/logging"
"github.com/splitio/go-toolkit/v3/nethelpers"
)
const (
// RedisConsumer mode
RedisConsumer = "redis-consumer"
// Localhost mode
Localhost = "localhost"
// InMemoryStandAlone mode
InMemoryStandAlone = "inmemory-standalone"
)
// SplitSdkConfig struct ...
// struct used to setup a Split.io SDK client.
//
// Parameters:
// - OperationMode (Required) Must be one of ["inmemory-standalone", "redis-consumer"]
// - InstanceName (Optional) Name to be used when submitting metrics & impressions to split servers
// - IPAddress (Optional) Address to be used when submitting metrics & impressions to split servers
// - BlockUntilReady (Optional) How much to wait until the sdk is ready
// - SplitFile (Optional) File with splits to use when running in localhost mode
// - LabelsEnabled (Optional) Can be used to disable labels if the user does not want to send that info to split servers.
// - Logger: (Optional) Custom logger complying with logging.LoggerInterface
// - LoggerConfig: (Optional) Options to setup the sdk's own logger
// - TaskPeriods: (Optional) How often should each task run
// - Redis: (Required for "redis-consumer". Sets up Redis config
// - Advanced: (Optional) Sets up various advanced options for the sdk
// - ImpressionsMode (Optional) Flag for enabling local impressions dedupe - Possible values <'optimized'|'debug'>
type SplitSdkConfig struct {
OperationMode string
InstanceName string
IPAddress string
IPAddressesEnabled bool
BlockUntilReady int
SplitFile string
LabelsEnabled bool
SplitSyncProxyURL string
Logger logging.LoggerInterface
LoggerConfig logging.LoggerOptions
TaskPeriods TaskPeriods
Advanced AdvancedConfig
Redis conf.RedisConfig
ImpressionsMode string
}
// TaskPeriods struct is used to configure the period for each synchronization task
type TaskPeriods struct {
SplitSync int
SegmentSync int
ImpressionSync int
GaugeSync int
CounterSync int
LatencySync int
EventsSync int
}
// AdvancedConfig exposes more configurable parameters that can be used to further tailor the sdk to the user's needs
// - ImpressionListener - struct that will be notified each time an impression bulk is ready
// - HTTPTimeout - Timeout for HTTP requests when doing synchronization
// - SegmentQueueSize - How many segments can be queued for updating (should be >= # segments the user has)
// - SegmentWorkers - How many workers will be used when performing segments sync.
type AdvancedConfig struct {
ImpressionListener impressionlistener.ImpressionListener
HTTPTimeout int
SegmentQueueSize int
SegmentWorkers int
AuthServiceURL string
SdkURL string
EventsURL string
StreamingServiceURL string
EventsBulkSize int64
EventsQueueSize int
ImpressionsQueueSize int
ImpressionsBulkSize int64
StreamingEnabled bool
}
// Default returns a config struct with all the default values
func Default() *SplitSdkConfig {
instanceName := "unknown"
ipAddress, err := nethelpers.ExternalIP()
if err != nil {
ipAddress = "unknown"
} else {
instanceName = fmt.Sprintf("ip-%s", strings.Replace(ipAddress, ".", "-", -1))
}
var splitFile string
usr, err := user.Current()
if err != nil {
splitFile = "splits"
} else {
splitFile = path.Join(usr.HomeDir, ".splits")
}
return &SplitSdkConfig{
OperationMode: InMemoryStandAlone,
LabelsEnabled: true,
IPAddress: ipAddress,
IPAddressesEnabled: true,
InstanceName: instanceName,
Logger: nil,
LoggerConfig: logging.LoggerOptions{},
SplitFile: splitFile,
ImpressionsMode: conf.ImpressionsModeOptimized,
Redis: conf.RedisConfig{
Database: 0,
Host: "localhost",
Password: "",
Port: 6379,
Prefix: "",
},
TaskPeriods: TaskPeriods{
GaugeSync: defaultTaskPeriod,
CounterSync: defaultTaskPeriod,
LatencySync: defaultTaskPeriod,
ImpressionSync: defaultImpressionSyncOptimized,
SegmentSync: defaultTaskPeriod,
SplitSync: defaultTaskPeriod,
EventsSync: defaultTaskPeriod,
},
Advanced: AdvancedConfig{
AuthServiceURL: "",
EventsURL: "",
SdkURL: "",
StreamingServiceURL: "",
HTTPTimeout: 0,
ImpressionListener: nil,
SegmentQueueSize: 500,
SegmentWorkers: 10,
EventsBulkSize: 5000,
EventsQueueSize: 10000,
ImpressionsQueueSize: 10000,
ImpressionsBulkSize: 5000,
StreamingEnabled: true,
},
}
}
func checkImpressionSync(cfg *SplitSdkConfig) error {
if cfg.TaskPeriods.ImpressionSync == 0 {
cfg.TaskPeriods.ImpressionSync = defaultImpressionSyncOptimized
} else {
if cfg.TaskPeriods.ImpressionSync < minImpressionSyncOptimized {
return fmt.Errorf("ImpressionSync must be >= %d. Actual is: %d", minImpressionSyncOptimized, cfg.TaskPeriods.ImpressionSync)
}
cfg.TaskPeriods.ImpressionSync = int(math.Max(float64(minImpressionSyncOptimized), float64(cfg.TaskPeriods.ImpressionSync)))
}
return nil
}
func validConfigRates(cfg *SplitSdkConfig) error {
if cfg.OperationMode == RedisConsumer {
return nil
}
if cfg.TaskPeriods.SplitSync < minSplitSync {
return fmt.Errorf("SplitSync must be >= %d. Actual is: %d", minSplitSync, cfg.TaskPeriods.SplitSync)
}
if cfg.TaskPeriods.SegmentSync < minSegmentSync {
return fmt.Errorf("SegmentSync must be >= %d. Actual is: %d", minSegmentSync, cfg.TaskPeriods.SegmentSync)
}
cfg.ImpressionsMode = strings.ToLower(cfg.ImpressionsMode)
switch cfg.ImpressionsMode {
case conf.ImpressionsModeOptimized:
err := checkImpressionSync(cfg)
if err != nil {
return err
}
case conf.ImpressionsModeDebug:
if cfg.TaskPeriods.ImpressionSync == 0 {
cfg.TaskPeriods.ImpressionSync = defaultImpressionSyncDebug
} else {
if cfg.TaskPeriods.ImpressionSync < minImpressionSync {
return fmt.Errorf("ImpressionSync must be >= %d. Actual is: %d", minImpressionSync, cfg.TaskPeriods.ImpressionSync)
}
}
default:
fmt.Println(`You passed an invalid impressionsMode, impressionsMode should be one of the following values: 'debug' or 'optimized'. Defaulting to 'optimized' mode.`)
cfg.ImpressionsMode = conf.ImpressionsModeOptimized
err := checkImpressionSync(cfg)
if err != nil {
return err
}
}
if cfg.TaskPeriods.EventsSync < minEventSync {
return fmt.Errorf("EventsSync must be >= %d. Actual is: %d", minEventSync, cfg.TaskPeriods.EventsSync)
}
if cfg.TaskPeriods.LatencySync < minTelemetrySync {
return fmt.Errorf("LatencySync must be >= %d. Actual is: %d", minTelemetrySync, cfg.TaskPeriods.LatencySync)
}
if cfg.TaskPeriods.GaugeSync < minTelemetrySync {
return fmt.Errorf("GaugeSync must be >= %d. Actual is: %d", minTelemetrySync, cfg.TaskPeriods.GaugeSync)
}
if cfg.TaskPeriods.CounterSync < minTelemetrySync {
return fmt.Errorf("CounterSync must be >= %d. Actual is: %d", minTelemetrySync, cfg.TaskPeriods.CounterSync)
}
if cfg.Advanced.SegmentWorkers <= 0 {
return errors.New("Number of workers for fetching segments MUST be greater than zero")
}
return nil
}
// Normalize checks that the parameters passed by the user are correct and updates parameters if necessary.
// returns an error if something is wrong
func Normalize(apikey string, cfg *SplitSdkConfig) error {
// Fail if no apikey is provided
if apikey == "" && cfg.OperationMode != Localhost {
return errors.New("Factory instantiation: you passed an empty apikey, apikey must be a non-empty string")
}
// To keep the interface consistent with other sdks we accept "localhost" as an apikey,
// which sets the operation mode to localhost
if apikey == Localhost {
cfg.OperationMode = Localhost
}
// Fail if an invalid operation-mode is provided
operationModes := set.NewSet(
Localhost,
InMemoryStandAlone,
RedisConsumer,
)
if !operationModes.Has(cfg.OperationMode) {
return fmt.Errorf("OperationMode parameter must be one of: %v", operationModes.List())
}
if cfg.SplitSyncProxyURL != "" {
cfg.Advanced.AuthServiceURL = cfg.SplitSyncProxyURL
cfg.Advanced.SdkURL = cfg.SplitSyncProxyURL
cfg.Advanced.EventsURL = cfg.SplitSyncProxyURL
cfg.Advanced.StreamingServiceURL = cfg.SplitSyncProxyURL
}
if !cfg.IPAddressesEnabled {
cfg.IPAddress = "NA"
cfg.InstanceName = "NA"
}
return validConfigRates(cfg)
}

48
vendor/github.com/splitio/go-client/v6/splitio/conf/util.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,48 @@
package conf
import (
"strings"
"github.com/splitio/go-split-commons/v2/conf"
)
// NormalizeSDKConf compares against SDK Config to set defaults
func NormalizeSDKConf(sdkConfig AdvancedConfig) conf.AdvancedConfig {
config := conf.GetDefaultAdvancedConfig()
if sdkConfig.HTTPTimeout > 0 {
config.HTTPTimeout = sdkConfig.HTTPTimeout
}
if sdkConfig.EventsBulkSize > 0 {
config.EventsBulkSize = sdkConfig.EventsBulkSize
}
if sdkConfig.EventsQueueSize > 0 {
config.EventsQueueSize = sdkConfig.EventsQueueSize
}
if sdkConfig.ImpressionsBulkSize > 0 {
config.ImpressionsBulkSize = sdkConfig.ImpressionsBulkSize
}
if sdkConfig.ImpressionsQueueSize > 0 {
config.ImpressionsQueueSize = sdkConfig.ImpressionsQueueSize
}
if sdkConfig.SegmentQueueSize > 0 {
config.SegmentQueueSize = sdkConfig.SegmentQueueSize
}
if sdkConfig.SegmentWorkers > 0 {
config.SegmentWorkers = sdkConfig.SegmentWorkers
}
if strings.TrimSpace(sdkConfig.EventsURL) != "" {
config.EventsURL = sdkConfig.EventsURL
}
if strings.TrimSpace(sdkConfig.SdkURL) != "" {
config.SdkURL = sdkConfig.SdkURL
}
if strings.TrimSpace(sdkConfig.AuthServiceURL) != "" {
config.AuthServiceURL = sdkConfig.AuthServiceURL
}
if strings.TrimSpace(sdkConfig.StreamingServiceURL) != "" {
config.StreamingServiceURL = sdkConfig.StreamingServiceURL
}
config.StreamingEnabled = sdkConfig.StreamingEnabled
return config
}

70
vendor/github.com/splitio/go-client/v6/splitio/engine/engine.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,70 @@
package engine
import (
"fmt"
"math"
"github.com/splitio/go-client/v6/splitio/engine/evaluator/impressionlabels"
"github.com/splitio/go-client/v6/splitio/engine/grammar"
"github.com/splitio/go-client/v6/splitio/engine/hash"
"github.com/splitio/go-toolkit/v3/logging"
)
// Engine struct is responsible for cheking if any of the conditions of the split matches,
// performing traffic allocation, calculating the bucket and returning the appropriate treatment
type Engine struct {
logger logging.LoggerInterface
}
// DoEvaluation performs the main evaluation against each condition
func (e *Engine) DoEvaluation(
split *grammar.Split,
key string,
bucketingKey string,
attributes map[string]interface{},
) (*string, string) {
inRollOut := false
for _, condition := range split.Conditions() {
if !inRollOut && condition.ConditionType() == grammar.ConditionTypeRollout {
if split.TrafficAllocation() < 100 {
bucket := e.calculateBucket(split.Algo(), bucketingKey, split.TrafficAllocationSeed())
if bucket > split.TrafficAllocation() {
e.logger.Debug(fmt.Sprintf(
"Traffic allocation exceeded for feature %s and key %s."+
" Returning default treatment", split.Name(), key,
))
defaultTreatment := split.DefaultTreatment()
return &defaultTreatment, impressionlabels.NotInSplit
}
inRollOut = true
}
}
if condition.Matches(key, &bucketingKey, attributes) {
bucket := e.calculateBucket(split.Algo(), bucketingKey, split.Seed())
treatment := condition.CalculateTreatment(bucket)
return treatment, condition.Label()
}
}
return nil, impressionlabels.NoConditionMatched
}
func (e *Engine) calculateBucket(algo int, bucketingKey string, seed int64) int {
var hashedKey uint32
switch algo {
case grammar.SplitAlgoMurmur:
hashedKey = hash.Murmur3_32([]byte(bucketingKey), uint32(seed))
case grammar.SplitAlgoLegacy:
fallthrough
default:
hashedKey = hash.Legacy([]byte(bucketingKey), uint32(seed))
}
return int(math.Abs(float64(hashedKey%100)) + 1)
}
// NewEngine instantiates and returns a new engine
func NewEngine(logger logging.LoggerInterface) *Engine {
return &Engine{logger: logger}
}

160
vendor/github.com/splitio/go-client/v6/splitio/engine/evaluator/evaluator.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,160 @@
package evaluator
import (
"fmt"
"time"
"github.com/splitio/go-client/v6/splitio/engine"
"github.com/splitio/go-client/v6/splitio/engine/evaluator/impressionlabels"
"github.com/splitio/go-client/v6/splitio/engine/grammar"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-split-commons/v2/storage"
"github.com/splitio/go-toolkit/v3/injection"
"github.com/splitio/go-toolkit/v3/logging"
)
const (
// Control is the treatment returned when something goes wrong
Control = "control"
)
// Result represents the result of an evaluation, including the resulting treatment, the label for the impression,
// the latency and error if any
type Result struct {
Treatment string
Label string
EvaluationTimeNs int64
SplitChangeNumber int64
Config *string
}
// Results represents the result of multiple evaluations at once
type Results struct {
Evaluations map[string]Result
EvaluationTimeNs int64
}
// Evaluator struct is the main evaluator
type Evaluator struct {
splitStorage storage.SplitStorageConsumer
segmentStorage storage.SegmentStorageConsumer
eng *engine.Engine
logger logging.LoggerInterface
}
// NewEvaluator instantiates an Evaluator struct and returns a reference to it
func NewEvaluator(
splitStorage storage.SplitStorageConsumer,
segmentStorage storage.SegmentStorageConsumer,
eng *engine.Engine,
logger logging.LoggerInterface,
) *Evaluator {
return &Evaluator{
splitStorage: splitStorage,
segmentStorage: segmentStorage,
eng: eng,
logger: logger,
}
}
func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, feature string, splitDto *dtos.SplitDTO, attributes map[string]interface{}) *Result {
var config *string
if splitDto == nil {
e.logger.Warning(fmt.Sprintf("Feature %s not found, returning control.", feature))
return &Result{Treatment: Control, Label: impressionlabels.SplitNotFound, Config: config}
}
ctx := injection.NewContext()
ctx.AddDependency("segmentStorage", e.segmentStorage)
ctx.AddDependency("evaluator", e)
split := grammar.NewSplit(splitDto, ctx, e.logger)
if split.Killed() {
e.logger.Warning(fmt.Sprintf(
"Feature %s has been killed, returning default treatment: %s",
feature,
split.DefaultTreatment(),
))
if _, ok := split.Configurations()[split.DefaultTreatment()]; ok {
treatmentConfig := split.Configurations()[split.DefaultTreatment()]
config = &treatmentConfig
}
return &Result{
Treatment: split.DefaultTreatment(),
Label: impressionlabels.Killed,
SplitChangeNumber: split.ChangeNumber(),
Config: config,
}
}
treatment, label := e.eng.DoEvaluation(split, key, bucketingKey, attributes)
if treatment == nil {
e.logger.Warning(fmt.Sprintf(
"No condition matched, returning default treatment: %s",
split.DefaultTreatment(),
))
defaultTreatment := split.DefaultTreatment()
treatment = &defaultTreatment
label = impressionlabels.NoConditionMatched
}
if _, ok := split.Configurations()[*treatment]; ok {
treatmentConfig := split.Configurations()[*treatment]
config = &treatmentConfig
}
return &Result{
Treatment: *treatment,
Label: label,
SplitChangeNumber: split.ChangeNumber(),
Config: config,
}
}
// EvaluateFeature returns a struct with the resulting treatment and extra information for the impression
func (e *Evaluator) EvaluateFeature(key string, bucketingKey *string, feature string, attributes map[string]interface{}) *Result {
before := time.Now()
splitDto := e.splitStorage.Split(feature)
if bucketingKey == nil {
bucketingKey = &key
}
result := e.evaluateTreatment(key, *bucketingKey, feature, splitDto, attributes)
after := time.Now()
result.EvaluationTimeNs = after.Sub(before).Nanoseconds()
return result
}
// EvaluateFeatures returns a struct with the resulting treatment and extra information for the impression
func (e *Evaluator) EvaluateFeatures(key string, bucketingKey *string, features []string, attributes map[string]interface{}) Results {
var results = Results{
Evaluations: make(map[string]Result),
EvaluationTimeNs: 0,
}
before := time.Now()
splits := e.splitStorage.FetchMany(features)
if bucketingKey == nil {
bucketingKey = &key
}
for _, feature := range features {
results.Evaluations[feature] = *e.evaluateTreatment(key, *bucketingKey, feature, splits[feature], attributes)
}
after := time.Now()
results.EvaluationTimeNs = after.Sub(before).Nanoseconds()
return results
}
// EvaluateDependency SHOULD ONLY BE USED by DependencyMatcher.
// It's used to break the dependency cycle between matchers and evaluators.
func (e *Evaluator) EvaluateDependency(key string, bucketingKey *string, feature string, attributes map[string]interface{}) string {
res := e.EvaluateFeature(key, bucketingKey, feature, attributes)
return res.Treatment
}

22
vendor/github.com/splitio/go-client/v6/splitio/engine/evaluator/impressionlabels/impression_labels.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,22 @@
package impressionlabels
// SplitNotFound label will be returned when the split requested is not present in storage
const SplitNotFound = "definition not found"
// Killed label will be returned when the split requested has been killed
const Killed = "killed"
// NoConditionMatched label will be returned when no condition of the split has matched
const NoConditionMatched = "default rule"
// MatcherNotFound label will be returned when matchertype is unknown
const MatcherNotFound = "matcher not found"
// NotInSplit label will be returned when traffic allocation fails
const NotInSplit = "not in split"
// Exception label will be returned if something goes wrong during the split evaluation
const Exception = "exception"
// ClientNotReady label will be returned when the client is not ready
const ClientNotReady = "not ready"

7
vendor/github.com/splitio/go-client/v6/splitio/engine/evaluator/interface.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,7 @@
package evaluator
// Interface should be implemented by concrete treatment evaluator structs
type Interface interface {
EvaluateFeature(key string, bucketingKey *string, feature string, attributes map[string]interface{}) *Result
EvaluateFeatures(key string, bucketingKey *string, features []string, attributes map[string]interface{}) Results
}

99
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/condition.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,99 @@
package grammar
import (
"github.com/splitio/go-client/v6/splitio/engine/grammar/matchers"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/injection"
"github.com/splitio/go-toolkit/v3/logging"
)
// Condition struct with added logic that wraps around a DTO
type Condition struct {
matchers []matchers.MatcherInterface
combiner string
partitions []Partition
label string
conditionType string
}
// NewCondition instantiates a new Condition struct with appropriate wrappers around dtos and returns it.
func NewCondition(cond *dtos.ConditionDTO, ctx *injection.Context, logger logging.LoggerInterface) *Condition {
partitions := make([]Partition, 0)
for _, part := range cond.Partitions {
partitions = append(partitions, Partition{partitionData: part})
}
matcherObjs := make([]matchers.MatcherInterface, 0)
for _, matcher := range cond.MatcherGroup.Matchers {
m, err := matchers.BuildMatcher(&matcher, ctx, logger)
if err == nil {
matcherObjs = append(matcherObjs, m)
}
}
return &Condition{
combiner: cond.MatcherGroup.Combiner,
matchers: matcherObjs,
partitions: partitions,
label: cond.Label,
conditionType: cond.ConditionType,
}
}
// Partition struct with added logic that wraps around a DTO
type Partition struct {
partitionData dtos.PartitionDTO
}
// ConditionType returns validated condition type. Whitelist by default
func (c *Condition) ConditionType() string {
switch c.conditionType {
case ConditionTypeRollout:
return ConditionTypeRollout
case ConditionTypeWhitelist:
return ConditionTypeWhitelist
default:
return ConditionTypeWhitelist
}
}
// Label returns the condition's label
func (c *Condition) Label() string {
return c.label
}
// Matches returns true if the condition matches for a specific key and/or set of attributes
func (c *Condition) Matches(key string, bucketingKey *string, attributes map[string]interface{}) bool {
partial := make([]bool, len(c.matchers))
for i, matcher := range c.matchers {
partial[i] = matcher.Match(key, attributes, bucketingKey)
if matcher.Negate() {
partial[i] = !partial[i]
}
}
return applyCombiner(partial, c.combiner)
}
// CalculateTreatment calulates the treatment for a specific condition based on the bucket
func (c *Condition) CalculateTreatment(bucket int) *string {
accum := 0
for _, partition := range c.partitions {
accum += partition.partitionData.Size
if bucket <= accum {
return &partition.partitionData.Treatment
}
}
return nil
}
func applyCombiner(results []bool, combiner string) bool {
temp := true
switch combiner {
case "AND":
for _, result := range results {
temp = temp && result
}
default:
return false
}
return temp
}

22
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/constants.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,22 @@
package grammar
const (
// SplitStatusActive represents an active split
SplitStatusActive = "ACTIVE"
// SplitStatusArchived represents an archived split
SplitStatusArchived = "ARCHIVED"
// SplitAlgoLegacy represents the legacy implementation of hash function for bucketing
SplitAlgoLegacy = 1
// SplitAlgoMurmur represents the murmur implementation of the hash funcion for bucketing
SplitAlgoMurmur = 2
// ConditionTypeWhitelist represents a normal condition
ConditionTypeWhitelist = "WHITELIST"
// ConditionTypeRollout represents a condition that will return default if traffic allocatio is exceeded
ConditionTypeRollout = "ROLLOUT"
// MatcherCombinerAnd represents that all matchers in the group are required
MatcherCombinerAnd = 0
)

16
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/allkeys.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,16 @@
package matchers
// AllKeysMatcher matches any given key and set of attributes
type AllKeysMatcher struct {
Matcher
}
// Match implementation for AllKeysMatcher
func (m AllKeysMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
return true
}
// NewAllKeysMatcher returns a pointer to a new instance of AllKeysMatcher
func NewAllKeysMatcher(negate bool) *AllKeysMatcher {
return &AllKeysMatcher{Matcher: Matcher{negate: negate}}
}

56
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/allofset.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,56 @@
package matchers
import (
"fmt"
"github.com/splitio/go-toolkit/v3/datastructures/set"
"reflect"
)
// ContainsAllOfSetMatcher matches if the set supplied to the getTreatment is a superset of the one in the split
type ContainsAllOfSetMatcher struct {
Matcher
comparisonSet *set.ThreadUnsafeSet
}
// Match returns true if the set provided is a superset of the one in the split
func (m *ContainsAllOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("AllOfSetMatcher: ", err)
return false
}
conv, ok := matchingKey.([]string)
if !ok {
m.logger.Error(
"AllOfSetMatcher: Attribute passed is not a slice of strings. ",
fmt.Sprintf("Key is of type %s\n", reflect.TypeOf(matchingKey).String()),
)
return false
}
matchingSet := set.NewSet()
for _, x := range conv {
matchingSet.Add(x)
}
res := m.comparisonSet.IsSuperset(matchingSet)
return res
}
// NewContainsAllOfSetMatcher returns a pointer to a new instance of ContainsAllOfSetMatcher
func NewContainsAllOfSetMatcher(negate bool, setItems []string, attributeName *string) *ContainsAllOfSetMatcher {
setObj := set.NewSet()
for _, item := range setItems {
setObj.Add(item)
}
return &ContainsAllOfSetMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
comparisonSet: setObj,
}
}

51
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/anyofset.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,51 @@
package matchers
import (
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// ContainsAnyOfSetMatcher matches if the set supplied to the getTreatment is a superset of the one in the split
type ContainsAnyOfSetMatcher struct {
Matcher
comparisonSet *set.ThreadUnsafeSet
}
// Match returns true if the set provided is a superset of the one in the split
func (m *ContainsAnyOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("AnyOfSetMatcher: ", err)
return false
}
conv, ok := matchingKey.([]string)
if !ok {
m.logger.Error("AnyOfSetMatcher: Failed to parse the key as a []string")
return false
}
matchingSet := set.NewSet()
for _, x := range conv {
matchingSet.Add(x)
}
intersection := set.Intersection(matchingSet, m.comparisonSet)
return intersection.Size() > 0
}
// NewContainsAnyOfSetMatcher returns a pointer to a new instance of ContainsAnyOfSetMatcher
func NewContainsAnyOfSetMatcher(negate bool, setItems []string, attributeName *string) *ContainsAnyOfSetMatcher {
setObj := set.NewSet()
for _, item := range setItems {
setObj.Add(item)
}
return &ContainsAnyOfSetMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
comparisonSet: setObj,
}
}

70
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/between.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,70 @@
package matchers
import (
"fmt"
"github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/datatypes"
"reflect"
)
// BetweenMatcher will match if two numbers or two datetimes are equal
type BetweenMatcher struct {
Matcher
ComparisonDataType string
LowerComparisonValue int64
UpperComparisonValue int64
}
// Match will match if the matchingValue is between lowerComparisonValue and upperComparisonValue
func (m *BetweenMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingRaw, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("BetweenMatcher: Could not retrieve matching key. ", err)
return false
}
var matchingValue int64
matchingValue, okMatching := matchingRaw.(int64)
if !okMatching {
var asInt int
asInt, okMatching = matchingRaw.(int)
if okMatching {
matchingValue = int64(asInt)
}
}
if !okMatching {
m.logger.Error(
"BetweenMatcher: Could not parse attribute to an int. ",
fmt.Sprintf("Attribute is of type %s\n", reflect.TypeOf(matchingRaw).String()),
)
return false
}
var comparisonLower int64
var comparisonUpper int64
switch m.ComparisonDataType {
case datatypes.Number:
comparisonLower = m.LowerComparisonValue
comparisonUpper = m.UpperComparisonValue
case datatypes.Datetime:
matchingValue = datatypes.ZeroSecondsTS(matchingValue)
comparisonLower = datatypes.ZeroSecondsTS(datatypes.TsFromJava(m.LowerComparisonValue))
comparisonUpper = datatypes.ZeroSecondsTS(datatypes.TsFromJava(m.UpperComparisonValue))
default:
m.base().logger.Error(fmt.Sprintf("BetweenMatcher: Incorrect type %s", m.ComparisonDataType))
return false
}
return matchingValue >= comparisonLower && matchingValue <= comparisonUpper
}
// NewBetweenMatcher returns a pointer to a new instance of BetweenMatcher
func NewBetweenMatcher(negate bool, lower int64, upper int64, cmpType string, attributeName *string) *BetweenMatcher {
return &BetweenMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
LowerComparisonValue: lower,
UpperComparisonValue: upper,
ComparisonDataType: cmpType,
}
}

60
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/boolean.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,60 @@
package matchers
import (
"reflect"
"strconv"
"strings"
)
// BooleanMatcher returns true if the value supplied can be interpreted as a boolean and is equal to the one stored
type BooleanMatcher struct {
Matcher
value *bool
}
// Match returns true if the value supplied can be interpreted as a boolean and is equal to the one stored
func (m *BooleanMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("BooleanMatcher: Couldn't parse matching key to a boolean")
return false
}
var asBool bool
var ok bool
switch reflect.TypeOf(matchingKey).Kind() {
case reflect.String:
asStr, ok := matchingKey.(string)
if !ok {
m.logger.Error("BooleanMatcher: Couldn't type-assert string")
return false
}
asBool, err = strconv.ParseBool(strings.ToLower(asStr))
if err != nil {
m.logger.Error("BooleanMatcher: Couldn't parse boolean from string")
return false
}
case reflect.Bool:
asBool, ok = matchingKey.(bool)
if !ok {
m.logger.Error("BooleanMatcher: Couldn't type assert boolean")
return false
}
default:
m.logger.Error("BooleanMatcher: Incompatible type: ", reflect.TypeOf(matchingKey).String())
return false
}
return m.value != nil && *m.value == asBool
}
// NewBooleanMatcher instantiates a new BooleanMatcher
func NewBooleanMatcher(negate bool, value *bool, attributeName *string) *BooleanMatcher {
return &BooleanMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
value: value,
}
}

45
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/contains.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,45 @@
package matchers
import (
"strings"
)
// ContainsStringMatcher matches strings contain one of the substrings in the split
type ContainsStringMatcher struct {
Matcher
substrings []string
}
// Match returns true if the key contains one of the substrings in the split
func (m *ContainsStringMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("ContainsAllOfSetMatcher: Error retrieving matching key")
return false
}
asString, ok := matchingKey.(string)
if !ok {
m.logger.Error("ContainsAllOfSetMatcher: Failed to type-assert string")
return false
}
for _, substring := range m.substrings {
if strings.Contains(asString, substring) {
return true
}
}
return false
}
// NewContainsStringMatcher returns a new instance of ContainsStringMatcher
func NewContainsStringMatcher(negate bool, substrings []string, attributeName *string) *ContainsStringMatcher {
return &ContainsStringMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
substrings: substrings,
}
}

33
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/datatypes/datatypes.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,33 @@
package datatypes
import (
"time"
)
const (
// Number data type
Number = "NUMBER"
// Datetime data type
Datetime = "DATETIME"
)
// TsFromJava converts a java timestamp to standard unix format
func TsFromJava(ts int64) int64 {
return ts / 1000
}
// ZeroTimeTS Takes a timestamp in milliseconds as a parameter and
// returns another timestamp in seconds with the same date and zero time.
func ZeroTimeTS(ts int64) int64 {
t := time.Unix(ts, 0).UTC() // Timestamp is converted from milliseconds to seconds
rounded := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)
return rounded.Unix()
}
// ZeroSecondsTS Takes a timestamp in milliseconds as a parameter and
// returns another timestamp in seconds with the same date & time but zero seconds.
func ZeroSecondsTS(ts int64) int64 {
t := time.Unix(ts, 0).UTC() // Timestamp is converted from milliseconds to seconds
rounded := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 0, 0, time.UTC)
return rounded.Unix()
}

43
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/dependency.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,43 @@
package matchers
type dependencyEvaluator interface {
EvaluateDependency(key string, bucketingKey *string, feature string, attributes map[string]interface{}) string
}
// DependencyMatcher will match if the evaluation of another split results in one of the treatments defined
// in the split
type DependencyMatcher struct {
Matcher
feature string
treatments []string
}
// Match will return true if the evaluation of another split results in one of the treatments defined in the
// split
func (m *DependencyMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
evaluator, ok := m.Context.Dependency("evaluator").(dependencyEvaluator)
if !ok {
m.logger.Error("DependencyMatcher: Error retrieving matching key")
return false
}
result := evaluator.EvaluateDependency(key, bucketingKey, m.feature, attributes)
for _, treatment := range m.treatments {
if treatment == result {
return true
}
}
return false
}
// NewDependencyMatcher will return a new instance of DependencyMatcher
func NewDependencyMatcher(negate bool, feature string, treatments []string) *DependencyMatcher {
return &DependencyMatcher{
Matcher: Matcher{
negate: negate,
},
feature: feature,
treatments: treatments,
}
}

45
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/endswith.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,45 @@
package matchers
import (
"strings"
)
// EndsWithMatcher matches strings which end with one of the suffixes in the split
type EndsWithMatcher struct {
Matcher
suffixes []string
}
// Match returns true if the key provided ends with one of the suffixes in the split.
func (m *EndsWithMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("EndsWithMatcher: ", err)
return false
}
asString, ok := matchingKey.(string)
if !ok {
m.logger.Error("EndsWithMatcher: Error type-asserting string")
return false
}
for _, suffix := range m.suffixes {
if strings.HasSuffix(asString, suffix) {
return true
}
}
return false
}
// NewEndsWithMatcher returns a new instance of EndsWithMatcher
func NewEndsWithMatcher(negate bool, suffixes []string, attributeName *string) *EndsWithMatcher {
return &EndsWithMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
suffixes: suffixes,
}
}

65
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/equalto.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,65 @@
package matchers
import (
"fmt"
"github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/datatypes"
"reflect"
)
// EqualToMatcher will match if two numbers or two datetimes are equal
type EqualToMatcher struct {
Matcher
ComparisonDataType string
ComparisonValue int64
}
// Match will match if the comparisonValue is equal to the matchingValue
func (m *EqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingRaw, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("EqualToMatcher: ", err)
return false
}
matchingValue, ok := matchingRaw.(int64)
if !ok {
var asInt int
asInt, ok = matchingRaw.(int)
if ok {
matchingValue = int64(asInt)
}
}
if !ok {
m.base().logger.Error(
"EqualToMatcher: Error type-asserting matching key to an int",
fmt.Sprintf("%s is a %s\n", matchingRaw, reflect.TypeOf(matchingRaw).String()),
)
return false
}
var comparisonValue int64
switch m.ComparisonDataType {
case datatypes.Number:
comparisonValue = m.ComparisonValue
case datatypes.Datetime:
matchingValue = datatypes.ZeroTimeTS(matchingValue)
comparisonValue = datatypes.ZeroTimeTS(datatypes.TsFromJava(m.ComparisonValue))
default:
m.logger.Error(fmt.Sprintf("EqualToMatcher: Invalid comparison type %s\n", m.ComparisonDataType))
return false
}
return matchingValue == comparisonValue
}
// NewEqualToMatcher returns a pointer to a new instance of EqualToMatcher
func NewEqualToMatcher(negate bool, cmpVal int64, cmpType string, attributeName *string) *EqualToMatcher {
return &EqualToMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
ComparisonValue: cmpVal,
ComparisonDataType: cmpType,
}
}

50
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/equaltoset.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,50 @@
package matchers
import (
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// EqualToSetMatcher matches if the set supplied to the getTreatment is equal to the one in the split
type EqualToSetMatcher struct {
Matcher
comparisonSet *set.ThreadUnsafeSet
}
// Match returns true if the match provided and the one in the split are equal
func (m *EqualToSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("EqualToSetMatcher: ", err)
return false
}
conv, ok := matchingKey.([]string)
if !ok {
m.logger.Error("EqualToSetMatcher: Cannot type assert to []string")
return false
}
matchingSet := set.NewSet()
for _, x := range conv {
matchingSet.Add(x)
}
return matchingSet.IsEqual(m.comparisonSet)
}
// NewEqualToSetMatcher returns a pointer to a new instance of EqualToSetMatcher
func NewEqualToSetMatcher(negate bool, setItems []string, attributeName *string) *EqualToSetMatcher {
setObj := set.NewSet()
for _, item := range setItems {
setObj.Add(item)
}
return &EqualToSetMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
comparisonSet: setObj,
}
}

59
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/gtoet.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,59 @@
package matchers
import (
"github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/datatypes"
)
// GreaterThanOrEqualToMatcher will match if two numbers or two datetimes are equal
type GreaterThanOrEqualToMatcher struct {
Matcher
ComparisonDataType string
ComparisonValue int64
}
// Match will match if the comparisonValue is greater than or equal to the matchingValue
func (m *GreaterThanOrEqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingRaw, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("GreaterThanOrEqualToMatcher: ", err)
return false
}
matchingValue, ok := matchingRaw.(int64)
if !ok {
var asInt int
asInt, ok = matchingRaw.(int)
if ok {
matchingValue = int64(asInt)
}
}
if !ok {
m.logger.Error("GreaterThanOrEqualToMatcher: Cannot type-assert key matching key to int")
return false
}
var comparisonValue int64
switch m.ComparisonDataType {
case datatypes.Number:
comparisonValue = m.ComparisonValue
case datatypes.Datetime:
matchingValue = datatypes.ZeroSecondsTS(matchingValue)
comparisonValue = datatypes.ZeroSecondsTS(datatypes.TsFromJava(m.ComparisonValue))
default:
m.logger.Error("GreaterThanOrEqualToMatcher: Incorrect attribute type")
return false
}
return matchingValue >= comparisonValue
}
// NewGreaterThanOrEqualToMatcher returns a pointer to a new instance of GreaterThanOrEqualToMatcher
func NewGreaterThanOrEqualToMatcher(negate bool, cmpVal int64, cmpType string, attributeName *string) *GreaterThanOrEqualToMatcher {
return &GreaterThanOrEqualToMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
ComparisonValue: cmpVal,
ComparisonDataType: cmpType,
}
}

39
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/insegment.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,39 @@
package matchers
import (
"fmt"
"github.com/splitio/go-split-commons/v2/storage"
)
// InSegmentMatcher matches if the key passed is in the segment which the matcher was constructed with
type InSegmentMatcher struct {
Matcher
segmentName string
}
// Match returns true if the key is in the matcher's segment
func (m *InSegmentMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
segmentStorage, ok := m.Context.Dependency("segmentStorage").(storage.SegmentStorageConsumer)
if !ok {
m.logger.Error("InSegmentMatcher: Unable to retrieve segment storage!")
return false
}
isInSegment, err := segmentStorage.SegmentContainsKey(m.segmentName, key)
if err != nil {
m.logger.Error(fmt.Printf("InSegmentMatcher: Segment %s not found", m.segmentName))
}
return isInSegment
}
// NewInSegmentMatcher instantiates a new InSegmentMatcher
func NewInSegmentMatcher(negate bool, segmentName string, attributeName *string) *InSegmentMatcher {
return &InSegmentMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
segmentName: segmentName,
}
}

61
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/ltoet.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,61 @@
package matchers
import (
"github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/datatypes"
)
// LessThanOrEqualToMatcher will match if two numbers or two datetimes are equal
type LessThanOrEqualToMatcher struct {
Matcher
ComparisonDataType string
ComparisonValue int64
}
// Match will match if the comparisonValue is less than or equal to the matchingValue
func (m *LessThanOrEqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingRaw, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("LessThanOrEqualToMatcher: ", err)
return false
}
matchingValue, ok := matchingRaw.(int64)
if !ok {
var asInt int
asInt, ok = matchingRaw.(int)
if ok {
matchingValue = int64(asInt)
}
}
if !ok {
m.logger.Error("LessThanOrEqualToMatcher: Unable to type-assert key to int")
return false
}
var comparisonValue int64
switch m.ComparisonDataType {
case datatypes.Number:
comparisonValue = m.ComparisonValue
case datatypes.Datetime:
matchingValue = datatypes.ZeroSecondsTS(matchingValue)
comparisonValue = datatypes.ZeroSecondsTS(datatypes.TsFromJava(m.ComparisonValue))
default:
m.logger.Error("LessThanOrEqualToMatcher: Incorrect data type")
return false
}
return matchingValue <= comparisonValue
}
// NewLessThanOrEqualToMatcher returns a pointer to a new instance of LessThanOrEqualToMatcher
func NewLessThanOrEqualToMatcher(negate bool, cmpVal int64, cmpType string, attributeName *string) *LessThanOrEqualToMatcher {
return &LessThanOrEqualToMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
ComparisonValue: cmpVal,
ComparisonDataType: cmpType,
}
}

350
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/matchers.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,350 @@
package matchers
import (
"errors"
"fmt"
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/injection"
"github.com/splitio/go-toolkit/v3/logging"
)
const (
// MatcherTypeAllKeys string value
MatcherTypeAllKeys = "ALL_KEYS"
// MatcherTypeInSegment string value
MatcherTypeInSegment = "IN_SEGMENT"
// MatcherTypeWhitelist string value
MatcherTypeWhitelist = "WHITELIST"
// MatcherTypeEqualTo string value
MatcherTypeEqualTo = "EQUAL_TO"
// MatcherTypeGreaterThanOrEqualTo string value
MatcherTypeGreaterThanOrEqualTo = "GREATER_THAN_OR_EQUAL_TO"
// MatcherTypeLessThanOrEqualTo string value
MatcherTypeLessThanOrEqualTo = "LESS_THAN_OR_EQUAL_TO"
// MatcherTypeBetween string value
MatcherTypeBetween = "BETWEEN"
// MatcherTypeEqualToSet string value
MatcherTypeEqualToSet = "EQUAL_TO_SET"
// MatcherTypePartOfSet string value
MatcherTypePartOfSet = "PART_OF_SET"
// MatcherTypeContainsAllOfSet string value
MatcherTypeContainsAllOfSet = "CONTAINS_ALL_OF_SET"
// MatcherTypeContainsAnyOfSet string value
MatcherTypeContainsAnyOfSet = "CONTAINS_ANY_OF_SET"
// MatcherTypeStartsWith string value
MatcherTypeStartsWith = "STARTS_WITH"
// MatcherTypeEndsWith string value
MatcherTypeEndsWith = "ENDS_WITH"
// MatcherTypeContainsString string value
MatcherTypeContainsString = "CONTAINS_STRING"
// MatcherTypeInSplitTreatment string value
MatcherTypeInSplitTreatment = "IN_SPLIT_TREATMENT"
// MatcherTypeEqualToBoolean string value
MatcherTypeEqualToBoolean = "EQUAL_TO_BOOLEAN"
// MatcherTypeMatchesString string value
MatcherTypeMatchesString = "MATCHES_STRING"
)
// MatcherInterface should be implemented by all matchers
type MatcherInterface interface {
Match(key string, attributes map[string]interface{}, bucketingKey *string) bool
Negate() bool
base() *Matcher // This method is used to return the embedded matcher when iterating over interfaces
matchingKey(key string, attributes map[string]interface{}) (interface{}, error)
}
// Matcher struct with added logic that wraps around a DTO
type Matcher struct {
*injection.Context
negate bool
attributeName *string
logger logging.LoggerInterface
}
// Negate returns whether this mather is negated or not
func (m *Matcher) Negate() bool {
return m.negate
}
func (m *Matcher) matchingKey(key string, attributes map[string]interface{}) (interface{}, error) {
if m.attributeName == nil {
return key, nil
}
// Reaching this point means WE NEED attributes
if attributes == nil {
return nil, errors.New("Attribute required but no attributes provided")
}
attrValue, found := attributes[*m.attributeName]
if !found {
return nil, fmt.Errorf(
"Attribute \"%s\" required but not present in provided attribute map",
*m.attributeName,
)
}
return attrValue, nil
}
// matcher returns the matcher instance embbeded in structs
func (m *Matcher) base() *Matcher {
return m
}
// BuildMatcher constructs the appropriate matcher based on the MatcherType attribute of the dto
func BuildMatcher(dto *dtos.MatcherDTO, ctx *injection.Context, logger logging.LoggerInterface) (MatcherInterface, error) {
var matcher MatcherInterface
var attributeName *string
if dto.KeySelector != nil {
attributeName = dto.KeySelector.Attribute
}
switch dto.MatcherType {
case MatcherTypeAllKeys:
logger.Debug(fmt.Sprintf("Building AllKeysMatcher with negate=%t", dto.Negate))
matcher = NewAllKeysMatcher(dto.Negate)
case MatcherTypeEqualTo:
if dto.UnaryNumeric == nil {
return nil, errors.New("UnaryNumeric is required for EQUAL_TO matcher type")
}
logger.Debug(fmt.Sprintf(
"Building EqualToMatcher with negate=%t, value=%d, type=%s, attributeName=%v",
dto.Negate, dto.UnaryNumeric.Value, dto.UnaryNumeric.DataType, attributeName,
))
matcher = NewEqualToMatcher(
dto.Negate,
dto.UnaryNumeric.Value,
dto.UnaryNumeric.DataType,
attributeName,
)
case MatcherTypeInSegment:
if dto.UserDefinedSegment == nil {
return nil, errors.New("UserDefinedSegment is required for IN_SEGMENT matcher type")
}
logger.Debug(fmt.Sprintf(
"Building InSegmentMatcher with negate=%t, segmentName=%s, attributeName=%v",
dto.Negate, dto.UserDefinedSegment.SegmentName, attributeName,
))
matcher = NewInSegmentMatcher(
dto.Negate,
dto.UserDefinedSegment.SegmentName,
attributeName,
)
case MatcherTypeWhitelist:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for WHITELIST matcher type")
}
logger.Debug(fmt.Sprintf(
"Building WhitelistMatcher with negate=%t, whitelist=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewWhitelistMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeGreaterThanOrEqualTo:
if dto.UnaryNumeric == nil {
return nil, errors.New("UnaryNumeric is required for GREATER_THAN_OR_EQUAL_TO matcher type")
}
logger.Debug(fmt.Sprintf(
"Building GreaterThanOrEqualToMatcher with negate=%t, value=%d, type=%s, attributeName=%v",
dto.Negate, dto.UnaryNumeric.Value, dto.UnaryNumeric.DataType, attributeName,
))
matcher = NewGreaterThanOrEqualToMatcher(
dto.Negate,
dto.UnaryNumeric.Value,
dto.UnaryNumeric.DataType,
attributeName,
)
case MatcherTypeLessThanOrEqualTo:
if dto.UnaryNumeric == nil {
return nil, errors.New("UnaryNumeric is required for LESS_THAN_OR_EQUAL_TO matcher type")
}
logger.Debug(fmt.Sprintf(
"Building LessThanOrEqualToMatcher with negate=%t, value=%d, type=%s, attributeName=%v",
dto.Negate, dto.UnaryNumeric.Value, dto.UnaryNumeric.DataType, attributeName,
))
matcher = NewLessThanOrEqualToMatcher(
dto.Negate,
dto.UnaryNumeric.Value,
dto.UnaryNumeric.DataType,
attributeName,
)
case MatcherTypeBetween:
if dto.Between == nil {
return nil, errors.New("Between is required for BETWEEN matcher type")
}
logger.Debug(fmt.Sprintf(
"Building BetweenMatcher with negate=%t, start=%d, end=%d, type=%s, attributeName=%v",
dto.Negate, dto.Between.Start, dto.Between.End, dto.Between.DataType, attributeName,
))
matcher = NewBetweenMatcher(
dto.Negate,
dto.Between.Start,
dto.Between.End,
dto.Between.DataType,
attributeName,
)
case MatcherTypeEqualToSet:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for EQUAL_TO_SET matcher type")
}
logger.Debug(fmt.Sprintf(
"Building EqualToSetMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewEqualToSetMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypePartOfSet:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for PART_OF_SET matcher type")
}
logger.Debug(fmt.Sprintf(
"Building PartOfSetMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewPartOfSetMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeContainsAllOfSet:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for CONTAINS_ALL_OF_SET matcher type")
}
logger.Debug(fmt.Sprintf(
"Building AllOfSetMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewContainsAllOfSetMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeContainsAnyOfSet:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for CONTAINS_ANY_OF_SET matcher type")
}
logger.Debug(fmt.Sprintf(
"Building AnyOfSetMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewContainsAnyOfSetMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeStartsWith:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for STARTS_WITH matcher type")
}
logger.Debug(fmt.Sprintf(
"Building StartsWithMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewStartsWithMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeEndsWith:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for ENDS_WITH matcher type")
}
logger.Debug(fmt.Sprintf(
"Building EndsWithMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewEndsWithMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeContainsString:
if dto.Whitelist == nil {
return nil, errors.New("Whitelist is required for CONTAINS_STRING matcher type")
}
logger.Debug(fmt.Sprintf(
"Building ContainsStringMatcher with negate=%t, set=%v, attributeName=%v",
dto.Negate, dto.Whitelist.Whitelist, attributeName,
))
matcher = NewContainsStringMatcher(
dto.Negate,
dto.Whitelist.Whitelist,
attributeName,
)
case MatcherTypeInSplitTreatment:
if dto.Dependency == nil {
return nil, errors.New("Dependency is required for IN_SPLIT_TREATMENT matcher type")
}
logger.Debug(fmt.Sprintf(
"Building DependencyMatcher with negate=%t, feature=%s, treatments=%v, attributeName=%v",
dto.Negate, dto.Dependency.Split, dto.Dependency.Treatments, attributeName,
))
matcher = NewDependencyMatcher(
dto.Negate,
dto.Dependency.Split,
dto.Dependency.Treatments,
)
case MatcherTypeEqualToBoolean:
if dto.Boolean == nil {
return nil, errors.New("Boolean is required for EQUAL_TO_BOOLEAN matcher type")
}
logger.Debug(fmt.Sprintf(
"Building BooleanMatcher with negate=%t, value=%t, attributeName=%v",
dto.Negate, *dto.Boolean, attributeName,
))
matcher = NewBooleanMatcher(
dto.Negate,
dto.Boolean,
attributeName,
)
case MatcherTypeMatchesString:
if dto.String == nil {
return nil, errors.New("String is required for MATCHES_STRING matcher type")
}
logger.Debug(fmt.Sprintf(
"Building RegexMatcher with negate=%t, regex=%s, attributeName=%v",
dto.Negate, *dto.String, attributeName,
))
matcher = NewRegexMatcher(
dto.Negate,
*dto.String,
attributeName,
)
default:
return nil, errors.New("Matcher not found")
}
if ctx != nil {
ctx.Inject(matcher.base())
}
matcher.base().logger = logger
return matcher, nil
}

52
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/partofset.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,52 @@
package matchers
import (
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// PartOfSetMatcher matches if the set supplied to the getTreatment is a subset of the one in the split
type PartOfSetMatcher struct {
Matcher
comparisonSet *set.ThreadUnsafeSet
}
// Match returns true if the match provided is a subset of the one in the split
func (m *PartOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("PartOfSetMatcher: ", err)
return false
}
conv, ok := matchingKey.([]string)
if !ok {
m.logger.Error("Unable to type-assert key to []string")
return false
}
matchingSet := set.NewSet()
for _, x := range conv {
matchingSet.Add(x)
}
if matchingSet.IsEmpty() {
return false
}
return m.comparisonSet.IsSubset(matchingSet)
}
// NewPartOfSetMatcher returns a pointer to a new instance of PartOfSetMatcher
func NewPartOfSetMatcher(negate bool, setItems []string, attributeName *string) *PartOfSetMatcher {
setObj := set.NewSet()
for _, item := range setItems {
setObj.Add(item)
}
return &PartOfSetMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
comparisonSet: setObj,
}
}

48
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/regex.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,48 @@
package matchers
import (
"reflect"
"regexp"
)
// RegexMatcher matches if the supplied key matches the split's regex
type RegexMatcher struct {
Matcher
regex string
}
// Match returns true if the supplied key matches the split's regex
func (m *RegexMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("RegexMatcher: ", err)
return false
}
conv, ok := matchingKey.(string)
if !ok {
m.logger.Error(
"RegexMatcher: Incorrect type. Expected string and received ",
reflect.TypeOf(matchingKey).String(),
)
return false
}
re, err := regexp.Compile(m.regex)
if err != nil {
m.logger.Error("RegexMatcher: Failed to compile regexp. ", err)
return false
}
return re.MatchString(conv)
}
// NewRegexMatcher returns a new instance to a RegexMatcher
func NewRegexMatcher(negate bool, regex string, attributeName *string) *RegexMatcher {
return &RegexMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
regex: regex,
}
}

45
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/startswith.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,45 @@
package matchers
import (
"strings"
)
// StartsWithMatcher matches strings which start with one of the prefixes in the split
type StartsWithMatcher struct {
Matcher
prefixes []string
}
// Match returns true if the key provided starts with one of the prefixes in the split.
func (m *StartsWithMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("StartsWithMatcher: ", err)
return false
}
asString, ok := matchingKey.(string)
if !ok {
m.logger.Error("StartsWithMatcher: Failed to type-assert key to string")
return false
}
for _, prefix := range m.prefixes {
if strings.HasPrefix(asString, prefix) {
return true
}
}
return false
}
// NewStartsWithMatcher returns a new instance of StartsWithMatcher
func NewStartsWithMatcher(negate bool, prefixes []string, attributeName *string) *StartsWithMatcher {
return &StartsWithMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
prefixes: prefixes,
}
}

43
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/matchers/whitelist.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,43 @@
package matchers
import (
"github.com/splitio/go-toolkit/v3/datastructures/set"
)
// WhitelistMatcher matches if the key received is present in the matcher's whitelist
type WhitelistMatcher struct {
Matcher
whitelist *set.ThreadUnsafeSet
}
// Match returns true if the key is present in the whitelist.
func (m *WhitelistMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
matchingKey, err := m.matchingKey(key, attributes)
if err != nil {
m.logger.Error("WhitelistMatcher: ", err)
return false
}
stringMatchingKey, ok := matchingKey.(string)
if !ok {
m.logger.Error("WhitelistMatcher: Cannot type-assert key to string")
return false
}
return m.whitelist.Has(stringMatchingKey)
}
// NewWhitelistMatcher returns a new WhitelistMatcher
func NewWhitelistMatcher(negate bool, whitelist []string, attributeName *string) *WhitelistMatcher {
wlSet := set.NewSet()
for _, elem := range whitelist {
wlSet.Add(elem)
}
return &WhitelistMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
whitelist: wlSet,
}
}

94
vendor/github.com/splitio/go-client/v6/splitio/engine/grammar/split.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,94 @@
package grammar
import (
"github.com/splitio/go-split-commons/v2/dtos"
"github.com/splitio/go-toolkit/v3/injection"
"github.com/splitio/go-toolkit/v3/logging"
)
// Split struct with added logic that wraps around a DTO
type Split struct {
splitData *dtos.SplitDTO
conditions []*Condition
}
// NewSplit instantiates a new Split object and all it's internal structures mapped to model classes
func NewSplit(splitDTO *dtos.SplitDTO, ctx *injection.Context, logger logging.LoggerInterface) *Split {
conditions := make([]*Condition, 0)
for _, cond := range splitDTO.Conditions {
conditions = append(conditions, NewCondition(&cond, ctx, logger))
}
split := Split{
conditions: conditions,
splitData: splitDTO,
}
return &split
}
// Name returns the name of the feature
func (s *Split) Name() string {
return s.splitData.Name
}
// Seed returns the seed use for hashing
func (s *Split) Seed() int64 {
return s.splitData.Seed
}
// Status returns whether the split is active or arhived
func (s *Split) Status() string {
status := s.splitData.Status
if status == "" || (status != SplitStatusActive && status != SplitStatusArchived) {
return SplitStatusActive
}
return status
}
// Killed returns whether the split has been killed or not
func (s *Split) Killed() bool {
return s.splitData.Killed
}
// DefaultTreatment returns the default treatment for the current split
func (s *Split) DefaultTreatment() string {
return s.splitData.DefaultTreatment
}
// TrafficAllocation returns the traffic allocation configured for the current split
func (s *Split) TrafficAllocation() int {
return s.splitData.TrafficAllocation
}
// TrafficAllocationSeed returns the seed for traffic allocation configured for this split
func (s *Split) TrafficAllocationSeed() int64 {
return s.splitData.TrafficAllocationSeed
}
// Algo returns the hashing algorithm configured for this split
func (s *Split) Algo() int {
switch s.splitData.Algo {
case SplitAlgoLegacy:
return SplitAlgoLegacy
case SplitAlgoMurmur:
return SplitAlgoMurmur
default:
return SplitAlgoLegacy
}
}
// Conditions returns a slice of Condition objects
func (s *Split) Conditions() []*Condition {
return s.conditions
}
// ChangeNumber returns the change number for this split
func (s *Split) ChangeNumber() int64 {
return s.splitData.ChangeNumber
}
// Configurations returns the configurations for this split
func (s *Split) Configurations() map[string]string {
return s.splitData.Configurations
}

10
vendor/github.com/splitio/go-client/v6/splitio/engine/hash/legacy.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,10 @@
package hash
// Legacy calculates the bucket for the key and seed provided using the legacy algorithm
func Legacy(key []byte, seed uint32) uint32 {
var h uint32
for _, char := range key {
h = 31*h + uint32(char)
}
return uint32(h ^ seed)
}

66
vendor/github.com/splitio/go-client/v6/splitio/engine/hash/murmur.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,66 @@
package hash
// Murmur calculates the bucket for the key and seed provided using the legacy algorithm
// © Copyright 2014 Lawrence E. Bakst All Rights Reserved
// THIS SOURCE CODE IS THE PROPRIETARY INTELLECTUAL PROPERTY AND CONFIDENTIAL
// INFORMATION OF LAWRENCE E. BAKST AND IS PROTECTED UNDER U.S. AND
// INTERNATIONAL LAW. ANY USE OF THIS SOURCE CODE WITHOUT THE
// AUTHORIZATION OF LAWRENCE E. BAKST IS STRICTLY PROHIBITED.
// This package implements the 32 bit version of the MurmurHash3 hash code.
// With the exception of the interface check, this version was developed independtly.
// However, the "spaolacci" implementation with it's bmixer interface is da bomb, although
// this version is slightly faster.
//
// https://en.wikipedia.org/wiki/MurmurHash
// https://github.com/spaolacci/murmur3
const (
c1 uint32 = 0xcc9e2d51
c2 uint32 = 0x1b873593
r1 uint32 = 15
r2 uint32 = 13
m uint32 = 5
n uint32 = 0xe6546b64
)
// Murmur3_32 returns the 32 bit hash of data given the seed.
// This is code is what I started with before I added the hash.Hash and hash.Hash32 interfaces.
func Murmur3_32(data []byte, seed uint32) uint32 {
hash := seed
nblocks := len(data) / 4
for i := 0; i < nblocks; i++ {
// k := *(*uint32)(unsafe.Pointer(&data[i*4]))
k := uint32(data[i*4+0])<<0 | uint32(data[i*4+1])<<8 | uint32(data[i*4+2])<<16 | uint32(data[i*4+3])<<24
k *= c1
k = (k << r1) | (k >> (32 - r1))
k *= c2
hash ^= k
hash = ((hash<<r2)|(hash>>(32-r2)))*m + n
}
l := nblocks * 4
k1 := uint32(0)
switch len(data) & 3 {
case 3:
k1 ^= uint32(data[l+2]) << 16
fallthrough
case 2:
k1 ^= uint32(data[l+1]) << 8
fallthrough
case 1:
k1 ^= uint32(data[l+0])
k1 *= c1
k1 = (k1 << r1) | (k1 >> (32 - r1))
k1 *= c2
hash ^= k1
}
hash ^= uint32(len(data))
hash ^= hash >> 16
hash *= 0x85ebca6b
hash ^= hash >> 13
hash *= 0xc2b2ae35
hash ^= hash >> 16
return hash
}

6
vendor/github.com/splitio/go-client/v6/splitio/impressionListener/impression_listener.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,6 @@
package impressionlistener
// ImpressionListener declaration of ImpressionListener interface
type ImpressionListener interface {
LogImpression(data ILObject)
}

41
vendor/github.com/splitio/go-client/v6/splitio/impressionListener/impressions_listener_wrapper.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,41 @@
package impressionlistener
import (
"github.com/splitio/go-split-commons/v2/dtos"
)
// ILObject struct to map entire data for listener
type ILObject struct {
Impression dtos.Impression
Attributes map[string]interface{}
InstanceID string
SDKLanguageVersion string
}
// WrapperImpressionListener struct
type WrapperImpressionListener struct {
ImpressionListener ImpressionListener
metadata dtos.Metadata
}
// NewImpressionListenerWrapper instantiates a new ImpressionListenerWrapper
func NewImpressionListenerWrapper(impressionListener ImpressionListener, metadata dtos.Metadata) *WrapperImpressionListener {
return &WrapperImpressionListener{
ImpressionListener: impressionListener,
metadata: metadata,
}
}
// SendDataToClient sends the data to client
func (i *WrapperImpressionListener) SendDataToClient(impressions []dtos.Impression, attributes map[string]interface{}) {
for _, impression := range impressions {
datToSend := ILObject{
Impression: impression,
Attributes: attributes,
InstanceID: i.metadata.MachineName,
SDKLanguageVersion: i.metadata.SDKVersion,
}
i.ImpressionListener.LogImpression(datToSend)
}
}

4
vendor/github.com/splitio/go-client/v6/splitio/version.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,4 @@
package splitio
// Version contains a string with the split sdk version
const Version = "6.0.0"