--- tags: improving-overlay-err-msgs --- # Overlay error messages ## Problem When an overlay matcher's expectation fails, the error message `ytt` gives falls short of helping most users determine what to do next. For example, `test.yml` ```yaml= --- clients: - client1: secret: blah1 - client2: secret: blah2 #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.all --- clients: #@overlay/match by=overlay.all,expects="1+" - client1: secret: foo ``` results in this error message: ``` ytt: Error: Overlaying (in following order: test.yml): Document on line test.yml:10: Map item (key 'clients') on line test.yml:11: Array item on line test.yml:13: Map item (key 'client1') on line test.yml:13: Expected number of matched nodes to be 1, but was 0 ``` ## Analysis Specifically, we've identified the following deficiencies: 1. **missing "left" context** — there is no information about which document was being matched and where the expectation failed. 2. **information overload** — the error message contains a lot of contextualizing information which can be overwhelming, at least at first. 3. **buried the leid** — the most important bit of information — which matcher failed expectations — is not the most prominent/obvious piece of information. 4. **implied implicit context** — in this particular case, there's an implied matcher ... and _it's the one that's failing!_ In addition to filling those gaps, we observed... - **name the types** — given the overlapping nature of node types, and how crucial it can be to understand which node is involved in a failure. ## Proposals Examples refer to files named in [Use Cases](#Use-Cases). ### "Minimalist" _In fewest words possible, without losing clarity._ Variant A: ``` ytt: Error: Overlaying (in order: bar-nemi.yml): Expected: bar-nemi.yml:6 (map item) to match 1 in foo.yml:5 (map) but matched 0. ``` ``` ytt: Error: Overlaying (in order: bar-tmai.yml): Expected: bar-tmai.yml:6 (array item) to match 1 in foo.yml:2 (array) but matched 2 (foo.yml:3, foo.yml:5). ``` Variant B: _French quotes around types._ ``` ytt: Error: Overlaying (in order: bar-nemi.yml): Expected: bar-nemi.yml:6 «map item» to match 1 in foo.yml:5 «map» but matched 0. ``` ``` ytt: Error: Overlaying (in order: bar-tmai.yml): Expected: bar-tmai.yml:6 «array item» to match 1 in foo.yml:2 «array» but matched 2 (foo.yml:3, foo.yml:5). ``` Variant C: _In columns._ ``` ytt: Error: Overlaying (in order: bar-nemi.yml): Expected: bar-nemi.yml:6 (map item) to have matched 1 in foo.yml:5 (map) but has matched 0. ``` ``` ytt: Error: Overlaying (in order: bar-tmai.yml): Expected: bar-tmai.yml:6 «array item» to have matched 1 in foo.yml:2 «array» but has matched 2 (foo.yml:3, foo.yml:5). ``` Variant D: _Terse "match" tenses._ ``` ytt: Error: Overlaying (in order: bar-nemi.yml): Expected: bar-nemi.yml:6 (map item) to match 1 in foo.yml:5 (map) matched 0. ``` ``` ytt: Error: Overlaying (in order: bar-tmai.yml): Expected: bar-tmai.yml:6 «array item» to match 1 in foo.yml:2 «array» matched 2 (foo.yml:3, foo.yml:5). ``` ### "Failure separate from context" ``` ytt: Error: Expected overlay to match 1 map item in target, but matched 0. Overlay: bar-nemi.yml:6 (map item) Target: foo.yml:5 (map) ``` ``` ytt: Overlay error: Expected to match 1 map item in target, but matched 0. overlay: bar-nemi.yml:6 (map item) target: foo.yml:5 (map) ``` ``` ytt: Overlay error: Expected to match 1 map item in target, but matched 0. Overlay bar-nemi.yml on line 6 (map item) Inside foo.yml on line 5 (map) ``` ``` ytt: Overlay error: Expected to match 1 document in target, but matched 0. overlay: bar-nemi.yml:2 (document) target: <all documents> ``` ### XYZ ``` ytt: Overlay error: Expected to match 1 map item in target, but matched 0. overlay: bar-nemi.yml on line 6 (map item) base: foo.yml on line 5 (map) matcher: (implicit) "#@overlay/match by=<key equality>, expects=1" ``` ``` $ ytt .... ytt: Overlay error: Expected to match 1 map item in target, but matched 0. overlay: bar-nemi.yml on line 6 (map item) base: foo.yml on line 3 (map) matcher: '#@overlay/match by=overlay.subset({...}), expects="1+"' ``` ``` ytt: Overlay error: Expected to match 1 map item in target, but matched 0. matcher: '#@overlay/match by=overlay.subset({...}), expects="1+"' overlay: bar-nemi.yml:6 (map item) base: foo.yml:3 (map) ``` ``` ytt: Error: Overlay expected to match 1 map item in target, but matched 0. overlay: bar-nemi.yml on line 6 (map item) base: foo.yml on line 5 (map) matcher: (implicit) "#@overlay/match by=<key equality>, expects=1" ``` ``` ytt: Error: Overlay expected to match 1 map item in target, but matched 0. overlay: bar-nemi.yml on line 6 (map item, 'client1') base: foo.yml on line 5 (map, 'clients[1]') matcher: (implicit) "#@overlay/match by=<key equality>, expects=1" ``` ``` ytt: Error: Overlay expected to match 1 map item in target, but matched 0. overlay: bar-nemi.yml on line 6 (map item) base: foo.yml on line 5 (map) matcher: (implicit) "#@overlay/match by=<key equality>, expects=1" --- clients: - <expected to match 1 map item, matched 0> ``` ### "Named" _[Minimalist](#“Minimalist”) w/ names of items._ ``` ytt: Error: Overlaying (in order: bar-nemi.yml): Expected: bar-nemi.yml:6 (map item, 'client1') to match 1 item in foo.yml:5 (map in 'clients[1]') but matched 0. ``` ``` ytt: Error: Overlaying (in order: bar-tmai.yml): Expected: bar-tmai.yml:6: array item 'clients[0]' to match 1 in foo.yml:2: array 'clients[]' but matched 2 (foo.yml:3, foo.yml:5). ``` Note: - when calculating paths, walk up tree until first map item key. ```yaml= template: spec: containers: - name: nginx - - name: sidecar ``` - map item on line 3 is `'containers'` - array on line 4 is `'containers[]'` - array item on line 4 is `'containers[0]'` - map item on line 4 is `'containers[0].name'` - array item on line 5 is `'containers[1]'` - array on line 5 is `'containers[1][]'` - array item on line 5 is `'containers[1][0]'` - map item on line 5 is `'containers[1][0].name'` ### "Complete" _[Named](#“Named”) w/ failing matcher_ Variant A: _Name matcher function (including implicits)._ ``` ytt: Error: Overlaying (in order: bar-nemi.yml): Expected: bar-nemi.yml:6 (map item, 'client1') to match 1 (by <key equality>) item in foo.yml:5 (map in 'clients[1]') but matched 0. ``` ``` ytt: Error: Overlaying (in order: bar-tmai.yml): Expected: bar-tmai.yml:6: array item 'clients[0]' to match 1 (by `overlay.all`) in foo.yml:2: array 'clients[]' but matched 2 (foo.yml:3, foo.yml:5). ``` Note: - implied matcher indicated by angled brackets (`<key equality>`) - literal Starlark expressions indicated by back-tics (`overlay.subset(...)`) - map key short-hand expanded (`#@overlay/matcher by="name"` ==> ``by `overlay.map_key("name")` ``) Variant B: _Include entire annotation._ ``` ytt: Error: Overlaying (in order: bar-nemi.yml): Expected: "@overlay/match by=<key equality>, expects=1" (implicit) annotating bar-nemi.yml:6 (map item, 'client1') to match 1 item in foo.yml:5 (map in 'clients[1]') but matched 0. ``` ``` ytt: Error: Overlaying (in order: bar-tmai.yml): Expected: "#@overlay/match by=overlay.all,expects=1" annotating bar-tmai.yml:6: array item 'clients[0]' to match 1 in foo.yml:2: array 'clients[]' but matched 2 (foo.yml:3, foo.yml:5). ``` ### "Parsed" _Include trees of both overlay and target from match failure, up._ ``` ytt: Error: Overlaying (in order: bar-nemi.yml): bar-nemi.yml (overlay): 3: «document» — `overlay.all` over <all documents> 4: «map item» 'clients' — <key equality> over «map» @ foo.yml:2 5: «array item» [0] — `overlay.all` over «array» @ foo.yml:3 5: «map item» 'client1' — <key equality> over «map» @ foo.yml:5 foo.yml (target): 1: «document» 2: «map» 2: «map item» 'clients' 3: «array» 5: «array item» [1] 5: «map» Expected: bar-nemi.yml:6 «map item» 'client1' to match 1 (by <key equality>) item in foo.yml:5 «map» in 'clients[1]' but matched 0. ``` Note: - «array» and «map» are excluded from overlays since those kinds of nodes cannot be annotated. ### "Process Orientation" _Illustrate which step in execution failed._ ``` Progress: ✓ enumerate files ✓ prepare data values ✓ render templates ✗ overlay documents • render output ytt: Error: Overlay matcher failed to meet expectations: Expected: bar-nemi.yml:6 (map item, 'client1') to match 1 (by <key equality>) item in foo.yml:5 (map in 'clients[1]') but matched 0. ``` ### "Full" _Some form of all elements mentioned above, all together._ ``` ytt: Error: Progress: ✓ enumerate files ✓ prepare data values ✓ render templates ✗ overlay documents • render output bar-nemi.yml (overlay): 3: «document» — `overlay.all` over <all documents> 4: «map item» 'clients' — <key equality> over «map» @ foo.yml:2 5: «array item» [0] — `overlay.all` over «array» @ foo.yml:3 5: «map item» 'client1' — <key equality> over «map» @ foo.yml:5 foo.yml (target): 1: «document» 2: «map» 2: «map item» 'clients' 3: «array» 5: «array item» [1] 5: «map» Expected: bar-nemi.yml:6 «map item» 'client1' to match 1 (by <key equality>) item in foo.yml:5 «map» in 'clients[1]' but matched 0. ``` Variant B: ``` ytt: Overlay error: Expected to match 1 map item in target, but matched 0. Overlay bar-nemi.yml on line 6 (map item) Inside foo.yml on line 5 (map) bar-nemi.yml 3: --- 4: clients: 5: #@overlay/match by=overlay.all,expects="1+" 6: - client1: foo.yml 1: --- 2: clients: 5: - client2: ``` ``` ytt: Overlay error: Expected to match 1 map item in target, but matched 0. Overlay bar-nemi.yml on line 6 (map item) Inside foo.yml on line 5 (map) bar-nemi.yml 5: #@overlay/match by=overlay.all,expects="1+" 6: - client1: foo.yml 2: clients: ... 5: - client2: ``` Variant C: ``` ytt: Overlay error: Expected to match 1 map item in target map, but matched 0. (implicit) Overlay <key equality> matcher on map item bar-nemi.yml:6 | (implicit) #@overlay/match by=<key equality>, expects=0 bar-nemi.yml:6 | - client1: Inside map foo.yml:5 | - client2: ``` ``` ytt: Overlay error: Expected to match 1 map item in target map, but matched 0. Overlay matcher on map item bar-nemi.yml:5 | #@overlay/match by=overlay.all, expects=0 bar-nemi.yml:6 | - client1: ~ Inside map foo.yml:5 | - client2: ``` Variant D: ``` ytt: Overlay error: Expected to match 1 map item in target map, but matched 0. overlay: bar-nemi.yml:6 base: foo.yml:5 matcher: (implicit) #@overlay/match by=<key equality>, expects=0 ``` ## Use Cases ### Input Input for all use-cases is the same: `foo.yml` ```yaml= --- clients: - client1: secret: blah1 - client2: secret: blah2 ``` ### "Not Enough Documents" `bar-ned.yml` ```ymal= #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.all, expects=2 --- ``` ### "Too Many Documents" `bar-tmd.yml` ```yaml= #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.all, expects=0 --- ``` ### "Not Enough Map Items" `bar-nemi.yml` ```yaml= #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.all --- clients: #@overlay/match by=overlay.all - client1: ~ ``` ### "Too Many Map Items" `bar-tmmi.yml` ```yaml= #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.all --- #@overlay/match by=overlay.all, expects=0 clients: ~ ``` ### "Not Enough Array Items" `bar-neai.yml` ```yaml= #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.all --- clients: #@overlay/match by=overlay.all,expects=3 - client1: ~ ``` ### "Too Many Array Items" `bar-tmai.yml` ```yaml= #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.all --- clients: #@overlay/match by=overlay.all,expects=1 - client1: ~ ```