diff --git a/cmd/proxy/proxy_server.go b/cmd/proxy/proxy_server.go index 53e0051..18bfb24 100644 --- a/cmd/proxy/proxy_server.go +++ b/cmd/proxy/proxy_server.go @@ -54,6 +54,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/config" "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/geoip" + "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/proxy" "github.com/strukturag/nextcloud-spreed-signaling/session" @@ -1224,8 +1225,7 @@ func (s *ProxyServer) processCommand(ctx context.Context, client *ProxyClient, s defer cancel() if err := publisher.PublishRemote(ctx2, session.PublicId(), cmd.Hostname, cmd.Port, cmd.RtcpPort); err != nil { - var je *janusapi.ErrorMsg - if !errors.As(err, &je) || je.Err.Code != janusapi.JANUS_VIDEOROOM_ERROR_ID_EXISTS { + if je, ok := internal.AsErrorType[*janusapi.ErrorMsg](err); !ok || je.Err.Code != janusapi.JANUS_VIDEOROOM_ERROR_ID_EXISTS { s.logger.Printf("Error publishing %s %s to remote %s (port=%d, rtcpPort=%d): %s", publisher.StreamType(), cmd.ClientId, cmd.Hostname, cmd.Port, cmd.RtcpPort, err) session.sendMessage(message.NewWrappedErrorServerMessage(err)) return diff --git a/config/config.go b/config/config.go index 25e8833..a8dd096 100644 --- a/config/config.go +++ b/config/config.go @@ -22,11 +22,12 @@ package config import ( - "errors" "os" "regexp" "github.com/dlintw/goconf" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) var ( @@ -71,8 +72,7 @@ func GetStringOptions(config *goconf.ConfigFile, section string, ignoreErrors bo continue } - var ge goconf.GetError - if errors.As(err, &ge) && ge.Reason == goconf.OptionNotFound { + if ge, ok := internal.AsErrorType[goconf.GetError](err); ok && ge.Reason == goconf.OptionNotFound { // Skip options from "default" section. continue } diff --git a/internal/as_error.go b/internal/as_error.go new file mode 100644 index 0000000..a7ab70e --- /dev/null +++ b/internal/as_error.go @@ -0,0 +1,50 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 struktur AG + * + * @author Joachim Bauch + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package internal + +import ( + "errors" +) + +// AsErrorType finds the first error in err's tree that matches the type E, +// and if one is found, returns that error value and true. Otherwise, it +// returns the zero value of E and false. +// +// The tree consists of err itself, followed by the errors obtained by +// repeatedly calling its Unwrap() error or Unwrap() []error method. +// When err wraps multiple errors, AsErrorType examines err followed by a +// depth-first traversal of its children. +// +// An error err matches the type E if the type assertion err.(E) holds, +// or if the error has a method As(any) bool such that err.As(target) +// returns true when target is a non-nil *E. In the latter case, the As +// method is responsible for setting target. +func AsErrorType[E error](err error) (E, bool) { + var e E + if err == nil { + return e, false + } else if errors.As(err, &e) { + return e, true + } + + return e, false +} diff --git a/internal/as_error_test.go b/internal/as_error_test.go new file mode 100644 index 0000000..5fc2564 --- /dev/null +++ b/internal/as_error_test.go @@ -0,0 +1,61 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2026 struktur AG + * + * @author Joachim Bauch + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package internal + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +type testError struct{} + +func (e testError) Error() string { + return "test error" +} + +func TestAsErrorType(t *testing.T) { + t.Parallel() + + assert := assert.New(t) + + if e, ok := AsErrorType[*testError](nil); assert.False(ok) { + assert.Nil(e) + } + + err1 := &testError{} + if e, ok := AsErrorType[*testError](err1); assert.True(ok) { + assert.Same(err1, e) + } + + err2 := errors.New("other error") + if e, ok := AsErrorType[*testError](err2); assert.False(ok) { + assert.Nil(e) + } + + err3 := fmt.Errorf("wrapped error: %w", err1) + if e, ok := AsErrorType[*testError](err3); assert.True(ok) { + assert.Same(err1, e) + } +} diff --git a/server/backend_server.go b/server/backend_server.go index 303cd39..cf8be1f 100644 --- a/server/backend_server.go +++ b/server/backend_server.go @@ -831,9 +831,10 @@ func (b *BackendServer) startDialout(ctx context.Context, roomid string, backend response, err := b.startDialoutInSession(ctx, session, roomid, backend, backendUrl, request) if err != nil { b.logger.Printf("Error starting dialout request %+v in session %s: %+v", request.Dialout, session.PublicId(), err) - var e *api.Error - if sessionError == nil && errors.As(err, &e) { - sessionError = e + if sessionError == nil { + if e, ok := internal.AsErrorType[*api.Error](err); ok { + sessionError = e + } } continue } diff --git a/server/federation.go b/server/federation.go index c9be1e4..a80beed 100644 --- a/server/federation.go +++ b/server/federation.go @@ -469,8 +469,8 @@ func (c *FederationClient) writePump() { func (c *FederationClient) closeWithError(err error) { c.Close() - var e *api.Error - if !errors.As(err, &e) { + e, ok := internal.AsErrorType[*api.Error](err) + if !ok { e = api.NewError("federation_error", err.Error()) } diff --git a/server/hub.go b/server/hub.go index dd5e024..3251dbd 100644 --- a/server/hub.go +++ b/server/hub.go @@ -1844,39 +1844,31 @@ func (h *Hub) processRoom(sess Session, message *api.ClientMessage) { if session.UserId() == "" && client == nil { h.startWaitAnonymousSessionRoom(session) } - var ae *api.Error - if errors.As(err, &ae) { + if ae, ok := internal.AsErrorType[*api.Error](err); ok { session.SendMessage(message.NewErrorServerMessage(ae)) return } var details any - var ce *tls.CertificateVerificationError - if errors.As(err, &ce) { + if ce, ok := internal.AsErrorType[*tls.CertificateVerificationError](err); ok { details = map[string]string{ "code": "certificate_verification_error", "message": ce.Error(), } - } - var ne net.Error - if details == nil && errors.As(err, &ne) { + } else if ne, ok := internal.AsErrorType[net.Error](err); ok { details = map[string]string{ "code": "network_error", "message": ne.Error(), } - } - if details == nil { - var we websocket.HandshakeError - if errors.Is(err, websocket.ErrBadHandshake) { - details = map[string]string{ - "code": "network_error", - "message": err.Error(), - } - } else if errors.As(err, &we) { - details = map[string]string{ - "code": "network_error", - "message": we.Error(), - } + } else if errors.Is(err, websocket.ErrBadHandshake) { + details = map[string]string{ + "code": "network_error", + "message": err.Error(), + } + } else if we, ok := internal.AsErrorType[websocket.HandshakeError](err); ok { + details = map[string]string{ + "code": "network_error", + "message": we.Error(), } } diff --git a/test/network.go b/test/network.go index ec7d582..6d7cc78 100644 --- a/test/network.go +++ b/test/network.go @@ -22,19 +22,20 @@ package test import ( - "errors" "os" "runtime" "syscall" + + "github.com/strukturag/nextcloud-spreed-signaling/internal" ) func IsErrorAddressAlreadyInUse(err error) bool { - var eOsSyscall *os.SyscallError - if !errors.As(err, &eOsSyscall) { + eOsSyscall, ok := internal.AsErrorType[*os.SyscallError](err) + if !ok { return false } - var errErrno syscall.Errno // doesn't need a "*" (ptr) because it's already a ptr (uintptr) - if !errors.As(eOsSyscall, &errErrno) { + errErrno, ok := internal.AsErrorType[syscall.Errno](eOsSyscall) + if !ok { return false } if errErrno == syscall.EADDRINUSE {