Optional params with Play 2 and Swagger - scala

I'm trying to use Swagger to document a Play 2 REST API but swagger-play2 doesn't seem to understand optional parameters defined with Scala's Option type - the normal way to make a param optional in Play 2:
GET /documents controllers.DocumentController.getDocuments(q: Option[String])
I want the q param to be optional. There is a matching annotated controller method with this Option[String] param. On startup I'm getting UNKOWN TYPE in the log and the json produced by api-docs breaks swagger-ui:
UNKNOWN TYPE: scala.Option
[info] play - Application started (Dev)
Is there another way to specify an optional parameter in Play 2 and have Swagger understand it?

One workaround I've found so far is to remove the param from the params list, use Swagger's #ApiImplicitParams annotation and grab the param from the request object in your controller method. Swagger will then consider the param to be optional.
GET /documents controllers.DocumentController.getDocuments()
and then in the controller:
#ApiOperation(...)
#ApiImplicitParams(Array(
new ApiImplicitParam(name = "q", value = "Query", required = false, dataType = "string", paramType = "query"),
))
def getDocuments = Action { implicit request =>
// use param via request object
}
This is certainly not as nice as using Scala's Option type but it produces correct Swagger docs.

I've worked around this similarly to #Tom Wadley's answer.
This code creates the problem:
#ApiOperation( ... )
def foo(#ApiParam(value="Argument 1") #PathParam("a1") a1 : Option[Int]) = ...
To avoid the problem just remove the annotations from the argument, and instead declare an implicit parameter with the same name:
#ApiOperation( ... )
#ApiImplicitParams(Array(new ApiImplicitParam(name="a1", dataType="Int", required=false, paramType="query", ...)
def foo(a1 : Option[Int]) = ...
(Scala 2.11.2, Play 2.3, Swagger 1.3.8)
I logged Issue 706 against Swagger too.

Alternatively you can use this lib
https://github.com/iheartradio/play-swagger
This library takes a different approach than annotation (which force you into learning a new API), you write swagger spec directly in your routes file as comments. It automatically generates parameters definition based on routes file and for Option[T] typed parameters it automatically mark them as required=false.

The APIImplicitParam workaround was not working for me.
Another workaround is to omit the option param from the routes
GET /documents controllers.DocumentController.getDocuments()
But grab it in the code:
val qSeq = request.queryString.get("q")
val q = qSeq match {
case None => None
case Some(seq) => seq.headOption
}
and annotate it with ApiImplicitParam for Swagger docs

Related

API return writeable

I'm trying to convert a few endpoints I have to use concurrency. I have the following method for the controller:
Original method
def getHistory(id:String) = Action {
val response = historyService.getPersonHistory(id)
Ok(write(response)) as "application/json"
}
New Method
def getHistory(id:String) = Action.async {
val response = scala.concurrent.Future {
historyService.getPersonHistory(id)
}
response.map(i => Ok(i))
}
So, when we try this with a simple example process (not calling another method, but just calculating an Int) it seems to work. In the new version above, I keep getting the error:
"No implicits found for parameter writable: Writeable[historyResponse]
Cannot write an instance of models.HistoryResponse to HTTP response. Try to define a Writeable[models.HistoryResponse]"
I'm new to Scala, and having difficulty finding information on making writeables. What do I need to be able to return the results as before?
Thanks
You need to define an implicit val tjs: Writes[HistoryResponse] or even better, implicit val format: Format[HistoryResponse] = Json.format[HistoryResponse] in the companion object for HistoryResponse, so that play can auto convert your data to json. by the way, not a good name for i in the map function, something like "history" would be better instead of "i".

Cannot mock WSRequest.post() using scalamock

I am writing unit tests for Play application using Scalamock and Scalatest.
My original code looks like:
// Here ws is an injected WSClient
val req = Json.toJson(someRequestObject)
val resp: Future[WSResponse] = ws.url(remoteURL).post(Json.toJson(req))
In a part I have to mock external calls to a web service, which I am trying to do using scalamock:
ws = stub[WSClient]
wsReq = stub[WSRequest]
wsResp = stub[WSResponse]
ws.url _ when(*) returns wsReq
wsReq.withRequestTimeout _ when(*) returns wsReq
(wsReq.post (_: java.io.File)).when(*) returns Future(wsResp)
I am successfully able to mock post requests using a file, but I cannot mock post requests using JSON.
I tried putting stub function references separately like:
val f: StubFunction1[java.io.File, Future[WSResponse]] = wsReq.post (_: java.io.File)
val j: StubFunction1[JsValue, Future[WSResponse]] = wsReq.post(_: JsValue)
I get the compile error for second line: Unable to resolve overloaded method post
What am I missing here? Why cannot I mock one overloaded method but not the other one?
play.api.libs.ws.WSRequest has two post methods (https://www.playframework.com/documentation/2.4.x/api/scala/index.html#play.api.libs.ws.WSRequest), taking:
File
T (where T has an implicit bounds on Writeable)
The compiler is failing because you are trying to calling post with a single parameter, which only matches version 1. However, JsValue cannot be substituted with File.
You actually want to call the 2nd version, but this is a curried method that takes two sets of parameters (albeit the 2nd are implicit). Therefore you need to explicitly provide the mock value that you expect for the implicit, i.e.
val j: StubFunction1[JsValue, Future[WSResponse]] = wsReq.post(_: JsValue)(implicitly[Writeable[JsValue]])
Therefore a working solution would be:
(wsReq.post(_)(_)).when(*) returns Future(wsResp)
Old answer:
WSRequest provides 4 overloads of post method (https://www.playframework.com/documentation/2.5.8/api/java/play/libs/ws/WSRequest.html), taking:
String
JsonNode
InputStream
File
You can mock with a File because it matches overload 4, but JsValue does not match (this is part of the Play JSON model, whereas JsonNode is part of the Jackson JSON model). If you convert to a String or JsonNode, then it will resolve the correct overload and compile.
My best guess is that your WSRequest is actually a play.libs.ws.WSRequest which is part of the Java API, instead you should use play.api.libs.ws.WSRequest which is the Scala API.
The method WSRequest.post exists and BodyWritable[JsValue] is implicitly provided by WSBodyWritables in the Scala API but not in the Java API.
Another cause could be that your JsValue is not a play.api.libs.json.JsValue but something else (e.g. spray.json.JsValue).
I'll quote an example where I have successfully achieved what you are trying to do, the main difference is that I used mock instead of stub.
The important part is:
val ws = mock[WSClient]
val responseBody = "{...}"
...
"availableBooks" should {
"retrieve available books" in {
val expectedBooks = "BTC_DASH ETH_DASH USDT_LTC BNB_LTC".split(" ").map(Book.fromString).map(_.get).toList
val request = mock[WSRequest]
val response = mock[WSResponse]
val json = Json.parse(responseBody)
when(ws.url(anyString)).thenReturn(request)
when(response.status).thenReturn(200)
when(response.json).thenReturn(json)
when(request.get()).thenReturn(Future.successful(response))
whenReady(service.availableBooks()) { books =>
books.size mustEqual expectedBooks.size
books.sortBy(_.string) mustEqual expectedBooks.sortBy(_.string)
}
}
}
An you could see the complete test at: BinanceServiceSpec
I guess it should work fine, if you mock a response that is JsValue.
when(wsReq.post(Json.parse("""{...json request...}"""))).thenReturn(Future(wsResp))
Here Json.parse returns JsValue. Yo should pass the json string that you expect in the request body.

How to get object from Play cache (scala)

How to get object from Play cache (scala)
Code to set:
play.api.cache.Cache.set("mykey98", new Product(98), 0)
Code to get:
val product1: Option[Any] = play.api.cache.Cache.get("mykey98")
I get Option object. How to get actual Product object I stored in first step.
First and foremost, I would suggest using Cache.getAs, which takes a type parameter. That way you won't be stuck with Option[Any]. There are a few ways you can do this. In my example, I'll use String, but it will work the same with any other class. My preferred way is by pattern matching:
import play.api.cache.Cache
Cache.set("mykey", "cached string", 0)
val myString:String = Cache.getAs[String]("mykey") match {
case Some(string) => string
case None => SomeOtherClass.getNewString() // or other code to handle an expired key
}
This example is a bit over-simplified for pattern matching, but I think its a nicer method when needing to branch code based on the existence of a key. You could also use Cache.getOrElse:
val myString:String = Cache.getOrElse[String]("mykey") {
SomeOtherClass.getNewString()
}
In your specific case, replace String with Product, then change the code to handle what will happen if the key does not exist (such as setting a default key).

Generic Form processing in Play! Framework: is this -crazy- even feasible?

I'm trying to create a generic database insertion method in Scala using the Slick and Play! frameworks. This involves passing in a generic Form instance and the model object that it's associated to. There are two problems I'm running into that are driving me nuts at the moment:
How do I instantiate a generic type?
How do I dynamically generate the parameters of that generic type from generic form binding?
Code so far
/**
* Template method for accessing a database that abstracts out
* Database.forDataSource(DB.getDataSource()) withSession {}
* and actual form vals; will allow for anonymous Form declarations.
*/
def dbAccessThatUsesAForm[I <: AnyRef, T <: Table[I]](
f: Form[Product], // a Form of some generic tuple or mapping
t: T, // a Slick table to insert I objects into
i: Class[I] // the actual class that I belongs to (maybe not needed)
)(
request: SecuredRequest[AnyContent] // this is a SecureSocial auth. thing
): Boolean = {
f.bindFromRequest((request.request.body.asFormUrlEncoded).getOrElse(Map())).fold(
errors => {
logger.error(t.toString + "was not bound to " + t.toString + "'s Form correctly")
false
},
success => {
t.insert(new I(someParamsThatIHaveNoIdeaWhereToStart)) // DOES NOT WORK
true
}
)
}
On one
type not found: I
At this point I think I'm deeply misunderstanding something about Scala generics and considering using dependency injection as a solution. Maybe pass in a function that binds a class to this method, and call that within my method? But I already have an Injector defined in Global.scala that follows this post... Dependency injection here is based on a module... but injection here isn't based on whether I'm in production or in testing...
On two
Play Forms can take tuples for their field mappings. So I tried looking up how to describe generic tuple types . Therefore I surmised that I'd be passing in a Form[Product] (API for Product and Form) instead of Form[_], and doing something like:
(the thing in the for loop won't work because productArity isn't actually part of mapping )
for( i = 1 to f.mapping.productArity ) { // pretty sure this won't work.
// generate the parameters and store them to some val by calling smth like
val store Params += success.productElem(i)
}
As you can see, I'm quite lost as to
how to get the number of fields in a Form because a Form is actually composed of a Seq[Mapping]
and
how in the world I'd store dynamically generated parameters.
Do constructors receive parameters as a tuple? Is there a sort of Parameter object I could pass in to a generic class instantiator?

Is it possible to get the parameter names of a method in scala

As far as I know, we can't do this in Java. Can we do this in scala?
Suppose there is a method as following:
def insert(name:String, age:Int) {
// insert new user
}
Is it possible to get the parameter names name and age in scala?
UPDATE
I want to do this, because I want to bind the parameters of methods automaticlly.
For example, this is a web app, it has some actions defined as:
class UsersController extends Controller {
def create(name: String, age: Int) {
// insert the user
}
}
A client clicked the submit button of a creating-user form. The url will be /users/create and with some parameters sending.
On the server side, when we get a url named /users/create, we will find a method create in the controller UsersController, found one now. Then I have to get the parameter names of that method, then we can get the values of them:
val params = getParamsFromRequest()
val method = findMethodFromUrl("/users/create")
val paramNames = getParamNamesOfMethod(method)
val paramValues = getValues(params, paramNames)
// then invoke
method.invoke(controller, paramValues)
Now, the key is how to get the parameter names of a method?
It's still very much a work in progress, but you should be able to:
import scalaj.reflect._
for {
clazz <- Mirror.ofClass[UsersController].toSeq
method <- clazz.allDefs.find(_.name == "insert").toSeq
params <- method.flatParams
} yield params
Sorry about the toSeqs, they're to work around a known issue using Options in a for-comprehension.
Please don't push it too hard, it's still very young and fragile. I'm certainly not at the stage yet where I'd accept bug reports :)
UPDATE
Well... I know that I said I'm not accepting bug reports, but this one seemed so useful that I've pushed it up to github anyway.
To do the job dynamically (from a String class name):
import scalaj.reflect._
for {
clazz <- Option(Class forName "x.y.UsersController")
mirror <- Mirror.ofClass(clazz).toSeq
method <- mirror.allDefs.find(_.name == "insert").toSeq
params <- method.flatParams
} yield params
This question has some insight on how it is done in Java: Is there a way to obtain names of method parameters in Java?
Why not just using different method names (to reflect the parameters that may be passed)? Like, craeteWithNameAndAgeAndGender (pretty standard approach). You won't anyways be able to have multiple methods with the same names(/arity/parameter types and order) and just different parameter names - method overloading doesn't work this way.