Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,12 @@ case class CompositeUnderlying(
subtype: String,
simpleSubtype: Int
)

// Test types for stacked types
type NewtypeValidatedSubtype = NewtypeValidatedSubtype.Type
object NewtypeValidatedSubtype extends Newtype[ValidatedSubtype]

type TwiceValidatedSubtype = TwiceValidatedSubtype.Type
object TwiceValidatedSubtype extends Subtype[ValidatedSubtype]:
override inline def validate(input: ValidatedSubtype): Boolean | String =
if input.length > 15 then true else "String must be longer than 15 characters"
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,24 @@ given validated[A, B](using nt: ValidatedWrappedType[A, B], config: DeriveConfig

given simple[A, B](using nt: SimpleWrappedType[A, B], config: DeriveConfig[A]): DeriveConfig[B] =
nt.unsafeMakeF(config)

given validatedMap[A, B, V](using
nt: ValidatedWrappedType[A, B],
mapConfig: DeriveConfig[Map[A, V]]
): DeriveConfig[Map[B, V]] =
mapConfig.mapOrFail(avMap =>
avMap.foldLeft[Either[Config.Error, Map[B, V]]](Right(Map.empty)) { case (acc, (keyA, value)) =>
for
map <- acc
b <- nt.make(keyA)
.left
.map(e => Config.Error.InvalidData(Chunk.empty, message = e))
yield map + (b -> value)
}
)

given simpleMap[A, B, V](using
nt: SimpleWrappedType[A, B],
mapConfig: DeriveConfig[Map[A, V]]
): DeriveConfig[Map[B, V]] =
mapConfig.map(avMap => avMap.map((a, v) => nt.unsafeMake(a) -> v))
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ import zio.config.magnolia.*
import zio.test.*

object ZioConfigSpec extends ZIOSpecDefault:
case class ValidatedMapConfig(entries: Map[ValidatedNewtype, Int])
case class ValidatedSubtypeMapConfig(entries: Map[ValidatedSubtype, Int])
case class NewtypeValidatedSubtypeMapConfig(entries: Map[NewtypeValidatedSubtype, Int])
case class TwiceValidatedSubtypeMapConfig(entries: Map[TwiceValidatedSubtype, Int])

type SimpleStringNewtype = SimpleStringNewtype.Type
object SimpleStringNewtype extends Newtype[String]

case class SimpleMapConfig(entries: Map[SimpleStringNewtype, Int])

def spec = suite("zio-config")(
test("successfully read config") {
val expectedConfig = Composite(
Expand Down Expand Up @@ -44,5 +54,83 @@ object ZioConfigSpec extends ZIOSpecDefault:
config.is(_.left).getMessage.contains("String must not be empty"),
config.is(_.left).getMessage.contains("String must be longer than 10 characters")
)
}
},
suite("Map")(
test("validated map keys") {
val expectedConfig = ValidatedMapConfig(
Map(ValidatedNewtype("hello") -> 1, ValidatedNewtype("world") -> 2)
)

val source = ConfigProvider.fromMap(
Map(
"entries.hello" -> "1",
"entries.world" -> "2"
)
)

for config <- read(deriveConfig[ValidatedMapConfig] from source)
yield assertTrue(config == expectedConfig)
},
test("simple map keys") {
val expectedConfig = SimpleMapConfig(
Map(SimpleStringNewtype("foo") -> 1, SimpleStringNewtype("bar") -> 2)
)

val source = ConfigProvider.fromMap(
Map(
"entries.foo" -> "1",
"entries.bar" -> "2"
)
)

for config <- read(deriveConfig[SimpleMapConfig] from source)
yield assertTrue(config == expectedConfig)
},
test("fails with invalid validated map keys") {
val source = ConfigProvider.fromMap(
Map(
"entries.short" -> "1"
)
)

for config <- read(deriveConfig[ValidatedSubtypeMapConfig] from source).either
yield assertTrue(
config.is(_.left).getMessage.contains("String must be longer than 10 characters")
)
},
test("NewtypeValidatedSubtype map keys") {
val expectedConfig = NewtypeValidatedSubtypeMapConfig(
Map(NewtypeValidatedSubtype(ValidatedSubtype("long enough key")) -> 1)
)

val source = ConfigProvider.fromMap(
Map("entries.long enough key" -> "1")
)

for config <- read(deriveConfig[NewtypeValidatedSubtypeMapConfig] from source)
yield assertTrue(config == expectedConfig)
},
test("TwiceValidatedSubtype map keys") {
val expectedConfig = TwiceValidatedSubtypeMapConfig(
Map(TwiceValidatedSubtype.makeOrThrow(ValidatedSubtype("this key is long enough")) -> 1)
)

val source = ConfigProvider.fromMap(
Map("entries.this key is long enough" -> "1")
)

for config <- read(deriveConfig[TwiceValidatedSubtypeMapConfig] from source)
yield assertTrue(config == expectedConfig)
},
test("fails with TwiceValidatedSubtype map keys that are too short") {
val source = ConfigProvider.fromMap(
Map("entries.twelve chars" -> "1")
)

for config <- read(deriveConfig[TwiceValidatedSubtypeMapConfig] from source).either
yield assertTrue(
config.is(_.left).getMessage.contains("String must be longer than 15 characters")
)
}
)
)