boltless 1.0.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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +8 -0
  3. data/Guardfile +44 -0
  4. data/Makefile +138 -0
  5. data/Rakefile +26 -0
  6. data/docker-compose.yml +19 -0
  7. data/lib/boltless/configuration.rb +69 -0
  8. data/lib/boltless/errors/invalid_json_error.rb +9 -0
  9. data/lib/boltless/errors/request_error.rb +24 -0
  10. data/lib/boltless/errors/response_error.rb +30 -0
  11. data/lib/boltless/errors/transaction_begin_error.rb +9 -0
  12. data/lib/boltless/errors/transaction_in_bad_state_error.rb +11 -0
  13. data/lib/boltless/errors/transaction_not_found_error.rb +11 -0
  14. data/lib/boltless/errors/transaction_rollback_error.rb +26 -0
  15. data/lib/boltless/extensions/configuration_handling.rb +37 -0
  16. data/lib/boltless/extensions/connection_pool.rb +127 -0
  17. data/lib/boltless/extensions/operations.rb +175 -0
  18. data/lib/boltless/extensions/transactions.rb +301 -0
  19. data/lib/boltless/extensions/utilities.rb +187 -0
  20. data/lib/boltless/request.rb +386 -0
  21. data/lib/boltless/result.rb +98 -0
  22. data/lib/boltless/result_row.rb +90 -0
  23. data/lib/boltless/statement_collector.rb +40 -0
  24. data/lib/boltless/transaction.rb +234 -0
  25. data/lib/boltless/version.rb +23 -0
  26. data/lib/boltless.rb +36 -0
  27. data/spec/benchmark/transfer.rb +57 -0
  28. data/spec/boltless/extensions/configuration_handling_spec.rb +39 -0
  29. data/spec/boltless/extensions/connection_pool_spec.rb +131 -0
  30. data/spec/boltless/extensions/operations_spec.rb +189 -0
  31. data/spec/boltless/extensions/transactions_spec.rb +418 -0
  32. data/spec/boltless/extensions/utilities_spec.rb +546 -0
  33. data/spec/boltless/request_spec.rb +946 -0
  34. data/spec/boltless/result_row_spec.rb +161 -0
  35. data/spec/boltless/result_spec.rb +127 -0
  36. data/spec/boltless/statement_collector_spec.rb +45 -0
  37. data/spec/boltless/transaction_spec.rb +601 -0
  38. data/spec/boltless_spec.rb +11 -0
  39. data/spec/fixtures/files/raw_result.yml +21 -0
  40. data/spec/fixtures/files/raw_result_with_graph_result.yml +48 -0
  41. data/spec/fixtures/files/raw_result_with_meta.yml +11 -0
  42. data/spec/fixtures/files/raw_result_with_stats.yml +26 -0
  43. data/spec/spec_helper.rb +89 -0
  44. metadata +384 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1d776e0f6ae5034f522faf66e8900c225d0799bc9b73257b681e9afd7b912f02
4
+ data.tar.gz: 3ed2ba355d515b9dfd620616772e083c097d02b401a847f43939c87024119fef
5
+ SHA512:
6
+ metadata.gz: 34e8e551fd39dd5069e3b58934ec7834eb9629defc3ca2accb185449cbb956c297bbd36f7a340cca836182d0b67c09b64719112ffc4d7179bd0eac158534da9d
7
+ data.tar.gz: cc5f3dabddbf7845f0a43126c3b3919d92a6873718f1895cd95d8b05c7ac2a29c787b18e7dae3b1074677ab3ef5c9295ec03fc57d8b0b76a50f66c2d900513d1
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in boltless.gemspec
8
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A sample Guardfile
4
+ # More info at https://github.com/guard/guard#readme
5
+
6
+ ## Uncomment and set this to only include directories you want to watch
7
+ (directories %w[lib spec]).select do |d|
8
+ if Dir.exist?(d)
9
+ d
10
+ else
11
+ UI.warning("Directory #{d} does not exist")
12
+ end
13
+ end
14
+
15
+ ## Note: if you are using the `directories` clause above and you are not
16
+ ## watching the project directory ('.'), then you will want to move
17
+ ## the Guardfile to a watched dir and symlink it back, e.g.
18
+ #
19
+ # $ mkdir config
20
+ # $ mv Guardfile config/
21
+ # $ ln -s config/Guardfile .
22
+ #
23
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
24
+
25
+ # NOTE: The cmd option is now required due to the increasing number of ways
26
+ # rspec may be run, below are examples of the most common uses.
27
+ # * bundler: 'bundle exec rspec'
28
+ # * bundler binstubs: 'bin/rspec'
29
+ # * spring: 'bin/rspec' (This will use spring if running and you have
30
+ # installed the spring binstubs per the docs)
31
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
32
+ # * 'just' rspec: 'rspec'
33
+
34
+ guard :rspec, cmd: 'bundle exec rspec' do
35
+ watch('spec/spec_helper.rb') { 'spec' }
36
+ watch(%r{^lib/boltless.rb}) { 'spec' }
37
+ watch(%r{^spec/.+_spec\.rb$})
38
+ watch(%r{^lib/boltless/([^\\]+)\.rb$}) do |m|
39
+ "spec/boltless/#{m[1]}_spec.rb"
40
+ end
41
+ watch(%r{^lib/boltless/([^\\]+)/(.*)\.rb$}) do |m|
42
+ "spec/#{m[1]}/#{m[2]}_spec.rb"
43
+ end
44
+ end
data/Makefile ADDED
@@ -0,0 +1,138 @@
1
+ MAKEFLAGS += --warn-undefined-variables -j1
2
+ SHELL := bash
3
+ .SHELLFLAGS := -eu -o pipefail -c
4
+ .DEFAULT_GOAL := all
5
+ .DELETE_ON_ERROR:
6
+ .SUFFIXES:
7
+ .PHONY:
8
+
9
+ # Environment switches
10
+ MAKE_ENV ?= docker
11
+ COMPOSE_RUN_SHELL_FLAGS ?= --rm
12
+ BASH_RUN_SHELL_FLAGS ?=
13
+
14
+ # Directories
15
+ VENDOR_DIR ?= vendor/bundle
16
+ GEMFILES_DIR ?= gemfiles
17
+
18
+ # Host binaries
19
+ BASH ?= bash
20
+ COMPOSE ?= docker-compose
21
+ ID ?= id
22
+ MKDIR ?= mkdir
23
+ RM ?= rm
24
+
25
+ # Container binaries
26
+ BUNDLE ?= bundle
27
+ APPRAISAL ?= appraisal
28
+ RAKE ?= rake
29
+ RUBOCOP ?= rubocop
30
+ GUARD ?= guard
31
+ YARD ?= yard
32
+
33
+ # Files
34
+ GEMFILES ?= $(subst _,-,$(patsubst $(GEMFILES_DIR)/%.gemfile,%,\
35
+ $(wildcard $(GEMFILES_DIR)/*.gemfile)))
36
+ TEST_GEMFILES := $(GEMFILES:%=test-%)
37
+
38
+ # Define a generic shell run wrapper
39
+ # $1 - The command to run
40
+ ifeq ($(MAKE_ENV),docker)
41
+ define run-shell
42
+ $(COMPOSE) run $(COMPOSE_RUN_SHELL_FLAGS) \
43
+ -e LANG=en_US.UTF-8 -e LANGUAGE=en_US.UTF-8 -e LC_ALL=en_US.UTF-8 \
44
+ -e HOME=/tmp -e BUNDLE_APP_CONFIG=/app/.bundle \
45
+ -u `$(ID) -u` test \
46
+ bash $(BASH_RUN_SHELL_FLAGS) -c 'sleep 0.1; echo; $(1)'
47
+ endef
48
+ else ifeq ($(MAKE_ENV),baremetal)
49
+ define run-shell
50
+ $(1)
51
+ endef
52
+ endif
53
+
54
+ all:
55
+ # Boltless
56
+ #
57
+ # install Install the dependencies
58
+ # test Run the whole test suite
59
+ # clean Clean the dependencies
60
+ # watch Watch for code changes and rerun the test suite
61
+ #
62
+ # docs Generate the Ruby documentation of the library
63
+ # stats Print the code statistics (library and test suite)
64
+ # notes Print all the notes from the code
65
+ # release Release a new Gem version (maintainers only)
66
+ #
67
+ # shell Run an interactive shell on the container
68
+ # shell-irb Run an interactive IRB shell on the container
69
+
70
+ .interactive:
71
+ @$(eval BASH_RUN_SHELL_FLAGS = --login)
72
+
73
+ install:
74
+ # Install the dependencies
75
+ @$(MKDIR) -p $(VENDOR_DIR)
76
+ @$(call run-shell,$(BUNDLE) check || $(BUNDLE) install --path $(VENDOR_DIR))
77
+ @$(call run-shell,$(BUNDLE) exec $(APPRAISAL) install)
78
+
79
+ test: \
80
+ test-specs \
81
+ test-style
82
+
83
+ test-specs:
84
+ # Run the whole test suite
85
+ @$(call run-shell,$(BUNDLE) exec $(RAKE) stats spec)
86
+
87
+ $(TEST_GEMFILES): GEMFILE=$(@:test-%=%)
88
+ $(TEST_GEMFILES):
89
+ # Run the whole test suite ($(GEMFILE))
90
+ @$(call run-shell,$(BUNDLE) exec $(APPRAISAL) $(GEMFILE) $(RAKE))
91
+
92
+ test-style: \
93
+ test-style-ruby
94
+
95
+ test-style-ruby:
96
+ # Run the static code analyzer (rubocop)
97
+ @$(call run-shell,$(BUNDLE) exec $(RUBOCOP) -a)
98
+
99
+ watch: install .interactive
100
+ # Watch for code changes and rerun the test suite
101
+ @$(call run-shell,$(BUNDLE) exec $(GUARD))
102
+
103
+ clean:
104
+ # Clean the dependencies
105
+ @$(RM) -rf $(VENDOR_DIR)
106
+
107
+ clean-containers:
108
+ # Clean running containers
109
+ ifeq ($(MAKE_ENV),docker)
110
+ @$(COMPOSE) down
111
+ endif
112
+
113
+ distclean: clean clean-containers
114
+
115
+ shell:
116
+ # Run an interactive shell on the container
117
+ @$(call run-shell,$(BASH) -i)
118
+
119
+ shell-irb:
120
+ # Run an interactive IRB shell on the container
121
+ @$(call run-shell,bin/console)
122
+
123
+ docs:
124
+ # Build the API documentation
125
+ @$(call run-shell,$(BUNDLE) exec $(YARD) -q && \
126
+ $(BUNDLE) exec $(YARD) stats --list-undoc --compact)
127
+
128
+ notes:
129
+ # Print the code statistics (library and test suite)
130
+ @$(call run-shell,$(BUNDLE) exec $(RAKE) notes)
131
+
132
+ stats:
133
+ # Print all the notes from the code
134
+ @$(call run-shell,$(BUNDLE) exec $(RAKE) stats)
135
+
136
+ release:
137
+ # Release a new gem version
138
+ @$(BUNDLE) exec $(RAKE) release
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'countless/rake_tasks'
6
+ require 'pp'
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ task default: :spec
11
+
12
+ # Configure all code statistics directories
13
+ Countless.configure do |config|
14
+ config.stats_base_directories = [
15
+ { name: 'Top-levels', dir: 'lib',
16
+ pattern: %r{/lib(/boltless)?/[^/]+\.rb$} },
17
+ { name: 'Top-levels specs', test: true, dir: 'spec',
18
+ pattern: %r{/spec(/boltless)?/[^/]+_spec\.rb$} },
19
+ { name: 'Extensions', pattern: 'lib/boltless/extensions/**/*.rb' },
20
+ { name: 'Extensions specs', test: true,
21
+ pattern: 'spec/boltless/extensions/**/*_spec.rb' },
22
+ { name: 'Errors', pattern: 'lib/boltless/errors/**/*.rb' },
23
+ { name: 'Errors specs', test: true,
24
+ pattern: 'spec/boltless/errors/**/*_spec.rb' }
25
+ ]
26
+ end
@@ -0,0 +1,19 @@
1
+ version: "3"
2
+ services:
3
+ test:
4
+ image: ruby:2.7
5
+ network_mode: bridge
6
+ working_dir: /app
7
+ links:
8
+ - neo4j
9
+ volumes:
10
+ - .:/app
11
+
12
+ neo4j:
13
+ image: hausgold/neo4j:4.4
14
+ network_mode: bridge
15
+ ports: ["7474", "7687"]
16
+ volumes:
17
+ - .:/app:${DOCKER_MOUNT_MODE:-rw}
18
+ environment:
19
+ MDNS_HOSTNAME: neo4j.boltless.local
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boltless
4
+ # The configuration for the Boltless gem.
5
+ class Configuration
6
+ include ActiveSupport::Configurable
7
+
8
+ # The base URL of the neo4j HTTP API (port 7474 for HTTP, port 7473
9
+ # for HTTPS when configured at server side)
10
+ config_accessor(:base_url) { 'http://neo4j:7474' }
11
+
12
+ # The username for the neo4j database (used for HTTP Basic Authentication)
13
+ config_accessor(:username) { 'neo4j' }
14
+
15
+ # The password for the neo4j database (used for HTTP Basic Authentication)
16
+ config_accessor(:password) { 'neo4j' }
17
+
18
+ # The default user database of the neo4j instance/cluster, for the
19
+ # community edition this is always +neo4j+, only a single user database is
20
+ # supported with the community editon. You can always specify which
21
+ # database to operate on, on each top-level querying method (eg.
22
+ # +Boltless.execute(.., database: 'custom')+
23
+ config_accessor(:default_db) { 'neo4j' }
24
+
25
+ # The seconds to wait for a connection from the pool,
26
+ # when all connections are currently in use
27
+ config_accessor(:connection_pool_timeout) { 15.seconds }
28
+
29
+ # The size of the connection pool, make sure it matches your application
30
+ # server (eg. Puma) thread pool size, in order to avoid
31
+ # timeouts/bottlenecks
32
+ config_accessor(:connection_pool_size) { 10 }
33
+
34
+ # The overall timeout for a single HTTP request (including connecting,
35
+ # transmitting and response completion)
36
+ config_accessor(:request_timeout) { 10.seconds }
37
+
38
+ # We allow the neo4j server to bootup for the configured time. This allows
39
+ # parallel starts of the user application and the neo4j server, without
40
+ # glitching.
41
+ config_accessor(:wait_for_upstream_server) { 30.seconds }
42
+
43
+ # Configure a logger for the gem
44
+ config_accessor(:logger) do
45
+ Logger.new($stdout).tap do |logger|
46
+ logger.level = :info
47
+ end
48
+ end
49
+
50
+ # Whenever we should log the neo4j queries, including benchmarking. When
51
+ # disabled we reduce the logging overhead even more as no debug logs hit
52
+ # the logger at all. Enable this for testing purposes or for local
53
+ # development. (Heads up: No parameter sanitation is done, so passwords etc
54
+ # will be logged with this enabled) Setting the value to +:debug+ will
55
+ # print the actual Cypher statements before any request is sent. This may
56
+ # be helpful inspection of slow/never-ending Cypher statements.
57
+ config_accessor(:query_log_enabled) { false }
58
+
59
+ # We allow the http.rb gem to be configured by the user for special needs.
60
+ # Just assign a user given block here and you can reconfigure the client.
61
+ # Just make sure to return the configured +HTTP::Client+ instance
62
+ # afterwards.
63
+ config_accessor(:http_client_configure) do
64
+ proc do |connection|
65
+ connection
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boltless
4
+ module Errors
5
+ # This exception is raised whenever we were not able to produce or
6
+ # consume JSON data.
7
+ class InvalidJsonError < RequestError; end
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boltless
4
+ module Errors
5
+ # A generic request error wrapper, from the low-level http.rb gem
6
+ class RequestError < StandardError
7
+ # We allow to read our details
8
+ attr_accessor :message
9
+ attr_reader :response
10
+
11
+ # Create a new generic request error instance.
12
+ #
13
+ # @param message [String] the error message
14
+ # @param response [HTTP::Response, nil] the HTTP response,
15
+ # or +nil+ when not available
16
+ # @return [Errors::RequestError] the new error instance
17
+ def initialize(message, response: nil)
18
+ super(message)
19
+ @message = message
20
+ @response = response
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boltless
4
+ module Errors
5
+ # A generic response error, for everything neo4j want to tell us
6
+ class ResponseError < StandardError
7
+ # We allow to read our details
8
+ attr_accessor :message
9
+ attr_reader :code, :response
10
+
11
+ # Create a new generic response error instance.
12
+ #
13
+ # @param message [String] the error message
14
+ # @param code [String, nil] the neo4j error code,
15
+ # or +nil+ when not available
16
+ # @param response [HTTP::Response, nil] the HTTP response,
17
+ # or +nil+ when not available
18
+ # @return [Errors::RequestError] the new error instance
19
+ def initialize(message, code: nil, response: nil)
20
+ formatted = "#{message} (#{code})" if message && code
21
+ formatted = code unless message
22
+ formatted ||= message
23
+ super(formatted)
24
+ @message = formatted
25
+ @code = code
26
+ @response = response
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boltless
4
+ module Errors
5
+ # This exception is raised when we failed to start a new transaction
6
+ # at the neo4j server.
7
+ class TransactionBeginError < RequestError; end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boltless
4
+ module Errors
5
+ # This exception is raised when a transaction is going to be used, but is
6
+ # not usable in its current state. This may happen when a not-yet-started
7
+ # transaction should send a query, or when an already rolled back
8
+ # transaction should be used, etc.
9
+ class TransactionInBadStateError < RequestError; end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boltless
4
+ module Errors
5
+ # This exception is raised when there is no open transaction at the neo4j
6
+ # server with the given identifier. The neo4j server closes an
7
+ # idling/inactive transaction after 60 seconds by default (after the last
8
+ # interaction). This value can be configured at the neo4j server.
9
+ class TransactionNotFoundError < RequestError; end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boltless
4
+ module Errors
5
+ # This exception is raised when we failed to rollback a transaction
6
+ # at the neo4j server, or when another error caused a transaction rollback.
7
+ class TransactionRollbackError < RequestError
8
+ # We allow to read our details
9
+ attr_reader :errors
10
+
11
+ # Create a new generic response error instance.
12
+ #
13
+ # @param message [String] the error message
14
+ # @param errors [Array<Errors::ResponseError>, Errors::ResponseError]
15
+ # a single/multiple response errors
16
+ # @param response [HTTP::Response, nil] the HTTP response,
17
+ # or +nil+ when not available
18
+ # @return [Errors::TransactionRollbackError] the new error instance
19
+ def initialize(message, errors: [], response: nil)
20
+ @errors = Array(errors)
21
+ message += "\n\n#{@errors.map { |err| "* #{err.message}" }.join("\n")}"
22
+ super(message, response: response)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boltless
4
+ module Extensions
5
+ # A top-level gem-module extension to handle configuration needs.
6
+ module ConfigurationHandling
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ # Retrieve the current configuration object.
11
+ #
12
+ # @return [Configuration] the current configuration object
13
+ def configuration
14
+ @configuration ||= Configuration.new
15
+ end
16
+
17
+ # Configure the concern by providing a block which takes
18
+ # care of this task. Example:
19
+ #
20
+ # Boltless.configure do |conf|
21
+ # # conf.xyz = [..]
22
+ # end
23
+ def configure
24
+ yield(configuration)
25
+ end
26
+
27
+ # Reset the current configuration with the default one.
28
+ def reset_configuration!
29
+ @configuration = Configuration.new
30
+ end
31
+
32
+ # A shortcut to the configured logger.
33
+ delegate :logger, to: :configuration
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Boltless
4
+ module Extensions
5
+ # A top-level gem-module extension to add easy-to-use connection pool.
6
+ #
7
+ # rubocop:disable Metrics/BlockLength because this is how
8
+ # an +ActiveSupport::Concern+ looks like
9
+ module ConnectionPool
10
+ extend ActiveSupport::Concern
11
+
12
+ class_methods do
13
+ # Check if the neo4j server is ready to rumble. This comes in handy in
14
+ # local scenarios when the neo4j server is booted in parallel to the
15
+ # user application. Then it may be necessary to wait for the neo4j
16
+ # server to come up, before sending real requests which may otherwise
17
+ # be ignored.
18
+ #
19
+ # @param connection [HTTP::Client]
20
+ # @return [HTTP::Client] the given connection
21
+ #
22
+ # @raise [HTTP::Error] in case the upstream server did not come up
23
+ #
24
+ # rubocop:disable Metrics/MethodLength because of the
25
+ # retry logic
26
+ # rubocop:disable Metrics/AbcSize dito
27
+ def wait_for_server!(connection)
28
+ # Check if the server already accepted connections
29
+ return connection if @upstream_is_ready
30
+
31
+ # Otherwise we setup the retry counter and
32
+ # increment it for the current try
33
+ @upstream_retry_count ||= 0
34
+ @upstream_retry_count += 1
35
+
36
+ # We didn't checked the upstream server yet
37
+ body = connection.get('/').to_s
38
+ raise "Upstream server not available: #{body}" \
39
+ unless body.include? 'neo4j_version'
40
+
41
+ # When we reached this point, the remote connection is established,
42
+ # so we reset the retry counter
43
+ @upstream_retry_count = 0
44
+
45
+ # Everything looks good, when we passed this point
46
+ @upstream_is_ready = true
47
+
48
+ # Return the given connection
49
+ connection
50
+ rescue HTTP::Error, RuntimeError => e
51
+ # Something is bad, we got a timeout or the response body
52
+ # was unexpected, so lets try it again
53
+ retry_sleep = 2.seconds
54
+
55
+ # We allow the service to be unavailable for 30 seconds
56
+ max_retries = (
57
+ configuration.wait_for_upstream_server / retry_sleep
58
+ ).ceil
59
+ raise e if @upstream_retry_count >= max_retries
60
+
61
+ logger.warn do
62
+ '> neo4j is unavailable, retry in 2 seconds' \
63
+ " (#{@upstream_retry_count}/#{max_retries}, " \
64
+ "#{configuration.base_url})"
65
+ .colorize(:yellow)
66
+ end
67
+ sleep(retry_sleep)
68
+ retry
69
+ end
70
+ # rubocop:enable Metrics/MethodLength
71
+ # rubocop:enable Metrics/AbcSize
72
+
73
+ # A memoized connection pool for our HTTP API clients.
74
+ #
75
+ # @see https://github.com/mperham/connection_pool
76
+ # @return [::ConnectionPool] the connection pool instance
77
+ #
78
+ # rubocop:disable Metrics/MethodLength because of the
79
+ # connection configuration
80
+ # rubocop:disable Metrics/AbcSize dito
81
+ def connection_pool
82
+ @connection_pool ||= begin
83
+ conf = Boltless.configuration
84
+
85
+ ::ConnectionPool.new(
86
+ size: conf.connection_pool_size,
87
+ timeout: conf.connection_pool_timeout
88
+ ) do
89
+ HTTP
90
+ .use({ normalize_uri: { normalizer: ->(uri) { uri } } })
91
+ .use(:auto_inflate)
92
+ .timeout(conf.request_timeout.to_i)
93
+ .basic_auth(
94
+ user: conf.username,
95
+ pass: conf.password
96
+ )
97
+ .headers(
98
+ 'User-Agent' => "Boltless/#{Boltless::VERSION}",
99
+ 'Accept-Encoding' => 'gzip',
100
+ 'Accept' => 'application/json',
101
+ 'Content-Type' => 'application/json',
102
+ 'X-Stream' => 'true'
103
+ )
104
+ .encoding('UTF-8')
105
+ .persistent(conf.base_url)
106
+ .yield_self do |connection|
107
+ conf.http_client_configure.call(connection)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ # rubocop:enable Metrics/MethodLength
113
+ # rubocop:enable Metrics/AbcSize
114
+ end
115
+
116
+ included do
117
+ # Install an shutdown handler for our connection pool
118
+ at_exit do
119
+ connection_pool.shutdown do |connection|
120
+ connection&.close
121
+ end
122
+ end
123
+ end
124
+ end
125
+ # rubocop:enable Metrics/BlockLength
126
+ end
127
+ end