Is there an easy way to make GORM errors restful api friendly - json

I'm working on a simple Restful API in GRAILS, I want users to be able to create an entry on one of my domain classes, so they can hit an entry point /rest/v1/create/event?params
In the receiving controller if the GORM entry fails, !event.save()
I have code like this:
def result = [
'status' : 'error',
'data' : event.errors.fieldErrors.toList()
]
render result as JSON
Is there a way to easily make event.errors.fieldErrors JSON friendly, something with just the field error and message, or will I have to write a parser method to handle this?

Ending up writing a short method to parse through and make friendly errors
If anyone finds this useful, here it is:
def gorm_errors(results) {
results = results.fieldErrors.toList()
def errors = []
for(error in results) {
errors.add([
'type' : 'invalid_entry',
'field' : error.field,
'rejected_value' : error.rejectedValue,
'message' : error.defaultMessage
])
}
return errors
}

Here is a more "groovy-er" version of the above example:
def gorm_errors(errors) {
errors.fieldErrors.toList().collect {error ->
[
'type': 'invalid_entry',
'field': error.field,
'rejected_value': error.rejectedValue,
'message': error.defaultMessage
]
}

Related

Using Laravel, is there a way to run validation on one ajax call with data for multiple models?

Assuming one were to post multiple data sets of one model at the time through JSON, it is possible to insert these using Eloquent's Model::create() function. However in my case I'll also need to validate this data.
The Validator only takes a Request object as input, and as far as I've seen I can't create a new Request instance with only one model.
Assuming this would be the input data (JSON), and index is the value for the browser to know what data belongs to an what item (as they have no unique ID assigned at the point of creation)
[
{
"index" : 1,
"name" : "Item 1",
"value" : "Some description"
},
{
"index" : 2,
"name" : "Item 2",
"value" : "Something to describe item 2"
},
(and so on)
]
Every object in the root array needs to be ran through the same validator. The rules of it are defined in Model::$rules (public static array).
Would there be a way to run the validator against every item, and possibly capture the errors per item?
You can utilize Validator for manual validation:
...
use Validator;
...
$validator = Validator::make(
json_decode($data, true), // where $data contains your JSON data string
[
// List your rules here using wildcard syntax.
'*.index' => 'required|integer',
'*.name' => 'required|min:2',
...
],
[
// Array of messages for validation errors.
...
],
[
// Array of attribute titles for validation errors.
...
]
);
if ($validator->fails()) {
// Validation failed.
// $validator->errors() will return MessageBag with what went wrong.
...
}
You can read more about validating arrays here.

Ignoring => when converting hash in ruby? How to properly convert hash?

I'm sending a post request, and this is the payload:
data = {updates: [{action: "create", user_id: "2", type: "view"}]}
but when I send it, the API says it was expecting:
{ action: \"create\", user_id: \"id\", type: \"type\" }
but it getting:
{:action=\u003e\\\"create\\\", :user_id=\u003e2\\\"2\\\", :type=\u003e\\\"view\\\"}\
The => is getting converted. I tried using to_json but that didn't help me.
How do I convert this properly? I think it has something to do with the nested array/hash because all the others work fine.
Also, I'm setting my request body and sending like so:
request.set_form_data(body)
https.request(request)
Looks like I need to use this syntax in order to pass set_form_data a nested hash:
{'a[b]': 'c'} is the same as {'a' => {'b' => 'c'}}
but is there a way to include the array? I need to have:
data = {updates: [{action: "create", user_id: "2", type: "view"}]}
in this notation.
As you mentioned, set_form_data does not accept arrays. It looks like a workaround if you wanted to continue using the Net::HTTP library is to encode the data and pass it to request.body.
request.body = "updates[][action]=create&updates[][type]=view&updates[][user_id]=2"
request.content_type = 'application/x-www-form-urlencoded'
You could also create a custom function like they did in this post: http://cloudlines.tumblr.com/post/653645115/postput-arrays-with-ruby-nethttp-setformdata
Check out this post on changing complex objects into a querystring: https://coderwall.com/p/uh8kiw/pass-arrays-objects-via-querystring-the-rack-rails-way.
Hope that helps!

Laravel 5.4 won't validate JSON

I'm using Laravel 5.4 and trying to validate JSON in my POST request however the validator fails stating that the JSON isn't valid, even though it is. I'm assuming I'm not understanding the validation rules correctly and my implementation is wrong, rather than a bug or something else.
I have a simple POST endpoint which has both the Accept and Content-Type headers set to application/json.
In my POST request (testing using Postman) I'm supplying RAW data.
{
"only_this_key": { "one": "two" }
}
In my controller method I have the following:
// I'm using intersect to remove any other parameters that may have been supplied as this endpoint only requires one
$requestData = $request->intersect(['only_this_key']);
$messages = [
'only_this_key.required' => 'The :attribute is required',
'only_this_key.json' => 'The :attribute field must be valid JSON',
];
$validator = \Validator::make($requestData, [
'only_this_key' => 'required|json',
], $messages);
if ($validator->fails()) {
return new APIErrorValidationResponse($request, $validator);
}
return response()->json(['all good' => 'here']);
The error I get back is The inventory field must be valid JSON even though it is!
Passing in the raw data using Postman
{
"only-this-key": {
"item-one": "one",
"item-two": "two",
"item-three": "three"
},
"not": "wanted"
}
When I use dd($request->all()); within the method
array:2 [
"what-i-want" => array:3 [
"item-one" => "one"
"item-two" => "two"
"item-three" => "three"
]
"not" => "wanted"
]
The problem is with how Laravel is interpreting the raw data in the request. If you run dd($request->all()) in your controller you will see this output:
array:1 [
"{"only_this_key":{"one":"two"}}" => ""
]
Your entire JSON string is getting set as a key with a value of an empty string. If you absolutely must send it as raw data, then you're going to have to grab that key value and save it to an array with the key that you want. This should work (instead of the intersect line).
$requestData = ['only_this_key' => key($request->all())];
Alternatively, you can just send the body as x-www-form-urlencoded with your entire JSON string as the only value for one key.

Json::decode returns NULL

I have problem with Json::decode. I'm using this code:
use Drupal\Component\Serialization\Json;
$client = \Drupal::httpClient();
$request = $client->post($rest_url, [
'form_params' => [
'id' => $rest_id,
],
]);
$response = Json::decode($request->getBody());
to get JSON from some server but it returns NULL. Of course this is just a part of the code (without try, catch...)
$request->getBody() return is ok, but in Json::decode I'm still getting NULL.
The only thing I noticed is that in Postman, when I look at raw body content, I see some empty lines at the beginning of the JSON (like return on keyboard when typing), but I checked JSON as it is on JSONLint and it's valid.
Any idea what is the problem?
I'm not familiar with Drupal's JSON serializer, but try to force response body conversation to a string.
$response = Json::decode($request->getBody()->getContents());
Guzzle return a Stream object from getBody(), it could be the issue.

Gatling - Looping through JSON array

I have a block of code which needs to loop through a JSON array which is obtained from response of a REST service. (Full gist available here.)
.exec(http("Request_1")
.post("/endPoint")
.headers(headers_1)
.body(StringBody("""REQUEST_BODY""")).asJSON
.check(jsonPath("$.result").is("SUCCESS"))
.check(jsonPath("$.data[*]").findAll.saveAs("pList")))
.exec(session => {
println(session)
session
})
.foreach("${pList}", "player"){
exec(session => {
val playerId = JsonPath.query("$.playerId", "${player}")
session.set("playerId", playerId)
})
.exec(http("Request_1")
.post("/endPoint")
.headers(headers_1)
.body(StringBody("""{"playerId":"${playerId}"}""")).asJSON
.check(jsonPath("$.result").is("SUCCESS")))
}
The response format of the first request was
{
"result": "SUCCESS",
"data": [
{
"playerId": 2
},
{
"playerId": 3
},
{
"playerId": 4
}
]
}
And playerId shows up in the session as
pList -> Vector({playerId=2, score=200}, {playerId=3, score=200}
I am seeing in the second request the body is
{"playerId":"Right(empty iterator)}
Expected : 3 requests with body as
{"playerId":1}
{"playerId":2}
{"playerId":3}
I can loop over the resulting array successfully if I save just the playerIds:
.check(jsonPath("$.data[*].playerId").findAll.saveAs("pList")))
I managed to get the requests you're looking for sent out (although still getting a 404, but that might be server-side or the request your gist is sending might be missing something). The trick was to give up on JsonPath entirely:
.exec(http("Request_10")
.get("gatling1")
.headers(headers_10)
.check(jsonPath("$.result").is("SUCCESS"),
jsonPath("$.data[*]").ofType[Map[String,Any]].findAll.saveAs("pList")))
.foreach("${pList}", "player") {
exec(session => {
val playerMap = session("player").as[Map[String,Any]]
val playerId = playerMap("playerId")
session.set("playerId", playerId)
})
Here, the jsonPath check can automatically store your JSON object as a map, and then you can access the player ID by key. The value type doesn't have to be Any, you could use Int or Long if all your values are numbers. If you want more info on what went wrong with JsonPath, read on.
Your first problem is that JsonPath.query() doesn't just return the value you're looking for. From the JsonPath readme:
JsonPath.query("$.a", jsonSample) gives you Right(non-empty iterator). This will allow you to iterate over all possible solutions to the query.
Now, when it says Right(non-empty iterator), I assumed that meant the iterator was not empty. However, if you try this:
val playerId = JsonPath.query("$.playerId", session("player").as[String]).right.get
println(playerId)
...it prints "empty iterator". I'm not sure whether it's a problem with JsonPath, the jsonPath check, or usage somewhere in between, but there's not quite enough documentation for me to want to dig into it.