this repo has no description
0
fork

Configure Feed

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

cue: fail on required field in various cases

In the cue package, structValue used to keep a list
of features to include. This is no longer necessary, as
optional and required fields are now arcs as well.
We replace it with a list of arcs instead. This allows
replacement arcs to be generated to reflect a required
field error when appropriate.

Fixes #2309

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

+132 -69
+47 -27
cue/types.go
··· 88 88 // 89 89 // TODO: remove 90 90 type structValue struct { 91 - ctx *adt.OpContext 92 - v Value 93 - obj *adt.Vertex 94 - features []adt.Feature 91 + ctx *adt.OpContext 92 + v Value 93 + obj *adt.Vertex 94 + arcs []*adt.Vertex 95 95 } 96 96 97 97 type hiddenStructValue = structValue ··· 101 101 if o.obj == nil { 102 102 return 0 103 103 } 104 - return len(o.features) 104 + return len(o.arcs) 105 105 } 106 106 107 107 // At reports the key and value of the ith field, i < o.Len(). 108 108 func (o *hiddenStructValue) At(i int) (key string, v Value) { 109 - f := o.features[i] 110 - return o.v.idx.LabelStr(f), newChildValue(o, i) 109 + arc := o.arcs[i] 110 + return o.v.idx.LabelStr(arc.Label), newChildValue(o, i) 111 111 } 112 112 113 113 func (o *hiddenStructValue) at(i int) *adt.Vertex { 114 - f := o.features[i] 115 - return o.obj.Lookup(f) 114 + return o.arcs[i] 116 115 } 117 116 118 117 // Lookup reports the field for the given key. The returned Value is invalid ··· 122 121 i := 0 123 122 len := o.Len() 124 123 for ; i < len; i++ { 125 - if o.features[i] == f { 124 + if o.arcs[i].Label == f { 126 125 break 127 126 } 128 127 } ··· 1386 1385 } 1387 1386 } 1388 1387 1388 + // features are topologically sorted. 1389 + // TODO(sort): make sort order part of the evaluator and eliminate this. 1389 1390 features := export.VertexFeatures(ctx, obj) 1390 1391 1391 - k := 0 1392 + arcs := make([]*adt.Vertex, 0, len(obj.Arcs)) 1393 + 1392 1394 for _, f := range features { 1393 1395 if f.IsLet() { 1394 1396 continue ··· 1403 1405 if arc == nil { 1404 1406 continue 1405 1407 } 1406 - if arc.IsConstraint() && o.omitOptional { 1407 - continue 1408 + switch arc.ArcType { 1409 + case adt.ArcOptional: 1410 + if o.omitOptional { 1411 + continue 1412 + } 1413 + case adt.ArcRequired: 1414 + // We report an error for required fields if the configuration is 1415 + // final or concrete. We also do so if omitOptional is true, as 1416 + // it avoids hiding errors in required fields. 1417 + if o.omitOptional || o.concrete || o.final { 1418 + arc = &adt.Vertex{ 1419 + Label: arc.Label, 1420 + Parent: arc.Parent, 1421 + Conjuncts: arc.Conjuncts, 1422 + BaseValue: adt.NewRequiredNotPresentError(ctx, arc), 1423 + } 1424 + arc.ForceDone() 1425 + } 1408 1426 } 1409 - features[k] = f 1410 - k++ 1427 + arcs = append(arcs, arc) 1411 1428 } 1412 - features = features[:k] 1413 - return structValue{ctx, v, obj, features}, nil 1429 + return structValue{ctx, v, obj, arcs}, nil 1414 1430 } 1415 1431 1416 1432 // Struct returns the underlying struct of a value or an error if the value ··· 1476 1492 // it interprets name as an arbitrary string for a regular field. 1477 1493 func (s *hiddenStruct) FieldByName(name string, isIdent bool) (FieldInfo, error) { 1478 1494 f := s.v.idx.Label(name, isIdent) 1479 - for i, a := range s.features { 1480 - if a == f { 1495 + for i, a := range s.arcs { 1496 + if a.Label == f { 1481 1497 return s.Field(i), nil 1482 1498 } 1483 1499 } ··· 1501 1517 return &Iterator{idx: v.idx, ctx: ctx}, v.toErr(err) 1502 1518 } 1503 1519 1504 - arcs := []*adt.Vertex{} 1505 - for i := range obj.features { 1506 - arc := obj.at(i) 1507 - arcs = append(arcs, arc) 1508 - } 1509 - return &Iterator{idx: v.idx, ctx: ctx, val: v, arcs: arcs}, nil 1520 + return &Iterator{idx: v.idx, ctx: ctx, val: v, arcs: obj.arcs}, nil 1510 1521 } 1511 1522 1512 1523 // Lookup reports the value at a path starting from v. The empty path returns v ··· 2214 2225 2215 2226 // Walk descends into all values of v, calling f. If f returns false, Walk 2216 2227 // will not descent further. It only visits values that are part of the data 2217 - // model, so this excludes optional fields, hidden fields, and definitions. 2228 + // model, so this excludes definitions and optional, required, and hidden 2229 + // fields. 2218 2230 func (v Value) Walk(before func(Value) bool, after func(Value)) { 2219 2231 ctx := v.ctx() 2220 2232 switch v.Kind() { ··· 2222 2234 if before != nil && !before(v) { 2223 2235 return 2224 2236 } 2225 - obj, _ := v.structValData(ctx) 2237 + obj, _ := v.structValOpts(ctx, options{ 2238 + omitHidden: true, 2239 + omitDefinitions: true, 2240 + }) 2226 2241 for i := 0; i < obj.Len(); i++ { 2227 2242 _, v := obj.At(i) 2243 + // TODO: should we error on required fields, or visit them anyway? 2244 + // Walk is not designed to error at this moment, though. 2245 + if v.v.ArcType != adt.ArcMember { 2246 + continue 2247 + } 2228 2248 v.Walk(before, after) 2229 2249 } 2230 2250 case ListKind:
+74 -36
cue/types_test.go
··· 798 798 if step1.value > 100 { 799 799 }`, 800 800 err: "undefined field: value", 801 + }, { 802 + value: `{a!: 1, b?: 2, c: 3}`, 803 + err: "a: field is required but not present", 801 804 }} 802 805 for _, tc := range testCases { 803 806 t.Run(tc.value, func(t *testing.T) { ··· 895 898 [string]: int64 896 899 } & #V 897 900 v: #X 901 + 902 + a: { 903 + b!: 1 904 + c: 2 905 + } 898 906 `) 899 907 if err != nil { 900 908 t.Fatalf("compile: %v", err) ··· 904 912 // log.Fatalf("parseExpr: %v", err) 905 913 // } 906 914 // v := inst.Eval(expr) 907 - testCases := []struct { 908 - ref []string 909 - raw string 910 - eval string 911 - }{{ 912 - ref: []string{"v", "x"}, 913 - raw: ">=-9223372036854775808 & <=9223372036854775807 & int", 914 - eval: "int64", 915 + 916 + type testCase struct { 917 + ref []string 918 + result string 919 + syntax string 920 + } 921 + testCases := []testCase{{ 922 + ref: []string{"a"}, 923 + result: `{ 924 + b!: 1 925 + c: 2 926 + }`, 927 + syntax: "{b!: 1, c: 2}", 928 + }, { 929 + // Allow descending into structs even if it has a required field error. 930 + ref: []string{"a", "c"}, 931 + result: "2", 932 + syntax: "2", 933 + }, { 934 + ref: []string{"a", "b"}, 935 + result: "_|_ // a.b: field is required but not present", 936 + syntax: "1", 937 + }, { 938 + ref: []string{"v", "x"}, 939 + result: "int64", 940 + syntax: "int64", 915 941 }} 916 942 for _, tc := range testCases { 917 - v := inst.Lookup(tc.ref...) 943 + t.Run("", func(t *testing.T) { 944 + v := inst.Lookup(tc.ref...) 918 945 919 - if got := fmt.Sprintf("%#v", v); got != tc.raw { 920 - t.Errorf("got %v; want %v", got, tc.raw) 921 - } 946 + if got := fmt.Sprintf("%+v", v); got != tc.result { 947 + t.Errorf("got %v; want %v", got, tc.result) 948 + } 949 + 950 + got := fmt.Sprint(astinternal.DebugStr(v.Eval().Syntax())) 951 + if got != tc.syntax { 952 + t.Errorf("got %v; want %v", got, tc.syntax) 953 + } 922 954 923 - got := fmt.Sprint(astinternal.DebugStr(v.Eval().Syntax())) 924 - if got != tc.eval { 925 - t.Errorf("got %v; want %v", got, tc.eval) 926 - } 955 + v = inst.Lookup() 956 + for _, ref := range tc.ref { 957 + s, err := v.Struct() 958 + if err != nil { 959 + t.Fatal(err) 960 + } 961 + fi, err := s.FieldByName(ref, false) 962 + if err != nil { 963 + t.Fatal(err) 964 + } 965 + v = fi.Value 927 966 928 - v = inst.Lookup() 929 - for _, ref := range tc.ref { 930 - s, err := v.Struct() 931 - if err != nil { 932 - t.Fatal(err) 967 + // Struct gets all fields. Skip tests with optional fields, 968 + // as the result will differ. 969 + if v.v.ArcType != adt.ArcMember { 970 + return 971 + } 933 972 } 934 - fi, err := s.FieldByName(ref, false) 935 - if err != nil { 936 - t.Fatal(err) 937 - } 938 - v = fi.Value 939 - } 940 973 941 - if got := fmt.Sprintf("%#v", v); got != tc.raw { 942 - t.Errorf("got %v; want %v", got, tc.raw) 943 - } 974 + if got := fmt.Sprintf("%+v", v); got != tc.result { 975 + t.Errorf("got %v; want %v", got, tc.result) 976 + } 944 977 945 - got = fmt.Sprint(astinternal.DebugStr(v.Eval().Syntax())) 946 - if got != tc.eval { 947 - t.Errorf("got %v; want %v", got, tc.eval) 948 - } 978 + got = fmt.Sprint(astinternal.DebugStr(v.Eval().Syntax())) 979 + if got != tc.syntax { 980 + t.Errorf("got %v; want %v", got, tc.syntax) 981 + } 982 + }) 949 983 } 950 984 } 951 985 ··· 2594 2628 // TODO: unwrap marshal error 2595 2629 // TODO: improve error messages 2596 2630 func TestMarshalJSON(t *testing.T) { 2597 - testCases := []struct { 2631 + type testCase struct { 2598 2632 value string 2599 2633 json string 2600 2634 err string 2601 - }{{ 2635 + } 2636 + testCases := []testCase{{ 2602 2637 value: `""`, 2603 2638 json: `""`, 2604 2639 }, { ··· 2662 2697 }, { 2663 2698 value: `{foo?: 1, bar?: 2, baz: 3}`, 2664 2699 json: `{"baz":3}`, 2700 + }, { 2701 + value: `{foo!: 1, bar: 2}`, 2702 + err: "cue: marshal error: foo: field is required but not present", 2665 2703 }, { 2666 2704 // Has an unresolved cycle, but should not matter as all fields involved 2667 2705 // are optional
+10
internal/core/adt/errors.go
··· 206 206 } 207 207 } 208 208 209 + func NewRequiredNotPresentError(ctx *OpContext, v *Vertex) *Bottom { 210 + saved := ctx.PushArc(v) 211 + b := &Bottom{ 212 + Code: IncompleteError, 213 + Err: ctx.Newf("field is required but not present"), 214 + } 215 + ctx.PopArc(saved) 216 + return b 217 + } 218 + 209 219 // A ValueError is returned as a result of evaluating a value. 210 220 type ValueError struct { 211 221 r Runtime
+1 -6
internal/core/validate/validate.go
··· 99 99 100 100 for _, a := range x.Arcs { 101 101 if a.ArcType == adt.ArcRequired && v.inDefinition == 0 { 102 - saved := v.ctx.PushArc(a) 103 - v.add(&adt.Bottom{ 104 - Code: adt.IncompleteError, 105 - Err: v.ctx.Newf("field is required but not present"), 106 - }) 107 - v.ctx.PopArc(saved) 102 + v.add(adt.NewRequiredNotPresentError(v.ctx, a)) 108 103 continue 109 104 } 110 105