diff --git a/examples/lint_flag_programmatically/README.md b/examples/lint_flag_programmatically/README.md new file mode 100644 index 00000000000..01f5d993063 --- /dev/null +++ b/examples/lint_flag_programmatically/README.md @@ -0,0 +1,73 @@ +# Lint Feature Flags Programmatically + +This example demonstrates how to lint/validate a GO Feature Flag configuration programmatically without using the CLI. + +## Overview + +This example shows how to: +1. Retrieve flag configurations from your storage +2. Unmarshal the configuration into DTOs +3. Validate each flag configuration programmatically + +## Prerequisites + +- Access to the GO Feature Flag core module + +## Installation + +### Step 1: Install Dependencies + +Install the required dependencies: + +```bash +go get github.com/thomaspoignant/go-feature-flag/modules/core +``` + +#### Step 2: Retrieve Flag Configuration from Storage + +```go +myFlags := getMyFlagsFromStorage() +``` + +This simulates retrieving your flag configuration from a storage system (database, file system, API, etc.). In this example, it returns a YAML string, but you can adapt this to your specific storage mechanism. + +**Replace `getMyFlagsFromStorage()`** with your actual retrieval logic. + +#### Step 3: Unmarshal Flag Configuration + +```go +var flags map[string]dto.DTO +err := yaml.Unmarshal([]byte(myFlags), &flags) +if err != nil { + panic(err) // Don't forget to handle errors appropriately in production code. +} +``` + +The configuration is unmarshaled from YAML format into a map where: +- **Key**: The flag key (identifier) +- **Value**: A `dto.DTO` object representing the flag configuration + +**Note**: You can also use JSON or TOML formats depending on your storage format. Simply use the appropriate unmarshaler (`json.Unmarshal` or `toml.Unmarshal`). + +#### Step 4: Validate Each Flag Configuration + +```go +for key, flagDto := range flags { + convertedFlag := flagDto.Convert() + if err := convertedFlag.IsValid(); err != nil { + panic(fmt.Sprintf("Invalid flag %s: %s", key, err.Error())) + } +} +``` + +For each flag: +1. Convert the DTO to a Flag object using `flagDto.Convert()` +2. Validate the flag using `IsValid()` +3. Handle any validation errors appropriately + +**Handle validation errors** according to your application's error handling strategy. + +## Additional Resources + +- [GO Feature Flag Documentation](https://gofeatureflag.org/) +- [YAML Package Documentation](https://pkg.go.dev/gopkg.in/yaml.v3) diff --git a/examples/lint_flag_programmatically/go.mod b/examples/lint_flag_programmatically/go.mod new file mode 100644 index 00000000000..1f47a007c85 --- /dev/null +++ b/examples/lint_flag_programmatically/go.mod @@ -0,0 +1,20 @@ +module github.com/thomaspoignant/go-feature-flag/examples/lint_flag_programmatically + +go 1.24.8 + +require ( + github.com/thomaspoignant/go-feature-flag/modules/core v0.1.4 + go.yaml.in/yaml/v3 v3.0.4 +) + +require ( + github.com/GeorgeD19/json-logic-go v0.0.0-20220225111652-48cc2d2c387e // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/dariubs/percent v0.0.0-20190521174708-8153fcbd48ae // indirect + github.com/kr/text v0.2.0 // indirect + github.com/nikunjy/rules v1.5.0 // indirect + github.com/spf13/cast v1.3.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect +) diff --git a/examples/lint_flag_programmatically/go.sum b/examples/lint_flag_programmatically/go.sum new file mode 100644 index 00000000000..5d7922f2909 --- /dev/null +++ b/examples/lint_flag_programmatically/go.sum @@ -0,0 +1,43 @@ +github.com/GeorgeD19/json-logic-go v0.0.0-20220225111652-48cc2d2c387e h1:pGKbZyClLVd95fyMC8yib8STgy76ShCwIaPOSZPhDMM= +github.com/GeorgeD19/json-logic-go v0.0.0-20220225111652-48cc2d2c387e/go.mod h1:vIXtt8GZPXz4N4IZmJHYp8W8QWCi2IfNhOKWeqYc6RY= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/dariubs/percent v0.0.0-20190521174708-8153fcbd48ae h1:0SUXUFz3+ksMulwvkS6XZnxCqw5ygjYJPKjpEBWNCJU= +github.com/dariubs/percent v0.0.0-20190521174708-8153fcbd48ae/go.mod h1:NqjuQSHe8CjRVziJtxGCQDmOwoj68QdlKRkbddHfRtY= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/nikunjy/rules v1.5.0 h1:KJDSLOsFhwt7kcXUyZqwkgrQg5YoUwj+TVu6ItCQShw= +github.com/nikunjy/rules v1.5.0/go.mod h1:TlZtZdBChrkqi8Lr2AXocme8Z7EsbxtFdDoKeI6neBQ= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/thomaspoignant/go-feature-flag/modules/core v0.1.4 h1:m4ziK5hU5yM1SLknBC+8jZgOcwPLp6I5uSUiC8S0MuY= +github.com/thomaspoignant/go-feature-flag/modules/core v0.1.4/go.mod h1:WvPwGQdqVj+qzPsT4e6kSwX/4cU7FG/qF/PkbTxPsbM= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/lint_flag_programmatically/main.go b/examples/lint_flag_programmatically/main.go new file mode 100644 index 00000000000..fb513067e69 --- /dev/null +++ b/examples/lint_flag_programmatically/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + + "github.com/thomaspoignant/go-feature-flag/modules/core/dto" + "go.yaml.in/yaml/v3" +) + +// This example is demonstrating how to lint/validate a GO Feature Flag configuration programmatically +// without using the CLI. +// The example is retrieving a flag configuration from a storage (could be a database, a file, etc...), +// unmarshaling it into a DTO structure, and validating each flag configuration. +// If you're storage contains only 1 flag this will still work as the configuration is a map of flag key to flag DTO. +func main() { + // Step 1: Retrieve your flag configuration from your storage + myFlags := getMyFlagsFromStorage() + + // Step 2: Unmarshal the flag configuration into a flag DTO + // In the example we are using YAML but you can use JSON or TOML as well depending on your storage format. + var flags map[string]dto.DTO + err := yaml.Unmarshal([]byte(myFlags), &flags) + if err != nil { + panic(err) // Don't forget to handle errors appropriately in production code. + } + + // Step 3: Validate the flags configuration + // We are converting each DTO into a Flag and calling the IsValid method exactly like the CLI linter would do. + for key, flagDto := range flags { + convertedFlag := flagDto.Convert() + if err := convertedFlag.IsValid(); err != nil { + panic(fmt.Sprintf("Invalid flag %s: %s", key, err.Error())) // Don't forget to handle errors appropriately in production code. + } + } +} + +// getMyFlagsFromStorage simulates retrieving a flag configuration from some storage +// and returns it as a string. +func getMyFlagsFromStorage() string { + return ` +my-test-flag: + variations: + enabled: true + disabled: false + targeting: + - query: key eq "785a14bf-d2c5-4caa-9c70-2bbc4e3732a5" + percentage: + enabled: 0 + disabled: 100 + defaultRule: + variation: disabled +` +} diff --git a/website/docs/tooling/linter.mdx b/website/docs/tooling/linter.mdx index 9a948b0f9f4..3315c33295d 100644 --- a/website/docs/tooling/linter.mdx +++ b/website/docs/tooling/linter.mdx @@ -93,3 +93,7 @@ lint-job: + +## Lint Feature Flags Programmatically +You can also lint/validate a GO Feature Flag configuration programmatically without using the CLI. +Check the [example repository](https://github.com/thomaspoignant/go-feature-flag/tree/main/examples/lint_flag_programmatically/README.md) to see how to do it in Go.