f88a029ecc
Client.Go-002: the Events/EventsAfter compatibility path silently dropped events when the 16-slot results channel filled — it cancelled the stream and closed the channel with no error delivered. sendEventResult now evicts an old buffered event and delivers a terminal EventResult carrying the new exported ErrEventBufferOverflow before close, so the overflow is observable. Client.Go-003: parseInt32List panicked on a malformed -item-handles token, crashing the CLI with a stack trace. It now returns an error that runUnsubscribeBulk propagates, exiting 2 with a clean message. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
144 lines
4.4 KiB
Go
144 lines
4.4 KiB
Go
package mxgateway
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
pb "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated"
|
|
)
|
|
|
|
// ErrEventBufferOverflow is the terminal error delivered on the compatibility
|
|
// event channel returned by Session.Events / Session.EventsAfter when a slow
|
|
// consumer lets the bounded result buffer fill. It signals that the stream was
|
|
// cancelled and events were dropped, so a consumer can tell an overflow apart
|
|
// from a normal end-of-stream. Use Session.SubscribeEvents to block instead of
|
|
// dropping.
|
|
var ErrEventBufferOverflow = errors.New("mxgateway: event buffer overflow; compatibility stream cancelled and events dropped")
|
|
|
|
// GatewayError wraps transport-level gRPC failures.
|
|
type GatewayError struct {
|
|
// Op names the operation that failed (for example "dial" or "invoke").
|
|
Op string
|
|
// Err is the underlying gRPC or transport error.
|
|
Err error
|
|
}
|
|
|
|
// Error returns the formatted gateway error message.
|
|
func (e *GatewayError) Error() string {
|
|
if e == nil {
|
|
return ""
|
|
}
|
|
if e.Op == "" {
|
|
return fmt.Sprintf("mxgateway: %v", e.Err)
|
|
}
|
|
return fmt.Sprintf("mxgateway: %s failed: %v", e.Op, e.Err)
|
|
}
|
|
|
|
// Unwrap returns the wrapped transport error.
|
|
func (e *GatewayError) Unwrap() error {
|
|
if e == nil {
|
|
return nil
|
|
}
|
|
return e.Err
|
|
}
|
|
|
|
// CommandError reports a non-OK gateway protocol status and keeps the raw
|
|
// command reply when one exists.
|
|
type CommandError struct {
|
|
// Op names the gateway operation that produced the non-OK status.
|
|
Op string
|
|
// Status carries the gateway-reported protocol status.
|
|
Status *ProtocolStatus
|
|
// Reply is the raw command reply, when one was returned alongside the status.
|
|
Reply *MxCommandReply
|
|
}
|
|
|
|
// Error returns the formatted command error message.
|
|
func (e *CommandError) Error() string {
|
|
if e == nil {
|
|
return ""
|
|
}
|
|
status := e.Status
|
|
if status == nil {
|
|
return fmt.Sprintf("mxgateway: %s failed with missing protocol status", e.Op)
|
|
}
|
|
if status.GetMessage() == "" {
|
|
return fmt.Sprintf("mxgateway: %s failed with protocol status %s", e.Op, status.GetCode())
|
|
}
|
|
return fmt.Sprintf("mxgateway: %s failed with protocol status %s: %s", e.Op, status.GetCode(), status.GetMessage())
|
|
}
|
|
|
|
// MxAccessError reports HRESULT or MXSTATUS_PROXY failures returned by MXAccess.
|
|
type MxAccessError struct {
|
|
// Command is the wrapped CommandError when the protocol status carried one.
|
|
Command *CommandError
|
|
// Reply is the raw MXAccess command reply that surfaced the failure.
|
|
Reply *MxCommandReply
|
|
}
|
|
|
|
// Error returns the formatted MXAccess error message.
|
|
func (e *MxAccessError) Error() string {
|
|
if e == nil {
|
|
return ""
|
|
}
|
|
if e.Command != nil && e.Command.Status != nil && e.Command.Status.GetMessage() != "" {
|
|
return e.Command.Error()
|
|
}
|
|
if e.Reply != nil && e.Reply.GetDiagnosticMessage() != "" {
|
|
return fmt.Sprintf("mxgateway: MXAccess command %s failed: %s", e.Reply.GetKind(), e.Reply.GetDiagnosticMessage())
|
|
}
|
|
if e.Reply != nil && e.Reply.Hresult != nil {
|
|
return fmt.Sprintf("mxgateway: MXAccess command %s failed with HRESULT 0x%08X", e.Reply.GetKind(), uint32(e.Reply.GetHresult()))
|
|
}
|
|
return "mxgateway: MXAccess command failed"
|
|
}
|
|
|
|
// Unwrap returns the wrapped CommandError, when one is present.
|
|
//
|
|
// When Command is nil (the HRESULT / MxStatusProxy path) it returns an
|
|
// untyped nil rather than a typed-nil *CommandError, so errors.As does not
|
|
// bind a nil pointer that a caller would then panic on.
|
|
func (e *MxAccessError) Unwrap() error {
|
|
if e == nil || e.Command == nil {
|
|
return nil
|
|
}
|
|
return e.Command
|
|
}
|
|
|
|
// EnsureProtocolSuccess returns a typed CommandError when status is non-OK.
|
|
func EnsureProtocolSuccess(op string, status *ProtocolStatus, reply *MxCommandReply) error {
|
|
if status == nil || status.GetCode() == pb.ProtocolStatusCode_PROTOCOL_STATUS_CODE_OK {
|
|
return nil
|
|
}
|
|
|
|
commandError := &CommandError{
|
|
Op: op,
|
|
Status: status,
|
|
Reply: reply,
|
|
}
|
|
if status.GetCode() == pb.ProtocolStatusCode_PROTOCOL_STATUS_CODE_MXACCESS_FAILURE {
|
|
return &MxAccessError{
|
|
Command: commandError,
|
|
Reply: reply,
|
|
}
|
|
}
|
|
return commandError
|
|
}
|
|
|
|
// EnsureMxAccessSuccess returns a typed MxAccessError for failing HRESULTs or
|
|
// MXSTATUS_PROXY entries.
|
|
func EnsureMxAccessSuccess(op string, reply *MxCommandReply) error {
|
|
if reply == nil {
|
|
return nil
|
|
}
|
|
if reply.Hresult != nil && reply.GetHresult() != 0 {
|
|
return &MxAccessError{Reply: reply}
|
|
}
|
|
for _, status := range reply.GetStatuses() {
|
|
if !StatusSucceeded(status) {
|
|
return &MxAccessError{Reply: reply}
|
|
}
|
|
}
|
|
return nil
|
|
}
|