├── .gitignore ├── CHANGES.txt ├── CONTRIBUTORS-GUIDE.md ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── provider.go ├── provider_test.go └── split.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .idea/ -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | - 10/3/2022. Up to date with spec v0.5.0 and go sdk v0.5.0 3 | 1.0.1 4 | - 10/14/2022. Up to date with spec v0.5.0 and go sdk v0.6.0 -------------------------------------------------------------------------------- /CONTRIBUTORS-GUIDE.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Split OpenFeature Provider 2 | 3 | The Split Provider 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). 4 | 5 | ## Development 6 | 7 | ### Development process 8 | 9 | 1. Fork the repository and create a topic branch from `development` branch. Please use a descriptive name for your branch. 10 | 2. While developing, use descriptive messages in your commits. Avoid short or meaningless sentences like "fix bug". 11 | 3. Make sure to add tests for both positive and negative cases. 12 | 4. Run the build script and make sure it runs with no errors. 13 | 5. Run all tests and make sure there are no failures. 14 | 6. `git push` your changes to GitHub within your topic branch. 15 | 7. Open a Pull Request(PR) from your forked repo and into the `development` branch of the original repository. 16 | 8. When creating your PR, please fill out all the fields of the PR template, as applicable, for the project. 17 | 9. Check for conflicts once the pull request is created to make sure your PR can be merged cleanly into `development`. 18 | 10. Keep an eye out for any feedback or comments from the Split team. 19 | 20 | ### Building the Split Provider 21 | - `go build` 22 | 23 | ### Running tests 24 | - `go test` 25 | 26 | # Contact 27 | 28 | If you have any other questions or need to contact us directly in a private manner send us a note at sdks@split.io 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2022 Split Software, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Split OpenFeature Provider for Go 2 | [![Twitter Follow](https://img.shields.io/twitter/follow/splitsoftware.svg?style=social&label=Follow&maxAge=1529000)](https://twitter.com/intent/follow?screen_name=splitsoftware) 3 | 4 | ## Overview 5 | This Provider is designed to allow the use of OpenFeature with Split, the platform for controlled rollouts, serving features to your users via the Split feature flag to manage your complete customer experience. 6 | 7 | ## Compatibility 8 | This SDK is compatible with Go 1.19 and higher. 9 | 10 | ## Getting started 11 | Below is a simple example that describes the instantiation of the Split Provider. Please see the [OpenFeature Documentation](https://docs.openfeature.dev/docs/reference/concepts/evaluation-api) for details on how to use the OpenFeature SDK. 12 | 13 | ```go 14 | import ( 15 | "github.com/open-feature/go-sdk/pkg/openfeature" 16 | splitProvider "github.com/splitio/split-openfeature-provider-go" 17 | ) 18 | 19 | provider, err := splitProvider.NewProviderSimple("YOUR_SDK_TYPE_API_KEY") 20 | if err != nil { 21 | // Provider creation error 22 | } 23 | openfeature.SetProvider(provider) 24 | 25 | ``` 26 | 27 | If you are more familiar with Split or want access to other initialization options, you can provide a `SplitClient` to the constructor. See the [Split Go SDK Documentation](https://help.split.io/hc/en-us/articles/360020093652-Go-SDK#initialization) for more information. 28 | ```go 29 | import ( 30 | "github.com/open-feature/go-sdk/pkg/openfeature" 31 | "github.com/splitio/go-client/v6/splitio/client" 32 | "github.com/splitio/go-client/v6/splitio/conf" 33 | splitProvider "github.com/splitio/split-openfeature-provider-go" 34 | ) 35 | 36 | cfg := conf.Default() 37 | factory, err := client.NewSplitFactory("YOUR_SDK_TYPE_API_KEY", cfg) 38 | if err != nil { 39 | // SDK initialization error 40 | } 41 | 42 | splitClient := factory.Client() 43 | 44 | err = splitClient.BlockUntilReady(10) 45 | if err != nil { 46 | // SDK timeout error 47 | } 48 | 49 | provider, err := splitProvider.NewProvider(*splitClient) 50 | if err != nil { 51 | // Provider creation error 52 | } 53 | openfeature.SetProvider(provider) 54 | ``` 55 | 56 | ## Use of OpenFeature with Split 57 | After the initial setup you can use OpenFeature according to their [documentation](https://docs.openfeature.dev/docs/reference/concepts/evaluation-api/). 58 | 59 | One important note is that the Split Provider **requires a targeting key** to be set. Often times this should be set when evaluating the value of a flag by [setting an EvaluationContext](https://docs.openfeature.dev/docs/reference/concepts/evaluation-context) which contains the targeting key. An example flag evaluation is 60 | ```go 61 | client := openfeature.NewClient("CLIENT_NAME"); 62 | 63 | evaluationContext := openfeature.NewEvaluationContext("TARGETING_KEY", nil) 64 | boolValue := client.BooleanValue(nil, "boolFlag", false, evaluationContext) 65 | ``` 66 | If the same targeting key is used repeatedly, the evaluation context may be set at the client level 67 | ```go 68 | evaluationContext := openfeature.NewEvaluationContext("TARGETING_KEY", nil) 69 | client.SetEvaluationContext(context) 70 | ``` 71 | or at the OpenFeatureAPI level 72 | ```go 73 | evaluationContext := openfeature.NewEvaluationContext("TARGETING_KEY", nil) 74 | openfeature.SetEvaluationContext(context) 75 | ```` 76 | If the context was set at the client or api level, it is not required to provide it during flag evaluation. 77 | 78 | ## Submitting issues 79 | 80 | The Split team monitors all issues submitted to this [issue tracker](https://github.com/splitio/split-openfeature-provider-go/issues). We encourage you to use this issue tracker to submit any bug reports, feedback, and feature enhancements. We'll do our best to respond in a timely manner. 81 | 82 | ## Contributing 83 | Please see [Contributors Guide](CONTRIBUTORS-GUIDE.md) to find all you need to submit a Pull Request (PR). 84 | 85 | ## License 86 | Licensed under the Apache License, Version 2.0. See: [Apache License](http://www.apache.org/licenses/). 87 | 88 | ## About Split 89 | 90 | Split is the leading Feature Delivery Platform for engineering teams that want to confidently deploy features as fast as they can develop them. Split’s fine-grained management, real-time monitoring, and data-driven experimentation ensure that new features will improve the customer experience without breaking or degrading performance. Companies like Twilio, Salesforce, GoDaddy and WePay trust Split to power their feature delivery. 91 | 92 | To learn more about Split, contact hello@split.io, or get started with feature flags for free at https://www.split.io/signup. 93 | 94 | Split has built and maintains SDKs for: 95 | 96 | * Java [Github](https://github.com/splitio/java-client) [Docs](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK) 97 | * Javascript [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK) 98 | * Node [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK) 99 | * .NET [Github](https://github.com/splitio/dotnet-client) [Docs](https://help.split.io/hc/en-us/articles/360020240172--NET-SDK) 100 | * Ruby [Github](https://github.com/splitio/ruby-client) [Docs](https://help.split.io/hc/en-us/articles/360020673251-Ruby-SDK) 101 | * PHP [Github](https://github.com/splitio/php-client) [Docs](https://help.split.io/hc/en-us/articles/360020350372-PHP-SDK) 102 | * Python [Github](https://github.com/splitio/python-client) [Docs](https://help.split.io/hc/en-us/articles/360020359652-Python-SDK) 103 | * GO [Github](https://github.com/splitio/go-client) [Docs](https://help.split.io/hc/en-us/articles/360020093652-Go-SDK) 104 | * Android [Github](https://github.com/splitio/android-client) [Docs](https://help.split.io/hc/en-us/articles/360020343291-Android-SDK) 105 | * iOS [Github](https://github.com/splitio/ios-client) [Docs](https://help.split.io/hc/en-us/articles/360020401491-iOS-SDK) 106 | 107 | For a comprehensive list of open source projects visit our [Github page](https://github.com/splitio?utf8=%E2%9C%93&query=%20only%3Apublic%20). 108 | 109 | **Learn more about Split:** 110 | 111 | Visit [split.io/product](https://www.split.io/product) for an overview of Split, or visit our documentation at [help.split.io](http://help.split.io) for more detailed information. 112 | 113 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/splitio/split-openfeature-provider-go 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/open-feature/go-sdk v0.6.0 7 | github.com/splitio/go-client v6.1.1-0.20210611192632-af2ff877b14a+incompatible 8 | github.com/splitio/go-toolkit v4.2.1-0.20210714181516-85e7c471376a+incompatible 9 | ) 10 | 11 | require ( 12 | github.com/go-logr/logr v1.2.3 // indirect 13 | github.com/go-redis/redis v6.15.9+incompatible // indirect 14 | github.com/onsi/ginkgo v1.16.5 // indirect 15 | github.com/onsi/gomega v1.20.2 // indirect 16 | github.com/splitio/go-split-commons v3.1.1-0.20210714173613-90097f92c8af+incompatible // indirect 17 | golang.org/x/net v0.7.0 // indirect 18 | golang.org/x/text v0.7.0 // indirect 19 | gopkg.in/yaml.v2 v2.4.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 4 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 5 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 6 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 7 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 8 | github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= 9 | github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 10 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 11 | github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 12 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 14 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 15 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 16 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 17 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 18 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 19 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 20 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 21 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 22 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 23 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 24 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 25 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 26 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 27 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 28 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 29 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 30 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 31 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 32 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 33 | github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= 34 | github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= 35 | github.com/open-feature/go-sdk v0.4.0 h1:4MC58EBEqsZRPrBfjywTEZXlgiD7lFQVSz0XIJQIRLM= 36 | github.com/open-feature/go-sdk v0.4.0/go.mod h1:rLTOsXIC5wJ/5iVZ0LOTz3/ahJmzxhzWcJTS81AaSqM= 37 | github.com/open-feature/go-sdk v0.5.0 h1:1Y3TYoiZn8yhez9SS6VkS0n9WTfIDst1QDGV92WWHeE= 38 | github.com/open-feature/go-sdk v0.5.0/go.mod h1:5yoSk6QrkAHXKQW9pD+ejxOx3uXUqJwoHmwEK4hlZvk= 39 | github.com/open-feature/go-sdk v0.5.1 h1:gra5dYqcgz3DuyKuOA3TIXS8MuYqNCTVgJpNGemkAQ8= 40 | github.com/open-feature/go-sdk v0.5.1/go.mod h1:5yoSk6QrkAHXKQW9pD+ejxOx3uXUqJwoHmwEK4hlZvk= 41 | github.com/open-feature/go-sdk v0.6.0 h1:/u1XH4msHeChaen65Alfk139/ifu8ZS3mLt37CenR5k= 42 | github.com/open-feature/go-sdk v0.6.0/go.mod h1:5yoSk6QrkAHXKQW9pD+ejxOx3uXUqJwoHmwEK4hlZvk= 43 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 44 | github.com/splitio/go-client v6.1.1-0.20210611192632-af2ff877b14a+incompatible h1:ahRviKx2RNNwK2b9NQbD9Iv1DLfHn+KHoBXwmbQ1EgY= 45 | github.com/splitio/go-client v6.1.1-0.20210611192632-af2ff877b14a+incompatible/go.mod h1:dJcPPOO+DlFMELdWAqGUcHTXGvGw0km+UEZJie7Hejk= 46 | github.com/splitio/go-split-commons v3.1.1-0.20210714173613-90097f92c8af+incompatible h1:jaP0z3iiwOYgneBEL7MGkUZNeQgsDiWqa6EBKBgSpQc= 47 | github.com/splitio/go-split-commons v3.1.1-0.20210714173613-90097f92c8af+incompatible/go.mod h1:w1uWXr+HcRVJLeoVyZucm+r3dt0W7zj7Sa9H2TCB3kA= 48 | github.com/splitio/go-toolkit v4.2.1-0.20210714181516-85e7c471376a+incompatible h1:vK8jmQOWqghCU9ZYPjHfrngpugLOFsc4tUMa4OqRk8M= 49 | github.com/splitio/go-toolkit v4.2.1-0.20210714181516-85e7c471376a+incompatible/go.mod h1:Oygm4Hgf3KotB5ZAaXIluLk5HgH2qu723HEPNvszJi8= 50 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 51 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 52 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 53 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 54 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 55 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 56 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 57 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 58 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 59 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 60 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 61 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 62 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= 63 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 64 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 65 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 66 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 67 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 68 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 69 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 70 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 71 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 72 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 73 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 74 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 75 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 76 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 77 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 78 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 79 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 80 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 81 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 82 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 83 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 84 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 85 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 86 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 87 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 88 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 89 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 90 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 91 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 92 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 93 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 94 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 95 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 96 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 97 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 98 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 99 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 100 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 101 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 102 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 103 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 104 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 105 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 106 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 107 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 108 | -------------------------------------------------------------------------------- /provider.go: -------------------------------------------------------------------------------- 1 | package split_openfeature_provider_go 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "github.com/splitio/go-client/splitio/conf" 7 | "strconv" 8 | 9 | "github.com/open-feature/go-sdk/pkg/openfeature" 10 | "github.com/splitio/go-client/splitio/client" 11 | ) 12 | 13 | type SplitProvider struct { 14 | client client.SplitClient 15 | } 16 | 17 | func NewProvider(splitClient client.SplitClient) (*SplitProvider, error) { 18 | return &SplitProvider{ 19 | client: splitClient, 20 | }, nil 21 | } 22 | 23 | func NewProviderSimple(apiKey string) (*SplitProvider, error) { 24 | cfg := conf.Default() 25 | factory, err := client.NewSplitFactory(apiKey, cfg) 26 | if err != nil { 27 | return nil, err 28 | } 29 | splitClient := factory.Client() 30 | err = splitClient.BlockUntilReady(10) 31 | if err != nil { 32 | return nil, err 33 | } 34 | return NewProvider(*splitClient) 35 | } 36 | 37 | func (provider *SplitProvider) Metadata() openfeature.Metadata { 38 | return openfeature.Metadata{ 39 | Name: "Split", 40 | } 41 | } 42 | 43 | func (provider *SplitProvider) BooleanEvaluation(ctx context.Context, flag string, defaultValue bool, evalCtx openfeature.FlattenedContext) openfeature.BoolResolutionDetail { 44 | if noTargetingKey(evalCtx) { 45 | return openfeature.BoolResolutionDetail{ 46 | Value: defaultValue, 47 | ProviderResolutionDetail: resolutionDetailTargetingKeyMissing(), 48 | } 49 | } 50 | evaluated := provider.evaluateTreatment(flag, evalCtx) 51 | if noTreatment(evaluated) { 52 | return openfeature.BoolResolutionDetail{ 53 | Value: defaultValue, 54 | ProviderResolutionDetail: resolutionDetailNotFound(evaluated), 55 | } 56 | } 57 | var value bool 58 | if evaluated == "true" || evaluated == "on" { 59 | value = true 60 | } else if evaluated == "false" || evaluated == "off" { 61 | value = false 62 | } else { 63 | return openfeature.BoolResolutionDetail{ 64 | Value: defaultValue, 65 | ProviderResolutionDetail: resolutionDetailParseError(evaluated), 66 | } 67 | } 68 | return openfeature.BoolResolutionDetail{ 69 | Value: value, 70 | ProviderResolutionDetail: resolutionDetailTargetingMatch(evaluated), 71 | } 72 | } 73 | 74 | func (provider *SplitProvider) StringEvaluation(ctx context.Context, flag string, defaultValue string, evalCtx openfeature.FlattenedContext) openfeature.StringResolutionDetail { 75 | if noTargetingKey(evalCtx) { 76 | return openfeature.StringResolutionDetail{ 77 | Value: defaultValue, 78 | ProviderResolutionDetail: resolutionDetailTargetingKeyMissing(), 79 | } 80 | } 81 | evaluated := provider.evaluateTreatment(flag, evalCtx) 82 | if noTreatment(evaluated) { 83 | return openfeature.StringResolutionDetail{ 84 | Value: defaultValue, 85 | ProviderResolutionDetail: resolutionDetailNotFound(evaluated), 86 | } 87 | } 88 | return openfeature.StringResolutionDetail{ 89 | Value: evaluated, 90 | ProviderResolutionDetail: resolutionDetailTargetingMatch(evaluated), 91 | } 92 | } 93 | 94 | func (provider *SplitProvider) FloatEvaluation(ctx context.Context, flag string, defaultValue float64, evalCtx openfeature.FlattenedContext) openfeature.FloatResolutionDetail { 95 | if noTargetingKey(evalCtx) { 96 | return openfeature.FloatResolutionDetail{ 97 | Value: defaultValue, 98 | ProviderResolutionDetail: resolutionDetailTargetingKeyMissing(), 99 | } 100 | } 101 | evaluated := provider.evaluateTreatment(flag, evalCtx) 102 | if noTreatment(evaluated) { 103 | return openfeature.FloatResolutionDetail{ 104 | Value: defaultValue, 105 | ProviderResolutionDetail: resolutionDetailNotFound(evaluated), 106 | } 107 | } 108 | floatEvaluated, parseErr := strconv.ParseFloat(evaluated, 64) 109 | if parseErr != nil { 110 | return openfeature.FloatResolutionDetail{ 111 | Value: defaultValue, 112 | ProviderResolutionDetail: resolutionDetailParseError(evaluated), 113 | } 114 | } 115 | return openfeature.FloatResolutionDetail{ 116 | Value: floatEvaluated, 117 | ProviderResolutionDetail: resolutionDetailTargetingMatch(evaluated), 118 | } 119 | } 120 | 121 | func (provider *SplitProvider) IntEvaluation(ctx context.Context, flag string, defaultValue int64, evalCtx openfeature.FlattenedContext) openfeature.IntResolutionDetail { 122 | if noTargetingKey(evalCtx) { 123 | return openfeature.IntResolutionDetail{ 124 | Value: defaultValue, 125 | ProviderResolutionDetail: resolutionDetailTargetingKeyMissing(), 126 | } 127 | } 128 | evaluated := provider.evaluateTreatment(flag, evalCtx) 129 | if noTreatment(evaluated) { 130 | return openfeature.IntResolutionDetail{ 131 | Value: defaultValue, 132 | ProviderResolutionDetail: resolutionDetailNotFound(evaluated), 133 | } 134 | } 135 | intEvaluated, parseErr := strconv.ParseInt(evaluated, 10, 64) 136 | if parseErr != nil { 137 | return openfeature.IntResolutionDetail{ 138 | Value: defaultValue, 139 | ProviderResolutionDetail: resolutionDetailParseError(evaluated), 140 | } 141 | } 142 | return openfeature.IntResolutionDetail{ 143 | Value: intEvaluated, 144 | ProviderResolutionDetail: resolutionDetailTargetingMatch(evaluated), 145 | } 146 | } 147 | 148 | func (provider *SplitProvider) ObjectEvaluation(ctx context.Context, flag string, defaultValue interface{}, evalCtx openfeature.FlattenedContext) openfeature.InterfaceResolutionDetail { 149 | if noTargetingKey(evalCtx) { 150 | return openfeature.InterfaceResolutionDetail{ 151 | Value: defaultValue, 152 | ProviderResolutionDetail: resolutionDetailTargetingKeyMissing(), 153 | } 154 | } 155 | evaluated := provider.evaluateTreatment(flag, evalCtx) 156 | if noTreatment(evaluated) { 157 | return openfeature.InterfaceResolutionDetail{ 158 | Value: defaultValue, 159 | ProviderResolutionDetail: resolutionDetailNotFound(evaluated), 160 | } 161 | } 162 | var data map[string]interface{} 163 | parseErr := json.Unmarshal([]byte(evaluated), &data) 164 | if parseErr != nil { 165 | return openfeature.InterfaceResolutionDetail{ 166 | Value: defaultValue, 167 | ProviderResolutionDetail: resolutionDetailParseError(evaluated), 168 | } 169 | } else { 170 | return openfeature.InterfaceResolutionDetail{ 171 | Value: data, 172 | ProviderResolutionDetail: resolutionDetailTargetingMatch(evaluated), 173 | } 174 | } 175 | 176 | } 177 | 178 | func (provider *SplitProvider) Hooks() []openfeature.Hook { 179 | return []openfeature.Hook{} 180 | } 181 | 182 | // *** Helpers *** 183 | 184 | func (provider *SplitProvider) evaluateTreatment(flag string, evalContext openfeature.FlattenedContext) string { 185 | return provider.client.Treatment(evalContext[openfeature.TargetingKey], flag, nil) 186 | } 187 | 188 | func noTargetingKey(evalContext openfeature.FlattenedContext) bool { 189 | _, ok := evalContext[openfeature.TargetingKey] 190 | return !ok 191 | } 192 | 193 | func noTreatment(treatment string) bool { 194 | return treatment == "" || treatment == "control" 195 | } 196 | 197 | func resolutionDetailNotFound(variant string) openfeature.ProviderResolutionDetail { 198 | return providerResolutionDetailError( 199 | openfeature.NewFlagNotFoundResolutionError( 200 | "Flag not found."), 201 | openfeature.DefaultReason, 202 | variant) 203 | } 204 | 205 | func resolutionDetailParseError(variant string) openfeature.ProviderResolutionDetail { 206 | return providerResolutionDetailError( 207 | openfeature.NewParseErrorResolutionError("Error parsing the treatment to the given type."), 208 | openfeature.ErrorReason, 209 | variant) 210 | } 211 | 212 | func resolutionDetailTargetingKeyMissing() openfeature.ProviderResolutionDetail { 213 | return providerResolutionDetailError( 214 | openfeature.NewTargetingKeyMissingResolutionError("Targeting key is required and missing."), 215 | openfeature.ErrorReason, 216 | "") 217 | } 218 | 219 | func providerResolutionDetailError(error openfeature.ResolutionError, reason openfeature.Reason, variant string) openfeature.ProviderResolutionDetail { 220 | return openfeature.ProviderResolutionDetail{ 221 | ResolutionError: error, 222 | Reason: reason, 223 | Variant: variant, 224 | } 225 | } 226 | 227 | func resolutionDetailTargetingMatch(variant string) openfeature.ProviderResolutionDetail { 228 | return openfeature.ProviderResolutionDetail{ 229 | Reason: openfeature.TargetingMatchReason, 230 | Variant: variant, 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /provider_test.go: -------------------------------------------------------------------------------- 1 | package split_openfeature_provider_go 2 | 3 | import ( 4 | "github.com/open-feature/go-sdk/pkg/openfeature" 5 | "github.com/splitio/go-client/splitio/client" 6 | "github.com/splitio/go-client/splitio/conf" 7 | "github.com/splitio/go-toolkit/logging" 8 | "reflect" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func create(t *testing.T) *openfeature.Client { 14 | cfg := conf.Default() 15 | cfg.SplitFile = "./split.yaml" 16 | cfg.LoggerConfig.LogLevel = logging.LevelNone 17 | factory, err := client.NewSplitFactory("localhost", cfg) 18 | if err != nil { 19 | // error 20 | t.Error("Error creating split factory") 21 | } 22 | splitClient := factory.Client() 23 | err = splitClient.BlockUntilReady(10) 24 | if err != nil { 25 | // error timeout 26 | t.Error("Split sdk timeout error") 27 | } 28 | provider, err := NewProvider(*splitClient) 29 | if err != nil { 30 | t.Error(err) 31 | } 32 | if provider == nil { 33 | t.Error("Error creating Split Provider") 34 | } 35 | openfeature.SetProvider(provider) 36 | return openfeature.NewClient("test_client") 37 | } 38 | 39 | func evaluationContext() openfeature.EvaluationContext { 40 | return openfeature.NewEvaluationContext("key", nil) 41 | } 42 | 43 | func TestCreateSimple(t *testing.T) { 44 | provider, err := NewProviderSimple("localhost") 45 | if err != nil { 46 | t.Error(err) 47 | } 48 | if provider == nil { 49 | t.Error("Error creating Split Provider") 50 | } 51 | } 52 | 53 | func TestUseDefault(t *testing.T) { 54 | ofClient := create(t) 55 | flagName := "random-non-existent-feature" 56 | evalCtx := evaluationContext() 57 | 58 | result, err := ofClient.BooleanValue(nil, flagName, false, evalCtx) 59 | if err == nil { 60 | t.Error("Should have returned flag not found error") 61 | } else if !strings.Contains(err.Error(), string(openfeature.FlagNotFoundCode)) { 62 | t.Errorf("Unexpected error occurred %s", err.Error()) 63 | } else if result == true { 64 | t.Error("Result was true, but should have been default value of false") 65 | } 66 | result, err = ofClient.BooleanValue(nil, flagName, true, evalCtx) 67 | if err == nil { 68 | t.Error("Should have returned flag not found error") 69 | } else if !strings.Contains(err.Error(), string(openfeature.FlagNotFoundCode)) { 70 | t.Errorf("Unexpected error occurred %s", err.Error()) 71 | } else if result == false { 72 | t.Error("Result was false, but should have been default value of true") 73 | } 74 | } 75 | 76 | func TestMissingTargetingKey(t *testing.T) { 77 | ofClient := create(t) 78 | flagName := "random-non-existent-feature" 79 | 80 | result, err := ofClient.BooleanValue(nil, flagName, false, openfeature.EvaluationContext{}) 81 | if err == nil { 82 | t.Error("Should have returned targeting key missing error") 83 | } else if !strings.Contains(err.Error(), string(openfeature.TargetingKeyMissingCode)) { 84 | t.Errorf("Unexpected error occurred %s", err.Error()) 85 | } else if result == true { 86 | t.Error("Result was true, but should have been default value of false") 87 | } 88 | } 89 | 90 | func TestGetControlVariantNonExistentSplit(t *testing.T) { 91 | ofClient := create(t) 92 | flagName := "random-non-existent-feature" 93 | evalCtx := evaluationContext() 94 | 95 | result, err := ofClient.BooleanValueDetails(nil, flagName, false, evalCtx) 96 | if err == nil { 97 | t.Error("Should have returned flag not found error") 98 | } else if !strings.Contains(err.Error(), string(openfeature.FlagNotFoundCode)) { 99 | t.Errorf("Unexpected error occurred %s", err.Error()) 100 | } else if result.Value == true { 101 | t.Error("Result was true, but should have been default value of false") 102 | } else if result.Variant != "control" { 103 | t.Error("Variant should be control due to Split Go SDK functionality") 104 | } 105 | } 106 | 107 | func TestGetBooleanSplit(t *testing.T) { 108 | ofClient := create(t) 109 | flagName := "some_other_feature" 110 | evalCtx := evaluationContext() 111 | 112 | result, err := ofClient.BooleanValue(nil, flagName, true, evalCtx) 113 | if err != nil { 114 | t.Errorf("Unexpected error occurred %s", err.Error()) 115 | } else if result == true { 116 | t.Error("Result was true, but should have been false as set in split.yaml") 117 | } 118 | } 119 | 120 | func TestGetBooleanWithKeySplit(t *testing.T) { 121 | ofClient := create(t) 122 | flagName := "my_feature" 123 | evalCtx := evaluationContext() 124 | 125 | result, err := ofClient.BooleanValue(nil, flagName, false, evalCtx) 126 | if err != nil { 127 | t.Errorf("Unexpected error occurred %s", err.Error()) 128 | } else if result == false { 129 | t.Error("Result was false, but should have been true as set in split.yaml") 130 | } 131 | 132 | evalCtx = openfeature.NewEvaluationContext("randomKey", nil) 133 | result, err = ofClient.BooleanValue(nil, flagName, true, evalCtx) 134 | if err != nil { 135 | t.Errorf("Unexpected error occurred %s", err.Error()) 136 | } else if result == true { 137 | t.Error("Result was true, but should have been false as set in split.yaml") 138 | } 139 | } 140 | 141 | func TestGetStringSplit(t *testing.T) { 142 | ofClient := create(t) 143 | flagName := "some_other_feature" 144 | evalCtx := evaluationContext() 145 | 146 | result, err := ofClient.StringValue(nil, flagName, "on", evalCtx) 147 | if err != nil { 148 | t.Errorf("Unexpected error occurred %s", err.Error()) 149 | } else if result != "off" { 150 | t.Errorf("Result was %s, not off as set in split.yaml", result) 151 | } 152 | } 153 | 154 | func TestGetIntegerSplit(t *testing.T) { 155 | ofClient := create(t) 156 | flagName := "int_feature" 157 | evalCtx := evaluationContext() 158 | 159 | result, err := ofClient.IntValue(nil, flagName, 0, evalCtx) 160 | if err != nil { 161 | t.Errorf("Unexpected error occurred %s", err.Error()) 162 | } else if result != 32 { 163 | t.Errorf("Result was %d, not 32 as set in split.yaml", result) 164 | } 165 | } 166 | 167 | func TestGetObjectSplit(t *testing.T) { 168 | ofClient := create(t) 169 | flagName := "obj_feature" 170 | evalCtx := evaluationContext() 171 | 172 | result, err := ofClient.ObjectValue(nil, flagName, 0, evalCtx) 173 | expectedResult := map[string]interface{}{ 174 | "key": "value", 175 | } 176 | if err != nil { 177 | t.Errorf("Unexpected error occurred %s", err.Error()) 178 | } else if !reflect.DeepEqual(result, expectedResult) { 179 | t.Error("Result was not map from key to value as set in split.yaml") 180 | } 181 | } 182 | 183 | func TestGetFloatSplit(t *testing.T) { 184 | ofClient := create(t) 185 | flagName := "int_feature" 186 | evalCtx := evaluationContext() 187 | 188 | result, err := ofClient.FloatValue(nil, flagName, 0, evalCtx) 189 | if err != nil { 190 | t.Errorf("Unexpected error occurred %s", err.Error()) 191 | } else if result != float64(32) { 192 | t.Errorf("Result was %f, not 32 as set in split.yaml", result) 193 | } 194 | } 195 | 196 | func TestMetadataName(t *testing.T) { 197 | ofClient := create(t) 198 | if ofClient.Metadata().Name() != "test_client" { 199 | t.Error("Client name was not set properly") 200 | } 201 | if openfeature.ProviderMetadata().Name != "Split" { 202 | t.Errorf("Provider metadata name was %s, not Split", openfeature.ProviderMetadata().Name) 203 | } 204 | } 205 | 206 | func TestBooleanDetails(t *testing.T) { 207 | ofClient := create(t) 208 | flagName := "some_other_feature" 209 | evalCtx := evaluationContext() 210 | 211 | result, err := ofClient.BooleanValueDetails(nil, flagName, true, evalCtx) 212 | if err != nil { 213 | t.Errorf("Unexpected error occurred %s", err.Error()) 214 | } else if result.FlagKey != flagName { 215 | t.Errorf("Flag name is %s, not %s", result.FlagKey, flagName) 216 | } else if !strings.Contains(string(result.Reason), string(openfeature.TargetingMatchReason)) { 217 | t.Errorf("reason is %s, not targeting match", result.Reason) 218 | } else if result.Value == true { 219 | t.Error("Result was true, but should have been false as in split.yaml") 220 | } else if result.Variant != "off" { 221 | t.Errorf("Variant should be off as in split.yaml, but was %s", result.Variant) 222 | } else if result.ErrorCode != "" { 223 | t.Errorf("Unexpected error in result %s", result.ErrorCode) 224 | } 225 | } 226 | 227 | func TestIntegerDetails(t *testing.T) { 228 | ofClient := create(t) 229 | flagName := "int_feature" 230 | evalCtx := evaluationContext() 231 | 232 | result, err := ofClient.IntValueDetails(nil, flagName, 0, evalCtx) 233 | if err != nil { 234 | t.Errorf("Unexpected error occurred %s", err.Error()) 235 | } else if result.FlagKey != flagName { 236 | t.Errorf("Flag name is %s, not %s", result.FlagKey, flagName) 237 | } else if !strings.Contains(string(result.Reason), string(openfeature.TargetingMatchReason)) { 238 | t.Errorf("reason is %s, not targeting match", result.Reason) 239 | } else if result.Value != int64(32) { 240 | t.Errorf("Result was %d, but should have been 32 as in split.yaml", result.Value) 241 | } else if result.Variant != "32" { 242 | t.Errorf("Variant should be 32 as in split.yaml, but was %s", result.Variant) 243 | } else if result.ErrorCode != "" { 244 | t.Errorf("Unexpected error in result %s", result.ErrorCode) 245 | } 246 | } 247 | 248 | func TestStringDetails(t *testing.T) { 249 | ofClient := create(t) 250 | flagName := "some_other_feature" 251 | evalCtx := evaluationContext() 252 | 253 | result, err := ofClient.StringValueDetails(nil, flagName, "blah", evalCtx) 254 | if err != nil { 255 | t.Errorf("Unexpected error occurred %s", err.Error()) 256 | } else if result.FlagKey != flagName { 257 | t.Errorf("Flag name is %s, not %s", result.FlagKey, flagName) 258 | } else if !strings.Contains(string(result.Reason), string(openfeature.TargetingMatchReason)) { 259 | t.Errorf("reason is %s, not targeting match", result.Reason) 260 | } else if result.Value != "off" { 261 | t.Errorf("Result was %s, but should have been off as in split.yaml", result.Value) 262 | } else if result.Variant != "off" { 263 | t.Errorf("Variant should be off as in split.yaml, but was %s", result.Variant) 264 | } else if result.ErrorCode != "" { 265 | t.Errorf("Unexpected error in result %s", result.ErrorCode) 266 | } 267 | } 268 | 269 | func TestObjectDetails(t *testing.T) { 270 | ofClient := create(t) 271 | flagName := "obj_feature" 272 | evalCtx := evaluationContext() 273 | 274 | result, err := ofClient.ObjectValueDetails(nil, flagName, map[string]interface{}{}, evalCtx) 275 | expectedResult := map[string]interface{}{ 276 | "key": "value", 277 | } 278 | if err != nil { 279 | t.Errorf("Unexpected error occurred %s", err.Error()) 280 | } else if result.FlagKey != flagName { 281 | t.Errorf("Flag name is %s, not %s", result.FlagKey, flagName) 282 | } else if !strings.Contains(string(result.Reason), string(openfeature.TargetingMatchReason)) { 283 | t.Errorf("reason is %s, not targeting match", result.Reason) 284 | } else if !reflect.DeepEqual(result.Value, expectedResult) { 285 | t.Error("Result was not map of key->value as in split.yaml") 286 | } else if result.Variant != "{\"key\": \"value\"}" { 287 | t.Errorf("Variant should be {\"key\": \"value\"} as in split.yaml, but was %s", result.Variant) 288 | } else if result.ErrorCode != "" { 289 | t.Errorf("Unexpected error in result %s", result.ErrorCode) 290 | } 291 | } 292 | 293 | func TestFloatDetails(t *testing.T) { 294 | ofClient := create(t) 295 | flagName := "int_feature" 296 | evalCtx := evaluationContext() 297 | 298 | result, err := ofClient.FloatValueDetails(nil, flagName, 0, evalCtx) 299 | if err != nil { 300 | t.Errorf("Unexpected error occurred %s", err.Error()) 301 | } else if result.FlagKey != flagName { 302 | t.Errorf("Flag name is %s, not %s", result.FlagKey, flagName) 303 | } else if !strings.Contains(string(result.Reason), string(openfeature.TargetingMatchReason)) { 304 | t.Errorf("reason is %s, not targeting match", result.Reason) 305 | } else if result.Value != float64(32) { 306 | t.Errorf("Result was %f, but should have been 32 as in split.yaml", result.Value) 307 | } else if result.Variant != "32" { 308 | t.Errorf("Variant should be 32 as in split.yaml, but was %s", result.Variant) 309 | } else if result.ErrorCode != "" { 310 | t.Errorf("Unexpected error in result %s", result.ErrorCode) 311 | } 312 | 313 | flagName = "float_feature" 314 | result, err = ofClient.FloatValueDetails(nil, flagName, 0, evalCtx) 315 | if err != nil { 316 | t.Errorf("Unexpected error occurred %s", err.Error()) 317 | } else if result.Value != 32.5 { 318 | t.Errorf("Result was %f, but should have been 32.5 as in split.yaml", result.Value) 319 | } else if result.Variant != "32.5" { 320 | t.Errorf("Variant should be 32 as in split.yaml, but was %s", result.Variant) 321 | } else if result.ErrorCode != "" { 322 | t.Errorf("Unexpected error in result %s", result.ErrorCode) 323 | } 324 | } 325 | 326 | func TestBooleanFail(t *testing.T) { 327 | // attempt to fetch an object treatment as a boolean. Should result in the default 328 | ofClient := create(t) 329 | flagName := "obj_feature" 330 | evalCtx := evaluationContext() 331 | 332 | result, err := ofClient.BooleanValue(nil, flagName, false, evalCtx) 333 | if err == nil { 334 | t.Error("Expected exception to occur") 335 | } else if !strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) { 336 | t.Errorf("Expected parse error, got %s", err.Error()) 337 | } else if result != false { 338 | t.Error("Result was true, but should have been default of false") 339 | } 340 | 341 | resultDetails, err := ofClient.BooleanValueDetails(nil, flagName, false, evalCtx) 342 | if err == nil { 343 | t.Error("Expected exception to occur") 344 | } else if !strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) { 345 | t.Errorf("Expected parse error, got %s", err.Error()) 346 | } else if resultDetails.Value != false { 347 | t.Error("Result was true, but should have been default of false") 348 | } else if resultDetails.ErrorCode != openfeature.ParseErrorCode { 349 | t.Errorf("Expected parse error code, got %s", resultDetails.ErrorCode) 350 | } else if resultDetails.Reason != openfeature.ErrorReason { 351 | t.Errorf("Expected error reason code, got %s", resultDetails.Reason) 352 | } else if resultDetails.Variant != "{\"key\": \"value\"}" { 353 | t.Errorf("Expected variant to be string of map, got %s", resultDetails.Variant) 354 | } 355 | } 356 | 357 | func TestIntegerFail(t *testing.T) { 358 | // attempt to fetch an object treatment as an integer. Should result in the default 359 | ofClient := create(t) 360 | flagName := "obj_feature" 361 | evalCtx := evaluationContext() 362 | 363 | result, err := ofClient.IntValue(nil, flagName, 10, evalCtx) 364 | if err == nil { 365 | t.Error("Expected exception to occur") 366 | } else if !strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) { 367 | t.Errorf("Expected parse error, got %s", err.Error()) 368 | } else if result != int64(10) { 369 | t.Errorf("Result was %d, but should have been default of 10", result) 370 | } 371 | 372 | resultDetails, err := ofClient.IntValueDetails(nil, flagName, 10, evalCtx) 373 | if err == nil { 374 | t.Error("Expected exception to occur") 375 | } else if !strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) { 376 | t.Errorf("Expected parse error, got %s", err.Error()) 377 | } else if resultDetails.Value != int64(10) { 378 | t.Errorf("Result was %d, but should have been default of 10", resultDetails.Value) 379 | } else if resultDetails.ErrorCode != openfeature.ParseErrorCode { 380 | t.Errorf("Expected parse error code, got %s", resultDetails.ErrorCode) 381 | } else if resultDetails.Reason != openfeature.ErrorReason { 382 | t.Errorf("Expected error reason code, got %s", resultDetails.Reason) 383 | } else if resultDetails.Variant != "{\"key\": \"value\"}" { 384 | t.Errorf("Expected variant to be string of map, got %s", resultDetails.Variant) 385 | } 386 | } 387 | 388 | func TestFloatFail(t *testing.T) { 389 | // attempt to fetch an object treatment as a float. Should result in the default 390 | ofClient := create(t) 391 | flagName := "obj_feature" 392 | evalCtx := evaluationContext() 393 | 394 | result, err := ofClient.FloatValue(nil, flagName, 10, evalCtx) 395 | if err == nil { 396 | t.Error("Expected exception to occur") 397 | } else if !strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) { 398 | t.Errorf("Expected parse error, got %s", err.Error()) 399 | } else if result != float64(10) { 400 | t.Errorf("Result was %f, but should have been default of 10", result) 401 | } 402 | 403 | resultDetails, err := ofClient.FloatValueDetails(nil, flagName, 10, evalCtx) 404 | if err == nil { 405 | t.Error("Expected exception to occur") 406 | } else if !strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) { 407 | t.Errorf("Expected parse error, got %s", err.Error()) 408 | } else if resultDetails.Value != float64(10) { 409 | t.Errorf("Result was %f, but should have been default of 10", resultDetails.Value) 410 | } else if resultDetails.ErrorCode != openfeature.ParseErrorCode { 411 | t.Errorf("Expected parse error code, got %s", resultDetails.ErrorCode) 412 | } else if resultDetails.Reason != openfeature.ErrorReason { 413 | t.Errorf("Expected error reason code, got %s", resultDetails.Reason) 414 | } else if resultDetails.Variant != "{\"key\": \"value\"}" { 415 | t.Errorf("Expected variant to be string of map, got %s", resultDetails.Variant) 416 | } 417 | } 418 | 419 | func TestObjectFail(t *testing.T) { 420 | // attempt to fetch an int as an object. Should result in the default 421 | ofClient := create(t) 422 | flagName := "int_feature" 423 | evalCtx := evaluationContext() 424 | defaultTreatment := map[string]interface{}{ 425 | "key": "value", 426 | } 427 | 428 | result, err := ofClient.ObjectValue(nil, flagName, defaultTreatment, evalCtx) 429 | if err == nil { 430 | t.Error("Expected exception to occur") 431 | } else if !strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) { 432 | t.Errorf("Expected parse error, got %s", err.Error()) 433 | } else if !reflect.DeepEqual(result, defaultTreatment) { 434 | t.Error("Result was not default treatment") 435 | } 436 | 437 | resultDetails, err := ofClient.ObjectValueDetails(nil, flagName, defaultTreatment, evalCtx) 438 | if err == nil { 439 | t.Error("Expected exception to occur") 440 | } else if !strings.Contains(err.Error(), string(openfeature.ParseErrorCode)) { 441 | t.Errorf("Expected parse error, got %s", err.Error()) 442 | } else if !reflect.DeepEqual(resultDetails.Value, defaultTreatment) { 443 | t.Errorf("Result was %f, but should have been default of 10", resultDetails.Value) 444 | } else if resultDetails.ErrorCode != openfeature.ParseErrorCode { 445 | t.Errorf("Expected parse error code, got %s", resultDetails.ErrorCode) 446 | } else if resultDetails.Reason != openfeature.ErrorReason { 447 | t.Errorf("Expected error reason code, got %s", resultDetails.Reason) 448 | } else if resultDetails.Variant != "32" { 449 | t.Errorf("Expected variant to be string of integer, got %s", resultDetails.Variant) 450 | } 451 | } 452 | -------------------------------------------------------------------------------- /split.yaml: -------------------------------------------------------------------------------- 1 | - my_feature: 2 | treatment: "on" 3 | keys: "key" 4 | config: "{\"desc\" : \"this applies only to ON treatment\"}" 5 | - my_feature: 6 | treatment: "off" 7 | - some_other_feature: 8 | treatment: "off" 9 | - int_feature: 10 | treatment: "32" 11 | - obj_feature: 12 | treatment: "{\"key\": \"value\"}" 13 | - float_feature: 14 | treatment: "32.5" --------------------------------------------------------------------------------