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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
8bb772638c
Коммит
1aadd36644
27
vendor/github.com/splitio/go-client/v6/CONTRIBUTORS-GUIDE.md
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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 you’re 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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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
сгенерированный
поставляемый
Обычный файл
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"
|
||||
Ссылка в новой задаче
Block a user