Skip to content
Draft
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
35 changes: 35 additions & 0 deletions cel/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,41 @@ func (i *Issues) Errors() []*Error {
return i.errs.GetErrors()
}

// Source returns the source associated with the issues.
func (i *Issues) Source() Source {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually the best way to take care of this is to preserve the Source by calling cel.CompileSource and then performing these operations in terms of the Issues and Source values. This is usually more guaranteed to be correct than poking into the internal Issues state.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok ,makes sense. Seems the preferred pattern is for callers to create/preserve a Source, call CompileSource or ParseSource, and then map each issue’s Error.Location through Source.LocationOffset, rather than exposing more of the internal Issues state.

Would you prefer that I close this PR, convert it into docs/example coverage for that usage pattern, or narrow it to a smaller convenience API if there is still a gap worth covering?

if i == nil {
return nil
}
return i.errs.Source()
}

// ErrorOffset returns the start character offset for the error, if available.
func (i *Issues) ErrorOffset(err *Error) (int32, bool) {
offsetRange, found := i.ErrorOffsetRange(err)
if found {
return offsetRange.Start, true
}
return 0, false
}

// ErrorOffsetRange returns the character offset range for the error, if available.
func (i *Issues) ErrorOffsetRange(err *Error) (celast.OffsetRange, bool) {
if i == nil || err == nil {
return celast.OffsetRange{}, false
}
if i.info != nil {
if offsetRange, found := i.info.GetOffsetRange(err.ExprID); found {
return offsetRange, true
}
}
if src := i.Source(); src != nil {
if offset, found := src.LocationOffset(err.Location); found {
return celast.OffsetRange{Start: offset, Stop: offset}, true
}
}
return celast.OffsetRange{}, false
}

// Append collects the issues from another Issues struct into a new Issues object.
func (i *Issues) Append(other *Issues) *Issues {
if i == nil {
Expand Down
32 changes: 32 additions & 0 deletions cel/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,38 @@ ERROR: <input>:1:2: Syntax error: mismatched input '<EOF>' expecting {'[', '{',
}
}

func TestIssuesErrorOffsetRange(t *testing.T) {
e, err := NewEnv()
if err != nil {
t.Fatalf("NewEnv() failed: %v", err)
}
_, iss := e.Compile("missing")
if len(iss.Errors()) != 1 {
t.Fatalf("iss.Errors() got %v, wanted 1 error", iss.Errors())
}
errInfo := iss.Errors()[0]

offset, found := iss.ErrorOffset(errInfo)
if !found {
t.Fatal("ErrorOffset() got found false, wanted true")
}
if offset != 0 {
t.Errorf("ErrorOffset() got %d, wanted 0", offset)
}

offsetRange, found := iss.ErrorOffsetRange(errInfo)
if !found {
t.Fatal("ErrorOffsetRange() got found false, wanted true")
}
if offsetRange != (ast.OffsetRange{Start: 0, Stop: 7}) {
t.Errorf("ErrorOffsetRange() got %v, wanted [0, 7]", offsetRange)
}

if iss.Source() == nil {
t.Fatal("Source() got nil, wanted source")
}
}

func TestFormatCELTypeEquivalence(t *testing.T) {
values := []*Type{
AnyType,
Expand Down
5 changes: 5 additions & 0 deletions common/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ func (e *Errors) GetErrors() []*Error {
return e.errors[:]
}

// Source returns the source associated with the errors.
func (e *Errors) Source() Source {
return e.source
}

// Append creates a new Errors object with the current and input errors.
func (e *Errors) Append(errs []*Error) *Errors {
return &Errors{
Expand Down