Last active
June 16, 2018 13:53
-
-
Save mrange/1b725ddd2c4598e740ac9e167d962222 to your computer and use it in GitHub Desktop.
Scala Transform
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
import scala.reflect._ | |
sealed trait TransformResult[+T] | |
case class TransformContext(path: List[String]) { | |
def append(p: String) = TransformContext(p::path) | |
override def toString = path.reverse.mkString(".") | |
} | |
object TransformContext { | |
val empty = TransformContext(List()) | |
} | |
case class Success[+T](v: T) extends TransformResult[T] | |
case class Failure(context: TransformContext, message: String) extends TransformResult[Nothing] | |
trait Transform[+T] { | |
def run(context: TransformContext, current: Any): TransformResult[T] | |
def flatMap[U](uf: (T) => Transform[U]) = Transform.flatMap(this, uf) | |
def map[U](m: T => U) = Transform.map(this, m) | |
def and[U](u: Transform[U]) = Transform.and(this, u) | |
def named(name: String) = Transform.named(this, name) | |
def optional = Transform.optional(this) | |
def transform(m: Map[String, Any]): TransformResult[T] = run(TransformContext.empty, m) | |
} | |
object Implicits { | |
implicit class ImplicitFlattenTransform[T](val t: Transform[Transform[T]]) extends AnyVal { | |
def flatten = Transform.flatten(t) | |
} | |
implicit class ImplicitOrTransform[T](val f: Transform[T]) extends AnyVal { | |
def or(s: Transform[T]) = Transform.or(f, s) | |
} | |
implicit class ImplicitApplyTransform[T, U](val f: Transform[T => U]) extends AnyVal { | |
def apply(t: Transform[T]) = Transform.apply(f, t) | |
} | |
} | |
object Transform { | |
def create[T](m: (TransformContext, Any) => TransformResult[T]) = new Transform[T] { | |
def run(context: TransformContext, current: Any): TransformResult[T] = { | |
m(context, current) | |
} | |
} | |
def value[T](v: T) = new Transform[T] { | |
def run(context: TransformContext, current: Any): TransformResult[T] = { | |
Success(v) | |
} | |
} | |
def failWith(message: String) = new Transform[Nothing] { | |
def run(context: TransformContext, current: Any): TransformResult[Nothing] = { | |
Failure(context, message) | |
} | |
} | |
def flatMap[T, U](t: Transform[T], uf: (T) => Transform[U]) = new Transform[U] { | |
def run(context: TransformContext, current: Any): TransformResult[U] = { | |
t.run(context, current) match { | |
case Success(tv) => | |
val u = uf(tv) | |
u.run(context, current) | |
case Failure(tctx, tmsg) => Failure(tctx, tmsg) | |
} | |
} | |
} | |
def flatten[T](t: Transform[Transform[T]]) = t.flatMap(tv => tv) | |
def apply[T, U](f: Transform[T => U], t: Transform[T]) = f.flatMap(fv => t.flatMap(tv => Transform.value(fv(tv)))) | |
def map[T, U](t: Transform[T], m: T => U) = t.flatMap(tv => value(m(tv))) | |
def and[T, U](t: Transform[T], u: Transform[U]) = t.flatMap(tv => u.flatMap(uv => value((tv, uv)))) | |
def or[T](f: Transform[T], s: Transform[T]) = new Transform[T] { | |
def run(context: TransformContext, current: Any): TransformResult[T] = { | |
f.run(context, current) match { | |
case Success(tv) => Success(tv) | |
case Failure(tctx, tmsg) => s.run(context, current) | |
} | |
} | |
} | |
def optional[T](t: Transform[T]) = new Transform[Option[T]] { | |
def run(context: TransformContext, current: Any): TransformResult[Option[T]] = { | |
t.run(context, current) match { | |
case Success(tv) => Success(Some(tv)) | |
case Failure(_, _) => Success(None) | |
} | |
} | |
} | |
def named[T](t: Transform[T], name: String) = new Transform[T] { | |
def run(context: TransformContext, current: Any): TransformResult[T] = { | |
current match { | |
case null => Failure(context, "current is null") | |
case m : Map[String,_] => | |
m.get(name) match { | |
case Some(next) => | |
val nctx = context.append(name) | |
t.run(nctx, next) | |
case None => Failure(context, s"Map does not contain key: $name") | |
} | |
case _ => Failure(context, "current is not a Map") | |
} | |
} | |
} | |
def cast[T : ClassTag] = new Transform[T] { | |
def run(context: TransformContext, current: Any): TransformResult[T] = { | |
current match { | |
case tv : T => Success(tv) | |
case _ => Failure(context, s"Cannot cast current to T") // TODO: Improve | |
} | |
} | |
} | |
val asString = new Transform[String] { | |
def run(context: TransformContext, current: Any): TransformResult[String] = { | |
current match { | |
case null => Success("") | |
case s : String => Success(s) | |
case v => Success(v.toString) | |
} | |
} | |
} | |
val asNumber = new Transform[Number] { | |
def run(context: TransformContext, current: Any): TransformResult[Number] = { | |
current match { | |
case null => Success(0) | |
case n : Number => Success(n) | |
case _ => Failure(context, "Not a number") | |
} | |
} | |
} | |
} | |
case class MyInnerModel(xx: Either[Int, String]) | |
case class MyModel(x: Number, y: String,z: MyInnerModel) | |
import Implicits._ | |
object Program { | |
def main(args: Array[String]): Unit = { | |
def intVal(v: Int): Either[Int, String] = Left(v) | |
def stringVal(v: String): Either[Int, String] = Right(v) | |
def string(key: String) = Transform.asString.named(key) | |
def number(key: String) = Transform.asNumber.named(key) | |
def either(key: String) = Transform.cast[Int].map(intVal(_)) | |
.or(Transform.cast[String].map(stringVal(_))) | |
.or(Transform.failWith("Expected either int or string")) | |
.named(key) | |
def map(kvs: (String, Any)*) = Map(kvs:_*) | |
val tmyInnermodel = either("xx") | |
.map(MyInnerModel(_)) | |
val tmyModel1 = Transform | |
.value(MyModel.curried) | |
.apply(number("x")) | |
.apply(string("y")) | |
.apply(tmyInnermodel.named("z")) | |
val tmyModel3 = Transform | |
.value(MyModel.curried) | |
.apply(number("x")) | |
.apply(string("y")) | |
.apply(Transform.value(MyInnerModel(Left(0)))) | |
val versions = Map(1 -> tmyModel1, 3 -> tmyModel3) | |
def tmyModel = Transform.cast[Int].flatMap { v => | |
versions.get(v) match { | |
case Some(vv) => Transform.value(vv) | |
case None => Transform.failWith(s"Unrecognized version: $v") | |
} | |
}.named("v").flatten | |
def test(map: Map[String, Any]) = { | |
val result = tmyModel.transform(map) | |
println(map) | |
println(result) | |
} | |
test(map( | |
"v" -> 1, | |
"x" -> 1, | |
"y" -> "a", | |
"z" -> map("xx" -> 11))) | |
test(map( | |
"v" -> 1, | |
"x" -> 1, | |
"y" -> "a", | |
"z" -> map("xx" -> "XX"))) | |
test(map( | |
"v" -> 1, | |
"x" -> 1, | |
"y" -> "a", | |
"z" -> map("xx" -> true))) | |
test(map( | |
"v" -> 1, | |
"x" -> 1, | |
"y" -> "a", | |
"z" -> map())) | |
test(map( | |
"v" -> 1, | |
"x" -> 1, | |
"y" -> "a", | |
"z" -> 1)) | |
test(map( | |
"v" -> 1, | |
"x" -> 1, | |
"y" -> "a")) | |
test(map( | |
"v" -> 1, | |
"x" -> "a", | |
"y" -> "a")) | |
test(map( | |
"v" -> 2)) | |
test(map( | |
"v" -> "")) | |
test(map( | |
"v" -> 3, | |
"x" -> 1, | |
"y" -> "a")) | |
test(map()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment