I have a Play application with several modules, each of which has its own exception set. Here are three examples:
Module common:
package services.common
trait CommonErrors {
final case class NotFound(id: String) extends Exception(s"object $id not found")
final case class InvalidId(id: String) extends Exception(s"$id is an invalid id")
...
// `toJson` is just an extension method that converts an exception to JSON
def toResult(e: Exception): Result = e match {
case NotFound => Results.NotFound(e.toJson)
case InvalidId => Results.BadRequest(e.toJson)
case _ => Results.InternalError(e.toJson)
}
}
Module auth:
package services.auth
trait AuthErrors {
final case class UserNotFound(e: NotFound) extends Exception(s"user ${e.id} not found")
final case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")
...
// `toJson` is just an extension method that converts an exception to JSON
def toResult(e: Exception): Result = e match {
case UserNotFound => Results.NotFound(e.toJson)
case UserAlreadyExists => Results.BadRequest(e.toJson)
case _ => Results.InternalError(e.toJson)
}
}
Module other:
trait OtherErrors {
final case class AnotherError(s: String) extends Exception(s"another error: $s")
...
// `toJson` is just an extension method that converts an exception to JSON
def toResult(e: Exception): Result = e match {
case AnotherError => Results.BadRequest(e.toJson)
...
case _ => Results.InternalError(e.toJson)
}
}
As you can see, each trait defines a set of exceptions and provides a method to convert that exception to a JSON response like this:
{
"status": 404,
"code": "not_found",
"message": "user 123456789123456789123456 not found",
"request": "https://myhost.com/users/123456789123456789123456"
}
What I'm trying to achieve is to have each module defining its exceptions, reusing the ones defined in the common module, and mixin the exception traits as needed:
object Users extends Controller {
val errors = new CommonErrors with AuthErrors with OtherErrors {
// here I have to override `toResult` to make the compiler happy
override def toResult(e: Exception) = super.toResult
}
def find(id: String) = Action { request =>
userService.find(id).map { user =>
Ok(success(user.toJson))
}.recover { case e =>
errors.toResult(e) // this returns the appropriate result
}
}
}
If you look at how I've overridden toResult, I always return super.toResult, which corresponds to the implementation contained in trait OtherErrors... and this implementation might miss some patterns that are expected to be found in CommonErrors.toResult.
For sure I'm missing something... so the question is: what's the design pattern to fix the issue with multiple implementations of toResult?
You could use Stackable Trait pattern. I'll replaced your .toJson with .getMessage for simplification reason:
define base trait:
trait ErrorsStack {
def toResult(e: Exception): Result = e match {
case _ => Results.InternalServerError(e.getMessage)
}
}
and stackable traits:
trait CommonErrors extends ErrorsStack {
case class NotFound(id: String) extends Exception(s"object $id not found")
case class InvalidId(id: String) extends Exception(s"$id is an invalid id")
override def toResult(e: Exception): Result = e match {
case e: NotFound => Results.NotFound(e.getMessage)
case e: InvalidId => Results.BadRequest(e.getMessage)
case _ => super.toResult(e)
}
}
trait AuthErrors extends ErrorsStack {
case class UserNotFound(id: String) extends Exception(s"user $id not found")
case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")
override def toResult(e: Exception): Result = e match {
case e: UserNotFound => Results.NotFound(e.getMessage)
case e: UserAlreadyExists => Results.BadRequest(e.getMessage)
case _ => super.toResult(e)
}
}
trait OtherErrors extends ErrorsStack {
case class AnotherError(s: String) extends Exception(s"another error: $s")
override def toResult(e: Exception): Result = e match {
case e: AnotherError => Results.BadRequest(e.getMessage)
case _ => super.toResult(e)
}
}
so if we have some stack
val errors = new CommonErrors with AuthErrors with OtherErrors
and defined some helper
import java.nio.charset.StandardCharsets.UTF_8
import play.api.libs.iteratee.Iteratee
import concurrent.duration._
import scala.concurrent.Await
def getResult(ex: Exception) = {
val res = errors.toResult(ex)
val body = new String(Await.result(res.body.run(Iteratee.consume()), 5 seconds), UTF_8)
(res.header.status, body)
}
following code
import java.security.GeneralSecurityException
getResult(errors.UserNotFound("Riddle"))
getResult(errors.UserAlreadyExists("Weasley"))
getResult(errors.NotFound("Gryffindor sword"))
getResult(errors.AnotherError("Snape's death"))
getResult(new GeneralSecurityException("Marauders's map"))
will produce reasonable output
res0: (Int, String) = (404,user Riddle not found)
res1: (Int, String) = (400,user identified by Weasley already exists)
res2: (Int, String) = (404,object Gryffindor sword not found)
res3: (Int, String) = (400,another error: Snape's death)
res4: (Int, String) = (500,Marauders's map)
also we can refactor this code, pulling case classed from traits, and make function's more composable:
type Resolver = PartialFunction[Exception, Result]
object ErrorsStack {
val resolver: Resolver = {
case e => Results.InternalServerError(e.getMessage)
}
}
trait ErrorsStack {
def toResult: Resolver = ErrorsStack.resolver
}
object CommonErrors {
case class NotFound(id: String) extends Exception(s"object $id not found")
case class InvalidId(id: String) extends Exception(s"$id is an invalid id")
val resolver: Resolver = {
case e: NotFound => Results.NotFound(e.getMessage)
case e: InvalidId => Results.BadRequest(e.getMessage)
}
}
trait CommonErrors extends ErrorsStack {
override def toResult = CommonErrors.resolver orElse super.toResult
}
object AuthErrors {
case class UserNotFound(id: String) extends Exception(s"user $id not found")
case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")
val resolver: Resolver = {
case e: UserNotFound => Results.NotFound(e.getMessage)
case e: UserAlreadyExists => Results.BadRequest(e.getMessage)
}
}
trait AuthErrors extends ErrorsStack {
override def toResult = AuthErrors.resolver orElse super.toResult
}
object OtherErrors {
case class AnotherError(s: String) extends Exception(s"another error: $s")
val resolver: Resolver = {
case e: AnotherError => Results.BadRequest(e.getMessage)
}
}
trait OtherErrors extends ErrorsStack {
override def toResult = OtherErrors.resolver orElse super.toResult
}
Related
How to modify Foobar process method so I can use case match output and call run(data) once and not three times? Is it possible to set some common type for ClassA, ClassB, ClassC and if case match output is specific type then call run(data) else if unknown mode then raise exception?
This is my code:
object Foobar {
def process(someConfig: String, someVar: String, data: Seq[String]) = {
someConfig match {
case "a" => new ClassA(someVar).run(data)
case "b" => new ClassB(someVar).run(data)
case "c" => new ClassC(someVar).run(data)
case _ => throw new IllegalArgumentException("Unknown mode")
class ClassA(someVar: String) {
def run(data: Seq[String]) = {
// do some processing 1 with data & someVar
...
}
class ClassB(someVar: String) {
def run(data: Seq[String]) = {
// do some processing 2 with data & someVar
...
}
class ClassC(someVar: String) {
def run(data: Seq[String]) = {
// do some processing 3 with data & someVar
...
}
What I would do, is define a trait with def run(data: Seq[String]): Unit method, and then extend if from the other classes. Something like:
trait Letter {
def run(data: Seq[String]): Unit
}
class ClassA(someVar: String) extends Letter {
override def run(data: Seq[String]) = {
println(data)
}
}
class ClassB(someVar: String) extends Letter {
override def run(data: Seq[String]) = {
println(data)
}
}
class ClassC(someVar: String) extends Letter {
override def run(data: Seq[String]) = {
println(data)
}
}
object Foobar {
def process(someConfig: String, someVar: String, data: Seq[String]) = {
val letter = someConfig match {
case "a" => new ClassA(someVar)
case "b" => new ClassB(someVar)
case "c" => new ClassC(someVar)
case _ => throw new IllegalArgumentException("Unknown mode")
}
letter.run(data)
}
}
You can use implicit mappers for that
sealed trait Letter { def run(data: Seq[String]): Unit }
case class A(someVar: String) extends Letter { override def run(data: Seq[String]): Unit = println("A") }
case class B(someVar: String) extends Letter { override def run(data: Seq[String]): Unit = println("B") }
case class C(someVar: String) extends Letter { override def run(data: Seq[String]): Unit = println("C") }
// This can be in mappers object
implicit class ClassesMapper(someConfig: String) {
def toClass(someVar: String): Letter = {
someConfig match {
case "a" => A(someVar)
case "b" => B(someVar)
case "c" => C(someVar)
case _ => throw new IllegalArgumentException("<exception>")
}
}
}
object Foobar {
def process(someConfig: String, someVar: String, data: Seq[String]) = {
someConfig.toClass(someVar).run(data)
}
}
Foobar.process("a", "someVar", Nil)
Output: A
I've been trying to use websocket on play framework 2.5 in scala.
Now, I want to exchange multiple message types.
But, I get the following errors when a client sends a json via websocket.
Do I have to choose different way to exchange multiple message types?
missing something or is there any easier way?
[error] a.a.OneForOneStrategy - {"sort":"post", "to":"receiver","message":"Test"} (of class play.api.libs.json.JsObject)
scala.MatchError: {"sort":"post", "to":"receiver","message":"Test"} (of class play.api.libs.json.JsObject)
at controllers.MessageController$MessageActor$$anonfun$receive$1.applyOrElse(MessageController.scala:72)
at akka.actor.Actor$class.aroundReceive(Actor.scala:514)
at controllers.MessageController$MessageActor.aroundReceive(MessageController.scala:63)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:527)
at akka.actor.ActorCell.invoke(ActorCell.scala:496)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)
at akka.dispatch.Mailbox.run(Mailbox.scala:224)
at akka.dispatch.Mailbox.exec(Mailbox.scala:234)
at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
models.scala
abstract class WSMessageIn()
// Classes for input
case class PostMessage(
sort: String = "post",
to: String,
...
message: Option[String] = None
) extends WSMessageIn
object PostMessage {
implicit val postMessageFormat = Json.format[PostMessage]
}
case class MessageFromIn(
sort: String = "receive",
from: String,
...
message: Option[String] = None
) extends WSMessageIn
object MessageFromIn {
implicit val messageFromInFormat = Json.format[MessageFromIn]
}
object WSMessageIn {
implicit def json2object(value: JsValue): WSMessageIn = {
(value \ "sort").as[String] match {
case "post" => value.as[PostMessage]
case "receive" => value.as[MessageFromIn]
}
}
implicit def object2json(in: WSMessageIn): JsValue = {
in match {
case in: PostMessage => Json.toJson(in)
case in: MessageFromIn => Json.toJson(in)
}
}
}
MessageController.scala
class MessageController #Inject() (
silhouette: Silhouette[DefaultEnv],
..
implicit val system: ActorSystem,
implicit val materializer: Materializer)
extends Controller {
class MessageActor(out: ActorRef) extends Actor {
override def receive = {
case message: JsValue =>
message match { // !!The error occurs here
case message: PostMessage =>
...
case message: MessageFromIn =>
...
}
}
}
object MessageActor {
def props(out: ActorRef) = Props(new MessageActor(out))
}
implicit val messageFlowTransformer = MessageFlowTransformer.jsonMessageFlowTransformer[JsValue, JsValue]
def socket() = WebSocket.acceptOrResult[JsValue, JsValue] { request =>
implicit val req = Request(request, AnyContentAsEmpty)
silhouette.SecuredRequestHandler { securedRequest =>
Future.successful(HandlerResult(Ok, Some(securedRequest.identity)))
}.map {
case HandlerResult(r, Some(user)) => Right(ActorFlow.actorRef(out => MessageActor.props(out)))
case HandlerResult(r, None) => Left(r)
}
}
}
As is: WebSocket.acceptOrResult[JsValue, JsValue] you expect your websocket to receive JsValue.
This seems to match you code below in MessageActor:
override def receive = {
case message: JsValue => // ...
}
However, you shouldn't expect the conversion from JsValue to WSMessageIn to happen without your intervention.
What you could do is simply:
override def receive = {
case message: JsValue =>
WSMessageIn.json2object(message) match {
case message: PostMessage =>
...
case message: MessageFromIn =>
...
}
}
You were probably expecting an implicitly conversion, you can get it by giving a hint to the compiler (i.e., saying what you expect):
override def receive = {
case message: JsValue =>
(message: WSMessageIn) match {
case message: PostMessage =>
...
case message: MessageFromIn =>
...
}
}
Note: you could turn abstract class WSMessageIn() into: sealed trait WSMessageIn
What I want to do is basically 1:1 this: Scala Play Websocket - use one out actor to send both: Array[Byte] and String messages
Sadly the API has changed a lot since 2.4 (I am on 2.6.0-M4 right now).
What I tried (does not compile for obvious reasons):
WebSocket.accept[WSMessage, WSMessage]
{
request =>
ActorFlow.actorRef
{
out => WebSocketActor.props(out)
}
}
sealed trait WSMessage
case class StringMessage(s: String) extends WSMessage
case class BinaryMessage(a: Array[Byte]) extends WSMessage
case class JsonMessage(js: JsValue) extends WSMessage
object MyMessageFlowTransformer
{
implicit val WSMessageFlowTransformer: MessageFlowTransformer[WSMessage, WSMessage] =
{
new MessageFlowTransformer[WSMessage, WSMessage]
{
def transform(flow: Flow[WSMessage, WSMessage, _]) =
{
AkkaStreams.bypassWith[Message, WSMessage, Message](Flow[Message] collect
{
case StringMessage(s) => Left(s)
case BinaryMessage(b) => Left(b)
case JsonMessage(j) => Left(j)
case _ => Right(CloseMessage(Some(CloseCodes.Unacceptable)))
})(flow map WSMessage.apply)
}
}
}
}
I am somewhat lost. play.api.http.websocket.Message is a sealed trait, and probably for a good reason...
Define a MessageFlowTransformer[Either[String, Array[Byte]], Either[String, Array[Byte]]]:
type WSMessage = Either[String, Array[Byte]]
implicit val mixedMessageFlowTransformer: MessageFlowTransformer[WSMessage, WSMessage] = {
new MessageFlowTransformer[WSMessage, WSMessage] {
def transform(flow: Flow[WSMessage, WSMessage, _]) = {
AkkaStreams.bypassWith[Message, WSMessage, Message](Flow[Message].collect {
case BinaryMessage(data) => Left(Right(data.toArray))
case TextMessage(text) => Left(Left(text))
})(flow map {
case Right(data) => BinaryMessage.apply(ByteString.apply(data))
case Left(text) => TextMessage.apply(text)
})
}
}
}
We have our status defined as:
sealed trait Status
case object Status {
case object StatusA extends Status
case object StatusB extends Status
case object StatusC extends Status
}
Our status looks like:
val status = Status.StatusA
Is there any way to (de)serialize that kind of structure with predefined json4s fomratters?
We tried with defult formatter like:
implicit val formats = new org.json4s.DefaultFormats
and it did not work. Next we tried to use ext provided by json4s with Enum support:
implicit val formats = org.json4s.DefaultFormats + new org.json4s.ext.EnumSerializer(Status)
and it did not work again. We had to totally change the structure of the sealed trait to actual Enumerations. Is there any way to do it with case classes?
Here is a full working example, I changed your classes a bit to make the example simpler and this way you can use the "name" in different ways you can actually omit the "def name", but in this case you will need to change the serialiser a bit. The second Serializer is just below.
sealed trait Status {
def name: String
}
case object Status {
def apply(name: String): Status = name match {
case StatusA.name => StatusA
case StatusB.name => StatusB
case StatusC.name => StatusC
case _ => throw new UnsupportedOperationException("Unknown value")
}
}
case object StatusA extends Status {
override val name = "StatusA"
}
case object StatusB extends Status {
override val name = "StatusB"
}
case object StatusC extends Status {
override val name = "StatusC"
}
class StatusSerializer extends CustomSerializer[Status](formats =>
( {
case JString(s) => Status(s)
case JNull => throw new UnsupportedOperationException("No status specified")
}, {
case status: Status => JString(status.name)
})
)
case class SimpleRichObject(someString: String, someInt: Int, statuses: List[Status])
object Test extends App {
implicit val formats = DefaultFormats + new StatusSerializer
val obj = SimpleRichObject("Answer to life the universe and everything", 42, List(StatusA, StatusB, StatusC))
def toCompactJsonString(any: Any) = {
JsonMethods.compact(JsonMethods.render(Extraction.decompose(any)))
}
def toPrettyJsonString(any: Any) = {
JsonMethods.pretty(JsonMethods.render(Extraction.decompose(any)))
}
/** To Json */
println(s"Compact json:\n${toCompactJsonString(obj)}")
println(s"Pretty json:\n${toPrettyJsonString(obj)}")
/** From Json */
val json =
"""{
| "someString":"Here is a another String",
| "someInt":1234,
| "statuses":["StatusA","StatusB"]
|}""".stripMargin
val richObj = JsonMethods.parse(json).extract[SimpleRichObject]
println(s"Rich object toString: $richObj")
}
Here is the second Serializer, by using the second one you don't need to define extra code in your "Enums"
class SecondStatusSerializer extends CustomSerializer[Status](formats =>
( {
case JString(s) => s match {
case "StatusA" => StatusA
case "StatusB" => StatusB
case "StatusC" => StatusC
}
case JNull => throw new UnsupportedOperationException("No status specified")
}, {
case status: Status => status match {
case StatusA => JString("StatusA")
case StatusB => JString("StatusB")
case StatusC => JString("StatusC")
}
})
)
And here how this one looks when run, compact json:
{"someString":"Answer to life the universe and everything","someInt":42,"statuses":["StatusA","StatusB","StatusC"]}
Pretty json:
{
"someString":"Answer to life the universe and everything",
"someInt":42,
"statuses":["StatusA","StatusB","StatusC"]
}
Rich object toString: SimpleRichObject(Here is a another String,1234,List(StatusA, StatusB))
I have a case class with annotated fields, like this:
case class Foo(#alias("foo") bar: Int)
I have a macro that processes the declaration of this class:
val (className, access, fields, bases, body) = classDecl match {
case q"case class $n $m(..$ps) extends ..$bs { ..$ss }" => (n, m, ps, bs, ss)
case _ => abort
}
Later, I search for the aliased fields, as follows:
val aliases = fields.asInstanceOf[List[ValDef]].flatMap {
field => field.symbol.annotations.collect {
//deprecated version:
//case annotation if annotation.tpe <:< cv.weakTypeOf[alias] =>
case annotation if annotation.tree.tpe <:< c.weakTypeOf[alias] =>
//deprecated version:
//annotation.scalaArgs.head match {
annotation.tree.children.tail.head match {
case Literal(Constant(param: String)) => (param, field.name)
}
}
}
However, the list of aliases ends up being empty. I have determined that field.symbol.annotations.size is, in fact, 0, despite the annotation clearly sitting on the field.
Any idea of what's wrong?
EDIT
Answering the first two comments:
(1) I tried mods.annotations, but that didn't work. That actually returns List[Tree] instead of List[Annotation], returned by symbol.annotations. Perhaps I didn't modify the code correctly, but the immediate effect was an exception during macro expansion. I'll try to play with it some more.
(2) The class declaration is grabbed while processing an annotation macro slapped on the case class.
The complete code follows. The usage is illustrated in the test code further below.
package com.xxx.util.macros
import scala.collection.immutable.HashMap
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.reflect.macros.whitebox
trait Mapped {
def $(key: String) = _vals.get(key)
protected def +=(key: String, value: Any) =
_vals += ((key, value))
private var _vals = new HashMap[String, Any]
}
class alias(val key: String) extends StaticAnnotation
class aliased extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro aliasedMacro.impl
}
object aliasedMacro {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val (classDecl, compDecl) = annottees.map(_.tree) match {
case (clazz: ClassDef) :: Nil => (clazz, None)
case (clazz: ClassDef) :: (comp: ModuleDef) :: Nil => (clazz, Some(comp))
case _ => abort(c, "#aliased must annotate a class")
}
val (className, access, fields, bases, body) = classDecl match {
case q"case class $n $m(..$ps) extends ..$bs { ..$ss }" => (n, m, ps, bs, ss)
case _ => abort(c, "#aliased is only supported on case class")
}
val mappings = fields.asInstanceOf[List[ValDef]].flatMap {
field => field.symbol.annotations.collect {
case annotation if annotation.tree.tpe <:< c.weakTypeOf[alias] =>
annotation.tree.children.tail.head match {
case Literal(Constant(param: String)) =>
q"""this += ($param, ${field.name})"""
}
}
}
val classCode = q"""
case class $className $access(..$fields) extends ..$bases {
..$body; ..$mappings
}"""
c.Expr(compDecl match {
case Some(compCode) => q"""$compCode; $classCode"""
case None => q"""$classCode"""
})
}
protected def abort(c: whitebox.Context, message: String) =
c.abort(c.enclosingPosition, message)
}
The test code:
package test.xxx.util.macros
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import org.junit.runner.RunWith
import com.xxx.util.macros._
#aliased
case class Foo(#alias("foo") foo: Int,
#alias("BAR") bar: String,
baz: String) extends Mapped
#RunWith(classOf[JUnitRunner])
class MappedTest extends FunSuite {
val foo = 13
val bar = "test"
val obj = Foo(foo, bar, "extra")
test("field aliased with its own name") {
assertResult(Some(foo))(obj $ "foo")
}
test("field aliased with another string") {
assertResult(Some(bar))(obj $ "BAR")
assertResult(None)(obj $ "bar")
}
test("unaliased field") {
assertResult(None)(obj $ "baz")
}
}
Thanks for the suggestions! In the end, using field.mods.annotations did help. This is how:
val mappings = fields.asInstanceOf[List[ValDef]].flatMap {
field => field.mods.annotations.collect {
case Apply(Select(New(Ident(TypeName("alias"))), termNames.CONSTRUCTOR),
List(Literal(Constant(param: String)))) =>
q"""this += ($param, ${field.name})"""
}
}