cue: keep path in API error value
Change-Id: Ib5fbeec7a7a8c826335f08335b40de597963b2c9
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4940
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/script/cmd_import.txt b/cmd/cue/cmd/testdata/script/cmd_import.txt
index 0db42f7..530e21d 100644
--- a/cmd/cue/cmd/testdata/script/cmd_import.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_import.txt
@@ -2,7 +2,7 @@
cmp stderr expect-stderr
-- expect-stderr --
-reference "cli" not found
+command.pkg.t: reference "cli" not found
-- task_tool.cue --
package home
diff --git a/cmd/cue/cmd/testdata/script/export_err.txt b/cmd/cue/cmd/testdata/script/export_err.txt
index 1bd2881..aeb2fe2 100644
--- a/cmd/cue/cmd/testdata/script/export_err.txt
+++ b/cmd/cue/cmd/testdata/script/export_err.txt
@@ -2,7 +2,7 @@
cmp stdout expect-stdout
cmp stderr expect-stderr
-- expect-stderr --
-a.b.2.c: cue: marshal error at path a.b.2.c: cannot convert incomplete value "int" to JSON:
+a.b.2.c: cannot convert incomplete value "int" to JSON:
./exporterr/export_err.cue:4:16
-- expect-stdout --
-- exporterr/export_err.cue --
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index ba92d99..d89bcba 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -112,7 +112,7 @@
`true`,
}, {
test("encoding/json", `json.Validate("{\"a\":10}", {a:<3})`),
- `_|_(error in call to encoding/json.Validate: invalid value 10 (out of bound <3))`,
+ `_|_(error in call to encoding/json.Validate: a: invalid value 10 (out of bound <3))`,
}, {
test("encoding/yaml", `yaml.Validate("a: 2\n---\na: 4", {a:<3})`),
`_|_(error in call to encoding/yaml.Validate: invalid value 4 (out of bound <3))`,
@@ -124,7 +124,7 @@
`_|_(error in call to encoding/yaml.Validate: value not an instance)`,
}, {
test("encoding/yaml", `yaml.ValidatePartial("a: 2\n---\na: 4", {a:<3})`),
- `_|_(error in call to encoding/yaml.ValidatePartial: invalid value 4 (out of bound <3))`,
+ `_|_(error in call to encoding/yaml.ValidatePartial: a: invalid value 4 (out of bound <3))`,
}, {
test("encoding/yaml", `yaml.ValidatePartial("a: 2\n---\na: 4", {a:<5})`),
`true`,
@@ -269,13 +269,13 @@
`[{a: 1, v: 2},{a: 1, v: 3},{a: 2, v: 1}]`,
}, {
test("list", `list.Sort([{a:1}, {b:2}], list.Ascending)`),
- `_|_(error in call to list.Sort: conflicting values close(T, close(T)) and {b: 2} (mismatched types number|string and struct))`,
+ `_|_(error in call to list.Sort: less: conflicting values close(T, close(T)) and {b: 2} (mismatched types number|string and struct))`,
}, {
test("list", `list.SortStrings(["b", "a"])`),
`["a","b"]`,
}, {
test("list", `list.SortStrings([1, 2])`),
- `_|_(invalid list element 0 in argument 0 to list.SortStrings: cannot use value 1 (type int) as string)`,
+ `_|_(invalid list element 0 in argument 0 to list.SortStrings: 0: cannot use value 1 (type int) as string)`,
}, {
test("list", `list.Sum([1, 2, 3, 4])`),
`10`,
@@ -387,7 +387,7 @@
`"Hello World!"`,
}, {
test("strings", `strings.Join([1, 2], " ")`),
- `_|_(invalid list element 0 in argument 0 to strings.Join: cannot use value 1 (type int) as string)`,
+ `_|_(invalid list element 0 in argument 0 to strings.Join: 0: cannot use value 1 (type int) as string)`,
}, {
test("strings", `strings.ByteAt("a", 0)`),
strconv.Itoa('a'),
diff --git a/cue/errors.go b/cue/errors.go
index 07c308e..b9e988f 100644
--- a/cue/errors.go
+++ b/cue/errors.go
@@ -33,6 +33,10 @@
errors.Message
}
+func (n *nodeError) Error() string {
+ return errors.String(n)
+}
+
func nodeErrorf(n ast.Node, format string, args ...interface{}) *nodeError {
return &nodeError{
n: n,
@@ -81,7 +85,7 @@
}
func (e *valueError) Error() string {
- return fmt.Sprint(e.err)
+ return errors.String(e)
}
func (e *valueError) Position() token.Pos {
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index f57aa66..11debee 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -460,6 +460,44 @@
return w.String()
}
+// String generates a short message from a given Error.
+func String(err Error) string {
+ w := &strings.Builder{}
+ writeErr(w, err)
+ return w.String()
+}
+
+func writeErr(w io.Writer, err Error) {
+ if path := strings.Join(err.Path(), "."); path != "" {
+ _, _ = io.WriteString(w, path)
+ _, _ = io.WriteString(w, ": ")
+ }
+
+ for {
+ u := errors.Unwrap(err)
+
+ printed := false
+ msg, args := err.Msg()
+ if msg != "" || u == nil { // print at least something
+ fmt.Fprintf(w, msg, args...)
+ printed = true
+ }
+
+ if u == nil {
+ break
+ }
+
+ if printed {
+ _, _ = io.WriteString(w, ": ")
+ }
+ err, _ = u.(Error)
+ if err == nil {
+ fmt.Fprint(w, u)
+ break
+ }
+ }
+}
+
func defaultFprintf(w io.Writer, format string, args ...interface{}) {
fmt.Fprintf(w, format, args...)
}
@@ -503,16 +541,18 @@
positions = append(positions, s)
}
- if path := Path(err); path != nil {
- fprintf(w, "%s: ", strings.Join(path, "."))
+ if e, ok := err.(Error); ok {
+ writeErr(w, e)
+ } else {
+ fprintf(w, "%v", err)
}
if len(positions) == 0 {
- fprintf(w, "%v\n", err)
+ fprintf(w, "\n")
return
}
- fprintf(w, "%v:\n", err)
+ fprintf(w, ":\n")
for _, pos := range positions {
fprintf(w, " %s\n", pos)
}
diff --git a/cue/examples_test.go b/cue/examples_test.go
index 7f48b96..ddb3f78 100644
--- a/cue/examples_test.go
+++ b/cue/examples_test.go
@@ -113,5 +113,5 @@
// Output:
// V2 is backwards compatible with V1: <nil>
- // V3 is backwards compatible with V2: conflicting values <=150 and string (mismatched types number and string)
+ // V3 is backwards compatible with V2: V2: conflicting values <=150 and string (mismatched types number and string)
}
diff --git a/cue/types.go b/cue/types.go
index 0ba8a4f..3b60faf 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -172,7 +172,7 @@
ctx := o.ctx
x := ctx.mkErr(o.obj, codeNotExist, "value %q not found", key)
v := x.evalPartial(ctx)
- return Value{ctx.index, &valueData{o.path, 0, arc{cache: v, v: x}}}
+ return Value{ctx.index, &valueData{o.path.parent, 0, arc{feature: o.path.feature, cache: v, v: x}}}
}
return newChildValue(o, i)
}
@@ -221,12 +221,7 @@
}
func (e *marshalError) Error() string {
- path := e.Path()
- if len(path) == 0 {
- return fmt.Sprintf("cue: marshal error: %v", e.err)
- }
- p := strings.Join(path, ".")
- return fmt.Sprintf("cue: marshal error at path %s: %v", p, e.err)
+ return fmt.Sprintf("cue: marshal error: %v", e.err)
}
func (e *marshalError) Path() []string { return e.err.Path() }
@@ -602,6 +597,22 @@
path *valueData
}
+func newErrValue(v Value, b *bottom) Value {
+ ctx := v.ctx()
+ p := v.path
+ if p == nil {
+ return newValueRoot(ctx, b)
+ }
+ return Value{
+ ctx.index,
+ &valueData{p.parent, p.index, arc{
+ feature: p.arc.feature,
+ cache: b,
+ v: b,
+ }},
+ }
+}
+
func newValueRoot(ctx *context, x value) Value {
v := x.evalPartial(ctx)
return Value{ctx.index, &valueData{nil, 0, arc{cache: v, v: x}}}
@@ -1175,20 +1186,28 @@
// Struct returns the underlying struct of a value or an error if the value
// is not a struct.
-func (v Value) Struct(opts ...Option) (*Struct, error) {
+func (v Value) Struct() (*Struct, error) {
+ obj, err := v.getStruct()
+ if err != nil {
+ return nil, v.toErr(err)
+ }
+ return &Struct{v, obj}, nil
+}
+
+func (v Value) getStruct() (*structLit, *bottom) {
ctx := v.ctx()
if err := v.checkKind(ctx, structKind); err != nil {
- return nil, v.toErr(err)
+ return nil, err
}
obj := v.eval(ctx).(*structLit)
// TODO: This is expansion appropriate?
obj, err := obj.expandFields(ctx)
if err != nil {
- return nil, v.toErr(err)
+ return nil, err
}
- return &Struct{v, obj}, nil
+ return obj, nil
}
// Struct represents a CUE struct value.
@@ -1285,7 +1304,7 @@
obj, err := v.structValData(ctx)
if err != nil {
// TODO: return a Value at the same location and a new error?
- return newValueRoot(ctx, err)
+ return newErrValue(v, err)
}
v = obj.Lookup(k)
}
@@ -1382,6 +1401,13 @@
if w.path == nil {
return v
}
+ if v.Err() != nil {
+ // TODO: perhaps keep both errors.
+ return v
+ }
+ if w.Err() != nil {
+ return w
+ }
a := v.path.v
b := w.path.v
src := binSrc(token.NoPos, opUnify, a, b)
diff --git a/cue/types_test.go b/cue/types_test.go
index 8ad986f..0f6b2d7 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -1880,7 +1880,7 @@
err: `c.0: cannot convert incomplete value`,
}, {
value: `{a: [{b: [0, {c: string}] }] }`,
- err: `path a.0.b.1.c: cannot convert incomplete value`,
+ err: `a.0.b.1.c: cannot convert incomplete value`,
}, {
value: `{foo?: 1, bar?: 2, baz: 3}`,
json: `{"baz":3}`,
diff --git a/cuego/examples_test.go b/cuego/examples_test.go
index b56a323..257771e 100644
--- a/cuego/examples_test.go
+++ b/cuego/examples_test.go
@@ -91,6 +91,6 @@
//Output:
// error: <nil>
// validate: <nil>
- // validate: invalid value 39 (out of bound <=12)
- // validate: invalid value "foo.jso" (does not match =~".json$")
+ // validate: MinCount: invalid value 39 (out of bound <=12)
+ // validate: Filename: invalid value "foo.jso" (does not match =~".json$")
}