-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ConfDecoderEx: decode with initial value
- Loading branch information
Showing
12 changed files
with
569 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
metaconfig-core/shared/src/main/scala/metaconfig/ConfCodecExT.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package metaconfig | ||
|
||
import metaconfig.generic.Settings | ||
|
||
class ConfCodecExT[S, A]( | ||
encoder: ConfEncoder[A], | ||
decoder: ConfDecoderExT[S, A] | ||
) extends ConfDecoderExT[S, A] | ||
with ConfEncoder[A] { | ||
override def write(value: A): Conf = encoder.write(value) | ||
override def read(state: Option[S], conf: Conf): Configured[A] = | ||
decoder.read(state, conf) | ||
|
||
def bimap[B](in: B => A, out: A => B): ConfCodecExT[S, B] = | ||
new ConfCodecExT[S, B](encoder.contramap(in), decoder.map(out)) | ||
|
||
def noTypos(implicit settings: Settings[A]): ConfCodecExT[S, A] = { | ||
val noTyposDecoder = decoder.noTypos | ||
if (noTyposDecoder eq decoder) this | ||
else new ConfCodecExT(encoder, noTyposDecoder) | ||
} | ||
|
||
} | ||
|
||
object ConfCodecExT { | ||
def apply[A, B](implicit ev: ConfCodecExT[A, B]): ConfCodecExT[A, B] = ev | ||
} | ||
|
||
object ConfCodecEx { | ||
def apply[A](implicit obj: ConfCodecEx[A]): ConfCodecEx[A] = obj | ||
} |
198 changes: 198 additions & 0 deletions
198
metaconfig-core/shared/src/main/scala/metaconfig/ConfDecoderExT.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
package metaconfig | ||
|
||
import scala.collection.compat._ | ||
import scala.reflect.ClassTag | ||
|
||
import java.nio.file.Path | ||
import java.nio.file.Paths | ||
|
||
import metaconfig.internal.NoTyposDecoderEx | ||
|
||
trait ConfDecoderExT[-S, A] { | ||
|
||
def read(state: Option[S], conf: Conf): Configured[A] | ||
|
||
} | ||
|
||
object ConfDecoderExT { | ||
|
||
def apply[S, A](implicit ev: ConfDecoderExT[S, A]): ConfDecoderExT[S, A] = ev | ||
|
||
def from[S, A](f: (Option[S], Conf) => Configured[A]): ConfDecoderExT[S, A] = | ||
(state, conf) => f(state, conf) | ||
|
||
def fromPartial[S, A](expect: String)( | ||
f: PartialFunction[(Option[S], Conf), Configured[A]] | ||
): ConfDecoderExT[S, A] = | ||
(state, conf) => | ||
f.applyOrElse( | ||
(state, conf), | ||
(_: (Option[S], Conf)) => | ||
Configured.NotOk(ConfError.typeMismatch(expect, conf)) | ||
) | ||
|
||
def constant[S, A](value: A): ConfDecoderExT[S, A] = | ||
(_, _) => Configured.ok(value) | ||
|
||
implicit def confDecoder[S]: ConfDecoderExT[S, Conf] = | ||
(_, conf) => Configured.Ok(conf) | ||
|
||
implicit def subConfDecoder[S, A <: Conf: ClassTag]: ConfDecoderExT[S, A] = | ||
fromPartial("Config") { | ||
case (_, x: A) => Configured.Ok(x) | ||
} | ||
|
||
implicit def bigDecimalConfDecoder[S]: ConfDecoderExT[S, BigDecimal] = | ||
fromPartial[S, BigDecimal]("Number") { | ||
case (_, Conf.Num(x)) => Configured.Ok(x) | ||
case (_, Conf.Str(Extractors.Number(n))) => Configured.Ok(n) | ||
} | ||
|
||
implicit def intConfDecoder[S]: ConfDecoderExT[S, Int] = | ||
bigDecimalConfDecoder[S].map(_.toInt) | ||
|
||
implicit def stringConfDecoder[S]: ConfDecoderExT[S, String] = | ||
fromPartial[S, String]("String") { | ||
case (_, Conf.Str(x)) => Configured.Ok(x) | ||
} | ||
|
||
implicit def unitConfDecoder[S]: ConfDecoderExT[S, Unit] = | ||
from[S, Unit] { case _ => Configured.unit } | ||
|
||
implicit def booleanConfDecoder[S]: ConfDecoderExT[S, Boolean] = | ||
fromPartial[S, Boolean]("Bool") { | ||
case (_, Conf.Bool(x)) => Configured.Ok(x) | ||
case (_, Conf.Str("true" | "on" | "yes")) => Configured.Ok(true) | ||
case (_, Conf.Str("false" | "off" | "no")) => Configured.Ok(false) | ||
} | ||
|
||
implicit def pathConfDecoder[S]: ConfDecoderExT[S, Path] = | ||
stringConfDecoder[S].flatMap { path => | ||
Configured.fromExceptionThrowing(Paths.get(path)) | ||
} | ||
|
||
implicit def canBuildOptionT[S, A]( | ||
implicit ev: ConfDecoderExT[S, A] | ||
): ConfDecoderExT[S, Option[A]] = | ||
(state, conf) => | ||
conf match { | ||
case Conf.Null() => Configured.ok(None) | ||
case _ => ev.read(state, conf).map(Some.apply) | ||
} | ||
|
||
implicit def canBuildOption[A]( | ||
implicit ev: ConfDecoderEx[A] | ||
): ConfDecoderEx[Option[A]] = | ||
(state, conf) => | ||
conf match { | ||
case Conf.Null() => Configured.ok(None) | ||
case _ => ev.read(state.flatten, conf).map(Some.apply) | ||
} | ||
|
||
implicit def canBuildStringMapT[S, A, CC[_, _]]( | ||
implicit ev: ConfDecoderExT[S, A], | ||
factory: Factory[(String, A), CC[String, A]], | ||
classTag: ClassTag[A] | ||
): ConfDecoderExT[S, CC[String, A]] = | ||
fromPartial( | ||
s"Map[String, ${classTag.runtimeClass.getName}]" | ||
) { | ||
case (state, Conf.Obj(values)) => | ||
buildFrom(state, values, ev, factory)(_._2, (x, y) => (x._1, y)) | ||
} | ||
|
||
implicit def canBuildStringMap[A, CC[x, y] <: collection.Iterable[(x, y)]]( | ||
implicit ev: ConfDecoderEx[A], | ||
factory: Factory[(String, A), CC[String, A]], | ||
classTag: ClassTag[A] | ||
): ConfDecoderEx[CC[String, A]] = { | ||
val none: Option[A] = None | ||
fromPartial( | ||
s"Map[String, ${classTag.runtimeClass.getName}]" | ||
) { | ||
case (_, Conf.Obj(values)) => | ||
buildFrom(none, values, ev, factory)(_._2, (x, y) => (x._1, y)) | ||
} | ||
} | ||
|
||
implicit def canBuildSeqT[S, A, C[_]]( | ||
implicit ev: ConfDecoderExT[S, A], | ||
factory: Factory[A, C[A]], | ||
classTag: ClassTag[A] | ||
): ConfDecoderExT[S, C[A]] = | ||
fromPartial( | ||
s"List[${classTag.runtimeClass.getName}]" | ||
) { | ||
case (state, Conf.Lst(values)) => | ||
buildFrom(state, values, ev, factory)(identity, (_, x) => x) | ||
} | ||
|
||
implicit def canBuildSeq[A, C[x] <: collection.Iterable[x]]( | ||
implicit ev: ConfDecoderEx[A], | ||
factory: Factory[A, C[A]], | ||
classTag: ClassTag[A] | ||
): ConfDecoderEx[C[A]] = { | ||
val none: Option[A] = None | ||
fromPartial( | ||
s"List[${classTag.runtimeClass.getName}]" | ||
) { | ||
case (_, Conf.Lst(values)) => | ||
buildFrom(none, values, ev, factory)(identity, (_, x) => x) | ||
} | ||
} | ||
|
||
implicit final class Implicits[S, A](self: ConfDecoderExT[S, A]) { | ||
|
||
def read(state: Option[S], conf: Configured[Conf]): Configured[A] = | ||
conf.andThen(self.read(state, _)) | ||
|
||
def map[B](f: A => B): ConfDecoderExT[S, B] = | ||
(state, conf) => self.read(state, conf).map(f) | ||
|
||
def flatMap[B](f: A => Configured[B]): ConfDecoderExT[S, B] = | ||
(state, conf) => self.read(state, conf).andThen(f) | ||
|
||
def orElse(other: ConfDecoderExT[S, A]): ConfDecoderExT[S, A] = | ||
(state, conf) => | ||
self.read(state, conf).recoverWith { x => | ||
other.read(state, conf).recoverWith(x.combine) | ||
} | ||
|
||
def noTypos(implicit settings: generic.Settings[A]): ConfDecoderExT[S, A] = | ||
if (self.isInstanceOf[NoTyposDecoderEx[_, _]]) self | ||
else new NoTyposDecoderEx[S, A](self) | ||
|
||
} | ||
|
||
private[metaconfig] def buildFrom[V, S, A, B, Coll]( | ||
state: Option[S], | ||
values: List[V], | ||
ev: ConfDecoderExT[S, A], | ||
factory: Factory[B, Coll] | ||
)(a2conf: V => Conf, ab2c: (V, A) => B): Configured[Coll] = { | ||
val successB = factory.newBuilder | ||
val errorB = List.newBuilder[ConfError] | ||
successB.sizeHint(values.length) | ||
values.foreach { value => | ||
ev.read(state, a2conf(value)) match { | ||
case Configured.NotOk(e) => errorB += e | ||
case Configured.Ok(decoded) => successB += ab2c(value, decoded) | ||
} | ||
} | ||
Configured(successB.result(), errorB.result(): _*) | ||
} | ||
|
||
} | ||
|
||
object ConfDecoderEx { | ||
|
||
def apply[A](implicit ev: ConfDecoderEx[A]): ConfDecoderEx[A] = ev | ||
|
||
def from[A](f: (Option[A], Conf) => Configured[A]): ConfDecoderEx[A] = | ||
ConfDecoderExT.from[A, A](f) | ||
|
||
def fromPartial[A](expect: String)( | ||
f: PartialFunction[(Option[A], Conf), Configured[A]] | ||
): ConfDecoderEx[A] = ConfDecoderExT.fromPartial[A, A](expect)(f) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.