I have a Granite User model with some validations. When someone makes a POST request to users/new, I'd like to return the validation errors (if any) as JSON. Currently, I have:
if user.errors.size > 0
halt env, status_code: 500, response: user.errors.to_json
end
But when I try to compile, I get:
in /usr/local/Cellar/crystal/0.26.1/src/json/to_json.cr:66: no overload
matches 'Granite::Error#to_json' with type JSON::Builder
Overloads are:
- Object#to_json(io : IO)
- Object#to_json()
each &.to_json(json)
^~~~~~~
So the issue is that User#errors is an Array(Granite::Error), i.e. an Array holding Granite::Errors. Unfortunately, it doesn't look like Granite::Error implements the to_json(JSON::Builder) method (i.e. the method to_json taking a parameter of type JSON::Builder), which Array#to_json relies on (that snippet you see there is from the implementation of Array#to_json which you can view on GitHub.).
I'd recommend building the JSON yourself using JSON.build. This has the added side-effect of keeping the JSON that you respond with (which I imagine is being consumed by some client) is entirely in your control. If the developers of Granite were to change how they encode Granite::Errors in JSON and you were using their to_json method, the change wouldn't raise anything at compile time.
As a side-note, I'd recommend against using a status code of 500 to denote a validation error, as that is typically reserved for unexpected errors internal to your server. A 4xx error (e.g. 400 - Bad Request) would be more appropriate. As a second side-note, it would be more RESTful to have the POST be made to the /users endpoint, as opposed to /users/new.
With these changes, here would be the resulting snippet:
if user.errors.size > 0
errors_as_json = JSON.build do |json|
json.array do
user.errors.each do |error|
json.string error.to_s
end
end
end
halt env, status_code: 400, response: errors_as_json
end
Related
I'm building a very simple "to-do" API which only has 3 files: a controller with all the endpoints, a service with some logic to manipulate data and a DAO for DB access. So whenever an endpoint gets a request, it calls a method from the service and this method calls a DAO method to interact with the DB.
For example, the service has an add_item method which converts a field ("status") into an enum value, and a KeyError occurs when the value is something else. So my method goes something like this (self.dao is an instance of the DAO class and ValidStatus is the enum):
def add_item(self, name: str, status: str) -> int:
valid_status = ValidStatus[status.upper()]
inserted_id = self.dao.insert(item_name=name, item_status=valid_status)
return inserted_id
Since I'm now trying to handle exceptions, I modified the method to add a try-except block:
def add_item(self, name: str, status: str) -> Union[int, str]:
try:
valid_status = ValidStatus[status.upper()]
inserted_id = self.dao.insert_task(item_name=name, item_status=valid_status)
return inserted_id
except KeyError as e:
return (f"{e} status is not valid. Please enter {ValidStatus.TODO} or {ValidStatus.COMPLETED}.")
But I'm not sure this is a good approach, since now the method returns 2 different value types: the id of the new item if everthing is ok, or a string with the error message if the exception occurs. And I'm not sure that's the best choice, consistency-wise (and because all my type hinting would get messed up with Unions).
I thought another option would be to always return a tuple with a dict to create a json formatted response and an http code. So when everything goes well it would be something like return {"new item id": inserted_id}, 200 and when there's an exception it would be something like
return {"error": "Something went wrong"}, 400. But I'm not sure this is a good approach either, since then the service would be dealing with http status codes and json structured dicts, and that sounds more like a controller's responsibility.
So is there an advised best practice for this?
Well, generally speaking, there are two types of errors to handle, one is user input-related errors, such as the status key error in your case, which usually return 4xx, and the other is server-side processing errors, such as database errors, which usually return 5xx.
For the first type of error, we often use some kind of validator, such as marshmallow, and handle those errors in the controller. For example,
#bp.route("", methods=["POST"])
#login_required
def new_paper():
req = request.json
try:
cleaned_data = CreateUpdatePaperSchema(unknown=EXCLUDE).load(req)
except ValidationError as err:
return err_response(err_msg=fmt_validate_err(err), status_code=400)
For the second type of error, we often use a global exception catching mechanism to handle that uniformly. For example,
#app.errorhandler(Exception)
def handle_exception(exc):
db.session.rollback()
app.logger.error(exc, exc_info=True)
err_msg = "Something went wrong"
return err_response(err_msg=err_msg, status_code=500)
I want to be able to act on an error that happens when I run one of the commands to interface stripe. I can see the error, but can't seem to capture it.
When I run a the verification for stripe's ACH payment format and use bad deposist, I get the following error:
in handle_error_response
raise err
stripe.error.CardError: Request req_UyfXgBVRSOqUuJ: The amounts provided do not match the amounts that were sent to the bank account.
How do I take this and do something meaningful with it. My code looks like this:
ank_account_response = customer.sources.retrieve(request.stripe_id)
bank_account_response.verify(amounts=[request._post['deposit_1'], request._post['deposit_2']])
The error appears on the last line of code. I want either set do something like output = bank_account_response.verify... or try: bank_account_response, but I can't get it to work.
Thoughts?
You should handle error response something like below.
try:
bank_account_response.verify(amounts=[request._post['deposit_1'], request._post['deposit_2']])
except CARD_ERROR as card_error:
return JsonResponse(status_code=400,
message=card_error.message)
In my Ember app, I get notifications from a websocket. When the websocket receives a message (encoded as JSON), I want to push it into the store.
Here's my code so far:
console.log('About to normalize', data);
var normalizedData = this.store.normalize('message', data);
console.log('About to push', normalizedData);
this.store.push('message', normalizedData);
data and normalizedData end up being exactly the same values, which is something like this:
{"message":{"id":1,"chatroom":1,"player":1,"content":"A message"}}
And calling the push method provokes this error:
Error: Assertion Failed: Expected an object as `data` in a call to `push`/`update` for message ,
but was {"message":{"id":1,"chatroom":1,"player":1,"content":"31232132113"}}
I don't know what is wrong. When Ember gets a specific message from the server, it receives the same kind of JSON and Ember Data deals well with it. When it comes from a websocket and needs to be pushed, it crashes.
Update
I tried to use pushPayload instead like it was proposed in the comments. It still doesn't work. I get those messages:
"WARNING: Encountered "0" in payload, but no model was found for model name "0" (resolved model name using arkipel#serializer:-rest:.typeForRoot("0"))"
"WARNING: Encountered "0" in payload, but no model was found for model name "1" (resolved model name using arkipel#serializer:-rest:.typeForRoot("1"))"
"WARNING: Encountered "0" in payload, but no model was found for model name "2" (resolved model name using arkipel#serializer:-rest:.typeForRoot("2"))"
It goes up to number 67, then keeps going with words like fmt, camelize, htmlSafe... I'm pretty just data is just a string representing JSON.
After we did some debugging in the chat we found the problem. I'll share the solution here for others.
The websocket was sending messages in string format. So the data in the javascript code was a string and not a javascript object (JSON). Ember Data expects us to push a javascript object and not a string.
The solution is to first parse the response from the websocket to a javascript object before pushing it into the store, for example by doing JSON.parse(data).
I'm trying to get autocomplete working in my rails application using Magic Suggest.
I think this is the correct question: How can I get MagicSuggest to grab the JSON that is at the URL I give it?
This is the error that console returns when I type letters:
POST http://localhost:3000/search_foods 404 (Not Found) jquery.js:8706
Uncaught Could not reach server
Here's the magic suggest code:
input.magicSuggest({
data: "/foods/search/",
placeholder: "Search Foods...",
valueField:'idFood',
displayField:'foodName'
});
The Routes
resources :search_foods
The Controller and Action
class SearchFoodsController < ApplicationController
def index
render json: %['Crack', 'Cocain', 'Gorilla Test', 'Horse Test']
end
end
When I visit the /search_foods url directly I get
'Crack', 'Cocain', 'Gorilla Test', 'Horse Test'
as my code is designed to do.
I think the issue is in that MagicSuggest, by default, sends a POST request, although I'm not sure if that's entirely relevant:
You can pass the url from which the component will fetch its JSON data.Data will be fetched
* using a POST ajax request that will * include the entered text as 'query' parameter. The results
* fetched from the server can be:
* - an array of JSON objects (ex: [{id:...,name:...},{...}])
* - a string containing an array of JSON objects ready to be parsed (ex: "[{id:...,name:...},{...}]")
* - a JSON object whose data will be contained in the results property
* (ex: {results: [{id:...,name:...},{...}]
Try this:
input.magicSuggest({
data: "http://localhost:3000/search_foods",
placeholder: "Search Foods...",
valueField:'idFood',
displayField:'foodName'
});
The doc states that the component expects one of the following:
* - an array of JSON objects (ex: [{id:...,name:...},{...}])
* - a string containing an array of JSON objects ready to be parsed (ex: "[{id:...,name:...},{...}]")
* - a JSON object whose data will be contained in the results property
* (ex: {results: [{id:...,name:...},{...}]
When you visit /search_foods you get
'Crack', 'Cocain', 'Gorilla Test', 'Horse Test'
This does not fit any of the 3 supported cases.
My suspicions about the POST request was correct.
My friend helped out so that's why I was able to fix this.
This is what I did..
Eliminated the FoodSearch Controller, because that's not needed at all.
Created a search action in my Food Controller like so:
def search
render json: ['cocain', 'crack', 'gorilla testosterone']
end
Edited my routes to a POST request instead of a get *This was the key:
resources :foods do
collection do
post :search
end
end
--- Another option, as karlipoppins suggests, is to simply change the type of request that magicSuggest makes by including the method attribute like so:
input.magicSuggest({
method: 'get',
data: "/foods/search/",
placeholder: "Search Foods...",
valueField:'idFood',
displayField:'foodName'
});
Then I wouldn't need to change the route to post.
Added this path to the data attribute in the js
data: "/foods/search/"
This will be a huge help to anyone trying to get magicSuggest to work in rails. It's a pretty damn easy setup to be honest. That one bit and the JSON formatting was the only thing that was tripping me up here.
In emberjs I have a /product/:product_id dynamic route which renders product template backed by model hook of ProductRoute and ProductController as expected.
this.store.find('product', id) uses RESTAdapter and gives me a response (which follows JSON Conventions for RESTAdapter) as : {product: {"id":"1","name":"prod1"}} and renders the template as expected when product is found. But when the product is empty (not found in database), I get the response as : {product: {}}. Now I am unable to figure out the way to intercept the response and check for empty dict, and give proper alert message (something like 'Product with given ID not found').
Note: The promise returned by this.store.find becomes an Ember Object of type product once resolved.
Ember's error message when no product is found : You must include anidin a hash passed to 'push'
Thanks.
An empty request is telling Ember-Data that the request was successful even though it wasn't. Your server should be responding with a 404 when the product isn't found, not an empty request. Otherwise, Ember-Data is going to assume that the empty object is the product.