Skip to content

Commit 1e0ec3b

Browse files
feat(flagset): improve validation error formatting in Load function open-feature#110
1 parent eedccd6 commit 1e0ec3b

File tree

4 files changed

+79
-7
lines changed

4 files changed

+79
-7
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.0
55
toolchain go1.24.0
66

77
require (
8+
dagger.io/dagger v0.10.2
89
github.com/google/go-cmp v0.6.0
910
github.com/iancoleman/strcase v0.3.0
1011
github.com/invopop/jsonschema v0.13.0
@@ -16,13 +17,13 @@ require (
1617
github.com/stretchr/testify v1.10.0
1718
github.com/xeipuuv/gojsonschema v1.2.0
1819
golang.org/x/text v0.23.0
20+
gopkg.in/yaml.v3 v3.0.1
1921
)
2022

2123
require (
2224
atomicgo.dev/cursor v0.2.0 // indirect
2325
atomicgo.dev/keyboard v0.2.9 // indirect
2426
atomicgo.dev/schedule v0.1.0 // indirect
25-
dagger.io/dagger v0.10.2 // indirect
2627
github.com/99designs/gqlgen v0.17.31 // indirect
2728
github.com/Khan/genqlient v0.6.0 // indirect
2829
github.com/adrg/xdg v0.4.0 // indirect
@@ -57,5 +58,4 @@ require (
5758
golang.org/x/sync v0.12.0 // indirect
5859
golang.org/x/sys v0.31.0 // indirect
5960
golang.org/x/term v0.30.0 // indirect
60-
gopkg.in/yaml.v3 v3.0.1 // indirect
6161
)

go.sum

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc
2424
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
2525
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
2626
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
27+
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
2728
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
2829
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
2930
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
@@ -97,14 +98,14 @@ github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaK
9798
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
9899
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
99100
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
100-
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
101-
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
101+
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
102+
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
102103
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
103104
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
104105
github.com/sagikazarmark/locafero v0.8.0 h1:mXaMVw7IqxNBxfv3LdWt9MDmcWDQ1fagDH918lOdVaQ=
105106
github.com/sagikazarmark/locafero v0.8.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
106-
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
107107
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
108+
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
108109
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
109110
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
110111
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
@@ -192,8 +193,9 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
192193
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
193194
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
194195
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
195-
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
196196
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
197+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
198+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
197199
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
198200
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
199201
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

internal/flagset/flagset.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"sort"
8+
"strings"
89

910
"github.com/open-feature/cli/internal/filesystem"
1011
"github.com/open-feature/cli/internal/manifest"
@@ -64,7 +65,7 @@ func Load(manifestPath string) (*Flagset, error) {
6465
if err != nil {
6566
return nil, err
6667
} else if len(validationErrors) > 0 {
67-
return nil, fmt.Errorf("validation failed: %v", validationErrors)
68+
return nil, errors.New(FormatValidationError(validationErrors))
6869
}
6970

7071
var flagset Flagset
@@ -132,3 +133,43 @@ func (fs *Flagset) UnmarshalJSON(data []byte) error {
132133

133134
return nil
134135
}
136+
func FormatValidationError(issues []manifest.ValidationError) string {
137+
var sb strings.Builder
138+
sb.WriteString("flag manifest validation failed:\n\n")
139+
140+
// Group messages by flag path
141+
grouped := make(map[string]struct {
142+
flagType string
143+
messages []string
144+
})
145+
146+
for _, issue := range issues {
147+
entry := grouped[issue.Path]
148+
entry.flagType = issue.Type
149+
entry.messages = append(entry.messages, issue.Message)
150+
grouped[issue.Path] = entry
151+
}
152+
153+
// Sort paths for consistent output
154+
paths := make([]string, 0, len(grouped))
155+
for path := range grouped {
156+
paths = append(paths, path)
157+
}
158+
sort.Strings(paths)
159+
160+
// Format each row
161+
for _, path := range paths {
162+
entry := grouped[path]
163+
flagType := entry.flagType
164+
if flagType == "" {
165+
flagType = "missing"
166+
}
167+
sb.WriteString(fmt.Sprintf(
168+
"- flagType: %s\n flagPath: %s\n errors:\n ~ %s\n \tSuggestions:\n \t- flagType: boolean\n \t- defaultValue: true\n\n",
169+
flagType,
170+
path,
171+
strings.Join(entry.messages, "\n ~ "),
172+
))
173+
}
174+
return sb.String()
175+
}

internal/flagset/flagset_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package flagset
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/open-feature/cli/internal/manifest"
8+
)
9+
10+
// Sample test for FormatValidationError
11+
func TestFormatValidationError_SortsByPath(t *testing.T) {
12+
issues := []manifest.ValidationError{
13+
{Path: "zeta.flag", Type: "boolean", Message: "must not be empty"},
14+
{Path: "alpha.flag", Type: "string", Message: "invalid value"},
15+
{Path: "beta.flag", Type: "number", Message: "must be greater than zero"},
16+
}
17+
18+
output := FormatValidationError(issues)
19+
20+
// The output should mention 'alpha.flag' before 'beta.flag', and 'beta.flag' before 'zeta.flag'
21+
alphaIdx := strings.Index(output, "flagPath: alpha.flag")
22+
betaIdx := strings.Index(output, "flagPath: beta.flag")
23+
zetaIdx := strings.Index(output, "flagPath: zeta.flag")
24+
25+
if !(alphaIdx < betaIdx && betaIdx < zetaIdx) {
26+
t.Errorf("flag paths are not sorted: alphaIdx=%d, betaIdx=%d, zetaIdx=%d\nOutput:\n%s",
27+
alphaIdx, betaIdx, zetaIdx, output)
28+
}
29+
}

0 commit comments

Comments
 (0)