Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weird regression in ZIO Config 4 with the derived config #1224

Open
alphaho opened this issue Jul 22, 2023 · 0 comments
Open

Weird regression in ZIO Config 4 with the derived config #1224

alphaho opened this issue Jul 22, 2023 · 0 comments

Comments

@alphaho
Copy link

alphaho commented Jul 22, 2023

I was trying to upgrade ZIO Config from 3.0.6 to 4.0.0-RC16 and found a weird regression that I believe is related to the automatic derivation. Attached is the minimum reproduction of the issue.

With the below snippet, it will fail with the test case 3 and 5, while the rest would succeed.

This issue seems to be related to the names of the ADT. From below snippet, we can find different combinations of them, each with different ordering from the alphabet.

image

import zio.config.magnolia._
import zio.config.yaml.YamlConfigProvider
import zio.config.{ConfigOps, read}
import zio.test._

object ZioConfigTest extends ZIOSpecDefault {

  object ConfigThatWorks1 {
    sealed trait SubConfig

    object SubConfig {
      @name("empty")
      case object Empty extends SubConfig

      @name("some")
      case class F( // "F" comes after "E" (from "Empty") in the alphabet
        map: Map[String, Int]
      ) extends SubConfig
    }

    case class ParentConfig(conf: SubConfig)

    val parentConfig: Config[ParentConfig] = DeriveConfig.deriveConfig[ParentConfig]
  }

  object ConfigThatDoesNotWork1 {
    sealed trait SubConfig

    object SubConfig {
      @name("empty")
      case object Empty extends SubConfig

      @name("some")
      case class D( // "D" comes before "E" (from "Empty") in the alphabet
        map: Map[String, Int]
      ) extends SubConfig
    }

    case class ParentConfig(conf: SubConfig)

    val parentConfig: Config[ParentConfig] = DeriveConfig.deriveConfig[ParentConfig]
  }

  object ConfigThatDoesNotWork2 {
    sealed trait SubConfig

    object SubConfig {
      @name("empty")
      case object Empty extends SubConfig

      @name("some")
      case class E( // "E" is the same as "E" (from "Empty") in the alphabet
        map: Map[String, Int]
      ) extends SubConfig
    }

    case class ParentConfig(conf: SubConfig)

    val parentConfig: Config[ParentConfig] = DeriveConfig.deriveConfig[ParentConfig]
  }

  override def spec: Spec[TestEnvironment with Scope, Any] = suite("ZioConfigTest")(
    suite("suite 1: config that works - ConfigThatWorks1")(
      test("case 1: should be able to parse as `empty`") {
        val yaml =
          """
            |conf:
            |  empty
            |""".stripMargin

        val expected = ConfigThatWorks1.ParentConfig(
          ConfigThatWorks1.SubConfig.Empty
        )

        assertConfig(ConfigThatWorks1.parentConfig)(yaml, expected)
      },
      test("case 2: should be able to parse as `some`") {
        val yaml =
          """
            |conf:
            |  some:
            |    map:
            |      abc: 123
            |""".stripMargin

        val expected = ConfigThatWorks1.ParentConfig(
          ConfigThatWorks1.SubConfig.F(
            Map("abc" -> 123)
          )
        )

        assertConfig(ConfigThatWorks1.parentConfig)(yaml, expected)
      }
    ),
    suite("suite 2: config that works - ConfigThatDoesNotWork1")(
      test("case 3: should be able to parse as `empty`") {
        val yaml =
          """
            |conf:
            |  empty
            |""".stripMargin

        val expected = ConfigThatDoesNotWork1.ParentConfig(
          ConfigThatDoesNotWork1.SubConfig.Empty
        )

        assertConfig(ConfigThatDoesNotWork1.parentConfig)(yaml, expected)
      },
      test("case 4: should be able to parse as `some`") {
        val yaml =
          """
            |conf:
            |  some:
            |    map:
            |      abc: 123
            |""".stripMargin

        val expected = ConfigThatDoesNotWork1.ParentConfig(
          ConfigThatDoesNotWork1.SubConfig.D(
            Map("abc" -> 123)
          )
        )

        assertConfig(ConfigThatDoesNotWork1.parentConfig)(yaml, expected)
      }
    ),
    suite("suite 3: config that works - ConfigThatDoesNotWork2")(
      test("case 5: should be able to parse as `empty`") {
        val yaml =
          """
            |conf:
            |  empty
            |""".stripMargin

        val expected = ConfigThatDoesNotWork2.ParentConfig(
          ConfigThatDoesNotWork2.SubConfig.Empty
        )

        assertConfig(ConfigThatDoesNotWork2.parentConfig)(yaml, expected)
      },
      test("case 6: should be able to parse as `some`") {
        val yaml =
          """
            |conf:
            |  some:
            |    map:
            |      abc: 123
            |""".stripMargin

        val expected = ConfigThatDoesNotWork2.ParentConfig(
          ConfigThatDoesNotWork2.SubConfig.E(
            Map("abc" -> 123)
          )
        )

        assertConfig(ConfigThatDoesNotWork2.parentConfig)(yaml, expected)
      }
    )
  )

  private def assertConfig[T](config: Config[T])(yaml: String, expected: T): IO[Config.Error, TestResult] = {
    val source = YamlConfigProvider.fromYamlString(yaml)

    for {
      result <- read(config from source)
    } yield assertTrue(result == expected)
  }
}

Environment:

  • Scala 2.13.11
  • ZIO Config 4.0.0-RC16
  • ZIO 2.0.15
  • Windows 11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant