1// Package uierrors holds the error helper shared by the layer-surface UI
2// adapters.
3package uierrors
4
5import (
6 "context"
7 "errors"
8)
9
10// Drop removes from err, and recursively from errors.Join trees, every leaf
11// that is exactly one of targets. Only bare leaves match: a real failure that
12// merely wraps a target (fmt.Errorf("x: %w", context.Canceled)) is kept, and so
13// is every sibling of a dropped leaf. It returns nil when nothing else remains.
14//
15// Use it to discard the context.Canceled a layerui.Run reports after the
16// adapter itself ended it, without hiding a real error joined to it.
17func Drop(err error, targets ...error) error {
18 if err == nil {
19 return nil
20 }
21 if j, ok := err.(interface{ Unwrap() []error }); ok {
22 var keep []error
23 for _, e := range j.Unwrap() {
24 if e = Drop(e, targets...); e != nil {
25 keep = append(keep, e)
26 }
27 }
28 switch len(keep) {
29 case 0:
30 return nil
31 case 1:
32 return keep[0]
33 }
34 return errors.Join(keep...)
35 }
36 for _, t := range targets {
37 if err == t {
38 return nil
39 }
40 }
41 return err
42}
43
44// Ended classifies how a layerui.Run ended. It is pure: the caller snapshots
45// its state once and passes it, so a context that ends between two reads cannot
46// flip the answer.
47//
48// explained says that this adapter or its caller ended the run on purpose (a
49// decision was made, or the parent context is done). parent is the parent
50// context's error at that snapshot, nil when it is still live. closed is
51// returned when the run ended without an explanation.
52//
53// - explained: a bare context.Canceled (and bare parent) is the expected echo
54// of our own cancellation and is dropped. Anything else, including a real
55// error joined with it or an error that merely wraps Canceled, is kept.
56// - not explained: the run ended on its own. When something real remains
57// (an initial authorization, capability or font failure, say), exactly that
58// is returned: calling it a compositor close would mislead. Only when
59// nothing real remains, including a bare Canceled, is closed returned.
60func Ended(runErr error, explained bool, parent, closed error) error {
61 if explained {
62 return Drop(runErr, context.Canceled, parent)
63 }
64 if rest := Drop(runErr, context.Canceled, parent); rest != nil {
65 return rest
66 }
67 return closed
68}
69
70// IsCancellation reports whether err is non-nil and made only of
71// context.Canceled leaves, possibly wrapped with %w or joined. A failure
72// joined with, or merely mentioning, a cancellation is not one. Unlike Drop,
73// wrapped Canceled counts: it is how a caller's own cancellation surfaces
74// through layers that add context. DeadlineExceeded is deliberately not
75// included, so a bounded-operation timeout stays a real failure.
76func IsCancellation(err error) bool {
77 switch e := err.(type) {
78 case nil:
79 return false
80 case interface{ Unwrap() []error }:
81 children := e.Unwrap()
82 for _, child := range children {
83 if !IsCancellation(child) {
84 return false
85 }
86 }
87 return len(children) > 0
88 case interface{ Unwrap() error }:
89 return err == context.Canceled || IsCancellation(e.Unwrap())
90 default:
91 return err == context.Canceled
92 }
93}