typesense 0.2.0 → 0.4.0
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 +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +36 -21
- data/README.md +7 -0
- data/codecov.yml +10 -0
- data/examples/aliases.rb +10 -32
- data/examples/client_initialization.rb +67 -0
- data/examples/collections_and_documents.rb +36 -59
- data/examples/overrides.rb +13 -34
- data/examples/search.rb +17 -44
- data/lib/typesense/alias.rb +5 -5
- data/lib/typesense/aliases.rb +5 -5
- data/lib/typesense/api_call.rb +183 -76
- data/lib/typesense/client.rb +4 -3
- data/lib/typesense/collection.rb +7 -7
- data/lib/typesense/collections.rb +6 -6
- data/lib/typesense/configuration.rb +35 -19
- data/lib/typesense/debug.rb +3 -3
- data/lib/typesense/document.rb +4 -4
- data/lib/typesense/documents.rb +11 -6
- data/lib/typesense/error.rb +6 -0
- data/lib/typesense/override.rb +4 -4
- data/lib/typesense/overrides.rb +5 -5
- data/lib/typesense/version.rb +1 -1
- data/typesense.gemspec +7 -6
- metadata +30 -14
data/examples/overrides.rb
CHANGED
@@ -3,30 +3,9 @@
|
|
3
3
|
##
|
4
4
|
# These examples walk you through operations specifically related to overrides
|
5
5
|
# This is a Typesense Premium feature (see: https://typesense.org/premium)
|
6
|
+
# Be sure to add `--license-key=<>` as a parameter, when starting a Typesense Premium server
|
6
7
|
|
7
|
-
require_relative '
|
8
|
-
require 'awesome_print'
|
9
|
-
|
10
|
-
AwesomePrint.defaults = {
|
11
|
-
indent: -2
|
12
|
-
}
|
13
|
-
|
14
|
-
##
|
15
|
-
# Setup
|
16
|
-
#
|
17
|
-
# Start the master
|
18
|
-
# $ docker run -p 8108:8108 -it -v/tmp/typesense-data-master/:/data -it typesense/typesense:0.8.0-rc1 --data-dir /data --api-key=abcd --listen-port 8108 --license-key=<>
|
19
|
-
|
20
|
-
##
|
21
|
-
# Create a client
|
22
|
-
typesense = Typesense::Client.new(
|
23
|
-
master_node: {
|
24
|
-
host: 'localhost',
|
25
|
-
port: 8108,
|
26
|
-
protocol: 'http',
|
27
|
-
api_key: 'abcd'
|
28
|
-
}
|
29
|
-
)
|
8
|
+
require_relative './client_initialization'
|
30
9
|
|
31
10
|
##
|
32
11
|
# Create a collection
|
@@ -50,31 +29,31 @@ schema = {
|
|
50
29
|
'default_sorting_field' => 'num_employees'
|
51
30
|
}
|
52
31
|
|
53
|
-
typesense.collections.create(schema)
|
32
|
+
@typesense.collections.create(schema)
|
54
33
|
|
55
34
|
# Let's create a couple documents for us to use in our search examples
|
56
|
-
typesense.collections['companies'].documents.create(
|
35
|
+
@typesense.collections['companies'].documents.create(
|
57
36
|
'id' => '124',
|
58
37
|
'company_name' => 'Stark Industries',
|
59
38
|
'num_employees' => 5215,
|
60
39
|
'country' => 'USA'
|
61
40
|
)
|
62
41
|
|
63
|
-
typesense.collections['companies'].documents.create(
|
42
|
+
@typesense.collections['companies'].documents.create(
|
64
43
|
'id' => '127',
|
65
44
|
'company_name' => 'Stark Corp',
|
66
45
|
'num_employees' => 1031,
|
67
46
|
'country' => 'USA'
|
68
47
|
)
|
69
48
|
|
70
|
-
typesense.collections['companies'].documents.create(
|
49
|
+
@typesense.collections['companies'].documents.create(
|
71
50
|
'id' => '125',
|
72
51
|
'company_name' => 'Acme Corp',
|
73
52
|
'num_employees' => 1002,
|
74
53
|
'country' => 'France'
|
75
54
|
)
|
76
55
|
|
77
|
-
typesense.collections['companies'].documents.create(
|
56
|
+
@typesense.collections['companies'].documents.create(
|
78
57
|
'id' => '126',
|
79
58
|
'company_name' => 'Doofenshmirtz Inc',
|
80
59
|
'num_employees' => 2,
|
@@ -84,7 +63,7 @@ typesense.collections['companies'].documents.create(
|
|
84
63
|
##
|
85
64
|
# Create overrides
|
86
65
|
|
87
|
-
typesense.collections['companies'].overrides.create(
|
66
|
+
@typesense.collections['companies'].overrides.create(
|
88
67
|
"id": 'promote-doofenshmirtz',
|
89
68
|
"rule": {
|
90
69
|
"query": 'doofen',
|
@@ -92,7 +71,7 @@ typesense.collections['companies'].overrides.create(
|
|
92
71
|
},
|
93
72
|
"includes": [{ 'id' => '126', 'position' => 1 }]
|
94
73
|
)
|
95
|
-
typesense.collections['companies'].overrides.create(
|
74
|
+
@typesense.collections['companies'].overrides.create(
|
96
75
|
"id": 'promote-acme',
|
97
76
|
"rule": {
|
98
77
|
"query": 'stark',
|
@@ -103,19 +82,19 @@ typesense.collections['companies'].overrides.create(
|
|
103
82
|
|
104
83
|
##
|
105
84
|
# Search for documents
|
106
|
-
results = typesense.collections['companies'].documents.search(
|
85
|
+
results = @typesense.collections['companies'].documents.search(
|
107
86
|
'q' => 'doofen',
|
108
87
|
'query_by' => 'company_name'
|
109
88
|
)
|
110
89
|
ap results
|
111
90
|
|
112
|
-
results = typesense.collections['companies'].documents.search(
|
91
|
+
results = @typesense.collections['companies'].documents.search(
|
113
92
|
'q' => 'stark',
|
114
93
|
'query_by' => 'company_name'
|
115
94
|
)
|
116
95
|
ap results
|
117
96
|
|
118
|
-
results = typesense.collections['companies'].documents.search(
|
97
|
+
results = @typesense.collections['companies'].documents.search(
|
119
98
|
'q' => 'Inc',
|
120
99
|
'query_by' => 'company_name',
|
121
100
|
'filter_by' => 'num_employees:<100',
|
@@ -126,4 +105,4 @@ ap results
|
|
126
105
|
##
|
127
106
|
# Cleanup
|
128
107
|
# Drop the collection
|
129
|
-
typesense.collections['companies'].delete
|
108
|
+
@typesense.collections['companies'].delete
|
data/examples/search.rb
CHANGED
@@ -3,41 +3,7 @@
|
|
3
3
|
##
|
4
4
|
# These examples walk you through operations specifically related to search
|
5
5
|
|
6
|
-
require_relative '
|
7
|
-
require 'awesome_print'
|
8
|
-
|
9
|
-
AwesomePrint.defaults = {
|
10
|
-
indent: -2
|
11
|
-
}
|
12
|
-
|
13
|
-
##
|
14
|
-
# Setup
|
15
|
-
#
|
16
|
-
# Start the master
|
17
|
-
# $ docker run -p 8108:8108 -it -v/tmp/typesense-data-master/:/data -it typesense/typesense:0.8.0-rc1 --data-dir /data --api-key=abcd --listen-port 8108
|
18
|
-
#
|
19
|
-
# Start the read replica
|
20
|
-
# $ docker run -p 8109:8109 -it -v/tmp/typesense-data-read-replica-1/:/data -it typesense/typesense:0.8.0-rc1 --data-dir /data --api-key=wxyz --listen-port 8109 --master http://localhost:8108
|
21
|
-
|
22
|
-
##
|
23
|
-
# Create a client
|
24
|
-
typesense = Typesense::Client.new(
|
25
|
-
master_node: {
|
26
|
-
host: 'localhost',
|
27
|
-
port: 8108,
|
28
|
-
protocol: 'http',
|
29
|
-
api_key: 'abcd'
|
30
|
-
},
|
31
|
-
read_replica_nodes: [
|
32
|
-
{
|
33
|
-
host: 'localhost',
|
34
|
-
port: 8109,
|
35
|
-
protocol: 'http',
|
36
|
-
api_key: 'wxyz'
|
37
|
-
}
|
38
|
-
],
|
39
|
-
timeout_seconds: 10
|
40
|
-
)
|
6
|
+
require_relative './client_initialization'
|
41
7
|
|
42
8
|
##
|
43
9
|
# Create a collection
|
@@ -61,31 +27,38 @@ schema = {
|
|
61
27
|
'default_sorting_field' => 'num_employees'
|
62
28
|
}
|
63
29
|
|
64
|
-
|
30
|
+
# Delete the collection if it already exists
|
31
|
+
begin
|
32
|
+
@typesense.collections['companies'].delete
|
33
|
+
rescue Typesense::Error::ObjectNotFound
|
34
|
+
end
|
35
|
+
|
36
|
+
# Now create the collection
|
37
|
+
@typesense.collections.create(schema)
|
65
38
|
|
66
39
|
# Let's create a couple documents for us to use in our search examples
|
67
|
-
typesense.collections['companies'].documents.create(
|
40
|
+
@typesense.collections['companies'].documents.create(
|
68
41
|
'id' => '124',
|
69
42
|
'company_name' => 'Stark Industries',
|
70
43
|
'num_employees' => 5215,
|
71
44
|
'country' => 'USA'
|
72
45
|
)
|
73
46
|
|
74
|
-
typesense.collections['companies'].documents.create(
|
47
|
+
@typesense.collections['companies'].documents.create(
|
75
48
|
'id' => '127',
|
76
49
|
'company_name' => 'Stark Corp',
|
77
50
|
'num_employees' => 1031,
|
78
51
|
'country' => 'USA'
|
79
52
|
)
|
80
53
|
|
81
|
-
typesense.collections['companies'].documents.create(
|
54
|
+
@typesense.collections['companies'].documents.create(
|
82
55
|
'id' => '125',
|
83
56
|
'company_name' => 'Acme Corp',
|
84
57
|
'num_employees' => 1002,
|
85
58
|
'country' => 'France'
|
86
59
|
)
|
87
60
|
|
88
|
-
typesense.collections['companies'].documents.create(
|
61
|
+
@typesense.collections['companies'].documents.create(
|
89
62
|
'id' => '126',
|
90
63
|
'company_name' => 'Doofenshmirtz Inc',
|
91
64
|
'num_employees' => 2,
|
@@ -94,7 +67,7 @@ typesense.collections['companies'].documents.create(
|
|
94
67
|
|
95
68
|
##
|
96
69
|
# Search for documents
|
97
|
-
results = typesense.collections['companies'].documents.search(
|
70
|
+
results = @typesense.collections['companies'].documents.search(
|
98
71
|
'q' => 'Stark',
|
99
72
|
'query_by' => 'company_name'
|
100
73
|
)
|
@@ -133,7 +106,7 @@ ap results
|
|
133
106
|
|
134
107
|
##
|
135
108
|
# Search for more documents
|
136
|
-
results = typesense.collections['companies'].documents.search(
|
109
|
+
results = @typesense.collections['companies'].documents.search(
|
137
110
|
'q' => 'Inc',
|
138
111
|
'query_by' => 'company_name',
|
139
112
|
'filter_by' => 'num_employees:<100',
|
@@ -163,7 +136,7 @@ ap results
|
|
163
136
|
|
164
137
|
##
|
165
138
|
# Search for more documents
|
166
|
-
results = typesense.collections['companies'].documents.search(
|
139
|
+
results = @typesense.collections['companies'].documents.search(
|
167
140
|
'q' => 'Non-existent',
|
168
141
|
'query_by' => 'company_name'
|
169
142
|
)
|
@@ -179,4 +152,4 @@ ap results
|
|
179
152
|
##
|
180
153
|
# Cleanup
|
181
154
|
# Drop the collection
|
182
|
-
typesense.collections['companies'].delete
|
155
|
+
@typesense.collections['companies'].delete
|
data/lib/typesense/alias.rb
CHANGED
@@ -2,17 +2,17 @@
|
|
2
2
|
|
3
3
|
module Typesense
|
4
4
|
class Alias
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@
|
5
|
+
def initialize(name, api_call)
|
6
|
+
@name = name
|
7
|
+
@api_call = api_call
|
8
8
|
end
|
9
9
|
|
10
10
|
def retrieve
|
11
|
-
|
11
|
+
@api_call.get(endpoint_path)
|
12
12
|
end
|
13
13
|
|
14
14
|
def delete
|
15
|
-
|
15
|
+
@api_call.delete(endpoint_path)
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
data/lib/typesense/aliases.rb
CHANGED
@@ -4,21 +4,21 @@ module Typesense
|
|
4
4
|
class Aliases
|
5
5
|
RESOURCE_PATH = '/aliases'
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@
|
7
|
+
def initialize(api_call)
|
8
|
+
@api_call = api_call
|
9
9
|
@aliases = {}
|
10
10
|
end
|
11
11
|
|
12
12
|
def upsert(alias_name, mapping)
|
13
|
-
|
13
|
+
@api_call.put(endpoint_path(alias_name), mapping)
|
14
14
|
end
|
15
15
|
|
16
16
|
def retrieve
|
17
|
-
|
17
|
+
@api_call.get(RESOURCE_PATH)
|
18
18
|
end
|
19
19
|
|
20
20
|
def [](alias_name)
|
21
|
-
@aliases[alias_name] ||= Alias.new(
|
21
|
+
@aliases[alias_name] ||= Alias.new(alias_name, @api_call)
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
data/lib/typesense/api_call.rb
CHANGED
@@ -10,116 +10,223 @@ module Typesense
|
|
10
10
|
|
11
11
|
def initialize(configuration)
|
12
12
|
@configuration = configuration
|
13
|
+
|
14
|
+
@api_key = @configuration.api_key
|
15
|
+
@nodes = @configuration.nodes.dup # Make a copy, since we'll be adding additional metadata to the nodes
|
16
|
+
@nearest_node = @configuration.nearest_node.dup
|
17
|
+
@connection_timeout_seconds = @configuration.connection_timeout_seconds
|
18
|
+
@healthcheck_interval_seconds = @configuration.healthcheck_interval_seconds
|
19
|
+
@num_retries_per_request = @configuration.num_retries
|
20
|
+
@retry_interval_seconds = @configuration.retry_interval_seconds
|
21
|
+
|
22
|
+
@logger = @configuration.logger
|
23
|
+
|
24
|
+
initialize_metadata_for_nodes
|
25
|
+
@current_node_index = -1
|
13
26
|
end
|
14
27
|
|
15
28
|
def post(endpoint, parameters = {})
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end.parsed_response
|
29
|
+
headers, body = extract_headers_and_body_from(parameters)
|
30
|
+
|
31
|
+
perform_request :post,
|
32
|
+
endpoint,
|
33
|
+
body: body,
|
34
|
+
headers: default_headers.merge(headers)
|
23
35
|
end
|
24
36
|
|
25
37
|
def put(endpoint, parameters = {})
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end.parsed_response
|
38
|
+
headers, body = extract_headers_and_body_from(parameters)
|
39
|
+
|
40
|
+
perform_request :put,
|
41
|
+
endpoint,
|
42
|
+
body: body,
|
43
|
+
headers: default_headers.merge(headers)
|
33
44
|
end
|
34
45
|
|
35
46
|
def get(endpoint, parameters = {})
|
36
|
-
|
37
|
-
end
|
47
|
+
headers, query = extract_headers_and_query_from(parameters)
|
38
48
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
query: parameters,
|
44
|
-
headers: default_headers
|
45
|
-
))
|
46
|
-
end
|
49
|
+
perform_request :get,
|
50
|
+
endpoint,
|
51
|
+
query: query,
|
52
|
+
headers: default_headers.merge(headers)
|
47
53
|
end
|
48
54
|
|
49
55
|
def delete(endpoint, parameters = {})
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
56
|
+
headers, query = extract_headers_and_query_from(parameters)
|
57
|
+
|
58
|
+
perform_request :delete,
|
59
|
+
endpoint,
|
60
|
+
query: query,
|
61
|
+
headers: default_headers.merge(headers)
|
62
|
+
end
|
63
|
+
|
64
|
+
def perform_request(method, endpoint, options = {})
|
65
|
+
@configuration.validate!
|
66
|
+
last_exception = nil
|
67
|
+
@logger.debug "Performing #{method.to_s.upcase} request: #{endpoint}"
|
68
|
+
(1..(@num_retries_per_request + 1)).each do |num_tries|
|
69
|
+
node = next_node
|
70
|
+
|
71
|
+
@logger.debug "Attempting #{method.to_s.upcase} request Try ##{num_tries} to Node #{node[:index]}"
|
72
|
+
|
73
|
+
begin
|
74
|
+
response_object = self.class.send(method,
|
75
|
+
uri_for(endpoint, node),
|
76
|
+
default_options.merge(options))
|
77
|
+
response_code = response_object.response.code.to_i
|
78
|
+
set_node_healthcheck(node, is_healthy: true) if response_code >= 1 && response_code <= 499
|
79
|
+
|
80
|
+
@logger.debug "Request to Node #{node[:index]} was successfully made (at the network layer). Response Code was #{response_code}."
|
81
|
+
|
82
|
+
# If response is 2xx return the object, else raise the response as an exception
|
83
|
+
return response_object.parsed_response if response_object.response.code_type <= Net::HTTPSuccess # 2xx
|
84
|
+
|
85
|
+
raise custom_exception_klass_for(response_object.response), response_object.parsed_response['message'] || 'Error message not available'
|
86
|
+
rescue Net::ReadTimeout, Net::OpenTimeout,
|
87
|
+
EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
88
|
+
Errno::EINVAL, Errno::ENETDOWN, Errno::ENETUNREACH, Errno::ENETRESET, Errno::ECONNABORTED, Errno::ECONNRESET,
|
89
|
+
Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTDOWN, Errno::EHOSTUNREACH,
|
90
|
+
Timeout::Error, HTTParty::ResponseError, Typesense::Error::ServerError, Typesense::Error::HTTPStatus0Error => e
|
91
|
+
# Rescue network layer exceptions and HTTP 5xx errors, so the loop can continue.
|
92
|
+
# Using loops for retries instead of rescue...retry to maintain consistency with client libraries in
|
93
|
+
# other languages that might not support the same construct.
|
94
|
+
set_node_healthcheck(node, is_healthy: false)
|
95
|
+
last_exception = e
|
96
|
+
@logger.warn "Request to Node #{node[:index]} failed due to \"#{e.class}: #{e.message}\""
|
97
|
+
@logger.warn "Sleeping for #{@retry_interval_seconds}s and then retrying request..."
|
98
|
+
sleep @retry_interval_seconds
|
99
|
+
end
|
100
|
+
end
|
101
|
+
@logger.debug "No retries left. Raising last error \"#{last_exception.class}: #{last_exception.message}\"..."
|
102
|
+
raise last_exception
|
57
103
|
end
|
58
104
|
|
59
105
|
private
|
60
106
|
|
61
|
-
def
|
62
|
-
if
|
63
|
-
|
107
|
+
def extract_headers_and_body_from(parameters)
|
108
|
+
if json_request?(parameters)
|
109
|
+
headers = { 'Content-Type' => 'application/json' }
|
110
|
+
body = sanitize_parameters(parameters).to_json
|
64
111
|
else
|
65
|
-
|
112
|
+
headers = {}
|
113
|
+
body = parameters[:body]
|
66
114
|
end
|
115
|
+
[headers, body]
|
67
116
|
end
|
68
117
|
|
69
|
-
def
|
70
|
-
|
118
|
+
def extract_headers_and_query_from(parameters)
|
119
|
+
if json_request?(parameters)
|
120
|
+
headers = { 'Content-Type' => 'application/json' }
|
121
|
+
query = sanitize_parameters(parameters)
|
122
|
+
else
|
123
|
+
headers = {}
|
124
|
+
query = parameters[:query]
|
125
|
+
end
|
126
|
+
[headers, query]
|
127
|
+
end
|
128
|
+
|
129
|
+
def json_request?(parameters)
|
130
|
+
parameters[:as_json].nil? ? true : parameters[:as_json]
|
131
|
+
end
|
132
|
+
|
133
|
+
def sanitize_parameters(parameters)
|
134
|
+
sanitized_parameters = parameters.dup
|
135
|
+
sanitized_parameters.delete(:as_json)
|
136
|
+
sanitized_parameters.delete(:body)
|
137
|
+
sanitized_parameters.delete(:query)
|
71
138
|
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
if ([:use_read_replicas, true].include? use_read_replicas) &&
|
103
|
-
!@configuration.read_replica_nodes.nil?
|
104
|
-
node = :read_replica
|
105
|
-
node_index += 1
|
106
|
-
|
107
|
-
retry unless @configuration.read_replica_nodes[node_index].nil?
|
139
|
+
sanitized_parameters
|
140
|
+
end
|
141
|
+
|
142
|
+
def uri_for(endpoint, node)
|
143
|
+
"#{node[:protocol]}://#{node[:host]}:#{node[:port]}#{endpoint}"
|
144
|
+
end
|
145
|
+
|
146
|
+
## Attempts to find the next healthy node, looping through the list of nodes once.
|
147
|
+
# But if no healthy nodes are found, it will just return the next node, even if it's unhealthy
|
148
|
+
# so we can try the request for good measure, in case that node has become healthy since
|
149
|
+
def next_node
|
150
|
+
# Check if nearest_node is set and is healthy, if so return it
|
151
|
+
unless @nearest_node.nil?
|
152
|
+
@logger.debug "Nodes health: Node #{@nearest_node[:index]} is #{@nearest_node[:is_healthy] == true ? 'Healthy' : 'Unhealthy'}"
|
153
|
+
if @nearest_node[:is_healthy] == true || node_due_for_healthcheck?(@nearest_node)
|
154
|
+
@logger.debug "Updated current node to Node #{@nearest_node[:index]}"
|
155
|
+
return @nearest_node
|
156
|
+
end
|
157
|
+
@logger.debug 'Falling back to individual nodes'
|
158
|
+
end
|
159
|
+
|
160
|
+
# Fallback to nodes as usual
|
161
|
+
@logger.debug "Nodes health: #{@nodes.each_with_index.map { |node, i| "Node #{i} is #{node[:is_healthy] == true ? 'Healthy' : 'Unhealthy'}" }.join(' || ')}"
|
162
|
+
candidate_node = nil
|
163
|
+
(0..@nodes.length).each do |_i|
|
164
|
+
@current_node_index = (@current_node_index + 1) % @nodes.length
|
165
|
+
candidate_node = @nodes[@current_node_index]
|
166
|
+
if candidate_node[:is_healthy] == true || node_due_for_healthcheck?(candidate_node)
|
167
|
+
@logger.debug "Updated current node to Node #{candidate_node[:index]}"
|
168
|
+
return candidate_node
|
108
169
|
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# None of the nodes are marked healthy, but some of them could have become healthy since last health check.
|
173
|
+
# So we will just return the next node.
|
174
|
+
@logger.debug "No healthy nodes were found. Returning the next node, Node #{candidate_node[:index]}"
|
175
|
+
candidate_node
|
176
|
+
end
|
109
177
|
|
110
|
-
|
178
|
+
def node_due_for_healthcheck?(node)
|
179
|
+
is_due_for_check = Time.now.to_i - node[:last_access_timestamp] > @healthcheck_interval_seconds
|
180
|
+
@logger.debug "Node #{node[:index]} has exceeded healthcheck_interval_seconds of #{@healthcheck_interval_seconds}. Adding it back into rotation." if is_due_for_check
|
181
|
+
is_due_for_check
|
182
|
+
end
|
183
|
+
|
184
|
+
def initialize_metadata_for_nodes
|
185
|
+
unless @nearest_node.nil?
|
186
|
+
@nearest_node[:index] = 'nearest_node'
|
187
|
+
set_node_healthcheck(@nearest_node, is_healthy: true)
|
188
|
+
end
|
189
|
+
@nodes.each_with_index do |node, index|
|
190
|
+
node[:index] = index
|
191
|
+
set_node_healthcheck(node, is_healthy: true)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def set_node_healthcheck(node, is_healthy:)
|
196
|
+
node[:is_healthy] = is_healthy
|
197
|
+
node[:last_access_timestamp] = Time.now.to_i
|
198
|
+
end
|
199
|
+
|
200
|
+
def custom_exception_klass_for(response)
|
201
|
+
response_code_type = response.code_type
|
202
|
+
if response_code_type <= Net::HTTPBadRequest # 400
|
203
|
+
Typesense::Error::RequestMalformed
|
204
|
+
elsif response_code_type <= Net::HTTPUnauthorized # 401
|
205
|
+
Typesense::Error::RequestUnauthorized
|
206
|
+
elsif response_code_type <= Net::HTTPNotFound # 404
|
207
|
+
Typesense::Error::ObjectNotFound
|
208
|
+
elsif response_code_type <= Net::HTTPConflict # 409
|
209
|
+
Typesense::Error::ObjectAlreadyExists
|
210
|
+
elsif response_code_type <= Net::HTTPUnprocessableEntity # 422
|
211
|
+
Typesense::Error::ObjectUnprocessable
|
212
|
+
elsif response_code_type <= Net::HTTPServerError # 5xx
|
213
|
+
Typesense::Error::ServerError
|
214
|
+
elsif response.code.to_i.zero?
|
215
|
+
Typesense::Error::HTTPStatus0Error
|
216
|
+
else
|
217
|
+
Typesense::Error::HTTPError
|
111
218
|
end
|
112
219
|
end
|
113
220
|
|
114
221
|
def default_options
|
115
222
|
{
|
116
|
-
timeout: @
|
223
|
+
timeout: @connection_timeout_seconds
|
117
224
|
}
|
118
225
|
end
|
119
226
|
|
120
227
|
def default_headers
|
121
228
|
{
|
122
|
-
API_KEY_HEADER_NAME.to_s => @
|
229
|
+
API_KEY_HEADER_NAME.to_s => @api_key
|
123
230
|
}
|
124
231
|
end
|
125
232
|
end
|