From 78b6a899ee8776ac47dec73f34b3206b22955f70 Mon Sep 17 00:00:00 2001 From: elpatron Date: Sun, 22 Mar 2026 13:23:16 +0100 Subject: [PATCH] fix(radio): use sentinel errors for Radio Browser query validation - #5239 Export ErrQueryTooShort and ErrQueryTooLong; native API uses errors.Is instead of matching error strings. Signed-off-by: elpatron --- server/nativeapi/radios.go | 2 +- server/radiobrowser/radiobrowser.go | 11 +++++++++-- server/radiobrowser/radiobrowser_test.go | 20 +++++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/server/nativeapi/radios.go b/server/nativeapi/radios.go index 0d614b23d..2d5f91943 100644 --- a/server/nativeapi/radios.go +++ b/server/nativeapi/radios.go @@ -87,7 +87,7 @@ func (api *Router) searchRadioBrowser() http.HandlerFunc { } stations, err := radiobrowser.Search(r.Context(), q, limit) if err != nil { - if strings.Contains(err.Error(), "too short") || strings.Contains(err.Error(), "too long") { + if errors.Is(err, radiobrowser.ErrQueryTooShort) || errors.Is(err, radiobrowser.ErrQueryTooLong) { http.Error(w, err.Error(), http.StatusBadRequest) return } diff --git a/server/radiobrowser/radiobrowser.go b/server/radiobrowser/radiobrowser.go index 2299c2bf5..a962947a8 100644 --- a/server/radiobrowser/radiobrowser.go +++ b/server/radiobrowser/radiobrowser.go @@ -4,6 +4,7 @@ package radiobrowser import ( "context" "encoding/json" + "errors" "fmt" "io" "math/rand" @@ -39,6 +40,12 @@ type apiStation struct { StationUUID string `json:"stationuuid"` } +// Sentinel errors for query validation. Use errors.Is to detect them. +var ( + ErrQueryTooShort = errors.New("query too short") + ErrQueryTooLong = errors.New("query too long") +) + var fallbackAPIHosts = []string{ "de1.api.radio-browser.info", "nl1.api.radio-browser.info", @@ -87,10 +94,10 @@ func shuffleHosts(hosts []string) []string { func Search(ctx context.Context, rawQuery string, limit int) ([]Station, error) { q := strings.TrimSpace(rawQuery) if len(q) < minQueryLen { - return nil, fmt.Errorf("query too short (min %d characters)", minQueryLen) + return nil, fmt.Errorf("query too short (min %d characters): %w", minQueryLen, ErrQueryTooShort) } if len(q) > maxQueryLen { - return nil, fmt.Errorf("query too long (max %d characters)", maxQueryLen) + return nil, fmt.Errorf("query too long (max %d characters): %w", maxQueryLen, ErrQueryTooLong) } if limit <= 0 { limit = defaultLimit diff --git a/server/radiobrowser/radiobrowser_test.go b/server/radiobrowser/radiobrowser_test.go index 61a3bd885..c6dfcb90e 100644 --- a/server/radiobrowser/radiobrowser_test.go +++ b/server/radiobrowser/radiobrowser_test.go @@ -1,6 +1,11 @@ package radiobrowser -import "testing" +import ( + "context" + "errors" + "strings" + "testing" +) func TestNormalizeStations(t *testing.T) { raw := []apiStation{ @@ -19,3 +24,16 @@ func TestNormalizeStations(t *testing.T) { t.Fatalf("second stream: %q", got[1].StreamURL) } } + +func TestSearchSentinelErrors(t *testing.T) { + ctx := context.Background() + _, err := Search(ctx, "x", 10) + if !errors.Is(err, ErrQueryTooShort) { + t.Fatalf("short query: want ErrQueryTooShort, got %v", err) + } + long := strings.Repeat("a", maxQueryLen+1) + _, err = Search(ctx, long, 10) + if !errors.Is(err, ErrQueryTooLong) { + t.Fatalf("long query: want ErrQueryTooLong, got %v", err) + } +}