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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +146 -0
  3. data/.github/ISSUE_TEMPLATE.md +20 -0
  4. data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
  5. data/.gitignore +38 -0
  6. data/.rubocop.yml +186 -0
  7. data/.rubocop_todo.yml +14 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +18 -0
  10. data/LICENSE +21 -0
  11. data/README.md +56 -0
  12. data/Rakefile +45 -0
  13. data/Steepfile +6 -0
  14. data/algolia.gemspec +41 -0
  15. data/bin/console +21 -0
  16. data/bin/setup +8 -0
  17. data/lib/algolia.rb +42 -0
  18. data/lib/algolia/account_client.rb +65 -0
  19. data/lib/algolia/analytics_client.rb +105 -0
  20. data/lib/algolia/config/algolia_config.rb +40 -0
  21. data/lib/algolia/config/analytics_config.rb +20 -0
  22. data/lib/algolia/config/insights_config.rb +20 -0
  23. data/lib/algolia/config/recommendation_config.rb +20 -0
  24. data/lib/algolia/config/search_config.rb +40 -0
  25. data/lib/algolia/defaults.rb +35 -0
  26. data/lib/algolia/enums/call_type.rb +4 -0
  27. data/lib/algolia/enums/retry_outcome_type.rb +5 -0
  28. data/lib/algolia/error.rb +29 -0
  29. data/lib/algolia/helpers.rb +83 -0
  30. data/lib/algolia/http/http_requester.rb +84 -0
  31. data/lib/algolia/http/response.rb +23 -0
  32. data/lib/algolia/insights_client.rb +238 -0
  33. data/lib/algolia/iterators/base_iterator.rb +19 -0
  34. data/lib/algolia/iterators/object_iterator.rb +27 -0
  35. data/lib/algolia/iterators/paginator_iterator.rb +44 -0
  36. data/lib/algolia/iterators/rule_iterator.rb +9 -0
  37. data/lib/algolia/iterators/synonym_iterator.rb +9 -0
  38. data/lib/algolia/logger_helper.rb +14 -0
  39. data/lib/algolia/recommendation_client.rb +60 -0
  40. data/lib/algolia/responses/add_api_key_response.rb +38 -0
  41. data/lib/algolia/responses/base_response.rb +9 -0
  42. data/lib/algolia/responses/delete_api_key_response.rb +40 -0
  43. data/lib/algolia/responses/indexing_response.rb +28 -0
  44. data/lib/algolia/responses/multiple_batch_indexing_response.rb +29 -0
  45. data/lib/algolia/responses/multiple_response.rb +45 -0
  46. data/lib/algolia/responses/restore_api_key_response.rb +36 -0
  47. data/lib/algolia/responses/update_api_key_response.rb +39 -0
  48. data/lib/algolia/search_client.rb +614 -0
  49. data/lib/algolia/search_index.rb +1094 -0
  50. data/lib/algolia/transport/request_options.rb +94 -0
  51. data/lib/algolia/transport/retry_strategy.rb +117 -0
  52. data/lib/algolia/transport/stateful_host.rb +26 -0
  53. data/lib/algolia/transport/transport.rb +161 -0
  54. data/lib/algolia/user_agent.rb +25 -0
  55. data/lib/algolia/version.rb +3 -0
  56. data/sig/config/algolia_config.rbs +24 -0
  57. data/sig/config/analytics_config.rbs +11 -0
  58. data/sig/config/insights_config.rbs +11 -0
  59. data/sig/config/recommendation_config.rbs +11 -0
  60. data/sig/config/search_config.rbs +11 -0
  61. data/sig/enums/call_type.rbs +5 -0
  62. data/sig/helpers.rbs +12 -0
  63. data/sig/http/http_requester.rbs +17 -0
  64. data/sig/http/response.rbs +14 -0
  65. data/sig/interfaces/_connection.rbs +16 -0
  66. data/sig/iterators/base_iterator.rbs +15 -0
  67. data/sig/iterators/object_iterator.rbs +6 -0
  68. data/sig/iterators/paginator_iterator.rbs +8 -0
  69. data/sig/iterators/rule_iterator.rbs +5 -0
  70. data/sig/iterators/synonym_iterator.rbs +5 -0
  71. data/sig/transport/request_options.rbs +33 -0
  72. data/sig/transport/stateful_host.rbs +21 -0
  73. data/test/algolia/integration/account_client_test.rb +47 -0
  74. data/test/algolia/integration/analytics_client_test.rb +113 -0
  75. data/test/algolia/integration/base_test.rb +9 -0
  76. data/test/algolia/integration/insights_client_test.rb +80 -0
  77. data/test/algolia/integration/mocks/mock_requester.rb +45 -0
  78. data/test/algolia/integration/recommendation_client_test.rb +30 -0
  79. data/test/algolia/integration/search_client_test.rb +361 -0
  80. data/test/algolia/integration/search_index_test.rb +698 -0
  81. data/test/algolia/unit/helpers_test.rb +69 -0
  82. data/test/algolia/unit/retry_strategy_test.rb +139 -0
  83. data/test/algolia/unit/user_agent_test.rb +16 -0
  84. data/test/test_helper.rb +89 -0
  85. data/upgrade_guide.md +595 -0
  86. 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,3 @@
1
+ module Algolia
2
+ VERSION = '2.0.0-alpha.2'.freeze
3
+ 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