Skip to content

Commit

Permalink
Improve end-of-stream handling.
Browse files Browse the repository at this point in the history
As discussed in issue #247.
  • Loading branch information
quelgar committed Sep 15, 2020
1 parent 298f71d commit 603aa0d
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 9 deletions.
20 changes: 20 additions & 0 deletions docs/essentials/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,26 @@ libraryDependencies += "dev.zio" %% "zio-nio" % "1.0.0-RC9"
- **[Socket Channel](sockets.md)** — Provides API for remote communication with `InetSocket`s
- **[Character Sets](charsets.md)** - For encoding or decoding character data

### End-Of-Stream Handling

When reading from channels, the end of the stream may be reached at any time. This is indicated by the read effect failing with an `java.io.EOFException`. If you would prefer to explicitly represent the end-of-stream condition in the error channel, use the `eofCheck` extension method:

```scala mdoc:silent
import zio._
import zio.blocking.Blocking
import zio.nio.core._
import zio.nio.core.channels._
import zio.nio.core.file.Path
import java.io.IOException

val read100: ZIO[Blocking, Option[IOException], Chunk[Byte]] =
FileChannel.open(Path("foo.txt"))
.asSomeError
.flatMap(_.readChunk(100).eofCheck)
```

If the error is `None` if end-of-stream is reached, and it is `Some` if the read failed.

## References

- [ZIO github page](http://github.com/zio/zio)
Expand Down
23 changes: 16 additions & 7 deletions nio-core/src/main/scala/zio/nio/core/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package zio.nio

import java.io.EOFException

import com.github.ghik.silencer.silent
import zio.{ IO, ZIO }

package object core {
Expand All @@ -22,12 +23,20 @@ package object core {
private[nio] def eofCheck(value: Long): IO[EOFException, Long] =
if (value < 0L) IO.fail(new EOFException("Channel has reached the end of stream")) else IO.succeed(value)

/**
* Turns `EOFException` failures into a success with no result.
*/
def eofOption[R, A, E <: Throwable](effect: ZIO[R, E, A]): ZIO[R, E, Option[A]] =
effect.asSome.catchSome {
case _: EOFException => ZIO.none
}
implicit final class EffectOps[-R, +E, +A](val effect: ZIO[R, E, A]) extends AnyVal {

/**
* Explicitly represent end-of-stream in the error channel.
*
* This will catch an `EOFException` failure from the effect and translate it to a failure of `None`.
* Other exception types are wrapped in `Some`.
*/
@silent("parameter value ev in method .* is never used")
def eofCheck[E2 >: E](implicit ev: EOFException <:< E2): ZIO[R, Option[E2], A] =
effect.catchAll {
case _: EOFException => ZIO.fail(None)
case e => ZIO.fail(Some(e))
}
}

}
18 changes: 16 additions & 2 deletions nio-core/src/test/scala/zio/nio/core/channels/ChannelSpec.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package zio.nio.core.channels

import zio.nio.core.{ BaseSpec, Buffer, SocketAddress }
import java.io.{ EOFException, FileNotFoundException, IOException }

import zio.nio.core.{ BaseSpec, Buffer, EffectOps, SocketAddress }
import zio.test.{ suite, testM }
import zio.{ IO, _ }
import zio.test._
Expand Down Expand Up @@ -131,6 +133,18 @@ object ChannelSpec extends BaseSpec {
_ <- client(addr)
_ <- s2.join
} yield assertCompletes
}
},
suite("explicit end-of-stream")(
testM("converts EOFException to None") {
assertM(IO.fail(new EOFException).eofCheck.run)(fails(isNone))
},
testM("converts non EOFException to Some") {
val e: IOException = new FileNotFoundException()
assertM(IO.fail(e).eofCheck.run)(fails(isSome(equalTo(e))))
},
testM("passes through success") {
assertM(IO.succeed(42).eofCheck.run)(succeeds(equalTo(42)))
}
)
)
}

0 comments on commit 603aa0d

Please sign in to comment.