lib/errors.go (115 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package lib
import (
"fmt"
"path"
"runtime"
"strings"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)
// DecoratedError implements error source location rendering.
type DecoratedError struct {
AST *cel.Ast
Err error
}
func (e DecoratedError) Error() string {
if e.Err == nil {
return "<nil>"
}
id, ok := nodeID(e.Err)
if !ok {
return e.Err.Error()
}
if id == 0 {
return fmt.Sprintf("%v: unset node id", e.Err)
}
if e.AST == nil {
return fmt.Sprintf("%v: node %d", e.Err, id)
}
loc := e.AST.NativeRep().SourceInfo().GetStartLocation(id)
errs := common.NewErrors(e.AST.Source())
errs.ReportErrorAtID(id, loc, "%s", e.Err.Error())
return errs.ToDisplayString()
}
func nodeID(err error) (id int64, ok bool) {
if err == nil {
return 0, false
}
type nodeIDer interface {
NodeID() int64
}
for {
if node, ok := err.(nodeIDer); ok {
return node.NodeID(), true
}
switch x := err.(type) {
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
return 0, false
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if node, ok := err.(nodeIDer); ok {
return node.NodeID(), true
}
}
return 0, false
default:
return 0, false
}
}
}
type (
unop = func(value ref.Val) ref.Val
binop = func(lhs ref.Val, rhs ref.Val) ref.Val
varop = func(values ...ref.Val) ref.Val
bindings interface {
unop | binop | varop
}
)
func catch[B bindings](binding B) B {
switch binding := any(binding).(type) {
case unop:
return any(func(arg ref.Val) (ret ref.Val) {
defer handlePanic(&ret)
return binding(arg)
}).(B)
case binop:
return any(func(arg0, arg1 ref.Val) (ret ref.Val) {
defer handlePanic(&ret)
return binding(arg0, arg1)
}).(B)
case varop:
return any(func(args ...ref.Val) (ret ref.Val) {
defer handlePanic(&ret)
return binding(args...)
}).(B)
default:
panic("unreachable")
}
}
func handlePanic(ret *ref.Val) {
switch r := recover().(type) {
case nil:
return
default:
// We'll only try 64 stack frames deep. There are a few recursive
// functions in extensions, but panic in those functions are unlikely
// or are already explicitly recovered as part of normal operation.
pc := make([]uintptr, 64)
n := runtime.Callers(2, pc)
cf := runtime.CallersFrames(pc[:n])
for {
f, more := cf.Next()
if !more {
break
}
file := f.File
if strings.Contains(file, "mito/lib") {
_, file, _ := strings.Cut(file, "mito/")
*ret = types.NewErr("%s: %s %s:%d", r, path.Base(f.Function), file, f.Line)
return
}
}
*ret = types.NewErr("%s", r)
}
}