I'm trying to use akka.http.scaladsl.testkit.responseAs in order to test some endpoints, but I can't figure out how to handle the marshalling/unmarshalling process of a org.joda.time.DateTime object. For instance, consider the case class below:
case class ConfigEntity(id: Option[Int] = None, description: String, key: String, value: String, expirationDate: Option[DateTime] = None)
Also, consider the following route test:
"retrieve config by id" in new Context {
val testConfig = testConfigs(4)
Get(s"/configs/${testConfig.id.get}") ~> route ~> check {
responseAs[ConfigEntity] should be(testConfig)
}
}
When I run "sbt test", the code does not compile, throwing the following error: "could not find implicit value for evidence parameter of type akka.http.scaladsl.unmarshalling.FromResponseUnmarshaller[me.archdev.restapi.models.ConfigEntity]"
I know the message is very self explanatory, but I still don't know how to create the implicit FromResponseUnmarshaller that the code is complaining about.
My code is based in this example: https://github.com/ArchDev/akka-http-rest
I'm just creating some new entities and trying to play around...
Thanks in advance.
This project uses CirceSupport. That means that you need to provide a Circe Decoder for the compiler to derive the Akka Http Unmarshaller.
Put the Decoder in scope:
case class ConfigEntity(id: Option[Int] = None, description: String, key: String, value: String, expirationDate: Option[DateTime] = None)
implicit val decoder = Decoder.decodeString.emap[DateTime](str =>
Right(DateTime.parse(str))
)
"retrieve config by id" in new Context {
val testConfig = testConfigs(Some(4))
Get(s"/configs/${testConfig.id.get}") ~> route ~> check {
responseAs[ConfigEntity] should be(testConfig)
}
}
Obviously, you must deal with the possible exceptions trying to parse your DateTime and return Left instead Right...
I must say that I always use SprayJsonSupport for Akka Http, and this is the first time I´ve seen CirceSupport.
Hope this helps.
Related
I’m just heading an issue when I’m trying to create an endpoint with multiple bodies shape.
My model looks like this:
sealed trait FileExampleTrait {
def kind: String
}
case class FileExampleOne(name: String, length: Int) extends FileExampleTrait {
override def kind: String = “one”
}
case class FileExampleTwo(name: String) extends FileExampleTrait {
override def kind: String = “two”
}
case class FileExampleResponse(message: String)
And I’m trying to create this endpoint:
val fileExample = baseEndpoint.post
.in(“example”)
.in(jsonBody[FileExampleTrait])
.out(jsonBody[FileExampleResponse])
.summary(“something”)
.description(“something”)
The implementation of the endpoint looks like this:
private val fileExample = toAkkaRoute(jwtConsumer, errorHandler)(
FileApi.fileExample, { (scope: RequestScope, input: (FileExampleTrait)) =>
print(scope)
input match {
case FileExampleOne(name, _) => Future.successful(FileExampleResponse(name).asRight)
case FileExampleTwo(name) => Future.successful(FileExampleResponse(name).asRight)
}
}
)
This is just an example on what I’m trying to create. I added the schema derivation based on this:
val sOne = Schema.derived[FileExampleOne]
val sTwo = Schema.derived[FileExampleTwo]
implicit val sExampleTrait: Schema[FileExampleTrait] =
Schema.oneOfUsingField[FileExampleTrait, String](_.kind, _.toString)(“one” -> sOne, “two” -> sTwo)
I created a test for trying the endpoint based on Akka HTTP:
test(“Example test”) {
new Fixture() {
val request = FileExampleOne(“name”, 1)
Post(s”/api/v1/files/example”, jsonEntity(request)).withHeaders(requestHeaders) ~> wrappedRoute ~> check {
response should be(successful)
contentType shouldEqual ContentTypes.`application/json`
}
}
}
The error I’m getting is the following:
Response error: {“code”:400,“message”:“Invalid value for: body (No constructor for type FileExampleTrait, JObject(List((name,JString(name)), (length,JInt(1)))))“}
I was following this documentation.
Well that's because a trait doesn't have a constructor as indicated in the error itself. I think I see where you're going, you want to try parsing the body as one of the traits subclasses. So imagine you have this type/class hierarchy:
T // parent trait
/ \
C1 C2 // class 1, etc...
/
C3
Now you want to deserialize some JSON into trait T, you need to define your custom behavior, like "First try converting into C3, if failed, try converting to C2, if failed again, try converting to C1", and you'll get your T value. Now depending on the JSON library you use, the implementation might differ, see the documentation by softwaremill to get more information about how to deal with JSONs in tapir, and if you use Play Json, I can recommend:
object FileExampleOne {
implicit val reader: Reads[FileExampleOne] = Json.reads
}
object FileExampleTwo {
implicit val reader: Reads[FileExampleTwo] = Json.reads
}
object FileExampleTrait {
implicit val reads: Reads[FileExampleTrait] = json => {
json.validate[FileExampleOne] orElse json.validate[FileExampleTwo]
}
}
You can see it running on scastie. And based on the tapir documentations, you need a Codec for your types, and one of the approaches to create your coded for JSON, is using one of tapir's supported libraries (circe, Play Json, ...).
I'm trying to create a generic HTTP client in Scala using spray. Here is the class definition:
object HttpClient extends HttpClient
class HttpClient {
implicit val system = ActorSystem("api-spray-client")
import system.dispatcher
val log = Logging(system, getClass)
def httpSaveGeneric[T1:Marshaller,T2:Unmarshaller](uri: String, model: T1, username: String, password: String): Future[T2] = {
val pipeline: HttpRequest => Future[T2] = logRequest(log) ~> sendReceive ~> logResponse(log) ~> unmarshal[T2]
pipeline(Post(uri, model))
}
val genericResult = httpSaveGeneric[Space,Either[Failure,Success]](
"http://", Space("123", IdName("456", "parent"), "my name", "short_name", Updated("", 0)), "user", "password")
}
An object utils.AllJsonFormats has the following declaration. It contains all the model formats. The same class is used on the "other end" i.e. I also wrote the API and used the same formatters there with spray-can and spray-json.
object AllJsonFormats
extends DefaultJsonProtocol with SprayJsonSupport with MetaMarshallers with MetaToResponseMarshallers with NullOptions {
Of course that object has definitions for the serialization of the models.api.Space, models.api.Failure and models.api.Success.
The Space type seems fine, i.e. when I tell the generic method that it will be receiving and returning a Space, no errors. But once I bring an Either into the method call, I get the following compiler error:
could not find implicit value for evidence parameter of type
spray.httpx.unmarshalling.Unmarshaller[Either[models.api.Failure,models.api.Success]].
My expectation was that the either implicit in spray.json.DefaultJsonProtocol, i.e. in spray.json.StandardFormts, would have me covered.
The following is my HttpClient class trying it's best to be generic:
Update: Clearer/Repeatable Code Sample
object TestHttpFormats
extends DefaultJsonProtocol {
// space formats
implicit val idNameFormat = jsonFormat2(IdName)
implicit val updatedByFormat = jsonFormat2(Updated)
implicit val spaceFormat = jsonFormat17(Space)
// either formats
implicit val successFormat = jsonFormat1(Success)
implicit val failureFormat = jsonFormat2(Failure)
}
object TestHttpClient
extends SprayJsonSupport {
import TestHttpFormats._
import DefaultJsonProtocol.{eitherFormat => _, _ }
val genericResult = HttpClient.httpSaveGeneric[Space,Either[Failure,Success]](
"https://api.com/space", Space("123", IdName("456", "parent"), "my name", "short_name", Updated("", 0)), "user", "password")
}
With the above, the problem still occurs where the unmarshaller is unresolved. Help would be greatly appreciated..
Thanks.
Spray defines a default marshaller for Either[A,B] if a Marshaller[A] and Marshaller[B] are in defined scope inside the MetaMarshallers trait. But, going the other direction requires an Unmarshaller. You will need to define an in-scope Unmarshaller for Either[Failure, Success]. This cannot be coded without specific knowledge of the expected response and what the strategy will be for choosing whether to unmarshall a response as a Left or as a Right. For example, let's say you want to return a Failure on a non-200 response and a Success from a 200 json response body:
type ResultT = Either[Failure,Success]
implicit val resultUnmarshaller: FromResponseUnmarshaller[ResultT] =
new FromResponseUnmarshaller[ResultT] {
def apply(response: HttpResponse): Deserialized[ResultT] = response.status match {
case StatusCodes.Success(200) =>
Unmarshaller.unmarshal[Success](response.entity).right.map(Right(_))
case _ => Right(Left(Failure(...)))
}
}
Update
Looking deeper into this, the problem appears to be that the default eitherFormat in spray.json.StandardFormats is not a RootJsonFormat, which is required by the default JSON unmarshaller defined in spray.httpx.SprayJsonSupport. Defining the following implicit method should solve the issue:
implicit def rootEitherFormat[A : RootJsonFormat, B : RootJsonFormat] = new RootJsonFormat[Either[A, B]] {
val format = DefaultJsonProtocol.eitherFormat[A, B]
def write(either: Either[A, B]) = format.write(either)
def read(value: JsValue) = format.read(value)
}
I have an working example gist that hopefully explains how you would use this. https://gist.github.com/mikemckibben/fad4328de85a79a06bf3
I'm using spray-json to marshal lists of custom objects into JSON. I have the following case class and its JsonProtocol.
case class ElementResponse(name: String, symbol: String, code: String, pkwiu: String, remarks: String, priceNetto: BigDecimal, priceBrutto: BigDecimal, vat: Int, minInStock:Int, maxInStock: Int)
object JollyJsonProtocol extends DefaultJsonProtocol with SprayJsonSupport {
implicit val elementFormat = jsonFormat10(ElementResponse)
}
When I try to put in in a route like this one:
get {
complete {
List(new ElementResponse(...), new ElementResponse(...))
}
}
I get an error saying that:
could not find implicit value for evidence parameter of type spray.httpx.marshalling.Marshaller[List[pl.ftang.scala.polka.rest.ElementResponse]]
Perhaps you know what is the problem?
I'm using Scala 2.10.1 with spray 1.1-M7 and spray-json 1.2.5
This is an old issue, but I feel like giving my 2c. Was looking at similar issues today.
Marcin, it seems your issue was not actually solved (as far as I can read) - why did you accept one answer?
Did you try adding import spray.json.DefaultJsonProtocol._ in places? Those are in charge of making things such as Seqs, Maps, Options and Tuples to work. I would assume this might be the cause of your problem, since it's the List that is not getting converted.
The easiest way to do this, is to make a String from your list or you'll have to deal with ChunckedMessages:
implicit def ListMarshaller[T](implicit m: Marshaller[T]) =
Marshaller[List[T]]{ (value, ctx) =>
value match {
case Nil => ctx.marshalTo(EmptyEntity)
case v => v.map(m(_, ctx)).mkString(",")
}
}
The seconds way is to convert your list into the Stream[ElementResponse] and let spray chunck it for you.
get {
complete {
List(new ElementResponse(...), new ElementResponse(...)).toStream
}
}
You also need to import the format you defined on the route scope:
import JollyJsonProtocol._
get {
complete {
List(new ElementResponse(...), new ElementResponse(...))
}
}
there is a simple model class that contains some database ids. It looks like this:
case class Post(id: ObjectId, owner: Option[ObjectId], title: String)
object Post {
implicit val implicitPostWrites = Json.writes[Post]
}
With this code, the compiler gives me the following error:
No implicit Writes for com.mongodb.casbah.commons.TypeImports.ObjectId available.
implicit val implicitFooWrites = Json.writes[Foo]
It is obvious what's missing, but I don't know how to provide an implicit Writes for com.mongodb.casbah.commons.TypeImports.ObjectId. How can this be done?
The error means that it doesn't know how to serialize ObjectId and expects you to provide a Writer for it. This is one way to serialize it:
object Post {
implicit val objectIdWrites = new Writes[ObjectId] {
def writes(oId: ObjectId): JsValue = {
JsString(oId.toString)
}
}
implicit val implicitPostWrites = Json.writes[Post]
}
More information and explanations are available here.
I have:
case class One(someParam: String) {
private val _defaultTimeout = readFromConfig("defaultTimeout")
val timeout: Timeout = akka.util.Timeout(_defaultTimeout seconds)
val info: Option[Info] = Await.result(someSmartService.getInformationForSomething(someParam)), timeout.duration)
}
I'm building a service, which will obscure (encrypt) some sensitive data. I'm doing it in a such way:
def encrypt(oldOne: One): One = {
val encryptedSomeParam = EncryptService.getHash(oldOne.someParam)
val encryptedInfo = encryptInfo(oldOne.info)
// what to do with that? ^^
one.copy(someParam = encryptedSomeParam)
}
Also, I need to encrypt some data inside this "info" field of class One. The issue is that it is a val and I cannot reassign the value of a val. Is there an easy way how to do that? For now I'm thinking about changing it to a var, but I think it's not the best way to do that. Also, I cannot write encrypted data to this value from the beginning like this:
val info: Option[Info] = EncryptionService.encrypt(someSmartService.getInformationForSomething(someParam))
As this field is used in other places where I need the fields to be not encrypted. I want to encrypt sensitive data before the persistence of the object to a database.
Any ideas?
Thanks in advance!
EDIT: I know, that this looks like a bad design, so if someone has a better idea how to deal with it I'm looking forward to hear from you :)
Why not make info a case class argument as well?
case class One(someParam: String, info: Option[Info])
You could implement a default value for info by defining the companion object like
object One {
def apply(someParam: String): One = One(someParam, someSmartService.getInformationForSomething(someParam))
}
That would allow you to work with Ones as follows:
One("foo")
One("foo", Some(...))
One(encryptedSomeParam, encryptedInfo)
One("plaintext").copy(someParam = encryptedSomeParam, info = encryptedInfo)
EDIT 1: Lazy info
Case classes cannot have lazy val arguments, i.e., neither info: => Option[String] nor lazy val info: Option[String] is allowed as an argument type.
You could make info a parameter-less function, though
case class One(someParam: String, info: () => Option[String])
object One {
def apply(someParam: String): One = One(someParam, () => Some(someParam))
}
and then use it as
One("hi", () => Some("foo"))
println(One("hi", () => None).info())
This is obviously not ideal since it is not possible to introduce these changes without breaking code client code. Better solutions are welcome.
EDIT 2: Lazy info, no case class
If you don't insist on One being a case class (for example, because you really need copy), you could use a regular class with lazy values and a companion object for easy use:
class One(_someParam: String, _info: => Option[String]) {
val someParam = _someParam
lazy val info = _info
}
object One {
def apply(someParam: String): One = new One(someParam, Await.result(...))
def apply(someParam: String, info: => Option[String]): One = new One(someParam, info)
def unapply(one: One) = Some((one.someParam, one.info))
}