Last active
December 17, 2015 11:59
-
-
Save dustingetz/5606201 to your computer and use it in GitHub Desktop.
monadic logging across queries that can fail with scalaz
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 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 | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment