Skip to content

Commit 2e33ffc

Browse files
fix: don't catch exception when headOption is called
1 parent 6fe456c commit 2e33ffc

File tree

2 files changed

+26
-17
lines changed

2 files changed

+26
-17
lines changed

core/src/main/scala/ox/channels/SourceOps.scala

+14-13
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import ox.*
55
import java.util.concurrent.{CountDownLatch, Semaphore}
66
import scala.collection.{IterableOnce, mutable}
77
import scala.concurrent.duration.FiniteDuration
8-
import scala.util.Try
98

109
trait SourceOps[+T] { this: Source[T] =>
1110
// view ops (lazy)
@@ -515,11 +514,13 @@ trait SourceOps[+T] { this: Source[T] =>
515514
}
516515
c
517516

518-
/** Returns the first element from this source wrapped in `Some` or `None` when the source is empty or fails during the receive operation.
519-
* Note that `headOption` is not an idempotent operation on source as it receives elements from it.
517+
/** Returns the first element from this source wrapped in `Some` or `None` when the source is empty. Note that `headOption` is not an
518+
* idempotent operation on source as it receives elements from it.
520519
*
521520
* @return
522-
* A `Some(first element)` if source is not empty or None` otherwise.
521+
* A `Some(first element)` if source is not empty or `None` otherwise.
522+
* @throws ChannelClosedException.Error
523+
* When `receive()` fails.
523524
* @example
524525
* {{{
525526
* import ox.*
@@ -533,7 +534,13 @@ trait SourceOps[+T] { this: Source[T] =>
533534
* }
534535
* }}}
535536
*/
536-
def headOption(): Option[T] = Try(head()).toOption
537+
def headOption(): Option[T] =
538+
supervised {
539+
receive() match
540+
case ChannelClosed.Done => None
541+
case e: ChannelClosed.Error => throw e.toThrowable
542+
case t: T @unchecked => Some(t)
543+
}
537544

538545
/** Returns the first element from this source or throws `NoSuchElementException` when the source is empty. In case when the `receive()`
539546
* operation fails with exception then `ChannelClosedException.Error`` thrown. Note that `headOption` is not an idempotent operation on
@@ -544,7 +551,7 @@ trait SourceOps[+T] { this: Source[T] =>
544551
* @throws NoSuchElementException
545552
* When source is empty or `receive()` failed without error.
546553
* @throws ChannelClosedException.Error
547-
* When `receive()` fails then this exception is thrown.
554+
* When `receive()` fails.
548555
* @example
549556
* {{{
550557
* import ox.*
@@ -558,13 +565,7 @@ trait SourceOps[+T] { this: Source[T] =>
558565
* }
559566
* }}}
560567
*/
561-
def head(): T =
562-
supervised {
563-
receive() match
564-
case ChannelClosed.Done => throw new NoSuchElementException("cannot obtain head from an empty source")
565-
case e: ChannelClosed.Error => throw e.toThrowable
566-
case t: T @unchecked => t
567-
}
568+
def head(): T = headOption().getOrElse(throw new NoSuchElementException("cannot obtain head from an empty source"))
568569

569570
/** Returns the last element from this source wrapped in `Some` or `None` when the source is empty. Note that `lastOption` is a terminal
570571
* operation leaving the source in `ChannelClosed.Done` state.

core/src/test/scala/ox/channels/SourceOpsHeadOptionTest.scala

+12-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,18 @@ class SourceOpsHeadOptionTest extends AnyFlatSpec with Matchers with OptionValue
1212
Source.empty[Int].headOption() shouldBe None
1313
}
1414

15-
it should "return None for the failed source" in supervised {
16-
Source
17-
.failed(new RuntimeException("source is broken"))
18-
.headOption() shouldBe None
15+
it should "throw ChannelClosedException.Error with exception and message that was thrown during retrieval" in supervised {
16+
the[ChannelClosedException.Error] thrownBy {
17+
Source
18+
.failed(new RuntimeException("source is broken"))
19+
.headOption()
20+
} should have message "java.lang.RuntimeException: source is broken"
21+
}
22+
23+
it should "throw ChannelClosedException.Error for source failed without exception" in supervised {
24+
the[ChannelClosedException.Error] thrownBy {
25+
Source.failedWithoutReason[Int]().headOption()
26+
}
1927
}
2028

2129
it should "return Some element for the non-empty source" in supervised {

0 commit comments

Comments
 (0)