Tapir Custom Codec - scala

I am stuck at a place, I am using scala, tapir and circe.
sealed abstract class S1Error extends Product with Serializable
object S1Error {
final case class SError(error: SMError) extends S1Error
}
sealed abstract class SMError(message: String)
object SMError {
final case class SVError(message: String) extends SMError(message)
}
For tapir errorOut I am using this
val schemaVersionError: EndpointOutput.StatusMapping[SError] = statusMappingValueMatcher(
StatusCode.BadRequest,
jsonBody[SError]
.description("XXXX.")
) {
case SMError(SVError(_)) => true
case _ => false
}
Now because of this structure, the API result I get is
{
"error": {
"SVError": {
"message": "XXXXG"
}
}
}
where as ideally I would want a response as
"message": "XXXXG"
I can not change the error structure.
Is there a way to wrap this error using a custom codec to get the result as required.

Tapir codec is derived from Circe's decoder and encoder.
What you see is the default way of encoding case classes by circe.
Circe provides the possibility to encode case classes the way you described with deriveUnwrappedEncoder from circe-generic-extras. Unfortunately, it doesn't compile for SMError (probably derivation mechanism gets confused by your abstract class hierarchy).
What you can do is just creating encoder manually:
sealed abstract class S1Error extends Product with Serializable
object S1Error {
final case class SError(error: SMError) extends S1Error
implicit val encoder: Encoder[SError] = Encoder[SMError].contramap(_.error)
// or you can use deriveUnwrappedEncoder from circe-generic-extras:
// implicit val encoder: Encoder[SError] = deriveUnwrappedEncoder
}
//I also needed to make message a field in SMError
sealed abstract class SMError(val message: String)
object SMError {
final case class SVError(override val message: String) extends SMError(message)
implicit val encoder: Encoder[SMError] = Encoder.encodeJsonObject.contramap{s => JsonObject("message" -> s.message.asJson)}
}
Response now looks like:
{
message": "XXXXG"
}

Related

Implementing Config class containing Enum Map by PureConfig and Enumeratum

I'm trying to read my config file into my case class whose one of attribute is a Map of Enumeratum key and Case Class value by using pureconfig and pureconfig-enumeratum libraries version 0.14.0 with scala 2.11.
When I change the Map key from Enumeratum key to String, it works, but it does not work with Enum key.
import enumeratum.EnumEntry.{Hyphencase}
import enumeratum._
import pureconfig.{ConfigSource}
import pureconfig.generic.auto._
import pureconfig.module.enumeratum._
object CheckPureConfig extends App {
private val myConf = ConfigSource.default.loadOrThrow[SsystemConf]
println(myConf)
}
case class SsystemConf(target: Map[Ssystem, MyConfig])
case class MyConfig(path: Ssystem, link: String)
sealed abstract class Ssystem(myField: String) extends EnumEntry with Hyphencase{
def printit() = myField
}
object Ssystem extends Enum[Ssystem] {
val values = findValues
case object MyEnumA extends Ssystem("testFieldEnum1")
case object MyEnumB extends Ssystem("testFieldEnum2")
}
And this is my application.conf
target {
my-enum-a= {
path : "samplepath1"
link : "samplehttp1"
}
my-enum-b = {
path : "samplepath2"
link : "samplehttp2"
}
}
You have to use configurable converter to tell pureconfig how to transform your enum to Map keys. You have genericMapReader for that:
implicit def enumMapReader[V: ConfigReader]: ConfigReader[Map[Ssystem, V]] =
genericMapReader { name =>
Ssystem.withNameOption(name)
.fold[Either[String, Ssystem]](Left(s"$name is not enum"))(Right(_))
}

How does one provide an unmarshaller for a Scala trait in Akka HTTP?

I am using Akka HTTP and spray-json.
I have the following model hierarchy defined:
trait Animal {
def weight: Int
def name: String
}
case class Dog(
weight: Int,
name: String
//other specific attributes
) extends Animal
case class Cat(
weight: Int,
name: String
//other specific attributes
) extends Animal
I also have defined a correct custom JsonFormat for trait Animal as:
implicit val AnimalFormat = new JsonFormat[Animal] {
// custom read method
// custom write method
}
Now I want to have a generic POST method to create Animal instances:
post {
entity(as[Animal]) { animal =>
complete {
//save Animal
}
}
}
I want this POST method to accept JSON of both type Dog and Cat. The JSON formatter is also in scope. But I am getting the following compilation error:
could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[models.Animal]
I don't understand why the implicit formatter is not being detected.
Finally, I got around the problem. All I had to do was use RootJsonFormat instead of JsonFormat. So, the formatter looks like this now:
implicit val AnimalFormat = new RootJsonFormat[Animal] {
// custom read method
// custom write method
}

scala ADTs via sealed traits - is there a way to deserialize from string in a generic fashion

Let's say I have the following trait
trait Named {
def name: String
}
and the following Algebraic Data Type
sealed trait Animal extends Named
case object Dog extends Animal {
override val name: String = "dog man"
}
case object Cat extends Animal {
override val name: String = "cat man"
}
case object Owl extends Animal {
override val name: String = "I am an owl left in the dark"
}
Now, I can deserialize an instance of string into my Animal ADT with the following method.
object Animal {
def apply(name: String): Animal = name match {
case Dog.name => Dog
case Cat.name => Cat
}
}
#oxbow_lakes mentions at the end of his answer that:
Can't instantiate easily from persisted value. This is also true but,
except in the case of huge enumerations (for example, all currencies),
this doesn't present a huge overhead.
I find that the fact that when you add a new value it needs to be added to the deserialization code explicitly as error prone (I thought that the compiler would warn me of an in-exhaustive match, but take a look at Owl above and the apply method - there was no warning issued...)
Is there no better way? (If not with the standard scala toolset, a third party one?)
This problem already solved by enumeratum library:
https://github.com/lloydmeta/enumeratum
Your code could be written like this:
import enumeratum._
import enumeratum.EnumEntry.Lowercase
sealed trait Animal extends EnumEntry with Lowercase
object Animal extends Enum[Animal] {
val values = findValues
case object Dog extends Animal
case object Cat extends Animal
case object Owl extends Animal
}
val dogName = Animal.Dog.entryName
val dog = Animal.withNameInsensitive(dogName)
One thing you could try is to use reflection to obtain the set of types that extend Animal, then use that to create a Map[String,Animal] using name to lookup object values, then use the map in your Animal.apply function.
Refer to this question for more information on obtaining the Animal subclasses.

Scala generics in function return value

I have these classes defined.
trait ResultTrait {
}
case class PostResult (
#Key("_id") id: String,
success: String,
errors: Seq[String] = Seq.empty
) extends ResultTrait
case class PostError (
message: String,
errorCode: String
) extends ResultTrait
This won't compile. It gives error "Required T, but found PostResult (or PostError)".
def postLead[T <: SFDCResult](accessToken: AccessToken):
Future[T] = {
// depends on response from request, return PostResult or PostError
}
As #Travis Brown has already stated, it looks like you're trying to express the variability of the return type (i.e. "it's either a PostResult or a PostError") through generics, when really all you need is the parent trait.
Assuming your SDFCResult was an anonymization error where you meant to use ResultTrait, I would use the following:
// Make the trait sealed so we can only have our two known implementations:
sealed trait ResultTrait {}
...
// Two subclasses as before
And then your method should just be:
def postLead(accessToken: AccessToken):Future[ResultTrait] = {
// depends on response from request, return PostResult or PostError
}

Reify name of class implementing trait as String, from the trait itself

I have a trait that's implemented by a large number of classes, and I'd like to use the names of the classes that implement this trait at runtime, but with as much code centralized as possible.
Specifically, in my code, I'm using tokens to represent classes to be initialized at runtime. The tokens carry configuration, and the actual class is instantiated as needed via the token, combined with run-time information. For linking with resources outside of my app, I want to be able to access the name of the class for which a token is defined. See the example:
trait Token[Cls] {
val className = ???
// Example generic method depending on final class name
def printClassName = println(className)
}
case class ClassA(t: ClassAToken, runtimeContext: String) {
// a bunch of other code
}
object ClassA {
case class ClassAToken(configParam: String) extends Token[ClassA]
}
So, I'm trying to implement className. Ideally, I can pull this information once at compile time. How can I do this, while keeping boilerplate code out of ClassA? Although, if I can drop the type parameter and get the name of the class implementing the Token trait at runtime, that's great too.
Due to Type Erasure Cls is not available on runtime anymore. To get the informations at runtime, you need to use a TypeTag (in your case a ClassTag).
Your code could look like this:
import scala.reflect._
trait Token[Cls] {
def className(implicit ct: ClassTag[Cls]) = ct.runtimeClass.getName
// Example generic method depending on final class name
def printClassName(implicit ct: ClassTag[Cls]) = println(className)
}
case class ClassA(t: ClassAToken, runtimeContext: String) {
// a bunch of other code
}
object ClassA {
case class ClassAToken(configParam: String) extends Token[ClassA]
}
or if it is possible for you to let Token be an class, you could use the ClassTag context bounds:
import scala.reflect._
class Token[Cls: ClassTag] {
def className = classTag[Cls].runtimeClass.getName
// Example generic method depending on final class name
def printClassName = println(className)
}
case class ClassA(t: ClassAToken, runtimeContext: String) {
// a bunch of other code
}
object ClassA {
case class ClassAToken(configParam: String) extends Token[ClassA]
}
For more informations on TypeTags/ClassTags see Scala: What is a TypeTag and how do I use it?