Skip to content

Instantly share code, notes, and snippets.

@otobrglez
Created August 25, 2020 07:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save otobrglez/c989c2f1d93f1b97aa6806449eaf1942 to your computer and use it in GitHub Desktop.
Save otobrglez/c989c2f1d93f1b97aa6806449eaf1942 to your computer and use it in GitHub Desktop.
package com.opalab.experimental
import java.io.File
import cats.data.NonEmptyChain
import cats.implicits._
import io.circe.syntax._
import scala.io.Source
import scala.util.Using
object ErrorApp extends App {
/**
* The functions reads "File" and returns String or Exception (Throwable).
* Internally function also handles resource allocation and releasing.
* Response is String; however it could also be a stream or similar structure.
*/
val read: File => Either[Throwable, String] =
file => Using(Source.fromFile(file))(_.mkString.strip).toEither
/**
* Function reads file withing users "home directory" (i.e. "~") and
* returns either content as a String or Throwable.
*/
val fromHome: String => Either[Throwable, String] =
s => System.getProperty("user.home").some.map(_ + s)
.map(new File(_)).foldMap(read)
/**
* Function reads "username" that is located in a file called ".user-name",
* that is located inside users home directory. If something goes wrong
* internally the method returns String "Anonymous" instead of username.
*
* Note: This is bad practice as it overshadows critical path.
*/
val username: String =
fromHome("/.user-name").fold(_ => "Anonymous", (s: String) => s)
/**
* Function combines two streams and validates them.
* It is possible to do this in parallel and/or retry
* or even proceed in case of exceptions.
*/
def readUserStreams: Either[NonEmptyChain[Throwable], List[String]] =
(
fromHome("/.user-stream-new").toValidatedNec,
fromHome("/.user-stream-old").toValidatedNec)
.mapN((a: String, b: String) => List(a, b))
.toEither
/**
* The "app" function that combines high-level logic,
* rendering to JSON and output.
*
* Note: See how here only "happy path" is handled.
*/
val app = for {
streams <- readUserStreams
} yield Map("username" -> username).asJson.deepMerge(
Map("stream" -> streams).asJson).noSpaces
/**
* This is the so called "end-of-the-world" where the "run"
* is actually evaluated and outputs to command line.
*/
app match {
case Right(value) =>
System.out.println(value)
System.exit(0)
case Left(error) =>
System.err.println(error.map(_.getMessage).mkString_("\n"))
System.exit(1)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment