From 2353ab789b2f8b899da551b012a57df4ad6c4602 Mon Sep 17 00:00:00 2001 From: "i.a.finochenko" Date: Mon, 22 Apr 2024 13:01:52 +0300 Subject: [PATCH] Add safe ZIO methods builders of ConfigProvider --- build.sbt | 3 ++- .../zio/config/syntax/ConfigSyntax.scala | 22 +++++++++++++++- .../magnolia/CoproductSealedTraitSpec.scala | 2 -- .../TypesafeConfigProviderZIOTest.scala | 25 +++++++++++++++++++ .../typesafe/TypesafeConfigProvider.scala | 20 ++++++++++++++- .../scala/zio/config/typesafe/package.scala | 18 +++++++++++++ .../zio/config/yaml/YamlConfigProvider.scala | 25 ++++++++++++++----- .../main/scala/zio/config/yaml/package.scala | 18 +++++++++++++ .../zio/config/yaml/YamlConfigSpec.scala | 9 +++++++ 9 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 typesafe-magnolia-tests/shared/src/test/scala/zio/config/typesafe/TypesafeConfigProviderZIOTest.scala diff --git a/build.sbt b/build.sbt index ecd17861e..4050ae4c7 100644 --- a/build.sbt +++ b/build.sbt @@ -85,6 +85,7 @@ lazy val scala212projects = Seq[ProjectReference]( zioConfigCatsJVM, zioConfigRefinedJVM, zioConfigMagnoliaJVM, + zioConfigTypesafeMagnoliaTestsJVM, zioConfigZioAwsJVM, zioConfigXmlJVM, examplesJVM @@ -409,7 +410,7 @@ lazy val zioConfigTypesafeMagnoliaTests = crossProject(JVMPlatform) ), testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")) ) - .dependsOn(zioConfig % "compile->compile;test->test", zioConfigTypesafe, zioConfigMagnolia) + .dependsOn(zioConfig % "compile->compile;test->test", zioConfigTypesafe, zioConfigMagnolia, zioConfigDerivation) lazy val zioConfigTypesafeMagnoliaTestsJVM = zioConfigTypesafeMagnoliaTests.jvm lazy val docs = project diff --git a/core/shared/src/main/scala/zio/config/syntax/ConfigSyntax.scala b/core/shared/src/main/scala/zio/config/syntax/ConfigSyntax.scala index 0b8db513e..0e043bc83 100644 --- a/core/shared/src/main/scala/zio/config/syntax/ConfigSyntax.scala +++ b/core/shared/src/main/scala/zio/config/syntax/ConfigSyntax.scala @@ -2,7 +2,8 @@ package zio.config.syntax import zio.Config.Error.{And, InvalidData, MissingData, Or, SourceUnavailable, Unsupported} import zio.config.TupleConversion -import zio.{Chunk, Config, ConfigProvider, IO} +import zio.config.syntax.ConfigSyntax.ConfigProviderZIO +import zio.{Chunk, Config, ConfigProvider, IO, Task} import java.util.UUID import scala.util.control.NonFatal @@ -10,6 +11,17 @@ import scala.util.control.NonFatal // Backward compatible approach to minimise the client changes final case class Read[A](config: Config[A], configProvider: ConfigProvider) +final case class ReadZIO[A](config: Config[A], configProvider: ConfigProviderZIO) + +object ConfigSyntax { + type ConfigProviderZIO = IO[Config.Error, ConfigProvider] + + implicit class ConfigProviderZIOOps(buildConfigProvider: Task[ConfigProvider]) { + def toConfigProviderZIO: ConfigProviderZIO = + buildConfigProvider.mapError(throwable => Config.Error.Unsupported(message = throwable.toString)) + } +} + // To be moved to ZIO ? // Or may be zio-config can be considered as an extension to ZIO trait ConfigSyntax { @@ -18,6 +30,9 @@ trait ConfigSyntax { final def read[A](reader: Read[A]): IO[Config.Error, A] = reader.configProvider.load(reader.config) + final def read[A](reader: ReadZIO[A]): IO[Config.Error, A] = + reader.configProvider.flatMap(_.load(reader.config)) + implicit class ConfigErrorOps(error: Config.Error) { self => @@ -186,6 +201,11 @@ trait ConfigSyntax { // Example: read(config from ConfigProvider.fromMap("")) def from(configProvider: ConfigProvider): Read[A] = Read(config, configProvider) + + import ConfigSyntax.ConfigProviderZIOOps + + def from(configProvider: Task[ConfigProvider]): ReadZIO[A] = + ReadZIO(config, configProvider.toConfigProviderZIO) } implicit class FromConfigProviderOps(c: ConfigProvider.type) { diff --git a/typesafe-magnolia-tests/shared/src/test/scala/zio/config/magnolia/CoproductSealedTraitSpec.scala b/typesafe-magnolia-tests/shared/src/test/scala/zio/config/magnolia/CoproductSealedTraitSpec.scala index 08a07b40d..6bccf2d67 100644 --- a/typesafe-magnolia-tests/shared/src/test/scala/zio/config/magnolia/CoproductSealedTraitSpec.scala +++ b/typesafe-magnolia-tests/shared/src/test/scala/zio/config/magnolia/CoproductSealedTraitSpec.scala @@ -2,8 +2,6 @@ package zio.config.magnolia import zio.ConfigProvider import zio.config._ -import zio.config.derivation.name -import zio.config.magnolia._ import zio.test.Assertion._ import zio.test.{ZIOSpecDefault, _} diff --git a/typesafe-magnolia-tests/shared/src/test/scala/zio/config/typesafe/TypesafeConfigProviderZIOTest.scala b/typesafe-magnolia-tests/shared/src/test/scala/zio/config/typesafe/TypesafeConfigProviderZIOTest.scala new file mode 100644 index 000000000..d4416d93c --- /dev/null +++ b/typesafe-magnolia-tests/shared/src/test/scala/zio/config/typesafe/TypesafeConfigProviderZIOTest.scala @@ -0,0 +1,25 @@ +package zio.config.typesafe + +import zio.Config +import zio.config.magnolia.deriveConfig +import zio.config.read +import zio.test.Assertion.equalTo +import zio.test.{Spec, ZIOSpecDefault, assertZIO} + +object TypesafeConfigProviderZIOTest extends ZIOSpecDefault { + + final case class DataBaseConfig(url: String) + + val configDataBaseConfig: Config[DataBaseConfig] = deriveConfig[DataBaseConfig] + + val hocconConfig: String = s"""url = "some_url"""" + + override def spec: Spec[Any, Config.Error] = + suite("TypesafeConfigProviderZIOTest")( + test("safe read config") { + val result = read(configDataBaseConfig from TypesafeConfigProvider.fromHoconStringZIO(hocconConfig)) + val expected = DataBaseConfig("some_url") + assertZIO(result)(equalTo(expected)) + } + ) +} diff --git a/typesafe/shared/src/main/scala/zio/config/typesafe/TypesafeConfigProvider.scala b/typesafe/shared/src/main/scala/zio/config/typesafe/TypesafeConfigProvider.scala index 1e3161e21..9d11a2bbd 100644 --- a/typesafe/shared/src/main/scala/zio/config/typesafe/TypesafeConfigProvider.scala +++ b/typesafe/shared/src/main/scala/zio/config/typesafe/TypesafeConfigProvider.scala @@ -2,7 +2,7 @@ package zio.config.typesafe import com.typesafe.config._ import zio.config.IndexedFlat.{ConfigPath, KeyComponent} -import zio.{Chunk, ConfigProvider} +import zio.{Chunk, ConfigProvider, Task, ZIO} import java.io.File import scala.jdk.CollectionConverters._ @@ -62,6 +62,24 @@ object TypesafeConfigProvider { ) } + def fromResourcePathZIO(enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + ZIO.attempt(fromResourcePath(enableCommaSeparatedValueAsList)) + + def fromHoconFileZIO(file: File, enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + ZIO.attempt(fromHoconFile(file, enableCommaSeparatedValueAsList)) + + def fromHoconFilePathZIO(filePath: String, enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + ZIO.attempt(fromHoconFilePath(filePath, enableCommaSeparatedValueAsList)) + + def fromHoconStringZIO(input: String, enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + ZIO.attempt(fromHoconString(input, enableCommaSeparatedValueAsList)) + + def fromTypesafeConfigZIO( + config: com.typesafe.config.Config, + enableCommaSeparatedValueAsList: Boolean = false + ): Task[ConfigProvider] = + ZIO.attempt(fromTypesafeConfig(config, enableCommaSeparatedValueAsList)) + private[config] def getIndexedMap( input: com.typesafe.config.Config ): Map[Chunk[KeyComponent], String] = { diff --git a/typesafe/shared/src/main/scala/zio/config/typesafe/package.scala b/typesafe/shared/src/main/scala/zio/config/typesafe/package.scala index 8ed1a2bf9..d33ee1410 100644 --- a/typesafe/shared/src/main/scala/zio/config/typesafe/package.scala +++ b/typesafe/shared/src/main/scala/zio/config/typesafe/package.scala @@ -24,6 +24,24 @@ package object typesafe { enableCommaSeparatedValueAsList: Boolean = false ): ConfigProvider = TypesafeConfigProvider.fromTypesafeConfig(rawConfig, enableCommaSeparatedValueAsList) + + def fromResourcePathZIO(enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + TypesafeConfigProvider.fromResourcePathZIO(enableCommaSeparatedValueAsList) + + def fromHoconFileZIO(file: File, enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + TypesafeConfigProvider.fromHoconFileZIO(file, enableCommaSeparatedValueAsList) + + def fromHoconFilePathZIO(filePath: String, enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + TypesafeConfigProvider.fromHoconFilePathZIO(filePath, enableCommaSeparatedValueAsList) + + def fromHoconStringZIO(input: String, enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + TypesafeConfigProvider.fromHoconStringZIO(input, enableCommaSeparatedValueAsList) + + def fromTypesafeConfigZIO( + rawConfig: com.typesafe.config.Config, + enableCommaSeparatedValueAsList: Boolean = false + ): Task[ConfigProvider] = + TypesafeConfigProvider.fromTypesafeConfigZIO(rawConfig, enableCommaSeparatedValueAsList) } } diff --git a/yaml/shared/src/main/scala/zio/config/yaml/YamlConfigProvider.scala b/yaml/shared/src/main/scala/zio/config/yaml/YamlConfigProvider.scala index 228284826..f91713eea 100644 --- a/yaml/shared/src/main/scala/zio/config/yaml/YamlConfigProvider.scala +++ b/yaml/shared/src/main/scala/zio/config/yaml/YamlConfigProvider.scala @@ -1,10 +1,9 @@ package zio.config.yaml -import scala.annotation.nowarn import org.snakeyaml.engine.v2.api.{Load, LoadSettings} import zio.config._ import zio.config.syntax.IndexKey -import zio.{Chunk, ConfigProvider} +import zio.{Chunk, ConfigProvider, Task, ZIO} import java.io.{BufferedReader, ByteArrayInputStream, File, FileInputStream, InputStreamReader, Reader} import java.lang.{Boolean => JBoolean, Double => JDouble, Float => JFloat, Integer => JInteger, Long => JLong} @@ -13,12 +12,8 @@ import java.nio.file.Path import java.{util => ju} import scala.jdk.CollectionConverters._ -@nowarn("cat=unused-imports") object YamlConfigProvider { - import scala.collection.compat._ - import VersionSpecificSupport._ - /** * Retrieve a `ConfigSource` from yaml path. * @@ -111,6 +106,24 @@ object YamlConfigProvider { fromYamlReader(new BufferedReader(new InputStreamReader(configStream)), enableCommaSeparatedValueAsList) } + def fromYamlFileZIO(file: File, enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + ZIO.attempt(fromYamlFile(file, enableCommaSeparatedValueAsList)) + + def fromYamlPathZIO(path: Path, enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + ZIO.attempt(fromYamlPath(path, enableCommaSeparatedValueAsList)) + + def fromYamlReaderZIO( + reader: Reader, + enableCommaSeparatedValueAsList: Boolean = false + ): Task[ConfigProvider] = + ZIO.attempt(fromYamlReader(reader, enableCommaSeparatedValueAsList)) + + def fromYamlStringZIO( + yamlString: String, + enableCommaSeparatedValueAsList: Boolean = false + ): Task[ConfigProvider] = + ZIO.attempt(fromYamlString(yamlString, enableCommaSeparatedValueAsList)) + private[yaml] def getIndexedConfigProvider( data: AnyRef, enableCommaSeparatedValueAsList: Boolean = false diff --git a/yaml/shared/src/main/scala/zio/config/yaml/package.scala b/yaml/shared/src/main/scala/zio/config/yaml/package.scala index 1c2de2b1d..4c3a8454f 100644 --- a/yaml/shared/src/main/scala/zio/config/yaml/package.scala +++ b/yaml/shared/src/main/scala/zio/config/yaml/package.scala @@ -30,6 +30,24 @@ package object yaml { enableCommaSeparatedValueAsList: Boolean = false ): ConfigProvider = YamlConfigProvider.getIndexedConfigProvider(loadYaml(repr), enableCommaSeparatedValueAsList) + + def fromYamlFileZIO(file: File, enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + YamlConfigProvider.fromYamlFileZIO(file, enableCommaSeparatedValueAsList) + + def fromYamlPathZIO(path: Path, enableCommaSeparatedValueAsList: Boolean = false): Task[ConfigProvider] = + YamlConfigProvider.fromYamlPathZIO(path, enableCommaSeparatedValueAsList) + + def fromYamlReaderZIO( + reader: Reader, + enableCommaSeparatedValueAsList: Boolean = false + ): Task[ConfigProvider] = + YamlConfigProvider.fromYamlReaderZIO(reader, enableCommaSeparatedValueAsList) + + def fromYamlStringZIO( + yamlString: String, + enableCommaSeparatedValueAsList: Boolean = false + ): Task[ConfigProvider] = + YamlConfigProvider.fromYamlStringZIO(yamlString, enableCommaSeparatedValueAsList) } } diff --git a/yaml/shared/src/test/scala/zio/config/yaml/YamlConfigSpec.scala b/yaml/shared/src/test/scala/zio/config/yaml/YamlConfigSpec.scala index 79b4998f7..fb2ddfaaa 100644 --- a/yaml/shared/src/test/scala/zio/config/yaml/YamlConfigSpec.scala +++ b/yaml/shared/src/test/scala/zio/config/yaml/YamlConfigSpec.scala @@ -55,6 +55,15 @@ object YamlConfigSpec extends ZIOSpecDefault { val expected = Child(List(A("str", Nil), B(false, List(C(1), C(2)), Map("hi" -> 1, "bi" -> 2)))) assertZIO(zio.exit)(succeeds(equalTo(expected))) + }, + test("save read yaml config") { + final case class DataBaseConfig(url: String) + val configDataBaseConfig: Config[DataBaseConfig] = Config.string("url").to[DataBaseConfig] + + val yamlConfig: String = s"""url: "some_url"""" + val result = read(configDataBaseConfig from ConfigProvider.fromYamlStringZIO(yamlConfig)) + val expected = DataBaseConfig("some_url") + assertZIO(result)(equalTo(expected)) } ) }