public
Last active

monadic logging across queries that can fail with scalaz

  • Download Gist
gistfile1.scala
Scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
import scalaz._
import scalaz.std.list.listMonoid
import scalaz.std.option.optionInstance
 
 
object ApiEndpoints extends Controller with Auth with ServiceAuthConfig {
import play.api.Play.current
 
private implicit val skillPayloadFmt = Json.format[UserSkillPicker]
 
case class UserSkillPicker(id: String,
name: String,
enabled: Boolean)
 
 
// a value of generic type A, along with out of band logger that accumulates List[String]
type MyLogger[+A] = WriterT[scalaz.Id.Id, List[String], A]
 
// provide fake monoid to make it compile... i dont think it gets used
implicit object monoid extends Monoid[Throwable] {
def zero: Throwable = new Exception
def append(v1: Throwable, v2: => Throwable): Throwable = ???
}
 
// play http entry point for a JSON resource - responds to GET /api/list-skills-user-picker
// performs two queries, parses them into a single payload, renders http response
def listSkillsUserPicker = authorizedAction(NormalUser) { user => implicit request =>
 
val x: EitherT[MyLogger, Throwable, List[UserSkillPicker]] = DB.withConnection { dbconn =>
 
val a: EitherT[MyLogger, Throwable, List[UserSkillPicker]] =
 
for {
allSkills: Map[String, Skill] <- SkillsMapping.all(dbconn) // query can fail
userSkills: Map[String, Skill] <- SkillsMapping.forUser(dbconn, user) // query can fail
} yield {
 
// simple functional transform to build some arbitrary payload
// only gets called if both calls succeeded
allSkills.map{ case (id, skill) =>
val enabled = userSkills.contains(id)
UserSkillPicker(id, skill.name, enabled)
}.toList
 
}
 
a
}
 
val it = x.run.run // wtf ?
val logs: List[String] = it._1
val results: Throwable \/ List[UserSkillPicker] = it._2
 
val log: String = logs.mkString("\n")
 
results match {
case -\/(e) => InternalServerError("exception: %s\nlogs: %s".format(e.toString, log))
case \/-(v) => Ok("success!\nobject: %s\nlogs: %s".format(Json.toJson(v).toString, log))
}
}
 
 
object SkillsMapping {
 
val mappingWithId =
get[String]("skills.id") ~
get[String]("skills.name") ~
get[String]("skills.description") map {
case id~name~desc => id -> Skill(name, desc)
}
 
 
// the return type includes logs, as well as either a throwable or a result
def all(dbconn: java.sql.Connection): EitherT[MyLogger, Throwable, Map[String, Skill]] = {
 
val query = "SELECT skills.id, skills.name FROM skills"
 
def results: Throwable \/ Map[String, Skill] = Try(SQL(query).as(mappingWithId *)(dbconn).toMap) match {
case Failure(e) => \/.left(e)
case Success(v) => \/.right(v)
}
 
val resultsWithLog: EitherT[MyLogger, Throwable, Map[String, Skill]] =
for {
r <- EitherT[MyLogger, Throwable, Map[String, Skill]](Writer(List("running query: %s".format(query)), results))
_ <- EitherT[MyLogger, Throwable, Int](Writer(List("got results: %s".format(results.toString)), \/.right(0)))
} yield r
 
resultsWithLog
}
 
 
 
def forUser(dbconn: java.sql.Connection, userId: String): EitherT[MyLogger, Throwable, Map[String, Skill]] = {
 
val query =
"""
SELECT skills.id, skills.name, skills.description FROM skills
INNER JOIN skillsets
ON skills.id = skillsets.skill_id AND skillsets.user_id = {userId}
"""
 
def results: Throwable \/ Map[String, Skill] = Try(SQL(query).on('userId -> userId).as(mappingWithId *)(dbconn).toMap) match {
case Failure(e) => \/.left(e)
case Success(v) => \/.right(v)
}
 
val resultsWithLog: EitherT[MyLogger, Throwable, Map[String, Skill]] =
for {
r <- EitherT[MyLogger, Throwable, Map[String, Skill]](Writer(List("running query: %s".format(query)), results))
_ <- EitherT[MyLogger, Throwable, Int](Writer(List("got results: %s".format(results.toString)), \/.right(0)))
} yield r
 
resultsWithLog
}
}
 
 
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.