Rails 6 how to add an API error with message - ruby-on-rails

I'm trying to implement error handling for my API client. Everything looks pretty good but except of the error I want to error description from the API either (it's inside response.body['FirebirdApiError']['ApiStatusDescription']). Like below:
# error handler class
module Errors
class APIExceptionError < StandardError; end
BadRequestError = Class.new(APIExceptionError)
UnauthorizedError = Class.new(APIExceptionError)
ForbiddenError = Class.new(APIExceptionError)
ApiRequestsQuotaReachedError = Class.new(APIExceptionError)
NotFoundError = Class.new(APIExceptionError)
UnprocessableEntityError = Class.new(APIExceptionError)
ApiError = Class.new(APIExceptionError)
def error_class(status)
case status
when 400
BadRequestError
when 401
UnauthorizedError
when 403
ForbiddenError
when 404
NotFoundError
when 429
UnprocessableEntityError
else
ApiError
end
end
end
Which is user inside of client class:
#client class
class Client
include ::Errors
def get(path, options = {})
handle_response(client.public_send(:get, path.to_s, options))
end
private
(...)
def handle_response(response)
return response_body(response) if response.success?
raise error_class(response.status)
end
def response_body(response)
return if response.body.blank?
response.body
end
end
Which works well but when I'll reach 400 error it will show me Errors::BadRequestError. I don't think it's handy in case where the API provides a pretty good description of the cause of the error inside response.body['FirebirdApiError']['ApiStatusDescription']. How do I add this message to display with the error?

You want to add error message when your raise error, right? Maybe you can try
raise error_class(response.status).new(response.body['FirebirdApiError']['ApiStatusDescription'])

Related

Rails app to check the status of a server

I want to achieve a problem, where we manually go and check a webapp/server if it is up/down. I want to build a rails app which can automate this task.
Consider my app url is: HostName:PORT/Route?Params (may or may not have port in url)
I checked 'net/http'
def check_status()
#url='host'
uri = URI(#url)
http = Net::HTTP.new(#url,port)
response = http.request_get('/<route>?<params>')
if response == Net::HTTPSuccess
#result='Running'
else
#result='Not Running'
end
end
I am facing error at ,
response = http.request_get('/<route>?<params>')
when the app is down throwing 'Failed to open TCP connection to URL' which is correct.
Can you guys help me find some new solution or how can I improve the above implementation?
Since it's working as intended and you just need to handle the error that's returned when the app is down, wrap it in a rescue block.
def check_status()
#url='host'
uri = URI(#url)
http = Net::HTTP.new(#url,port)
begin
response = http.request_get('/<route>?<params>')
rescue TheClassNameOfThisErrorWhenSiteIsDown
#result = 'Not Running'
end
if response == Net::HTTPSuccess
#result='Running'
else
#result='Not Running'
end
end
end
Just came across this old question. Net::HTTP methods get and head don't raise an exception. So use one of these instead.
def up?(site)
Net::HTTP.new(site).head('/').kind_of? Net::HTTPOK
end
up? 'www.google.com' #=> true

How to change response.message in rails

Hello I am having given code
def create_profile(payment)
return unless payment.source.gateway_customer_profile_id.nil?
options = {
email: payment.order.email,
login: preferred_secret_key,
}.merge! address_for(payment)
source = update_source!(payment.source)
if source.number.blank? && source.gateway_payment_profile_id.present?
creditcard = source.gateway_payment_profile_id
else
creditcard = source
end
response = provider.store(creditcard, options)
if response.success?
cc_type=payment.source.cc_type
response_cc_type = response.params['sources']['data'].first['brand']
cc_type = CARD_TYPE_MAPPING[response_cc_type] if CARD_TYPE_MAPPING.include?(response_cc_type)
payment.source.update_attributes!({
cc_type: cc_type, # side-effect of update_source!
gateway_customer_profile_id: response.params['id'],
gateway_payment_profile_id: response.params['default_source'] || response.params['default_card']
})
else
payment.send(:gateway_error, response.message)
end
end
I need to change message in response.message for that i had tried using response = [ { message: "fraud card"} ].to_json but it gives error `
undefined method `message' for "[{"message":"fraud card"}]":String
I had also tried response.message = 'fraud error', still it gives error. Response I receive is :
params:
error:
message: Your card was declined.
type: card_error
code: card_declined
decline_code: fraudulent
charge: ch_1AgncyJEfCzWOpKDdoxn1x1R
message: Your card was declined.
success: false
test: false
authorization: ch_1AgncyJEfCzWOpKDdoxn1x1R
fraud_review:
error_code: card_declined
emv_authorization:
avs_result:
code:
message:
street_match:
postal_match:
cvv_result:
code:
message:
Now my requirement is to check if decline_code is fraudulent than my message should be fraud error. please let me know how to change this.
Based on your comment, you're using Spree Gateway. By passing a string instead of the proper response object, your solution circumvents Spree's default implementation which logs error details for the response.
What I'd do instead is adapt the gateway_error method to your needs by following Spree's suggested approach for logic customization:
# app/models/spree/payment_decorator.rb
Spree::Payment.class_eval do
private
def gateway_error(error)
if error.is_a? ActiveMerchant::Billing::Response
# replace this with your actual implementation, e.g. based on response.params['error']['code']
text = 'fraud message'
elsif error.is_a? ActiveMerchant::ConnectionError
text = Spree.t(:unable_to_connect_to_gateway)
else
text = error.to_s
end
logger.error(Spree.t(:gateway_error))
logger.error(" #{error.to_yaml}")
raise Core::GatewayError.new(text)
end
end
It's not the cleanest implementation since it does copy & paste for existing code. But that's just how Spree is (I've implemented and contributed to multiple Spree shops and it's always a bit painful when customizing logic, especially private logic).
Hope that helps.

How to DRY up Webhook handlers in Rails

I've been developing Stripe Webhook handler to create/update records depending the values.
It's not really hard, if it's a simple like this below;
StripeEvent.configure do |events|
events.subscribe 'charge.succeeded' do |event|
charge = event.data.object
StripeMailer.receipt(charge).deliver
StripeMailer.admin_charge_succeeded(charge).deliver
end
end
However If I need to store the data conditionally, it could be little messier.
In here I extracted the each Webhook handler and defined something like stripe_handlers/blahblah_handler.rb.
class InvoicePaymentFailed
def call(event)
invoice_obj = event.data.object
charge_obj = retrieve_charge_obj_of(invoice_obj)
invoice = Invoice.find_by(stripe_invoice_id: charge_obj[:invoice])
# common execution for subscription
invoice.account.subscription.renew_billing_period(start_at: invoice_obj[:period_start], end_at: invoice_obj[:period_end])
case invoice.state
when 'pending'
invoice.fail!(:processing,
amount_due: invoice[:amount_due],
error: {
code: charge_obj[:failure_code],
message: charge_obj[:failure_message]
})
when 'past_due'
invoice.failed_final_attempt!
end
invoice.next_attempt_at = Utils.unix_time_to_utc(invoice_obj[:next_payment_attempt].to_i)
invoice.attempt_count = invoice_obj[:attempt_count].to_i
invoice.save
end
private
def retrieve_charge_obj_of(invoice)
charge_obj = Stripe::Charge.retrieve(id: invoice.charge)
return charge_obj
rescue Stripe::InvalidRequestError, Stripe::AuthenticationError, Stripe::APIConnectionError, Stripe::StripeError => e
logger.error e
logger.error e.backtrace.join("\n")
end
end
end
I just wonder how I can DRY up this Webhook handler.
Is there some best practice to approach this or any ideas?
I suggest re-raising the exception in retrieve_charge_obj_of, since you'll just get a nil reference exception later on, which is misleading. (As is, you might as well let the exception bubble up, and let a dedicated error handling system rescue, log, and return a meaningful 500 error.)
a. If you don't want to return a 500, then you have a bug b/c retrieve_charge_obj_of will return nil after the exception is rescued. And if charge_obj is nil, then this service will raise a NPE, resulting in a 500.
if invoice_obj[:next_payment_attempt] can be !present? (blank?), then what is Utils.unix_time_to_utc(invoice_obj[:next_payment_attempt].to_i) supposed to mean?
a. If it was nil, false, or '', #to_i returns 0 -- is that intended? ([]/{} is also blank? but would raise)
Conceptually, this handler needs to issue a state transition on an Invoice, so a chunk of this logic can go in the model instead:
class Invoice < ApplicationRecord
# this method is "internal" to your application, so incoming params should be already "clean"
def mark_payment_failed!(err_code, err_msg, attempt_count, next_payment_at)
transaction do # payment processing usually needs to be transactional
case self.state
when 'pending'
err = { code: err_code, message: err_msg }
self.fail!(:processing, amount_due: self.amount_due, error: err)
when 'past_due'
self.failed_final_attempt!
else
ex_msg = "some useful data #{state} #{err_code}"
raise InvalidStateTransition, ex_msg
end
self.next_attempt_at = next_payment_at
self.attempt_count = attempt_count
self.save
end
end
class InvalidStateTransition < StandardError; end
end
Note: I recommend a formal state machine implementation (e.g. state_machine) before states & transitions get out of hand.
Data extraction, validation, and conversion should happen in the handler (that's what "handlers" are for), and they should happen before flowing deeper in your application. Errors are best caught early and execution stopped early, before any action has been taken.
There are still some other edge cases that I see that aren't really handled.

Printing error when using PARAMS in Rails

For my API in RAILS I have programmed a code that basically does the following.
class Api::V1::NameController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
end
def create
# Loading data
data_1_W = params[:data1]
data_2_W = params[:data2]
while len > i
# -Here I do some calculations with data_1_W and data_2_W.
# Its not important to show the code here
end
# -Organizing outputs to obtain only one JSON-
# Its not important to show the code here
# Finally HTTP responses
if check_error == 1
render status: 200, json: {
message: "Succesful data calculation",
data_output: response_hash
}.to_json
end
end
end
To test that everything is working I use the cURL command. I notice that loading the data could be a problem and therefore the code would break.
I want to tell the user that it was an error loading the data for some reason (HTTP response), but I don't know where to put it. If I put and else under my success status it would not print it because the code breaks just starting (instead of sending the correct name - d '#data.json' of the data in cURL I send -d '#dat.json').
The data I am loading is a JSON data {"data1":[{"name1":"value1"},{"name2":number2}...],"data2":[{"name1":"value1"},{"name2":number2...}]}. (This data has 70080 rows with 2 columns if we see it as a table, which I divided into two in my CODE for calculations purposes data_1_W and data_2_W)
Could anyone help me where to put it? more or less like this:
render status: 500, json: {
message: "Error loading the data",
}.to_json
Put it in a rescue block around the code that throws the error.
E.g.
def func
# code that raises exception
rescue SomeException => e
# render 422
end
Since you are working in Rails I'd recommend going the rails way. This means that I would create some kind of service and initialize it in the create action.
Now, within the service you do all you funky stuff (which also allows you to clean this controller and make i look prettier) and the moment a condition is not fulfilled in that service return false. So...
# controllers/api/v1/name_controller.rb
...
def create
meaningful_variable_name = YourFunkyService.new(args)
if meaningful_variable_name.perform # since you are in create then I assume you're creating some kind of resource
#do something
else
render json: {
error: "Your error",
status: error_code, # I don't think you want to return 500. Since you're the one handling it
}
end
end
# services/api/v1/your_funky_service.rb
class Api::V1::YourFunkyService
def initiliaze(params)
#params = params
end
def perfom #call it save if you wish
....
return false if check_error == 1
end
end

Getting HTTP response in Rails

In my Rails controller, I have the url that the user inputs:
url_parsed = URI.parse(url)
response = Net::HTTP.get_response(url_parsed)
If the user inputs www.google.com, it gives
undefined method `request_uri' for #<URI::Generic:0x00000002e07908 URL:www.google.com>
on the line response = ....
I want it to display my error page, instead of this error. How can I do it?
Don't know if your question is why it isn't working, or how to use the error message in the view.
Why you get the error
EDIT:
I think that it's because there is no protocol in 'www.google.com', 'http://www.google.com' should work
How to show the error
Rescue the error:
error = nil
begin
url_parsed = URI.parse(url)
response = Net::HTTP.get_response(url_parsed)
rescue => error
end
if error
#error_message = "Your URL wasn't good enough"
# or you can use error.message if you want
# then use #error_message in your view
else
# do stuff when ok
end

Resources