refined
refined copied to clipboard
[pureconfig] Refined keys for Map[String, T]
PureConfig provides an instance of ConfigReader for maps. (see https://github.com/pureconfig/pureconfig/blob/v0.9.0/core/src/main/scala/pureconfig/DerivedReaders.scala#L198)
Unfortunately, it seems impossible to provide a refinement for the map key without writing a custom ConfigReader
I think refined should provide a ConfigReader[Map[Refined[String, ?], T]] that would allow for map keys to be something like String Refined MaxSize[W.249.T].
I attempted to create such a ConfigReader but failed to provide a generic definition.
I did, however, manage to make a specific one for my use case (Kafka topics), so maybe that can help:
// https://github.com/apache/kafka/blob/1.0.0/clients/src/main/java/org/apache/kafka/common/internals/Topic.java
type TopicSpec = NonEmpty And MaxSize[W.`249`.T] And MatchesRegex[W.`"[a-zA-Z0-9\\\\.\\\\-_]+"`.T] And Not[Equal[W.`"."`.T]] And Not[Equal[W.`".."`.T]]
type Topic = String Refined TopicSpec
implicit def deriveTopicMap[T](implicit reader: Derivation[Lazy[ConfigReader[T]]]): ConfigReader[Map[Topic, T]] = new ConfigReader[Map[Topic, T]] {
override def from(cur: ConfigCursor): Either[ConfigReaderFailures, Map[Topic, T]] = {
cur.asMap.right.flatMap { map =>
map.foldLeft[Either[ConfigReaderFailures, Map[Topic, T]]](Right(Map())) {
case (acc, (key, valueConf)) => {
val topic = refineV[TopicSpec](key)
topic.map { key =>
combineResults(acc, reader.value.value.from(valueConf)) { (map, value) => map + (key -> value) }
}.getOrElse(Right(Map.empty[Topic, T]))
}
}
}
}
}
That sounds good to me. Something similar was recently merged in circe: https://github.com/circe/circe/pull/833
Maybe a generic version of that would have a signature similar to
implicit def deriveRefinedMap[F[_, _], K, P, T](
implicit reader: Derivation[Lazy[ConfigReader[T]]],
rt: RefType[F],
v: Validate[K, P]
): ConfigReader[Map[F[K, P], T]]
?
This looks like a good exercise to familiarize with refined.
The signature @fthomas proposes looks OK, however, I see cur.asMap returns a map of String → ConfigCursor, which makes sense because by definition, keys in HOCON have to be strings. So, it looks like the signature should constrain the map to refined string keys instead.
Something like:
implicit def deriveRefinedMap[F[String, _], P, T](
implicit reader: Derivation[Lazy[ConfigReader[T]]],
rt: RefType[F],
v: Validate[String, P]
): ConfigReader[Map[F[String, P], T]]
Does this make sense?
@crypticmind Yes, that makes sense. But note that F[String, _] in the type parameters list should still be F[_, _] since RefType[F] requires that F is a binary type constructor.