internal/core/adt: improve errors for validator conflicts
This also handles possible nil interface issues, which
are a result of the API.
Perhaps builtin implementations should not return Bottom,
but it _is_ a nice way to signal something is a validator.
The returned Bottom gets rewrapped with a standard
validator message up the stack, which also addes more
position information.
Fixes #627
Change-Id: I358a403432a74042410e34ed13847f294ad607e2
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8206
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index 679d493..aba1e14 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -106,13 +106,13 @@
test("struct", `struct.MinFields(2) & {a: 1}`),
// TODO: original value may be better.
// `_|_(invalid value {a:1} (does not satisfy struct.MinFields(2)))`,
- `_|_(struct has 1 fields < MinFields(2))`,
+ `_|_(invalid value {a:1} (does not satisfy struct.MinFields(2)): len(fields) < MinFields(2) (1 < 2))`,
}, {
test("time", `time.Time & "1937-01-01T12:00:27.87+00:20"`),
`"1937-01-01T12:00:27.87+00:20"`,
}, {
test("time", `time.Time & "no time"`),
- `_|_(error in call to time.Time: invalid time "no time")`,
+ `_|_(invalid value "no time" (does not satisfy time.Time): error in call to time.Time: invalid time "no time")`,
}, {
test("time", `time.Unix(1500000000, 123456)`),
`"2017-07-14T02:40:00.000123456Z"`,
diff --git a/cue/testdata/builtins/validators.txtar b/cue/testdata/builtins/validators.txtar
index bafa49e..541297b 100644
--- a/cue/testdata/builtins/validators.txtar
+++ b/cue/testdata/builtins/validators.txtar
@@ -123,7 +123,9 @@
incompleteError2: (struct){
MyType: (struct){
kv: (_|_){
- // [incomplete] struct has 0 fields < MinFields(1)
+ // [incomplete] incompleteError2.MyType.kv: invalid value {} (does not satisfy struct.MinFields(1)): len(fields) < MinFields(1) (0 < 1):
+ // ./in.cue:22:17
+ // ./in.cue:22:34
}
}
foo: (struct){
@@ -144,7 +146,9 @@
violation: (struct){
#MyType: (#struct){
kv: (_|_){
- // [incomplete] struct has 0 fields < MinFields(1)
+ // [incomplete] violation.#MyType.kv: invalid value {} (does not satisfy struct.MinFields(1)): len(fields) < MinFields(1) (0 < 1):
+ // ./in.cue:49:17
+ // ./in.cue:49:34
}
}
foo: (#struct){
diff --git a/cue/testdata/resolve/013_custom_validators.txtar b/cue/testdata/resolve/013_custom_validators.txtar
index 6ec7803..aade1ce 100644
--- a/cue/testdata/resolve/013_custom_validators.txtar
+++ b/cue/testdata/resolve/013_custom_validators.txtar
@@ -23,6 +23,7 @@
Errors:
b: invalid value "dog" (does not satisfy strings.ContainsAny("c")):
./in.cue:6:4
+ ./in.cue:6:24
Result:
(_|_){
@@ -31,6 +32,7 @@
b: (_|_){
// [eval] b: invalid value "dog" (does not satisfy strings.ContainsAny("c")):
// ./in.cue:6:4
+ // ./in.cue:6:24
}
c: (string){ "dog" }
}
diff --git a/internal/core/adt/errors.go b/internal/core/adt/errors.go
index ee55836..002ecc7 100644
--- a/internal/core/adt/errors.go
+++ b/internal/core/adt/errors.go
@@ -207,6 +207,7 @@
v *Vertex
pos token.Pos
auxpos []token.Pos
+ err errors.Error
errors.Message
}
@@ -302,3 +303,7 @@
}
return a
}
+
+func (e ValueError) Unwrap() error {
+ return e.err
+}
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 74031fa..cb25c4c 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -1232,13 +1232,20 @@
}
func validateWithBuiltin(c *OpContext, src token.Pos, b *Builtin, args []Value) *Bottom {
+ var severeness ErrorCode
+ var err errors.Error
+
res := b.call(c, src, args)
switch v := res.(type) {
case nil:
return nil
case *Bottom:
- return v
+ if v == nil {
+ return nil // caught elsewhere, but be defensive.
+ }
+ severeness = v.Code
+ err = v.Err
case *Bool:
if v.B {
@@ -1262,7 +1269,15 @@
}
buf.WriteString(")")
}
- return c.NewErrf("invalid value %s (does not satisfy %s)", c.Str(args[0]), buf.String())
+
+ vErr := c.NewPosf(src, "invalid value %s (does not satisfy %s)", c.Str(args[0]), buf.String())
+ vErr.err = err
+
+ for _, v := range args {
+ vErr.AddPosition(v)
+ }
+
+ return &Bottom{Code: severeness, Err: vErr}
}
// A Disjunction represents a disjunction, where each disjunct may or may not
diff --git a/pkg/internal/builtin.go b/pkg/internal/builtin.go
index ab23868..7105378 100644
--- a/pkg/internal/builtin.go
+++ b/pkg/internal/builtin.go
@@ -143,10 +143,20 @@
case nil:
// Validators may return a nil in case validation passes.
return nil
+ case *adt.Bottom:
+ // deal with API limitation: catch nil interface issue.
+ if v != nil {
+ return v
+ }
+ return nil
case adt.Value:
return v
case bottomer:
- return v.Bottom()
+ // deal with API limitation: catch nil interface issue.
+ if b := v.Bottom(); b != nil {
+ return b
+ }
+ return nil
}
if c.Err != nil {
return nil
diff --git a/pkg/regexp/testdata/gen.txtar b/pkg/regexp/testdata/gen.txtar
index 8560e28..c0f213d 100644
--- a/pkg/regexp/testdata/gen.txtar
+++ b/pkg/regexp/testdata/gen.txtar
@@ -23,7 +23,7 @@
error in call to regexp.FindAll: no match
error in call to regexp.FindAllNamedSubmatch: no match
error in call to regexp.FindAllSubmatch: no match
-error in call to regexp.Valid: error parsing regexp: unexpected ): `invalid)`
+t14: invalid value "invalid)" (does not satisfy regexp.Valid): error in call to regexp.Valid: error parsing regexp: unexpected ): `invalid)`
Result:
t1: "foo"
@@ -53,5 +53,5 @@
}]
t12: _|_ // error in call to regexp.FindAllNamedSubmatch: no match (and 1 more errors)
t13: "valid"
-t14: _|_ // error in call to regexp.Valid: error parsing regexp: unexpected ): `invalid)`
+t14: _|_ // t14: invalid value "invalid)" (does not satisfy regexp.Valid): error in call to regexp.Valid: error parsing regexp: unexpected ): `invalid)`
diff --git a/pkg/strings/testdata/gen.txtar b/pkg/strings/testdata/gen.txtar
index 837820b..636fd38 100644
--- a/pkg/strings/testdata/gen.txtar
+++ b/pkg/strings/testdata/gen.txtar
@@ -25,12 +25,16 @@
0: error in call to strings.Join: element 0 of list argument 0: cannot use value 1 (type int) as string
t10: invalid value "quux" (does not satisfy strings.MaxRunes(3)):
./in.cue:12:6
+ ./in.cue:12:23
t12: invalid value "e" (does not satisfy strings.MaxRunes(0)):
./in.cue:14:6
+ ./in.cue:14:23
t16: invalid value "hello" (does not satisfy strings.MaxRunes(3)):
./in.cue:18:6
+ ./in.cue:18:23
t17: invalid value "hello" (does not satisfy strings.MinRunes(10)):
./in.cue:19:6
+ ./in.cue:19:23
Result:
t1: "Hello World!"
diff --git a/pkg/struct/struct.go b/pkg/struct/struct.go
index d9b034e..2dd37ca 100644
--- a/pkg/struct/struct.go
+++ b/pkg/struct/struct.go
@@ -36,7 +36,7 @@
if count < n {
return &adt.Bottom{
Code: adt.IncompleteError, // could still be resolved
- Err: errors.Newf(token.NoPos, "struct has %d fields < MinFields(%d)", count, n),
+ Err: errors.Newf(token.NoPos, "len(fields) < MinFields(%[2]d) (%[1]d < %[2]d)", count, n),
}
}
return nil
diff --git a/pkg/struct/testdata/gen.txtar b/pkg/struct/testdata/gen.txtar
index 1ef363b..a73f573 100644
--- a/pkg/struct/testdata/gen.txtar
+++ b/pkg/struct/testdata/gen.txtar
@@ -15,6 +15,7 @@
./in.cue:3:27
t4: invalid value {a:1} (does not satisfy struct.MaxFields(0)):
./in.cue:6:5
+ ./in.cue:6:22
Result:
import "struct"
diff --git a/pkg/time/testdata/gen.txtar b/pkg/time/testdata/gen.txtar
index 6c02b50..235744d 100644
--- a/pkg/time/testdata/gen.txtar
+++ b/pkg/time/testdata/gen.txtar
@@ -8,10 +8,10 @@
t3: time.Unix(1500000000, 123456)
-- out/time --
Errors:
-error in call to time.Time: invalid time "no time"
+t2: invalid value "no time" (does not satisfy time.Time): error in call to time.Time: invalid time "no time"
Result:
t1: "1937-01-01T12:00:27.87+00:20"
-t2: _|_ // error in call to time.Time: invalid time "no time"
+t2: _|_ // t2: invalid value "no time" (does not satisfy time.Time): error in call to time.Time: invalid time "no time"
t3: "2017-07-14T02:40:00.000123456Z"