I have following code:
def delete(id: String) = {
client.execute {
deleteByQuery("myIndex", termQuery("id", id))
}.await
}
delete(id) match {
case failure: RequestFailure => complete(StatusCodes.InternalServerError, failure.body.getOrElse("Unknown reason"))
case success: RequestSuccess[DeleteByQueryResponse] => {
complete("I want to put info extracted from results here...")
}
I observe that inside success object there is a field by name 'result', and it is of type Left[DeleteByQueryResponse].
RequestSuccess has a map method
final def map[V](f: U => V): Response[V] = RequestSuccess(status, body, headers, f(result))
where U is of same type as 'result'.
I am not really sure how to extract all relevant information from 'result'?
Related
I have an incoming JSON data that looks like below:
{"id":"1000","premium":29999,"eventTime":"2021-12-22 00:00:00"}
Now, I have created a class that will accept this record and will check whether the data type of the incoming record is according to the data types defined in the case class. However, when I am calling the method it is always calling the Failure part of the match case.
case class Premium(id: String, premium: Long, eventTime: String)
class Splitter extends ProcessFunction[String, Premium] {
val outputTag = new OutputTag[String]("failed")
def fromJson[T](json: String)(implicit m: Manifest[T]): Either[String, T] = {
Try {
println("inside")
lazy val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
mapper.readValue[T](json)
} match {
case Success(x) => {
Right(x)
}
case Failure(err) => {
Left(json)
}
}
}
override def processElement(i: String, context: ProcessFunction[String, Premium]#Context, collector: Collector[Premium]): Unit = {
fromJson(i) match {
case Right(data) => {
collector.collect(data)
println("Good Records: " + data)
}
case Left(json) => {
context.output(outputTag, json)
println("Bad Records: " + json)
}
}
}
}
Based on the sample record above, it should pass the Success value but no matter what I pass, it always enters the Failure part. What else is missing?
I am using Scala 2.11.12 and I tried examples from this link and this link but no luck.
An action in the controller have a type Action[A], which means that the body is an A (i.e should parse to A).
I'd like to look at an action's signature and know if it has no body, which is useful for GET requests for example.
e.g here's a code example with two actions, one that accepts a Person in the body and one that does not have a body:
case class Person(name: String)
implicit val personFormats: Format[Person] = Json.format[Person]
val people: mutable.Set[Person] = mutable.Set(Person("alice"), Person("bob"))
/** here it is clear that the body should contain a person info, since the method returns `Action[Person]` */
def createPerson(): Action[Person] =
Action(parse.json[Person]) { request: Request[Person] =>
val person: Person = request.body
if (people.add(person))
Ok(s"added ${person.name}")
else
Ok(s"${person.name} is already there :) nothing special to do")
}
/** Can it be clearer from the signature that there's no body? */
def findPerson(name: String): Action[AnyContent] =
Action {
people.find(_.name == name) match {
case Some(person) => Ok(Json.toJson(person))
case None => NotFound(s"no person named $name")
}
}
When I look at the body of the second action, findPerson, it is clear from Action(parse.empty) that the body is empty.
How can it be clear from the signature as well without messing up the body?
Returning Action[AnyContentAsEmpty] cannot work because play.api.mvc.AnyContentAsEmpty is an object.
There's the following version, where we pass parse.empty as the parser, and it would work, but request is unused, and I wonder if there's another way:
def findPerson2(name: String): Action[Unit] =
Action(parse.empty) { request =>
people.find(_.name == name) match {
case Some(person) => Ok(Json.toJson(person))
case None => NotFound(s"no person named $name")
}
}
Consider EssentialAction which by-design does not have a body parser like so
def findPerson(name: String) =
EssentialAction { _ =>
people.find(_.name == name) match {
case Some(person) => Accumulator.done(Ok(Json.toJson(person)))
case None => Accumulator.done(NotFound(s"no person named $name"))
}
}
or ActionBuilder.IgnoringBody() like so
val ignoringBodyAction = new ActionBuilder.IgnoringBody()(executionContext)
def findPerson(name: String) =
ignoringBodyAction {
people.find(_.name == name) match {
case Some(person) => Ok(Json.toJson(person))
case None => NotFound(s"no person named $name")
}
}
There is a trait which works perfectly. However, I would like to refactor the part related to generic [T] in order to limit the data type which could be accepted by generic [T] (I need only Option[JsValue] , JsValue , StringEnumEntry , String ). Is it possible to solve this problem through shapeless coproduct? Maybe there are other solutions?
trait ParameterBinders extends Log {
def jsonBinder[T](json: T, jsonType: java.lang.String = "json"): ParameterBinderWithValue = {
val jsonObject = new PGobject()
jsonObject.setType(jsonType)
json match {
case json: Option[JsValue] =>
jsonObject.setValue(json.map(Json.stringify).orNull)
case json: JsValue =>
jsonObject.setValue(Json.stringify(json))
case json: StringEnumEntry =>
jsonObject.setValue(json.value)
case json: String =>
jsonObject.setValue(json)
case _ =>
logger.error("unexpected data type ")
}
if (jsonType == "JSONSCHEMATYPE" || jsonType == "SYSPROPERTYTYPE") {
ParameterBinder(this, (ps, i) => {
ps.setObject(i, jsonObject)
})
} else {
ParameterBinder(json, (ps, i) => {
ps.setObject(i, jsonObject)
})
}
}
}
The easiest way is to use an ADT as described in the link of the first comment.
If you don't want to change the types that are accepted in jsonBinder then you can solve the problem by using a typeclass.
e.g.
trait JsonBindValue[T] {
def value(t: T): String
}
you would then have to provide instances for your accepted datatypes
object JsonBindValue {
implicit val OptJsBinder = new JsonBindValue[Option[JsValue]] {
def value(t: Option[JsValue]): String = {
t.map(Json.stringify).orNull
}
}
... more instances here
}
finally your function would look like this:
def jsonBinder[T : JsonBindValue](json: T, jsonType: java.lang.String = "json"): ParameterBinderWithValue = {
val binder = implicitly[JsonBindValue[T]]
jsonObject.setType(jsonType)
jsonObject.setValue(binder.value(json))
...
}
if you call the function without a implicit instance in scope you will get a compile time error.
The following sample code uses Play Framework to parse JSON to an object:
def createEvent(): Action[JsValue] = Action.async(parse.tolerantJson) {
request => {
request.body.validate[SomeEvent] match {
case o:JsSuccess[SomeEvent] => {
//do something
Future(Created)
}
}
}
}
Is it possible to generalise it so it can handle different event types? e.g.
def createEvent(): Action[JsValue] = Action.async(parse.tolerantJson) {
request => {
val eventType = request.contentType match {
case Some("SomeEventType") => SomeEvent
case Some("OtherEventType") => OtherEvent
}
request.body.validate[eventType] match {
case o:JsSuccess[eventType] => {
//do something
Future(Created)
}
}
}
}
Currently, the above code will fail in the line request.body.validate[eventType]
You can extract body.validate[T] into a function and call it from your patten matching construct with a proper type, i.e:
def extract[T: JsonFormat](implicit req: Request[AnyContent]) = req.body.valudate[T]
request.contentType match {
case Some("SomeEventType") => extract[SomeEvent]
case Some("OtherEventType") => extract[OtherEvent]
}
You can read and create class from contentType dynamically. But you can have problem, if will be no implicit Format for extracted type in scope:
error: No Json formatter found for type A. Try to implement an implicit Format for this type.
or
java.lang.ClassNotFoundException: models.Bazz
The available data:
model
package models
case class Bar(name: String)
request
request.contentType: Option[String] = Some("models.Bar")
incoming body
request.body: JsValue = """{"name": "bar name"}"""
format[Bar]
implicit val BarFormat = Json.format[models.Bar]
extractor:
def extract[A <: Any](ct: String, json: JsValue)(implicit format: Format[A]) = {
val manifest:Manifest[A] = Manifest.classType[A](Class.forName(ct))
json.validate[A]
}
request.contentType.map(extract(_, request.body))
res1: Option[play.api.libs.json.JsResult[models.Bar]] = Some(JsSuccess(Bar(bar name),/name))
you can see https://github.com/strongtyped/fun-cqrs/blob/demos/shop-sample/samples/shop/src/main/scala/funcqrs/json/TypedJson.scala for other way of deserializing self-contained json.
Say I'm using an async library to read and write data. If a failure, I get an error message as a string, otherwise the data. Is there a better way to represent the return type than Future[Either[String, Future[Data]]]?
Possible implementation:
def createData(name: String): Future[Either[String, Future[Data]]] = {
dataDAO.findOneByName(name).flatMap {
case Some(data) =>
Future.successful(Left(s"Data with name already exists. $name"))
case None =>
val data = Data.createFromName(name)
dataDAO.save(data).map {
lastError =>
data
}.right.futureSuccessful
}
}
Your return type should be Future[Either[String, Data]]
To archive this you need to change your second case to:
case None =>
val data = Data.createFromName(name)
dataDAO.save(data).map {
_ => Right(data)
}
I even would improve your error type from String to e.g. CreateDataError, so your return type would be Future[Either[CreateDataError, Data]] and your first case would be
// Having somewhere decalred
trait CreateDataError
object DataAlreadyExistsError
//...
//...
case Some(data) =>
Future.successful(Left(DataAlreadyExistsError))