From 2adb5f23284b7df4fb93b4a19efde36105294010 Mon Sep 17 00:00:00 2001 From: kyri-petrou <67301607+kyri-petrou@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:21:33 +0530 Subject: [PATCH] Make it possible to use `derives Config` syntax in Scala 3 (#1479) --- docs/automatic-derivation-of-config.md | 19 ++++++++++++++ .../zio/config/magnolia/package.scala | 14 +++++++--- .../config/magnolia/AutomaticConfigSpec.scala | 26 ++++++++++++++++++- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/docs/automatic-derivation-of-config.md b/docs/automatic-derivation-of-config.md index cc97f2bce..6d3bf45f9 100644 --- a/docs/automatic-derivation-of-config.md +++ b/docs/automatic-derivation-of-config.md @@ -322,6 +322,25 @@ In this case, the config should be } ``` +### Config derivation via `derives` keyword + +Scala 3 has introduced `derives` keyword to derive typeclasses without the need of explicitely declaring the implicit / given. +This syntax can be enabled for `zio.Config` by importing `zio.config.magnolia.*` and then using the `derives` keyword on the type that needs to be derived: + +```scala 3 +import zio.config.magnolia.* +import zio.{Config, ZIO} + +sealed trait A +case class B(x: String) extends A +case class C(y: String) extends A + +case class MyConfig(a: A) derives Config + +// And then simply summon the `Config[MyConfig]` instance as you would normally: +val cfg = ZIO.config[MyConfig] +``` + ### No guaranteed behavior for scala-3 enum yet With the current release, there is no guaranteed support of scala-3 enum. Use `sealed trait` and `case class` pattern. diff --git a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/package.scala b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/package.scala index d8871b189..4fccb3835 100644 --- a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/package.scala +++ b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/package.scala @@ -1,10 +1,10 @@ package zio.config -import zio.ConfigProvider - -import zio.Config +import zio.{Config, ConfigProvider} import zio.IO +import scala.deriving.Mirror + package object magnolia { def deriveConfig[A](implicit ev: DeriveConfig[A]) = ev.desc @@ -26,4 +26,12 @@ package object magnolia { def autoLoad[A: DeriveConfig]: IO[Config.Error, A] = configProvider.load(DeriveConfig[A].desc) } + + /** + * Enables derivation of Config via the `derives Config` syntax + */ + extension (? : Config.type) { + inline def derived[A](using Mirror.Of[A]): Config[A] = DeriveConfig.derived[A].desc + } + } diff --git a/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/AutomaticConfigSpec.scala b/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/AutomaticConfigSpec.scala index 72d920b7e..3ba9ae24e 100644 --- a/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/AutomaticConfigSpec.scala +++ b/magnolia/shared/src/test/scala-dotty/zio/config/magnolia/AutomaticConfigSpec.scala @@ -1,6 +1,6 @@ package zio.config.magnolia -import zio.ConfigProvider +import zio.{Config, ConfigProvider} import zio.config._ import zio.test.Assertion._ import zio.test._ @@ -20,6 +20,14 @@ object AutomaticConfigSpec extends ZIOSpecDefault { val source = ConfigProvider.fromMap(environment) + assertZIO(source.load(configDesc).either)(isRight) + } + }, + test("derives syntax spec") { + check(genEnvironment) { environment => + val configDesc = summon[Config[MyConfigDerived]] + val source = ConfigProvider.fromMap(environment) + assertZIO(source.load(configDesc).either)(isRight) } } @@ -63,6 +71,22 @@ object AutomaticConfigTestUtils { id: UUID ) + final case class MyConfigDerived( + aws: Aws, + cost: Price, + dburl: DbUrl, + port: Int, + amount: Option[Long], + quantity: Either[Long, String], + default: Int = 1, + anotherDefault: Boolean = true, + descriptions: List[String], + created: LocalDate, + updated: LocalTime, + lastVisited: LocalDateTime, + id: UUID + ) derives Config + private val genPriceDescription = Gen.const(Description("some description")) private val genCurrency: Gen[Any, Currency] = Gen.double(10.0, 20.0).map(Currency.apply) private val genPrice: Gen[Any, Price] = Gen.oneOf(genPriceDescription, genCurrency)