diff --git a/internal/playback/source_retry.go b/internal/playback/source_retry.go new file mode 100644 index 0000000..f0e12a8 --- /dev/null +++ b/internal/playback/source_retry.go @@ -0,0 +1,24 @@ +package playback + +import ( + "context" + "errors" + "mxl-player/internal/source" +) + +func shouldRetrySourceError(err error) bool { + if errors.Is(err, context.Canceled) { + return false + } + + switch source.KindOf(err) { + case source.ErrorKindInvalidConfig: + return false + case source.ErrorKindTemporary, + source.ErrorKindUnavailable, + source.ErrorKindUnknown: + return true + default: + return true + } +} diff --git a/internal/playback/source_retry_test.go b/internal/playback/source_retry_test.go new file mode 100644 index 0000000..de127e1 --- /dev/null +++ b/internal/playback/source_retry_test.go @@ -0,0 +1,74 @@ +package playback + +import ( + "context" + "errors" + "fmt" + "testing" + + "mxl-player/internal/source" +) + +func TestShouldRetrySourceError(t *testing.T) { + baseErr := errors.New("source failed") + classified := func(kind source.ErrorKind) error { + return &source.SourceError{ + Op: "read media", + Kind: kind, + Err: baseErr, + } + } + + tests := []struct { + name string + err error + want bool + }{ + { + name: "temporary source error is retryable", + err: classified(source.ErrorKindTemporary), + want: true, + }, + { + name: "unavailable source is retryable", + err: classified(source.ErrorKindUnavailable), + want: true, + }, + { + name: "invalid configuration is not retryable", + err: classified(source.ErrorKindInvalidConfig), + want: false, + }, + { + name: "ordinary unknown error is retryable", + err: baseErr, + want: true, + }, + { + name: "wrapped invalid configuration is not retryable", + err: fmt.Errorf( + "worker failed: %w", + classified(source.ErrorKindInvalidConfig), + ), + want: false, + }, + { + name: "context cancellation is not retryable", + err: context.Canceled, + want: false, + }, + { + name: "wrapped context cancellation is not retryable", + err: fmt.Errorf("worker stopped: %w", context.Canceled), + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := shouldRetrySourceError(tt.err); got != tt.want { + t.Errorf("shouldRetrySourceError() = %t, want %t", got, tt.want) + } + }) + } +}