is there a side effect when using directives?

36 views
Skip to first unread message

Raghavan Chockalingam

unread,
May 3, 2016, 4:13:01 PM5/3/16
to spray.io User List
I am trying to learn spray and understand the expected workings of Directives. I have read the following


but i don't know whether the following is considered a side-effect or I am using the library incorrectly.

Problem 1

val route = get {
        println("get pong")
complete("get pong")
} ~ post {
println("post pong")
complete("post")
}
Post("/ping") ~> route ~> check {
println("response: " + responseAs[String])
}

Output:
get pong
post pong
response: post

I have some experience working with Spring Mvc where we use RequestMapping for paths/methods but the idea is if a mapping is found, only code under that mapping is executed. But from the above Spray example, even though the method is POST, code under get and post directives are both executed. I do see the response only coming from post directive and I also guess List(MethodRejection(GET)) is set from executing get directive which is cleared later by post directive.

Problem 2
say, i am developing a REST Get endpoint to return a company configuration. if a company is not found, i want to return 404 saying the passed in company id is not found. say i would make this check in multiple endpoints, what is the ideal way of writing a directive without side effects.

val route1 = get {
    path
("companies" / Segment){
        companyId
=>
            onCompanyExists
(companyId) {
                println
("do not print when company does not exist")
               
val company = companyRepository.get(companyId)
                complete
(serializer.serialize(company))
           
}
   
}
}

trait CompanyRepository{
   
def exists(id:Long):Boolean
   
def get(id:Long):Company
}

can you please suggest how to define a directive with a method 'onCompanyExists' and it would somehow make sure the print statement and the following lines do not execute if the company does not exist?

Age Mooij

unread,
May 3, 2016, 4:56:09 PM5/3/16
to spray...@googlegroups.com
Hi

This is an aspect of Spray that confuses so many people that we fixed it in akka-http (i.e. Spray 2.0). 

Please read the doc page that specifically deals with this topic:

http://spray.io/documentation/1.2.3/spray-routing/advanced-topics/understanding-dsl-structure/

It will explain why you are seeing printlns where you don't expect to see them.

Hope this helps
Age


--
You received this message because you are subscribed to the Google Groups "spray.io User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to spray-user+...@googlegroups.com.
Visit this group at https://groups.google.com/group/spray-user.
To view this discussion on the web visit https://groups.google.com/d/msgid/spray-user/979d6e2c-fbf4-40de-996e-235905c0acb7%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Ryan O'Rourke

unread,
May 3, 2016, 4:59:07 PM5/3/16
to spray.io User List
Regarding your first question, consult the following documentation for enlightenment: http://spray.io/documentation/1.2.3/spray-routing/advanced-topics/understanding-dsl-structure/

The short story is that your "get pong" is actually being printed when the route is created, not when the route is run. The code inside 'complete' is being run when the route matches. Consider this example for contrast:

val route = get {
 println
("get pong")

 complete
{
 println
("got pong")
 
"get pong"

 
}
} ~ post {
 println
("post pong")

 complete
{
 println
("posted pong")
 
"post"

 
}
}
Post("/ping") ~> route ~> check {
 println
("response: " + responseAs[String])
}
Post("/ping") ~> route ~> check {
 println
("response: " + responseAs[String])
}

Output:

get pong
post pong
posted pong
response: post
posted pong
response: post

For your second question, try this:

def onCompanyExists(id: String) = validate(CompanyRepository.exists(id), s"Company $id does not exist")


Raghavan Chockalingam

unread,
May 4, 2016, 5:16:50 PM5/4/16
to spray.io User List
thanks to both of you for the explanations and for sharing the spray documentation link.

i now understand only code inside a route (RequestContext => Unit) get executed for every request if matching conditions are met. 

i also tried 'validate' which seems to not pass to inner route if predicate function fails and a rejection is generated. i want the rejection to be caught with a custom rejection handler and converted to a response with status code 400 and custom message body. but i could not get this function tested as the test framework throws rejection errors

def onCompanyExists(companyId: Long): Directive0 =
    validate(companyId > 5, "company invalid")

val route = post {
onCompanyExists(2) { // should generate rejection and not execute inner route
complete {
"good company"
}

}
}

implicit val myRejectionHandler = RejectionHandler {
case ValidationRejection("company invalid", _) :: _ =>
complete(BadRequest, "Company does not exist")

}

Post("/ping") ~> route ~> check {
    status shouldEqual BadRequest
}

Output:
Request was rejected with List(ValidationRejection(company invalid,None))

Raghavan Chockalingam

unread,
May 4, 2016, 5:22:12 PM5/4/16
to spray.io User List


This is an aspect of Spray that confuses so many people that we fixed it in akka-http (i.e. Spray 2.0). 



is there any documentation for how the fix has been implemented in akka-http (spray 2.0) or any change in implementation of key concepts? 

Ryan O'Rourke

unread,
May 4, 2016, 7:30:04 PM5/4/16
to spray.io User List
I'm pretty sure the default rejection handler will turn a ValidationRejection into a 400 (Bad Request) and probably to include the error message in the failed "validation" directive with the response. (though honestly I'm not double checking that ;) ) I think the problem with your test is that you need to seal the route in order to test an error response like that. See http://spray.io/documentation/1.2.3/spray-testkit/#sealing-routes

Raghavan Chockalingam

unread,
May 5, 2016, 5:05:06 PM5/5/16
to spray.io User List
yes sealing the route made it pick up the implicit rejection handler.

thanks.
Reply all
Reply to author
Forward
0 new messages