Can I modify http response status in completion block? - spray

Is there way to modify the http status code inside of the complete {} block? I've defined a Marshaller to work with scalaz.concurrent.Task like so:
implicit def taskMarshaller[A](implicit m: Marshaller[A]) =
Marshaller[scalaz.concurrent.Task[A]]{(task, ctx) =>
task.runAsync(_.fold(l => throw l, r => m(r, ctx)))
}
and I do
complete {
Task {...}
}
I'd like to be able to modify the http status code in the repsonse based on the results of the Task.

The key is is to use a ToResponseMarshaller for scalaz.concurrent.Task[(StatusCode, A)] as follows:
implicit def scalazTaskWithStatusMarshaller[A](implicit m: ToResponseMarshaller[(StatusCode, A)]): ToResponseMarshaller[scalaz.concurrent.Task[(StatusCode, A)]] =
ToResponseMarshaller[scalaz.concurrent.Task[(StatusCode, A)]] { (task, ctx) =>
task.runAsync(_.fold(l => throw l, r => m(r, ctx)))
}

Related

unable to understand why validateOpt will return `None` instead of `JsError`

In the following code, I am unable to understand why validateOpt might return value JsSuccess(None) instead of JsError
def getQuestion = silhouette.UserAwareAction.async{
implicit request => {
val body: AnyContent = request.body
val jsonBodyOption: Option[JsValue] = body.asJson
jsonBodyOption.map((jsonBody:JsValue) => { //body is json
val personJsonJsResultOption = jsonBody.validateOpt[Person]//check that json structure is correct
personJsonJsResultOption match {
case personSuccessOption: JsSuccess[Option[Person]] => { //json is correct
val personOption = personSuccessOption.getOrElse(None) //why would getOrElse return None??
personOption match {
case Some(person) => {
... }
case None =>{ //I am not sure when this will be triggered.
...
}
}
}
}
case e: JsError => {
...
}
}
}
})
.getOrElse(//body is not json
...)
}
}
validateOpt by design considers success to be not only when body provides actual Person but also when Person is null or not provided. Note how documentation explains why JsSuccess(None) is returned:
/**
* If this result contains `JsNull` or is undefined, returns `JsSuccess(None)`.
* Otherwise returns the result of validating as an `A` and wrapping the result in a `Some`.
*/
def validateOpt[A](implicit rds: Reads[A]): JsResult[Option[A]]
Seems like your requirement is that Person must always be provided to be considered successful, so in this case validate should be used instead of validateOpt.

Invalidate Falcor jsonGraph fragment using jsonGraphEnvelope

I'm trying to invalidate a part of my jsonGraph object via the response from the falcor-router after making a CREATE call. I can successfully do so when returning a list of pathValues, similar to this earlier SE question:
{
route: 'foldersById[{keys:ids}].folders.createSubFolder',
call(callPath, args, refPaths, thisPaths) {
return createNewFolderSomehow(...)
.subscribe(folder => {
const folderPathValue = {
path: ['foldersById', folder.parentId, 'folders', folder.parentSubFolderCount -1],
value: $ref(['foldersById', folder.id])
};
const folderCollectionLengthPathValue = {
path: ['folderList', 'length'],
invalidated: true
};
return [folderPathValue, folderCollectionLengthPathValue];
});
})
}
However, when returning the equivalent (afaik) jsonGraphEnvelope, the invalidated path is dropped from the response:
{
route: 'foldersById[{keys:ids}].folders.createSubFolder',
call(callPath, args, refPaths, thisPaths) {
return createNewFolderSomehow(...)
.subscribe(folder => {
const newFolderPath = ['foldersById', folder.parentId, 'folders', folder.parentSubFolderCount -1];
return {
jsonGraph: R.assocPath(folderPath, $ref(['foldersById', folder.id]), {})
paths: [newFolderPath],
invalidated: [['folderList', 'length']]
};
});
})
}
Am I misunderstanding how a jsonGraphEnvelope works (had assumed it was a longhand format equivalent to an array of PathValues)? Or is this likely a bug?
Looks like a bug to me.
Invalidations don't seem to be handled in the part of the code responsible for merging partial JSONGraph envelopes returned from routes into the JSONGraph envelope response (see here), while they are handled in the path-value merge (see here).
I can't find any issue about this on GitHub so I invite you to open one.

Parsing Tweets with akka-streams

Im using the following code to connect to Twitter and get Tweets, however Im not able to create JsValue. If only map with byteString.utf8String I can see the strings. But when I add framing i get an error:
Read 7925 bytes which is more than 1000 without seeing a line terminator
No matter how long i make the input im still getting this error. What do I need to change to get a stream of JsValue in my websocket?
Information about how one is expected to consume twitter streams can be found here
#Singleton
class TwitterController #Inject() (ws: WSClient, conf: Configuration) {
def tweets = WebSocket.accept[JsValue,JsValue] { implicit request =>
Flow.fromSinkAndSource(Sink.ignore, queryToSource("cat"))
}
def queryToSource(keyword: String): Source[JsValue, NotUsed] = {
val url = "https://stream.twitter.com/1.1/statuses/filter.json"
credentials.map { case (consumerKey, requestToken) =>
val request = ws.url(url)
.sign(OAuthCalculator(consumerKey, requestToken))
.withQueryString("track" -> keyword)
.withMethod("GET")
streamResponse(request)
.via(Framing.delimiter(ByteString("\r\n"), maximumFrameLength = 1000, allowTruncation = true))
.map { byteString =>
Json.parse(byteString.utf8String)
// byteString.utf8String
}
} getOrElse {
Source.single(Json.parse("Twitter credentials missing"))
}
}
private def streamResponse(request:WSRequest): Source[ByteString, NotUsed] = Source.fromFuture(request.stream()).flatMapConcat(_.body)
lazy val credentials: Option[(ConsumerKey, RequestToken)] = for {
apiKey <- conf.getString("twitter.apiKey")
apiSecret <- conf.getString("twitter.apiSecret")
token <- conf.getString("twitter.token")
tokenSecret <- conf.getString("twitter.tokenSecret")
} yield (
ConsumerKey(apiKey, apiSecret),
RequestToken(token, tokenSecret)
)
}

Connection error with Relay

// didn't add node definitions because this problem occurs on initial data fetch
// user type
const userType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: globalIdField('User'),
email: { type: GraphQLString },
posts: {
type: postConnection,
args: connectionArgs,
// getUserPosts() function is below
resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args),
},
}),
interfaces: [nodeInterface],
})
// post type
const postType = new GraphQLObjectType({
name: 'Post',
fields: () => ({
id: globalIdField('Post'),
title: { type: GraphQLString },
content: { type: GraphQLString },
}),
interfaces: [nodeInterface],
})
// connection type
const {connectionType: postConnection} =
connectionDefinitions({name: 'Post', nodeType: postType})
// Mongoose query on other file
exports.getUserPosts = (userid) => {
return new Promise((resolve, reject) => {
Post.find({'author': userid}).exec((err, res) => {
err ? reject(err) : resolve(res)
})
})
}
I get the following warning in browser console:
Server request for query App failed for the following reasons:
Cannot read property 'length' of undefined
_posts40BFVD:posts(first:10) {
^^^
That's the only information I got, there's no more errors or references. What could be the reason?
This code is from relay-starter-kit, I only replaced all the Widget code with Post. Everything is almost the same as in starter, therefore I think the cause is somewhere around the database code.
But I can't see the problem because getUserPosts() returns same structure: array of objects..
What was the problem?
resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args)
getUserPosts() returned a promise (blocking code would probably be a bad idea) but there was no callback. What happened in my opinion was that connectionFromArray() continued executing the code but it didn't have the data from getUserPosts() yet, which caused the whole system to fail.
One possible solution
I use Babel and JavaScript "future features" anyway, therefore I decided to use async and await. But there still was a problem and getUserPosts() returned an empty array.
Then I discovered that if another function is called with await in a async function, that another function has to be async as well, otherwise all await-s fail. Here's my final solution that works:
// async function that makes db query
exports.getUserPosts = async (userId) => {
try {
// db query
const posts = await Post.find({author: args}).exec()
return posts
} catch (err) {
return err
}
}
// and resolve method in Schema, also async
resolve: async (user, args) => {
const posts = await getUserPosts(user._id)
return connectionFromArray(posts, args)
}
Im not still sure though if it's the best way. My logic tells me that I should use async as much as possible in Node but Im far from Node expert. I'll update the question when I know more about it.
I would be glad to know if there's a better or even a recommended way to deal with this database query situation using Relay.
The problem is with the resolve function of posts field.
resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args),
First, getUserPosts is an asynchronous function which returns a promise. You have to take that into account while writing resolve function. Second, user.id is a global ID field generated for you by graphql-relay module's globalIdField helper function. That ID should be converted to local ID.
With relay-starter-kit, you can use async and await. The code will look like:
resolve: async (user, args) => {
let userId = fromGlobalId(user.id).id;
// convert this userId string to Mongo ID if needed
// userId = mongoose.Types.ObjectId(userId).
const posts = await getUserPosts(userId);
return connectionFromArray(posts, args),
},
In your async version of getUserPosts, the err object is returned in case of error.
exports.getUserPosts = async (userId) => {
try {
// db query
const posts = await Post.find({author: args}).exec()
return posts
} catch (err) {
return err
}
}
A good practice is to either re-throw the error or return an empty array.
graphql-relay provides a connectionFromPromisedArray function which waits until the promise resolves. resolve: (user, args) => connectionFromPromisedArray(getUserPosts(user.id), args), which probably is the most recommended/easiest way when dealing with promised connections.
If your user.id is a global ID field then you have to extract the real DB ID from the base64 string const { type, id } = fromGlobalId(user.id);.
or
resolve: (user, args) => connectionFromPromisedArray(getUserPosts(fromGlobalId(user.id).id), args),
In a app I work on which is using Relay and Mongoose I found it better to write my own connection implementation to handle pagination and filtering on the DB level rather than on the app level. I took a lot of inspiration from graffiti-mongoose when I wrote it.

How to set status code and headers in spray-routing based on a future result

I'm using spray-routing with Akka to define a route like
def items = path("items") {
get {
complete {
actor.ask(GetItems)(requestTimeout).mapTo[Either[NoChange, Items]] map {
result => result match {
case Left(_) => StatusCodes.NotModified
case Right(items) =>
// here I want to set an HTTP Response header based on a
// field within items -- items.revision
items
}
}
}
}
}
The actor.ask returns a Future that gets mapped to a Future[Either[NoChange, Items]]. "complete" is happy to deal with the Future[StatusCodes...] or the Future[Items] but I'm not sure how to set an HTTP Response header within the Future.
If the header weren't being set within the Future then I could just wrap the complete in a directive but how do I set a header within the complete?
I'm using Spray 1.2.0.
Thanks for any pointers in the right direction!
If you are trying to do this inside of complete all branches of the expression inside must result in a type that can be marshalled by complete.
You could try a structure like this to make it work:
complete {
actor.ask(GetItems)(requestTimeout).mapTo[Either[NoChange, Items]] map {
result => result match {
case Left(_) => StatusCodes.NotModified: ToResponseMarshallable
case Right(items) =>
// here I want to set an HTTP Response header based on a
// field within items -- items.revision
val headers = // items...
HttpResponse(..., headers = headers): ToResponseMarshallable
}
}
}
This ensures that the type of the expression you pass to complete is Future[ToResponseMarshallable] which should always be marshallable.
A better way, though, is to use the onSuccess directive that lets you use other directives after a future was completed:
get {
def getResult() = actor.ask(GetItems)(requestTimeout).mapTo[Either[NoChange, Items]]
onSuccess(getResult()) {
case Left(_) => complete(StatusCodes.NotModified)
case Right(items) =>
// do whatever you want, e.g.
val extraHeaders = // items.revisions
respondWithHeaders(extraHeaders) {
complete(...)
}
}
}

Resources