diff --git a/.changeset/healthy-turtles-knock.md b/.changeset/healthy-turtles-knock.md new file mode 100644 index 00000000..0f20623f --- /dev/null +++ b/.changeset/healthy-turtles-knock.md @@ -0,0 +1,5 @@ +--- +"github.com/livekit/protocol": minor +--- + +Use SIP statuses as Go and gRPC errors. diff --git a/livekit/sip.go b/livekit/sip.go index f932eaa9..3aeef63f 100644 --- a/livekit/sip.go +++ b/livekit/sip.go @@ -8,10 +8,67 @@ import ( "strconv" "strings" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "github.com/livekit/protocol/utils/xtwirp" ) -var _ xtwirp.ErrorMeta = (*SIPStatus)(nil) +var ( + _ xtwirp.ErrorMeta = (*SIPStatus)(nil) + _ error = (*SIPStatus)(nil) +) + +func (p SIPStatusCode) ShortName() string { + return strings.TrimPrefix(p.String(), "SIP_STATUS_") +} + +func (p *SIPStatus) Error() string { + if p.Status != "" { + return fmt.Sprintf("sip status: %d: %s", p.Code, p.Status) + } + return fmt.Sprintf("sip status: %d (%s)", p.Code, p.Code.ShortName()) +} + +func (p *SIPStatus) GRPCStatus() *status.Status { + msg := p.Status + if msg == "" { + msg = p.Code.ShortName() + } + var code = codes.Internal + switch p.Code { + case SIPStatusCode_SIP_STATUS_OK: + return status.New(codes.OK, "OK") + case SIPStatusCode_SIP_STATUS_REQUEST_TERMINATED: + code = codes.Aborted + case SIPStatusCode_SIP_STATUS_BAD_REQUEST, + SIPStatusCode_SIP_STATUS_NOTFOUND, + SIPStatusCode_SIP_STATUS_ADDRESS_INCOMPLETE, + SIPStatusCode_SIP_STATUS_AMBIGUOUS, + SIPStatusCode_SIP_STATUS_BAD_EXTENSION, + SIPStatusCode_SIP_STATUS_EXTENSION_REQUIRED: + code = codes.InvalidArgument + case SIPStatusCode_SIP_STATUS_REQUEST_TIMEOUT, + SIPStatusCode_SIP_STATUS_GATEWAY_TIMEOUT: + code = codes.DeadlineExceeded + case SIPStatusCode_SIP_STATUS_SERVICE_UNAVAILABLE, + SIPStatusCode_SIP_STATUS_TEMPORARILY_UNAVAILABLE, + SIPStatusCode_SIP_STATUS_BUSY_HERE, + SIPStatusCode_SIP_STATUS_GLOBAL_BUSY_EVERYWHERE, + SIPStatusCode_SIP_STATUS_NOT_IMPLEMENTED, + SIPStatusCode_SIP_STATUS_GLOBAL_DECLINE: + code = codes.Unavailable + case SIPStatusCode_SIP_STATUS_PROXY_AUTH_REQUIRED, + SIPStatusCode_SIP_STATUS_UNAUTHORIZED, + SIPStatusCode_SIP_STATUS_FORBIDDEN: + code = codes.PermissionDenied + } + st := status.New(code, fmt.Sprintf("sip status %d: %s", p.Code, msg)) + if st2, err := st.WithDetails(p); err == nil { + return st2 + } + return st +} func (p *SIPStatus) TwirpErrorMeta() map[string]string { status := p.Status diff --git a/livekit/sip_test.go b/livekit/sip_test.go index 5dde6c7d..d38c05ac 100644 --- a/livekit/sip_test.go +++ b/livekit/sip_test.go @@ -5,6 +5,9 @@ import ( "testing" "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" ) func TestSIPTrunkAs(t *testing.T) { @@ -358,3 +361,16 @@ func TestSIPDispatchRuleFilter(t *testing.T) { }) } } + +func TestGRPCStatus(t *testing.T) { + e := &SIPStatus{Code: SIPStatusCode_SIP_STATUS_BUSY_HERE} + st, ok := status.FromError(e) + require.True(t, ok) + require.Equal(t, codes.Unavailable, st.Code()) + require.Equal(t, "sip status 486: BUSY_HERE", st.Message()) + details := st.Details() + require.Len(t, details, 1) + e2, ok := details[0].(*SIPStatus) + require.True(t, ok) + require.True(t, proto.Equal(e, e2)) +}