Identify wrong index name
This commit is contained in:
parent
a10b2d385b
commit
8e35d8eb10
@ -1,5 +1,6 @@
|
||||
require 'verbal_expressions'
|
||||
require 'filesize'
|
||||
require 'cgi'
|
||||
|
||||
module Jekyll
|
||||
module Algolia
|
||||
@ -19,12 +20,23 @@ module Jekyll
|
||||
exit 1
|
||||
end
|
||||
|
||||
# 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.
|
||||
def self.identify(error, context = {})
|
||||
known_errors = %w[
|
||||
unknown_application_id
|
||||
invalid_credentials_for_tmp_index
|
||||
invalid_credentials
|
||||
record_too_big
|
||||
unknown_settings
|
||||
invalid_index_name
|
||||
]
|
||||
|
||||
# Checking the errors against our known list
|
||||
@ -39,6 +51,12 @@ module Jekyll
|
||||
false
|
||||
end
|
||||
|
||||
# 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
|
||||
def self.error_hash(message)
|
||||
message = message.delete("\n")
|
||||
|
||||
@ -57,9 +75,17 @@ module Jekyll
|
||||
find '/'
|
||||
capture('api_section') { word }
|
||||
find '/'
|
||||
capture('index_name') { word }
|
||||
capture('index_name') do
|
||||
anything_but('/')
|
||||
end
|
||||
find '/'
|
||||
capture('api_action') { word }
|
||||
capture do
|
||||
capture('api_action') { word }
|
||||
maybe '?'
|
||||
capture('query_parameters') do
|
||||
anything_but(':')
|
||||
end
|
||||
end
|
||||
find ': '
|
||||
capture('json') do
|
||||
find '{'
|
||||
@ -82,10 +108,26 @@ module Jekyll
|
||||
|
||||
hash['api_version'] = hash['api_version'].to_i
|
||||
hash['http_error'] = hash['http_error'].to_i
|
||||
hash['json'] = JSON.parse(hash['json'])
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
def self.unknown_application_id?(error, _context = {})
|
||||
message = error.message
|
||||
return false if message !~ /^Cannot reach any host/
|
||||
@ -95,6 +137,13 @@ module Jekyll
|
||||
{ 'application_id' => matches[1] }
|
||||
end
|
||||
|
||||
# Public: Check if credentials specifically can't access the _tmp index
|
||||
#
|
||||
# _context - Not used
|
||||
#
|
||||
# If the error happens on a _tmp folder, it might mean that the key does
|
||||
# not have access to the _tmp indices and the error message will reflect
|
||||
# that.
|
||||
def self.invalid_credentials_for_tmp_index?(error, _context = {})
|
||||
return false unless invalid_credentials?(error)
|
||||
|
||||
@ -109,10 +158,15 @@ module Jekyll
|
||||
}
|
||||
end
|
||||
|
||||
# Public: Check if the credentials are working
|
||||
#
|
||||
# _context - Not used
|
||||
#
|
||||
# Application ID and API key submitted don't match any credentials known
|
||||
def self.invalid_credentials?(error, _context = {})
|
||||
details = error_hash(error.message)
|
||||
|
||||
if details['json']['message'] != 'Invalid Application-ID or API key'
|
||||
if details['message'] != 'Invalid Application-ID or API key'
|
||||
return false
|
||||
end
|
||||
|
||||
@ -122,16 +176,23 @@ module Jekyll
|
||||
}
|
||||
end
|
||||
|
||||
# Public: Check if the sent records are not too big
|
||||
#
|
||||
# context[:records] - list of records to push
|
||||
#
|
||||
# Records cannot weight more that 10Kb. If we're getting this error it
|
||||
# means that one of the records is too big, so we'll try to give
|
||||
# informations about it so the user can debug it.
|
||||
def self.record_too_big?(error, context = {})
|
||||
details = error_hash(error.message)
|
||||
|
||||
message = details['json']['message']
|
||||
message = details['message']
|
||||
return false if message !~ /^Record .* is too big .*/
|
||||
|
||||
# Getting the record size
|
||||
size, = /.*size=(.*) bytes.*/.match(message).captures
|
||||
size = Filesize.from("#{size} B").pretty
|
||||
object_id = details['json']['objectID']
|
||||
object_id = details['objectID']
|
||||
|
||||
# Getting record details
|
||||
record = Utils.find_by_key(context[:records], :objectID, object_id)
|
||||
@ -145,6 +206,44 @@ module Jekyll
|
||||
'size_limit' => '10 Kb'
|
||||
}
|
||||
end
|
||||
|
||||
# Public: Check if one of the index settings is invalid
|
||||
#
|
||||
# context[:settings] - 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.
|
||||
def self.unknown_settings?(error, context = {})
|
||||
details = error_hash(error.message)
|
||||
|
||||
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
|
||||
|
||||
# Public: Check if the index name is invalid
|
||||
#
|
||||
# Some characters are forbidden in index names
|
||||
def self.invalid_index_name?(error, _context = {})
|
||||
details = error_hash(error.message)
|
||||
|
||||
message = details['message']
|
||||
return false if message !~ /^indexName is not valid.*/
|
||||
|
||||
{
|
||||
'index_name' => Configurator.index_name
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -75,7 +75,12 @@ module Jekyll
|
||||
def self.delete_records_by_id(index, ids)
|
||||
Logger.log("I:Deleting #{ids.length} records")
|
||||
return if Configurator.dry_run?
|
||||
index.delete_objects!(ids)
|
||||
|
||||
begin
|
||||
index.delete_objects!(ids)
|
||||
rescue StandardError => error
|
||||
ErrorHandler.stop(error)
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Returns an array of all the objectIDs in the index
|
||||
@ -108,7 +113,11 @@ module Jekyll
|
||||
def self.update_settings(index, settings)
|
||||
Logger.verbose('I:Updating settings')
|
||||
return if Configurator.dry_run?
|
||||
index.set_settings(settings)
|
||||
begin
|
||||
index.set_settings(settings)
|
||||
rescue StandardError => error
|
||||
ErrorHandler.stop(error, settings: settings)
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Index content following the `diff` indexing mode
|
||||
@ -118,10 +127,13 @@ module Jekyll
|
||||
# The `diff` indexing mode will only push new content to the index and
|
||||
# remove old content from it. It won't touch records that haven't been
|
||||
# updated. It will be a bit slower as it will first need to get the list
|
||||
# of all records in the index, but it will consume less operations than
|
||||
# the `atomic` indexing mode.
|
||||
# of all records in the index, but it will consume less operations.
|
||||
def self.run_diff_mode(records)
|
||||
index = index(Configurator.index_name)
|
||||
|
||||
# Update settings
|
||||
update_settings(index, Configurator.settings)
|
||||
|
||||
# Getting list of objectID in remote and locally
|
||||
remote_ids = remote_object_ids(index)
|
||||
local_ids = local_object_ids(records)
|
||||
@ -135,9 +147,6 @@ module Jekyll
|
||||
new_records_ids.include?(record[:objectID])
|
||||
end
|
||||
update_records(index, new_records)
|
||||
|
||||
# Update settings
|
||||
update_settings(index, Configurator.settings)
|
||||
end
|
||||
|
||||
# Public: Get the settings of the remote index
|
||||
@ -145,6 +154,8 @@ module Jekyll
|
||||
# index - The Algolia Index
|
||||
def self.remote_settings(index)
|
||||
index.get_settings
|
||||
rescue StandardError => error
|
||||
ErrorHandler.stop(error)
|
||||
end
|
||||
|
||||
# Public: Rename an index
|
||||
@ -156,7 +167,11 @@ module Jekyll
|
||||
def self.rename_index(old_name, new_name)
|
||||
Logger.verbose("I:Renaming `#{old_name}` to `#{new_name}`")
|
||||
return if Configurator.dry_run?
|
||||
::Algolia.move_index(old_name, new_name)
|
||||
begin
|
||||
::Algolia.move_index(old_name, new_name)
|
||||
rescue StandardError => error
|
||||
ErrorHandler.stop(error, new_name: new_name)
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Index content following the `atomic` indexing mode
|
||||
@ -177,14 +192,14 @@ module Jekyll
|
||||
|
||||
Logger.verbose("I:Using `#{index_tmp_name}` as temporary index")
|
||||
|
||||
# Pushing everthing to a brand new index
|
||||
update_records(index_tmp, records)
|
||||
|
||||
# Copying original settings to the new index
|
||||
remote_settings = remote_settings(index)
|
||||
new_settings = remote_settings.merge(Configurator.settings)
|
||||
update_settings(index_tmp, new_settings)
|
||||
|
||||
# Pushing everthing to a brand new index
|
||||
update_records(index_tmp, records)
|
||||
|
||||
# Renaming the new index in place of the old
|
||||
rename_index(index_tmp_name, index_name)
|
||||
end
|
||||
|
||||
@ -5,6 +5,63 @@ describe(Jekyll::Algolia::ErrorHandler) do
|
||||
let(:current) { Jekyll::Algolia::ErrorHandler }
|
||||
let(:configurator) { Jekyll::Algolia::Configurator }
|
||||
|
||||
describe '.error_hash' do
|
||||
subject { current.error_hash(message) }
|
||||
|
||||
context 'with a regular error message' do
|
||||
let(:message) do
|
||||
'Cannot POST to '\
|
||||
'https://MY_APP_ID.algolia.net/1/section/index_name/action: '\
|
||||
'{"message":"Custom message","status":403}'\
|
||||
"\n (403)"
|
||||
end
|
||||
|
||||
it do
|
||||
should include('verb' => 'POST')
|
||||
should include('scheme' => 'https')
|
||||
should include('application_id' => 'MY_APP_ID')
|
||||
should include('api_version' => 1)
|
||||
should include('api_section' => 'section')
|
||||
should include('index_name' => 'index_name')
|
||||
should include('api_action' => 'action')
|
||||
should include('message' => 'Custom message')
|
||||
should include('status' => 403)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a message with query parameters' do
|
||||
let(:message) do
|
||||
'Cannot POST to '\
|
||||
'https://MY_APP_ID.algolia.net/1/section/index_name/action?foo=bar: '\
|
||||
'{"message":"Custom message","status":403}'\
|
||||
"\n (403)"
|
||||
end
|
||||
|
||||
it do
|
||||
should include('foo' => 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an error message with weird characaters' do
|
||||
let(:message) do
|
||||
'Cannot POST to '\
|
||||
'https://MY_APP_ID.algolia.net/1/section/index_name$`!</action: '\
|
||||
'{"message":"Custom message","status":403}'\
|
||||
"\n (403)"
|
||||
end
|
||||
|
||||
it do
|
||||
should include('index_name' => 'index_name$`!<')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a malformed error message' do
|
||||
let(:message) { 'Unable to even parse this' }
|
||||
|
||||
it { should eq false }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.identify' do
|
||||
subject { current.identify(error, context) }
|
||||
|
||||
@ -72,7 +129,7 @@ describe(Jekyll::Algolia::ErrorHandler) do
|
||||
context 'with a record too big' do
|
||||
let(:message) do
|
||||
'400: Cannot POST to '\
|
||||
'https://MXM0JWJNIW.algolia.net/1/indexes/my_index/batch: '\
|
||||
'https://MY_APP_ID.algolia.net/1/indexes/my_index/batch: '\
|
||||
'{"message":"Record at the position 3 '\
|
||||
'objectID=deadbeef is too big size=1091966 bytes. '\
|
||||
'Contact us if you need an extended quota","position":3,'\
|
||||
@ -102,5 +159,47 @@ describe(Jekyll::Algolia::ErrorHandler) do
|
||||
expect(details).to include('size_limit' => '10 Kb')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an unknown setting' do
|
||||
let(:message) do
|
||||
'400: Cannot PUT to '\
|
||||
'https://MY_APP_ID.algolia.net/1/indexes/my_index/settings: '\
|
||||
'{"message":"Invalid object attributes: deadbeef near line:1 column:456",'\
|
||||
'"status":400} (400)'
|
||||
end
|
||||
let(:context) do
|
||||
{ settings:
|
||||
{
|
||||
'searchableAttributes' => %w[foo bar],
|
||||
'deadbeef' => 'foofoo'
|
||||
} }
|
||||
end
|
||||
|
||||
it { should include(name: 'unknown_settings') }
|
||||
it do
|
||||
details = subject[:details]
|
||||
expect(details).to include('setting_name' => 'deadbeef')
|
||||
expect(details).to include('setting_value' => 'foofoo')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an invalid index name' do
|
||||
before do
|
||||
allow(configurator)
|
||||
.to receive(:index_name)
|
||||
.and_return('invalid_index_name')
|
||||
end
|
||||
let(:message) do
|
||||
'400: Cannot GET to '\
|
||||
'https://MY_APP_ID-dsn.algolia.net/1/indexes/invalid_index_name/settings?getVersion=2: '\
|
||||
'{"message":"indexName is not valid","status":400} (400)'
|
||||
end
|
||||
|
||||
it { should include(name: 'invalid_index_name') }
|
||||
it do
|
||||
details = subject[:details]
|
||||
expect(details).to include('index_name' => 'invalid_index_name')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user