refined icon indicating copy to clipboard operation
refined copied to clipboard

[pureconfig] Refined keys for Map[String, T]

Open NeQuissimus opened this issue 7 years ago • 3 comments

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]))
            }
        }
      }
    }
  }

NeQuissimus avatar Feb 11 '18 16:02 NeQuissimus

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]]

?

fthomas avatar Feb 12 '18 20:02 fthomas

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 StringConfigCursor, 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 avatar Mar 08 '19 12:03 crypticmind

@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.

fthomas avatar Mar 09 '19 10:03 fthomas