this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cue: add IsIncomplete for checking for incomplete errors

Currently `Value.Err` returns a non-nil error for incomplete errors,
even though it might be possible for the value to become complete later.
That isn't great, particularly when we want to unmarshal non-concrete
values into Go structs.

We could potentially change `Value.Err` so that it returns nil in such
cases, but that seems like a potentially dangerous change and might well
break existing clientrs.

Instead we add an `IsIncomplete` function to determine if an error _is_
an incomplete error, and use it inside `Value.Decode` so that we allow
decoding incomplete values when we're decoding into a CUE value.

Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: Ia6ddffe226be9f4dd7843915a1cc5f3ed88d33dd
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1229425
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>

+180 -2
+1 -1
cue/decode.go
··· 141 141 return 142 142 } 143 143 144 - if err := v.Err(); err != nil { 144 + if err := v.Err(); err != nil && !IsIncomplete(err) { 145 145 d.addErr(err) 146 146 return 147 147 }
+10 -1
cue/decode_test.go
··· 402 402 ... 403 403 } 404 404 `).Decode(&st) 405 - qt.Assert(t, qt.ErrorMatches(err, `field: 2 errors in empty disjunction:.*`)) 405 + qt.Assert(t, qt.IsNil(err)) 406 + qt.Assert(t, qt.Equals(fmt.Sprintf("%#v", st.Field), `(string | { 407 + #x!: { 408 + ... 409 + } 410 + ... 411 + }) & { 412 + #x: _ 413 + a: #x.b 414 + }`)) 406 415 }) 407 416 } 408 417
+29
cue/errors.go
··· 91 91 Err: errors.Newf(token.NoPos, "undefined value"), 92 92 } 93 93 94 + // IsIncomplete reports whether err is an incomplete error. 95 + // Incomplete errors occur when a value cannot be fully evaluated 96 + // due to missing information, such as unresolved references or 97 + // incomplete disjunctions. These errors may be acceptable in 98 + // non-concrete contexts. 99 + // 100 + // If the error returned by [Value.Err] is incomplete but the value 101 + // still exists (i.e., [Value.Exists] returns true), it typically means 102 + // the value is valid CUE but not fully resolved. In such cases, 103 + // [Value.Validate] with default options will return nil. 104 + func IsIncomplete(err error) bool { 105 + if err == nil { 106 + return false 107 + } 108 + // Fast path: 109 + if ve, ok := err.(*valueError); ok { 110 + return ve.err.IsIncomplete() 111 + } 112 + // Handle combined errors 113 + for _, e := range errors.Errors(err) { 114 + if ve, ok := e.(*valueError); ok { 115 + if ve.err.IsIncomplete() { 116 + return true 117 + } 118 + } 119 + } 120 + return false 121 + } 122 + 94 123 func mkErr(src adt.Node, args ...interface{}) *adt.Bottom { 95 124 var e *adt.Bottom 96 125 var code adt.ErrorCode = -1
+140
cue/errors_test.go
··· 1 + // Copyright 2026 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package cue_test 16 + 17 + import ( 18 + "testing" 19 + 20 + "cuelang.org/go/cue" 21 + "cuelang.org/go/cue/cuecontext" 22 + "github.com/go-quicktest/qt" 23 + ) 24 + 25 + func TestIsIncomplete(t *testing.T) { 26 + testCases := []struct { 27 + name string 28 + cueValue string 29 + path string // empty means use root value 30 + wantErr bool // whether Err() should return non-nil 31 + isIncomplete bool 32 + }{{ 33 + name: "field not found", 34 + cueValue: `a: 1`, 35 + path: "b", 36 + wantErr: true, 37 + isIncomplete: true, 38 + }, { 39 + name: "permanent error: type conflict", 40 + cueValue: `a: 1 & "foo"`, 41 + path: "a", 42 + wantErr: true, 43 + isIncomplete: false, 44 + }, { 45 + name: "empty disjunction or([])", 46 + cueValue: `#Test: or([])`, 47 + path: "#Test", 48 + wantErr: true, 49 + isIncomplete: true, 50 + }, { 51 + name: "invalid interpolation", 52 + cueValue: `input: string, x: "\(input)"`, 53 + path: "x", 54 + wantErr: true, 55 + isIncomplete: true, 56 + }, { 57 + name: "reference to undefined field", 58 + cueValue: `a: b`, 59 + path: "a", 60 + wantErr: true, 61 + // This is an eval error (permanent), not incomplete 62 + isIncomplete: false, 63 + }, { 64 + name: "required field missing", 65 + cueValue: `a!: int`, 66 + path: "a", 67 + wantErr: true, 68 + isIncomplete: true, 69 + }, { 70 + name: "concrete value - no error", 71 + cueValue: `a: 1`, 72 + path: "a", 73 + wantErr: false, 74 + isIncomplete: false, 75 + }, { 76 + name: "incomplete disjunction - no error from Err()", 77 + cueValue: `{#item: _, a: #item.b} | string`, 78 + path: "", 79 + // This is valid CUE, just incomplete - Err() returns nil 80 + wantErr: false, 81 + isIncomplete: false, 82 + }, { 83 + name: "cycle - no error from Err()", 84 + cueValue: `a: b, b: a`, 85 + path: "a", 86 + // Cycles are handled as valid incomplete values 87 + wantErr: false, 88 + isIncomplete: false, 89 + }, { 90 + name: "incomplete or with type conflict", 91 + cueValue: `or([]) & (1 & "foo")`, 92 + path: "", 93 + wantErr: true, 94 + // CUE prioritizes the permanent error (type conflict) over the incomplete error. 95 + // When both incomplete and permanent errors exist, Err() returns only the 96 + // permanent error. This means IsIncomplete correctly returns false. 97 + isIncomplete: false, 98 + }, { 99 + name: "struct with incomplete and permanent child errors", 100 + cueValue: `{a: or([]), b: 1 & "foo"}`, 101 + path: "", 102 + wantErr: true, 103 + // Root Err() shows only the permanent error from field b, not the 104 + // incomplete error from field a. CUE prioritizes permanent errors. 105 + isIncomplete: false, 106 + }, { 107 + name: "reference not found at root", 108 + cueValue: `x`, 109 + path: "", 110 + wantErr: true, 111 + isIncomplete: false, // eval error 112 + }, { 113 + name: "disjunction all arms fail", 114 + cueValue: `(1 | 2) & "foo"`, 115 + path: "", 116 + wantErr: true, 117 + isIncomplete: false, // eval error from failed disjunction 118 + }} 119 + 120 + ctx := cuecontext.New() 121 + for _, tc := range testCases { 122 + t.Run(tc.name, func(t *testing.T) { 123 + v := ctx.CompileString(tc.cueValue) 124 + if tc.path != "" { 125 + v = v.LookupPath(cue.ParsePath(tc.path)) 126 + } 127 + 128 + err := v.Err() 129 + 130 + if tc.wantErr { 131 + qt.Assert(t, qt.IsNotNil(err), qt.Commentf("expected error for %q path %q", tc.cueValue, tc.path)) 132 + } else { 133 + qt.Assert(t, qt.IsNil(err), qt.Commentf("expected no error for %q path %q", tc.cueValue, tc.path)) 134 + } 135 + 136 + qt.Check(t, qt.Equals(cue.IsIncomplete(err), tc.isIncomplete), 137 + qt.Commentf("IsIncomplete mismatch for error: %v", err)) 138 + }) 139 + } 140 + }