Skip to content

Instantly share code, notes, and snippets.

@jdegoes
Created October 9, 2022 12:16
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 jdegoes/5e3f12337d0e375203b77be6f41e1b4b to your computer and use it in GitHub Desktop.
Save jdegoes/5e3f12337d0e375203b77be6f41e1b4b to your computer and use it in GitHub Desktop.
import zio._
sealed trait Config[+A] { self =>
def ++ [B](that: Config[B])(implicit zippable: Zippable[A, B]): Config[zippable.Out] =
Config.Zipped[A, B, zippable.Out](self, that, zippable)
def || [A1 >: A](that: Config[A1]): Config[A1] = Config.Fallback(self, that)
def ?? (label: String): Config[A] = Config.Labelled(self, label)
def map[B](f: A => B): Config[B] = self.mapOrFail(a => Right(f(a)))
def mapOrFail[B](f: A => Either[Config.Error, B]): Config[B] = Config.MapOrFail(self, f)
def optional: Config[Option[A]] = Config.Optional(self)
def sequence: Config[Chunk[A]] = Config.Sequence(self)
def validate[A1 >: A](f: A1 => Boolean): Config[A1] =
self.mapOrFail(a => if (!f(a)) Left(Config.Error.Generic(Chunk.empty, "Validation of configuration data failed")) else Right(a))
}
object Config {
final case class CBoolean(name: String) extends Config[Boolean]
final case class CInt(name: String) extends Config[Int]
final case class CString(name: String) extends Config[String]
final case class CSecret(name: String) extends Config[Chunk[Byte]]
final case class MapOrFail[A, B](original: Config[A], mapOrFail: A => Either[Config.Error, B]) extends Config[B]
final case class Optional[A](config: Config[A]) extends Config[Option[A]]
final case class Sequence[A](config: Config[A]) extends Config[Chunk[A]]
final case class Fallback[A](first: Config[A], second: Config[A]) extends Config[A]
final case class Labelled[A](config: Config[A], label: String) extends Config[A]
final case class Zipped[A, B, C](left: Config[A], right: Config[B], zippable: Zippable.Out[A, B, C]) extends Config[C]
sealed trait Error {
def prefixed(prefix: Chunk[String]): Error = ???
}
object Error {
final case class Generic(path: Chunk[String], message: String) extends Error
final case class InvalidData(path: Chunk[String], message: String) extends Error
final case class MissingData(path: Chunk[String], message: String) extends Error
final case class Or(left: Error, right: Error) extends Error
final case class And(left: Error, right: Error) extends Error
}
def boolean(name: String): Config[Boolean] = CBoolean(name)
def int(name: String): Config[Int] = CInt(name)
def secret(name: String): Config[Chunk[Byte]] = CSecret(name)
def string(name: String): Config[String] = CString(name)
// ...
}
trait ConfigProvider {
def load[A: Tag](config: Config[A], forService: Option[Tag[_]], serviceTrace: Option[Trace])(implicit trace: Trace): IO[Config.Error, A]
}
object Example {
trait Client
def makeClient(host: String, port: Int): UIO[Client] = ???
implicit class ZIOObjectExtensions(zio: ZIO.type) {
def config[A: Tag](config: Config[A]): IO[Config.Error, A] = ???
}
implicit class ZLayerObjectExtensions(zlayer: ZLayer.type) {
def loadConfig[C: Tag](config: Config[C]): ZLayer[Any, Config.Error, C] = ZLayer.fromZIO(ZIO.config(config))
}
final case class ClientConfig(host: String, port: Int)
def clientConfig = (Config.string("host") ++ Config.int("port")).map { case (host, port) => ClientConfig(host, port) }
/**
* Can be configured with:
*
* {"my.package.Client": {"host": "localhost", "port": 8080}}
*/
def layer =
ZLayer {
for {
config <- ZIO.config(clientConfig)
client <- makeClient(config.host, config.port)
} yield client
}
def main(args: Array[String]): Unit = println("Hello World!")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment