package notify import ( "errors" "fmt" "strings" ) type detailedStateError struct { base error detail string cause error } func (e *detailedStateError) Error() string { if e == nil || e.base == nil { return "" } base := e.base.Error() detail := strings.TrimSpace(e.detail) switch { case detail != "" && e.cause != nil: return fmt.Sprintf("%s: %s: %v", base, detail, e.cause) case detail != "": return fmt.Sprintf("%s: %s", base, detail) case e.cause != nil: return fmt.Sprintf("%s: %v", base, e.cause) default: return base } } func (e *detailedStateError) Unwrap() error { if e == nil { return nil } return e.cause } func (e *detailedStateError) Is(target error) bool { if e == nil { return false } return target == e.base || errors.Is(e.cause, target) } func newDetailedStateError(base error, detail string, cause error) error { if base == nil { return cause } detail = strings.TrimSpace(detail) if detail == "" && cause == nil { return base } return &detailedStateError{ base: base, detail: detail, cause: cause, } } func transportDetachedError(detail string, cause error) error { return newDetailedStateError(errTransportDetached, detail, cause) } func clientTransportDetachedError(c *ClientCommon) error { if c == nil { return errTransportDetached } status := c.Status() if status.Alive && c.clientTransportAttachedSnapshot() { return errTransportDetached } switch status.Reason { case "", "recv stop signal from user": if status.Err != nil { return transportDetachedError("", status.Err) } return errTransportDetached default: return transportDetachedError(status.Reason, status.Err) } } func transportDetachedErrorForLogical(logical *LogicalConn) error { if logical == nil { return errTransportDetached } if detach := logical.transportDetachSnapshot(); detach != nil { detail := strings.TrimSpace(detach.Reason) if detach.Generation != 0 { if detail == "" { detail = fmt.Sprintf("generation=%d", detach.Generation) } else { detail = fmt.Sprintf("%s [generation=%d]", detail, detach.Generation) } } if detach.Err != "" { return transportDetachedError(detail, errors.New(detach.Err)) } return transportDetachedError(detail, nil) } status := logical.Status() if !status.Alive && (status.Reason != "" || status.Err != nil) { return transportDetachedError(status.Reason, status.Err) } return errTransportDetached } func transportDetachedErrorForTransport(transport *TransportConn) error { if transport == nil { return errTransportDetached } if logical := transport.logicalConnSnapshot(); logical != nil { if err := transportDetachedErrorForLogical(logical); err != errTransportDetached { return err } } switch { case !transport.Attached(): return transportDetachedError(fmt.Sprintf("transport generation=%d not attached", transport.TransportGeneration()), nil) case !transport.HasRuntimeConn(): return transportDetachedError(fmt.Sprintf("transport generation=%d has no runtime conn", transport.TransportGeneration()), nil) case !transport.IsCurrent(): return transportDetachedError(fmt.Sprintf("stale transport generation=%d", transport.TransportGeneration()), nil) default: return errTransportDetached } } func transportDetachedErrorForPeer(logical *LogicalConn, transport *TransportConn) error { if transport != nil { return transportDetachedErrorForTransport(transport) } return transportDetachedErrorForLogical(logical) } func transportDetachedGenerationMismatchError(expected uint64, transport *TransportConn) error { actual := uint64(0) if transport != nil { actual = transport.TransportGeneration() } return transportDetachedError( fmt.Sprintf("transport generation mismatch expected=%d actual=%d", expected, actual), nil, ) } func transportDetachedSessionEpochError() error { return transportDetachedError("stale client session epoch", nil) }