I am a play2.0-Scala-beginner and have to call several Webservices to generate a HTML page.
After reading the The Play WS API page and a very interesting article from Sadek Drobi I am still unsure what's the best way to accomplish this.
The article shows some code snippets which I don't fully understand as a Play beginner.
Figure 2 on page 4:
val response: Either[Response,Response] =
WS.url("http://someservice.com/post/123/comments").focusOnOk
val responseOrUndesired: Either[Result,Response] = response.left.map {
case Status(4,0,4) => NotFound
case Status(4,0,3) => NotAuthorized
case _ => InternalServerError
}
val comments: Either[Result,List[Comment]] =
responseOrUndesired.right.map(r => r.json.as[List[Comment]])
// in the controller
comment.fold(identity, cs => Ok(html.showComments(cs)))
What does the last line with the fold do? Should comment be comments? Haven't I group the last statement in an Async block?
Figure 4 shows how to combine several IO calls with a single for-expression:
for {
profile <- profilePromise
events <- attachedEventsPromise
articles <- topArticlesPromise
} yield Json.obj(
"profile" -> profile,
"events" -> events,
"articles" -> articles )
}
// in the controller
def showInfo(...) = Action { rq =>
Async {
actorInfo(...).map(info => Ok(info))
}
}
How can I use this snippet? (I am a bit confused by the extra-} after the for-expression.)
Should I write something like this?
var actorInfo = for { // Model
profile <- profilePromise
events <- attachedEventsPromise
articles <- topArticlesPromise
} yield Json.obj(
"profile" -> profile,
"events" -> events,
"articles" -> articles )
def showInfo = Action { rq => // Controller
Async {
actorInfo.map(info => Ok(info))
}
}
What's the best way to combine the snippets from figure 2 and 4 (error handling + composition of IO non-blocking calls)? (f.ex. I want to produce a Error 404 status code if any of the called webservice produce an Error 404).
Maybe someone knows a complete example of calling webservices in the play framework (cannot find an example in the play Sample applications or anywhere else).
I have to say that the article is wrong in the example you show in Figure 2. The method focusOnOk does not exist in Play 2.0. I assume the author of the article used a pre-release version of Play 2 then.
Regarding comment, yes it should be comments. The fold in the statement is operating on an Either. It takes 2 functions as parameters. The first is a function to apply if it is a left value. The second is a function to apply if it is a right value. A more detailed explanation can be found here: http://daily-scala.blogspot.com/2009/11/either.html
So what the line does is. If I have a left value (which meant I got an undesired response), apply the built-in identity function which just gives you back the value. If it has a right value (which means I got an OK response), make a new result that shows the comments somehow.
Regarding Async, it's not actually asynchronous. focusOnOk is a blocking function (a remnant from the old Java days of Play 1.x). But remember, that's not valid Play 2 code.
As for Figure 4, the trailing } is actually because it's a partial alternative of what's in Figure 3. Instead of the numerous promise flatMaps. You can do a for comprehension instead. Also, I think it should be userInfo(...).map instead of actorInfo(...).map.
The Play documentation you linked to actually already shows you a full example.
def feedTitle(feedUrl: String) = Action {
Async {
WS.url(feedUrl).get().map { response =>
Ok("Feed title: " + (response.json \ "title").as[String])
}
}
}
will get whatever is at feedUrl, and you map it to do something with the response which has a status field you can check to see if it was a 404 or something else.
To that end, the Figure 3 and 4 of your linked article should give you a starting point. So you'd have something like,
def getInfo(...) : Promise[String] = {
val profilePromise = WS.url(...).get()
val attachedEventsPromise = WS.url(...).get()
val topArticlesPromise = WS.url(...).get()
for {
profile <- profilePromise
events <- attachedEventsPromise
articles <- topArticlesPromise
} yield {
// or return whatever you want
// remember to change String to something else in the return type
profile.name
}
}
def showInfo(...) = Action { rq =>
Async {
getInfo(...).map { info =>
// convert your info to a Result
Ok(info)
}
}
}
Related
I'm trying to convert an existing Alexa app to Google Actions wherein I need to implement session and persistent data values. My understanding from https://developers.google.com/assistant/conversational/df-asdk/save-data is that conv.data and conv.user.storage are intended for this purpose. However, making any attempt to assign values to either results in the error "Error: Unauthorized, Your client does not have permission to the requested URL", and also a reference to the offending key which points to this in the firebase console log: https://us-central1-hello-world-e37ec.cloudfunctions.net/cf-p7ROQlBMjQId9Cws6XdJBA-name. Similar issues here in stackoverflow seem to indicate that I need to grant the appropriate function to all users, but I don't know which function is being called. I'm new to Google Actions, so apologies if I'm overlooking something obvious. Code is very similar to the example offered on google's doc.
const {conversation} = require('#assistant/conversation');
const functions = require('firebase-functions');
const app = conversation();
...
app.handle('status', async conv => {
conv.overwrite = false;
if (conv.user.verificationStatus === 'VERIFIED') {
conv.user.storage = {};
conv.user.storage.sum = 69;
conv.add(`Alright, I'll store that for next time. See you then.`);
} else {
conv.add(`I can't save that right now, but we can add ` +
`new numbers next time!`);
}
});
I found the answer for this issue. Appears that I was not in the correct area of documentation for the "conversation" object/app. Correct method is described here: https://developers.google.com/assistant/conversational/webhooks#read_and_write_storage.
Using my example
app.handle('status', async conv => {
conv.overwrite = false;
if (conv.user.verificationStatus === 'VERIFIED') {
conv.session.params.sum = 69; //within session
conv.user.params.sum = 100; //across sessions
conv.add(`Alright, I'll store that for next time. See you then.`);
} else {
conv.add(`I can't save that right now, but we can add ` +
`new numbers next time!`);
}
});
For analytic purposes I'd like to keep track on the client side of all the graphql operations (including ie #client ones). I was unable to find appropriate options in the API and wonder if this may be doable on the apollo-client level or may I need to introduce some proxy to intercept the calls by my own?
A custom Apollo link is a way to go.
You can use apollo-link-logger in particular to log all operations to console.
Usage (from docs):
import apolloLogger from 'apollo-link-logger';
// ...
ApolloLink.from([
apolloLogger,
// ...
]);
Note: Place apolloLogger before other links.
Output example:
As the answer from Yuriy was exactly what I was looking for I marked is as accepted answer - Thanks!
Still for the record here is the code doing a job for me - I believe someone may find it useful, also it is worth to show it's simplicity.
It's worth noting that Apollo links are chainable - thus the argument to a link function are operation: Operation and forward: NextLink which is supposed to be called from our link implementation.
let analytics: Analytics; // this is Fabric.io Analytics to be provided by DI
const analyticsLink = new ApolloLink((
operation: Operation,
forward?: NextLink
) => {
const operationType = operation.query.definitions[0].operation;
return forward(operation)
.map((result: FetchResult) => {
try {
analytics.sendCustomEvent(`${operationType}.${operation.operationName}`);
} catch (e) {
console.error('analytics error', e);
}
return result;
});
});
as a bonus we can also catch errors (i.e. to leverage fabric.io crashlytics) by using apollo-link-error (handling of errors in Apollo is a bit more complex);
const analyticsErrorLink = onError((error: ErrorResponse) => {
try {
// it's worth to rethink what we wanna log here
const message = error.graphQLErrors ? error.graphQLErrors[0].message :
(error.networkError.name + ': ' + error.networkError.message);
analytics.sendNonFatalCrash('GraphQL error: ' + message);
} catch(e) {
console.error('cannot report error to analytics', e);
}
});
Finally to compose the links we should put our intercepting implementations at the beginning so we will be able to catch all the GraphQL operations including those marked with #client which are not reaching network link - in my case full link looks like:
ApolloLink.from([
analyticsErrorLink,
analyticsLink,
stateLink,
auth,
http])
I'm playing around with peekAll(), trying to understand how it works for the ultimate purpose of iterating through the results.
In a route's model hook, I have:
var peekAllResults = this.store.peekAll('position');
console.log("peekAllResults = ", peekAllResults);
var peekAllResultsContent = peekAllResults.get('content');
console.log("peekAlLresultsContent = ", peekAllResultsContent);
This is returning data, as expected based on what I've got in my app.
In particular, here's what shows in the console:
So far so good. There are 8 records as expected based on what I've got going on.
But when when I add:
console.log("peekAllResultsContent.length=", peekAllResultsContent.length)
I get: peekAllResultsContent.length = 0
Same thing if I do peekAllResultsContent.get("length")
What is going on there?
I thought peekAll was a synchronous call that returned an array. Is there some trick to cracking it open and seeing what's actually in the array? I can't even get the length, so I figure I'm not on the right track.
Everything is wrapped into Ember.Model objects so you won't see clear results from console.log.
But there is no magic behind it. If the entities are already loaded into store you can get them via peekAll.
const positions = this.get('store').peekAll('position');
console.log('positions length', positions.get('length');
//we can iterate over them:
positions.forEach(position => {
console.log(position.get('name'));
};
//we can filter them:
const southOnlyPositions = positions.filter(position => position.get('direction') === 'south');
and so on...
Btw: even for promises you are not supposed to access content. You get the result like this:
const promises = this.get('store').findAll('position');
promises.then(positions => {
// positions here behave same as before
});
I am making a WS call to a service that returns a list of a users connections. After I have received the response I do a foreach on the list and in the foreach I make a WS call to another service to get more details for each connection.
Currently I am trying to use a ListBuffer but due to the async nature of the calls it is being returned empty before the details have been gathered.
My code is as below which returns an empty List to my controller:
def getAllConnections(username: String) = {
connectionsConnector.getAllConnections(username).map {
connections =>
val connectionsList: ListBuffer[ConnectionsResponse] = ListBuffer()
connections.map {
connection =>
usersService.getUser(connection.connectionUsername).foreach {
case Some(user) =>
val blah = ConnectionsResponse(user, connection)
connectionsList.+=(blah)
}
}
connectionsList.toList
}
}
Any suggestions on how I can return a Future[List] to my controller would be great, thanks.
for {
connections <- connectionsConnector.getAllConnections(username)
usersWithConnection <- Future.traverse(connections){ c => userService.getUser(c.connectionUsername).map(u => (u,c)) }
} yield usersWithConnection.collect{ case (Some(user), conn) => ConnectionsResponse(user, conn)}
Should give you some ideas at least. We can use a for comprehension in the context of a future. Future.traverse will turn a list of futures into a future of a list. Needing to return the connection along with the user adds an extra complication but we can just map over the individual future to include the connection with the user.
Use the monadic for loop:
def getAllConnections(username: String) = connectionsConnector.getAllConnections(username) map { connections ->
for {
connection <- connections
user <- usersService.getUser(connection.connectionUsername)
}
yield ConnectionsResponse(user, connection)
}
I had to guess the exact types you're using so this may need to be adapted, but something very similar to the above should solve your problem.
The outer map maps the original future, and since the first generator of the for comprehension is a list, the result will also be a list.
I never expected that I will need to ask a question on this site because everything is already answered normally but with Scalatra... I haven't find a lot of information so here it is:
I'm not experienced with all that so maybe I'm missing something but from what I understand, if I want to test the API that I develop on Scalatra, I need to start the server everytime I run the test suit, right ?
Second question, how can I reset the invocation counter on a method so I don't have to calculate how many times the method has been called since the beginning of the test suite ? Right now using this give me more than one because it counts the previous test.
there was one(*instance*).*method*(*parameter*)
I can still get around the problem by either counting or putting the test as first test for now but it's not a sustainable solution...
Other thing that I found:
Reset method on the mock... not found
http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html#17
Isolating the test in a class scope:
We need to add
val servlet = new Servlet(eventRepoMock)
addServlet(servlet, "/*")
and we can't repeat the addServlet at every initialization
https://etorreborre.github.io/specs2/guide/SPECS2-3.5/org.specs2.guide.Isolation.html
Last thing that I try is:
servlet.repo = mock[EventRepo]
but repo being a value, I can't change it like this.
Neither of these "solutions" feel very clean so I was wondering if someone had a genius idea that can solve that mess !?
Thank you in advance !
EDIT:
Thanks to Eric's comment the above question are solve(that was easy) but now I have problem because I'm testing the get/post which are asynchronous call so resetting the mock does not happen at the right time... Any suggestion ?
Here's a simplified version of the code:
class EventServiceSpec extends ScalatraSpec with Mockito with Before { def is = s2"""
Event Service
GET an existing event
must return status 200 $get_status200
must return the event with id = :id $get_rightEventElement
must call findById once on the event repository $get_findByIdOnRepo
"""
lazy val anEvent = Event(1, "Some Event"
lazy val eventsBaseUrl = "/events"
lazy val existingEventUrl = s"$eventsBaseUrl/${anEvent.id}"
lazy val eventRepoMock = mock[EventRepository]
lazy val servlet = new Servlet(eventRepoMock)
addServlet(servlet, "/*")
def before = {
eventRepoMock.findById(anEvent.id) returns Option(anEvent)
eventRepoMock.findById(unexistingId) returns None
eventRepoMock.save(anEvent) returns Option(anEvent)
}
def get_status200 = get(existingEventUrl){
status must_== 200
}
def get_findByIdOnRepo = get(existingEventUrl){
// TODO count considering previous test... need to find a cleaner way
there was three(eventRepoMock).findById(anEvent.id)
}
All org.mockito.Mockito functions can still be used in a specs2 specification and reset is one of them.
Now, since you are sharing the state of a mock object across several examples, you not only need to reset the mock state before each example but you also need to make your specification sequential:
class EventServiceSpec extends ScalatraSpec with Mockito
with BeforeAll with BeforeEach {
def is = sequential ^ s2"""
Event Service
GET an existing event
must return status 200 $get_status200
must return the event with id = :id $get_rightEventElement
must call findById once on the event repository $get_findByIdOnRepo
"""
lazy val anEvent = Event(1, "Some Event")
lazy val eventsBaseUrl = "/events"
lazy val existingEventUrl = s"$eventsBaseUrl/${anEvent.id}"
lazy val eventRepoMock = mock[EventRepository]
lazy val servlet = new Servlet(eventRepoMock)
def beforeAll = addServlet(servlet, "/*")
def before = {
reset(eventRepoMock)
eventRepoMock.findById(anEvent.id) returns Option(anEvent)
eventRepoMock.findById(unexistingId) returns None
eventRepoMock.save(anEvent) returns Option(anEvent)
}
def get_status200 = get(existingEventUrl){
status must_== 200
}
def get_findByIdOnRepo = get(existingEventUrl){
there was one(eventRepoMock).findById(anEvent.id)
}
}