aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app/errors.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/app/errors.go')
-rw-r--r--internal/app/errors.go181
1 files changed, 181 insertions, 0 deletions
diff --git a/internal/app/errors.go b/internal/app/errors.go
new file mode 100644
index 00000000..b9bd6f57
--- /dev/null
+++ b/internal/app/errors.go
@@ -0,0 +1,181 @@
+package app
+
+import (
+ "errors"
+ "log"
+
+ "hakurei.app/internal/hlog"
+)
+
+func PrintRunStateErr(rs *RunState, runErr error) (code int) {
+ code = rs.ExitStatus()
+
+ if runErr != nil {
+ if rs.Time == nil {
+ hlog.PrintBaseError(runErr, "cannot start app:")
+ } else {
+ var e *hlog.BaseError
+ if !hlog.AsBaseError(runErr, &e) {
+ log.Println("wait failed:", runErr)
+ } else {
+ // Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError
+ var se *StateStoreError
+ if !errors.As(runErr, &se) {
+ // does not need special handling
+ log.Print(e.Message())
+ } else {
+ // inner error are either unwrapped store errors
+ // or joined errors returned by *appSealTx revert
+ // wrapped in *app.BaseError
+ var ej RevertCompoundError
+ if !errors.As(se.InnerErr, &ej) {
+ // does not require special handling
+ log.Print(e.Message())
+ } else {
+ errs := ej.Unwrap()
+
+ // every error here is wrapped in *app.BaseError
+ for _, ei := range errs {
+ var eb *hlog.BaseError
+ if !errors.As(ei, &eb) {
+ // unreachable
+ log.Println("invalid error type returned by revert:", ei)
+ } else {
+ // print inner *app.BaseError message
+ log.Print(eb.Message())
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if code == 0 {
+ code = 126
+ }
+ }
+
+ if rs.RevertErr != nil {
+ var stateStoreError *StateStoreError
+ if !errors.As(rs.RevertErr, &stateStoreError) || stateStoreError == nil {
+ hlog.PrintBaseError(rs.RevertErr, "generic fault during cleanup:")
+ goto out
+ }
+
+ if stateStoreError.Err != nil {
+ if len(stateStoreError.Err) == 2 {
+ if stateStoreError.Err[0] != nil {
+ if joinedErrs, ok := stateStoreError.Err[0].(interface{ Unwrap() []error }); !ok {
+ hlog.PrintBaseError(stateStoreError.Err[0], "generic fault during revert:")
+ } else {
+ for _, err := range joinedErrs.Unwrap() {
+ if err != nil {
+ hlog.PrintBaseError(err, "fault during revert:")
+ }
+ }
+ }
+ }
+ if stateStoreError.Err[1] != nil {
+ log.Printf("cannot close store: %v", stateStoreError.Err[1])
+ }
+ } else {
+ log.Printf("fault during cleanup: %v",
+ errors.Join(stateStoreError.Err...))
+ }
+ }
+
+ if stateStoreError.OpErr != nil {
+ log.Printf("blind revert due to store fault: %v",
+ stateStoreError.OpErr)
+ }
+
+ if stateStoreError.DoErr != nil {
+ hlog.PrintBaseError(stateStoreError.DoErr, "state store operation unsuccessful:")
+ }
+
+ if stateStoreError.Inner && stateStoreError.InnerErr != nil {
+ hlog.PrintBaseError(stateStoreError.InnerErr, "cannot destroy state entry:")
+ }
+
+ out:
+ if code == 0 {
+ code = 128
+ }
+ }
+ if rs.WaitErr != nil {
+ hlog.Verbosef("wait: %v", rs.WaitErr)
+ }
+ return
+}
+
+// StateStoreError is returned for a failed state save
+type StateStoreError struct {
+ // whether inner function was called
+ Inner bool
+ // returned by the Save/Destroy method of [state.Cursor]
+ InnerErr error
+ // returned by the Do method of [state.Store]
+ DoErr error
+ // stores an arbitrary store operation error
+ OpErr error
+ // stores arbitrary errors
+ Err []error
+}
+
+// save saves arbitrary errors in [StateStoreError] once.
+func (e *StateStoreError) save(errs ...error) {
+ if len(errs) == 0 || e.Err != nil {
+ panic("invalid call to save")
+ }
+ e.Err = errs
+}
+
+func (e *StateStoreError) equiv(a ...any) error {
+ if e.Inner && e.InnerErr == nil && e.DoErr == nil && e.OpErr == nil && errors.Join(e.Err...) == nil {
+ return nil
+ } else {
+ return hlog.WrapErrSuffix(e, a...)
+ }
+}
+
+func (e *StateStoreError) Error() string {
+ if e.Inner && e.InnerErr != nil {
+ return e.InnerErr.Error()
+ }
+ if e.DoErr != nil {
+ return e.DoErr.Error()
+ }
+ if e.OpErr != nil {
+ return e.OpErr.Error()
+ }
+ if err := errors.Join(e.Err...); err != nil {
+ return err.Error()
+ }
+
+ // equiv nullifies e for values where this is reached
+ panic("unreachable")
+}
+
+func (e *StateStoreError) Unwrap() (errs []error) {
+ errs = make([]error, 0, 3)
+ if e.InnerErr != nil {
+ errs = append(errs, e.InnerErr)
+ }
+ if e.DoErr != nil {
+ errs = append(errs, e.DoErr)
+ }
+ if e.OpErr != nil {
+ errs = append(errs, e.OpErr)
+ }
+ if err := errors.Join(e.Err...); err != nil {
+ errs = append(errs, err)
+ }
+ return
+}
+
+// A RevertCompoundError encapsulates errors returned by
+// the Revert method of [system.I].
+type RevertCompoundError interface {
+ Error() string
+ Unwrap() []error
+}