Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cue): get error position using error path #2701

Merged
merged 3 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion internal/cue/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"strconv"

"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
Expand Down Expand Up @@ -114,6 +115,7 @@ func (v FeaturesValidator) validateSingleDocument(file string, f *ast.File, offs
Validate(cue.All(), cue.Concrete(true))

var errs []error
OUTER:
for _, e := range cueerrors.Errors(err) {
rerr := Error{
Message: e.Error(),
Expand All @@ -122,11 +124,38 @@ func (v FeaturesValidator) validateSingleDocument(file string, f *ast.File, offs
},
}

// if the error has path segments we're going to use that
// to select into the original document
// we parse the slice of the path into selector
selectors := []cue.Selector{}
for _, p := range e.Path() {
if i, err := strconv.ParseInt(p, 10, 64); err == nil {
selectors = append(selectors, cue.Index(int(i)))
continue
}

selectors = append(selectors, cue.Str(p))
}

// next we walk the selector back from the deapest path until
// we select something that exists in the document
for i := len(selectors); i > 0; i-- {
selectors = selectors[:i]
val := yv.LookupPath(cue.MakePath(selectors...))

// if we manage to locate something then we use that
// position in our error message
if pos := val.Pos(); pos.IsValid() {
rerr.Location.Line = pos.Line() + offset
errs = append(errs, rerr)
continue OUTER
}
}

if pos := cueerrors.Positions(e); len(pos) > 0 {
p := pos[len(pos)-1]
rerr.Location.Line = p.Line() + offset
}

errs = append(errs, rerr)
}

Expand Down
4 changes: 3 additions & 1 deletion internal/cue/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,7 @@ func TestValidate_Extended(t *testing.T) {

assert.Equal(t, `flags.1.description: incomplete value =~"^.+$"`, ferr.Message)
assert.Equal(t, "testdata/valid.yaml", ferr.Location.File)
assert.Equal(t, 2, ferr.Location.Line) // TODO: why 2?
// location of the start of the boolean flag
// which lacks a description
assert.Equal(t, 30, ferr.Location.Line)
}
6 changes: 3 additions & 3 deletions internal/storage/fs/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ func TestSnapshotFromFS_Invalid(t *testing.T) {
{
path: "testdata/invalid/namespace",
err: errors.Join(
cue.Error{Message: "namespace: 2 errors in empty disjunction:", Location: cue.Location{File: "features.json", Line: 0}},
cue.Error{Message: "namespace: conflicting values 1 and \"default\" (mismatched types int and string)", Location: cue.Location{File: "features.json", Line: 3}},
cue.Error{Message: "namespace: conflicting values 1 and string (mismatched types int and string)", Location: cue.Location{File: "features.json", Line: 3}},
cue.Error{Message: "namespace: 2 errors in empty disjunction:", Location: cue.Location{File: "features.json", Line: 1}},
cue.Error{Message: "namespace: conflicting values 1 and \"default\" (mismatched types int and string)", Location: cue.Location{File: "features.json", Line: 1}},
cue.Error{Message: "namespace: conflicting values 1 and string (mismatched types int and string)", Location: cue.Location{File: "features.json", Line: 1}},
),
},
} {
Expand Down
Loading