AWS S3 NoSuchBucket Exception Not Caught in Rescue Clause - ruby

I'm trying to get a bucket in Ruby using the AWS SDK, and trying to catch a NoSuchBucket error. Problem is, my rescue block is not catching the error, and so my app crashes. Here is the relevant code:
begin
b = s3.buckets[bucket_name]
rescue AWS::S3::Errors::NoSuchBucket
puts Invalid bucket name.
exit 1
end
and the error message is:
C:/Ruby193/lib/ruby/gems/1.9.1/gems/aws-sdk-1.5.6/lib/aws/core/client.rb:277:in
`return_or_raise': The specified bucket does not exist (AWS::S3::Errors::NoSuchBucket)
Am I just making a stupid beginner syntax error, or is there a bug in the AWS code that's not actually throwing the error? I've also tried catching all errors and still no dice.

b = s3.buckets[bucket_name]
Doesn't actually make any requests and won't ever through exceptions like NoSuchBucket.
It just returns a bucket object that knows what its name is. A request only happens when you actually try to do something with the bucket (list its contents, add a file to it) and it is at this point that NoSuchBucket is raised. This is outside of your begin block and so your rescue doesn't handle it. If you need to rescue that exception, you need to be putting your begin/rescue around the places that you actually use the bucket.
If you are just trying to validate that it actually exists you could do something like
s3.buckets[bucket_name].exists?

Related

Mongoid::Errors::DocumentNotFound is raised even after rescuing

I have a Rails controller with this method being triggered as a before_action:
def authenticate_user
Knock::AuthToken.new(token: token).entity_for(User)
rescue Mongoid::Errors::DocumentNotFound
render nothing: true, status: 401
end
Even though I can verify that it's rescuing the error(a byebug breakpoint gets triggered under the rescue statement), it still manages to be raised immediately after:
Mongoid::Errors::DocumentNotFound (
message:
Document(s) not found for class User with id(s) 1.
summary:
When calling User.find with an id or array of ids, each parameter must match a document in the database or this error will be raised. The search was for the id(s): 1 ... (1 total) and the following ids were not found: 1.
resolution:
Search for an id that is in the database or set the Mongoid.raise_not_found_error configuration option to false, which will cause a nil to be returned instead of raising this error when searching for a single id, or only the matched documents when searching for multiples.):
app/controllers/api/base_controller.rb:12:in `authenticate_user'
I've been using the rescue keyword for years in Ruby and have never encountered this.
What I'm running:
Ruby 2.5
Rails 5.2
Mongoid 7.0.1
Why is it that the error gets raised even when I rescue it, and how can I prevent the error from getting raised?
I'm not sure why it gets raised even when you rescue it, but I'll answer the "how to prevent it" part.
In your mongoid.yml, you need to set
raise_not_found_error: false
See this section in the Mongoid docs for an example.

Rescuing error does nothing?

I'm trying to rescue an exception with this method:
def template_deleted
mailchimp_client.templates.info(mailchimp_id)
rescue Mailchimp::InvalidTemplateError => error
puts "Template deleted in Mailchimp: #{error}"
return true
else
return false
end
And no matter what I use to output the message, whether it's STDERR, STDOUT, log.error, p, puts, or print, nothing gets out to the environment's log. This should definitely be returning an error, because the template definitely doesn't exist in Mailchimp.
When I try the same code in the console I can read the error just fine, so either there's something wrong with the rescuing itself (i.e., my method is returning false which it shouldn't), or there's something wrong with the way I'm outputting it.
To output something in the log file of the current environment, use the Rails logger like this:
logger.debug "Template deleted in Mailchimp: #{error}"
You can replace the debug method call with any logging level name, that are briefly described in the link above. Also don't forget to make sure you're running in correct environment!

Raising an an exception: string vs custom class

Is there any advantage to do:
ApiTokenExpired = Class.new(StandardError)
...
raise ApiTokenExpired if response.errorCode == 429
over this lazier alternative:
raise 'api token expired' if response.errorCode == 429
considering that this error detection happens only once in the code?
There is an important advantage of custom errors like ApiTokenExpired if you have to handle the error in an specific way somewhere in the call stack.
begin
# ...
rescue ApiTokenExpired => error
# handle the specific error
rescue => error
# default error handling
end
IMO the decision to create custom errors doesn't depend on the size of the project or future maintenance.
Since custom error classes cause an extra effort I don't use it by default. When a special error handling is needed then an error class should be introduced.
With the first one, you can selectively rescue that error by the class when that code is embedded in larger software. You can still do that with the second one using pattern match with the error message string, but in general, you might alter the message format over time due to refactoring (as opposed to unlikelihood of changing the exception class with your first form), and there can also be different exceptions that have a similar message string, both of which can break pattern matching. When you consider future maintenance, the first one is better.
It does if your app is designed to have error handling or plan to in the future(I mean, any successfull system will want that one day, right?), the only thing you can do with a string is read it in a log or display it in an error message, with types you can code all sorts of things in a decoupled fashion.
For example, ApiTokenExpired could maybe be handled with "try again" response since it's an external problem or be handled by the steps necessary to renew the API Token(and if you used a Typed Exception, you can plug it in later!), maybe you want all errors that are considered SEVERE or UNEXPECTED to send the system administrator an e-mail and also store statistics on the frequency of the error, typed exceptions allows you to code in anything you want in terms of what to do when your system is misbehaving.

abort in rails loop function

I've got a function in my rails controller (I know, not the rails way, but I find it easier to write in the controller when I have something big like this and then move to the model).
I have an error in a array that I'm looping through, unfortunately, the error is being added somewhere in the loop. It is a big array with lots of properties, and I'm trying to figure out where the error is being caused.
I think I can isolate which object in the array is causing the error, but I can't get it to print.
Aparently ruby has an abort('message') function, but that returns an error in rails.
return render isn't working, it gives me an error that render and/or redirect is being called multiple times. How can I do a php type die in this situation?
This SO Post makes an excellent suggestion.
raise RuntimeError, 'Message goes here'
In the 'Message goes here' section you could even add in the array element:
array.each do |array_element|
<logic>
raise RuntimeError, "#{array_element.inspect}; Message goes here"
end

Ruby append to string if error contains string

I have a Slack Bot that needs to respond in an error condition. If the error has certain text in it, I want to append some additional information to the return message. This block of code works fine if I comment out the message += line but breaks if I do not. When I try to replicate this in irb everything works fine too.
Does something look obviously wrong here?
begin
scan = #nsc.scan_devices(devices)
rescue Nexpose::APIError => e
puts "[!] API ERROR: Most likely caused by an orphaned asset (#{device_ids})"
puts "[!] #{e}"
$slackbot_logger.error("[!] API ERROR: Most likely caused by an orphaned asset (#{device_ids})")
$slackbot_logger.error(e)
# Message back to Slack
message = "<##{user_id}> scan for #{ip_list} *failed* :sob:"
message += 'There is a scheduled blackout Tues/Thurs until 1000 CST' if e.include? 'blackout'
SlackFunctions.slack_send_message(message, channel)
return
end
This particular error object (and maybe all error objects) did not have an include? method. Therefore using e.to_s seems to do the trick.

Resources