Last active
April 1, 2019 10:50
-
-
Save afsalthaj/74c91b2aef2a253d2126ad8b2eafb277 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Thanks to @adam_evans for this.. and cats documentation as well | |
// This can be a very simple open source... its far better than just about everything out there! | |
def fromCommandLineArguments(args: List[String]) = { | |
args.sliding(2, 2).flatMap(t => ( | |
(t.headOption.flatMap(str => str.startsWith("--").option(str.replace("--", ""))) |@| | |
t.lift(1).flatMap(str => (!str.startsWith("--")).option(str))){(_, _)}).toList).toMap | |
import Types._ | |
import scalaz.syntax.std.option._ | |
import scalaz.ValidationNel | |
import scalaz.NonEmptyList | |
import scalaz.Reader | |
import scalaz.\/ | |
import scalaz.Validation | |
import scalaz.Monad | |
final case class ConfigAction[A](run: EnvReader[ValidationNel[ConfigError, A]]) { | |
def map[B](f: A ⇒ B): ConfigAction[B] = | |
ConfigAction { run.map { _.map(f) } } | |
def disjunctioned: EnvReader[NonEmptyList[ConfigError] \/ A] = | |
run.map { _.disjunction } | |
def flatMap[B](f: A => ConfigAction[B]): ConfigAction[B] = | |
ConfigAction(Reader(env => run(env).andThen(a => f(a).run(env)))) | |
} | |
object ConfigAction extends ConfigActionInstances { | |
// Safely parse a environment variable to a given type | |
def read[A: Unmarshaller](key: String): ConfigAction[A] = | |
fromUnmarshaller(key) { Unmarshaller[A] } | |
def readWithDefault[A: Unmarshaller](key: String)(default: A): ConfigAction[A] = | |
fromUnmarshaller(key) { | |
Unmarshaller[A].recover { case Unmarshaller.Error.MissingEnvValue ⇒ default } | |
} | |
def fromUnmarshaller[A](key: String)(unmarshaller: Unmarshaller[A]): ConfigAction[A] = | |
ConfigAction { | |
Reader { (env: Env) ⇒ | |
unmarshaller | |
.read { env.get(key).toMaybe } | |
.leftMap(ConfigError(key, _)) | |
.validationNel | |
} | |
} | |
} | |
trait ConfigActionInstances { | |
implicit val monad: Monad[ConfigAction] = new Monad[ConfigAction] { | |
override def point[A](a: => A): ConfigAction[A] = | |
ConfigAction(Reader((_: Env) ⇒ Validation.success[NonEmptyList[ConfigError], A](a))) | |
override def ap[A, B](c1: ⇒ ConfigAction[A])(c2: ⇒ ConfigAction[(A) ⇒ B]): ConfigAction[B] = | |
ConfigAction(c1.run.flatMap(validation ⇒ c2.run.map(validation.ap(_)))) | |
override def bind[A, B](fa: ConfigAction[A])(f: A => ConfigAction[B]): ConfigAction[B] = | |
fa.flatMap(f) | |
} | |
} | |
import scalaz.Show | |
final case class ConfigError(key: String, value: Unmarshaller.Error) | |
object ConfigError { | |
implicit val configErrorShow: Show[ConfigError] = Show.show { | |
case ConfigError(key, Unmarshaller.Error.MissingEnvValue) ⇒ s"Config error, required environment variable ${key} is missing" | |
case ConfigError(key, Unmarshaller.Error.InvalidEnvValue(provided, expected)) ⇒ s"Config error, invalid ${key} environment variable. Expected ${expected}, got ${provided}" | |
} | |
} | |
import java.net.URI | |
import Unmarshaller.Error | |
import scalaz.syntax.either._ | |
import scalaz.syntax.id._ | |
import scalaz.syntax.maybe._ | |
import scalaz.{-\/, Maybe, NonEmptyList, \/, \/-} | |
import scala.annotation.tailrec | |
final case class Unmarshaller[A](run: Maybe[String] => Unmarshaller.Error \/ A) { | |
def read(s: Maybe[String]): Unmarshaller.Error \/ A = | |
run(s) | |
def map[B](f: A => B): Unmarshaller[B] = | |
Unmarshaller { run(_).map(f) } | |
def mapError[B](f: A => Unmarshaller.Error \/ B): Unmarshaller[B] = | |
Unmarshaller { run(_).flatMap(f) } | |
def flatMap[B](f: A => Unmarshaller[B]): Unmarshaller[B] = | |
Unmarshaller { str => run(str).flatMap { f(_).run(str) }} | |
def recover(err: PartialFunction[Unmarshaller.Error, A]): Unmarshaller[A] = | |
Unmarshaller { run(_).recover(err) } | |
} | |
object Unmarshaller extends UnmarshallerInstances { | |
sealed trait Error | |
object Error { | |
case object MissingEnvValue extends Error | |
final case class InvalidEnvValue(provided: String, expected: String) extends Error | |
} | |
@SuppressWarnings(Array("org.wartremover.warts.Overloading")) | |
def apply[A: Unmarshaller]: Unmarshaller[A] = | |
implicitly[Unmarshaller[A]] | |
} | |
trait UnmarshallerInstances { | |
implicit val stringValueUnmarshaller: Unmarshaller[String] = | |
Unmarshaller { _ \/> Error.MissingEnvValue } | |
implicit val boolValueUnmarshaller: Unmarshaller[Boolean] = | |
Unmarshaller[String].mapError { value ⇒ \/.fromTryCatchNonFatal(value.toBoolean).leftMap(_ => Error.InvalidEnvValue(value, "boolean"))} | |
implicit val intValueUnmarshaller: Unmarshaller[Int] = | |
Unmarshaller[String].mapError { value ⇒ \/.fromTryCatchNonFatal(value.toInt).leftMap(_ => Error.InvalidEnvValue(value, "integer")) } | |
implicit val longValueUnmarshaller: Unmarshaller[Long] = | |
Unmarshaller[String].mapError { value ⇒ \/.fromTryCatchNonFatal(value.toLong).leftMap(_ => Error.InvalidEnvValue(value, "long"))} | |
implicit def maybeValueUnmarshaller[A: Unmarshaller]: Unmarshaller[Maybe[A]] = | |
Unmarshaller { | |
Unmarshaller[A].read(_) match { | |
case -\/(Error.MissingEnvValue) ⇒ \/.right[Unmarshaller.Error, Maybe[A]](Maybe.empty) | |
case other ⇒ other.map(Maybe.just) | |
} | |
} | |
implicit def nonEmptyListValueUnmarshaller[A: Unmarshaller]: Unmarshaller[NonEmptyList[A]] = | |
Unmarshaller { value ⇒ | |
val list = value.getOrElse("").split(",").map(_.trim).filter(_.nonEmpty).toList | |
list match { | |
case x :: xs ⇒ | |
NonEmptyList(x, xs: _*).traverse1[Unmarshaller.Error \/ ?, A](_.just |> Unmarshaller[A].read) | |
case Nil ⇒ | |
Unmarshaller.Error.InvalidEnvValue(value.getOrElse(""), "nonemptylist").left[NonEmptyList[A]] | |
} | |
} | |
implicit def listValueUnmarshaller[A: Unmarshaller]: Unmarshaller[List[A]] = | |
Unmarshaller { value => | |
@tailrec | |
def loop(items: List[String], accum: List[A]): Unmarshaller.Error \/ List[A] = | |
items match { | |
case x :: xs => | |
Unmarshaller[A].read(x.just) match { | |
case \/-(a) => | |
loop(xs, a +: accum) | |
case -\/(_) => | |
Unmarshaller.Error.InvalidEnvValue(value.getOrElse(""), "list").left[List[A]] | |
} | |
case Nil => | |
accum.right[Unmarshaller.Error] | |
} | |
val list = value.getOrElse("").split(",").map(_.trim).filter(_.nonEmpty).toList | |
loop(list, List.empty[A]) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Added the library https://github.com/ajevans85/scala-simple-env-config as a reference point.
I will be very happy to push on with the idealogies kept in the library. As far as I can say, if HOCON can be flattened this will be the best one existing.