diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81f936ee7..69bb904ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: - name: Cache scala dependencies uses: coursier/cache-action@v6 - name: Check Document Generation - run: ./sbt docs/compileDocs + run: sbt docs/compileDocs lint: runs-on: ubuntu-22.04 diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index 4af492966..8f01d0e1b 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -21,14 +21,16 @@ jobs: uses: actions/checkout@v3.3.0 with: fetch-depth: '0' - - name: Setup Scala + - name: Setup JVM uses: actions/setup-java@v4.5.0 with: distribution: temurin java-version: 17 check-latest: true + - name: Setup SBT + uses: sbt/setup-sbt@v1 - name: Check website build process - run: sbt docs/buildWebsite + run: sbt docs/buildWebsite publish-docs: name: Publish Docs runs-on: ubuntu-latest @@ -38,19 +40,21 @@ jobs: uses: actions/checkout@v3.3.0 with: fetch-depth: '0' - - name: Setup Scala + - name: Setup JVM uses: actions/setup-java@v4.5.0 with: distribution: temurin java-version: 17 check-latest: true + - name: Setup SBT + uses: sbt/setup-sbt@v1 - name: Setup NodeJs uses: actions/setup-node@v3 with: node-version: 16.x registry-url: https://registry.npmjs.org - name: Publish Docs to NPM Registry - run: sbt docs/publishToNpm + run: sbt docs/publishToNpm env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} generate-readme: @@ -63,14 +67,16 @@ jobs: with: ref: ${{ github.head_ref }} fetch-depth: '0' - - name: Setup Scala + - name: Setup JVM uses: actions/setup-java@v4.5.0 with: distribution: temurin java-version: 17 check-latest: true + - name: Setup SBT + uses: sbt/setup-sbt@v1 - name: Generate Readme - run: sbt docs/generateReadme + run: sbt docs/generateReadme - name: Commit Changes run: | git config --local user.email "github-actions[bot]@users.noreply.github.com" diff --git a/core/shared/src/main/scala-3.x/zio/config/TupleConversion.scala b/core/shared/src/main/scala-3.x/zio/config/TupleConversion.scala index f442b2a07..decad4c4f 100644 --- a/core/shared/src/main/scala-3.x/zio/config/TupleConversion.scala +++ b/core/shared/src/main/scala-3.x/zio/config/TupleConversion.scala @@ -10,7 +10,7 @@ trait TupleConversion[A, B] { object TupleConversion extends ImplicitTupleConversion trait ImplicitTupleConversion { - inline given autoTupleConversion[Prod <: Product](using + given autoTupleConversion[Prod <: Product](using m: Mirror.ProductOf[Prod] ): TupleConversion[Prod, m.MirroredElemTypes] = new TupleConversion[Prod, m.MirroredElemTypes] { @@ -20,12 +20,11 @@ trait ImplicitTupleConversion { inline given autoTupleConversion1[Prod <: Product, A](using c: TupleConversion[Prod, Tuple1[A]] - ): TupleConversion[Prod, A] = - new TupleConversion[Prod, A] { - def to(a: Prod): A = { - val Tuple1(v) = c.to(a) - v - } - def from(b: A): Prod = c.from(Tuple1(b)) + ): TupleConversion[Prod, A] with { + def to(a: Prod): A = { + val Tuple1(v) = c.to(a) + v } + def from(b: A): Prod = c.from(Tuple1(b)) + } } diff --git a/core/shared/src/main/scala/zio/config/package.scala b/core/shared/src/main/scala/zio/config/package.scala index 9390ecacf..7b52c560b 100644 --- a/core/shared/src/main/scala/zio/config/package.scala +++ b/core/shared/src/main/scala/zio/config/package.scala @@ -1,10 +1,8 @@ package zio -import zio.config.syntax.ConfigSyntax - package object config extends KeyConversionFunctions - with ConfigSyntax + with syntax.ConfigSyntax with ImplicitTupleConversion with ConfigDocsModule { diff --git a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/DeriveConfig.scala b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/DeriveConfig.scala index 24516d3e7..bed52312f 100644 --- a/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/DeriveConfig.scala +++ b/magnolia/shared/src/main/scala-dotty/zio/config/magnolia/DeriveConfig.scala @@ -1,21 +1,17 @@ package zio.config.magnolia -import zio.config._ -import zio.NonEmptyChunk - -import java.io.File -import java.net.{URI, URL} -import java.time.{Duration, Instant, LocalDate, LocalDateTime, LocalTime, OffsetDateTime} +import zio.Config.* +import zio.config.* +import zio.config.derivation.* +import zio.config.magnolia.DeriveConfig.* +import zio.{Chunk, Config, LogLevel, NonEmptyChunk} + +import java.net.URI +import java.time.{LocalDate, LocalDateTime, LocalTime, OffsetDateTime, *} import java.util.UUID -import scala.concurrent.duration.{Duration => ScalaDuration} -import scala.deriving._ -import scala.compiletime.{constValue, constValueTuple, erasedValue, summonFrom, summonInline} -import scala.quoted -import scala.util.Try -import DeriveConfig._ -import zio.{Chunk, Config, ConfigProvider, LogLevel}, Config._ -import zio.config.syntax._ -import zio.config.derivation._ +import scala.annotation.{targetName, threadUnsafe} +import scala.compiletime.* +import scala.deriving.* final case class DeriveConfig[A](desc: Config[A], metadata: Option[DeriveConfig.Metadata] = None) { def ??(description: String): DeriveConfig[A] = @@ -119,7 +115,7 @@ object DeriveConfig { DeriveConfig.from(table(ev.desc)) inline def summonDeriveConfigForCoProduct[T <: Tuple]: List[DeriveConfig[Any]] = - inline erasedValue[T] match + inline erasedValue[T] match { case _: EmptyTuple => Nil case _: (t *: ts) => val desc = summonInline[DeriveConfig[t]] @@ -127,27 +123,35 @@ object DeriveConfig { desc.desc, desc.metadata ) :: summonDeriveConfigForCoProduct[ts] + } inline def summonDeriveConfigAll[T <: Tuple]: List[DeriveConfig[_]] = - inline erasedValue[T] match + inline erasedValue[T] match { case _: EmptyTuple => Nil - case _: (t *: ts) => - summonInline[DeriveConfig[t]] :: summonDeriveConfigAll[ts] + case _: (t *: ts) => summonInline[DeriveConfig[t]] :: summonDeriveConfigAll[ts] + } inline def labelsOf[T <: Tuple]: List[String] = - inline erasedValue[T] match + inline erasedValue[T] match { case _: EmptyTuple => Nil case _: (t *: ts) => constValue[t].toString :: labelsOf[ts] + } inline def customNamesOf[T]: List[String] = - Macros.nameOf[T].map(_.name) + inline Macros.nameOf[T] match { + case Nil => Nil + case names => names.map(_.name) + } inline def customFieldNamesOf[T]: Map[String, name] = - Macros.fieldNameOf[T].flatMap { case (str, nmes) => nmes.map(name => (str, name)) }.toMap + inline Macros.fieldNameOf[T] match { + case Nil => Map.empty[String, name] + case names => names.flatMap { case (str, nmes) => nmes.map(name => (str, name)) }.toMap + } inline given derived[T](using m: Mirror.Of[T]): DeriveConfig[T] = inline m match - case s: Mirror.SumOf[T] => + case _: Mirror.SumOf[T] => val coproductName: CoproductName = CoproductName( originalName = constValue[m.MirroredLabel], @@ -156,11 +160,8 @@ object DeriveConfig { typeDiscriminator = Macros.discriminator[T].headOption.map(_.keyName) ) - lazy val subClassDescriptions = - summonDeriveConfigForCoProduct[m.MirroredElemTypes] - - lazy val desc = - mergeAllProducts(subClassDescriptions.map(castTo[DeriveConfig[T]]), coproductName.typeDiscriminator) + val subClassDescriptions = summonDeriveConfigForCoProduct[m.MirroredElemTypes] + val desc = mergeAllProducts(subClassDescriptions.map(castTo[DeriveConfig[T]]), coproductName.typeDiscriminator) DeriveConfig.from(tryAllKeys(desc.desc, None, coproductName.alternativeNames)) @@ -172,44 +173,39 @@ object DeriveConfig { descriptions = Macros.documentationOf[T].map(_.describe) ) - lazy val originalFieldNamesList = - labelsOf[m.MirroredElemLabels] - - lazy val customFieldNameMap = - customFieldNamesOf[T] - - lazy val documentations = - Macros.fieldDocumentationOf[T].toMap - - lazy val fieldAndDefaultValues: Map[String, Any] = - Macros.defaultValuesOf[T].toMap - - lazy val fieldNames = - originalFieldNamesList.foldRight(Nil: List[FieldName]) { (str, list) => - val alternativeNames = customFieldNameMap.get(str).map(v => List(v.name)).getOrElse(Nil) - val descriptions = documentations.get(str).map(_.map(_.describe)).getOrElse(Nil) - FieldName(str, alternativeNames.toList, descriptions) :: list - } - - lazy val fieldConfigs = - summonDeriveConfigAll[m.MirroredElemTypes].asInstanceOf[List[DeriveConfig[Any]]] + val originalFieldNamesList = labelsOf[m.MirroredElemLabels] + val customFieldNameMap = customFieldNamesOf[T] + val documentations = Macros.fieldDocumentationOf[T].toMap + val fieldNames = mapOriginalNames(originalFieldNamesList, documentations, customFieldNameMap) - lazy val fieldConfigsWithDefaultValues = + @threadUnsafe lazy val fieldConfigsWithDefaultValues = { + val fieldConfigs = summonDeriveConfigAll[m.MirroredElemTypes].asInstanceOf[List[DeriveConfig[Any]]] + val fieldAndDefaultValues = Macros.defaultValuesOf[T].toMap addDefaultValues(fieldAndDefaultValues, originalFieldNamesList, fieldConfigs) + } mergeAllFields( fieldConfigsWithDefaultValues, productName, fieldNames, - lst => m.fromProduct(Tuple.fromArray(lst.toArray[Any])), - castTo[Product](_).productIterator.toList + lst => m.fromProduct(Tuple.fromArray(lst.toArray[Any])) ) + private def mapOriginalNames( + names: List[String], + docs: Map[String, List[describe]], + customFieldNames: Map[String, name] + ): List[FieldName] = + names.foldRight(List.empty[FieldName]) { (str, list) => + val alternativeNames = customFieldNames.get(str).map(v => List(v.name)).getOrElse(Nil) + val descriptions = docs.get(str).map(_.map(_.describe)).getOrElse(Nil) + FieldName(str, alternativeNames, descriptions) :: list + } + def mergeAllProducts[T]( allDescs: => List[DeriveConfig[T]], typeDiscriminator: Option[String] - ): DeriveConfig[T] = - + ): DeriveConfig[T] = { val desc = typeDiscriminator match { case None => @@ -238,11 +234,12 @@ object DeriveConfig { case None => Nil } - }: _* + }* ) } DeriveConfig.from(desc) + } def addDefaultValues( defaultValues: Map[String, Any], @@ -256,6 +253,7 @@ object DeriveConfig { } } + @deprecated("use overloaded method without providing `g`") def mergeAllFields[T]( allDescs: => List[DeriveConfig[_]], productName: ProductName, @@ -263,7 +261,15 @@ object DeriveConfig { f: List[Any] => T, g: T => List[Any] ): DeriveConfig[T] = - if fieldNames.isEmpty then // if there are no fields in the product then the value is the name of the product itself + mergeAllFields[T](allDescs, productName, fieldNames, f) + + def mergeAllFields[T]( + allDescs: => List[DeriveConfig[_]], + productName: ProductName, + fieldNames: List[FieldName], + f: List[Any] => T + ): DeriveConfig[T] = + if fieldNames.isEmpty then { // if there are no fields in the product then the value is the name of the product itself val tryAllPaths = (productName.originalName :: productName.alternativeNames) .map(n => zio.Config.constant(n)) @@ -273,7 +279,7 @@ object DeriveConfig { tryAllPaths.map[T](_ => f(Nil)), Some(Metadata.Object[T](productName, f(Nil))) // We propogate the info that product was actually an object ) - else + } else { val listOfDesc = fieldNames.zip(allDescs).map { case (fieldName, desc) => val fieldDesc = tryAllKeys(desc.desc, Some(fieldName.originalName), fieldName.alternativeNames) @@ -281,9 +287,10 @@ object DeriveConfig { } val descOfList = - Config.collectAll(listOfDesc.head, listOfDesc.tail: _*) + Config.collectAll(listOfDesc.head, listOfDesc.tail*) DeriveConfig(descOfList.map(f), Some(Metadata.Product(productName, fieldNames))) + } def tryAllKeys[A]( desc: Config[A],