module Jekyll::Algolia::ErrorHandler
def self.error_hash(message)
Returns a hash of all parts of the message, to be more easily consumed
message - The raw message as returned by the API
Public: Parses an Algolia error message into a hash of its content
def self.error_hash(message) message = message.delete("\n") # Ex: Cannot PUT to https://appid.algolia.net/1/indexes/index_name/settings: # {"message":"Invalid Application-ID or API key","status":403} (403) regex = VerEx.new do find 'Cannot ' capture('verb') { word } find ' to ' capture('scheme') { word } find '://' capture('application_id') { word } anything_but '/' find '/' capture('api_version') { digit } find '/' capture('api_section') { word } find '/' capture('index_name') do anything_but('/') end find '/' capture do capture('api_action') { word } maybe '?' capture('query_parameters') do anything_but(':') end end find ': ' capture('json') do find '{' anything_but('}') find '}' end find ' (' capture('http_error') { word } find ')' end matches = regex.match(message) return false unless matches # Convert matches to a hash hash = {} matches.names.each do |name| hash[name] = matches[name] end hash['api_version'] = hash['api_version'].to_i hash['http_error'] = hash['http_error'].to_i # Merging the JSON key directly in the answer hash = hash.merge(JSON.parse(hash['json'])) hash.delete('json') # Merging the query parameters in the answer CGI.parse(hash['query_parameters']).each do |key, values| hash[key] = values[0] end hash.delete('query_parameters') hash end
def self.identify(error, context = {})
that matches. Returns false if no match, or a hash of :name and :details
It will parse in order all potential known issues until it finds one
code intercepting the user
context - A hash of additional information that can be passed from the
error - The caught error
Public: Will identify the error and return its internal name
def self.identify(error, context = {}) known_errors = %w[ unknown_application_id invalid_credentials record_too_big_api too_many_records unknown_setting invalid_index_name ] # Checking the errors against our known list known_errors.each do |potential_error| error_check = send("#{potential_error}?", error, context) next if error_check == false return { name: potential_error, details: error_check } end false end
def self.invalid_credentials?(error, _context = {})
_context - Not used
Public: Check if the credentials are working
def self.invalid_credentials?(error, _context = {}) details = error_hash(error.message) return false if details == false if details['message'] != 'Invalid Application-ID or API key' return false end { 'application_id' => details['application_id'], 'index_name' => Configurator.index_name, 'index_object_ids_name' => Configurator.index_object_ids_name } end
def self.invalid_index_name?(error, _context = {})
Public: Check if the index name is invalid
def self.invalid_index_name?(error, _context = {}) details = error_hash(error.message) return false if details == false message = details['message'] return false if message !~ /^indexName is not valid.*/ { 'index_name' => Configurator.index_name } end
def self.record_too_big_api?(error, _contex = {})
them. If it still happens it means that the value set in max_record_size
should not happen as we proactively check for record size before pushing
One of the sent record is too big and has been rejected by the API. This
context[:records] - list of records sent in the batch
Public: Check if the sent records are not too big
def self.record_too_big_api?(error, _contex = {}) details = error_hash(error.message) return false if details == false message = details['message'] return false if message !~ /^Record .* is too big .*/ record_size, = /.*size=(.*) bytes.*/.match(message).captures record_size_readable = Filesize.from("#{record_size}B").to_s('Kb') max_record_size = Configurator.algolia('max_record_size') { 'record_size' => record_size_readable, 'max_record_size' => max_record_size } end
def self.stop(error, context = {})
context - A hash of values that will be passed from where the error
error - The caught error
a human-readable error message
Public: Stop the execution of the plugin and display if possible
def self.stop(error, context = {}) Logger.verbose("E:[jekyll-algolia] Raw error: #{error}") identified_error = identify(error, context) if identified_error == false Logger.log('E:[jekyll-algolia] Error:') Logger.log("E:#{error}") else Logger.known_message( identified_error[:name], identified_error[:details] ) end exit 1 end
def self.too_many_records?(error, _context = {})
Public: Check if the application has too many records
def self.too_many_records?(error, _context = {}) details = error_hash(error.message) return false if details == false message = details['message'] return false if message !~ /^Record quota exceeded.*/ {} end
def self.unknown_application_id?(error, _context = {})
is invalid. As we cannot actually contact the server, the error is raw
If the call to the cluster fails, chances are that the application ID
_context - Not used
Public: Check if the application id is available
def self.unknown_application_id?(error, _context = {}) message = error.message return false if message !~ /^Cannot reach any host/ matches = /.*\((.*)\.algolia.net.*/.match(message) # The API will browse on APP_ID-dsn, but push/delete on APP_ID only # We need to catch both potential errors app_id = matches[1].gsub(/-dsn$/, '') { 'application_id' => app_id } end
def self.unknown_setting?(error, context = {})
not available. We'll tell the user which one so they can fix their
The API will block any call that tries to update a setting value that is
context[:settings] - The settings passed to update the index
Public: Check if one of the index settings is invalid
def self.unknown_setting?(error, context = {}) details = error_hash(error.message) return false if details == false message = details['message'] return false if message !~ /^Invalid object attributes.*/ # Getting the unknown setting name regex = /^Invalid object attributes: (.*) near line.*/ setting_name, = regex.match(message).captures setting_value = context[:settings][setting_name] { 'setting_name' => setting_name, 'setting_value' => setting_value } end