cue: improve marshaling error message
And show a useful position at which the
marshaling fails. Adding file locations may
even be better, but will be a bit more involved.
Change-Id: If047ab6b55f4c062af8d27b016feaa69de65709b
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2002
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cmd/cue/cmd/export.go b/cmd/cue/cmd/export.go
index 5f651d0..ae6fb8e 100644
--- a/cmd/cue/cmd/export.go
+++ b/cmd/cue/cmd/export.go
@@ -83,6 +83,9 @@
root := instances[0].Value()
err := e.Encode(root)
if err != nil {
+ if x, ok := err.(*json.MarshalerError); ok {
+ err = x.Err
+ }
fmt.Fprintln(w, err)
}
return nil
diff --git a/cmd/cue/cmd/export_test.go b/cmd/cue/cmd/export_test.go
index 4431cb1..77f7f83 100644
--- a/cmd/cue/cmd/export_test.go
+++ b/cmd/cue/cmd/export_test.go
@@ -18,4 +18,5 @@
func TestExport(t *testing.T) {
runCommand(t, exportCmd.RunE, "export")
+ runCommand(t, exportCmd.RunE, "export_err")
}
diff --git a/cmd/cue/cmd/testdata/exporterr/export_err.cue b/cmd/cue/cmd/testdata/exporterr/export_err.cue
new file mode 100644
index 0000000..480a942
--- /dev/null
+++ b/cmd/cue/cmd/testdata/exporterr/export_err.cue
@@ -0,0 +1,5 @@
+package exporterr
+
+a: {
+ b: [0, 1, {c: int}, 3]
+}
diff --git a/cmd/cue/cmd/testdata/exporterr/export_err.out b/cmd/cue/cmd/testdata/exporterr/export_err.out
new file mode 100644
index 0000000..8ce55eb
--- /dev/null
+++ b/cmd/cue/cmd/testdata/exporterr/export_err.out
@@ -0,0 +1 @@
+cue: marshal error at path a.b.2.c: cannot convert incomplete value "int" to JSON
diff --git a/cue/types.go b/cue/types.go
index b3d0bcc..90b9510 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -112,20 +112,20 @@
// MarshalJSON returns a valid JSON encoding or reports an error if any of the
// fields is invalid.
-func (o *structValue) MarshalJSON() (b []byte, err error) {
+func (o *structValue) marshalJSON() (b []byte, err error) {
b = append(b, '{')
n := o.Len()
for i := 0; i < n; i++ {
k, v := o.At(i)
s, err := json.Marshal(k)
if err != nil {
- return nil, err
+ return nil, unwrapJSONError(k, err)
}
b = append(b, s...)
b = append(b, ':')
bb, err := json.Marshal(v)
if err != nil {
- return nil, err
+ return nil, unwrapJSONError(k, err)
}
b = append(b, bb...)
if i < n-1 {
@@ -136,6 +136,34 @@
return b, nil
}
+type marshalError struct {
+ path string
+ err error
+ // TODO: maybe also collect the file locations contributing to the
+ // failing values?
+}
+
+func (e *marshalError) Error() string {
+ return fmt.Sprintf("cue: marshal error at path %s: %v", e.path, e.err)
+}
+
+func unwrapJSONError(key interface{}, err error) error {
+ if je, ok := err.(*json.MarshalerError); ok {
+ err = je.Err
+ }
+ path := make([]string, 0, 2)
+ if key != nil {
+ path = append(path, fmt.Sprintf("%v", key))
+ }
+ if me, ok := err.(*marshalError); ok {
+ if me.path != "" {
+ path = append(path, me.path)
+ }
+ err = me.err
+ }
+ return &marshalError{strings.Join(path, "."), err}
+}
+
// An Iterator iterates over values.
//
type Iterator struct {
@@ -193,10 +221,10 @@
func marshalList(l *Iterator) (b []byte, err error) {
b = append(b, '[')
if l.Next() {
- for {
+ for i := 0; ; i++ {
x, err := json.Marshal(l.Value())
if err != nil {
- return nil, err
+ return nil, unwrapJSONError(i, err)
}
b = append(b, x...)
if !l.Next() {
@@ -583,6 +611,14 @@
// MarshalJSON marshalls this value into valid JSON.
func (v Value) MarshalJSON() (b []byte, err error) {
+ b, err = v.marshalJSON()
+ if err != nil {
+ return nil, unwrapJSONError(nil, err)
+ }
+ return b, nil
+}
+
+func (v Value) marshalJSON() (b []byte, err error) {
v, _ = v.Default()
if v.path == nil {
return json.Marshal(nil)
@@ -607,7 +643,7 @@
return marshalList(&i)
case structKind:
obj, _ := v.structVal(ctx)
- return obj.MarshalJSON()
+ return obj.marshalJSON()
case bottomKind:
return nil, x.(*bottom)
default: