module Jekyll::Algolia::ErrorHandler

Catch API errors and display messages

Public Class Methods

error_hash(message) click to toggle source

Public: Parses an Algolia error message into a hash of its content

message - The raw message as returned by the API

Returns a hash of all parts of the message, to be more easily consumed by our error matchers

# File lib/jekyll/algolia/error_handler.rb, line 75
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
identify(error, context = {}) click to toggle source

Public: Will identify the error and return its internal name

error - The caught error context - A hash of additional information that can be passed from the code intercepting the user

It will parse in order all potential known issues until it finds one that matches. Returns false if no match, or a hash of :name and :details further identifying the issue.

# File lib/jekyll/algolia/error_handler.rb, line 46
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
invalid_credentials?(error, _context = {}) click to toggle source

Public: Check if the credentials are working

_context - Not used

Application ID and API key submitted don't match any credentials known

# File lib/jekyll/algolia/error_handler.rb, line 164
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
invalid_index_name?(error, _context = {}) click to toggle source

Public: Check if the index name is invalid

Some characters are forbidden in index names

# File lib/jekyll/algolia/error_handler.rb, line 232
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
record_too_big_api?(error, _context = {}) click to toggle source

Public: Check if the sent records are not too big

context - list of records sent in the batch

One of the sent record is too big and has been rejected by the API. This should not happen as we proactively check for record size before pushing them. If it still happens it means that the value set in max_record_size is not matching the value in the plan.

# File lib/jekyll/algolia/error_handler.rb, line 187
def self.record_too_big_api?(error, _context = {})
  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
stop(error, context = {}) click to toggle source

Public: Stop the execution of the plugin and display if possible a human-readable error message

error - The caught error context - A hash of values that will be passed from where the error happened to the display

# File lib/jekyll/algolia/error_handler.rb, line 19
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
too_many_records?(error, _context = {}) click to toggle source

Public: Check if the application has too many records

We're trying to push too many records and it goes over quota

# File lib/jekyll/algolia/error_handler.rb, line 247
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
unknown_application_id?(error, _context = {}) click to toggle source

Public: Check if the application id is available

_context - Not used

If the call to the cluster fails, chances are that the application ID is invalid. As we cannot actually contact the server, the error is raw and does not follow our error spec

# File lib/jekyll/algolia/error_handler.rb, line 146
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
unknown_setting?(error, context = {}) click to toggle source

Public: Check if one of the index settings is invalid

context - The settings passed to update the index

The API will block any call that tries to update a setting value that is not available. We'll tell the user which one so they can fix their issue.

# File lib/jekyll/algolia/error_handler.rb, line 211
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