From 9e205569309eff118f86e5cbaae8e2f3d71c375b Mon Sep 17 00:00:00 2001 From: Kevin Chuang Date: Fri, 30 Aug 2024 16:03:45 +0200 Subject: [PATCH] feat: Add support for pureconfig --- README.md | 1 + build.sc | 14 ++++- docs/_docs/modules/pureconfig.md | 59 +++++++++++++++++++ docs/sidebar.yml | 1 + .../io/github/iltotore/iron/pureconfig.scala | 30 ++++++++++ .../iltotore/iron/PureConfigExample.scala | 17 ++++++ .../iltotore/iron/PureConfigSuite.scala | 18 ++++++ 7 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 docs/_docs/modules/pureconfig.md create mode 100644 pureconfig/src/io/github/iltotore/iron/pureconfig.scala create mode 100644 pureconfig/test/src/io/github/iltotore/iron/PureConfigExample.scala create mode 100644 pureconfig/test/src/io/github/iltotore/iron/PureConfigSuite.scala diff --git a/README.md b/README.md index c5db648a..33f22b69 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ ivy"io.github.iltotore::iron:version" | iron-decline | ✔️ | ✔️ | ✔️ | | iron-doobie | ✔️ | ❌ | ❌ | | iron-jsoniter | ✔️ | ✔️ | ✔️ | +| iron-pureconfig | ✔️ | ❌️ | ❌️ | | iron-scalacheck | ✔️ | ✔️ | ❌ | | iron-skunk | ✔️ | ✔️ | ✔️ | | iron-upickle | ✔️ | ✔️ | ✔️ | diff --git a/build.sc b/build.sc index b03b51b6..fc5f0cb9 100644 --- a/build.sc +++ b/build.sc @@ -78,7 +78,7 @@ object docs extends BaseModule { def artifactName = "iron-docs" val modules: Seq[ScalaModule] = - Seq(main, cats, circe, decline, doobie, upickle, ciris, jsoniter, scalacheck, skunk, upickle, zio, zioJson) + Seq(main, cats, circe, decline, doobie, upickle, ciris, jsoniter, pureconfig, scalacheck, skunk, upickle, zio, zioJson) def docSources = T.sources { T.traverse(modules)(_.docSources)().flatten @@ -138,6 +138,7 @@ object docs extends BaseModule { ".*com.monovore.decline.*" -> ("scaladoc3", "https://javadoc.io/doc/com.monovore/decline_3/latest/"), ".*doobie.*" -> ("scaladoc3", "https://www.javadoc.io/doc/org.tpolecat/doobie-core_3/latest/"), ".*com.github.plokhotnyuk.jsoniter_scala.core.*" -> ("scaladoc3", "https://www.javadoc.io/doc/com.github.plokhotnyuk.jsoniter-scala/jsoniter-scala-core_3/latest/"), + ".*pureconfig.*" -> ("scaladoc3", "https://www.javadoc.io/doc/com.github.pureconfig/pureconfig-core_3/latest/index.html"), ".*io.bullet.borer.*" -> ("scaladoc3", "https://javadoc.io/doc/io.bullet/borer-core_3/latest/"), ".*org.scalacheck.*" -> ("scaladoc3", "https://javadoc.io/doc/org.scalacheck/scalacheck_3/latest/"), ".*skunk.*" -> ("scaladoc3", "https://javadoc.io/doc/org.tpolecat/skunk-docs_3/latest/"), @@ -479,3 +480,14 @@ object decline extends SubModule { object native extends NativeCrossModule } + +object pureconfig extends SubModule { + + def artifactName = "iron-pureconfig" + + def ivyDeps = Agg( + ivy"com.github.pureconfig::pureconfig-core::0.17.7" + ) + + object test extends Tests +} diff --git a/docs/_docs/modules/pureconfig.md b/docs/_docs/modules/pureconfig.md new file mode 100644 index 00000000..16935ee9 --- /dev/null +++ b/docs/_docs/modules/pureconfig.md @@ -0,0 +1,59 @@ +--- +title: "PureConfig Support" +--- + +# PureConfig Support + +This module provides refined types ConfigReader instances for [PureConfig](https://pureconfig.github.io/). + +## Dependency + +SBT: + +```scala +libraryDependencies += "io.github.iltotore" %% "iron-pureconfig" % "version" +``` + +Mill: + +```scala +ivy"io.github.iltotore::iron-pureconfig:version" +``` + +### Following examples' dependencies + +SBT: + +```scala +libraryDependencies += "com.github.pureconfig" %% "pureconfig-core" % "0.17.7" +``` + +Mill: + +```scala +ivy"com.github.pureconfig::pureconfig-core::0.17.7" +``` + +## ConfigReader instances + +Iron provides `ConfigReader` instances for refined types: + +```scala +package io.github.iltotore.iron + +import pureconfig.ConfigReader +import pureconfig.generic.derivation.default.* +import io.github.iltotore.iron.constraint.all.* +import io.github.iltotore.iron.pureconfig.given + +opaque type Username = String :| MinLength[5] +object Username extends RefinedTypeOps[String, MinLength[5], Username] + +case class IronTypeConfig( + username: String :| MinLength[5] +) derives ConfigReader + +case class NewTypeConfig( + username: Username +) derives ConfigReader +``` diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 02d6c1b8..30bd6e64 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -25,6 +25,7 @@ subsection: - page: modules/decline.md - page: modules/doobie.md - page: modules/jsoniter.md + - page: modules/pureconfig.md - page: modules/skunk.md - page: modules/scalacheck.md - page: modules/upickle.md diff --git a/pureconfig/src/io/github/iltotore/iron/pureconfig.scala b/pureconfig/src/io/github/iltotore/iron/pureconfig.scala new file mode 100644 index 00000000..8c0b0bc7 --- /dev/null +++ b/pureconfig/src/io/github/iltotore/iron/pureconfig.scala @@ -0,0 +1,30 @@ +package io.github.iltotore.iron + +import _root_.pureconfig.ConfigReader +import _root_.pureconfig.error.FailureReason + +object pureconfig: + final case class RefinedConfigError(description: String) extends FailureReason + + /** + * A [[ConfigReader]] for refined types. Decodes to the underlying type then checks the constraint. + * + * @param reader the [[ConfigReader]] of the underlying type + * @param constraint the [[Constraint]] implementation to test the decoded value + */ + inline given [A, C](using inline reader: ConfigReader[A], inline constraint: Constraint[A, C]): ConfigReader[A :| C] = + reader + .emap: value => + value + .refineEither[C] + .left + .map(RefinedConfigError(_)) + + /** + * A [[ConfigReader]] for new types. Decodes to the underlying type then checks the constraint + + * @param mirror the mirror of the [[RefinedTypeOps.Mirror]] + * @param reader the [[ConfigReader]] of the underlying type + */ + inline given [A](using mirror: RefinedTypeOps.Mirror[A], reader: ConfigReader[mirror.IronType]): ConfigReader[A] = + reader.asInstanceOf[ConfigReader[A]] diff --git a/pureconfig/test/src/io/github/iltotore/iron/PureConfigExample.scala b/pureconfig/test/src/io/github/iltotore/iron/PureConfigExample.scala new file mode 100644 index 00000000..5e2f4cd9 --- /dev/null +++ b/pureconfig/test/src/io/github/iltotore/iron/PureConfigExample.scala @@ -0,0 +1,17 @@ +package io.github.iltotore.iron + +import _root_.pureconfig.ConfigReader +import _root_.pureconfig.generic.derivation.default.* +import io.github.iltotore.iron.constraint.all.* +import io.github.iltotore.iron.pureconfig.given + +opaque type Username = String :| MinLength[5] +object Username extends RefinedTypeOps[String, MinLength[5], Username] + +case class IronTypeConfig( + username: String :| MinLength[5] +) derives ConfigReader + +case class NewTypeConfig( + username: Username +) derives ConfigReader diff --git a/pureconfig/test/src/io/github/iltotore/iron/PureConfigSuite.scala b/pureconfig/test/src/io/github/iltotore/iron/PureConfigSuite.scala new file mode 100644 index 00000000..ce3a4194 --- /dev/null +++ b/pureconfig/test/src/io/github/iltotore/iron/PureConfigSuite.scala @@ -0,0 +1,18 @@ +package io.github.iltotore.iron + +import _root_.pureconfig.ConfigSource +import _root_.pureconfig.generic.derivation.default.* +import utest.* + +object PureConfigSuite extends TestSuite: + + val tests: Tests = Tests: + + test("reader"): + test("ironType"): + test("success") - assert(ConfigSource.string("{ username: admin }").load[IronTypeConfig] == Right(IronTypeConfig("admin"))) + test("failure") - assert(ConfigSource.string("{ username: a }").load[IronTypeConfig].isLeft) + + test("newType"): + test("success") - assert(ConfigSource.string("{ username: admin }").load[NewTypeConfig] == Right(NewTypeConfig(Username("admin")))) + test("failure") - assert(ConfigSource.string("{ username: a }").load[NewTypeConfig].isLeft)