diff --git a/internal/source/errors.go b/internal/source/errors.go new file mode 100644 index 0000000..d99e3b4 --- /dev/null +++ b/internal/source/errors.go @@ -0,0 +1,51 @@ +package source + +import ( + "errors" + "fmt" +) + +type ErrorKind uint8 + +const ( + ErrorKindUnknown ErrorKind = iota + ErrorKindTemporary // timeout or temporarily early/late data i.e. wait or resync + ErrorKindUnavailable // producer/flow disappeared + ErrorKindInvalidConfig // wrong media type, invalid rate, etc. +) + +type SourceError struct { + Op string + Kind ErrorKind + Err error +} + +func (e *SourceError) Error() string { + if e.Op == "" { + return e.Err.Error() + } + return fmt.Sprintf("%s: %v", e.Op, e.Err) +} + +func (e *SourceError) Unwrap() error { + return e.Err +} + +func errorKind(err error) ErrorKind { + var sourceErr *SourceError + if errors.As(err, &sourceErr) { + return sourceErr.Kind + } + return ErrorKindUnknown +} + +func wrapError(op string, kind ErrorKind, err error) error { + if err == nil { + return nil + } + return &SourceError{ + Op: op, + Kind: kind, + Err: err, + } +} diff --git a/internal/source/errors_test.go b/internal/source/errors_test.go new file mode 100644 index 0000000..bb6178f --- /dev/null +++ b/internal/source/errors_test.go @@ -0,0 +1,54 @@ +package source + +import ( + "errors" + "fmt" + "testing" +) + +func TestErrorKindThroughWrapping(t *testing.T) { + base := errors.New("producer disappeared") + wrapped := wrapError("read video", ErrorKindUnavailable, base) + outer := fmt.Errorf("worker failed: %w", wrapped) + + if got := errorKind(outer); got != ErrorKindUnavailable { + t.Fatalf("errorKind() = %v, want %v", got, ErrorKindUnavailable) + } + if !errors.Is(outer, base) { + t.Fatal("wrapped error does not preserve its cause") + } + if wrapError("nil", 0, nil) != nil { + t.Fatal("nil error is not wrapped as nil") + } +} + +func TestErrorKindUnknown(t *testing.T) { + if got := errorKind(errors.New("ordinary error")); got != ErrorKindUnknown { + t.Fatalf("errorKind() = %v, want %v", got, ErrorKindUnknown) + } +} + +func TestSourceErrorWithOperation(t *testing.T) { + err := &SourceError{ + Op: "read video", + Kind: ErrorKindTemporary, + Err: errors.New("timeout"), + } + + const want = "read video: timeout" + if got := err.Error(); got != want { + t.Fatalf("Error() = %q, want %q", got, want) + } +} + +func TestSourceErrorWithoutOperation(t *testing.T) { + err := &SourceError{ + Kind: ErrorKindTemporary, + Err: errors.New("timeout"), + } + + const want = "timeout" + if got := err.Error(); got != want { + t.Fatalf("Error() = %q, want %q", got, want) + } +}