Scala json4s sealed trait as enums - scala

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))

Related

Scala compilation fails with: Could not find implicit value for parameter W

I got a doobie query that doesn't want to compile:
package de.x.vmdbplanningvalues.impl.queries
import de.x.campaignplans.CampaignPlanId
import de.x.distributionbrands.DistributionBrandId
import de.x.vmdbplanningvalues._
import doobie._
object UpdateVmdbPlanningValuesQuery {
case class VmdbPlanningUpdate(
creditRatingRejections: Option[Double],
goodsCoupons: Option[Double],
customerDiscounts: Option[Double],
campaignPlanId: String,
distributionBrandId: String,
country: String,
categoryId: String,
lane: VmdbLane
)
def apply(
distributionBrand: DistributionBrandId,
country: String,
campaignPlanId: CampaignPlanId,
category: String,
updates: List[VmdbPlanningValuesForVmdbLane]
): ConnectionIO[Unit] = {
for {
_ <- updateQuery(updates.map(update =>
VmdbPlanningUpdate(
update.creditRatingRejections,
update.goodsCoupons,
update.customerDiscounts,
campaignPlanId.id,
distributionBrand.id,
country,
category,
update.lane
)
))
} yield ()
}
def updateQuery[T: Write](updates: List[VmdbPlanningUpdate]): ConnectionIO[Int] = {
val sql =
"""
UPDATE vmdb_planning_values as vmdb
SET vmdb.credit_rating_rejections = ?,
vmdb.goods_coupons = ?,
vmdb.customer_discounts = ?
FROM campaign_plan cp
WHERE cp.id = ?
AND vmdb.distribution_brand_id = ?
AND vmdb.country_id = ?
AND vmdb.year=DATE_PART('year', cp.start_date)
AND vmdb.quarter=DATE_PART('quarter', cp.start_date)
AND vmdb.category_id = ?
AND vmdb.lane = ?
"""
Update[VmdbPlanningUpdate](sql).updateMany(updates)
}
}
However it fails with the following error:
[error] /Users/johannesklauss/Documents/campaign-service/server/src/main/scala/de/x/vmdbplanningvalues/impl/queries/UpdateVmdbPlanningValuesQuery.scala:60:35: could not find implicit value for parameter W: doobie.Write[de.x.vmdbplanningvalues.impl.queries.UpdateVmdbPlanningValuesQuery.VmdbPlanningUpdate]
[error] Update[VmdbPlanningUpdate](sql).updateMany(updates)
[error]
I am not quite sure what the error message means, since I am still a bit fuzzy about implicits in Scala. Does anybody have an idea?
Edit: Added VmdbLane.scala:
package de.x.vmdbplanningvalues
import io.circe.Decoder.Result
import io.circe._
sealed trait VmdbLane {
override def toString: String = VmdbLane.toEnum(this)
}
object VmdbLane {
case object New extends VmdbLane
case object CarryOver extends VmdbLane
case object Sale extends VmdbLane
case object Sum extends VmdbLane
def toEnum(e: VmdbLane): String =
e match {
case New => "new"
case CarryOver => "carryOver"
case Sale => "sale"
case Sum => "sum"
}
def fromEnum(s: String): Option[VmdbLane] =
Option(s) collect {
case "new" => New
case "carryOver" => CarryOver
case "sale" => Sale
case "sum" => Sum
}
implicit val jsonFormat: Encoder[VmdbLane] with Decoder[VmdbLane] =
new Encoder[VmdbLane] with Decoder[VmdbLane] {
override def apply(a: VmdbLane): Json = Encoder.encodeString(toEnum(a))
override def apply(c: HCursor): Result[VmdbLane] =
c.value.asString.flatMap(s => fromEnum(s)) match {
case Some(a) => Right(a)
case None => Left(DecodingFailure("VmdbLane", c.history))
}
}
}
The problem was not related to Circe it is related to how the custom doobie type mapping works. If you include something like inside the VmdbLane object:
implicit val natGet: Get[VmdbLane] = Get[String].map(in => {
in match {
case "New" => New
case "CarryOver" => CarryOver
case "Sale" => Sale
case "Sum" => Sum
}
})
implicit val natPut: Put[VmdbLane] = Put[String].contramap {
case New => "New"
case CarryOver => "CarryOver"
case Sale => "Sale"
case Sum => "Sum"
}
The compiler should include the mapping an it should work.

getting serealizing case class with play json library

My case classes looks like this:
case class Person(personalInfo: PersonalInfo, bankInfo: BankInfo)
case class PersonalInfo(fname: String, lname: String)
case class BankInfo(atmCode: Int, creditCard: CreditCard)
case class CreditCard(number: Int, experationDate: String)
so to be able to get a person in my controller I added serealizer for person:
object PersonSerealizer {
implicit val PersonalInfoFormat: OFormat[PersonalInfo] = Json.format[PersonalInfo]
implicit val CreditCardFormat: OFormat[CreditCard] = Json.format[CreditCard]
implicit val BankInfoFormat: OFormat[BankInfo] = Json.format[BankInfo]
implicit val PersonFormat: OFormat[Person] = Json.format[Person]
}
in my controller I have a super simple action that looks like this:
i
mport serializers.PersonSerealizer._
def getBrothers(): Action[JsValue] = Action.async(parse.json) { request =>
request.body.validate[Person] match {
case JsSuccess(person, _) =>
brothersService.getBrothers(person) // this returns a List[Person]
.map(res => Future{Ok(res)})
case JsError(errors) => Future(BadRequest("Errors! " + errors.mkString))
}
}
but I get this error:
Error:(23, 81) No unapply or unapplySeq function found for class
BankInfo: / implicit val BankInfoFormat:
OFormat[BankInfo] = Json.format[BankInfo]
something is weird...from what I know it supposed to work
The order of defining your implicits seems to matter. Also, I think it is safer to use companion objects instead of defining them inside an arbitrary object.
case class PersonalInfo(fname: String, lname: String)
object PersonalInfo {
implicit val personalInfoJsonFormat = Json.format[PersonalInfo]
}
case class CreditCard(number: Int, experationDate: String)
object CreditCard {
implicit val creditCardJsonFormat = Json.format[CreditCard]
}
case class BankInfo(atmCode: Int, creditCard: CreditCard)
object BankInfo {
implicit val bankInfoJsonFormat = Json.format[BankInfo]
}
case class Person(personalInfo: PersonalInfo, bankInfo: BankInfo)
object Person {
implicit val personJsonFmt = Json.format[Person]
}
// Your action will be like this
def getBrothers() = Action.async(parse.json) { req =>
req.body.validate[Person] match {
case JsSuccess(p, _) =>
Future.successful(Ok(Json.toJson(service.getBrothers(p))))
case JsError(errors) =>
Future.successful(BadRequest(JsError.toJson(errors)))
}
}

Scala Play WebSocket - One Actor, multiple message types

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)
})
}
}
}

Scalameta: Identify particular annotations

I want to auto-generate REST API models in Scala using scalameta annotation macros. Specifically, given:
#Resource case class User(
#get id : Int,
#get #post #patch name : String,
#get #post email : String,
registeredOn : Long
)
I want to generate:
object User {
case class Get(id: Int, name: String, email: String)
case class Post(name: String, email: String)
case class Patch(name: Option[String])
}
trait UserRepo {
def getAll: Seq[User.Get]
def get(id: Int): User.Get
def create(request: User.Post): User.Get
def replace(id: Int, request: User.Put): User.Get
def update(id: Int, request: User.Patch): User.Get
def delete(id: Int): User.Get
}
I have something working here: https://github.com/pathikrit/metarest
Specifically I am doing this:
import scala.collection.immutable.Seq
import scala.collection.mutable
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.meta._
class get extends StaticAnnotation
class put extends StaticAnnotation
class post extends StaticAnnotation
class patch extends StaticAnnotation
#compileTimeOnly("#metarest.Resource not expanded")
class Resource extends StaticAnnotation {
inline def apply(defn: Any): Any = meta {
val (cls: Defn.Class, companion: Defn.Object) = defn match {
case Term.Block(Seq(cls: Defn.Class, companion: Defn.Object)) => (cls, companion)
case cls: Defn.Class => (cls, q"object ${Term.Name(cls.name.value)} {}")
case _ => abort("#metarest.Resource must annotate a class")
}
val paramsWithAnnotation = for {
Term.Param(mods, name, decltype, default) <- cls.ctor.paramss.flatten
seenMods = mutable.Set.empty[String]
modifier <- mods if seenMods.add(modifier.toString)
(tpe, defArg) <- modifier match {
case mod"#get" | mod"#put" | mod"#post" => Some(decltype -> default)
case mod"#patch" =>
val optDeclType = decltype.collect({case tpe: Type => targ"Option[$tpe]"})
val defaultArg = default match {
case Some(term) => q"Some($term)"
case None => q"None"
}
Some(optDeclType -> Some(defaultArg))
case _ => None
}
} yield modifier -> Term.Param(Nil, name, tpe, defArg)
val models = paramsWithAnnotation
.groupBy(_._1.toString)
.map({case (verb, pairs) =>
val className = Type.Name(verb.stripPrefix("#").capitalize)
val classParams = pairs.map(_._2)
q"case class $className[..${cls.tparams}] (..$classParams)"
})
val newCompanion = companion.copy(
templ = companion.templ.copy(stats = Some(
companion.templ.stats.getOrElse(Nil) ++ models
))
)
Term.Block(Seq(cls, newCompanion))
}
}
I am unhappy with the following snip of code:
modifier match {
case mod"#get" | mod"#put" | mod"#post" => ...
case mod"#patch" => ...
case _ => None
}
The above code does "stringly" pattern matching on the annotations I have. Is there anyway to re-use the exact annotations I have to pattern match for these:
class get extends StaticAnnotation
class put extends StaticAnnotation
class post extends StaticAnnotation
class patch extends StaticAnnotation
It's possible to replace the mod#get stringly typed annotation with a get() extractor using a bit of runtime reflection (at compile time).
In addition, let's say we also want to allow users to fully qualify the annotation with #metarest.get or #_root_.metarest.get
All the following code examples assume import scala.meta._. The tree structure of #get, #metarest.get and #_root_.metarest.get are
# mod"#get".structure
res4: String = """ Mod.Annot(Ctor.Ref.Name("get"))
"""
# mod"#metarest.get".structure
res5: String = """
Mod.Annot(Ctor.Ref.Select(Term.Name("metarest"), Ctor.Ref.Name("get")))
"""
# mod"#_root_.metarest.get".structure
res6: String = """
Mod.Annot(Ctor.Ref.Select(Term.Select(Term.Name("_root_"), Term.Name("metarest")), Ctor.Ref.Name("get")))
"""
The selectors are either Ctor.Ref.Select or Term.Select and the names are either Term.Name or Ctor.Ref.Name.
Let's first create a custom selector extractor
object Select {
def unapply(tree: Tree): Option[(Term, Name)] = tree match {
case Term.Select(a, b) => Some(a -> b)
case Ctor.Ref.Select(a, b) => Some(a -> b)
case _ => None
}
}
Then create a few helper utilities
object ParamAnnotation {
/* isSuffix(c, a.b.c) // true
* isSuffix(b.c, a.b.c) // true
* isSuffix(a.b.c, a.b.c) // true
* isSuffix(_root_.a.b.c, a.b.c) // true
* isSuffix(d.c, a.b.c) // false
*/
def isSuffix(maybeSuffix: Term, fullName: Term): Boolean =
(maybeSuffix, fullName) match {
case (a: Name, b: Name) => a.value == b.value
case (Select(q"_root_", a), b: Name) => a.value == b.value
case (a: Name, Select(_, b)) => a.value == b.value
case (Select(aRest, a), Select(bRest, b)) =>
a.value == b.value && isSuffix(aRest, bRest)
case _ => false
}
// Returns true if `mod` matches the tree structure of `#T`
def modMatchesType[T: ClassTag](mod: Mod): Boolean = mod match {
case Mod.Annot(term: Term.Ref) =>
isSuffix(term, termRefForType[T])
case _ => false
}
// Parses `T.getClass.getName` into a Term.Ref
// Uses runtime reflection, but this happens only at compile time.
def termRefForType[T](implicit ev: ClassTag[T]): Term.Ref =
ev.runtimeClass.getName.parse[Term].get.asInstanceOf[Term.Ref]
}
With this setup, we can add a companion object to the get definition with an
unapply boolean extractor
class get extends StaticAnnotation
object get {
def unapply(mod: Mod): Boolean = ParamAnnotation.modMatchesType[get](mod)
}
Doing the same for post and put, we can now write
// before
case mod"#get" | mod"#put" | mod"#post" => Some(decltype -> default)
// after
case get() | put() | post() => Some(decltype -> default)
Note that this approach will still not work if the user renames for example get on import
import metarest.{get => GET}
I would recommend aborting if an annotation does not match what you expected
// before
case _ => None
// after
case unexpected => abort("Unexpected modifier $unexpected. Expected one of: put, get post")
PS. The object get { def unapply(mod: Mod): Boolean = ... } part is boilerplate that could be generated by some #ParamAnnotation macro annotation, for example #ParamAnnotion class get extends StaticAnnotation

Play Framework: How to implement proper error handling

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
}