algolia 2.0.0.pre.alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.circleci/config.yml +146 -0
- data/.github/ISSUE_TEMPLATE.md +20 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- data/.gitignore +38 -0
- data/.rubocop.yml +186 -0
- data/.rubocop_todo.yml +14 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +18 -0
- data/LICENSE +21 -0
- data/README.md +56 -0
- data/Rakefile +45 -0
- data/Steepfile +6 -0
- data/algolia.gemspec +41 -0
- data/bin/console +21 -0
- data/bin/setup +8 -0
- data/lib/algolia.rb +42 -0
- data/lib/algolia/account_client.rb +65 -0
- data/lib/algolia/analytics_client.rb +105 -0
- data/lib/algolia/config/algolia_config.rb +40 -0
- data/lib/algolia/config/analytics_config.rb +20 -0
- data/lib/algolia/config/insights_config.rb +20 -0
- data/lib/algolia/config/recommendation_config.rb +20 -0
- data/lib/algolia/config/search_config.rb +40 -0
- data/lib/algolia/defaults.rb +35 -0
- data/lib/algolia/enums/call_type.rb +4 -0
- data/lib/algolia/enums/retry_outcome_type.rb +5 -0
- data/lib/algolia/error.rb +29 -0
- data/lib/algolia/helpers.rb +83 -0
- data/lib/algolia/http/http_requester.rb +84 -0
- data/lib/algolia/http/response.rb +23 -0
- data/lib/algolia/insights_client.rb +238 -0
- data/lib/algolia/iterators/base_iterator.rb +19 -0
- data/lib/algolia/iterators/object_iterator.rb +27 -0
- data/lib/algolia/iterators/paginator_iterator.rb +44 -0
- data/lib/algolia/iterators/rule_iterator.rb +9 -0
- data/lib/algolia/iterators/synonym_iterator.rb +9 -0
- data/lib/algolia/logger_helper.rb +14 -0
- data/lib/algolia/recommendation_client.rb +60 -0
- data/lib/algolia/responses/add_api_key_response.rb +38 -0
- data/lib/algolia/responses/base_response.rb +9 -0
- data/lib/algolia/responses/delete_api_key_response.rb +40 -0
- data/lib/algolia/responses/indexing_response.rb +28 -0
- data/lib/algolia/responses/multiple_batch_indexing_response.rb +29 -0
- data/lib/algolia/responses/multiple_response.rb +45 -0
- data/lib/algolia/responses/restore_api_key_response.rb +36 -0
- data/lib/algolia/responses/update_api_key_response.rb +39 -0
- data/lib/algolia/search_client.rb +614 -0
- data/lib/algolia/search_index.rb +1094 -0
- data/lib/algolia/transport/request_options.rb +94 -0
- data/lib/algolia/transport/retry_strategy.rb +117 -0
- data/lib/algolia/transport/stateful_host.rb +26 -0
- data/lib/algolia/transport/transport.rb +161 -0
- data/lib/algolia/user_agent.rb +25 -0
- data/lib/algolia/version.rb +3 -0
- data/sig/config/algolia_config.rbs +24 -0
- data/sig/config/analytics_config.rbs +11 -0
- data/sig/config/insights_config.rbs +11 -0
- data/sig/config/recommendation_config.rbs +11 -0
- data/sig/config/search_config.rbs +11 -0
- data/sig/enums/call_type.rbs +5 -0
- data/sig/helpers.rbs +12 -0
- data/sig/http/http_requester.rbs +17 -0
- data/sig/http/response.rbs +14 -0
- data/sig/interfaces/_connection.rbs +16 -0
- data/sig/iterators/base_iterator.rbs +15 -0
- data/sig/iterators/object_iterator.rbs +6 -0
- data/sig/iterators/paginator_iterator.rbs +8 -0
- data/sig/iterators/rule_iterator.rbs +5 -0
- data/sig/iterators/synonym_iterator.rbs +5 -0
- data/sig/transport/request_options.rbs +33 -0
- data/sig/transport/stateful_host.rbs +21 -0
- data/test/algolia/integration/account_client_test.rb +47 -0
- data/test/algolia/integration/analytics_client_test.rb +113 -0
- data/test/algolia/integration/base_test.rb +9 -0
- data/test/algolia/integration/insights_client_test.rb +80 -0
- data/test/algolia/integration/mocks/mock_requester.rb +45 -0
- data/test/algolia/integration/recommendation_client_test.rb +30 -0
- data/test/algolia/integration/search_client_test.rb +361 -0
- data/test/algolia/integration/search_index_test.rb +698 -0
- data/test/algolia/unit/helpers_test.rb +69 -0
- data/test/algolia/unit/retry_strategy_test.rb +139 -0
- data/test/algolia/unit/user_agent_test.rb +16 -0
- data/test/test_helper.rb +89 -0
- data/upgrade_guide.md +595 -0
- metadata +307 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module Algolia
|
2
|
+
module Transport
|
3
|
+
class RequestOptions
|
4
|
+
attr_accessor :headers, :params, :data, :timeout, :connect_timeout, :compression_type
|
5
|
+
|
6
|
+
# @param [Search::Config] config
|
7
|
+
#
|
8
|
+
def initialize(config)
|
9
|
+
@headers = {}
|
10
|
+
@params = {}
|
11
|
+
@data = {}
|
12
|
+
@timeout = nil
|
13
|
+
@connect_timeout = nil
|
14
|
+
@compression_type = config.compression_type
|
15
|
+
end
|
16
|
+
|
17
|
+
# Create and format headers and params from request options
|
18
|
+
#
|
19
|
+
# @param opts [Hash]
|
20
|
+
#
|
21
|
+
def create(opts = {})
|
22
|
+
add_headers(opts)
|
23
|
+
add_params(opts)
|
24
|
+
add_timeout(opts)
|
25
|
+
add_connect_timeout(opts)
|
26
|
+
add_compression_type(opts)
|
27
|
+
add_data_body(opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add or update headers
|
31
|
+
#
|
32
|
+
# @param opts [Hash]
|
33
|
+
#
|
34
|
+
def add_headers(opts = {})
|
35
|
+
unless opts[:headers].nil?
|
36
|
+
opts[:headers].each do |opt, value|
|
37
|
+
@headers[opt.to_sym] = value
|
38
|
+
end
|
39
|
+
opts.delete(:headers)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Add or update query parameters
|
44
|
+
#
|
45
|
+
# @param opts [Hash]
|
46
|
+
#
|
47
|
+
def add_params(opts = {})
|
48
|
+
unless opts[:params].nil?
|
49
|
+
opts[:params].each do |opt, value|
|
50
|
+
@params[opt.to_sym] = value
|
51
|
+
end
|
52
|
+
opts.delete(:params)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Add or update timeout
|
57
|
+
#
|
58
|
+
# @param opts [Hash]
|
59
|
+
#
|
60
|
+
def add_timeout(opts = {})
|
61
|
+
@timeout = opts[:timeout] || @timeout
|
62
|
+
opts.delete(:timeout)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add or update connect timeout
|
66
|
+
#
|
67
|
+
# @param opts [Hash]
|
68
|
+
#
|
69
|
+
def add_connect_timeout(opts = {})
|
70
|
+
@connect_timeout = opts[:connect_timeout] || @connect_timeout
|
71
|
+
opts.delete(:connect_timeout)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Add or update compression_type
|
75
|
+
#
|
76
|
+
# @param opts [Hash]
|
77
|
+
#
|
78
|
+
def add_compression_type(opts = {})
|
79
|
+
@compression_type = opts[:compression_type] || @compression_type
|
80
|
+
opts.delete(:compression_type)
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param opts [Hash]
|
84
|
+
#
|
85
|
+
def add_data_body(opts = {})
|
86
|
+
unless opts.empty?
|
87
|
+
opts.each do |key, value|
|
88
|
+
@data[key.to_sym] = value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Algolia
|
2
|
+
module Transport
|
3
|
+
# Class RetryStatregy
|
4
|
+
class RetryStrategy
|
5
|
+
include RetryOutcomeType
|
6
|
+
|
7
|
+
# @param config [Search::Config] config which contains the hosts
|
8
|
+
#
|
9
|
+
def initialize(config)
|
10
|
+
@hosts = config.default_hosts
|
11
|
+
@lock = Mutex.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# Retrieves the tryable hosts
|
15
|
+
#
|
16
|
+
# @param call_type [binary] type of the host
|
17
|
+
#
|
18
|
+
# @return [Array] list of StatefulHost
|
19
|
+
#
|
20
|
+
def get_tryable_hosts(call_type)
|
21
|
+
@lock.synchronize do
|
22
|
+
reset_expired_hosts
|
23
|
+
|
24
|
+
if @hosts.any? { |host| host.up && flag?(host.accept, call_type) }
|
25
|
+
@hosts.select { |host| host.up && flag?(host.accept, call_type) }
|
26
|
+
else
|
27
|
+
@hosts.each do |host|
|
28
|
+
reset(host) if flag?(host.accept, call_type)
|
29
|
+
end
|
30
|
+
@hosts
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Decides on the outcome of the request
|
36
|
+
#
|
37
|
+
# @param tryable_host [StatefulHost] host to test against
|
38
|
+
# @param http_response_code [Integer] http response code
|
39
|
+
# @param is_timed_out [Boolean] whether or not the request timed out
|
40
|
+
#
|
41
|
+
# @return [Binary] retry outcome code
|
42
|
+
#
|
43
|
+
def decide(tryable_host, http_response_code: nil, is_timed_out: false, network_failure: false)
|
44
|
+
@lock.synchronize do
|
45
|
+
if !is_timed_out && success?(http_response_code)
|
46
|
+
tryable_host.up = true
|
47
|
+
tryable_host.last_use = Time.now.utc
|
48
|
+
SUCCESS
|
49
|
+
elsif !is_timed_out && retryable?(http_response_code, network_failure)
|
50
|
+
tryable_host.up = false
|
51
|
+
tryable_host.last_use = Time.now.utc
|
52
|
+
RETRY
|
53
|
+
elsif is_timed_out
|
54
|
+
tryable_host.up = true
|
55
|
+
tryable_host.last_use = Time.now.utc
|
56
|
+
tryable_host.retry_count += 1
|
57
|
+
RETRY
|
58
|
+
else
|
59
|
+
FAILURE
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# @param http_response_code [Integer]
|
67
|
+
#
|
68
|
+
# @return [Boolean]
|
69
|
+
#
|
70
|
+
def success?(http_response_code)
|
71
|
+
!http_response_code.nil? && (http_response_code.to_i / 100).floor == 2
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param http_response_code [Integer]
|
75
|
+
#
|
76
|
+
# @return [Boolean]
|
77
|
+
#
|
78
|
+
def retryable?(http_response_code, network_failure)
|
79
|
+
if network_failure
|
80
|
+
return true
|
81
|
+
end
|
82
|
+
|
83
|
+
!http_response_code.nil? && (http_response_code.to_i / 100).floor != 2 && (http_response_code.to_i / 100).floor != 4
|
84
|
+
end
|
85
|
+
|
86
|
+
# Iterates in the hosts list and reset the ones that are down
|
87
|
+
#
|
88
|
+
def reset_expired_hosts
|
89
|
+
@hosts.each do |host|
|
90
|
+
host_last_usage = Time.now.utc - host.last_use
|
91
|
+
reset(host) if !host.up && host_last_usage.to_i > Defaults::TTL
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Reset a single host
|
96
|
+
#
|
97
|
+
# @param host [StatefulHost]
|
98
|
+
#
|
99
|
+
def reset(host)
|
100
|
+
host.up = true
|
101
|
+
host.retry_count = 0
|
102
|
+
host.last_use = Time.now.utc
|
103
|
+
end
|
104
|
+
|
105
|
+
# Make a binary check to know whether the item contains the flag
|
106
|
+
#
|
107
|
+
# @param item [binary] item to check
|
108
|
+
# @param flag [binary] flag to find in the item
|
109
|
+
#
|
110
|
+
# @return [Boolean]
|
111
|
+
#
|
112
|
+
def flag?(item, flag)
|
113
|
+
(item & (1 << (flag - 1))) > 0
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Algolia
|
2
|
+
module Transport
|
3
|
+
# Class StatefulHost
|
4
|
+
class StatefulHost
|
5
|
+
include CallType
|
6
|
+
|
7
|
+
attr_reader :url, :protocol, :accept
|
8
|
+
attr_accessor :last_use, :retry_count, :up
|
9
|
+
|
10
|
+
# @param url [String] host url
|
11
|
+
# @option options [binary] :accept accept type flag
|
12
|
+
# @option options [DateTime] :last_use last usage date
|
13
|
+
# @option options [Integer] :retry_count number of retries
|
14
|
+
# @option options [Boolean] :up host status
|
15
|
+
#
|
16
|
+
def initialize(url, opts = {})
|
17
|
+
@url = url
|
18
|
+
@protocol = opts[:protocol] || 'https://'
|
19
|
+
@accept = opts[:accept] || (READ | WRITE)
|
20
|
+
@last_use = opts[:last_use] || Time.now.utc
|
21
|
+
@retry_count = opts[:retry_count] || 0
|
22
|
+
@up = opts.has_key?(:up) ? opts[:up] : true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Algolia
|
4
|
+
module Transport
|
5
|
+
class Transport
|
6
|
+
include RetryOutcomeType
|
7
|
+
include CallType
|
8
|
+
include Helpers
|
9
|
+
|
10
|
+
# @param config [Search::Config] config used for search
|
11
|
+
# @param requester [Object] requester used for sending requests. Uses Algolia::Http::HttpRequester by default
|
12
|
+
#
|
13
|
+
def initialize(config, requester)
|
14
|
+
@config = config
|
15
|
+
@http_requester = requester
|
16
|
+
@retry_strategy = RetryStrategy.new(config)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Build a request with call type READ
|
20
|
+
#
|
21
|
+
# @param method [Symbol] method used for request
|
22
|
+
# @param path [String] path of the request
|
23
|
+
# @param body [Hash] request body
|
24
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
25
|
+
#
|
26
|
+
def read(method, path, body = {}, opts = {})
|
27
|
+
request(READ, method, path, body, opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Build a request with call type WRITE
|
31
|
+
#
|
32
|
+
# @param method [Symbol] method used for request
|
33
|
+
# @param path [String] path of the request
|
34
|
+
# @param body [Hash] request body
|
35
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
36
|
+
#
|
37
|
+
def write(method, path, body = {}, opts = {})
|
38
|
+
request(WRITE, method, path, body, opts)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param call_type [Binary] READ or WRITE operation
|
42
|
+
# @param method [Symbol] method used for request
|
43
|
+
# @param path [String] path of the request
|
44
|
+
# @param body [Hash] request body
|
45
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
46
|
+
#
|
47
|
+
# @return [Response] response of the request
|
48
|
+
#
|
49
|
+
def request(call_type, method, path, body = {}, opts = {})
|
50
|
+
@retry_strategy.get_tryable_hosts(call_type).each do |host|
|
51
|
+
opts[:timeout] ||= get_timeout(call_type) * (host.retry_count + 1)
|
52
|
+
opts[:connect_timeout] ||= @config.connect_timeout * (host.retry_count + 1)
|
53
|
+
|
54
|
+
request_options = RequestOptions.new(@config)
|
55
|
+
request_options.create(opts)
|
56
|
+
request_options.params.merge!(request_options.data) if method == :GET
|
57
|
+
|
58
|
+
request = build_request(method, path, body, request_options)
|
59
|
+
response = @http_requester.send_request(
|
60
|
+
host,
|
61
|
+
request[:method],
|
62
|
+
request[:path],
|
63
|
+
request[:body],
|
64
|
+
request[:headers],
|
65
|
+
request[:timeout],
|
66
|
+
request[:connect_timeout]
|
67
|
+
)
|
68
|
+
|
69
|
+
outcome = @retry_strategy.decide(host, http_response_code: response.status, is_timed_out: response.has_timed_out, network_failure: response.network_failure)
|
70
|
+
if outcome == FAILURE
|
71
|
+
decoded_error = json_to_hash(response.error, @config.symbolize_keys)
|
72
|
+
raise AlgoliaHttpError.new(get_option(decoded_error, 'status'), get_option(decoded_error, 'message'))
|
73
|
+
end
|
74
|
+
return json_to_hash(response.body, @config.symbolize_keys) unless outcome == RETRY
|
75
|
+
end
|
76
|
+
|
77
|
+
raise AlgoliaUnreachableHostError, 'Unreachable hosts'
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Parse the different information and build the request
|
83
|
+
#
|
84
|
+
# @param [Symbol] method
|
85
|
+
# @param [String] path
|
86
|
+
# @param [Hash] body
|
87
|
+
# @param [RequestOptions] request_options
|
88
|
+
#
|
89
|
+
# @return [Hash]
|
90
|
+
#
|
91
|
+
def build_request(method, path, body, request_options)
|
92
|
+
request = {}
|
93
|
+
request[:method] = method.downcase
|
94
|
+
request[:path] = build_uri_path(path, request_options.params)
|
95
|
+
request[:body] = build_body(body, request_options, method)
|
96
|
+
request[:headers] = generate_headers(request_options)
|
97
|
+
request
|
98
|
+
end
|
99
|
+
|
100
|
+
# Build the uri from path and additional params
|
101
|
+
#
|
102
|
+
# @param [Object] path
|
103
|
+
# @param [Object] params
|
104
|
+
#
|
105
|
+
# @return [String]
|
106
|
+
#
|
107
|
+
def build_uri_path(path, params)
|
108
|
+
path + handle_params(params)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Build the body of the request
|
112
|
+
#
|
113
|
+
# @param [Hash] body
|
114
|
+
#
|
115
|
+
# @return [Hash]
|
116
|
+
#
|
117
|
+
def build_body(body, request_options, method)
|
118
|
+
if method == :GET && body.empty?
|
119
|
+
return nil
|
120
|
+
end
|
121
|
+
|
122
|
+
# merge optional special request options to the body when it
|
123
|
+
# doesn't have to be in the array format
|
124
|
+
body.merge!(request_options.data) if body.is_a?(Hash) && method != :GET
|
125
|
+
to_json(body)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Generates headers from config headers and optional parameters
|
129
|
+
#
|
130
|
+
# @option options [String] :headers
|
131
|
+
#
|
132
|
+
# @return [Hash] merged headers
|
133
|
+
#
|
134
|
+
def generate_headers(request_options = {})
|
135
|
+
headers = {}
|
136
|
+
extra_headers = request_options.headers || {}
|
137
|
+
@config.default_headers.each { |key, val| headers[key.to_s] = val }
|
138
|
+
extra_headers.each { |key, val| headers[key.to_s] = val }
|
139
|
+
if request_options.compression_type == Defaults::GZIP_ENCODING
|
140
|
+
headers['Accept-Encoding'] = Defaults::GZIP_ENCODING
|
141
|
+
end
|
142
|
+
headers
|
143
|
+
end
|
144
|
+
|
145
|
+
# Retrieves a timeout according to call_type
|
146
|
+
#
|
147
|
+
# @param call_type [Binary] requested call type
|
148
|
+
#
|
149
|
+
# @return [Integer]
|
150
|
+
#
|
151
|
+
def get_timeout(call_type)
|
152
|
+
case call_type
|
153
|
+
when READ
|
154
|
+
@config.read_timeout
|
155
|
+
else
|
156
|
+
@config.write_timeout
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Algolia
|
2
|
+
class UserAgent
|
3
|
+
attr_accessor :value
|
4
|
+
|
5
|
+
@@value = Defaults::USER_AGENT
|
6
|
+
|
7
|
+
# Set the value of the UserAgent
|
8
|
+
#
|
9
|
+
def self.value
|
10
|
+
@@value
|
11
|
+
end
|
12
|
+
|
13
|
+
# Resets the value of the UserAgent
|
14
|
+
#
|
15
|
+
def self.reset_to_default
|
16
|
+
@@value = Defaults::USER_AGENT
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds a segment to the UserAgent
|
20
|
+
#
|
21
|
+
def self.add(segment, version)
|
22
|
+
@@value += format('; %<segment>s (%<version>s)', segment: segment, version: version)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Algolia
|
2
|
+
# Class AlgoliaConfig
|
3
|
+
class AlgoliaConfig
|
4
|
+
attr_accessor app_id: String
|
5
|
+
|
6
|
+
attr_accessor api_key: String
|
7
|
+
|
8
|
+
attr_accessor default_headers: Hash[String, String]
|
9
|
+
|
10
|
+
attr_accessor batch_size: Integer
|
11
|
+
|
12
|
+
attr_accessor read_timeout: Integer
|
13
|
+
|
14
|
+
attr_accessor write_timeout: Integer
|
15
|
+
|
16
|
+
attr_accessor connect_timeout: Integer
|
17
|
+
|
18
|
+
attr_accessor compression_type: String
|
19
|
+
|
20
|
+
attr_accessor symbolize_keys: bool
|
21
|
+
|
22
|
+
def initialize: (?::Hash[Symbol, String|[String]] opts) -> void
|
23
|
+
end
|
24
|
+
end
|