I want to refactor this code into something more readable and better in general. I know that in Scala there are normally neat ways of doing things but for me it's getting a bit messy (BTW I'm using the Play library in the code). this is a snippet of my code:
class HomeController #Inject()
(cc: ControllerComponents)
(implicit val config: Configuration)
extends AbstractController(cc) {
def removeIdElement(uid: String) =
HAction(uid, "AuthEvent", 1, "login", parse.text).async {
implicit request: Request[String] =>
val promise = Promise[Result]()
Future {
val removeId = request.body.toLong
println(s"remove id $removeId")
promise completeWith {
idElementsDAO.remove(removeId, uid.toLong) map {
_ => Ok("")
} recover {
case t: Throwable =>
val errorMessage: String = getMessageFromThrowable(t)
println("remove id element failure " + errorMessage)
BadRequest(errorMessage)
}
}
} recover {
case t: Throwable =>
val errorMessage: String = getMessageFromThrowable(t)
println("remove id element failure " + errorMessage)
promise.success(BadRequest(errorMessage))
}
promise.future
}
}
Assuming that idElementsDAO.remove return a Future, this is probably more idiomatic:
def removeIdElement(uid: String) =
HAction(uid, "AuthEvent", 1, "login", parse.text).async {implicit request =>
val removeId = request.body.toLong
println(s"remove id $removeId")
idElementsDAO.remove(removeId, uid.toLong)
.map(_ => NoContent) // probably more correct than `Ok("")`
.recover {
case t: Throwable =>
val errorMessage: String = getMessageFromThrowable(t)
println("remove id element failure " + errorMessage)
BadRequest(errorMessage)
}
}
No need for the Promise or the call to Future {...} (Future.apply).
Keep in mind, it's probably not the best idea to directly pass the underlying error of any Throwable directly to the http client (browser?).
If you add generic error handling code to the global error handler (for unexpected errors) that logs the error and sends a generic message to the front-end, you can then write it even cleaner like this:
def removeIdElement(uid: String) =
HAction(uid, "AuthEvent", 1, "login", parse.text).async {implicit request =>
val removeId = request.body.toLong
println(s"remove id $removeId")
for {
_ <- idElementsDAO.remove(removeId, uid.toLong)
} yield NoContent
}
https://www.playframework.com/documentation/2.6.x/ScalaErrorHandling
Here is a simpler version of your code:
class HomeController #Inject()(cc: ControllerComponents)(implicit val config: Configuration)
extends AbstractController(cc) {
def removeIdElement(uid: String) = HAction(uid, "AuthEvent", 1, "login", parse.text).async {
implicit request: Request[String] =>
Future {
val removeId = request.body.toLong
println(s"Removing id $removeId")
removeId
}.flatMap(id => idElementsDAO.remove(id, uid.toLong))
.map(_ => Ok(""))
.recover {
case t: Throwable =>
val errorMessage = getMessageFromThrowable(t)
println(s"Removing id element failed: ${errorMessage}")
BadRequest(errorMessage)
}
}
}
In the above code, a Promise is not needed, and the recover combinator is not repeated.
Related
Currently trying to test authentication controller using specs2 and scalatest, my application runs fine, but the test always return NullPointerException or Scala Match error null
val userRepository: UserRepository = mock[UserRepository]
val userService: UserService = mock[UserService]
val authenticator: Authenticator = mock[Authenticator]
val authenticationService: AuthenticationService = mock[AuthenticationService]
implicit val ec = mock[ExecutionContext]
implicit val materializer = mock[Materializer]
val authenticationController = new AuthenticationController(Helpers.stubControllerComponents(
playBodyParsers = Helpers.stubPlayBodyParsers(materializer)
), authenticationService, authenticator, userService)
"User" should "login successfully" in {
val request = FakeRequest(POST, "/api/login").withHeaders(CONTENT_TYPE -> "application/json")
.withBody[JsValue](Json.parse("""{"email": "nghia_hd#flinters.vn", "password": "a12306789H#"}"""))
val result: Future[Result] = authenticationController.login().apply(request)
result.map {data => data.header.status shouldBe OK}
}
}
When using Intellij debugger, the exception seems to be here, but I dont really understand how to fix it
def login: Action[JsValue] = Action(parse.json) {
implicit request =>
UserLoginForm.form.bindFromRequest.fold(
formWithErrors => badRequestWarning(formWithErrors.errors),
user => {
--> authenticationService.verify(user) match {
case Success(result) => success(UserPayload.encode(result))
case Failure(exception) => exception match {
case _: PasswordNotMatch => error(Unauthorized, 401, "Password not match")
case _: EntityNotFoundException => error(Unauthorized, 400, "Email not found")
case _ => error(InternalServerError, 500, "An error occurred")
}
}
}
)
}
I've not used specs2/scalatest specifically, but usually you need to specify the response of the mocked method. You need to add this to the test:
when(authenticationService.verify(any())).thenReturn(someMockUser)
Otherwise (depending on your test settings) it will return null. There's also testing frameworks where you can instead throw an exception if methods that aren't mocked are called on mock objects.
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.
I'm working through a slight variation of Sangria's Getting Started, using Akka HTTP. I'm attempting to use json4s-jackson as the serializaltion lib, but am running in to some trouble getting the response I want.
Specifically, the serialized response I get is the JSON version of the (StatusCode, Node) tuple:
{
"_1": {
"allowsEntity": true,
"defaultMessage": "OK",
"intValue": 200,
"reason": "OK"
},
"_2": {
"data": {
"foo": {
"id": "1",
"name": "Foo"
}
}
}
}
The data portion is correct, but obviously I just want that and not the first element of the serialized tuple.
I'm using akka-http-json4s, so my trait with route looks like:
case class GraphQlData(query: String, operation: Option[String])
trait FooController {
import de.heikoseeberger.akkahttpjson4s.Json4sSupport._
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
val fooRoutes = post {
entity(as[GraphQlData]) { data =>
QueryParser.parse(data.query) match {
// query parsed successfully, time to execute it!
case Success(queryAst) =>
complete {
Executor
.execute(
SchemaDefinition.FooSchema,
queryAst,
new FooService,
operationName = data.operation
)
.map(OK -> _)
.recover {
case error: QueryAnalysisError => BadRequest -> error.resolveError
case error: ErrorWithResolver => InternalServerError -> error.resolveError
}
}
// can't parse GraphQL query, return error
case Failure(error) =>
complete(BadRequest -> error.getMessage)
}
}
}
implicit def executionContext: ExecutionContext
}
For the life of me I can't figure out what's wrong. I've been looking at sangria-akka-http-example but it seems to be exactly the same, with the exception of using spray-json instead of json4s.
Ideas? Thanks!
Ah, figured it out. I neglected to add
import sangria.marshalling.json4s.jackson._
to the trait defining the route. Adding it does the trick.
Just wanted to provide a quick update to this answer for the full GraphQLRequest. Now the variables are included in the request.
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s._
import org.json4s.JsonAST.JObject
import sangria.marshalling.json4s.jackson._
case class GQLRequest(query: String, operationName: Option[String], variables: JObject)
trait SomeJsonSupport extends Json4sSupport {
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
}
trait GraphQLResource extends SomeJsonSupport{
implicit val timeout:Timeout
implicit val system:ActorSystem
import system.dispatcher
def graphqlRoute: Route =
(post & path("graphql")) {
entity(as[GQLRequest]) { requestJson =>
println(s"This is the requestJson = $requestJson")
graphQLEndpoint(requestJson)
}
} ~
get {
println(s"This is working")
getFromResource("graphiql.html")
}
def graphQLEndpoint(requestJson: GQLRequest): Route = {
val route = QueryParser.parse(requestJson.query) match {
case Success(query) =>
println(s"This is the query $query")
val vars = requestJson.variables match {
case jObj:JObject => jObj
case _ => JObject(List.empty)
}
val futureJValue = Executor.execute(clientSchema,
query,
NclhGqlRequest(this),
operationName = requestJson.operationName,
variables = vars)
val futureTupleStatusCodeJValue = futureJValue.map(OK -> _).recover {
case error: QueryAnalysisError => BadRequest -> error.resolveError
case error: ErrorWithResolver => InternalServerError -> error.resolveError
}
complete(futureTupleStatusCodeJValue)
case Failure(error) =>
complete(BadRequest, error.getMessage)
}
route
}
I have a simple flow of orders from an controller to services back to the controller, and im trying to make sure I use future recovers in the right place and in general to cover exceptions properly.
controller action:
def getSiblings(): Action[JsValue] = Action.async(parse.json) { request =>
request.body.validate[Person] match {
case JsSuccess(person, _) =>
peopleService.getSiblings(person).map(res => Ok(Json.toJson(res))) recover {
case t: Throwable =>
logger.error("error running getSiblings: ", t)
InternalServerError
}
case JsError(errors) => Future(BadRequest(s"not a good person format ${errors.mkString}"))
}
}
peopleService:
class PeopleService #Inject() extends LazyLogging {
def getSiblings(personToGetSiblings: Person): Future[List[SiblingResults]] = {
// isSibling is a method of a person that returnes a future and can fail
Future.sequence(listOfPeople.map(person => person.isSibling(personToGetSiblings))) recover {
case e: Exception => {
throw new RuntimeException("fail to get siblings with error: ", e)
}
}
}
}
case class SiblingResults (person: Option[Person])
and a Person:
#Singleton
class PersonA #Inject() (configuration: Configuration, peopleApi: PeopleApi) extends Person {
def isSibling(personToMatch: Person): Future[SiblingResults] = {
val res = for {
// areSiblings returnes a Future[Boolean]
areThey <- peopleApi.areSiblings(personToMatch, personInstance) recover {
case ex: Exception => throw new Exception("PeopleApi failed")
}
} yield areThey
if (res) Some(personInstance) else None
}
val personInstance = this
...
}
what will be the right way to recover those futures?
Use Play's Action composition to handle any failures. This your code would be clean handling only the business logic without the extra plumbing stuff like exception handling etc. You let the exceptions bubble up to the controller and exception is handled finally by the ActionBuilder.
ActionBuilder
import play.api.libs.json.Json
import play.api.mvc.{ActionBuilder, Request, Result}
import play.api.mvc.Results.Status
import scala.concurrent.Future
/**
* Created by chlr on 12/2/16.
*/
object ErrRecoveryAction extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: Request[A] => Future[Result]) = {
block(request) recover errorHandler
}
def errorHandler: PartialFunction[Throwable, Result] = {
// you can apply a different statuscode other than 500 depending on the type of exception matched.
case th: Throwable =>
new Status(500).apply(Json.obj("error_message" -> th.getMessage, "error_class" -> th.getClass.getName))
}
}
Controller usage:
Note how there is no exception handling in the controller and furthermore no exception handling is required in other service classes.
def getSiblings(): Action[JsValue] = ErrRecoveryAction.async(parse.json) {
request =>
peopleService
.getSiblings(request.body.as[Person])
.map(res => Ok(Json.toJson(res)))
}
I am just new to learning Scala and the related technologies.I am coming across the problem where the loadUser should return a record but its coming empty.
I am getting the following error:
java.util.NoSuchElementException: None.get
I appreciate this is not ideal Scala, so feel free to suggest me improvements.
class MongoDataAccess extends Actor {
val message = "Hello message"
override def receive: Receive = {
case data: Payload => {
val user: Future[Option[User]] = MongoDataAccess.loadUser(data.deviceId)
val twillioApiAccess = context.actorOf(Props[TwillioApiAccess], "TwillioApiAccess")
user onComplete {
case Failure(exception) => println(exception)
case p: Try[Option[User]] => p match {
case Failure(exception) => println(exception)
case u: Try[Option[User]] => twillioApiAccess ! Action(data, u.get.get.phoneNumber, message)
}
}
}
case _ => println("received unknown message")
}
}
object MongoDataAccess extends MongoDataApi {
def connect(): Future[DefaultDB] = {
// gets an instance of the driver
val driver = new MongoDriver
val connection = driver.connection(List("192.168.99.100:32768"))
// Gets a reference to the database "sensor"
connection.database("sensor")
}
def props = Props(new MongoDataAccess)
def loadUser(deviceId: UUID): Future[Option[User]] = {
println(s"Loading user from the database with device id: $deviceId")
val query = BSONDocument("deviceId" -> deviceId.toString)
// By default, you get a Future[BSONCollection].
val collection: Future[BSONCollection] = connect().map(_.collection("profile"))
collection flatMap { x => x.find(query).one[User] }
}
}
Thanks
There is no guaranty the find-one (.one[T]) matches at least one document in your DB, so you get an Option[T].
Then it's up to you to consider (or not) that having found no document is a failure (or not); e.g.
val u: Future[User] = x.find(query).one[User].flatMap[User] {
case Some(matchingUser) => Future.successful(matchingUser)
case _ => Future.failed(new MySemanticException("No matching user found"))
}
Using .get on Option is a bad idea anyway.