I've started using Play and the Play-ReactiveMongo plugin and testing for a 404 response on a GET "document by id" scenario. Unfortunately instead of Play returning a 404 NotFound response I get this exception:
java.util.NoSuchElementException: JsError.get
at play.api.libs.json.JsError.get(JsResult.scala:11) ~[play_2.10.jar:2.1.1]
at play.api.libs.json.JsError.get(JsResult.scala:10) ~[play_2.10.jar:2.1.1]
at play.modules.reactivemongo.json.collection.JSONGenericHandlers$StructureBufferWriter$.write(jsoncollection.scala:44) ~[play2-reactivemongo_2.10-0.9.jar:0.9]
at play.modules.reactivemongo.json.collection.JSONGenericHandlers$StructureBufferWriter$.write(jsoncollection.scala:42) ~[play2-reactivemongo_2.10-0.9.jar:0.9]
at reactivemongo.api.collections.GenericQueryBuilder$class.reactivemongo$api$collections$GenericQueryBuilder$$write(genericcollection.scala:323) ~[reactivemongo_2.10-0.9.jar:0.9]
at reactivemongo.api.collections.GenericQueryBuilder$class.cursor(genericcollection.scala:333) ~[reactivemongo_2.10-0.9.jar:0.9]
The getById function below successfully returns a single document if the id parameter matches an existing document, but an exception on the line "one[JsValue]" if document not found.
Route file:
GET /items/:id controllers.ItemsController.getById(id: String)
Controller object:
object ItemsController extends Controller with MongoController {
def itemsCollection: JSONCollection = db.collection[JSONCollection]("items")
def getById(id: String) = Action {
Async {
val query = Json.obj("_id" -> Json.obj("$oid" ->id))
val futureItem = itemsCollection.
find(query).
one[JsValue]
futureItem.map {
case Some(item) => Ok(item)
case None => NotFound(Json.obj("message" -> "No such item"))
}
}
}
}
Maybe I missed something in the docs?
There is partial example documented here:
https://github.com/sgodbillon/reactivemongo-demo-app#simple-query
The mandubian coast-to-coast example handles BadRequest as well NotFound scenario, but code is maybe out of date as it doesn't use the newer looking find(...).one[...] semantics?
http://mandubian.com/2013/01/13/JSON-Coast-to-Coast/#action-get
Turns out the ID needs to be a valid ObjectId, e.g. 24 characters and no illegal tokens.
If the ID is valid but does not reference an existing document then I get a 404 as expected.
If the ID format is invalid (e.g. only 12 characters or contains illegal tokens like '#') then I get an Exception.
When I compare behaviour with an equivalent Node.js + Mongoose app results are very similar.
For example if deliberately querying with a malformed 12 character ID I get this stacktrace in Node:
{ message: 'Cast to ObjectId failed for value "51bded70543f" at path "_id"',
name: 'CastError',
type: 'ObjectId',
value: '51bded70543f',
path: '_id' }
Not sure if this exception is the underlying error in the Play app too but it gave enough of a clue.
The answer would seem to be pre-validate IDs before calling find(query).one[T].
Related
I've referenced all existing threads related to the same issue and didn't find any help.
Basically, I'm creating an EntityError collection which contains some validation errors. I add EntityErrors to this collection as follows:
if (!String.IsNullOrEmpty(user.UserCode))
{
if (_usersRepo.HasUserCode(user.Id, user.UserCode, clientId))
{
entityErrors.Add(new EntityError() { EntityTypeName = entityTypeName, ErrorMessage = "UserCode already exist", ErrorName = errorName, PropertyName = "UserCode", KeyValues = new object[] { user.UserCode } });
}
}
Then I throw the collection as follows:
throw new EntityErrorsException(validationErrors);
I get the above mentioned error, only after introducing the KeyValues in to the EntityError object, without that the errors are thrown to the application and displayed correctly.
As some of the previous posts have suggested, I tried adding Manager.FetchMetaData before calling this validation, but still it didn't work.
Following are the http responses when I get the error vs when it works as expected. You can see that when the error is thrown, KeyValues has a value, and when it works KeyValues is null.
When it works:
{"$id":"1","$type":"Breeze.ContextProvider.SaveError, Breeze.ContextProvider","Message":"Exception of type 'Breeze.ContextProvider.EntityErrorsException' was thrown.","EntityErrors":[{"$id":"2","$type":"Breeze.ContextProvider.EntityError, Breeze.ContextProvider","ErrorName":"Attribute Validation Error","EntityTypeName":"Users","KeyValues":null,"PropertyName":"UserCode","ErrorMessage":"UserCode already exist"}]}
enter code here
When it errors:
{"$id":"1","$type":"Breeze.ContextProvider.SaveError, Breeze.ContextProvider","Message":"Exception of type 'Breeze.ContextProvider.EntityErrorsException' was thrown.","EntityErrors":[{"$id":"2","$type":"Breeze.ContextProvider.EntityError, Breeze.ContextProvider","ErrorName":"Attribute Validation Error","EntityTypeName":"Users","KeyValues":["782"],"PropertyName":"UserCode","ErrorMessage":"UserCode already exist"}]}
I'm wondering what's an acceptable approach to parsing JSON from third-party services considering deserialization errors.
For example, this service method:
def signInWithEmailAndPassword(email: String, password: String): Future[ApiResponse[SignInResponse]] =
request("/signin").post(Json.obj("email" -> email, "password" -> password))
.map(_.json.as[ApiResponse[SignInResponse]])
Will throw a server exception if json.as fails which play will catch in the default error handler.
Is that an OK structuring of the client? Seems like a JSON parse error is not really recoverable anyway, so uses the generic error handler is appropriate?
Here is some sample to help you get started. This is a method that you normally write in your Play framework controller.
def dispatchPowerPlant(id: Int) = Action.async(parse.tolerantJson) { request =>
request.body.validate[DispatchCommand].fold(
errors => {
Future.successful{
BadRequest(
Json.obj("status" -> "error", "message" -> JsError.toJson(errors))
)
}
},
dispatchCommand => {
actorFor(id) flatMap {
case None =>
Future.successful {
NotFound(s"HTTP 404 :: PowerPlant with ID $id not found")
}
case Some(actorRef) =>
sendCommand(actorRef, id, dispatchCommand)
}
}
)
}
So what it does is to check the validity of the JSON payload and send the response accordingly! Hope this helps!
You could probably have a similar setup to validate the JSON and return the response accordingly.
Assuming ApiResponse is going to hold any client errors (wrong password, etc) and the Future is going to hold server errors (couldn't establish connection, 500 from remote service, etc), then yes it's appropriate for the exception in the Future to bubble up to the error handler and return a 500 to your caller (also assuming there are no resources you need to clean up before returning).
I'm building a RESTful API and the endpoints I'm creating are using command objects to validate the request data. I'm trying to figure out the best way to render the validation errors as json. For xml responses I followed the recommendation in the Grails in Action book and did...
response.status = 403
render(contentType: "text/xml") {
errors {
eventSaleDataCommand.errors.fieldErrors.each { err ->
field(err.field)
message(g.message(error: err))
}
}
}
This works well for rendering xml responses so I'm wondering what the recommended approach for rendering json responses is?
I wanted to have some control of how the error gets displayed so for the json response I added:
def results = eventSaleDataCommand.errors.fieldErrors.toList()
def errors = []
for (error in results) {
errors.add([
'type' : 'invalid_entry',
'field' : error.field,
'rejected_value': error.rejectedValue,
'message' : error.defaultMessage
])
}
render errors as JSON
Problem with this approach is I'm using the Joda time plugin so I'm getting the following exception when I try to render the map as JSON:
Class org.codehaus.groovy.grails.web.converters.marshaller.json.GenericJavaBeanMarshaller can not access a member of class org.joda.time.tz.DateTimeZoneBuilder$PrecalculatedZone with modifiers "public".
Anyone know of a way around this?
How about?
render eventSaleDataCommand.errors.fieldErrors as JSON
I am attempting to retrieve the sizes various websites whose URL will be passed to my script, but I'm not getting an exception when I pass an invalid URL, instead simply getting a very small page. I'm using Source.fromURL, and I get the following results:
thisIsClearlyABoggusURLThatCantPossiblyLeadAnyway 1052
www.bbc.co.uk 113871
The first one, as it says, shouldn't have anything in it, but it does. My script is as follows:
def main( args:Array[String] ){
val tasks = for(arg <- args) yield future {
try {
println(arg + " " + Source.fromURL( attachPrefix(arg) ).length)
} catch {
case e : java.net.UnknownHostException => println(arg + " *")
}
}
awaitAll(20000L, tasks: _*)
}
def attachPrefix( url:String ) = url.slice(0, 4) match {
case "http" => url
case "www." => "http://" + url
case _ => "http://www." + url
}
Each argument is being passed into the function attachPrefix to ensure it has the necessary prefix before being used. This problem has only come about since I started passing the url in as a parameter instead of mapping it onto the arg, which is what I was doing earlier with
args map attachPrefix
What's the difference between the two, and why is my current one giving such behaviour?
You can use the Source.fromURL(URI) signature. Creating a URI will effectively validate the URL as documented here. However, in this case, the URL http://www.thisIsClearlyABoggusURLThatCantPossiblyLead‌​Anyway is valid as far as the URI is concerned. On the other hand, the UrlValidator suggested by om-nom-nom considers it invalid, because the top level domain segment has more than 4 characters which is already out of date. I don't know of any entirely Scala validation libraries or why this would be a requirement, but you could try using a regular expression for validation. For example, this will catch your example, because the top level domain exceeds 6 letters:
val re = """^(https?://)?(([\w!~*'().&=+$%-]+: )?[\w!~*'().&=+$%-]+#)?(([0-9]{1,3}\.){3}[0-9]{1,3}|([\w!~*'()-]+\.)*([\w^-][\w-]{0,61})?[\w]\.[a-z]{2,6})(:[0-9]{1,4})?((/*)|(/+[\w!~*'().;?:#&=+$,%#-]+)+/*)$""".r
re.pattern.matcher("http://google.com").matches // true
re.pattern.matcher("http://www.thisIsClearlyABoggusURLThatCantPossiblyLeadAnyway").matches // false
I'm having problem inserting (or updating) new data to database using anorm in Play Framework 2.0. My model is a blog post defined in the following code:
case class Post(title: String,
content: String,
created: String,
lastUpdate: String,
writer: Long,
id: Long = 0)
Then I do this on the insert function:
def create(title: String, content: String, userId: Long) = {
DB.withConnection { implicit connection =>
SQL("INSERT INTO post (title, content, created, writer) VALUES ({title}, {content}, NOW(), {writer})")
.on(
'title -> title,
'content -> content,
'writer -> userId).executeUpdate()
}
And the form:
val postForm = Form(
tuple(
"title" -> nonEmptyText,
"content" -> nonEmptyText))
I did not have a userId field in the form because I don't trust the user's input on their own id. I get it from session. Anyway, that's why I can't directly put the validation code in postForm's declaration (because I think I can't access session both from the form and from model). And here's when it becomes ugly. If the post is invalid, anorm throws an Exception so I need to pass the error to user AFTER the fold function like:
postForm.bindFromRequest.fold(
formWithErrors => BadRequest(views.html.post.newPost(formWithErrors)),
newPost => {
try {
val userId = request.session.get("id").getOrElse("0")
models.Post.create(newPost._1, newPost._2, userId.toLong)
} catch {
case e => BadRequest(views.html.post.newPost(postForm.fill(newPost)))
}
Redirect(routes.Application.index)
})
First, the try-catch is ugly. Second, the BadRequest call don't work. What am I doing wrong? What is the best way to handle errors in insert / update? I have the same problem with a login form, but it's not as bad as this because I can actually handle errors at login form declaration.
Thanks before.
Assuming the error is on an integrity constraint like 0 not existing in the database, what about something like this ?
postForm.bindFromRequest.fold(
formWithErrors => BadRequest(views.html.post.newPost(formWithErrors)),
newPost => {
request.session.get("id").map({ userId=>
models.Post.create(newPost._1, newPost._2, userId.toLong)
Redirect(routes.Application.index)
}).getOrElse{
BadRequest(views.html.post.newPost(postForm.fill(newPost)))
}
}
)
You don't give invalid information to anorm and don't get an error ...
ps : I don't have a scala compiler on hand to check the syntax right now, I might have missed a paren or mixed a paren with a curly :)