Module: Jekyll::Algolia::ErrorHandler

Includes:
Jekyll::Algolia
Defined in:
lib/jekyll/algolia/error_handler.rb

Overview

Catch API errors and display messages

Constant Summary

Constants included from Jekyll::Algolia

MissingCredentialsError, VERSION

Class Method Summary collapse

Methods included from Jekyll::Algolia

init, load_overwrites, run, site

Class Method Details

.error_hash(message) ⇒ Object

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



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# 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 = {}) ⇒ Object

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.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 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 = {}) ⇒ Boolean

Public: Check if the credentials are working

_context - Not used

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

Returns:

  • (Boolean)


164
165
166
167
168
169
170
171
172
173
174
175
176
177
# 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 = {}) ⇒ Boolean

Public: Check if the index name is invalid

Some characters are forbidden in index names

Returns:

  • (Boolean)


232
233
234
235
236
237
238
239
240
241
242
# 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 = {}) ⇒ Boolean

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.

Returns:

  • (Boolean)


187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# 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 = {}) ⇒ Object

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



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 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 = {}) ⇒ Boolean

Public: Check if the application has too many records

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

Returns:

  • (Boolean)


247
248
249
250
251
252
253
254
255
# 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 = {}) ⇒ Boolean

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

Returns:

  • (Boolean)


146
147
148
149
150
151
152
153
154
155
156
157
# 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 = {}) ⇒ Boolean

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.

Returns:

  • (Boolean)


211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# 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