boltless 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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