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.
- checksums.yaml +7 -0
- data/Gemfile +8 -0
- data/Guardfile +44 -0
- data/Makefile +138 -0
- data/Rakefile +26 -0
- data/docker-compose.yml +19 -0
- data/lib/boltless/configuration.rb +69 -0
- data/lib/boltless/errors/invalid_json_error.rb +9 -0
- data/lib/boltless/errors/request_error.rb +24 -0
- data/lib/boltless/errors/response_error.rb +30 -0
- data/lib/boltless/errors/transaction_begin_error.rb +9 -0
- data/lib/boltless/errors/transaction_in_bad_state_error.rb +11 -0
- data/lib/boltless/errors/transaction_not_found_error.rb +11 -0
- data/lib/boltless/errors/transaction_rollback_error.rb +26 -0
- data/lib/boltless/extensions/configuration_handling.rb +37 -0
- data/lib/boltless/extensions/connection_pool.rb +127 -0
- data/lib/boltless/extensions/operations.rb +175 -0
- data/lib/boltless/extensions/transactions.rb +301 -0
- data/lib/boltless/extensions/utilities.rb +187 -0
- data/lib/boltless/request.rb +386 -0
- data/lib/boltless/result.rb +98 -0
- data/lib/boltless/result_row.rb +90 -0
- data/lib/boltless/statement_collector.rb +40 -0
- data/lib/boltless/transaction.rb +234 -0
- data/lib/boltless/version.rb +23 -0
- data/lib/boltless.rb +36 -0
- data/spec/benchmark/transfer.rb +57 -0
- data/spec/boltless/extensions/configuration_handling_spec.rb +39 -0
- data/spec/boltless/extensions/connection_pool_spec.rb +131 -0
- data/spec/boltless/extensions/operations_spec.rb +189 -0
- data/spec/boltless/extensions/transactions_spec.rb +418 -0
- data/spec/boltless/extensions/utilities_spec.rb +546 -0
- data/spec/boltless/request_spec.rb +946 -0
- data/spec/boltless/result_row_spec.rb +161 -0
- data/spec/boltless/result_spec.rb +127 -0
- data/spec/boltless/statement_collector_spec.rb +45 -0
- data/spec/boltless/transaction_spec.rb +601 -0
- data/spec/boltless_spec.rb +11 -0
- data/spec/fixtures/files/raw_result.yml +21 -0
- data/spec/fixtures/files/raw_result_with_graph_result.yml +48 -0
- data/spec/fixtures/files/raw_result_with_meta.yml +11 -0
- data/spec/fixtures/files/raw_result_with_stats.yml +26 -0
- data/spec/spec_helper.rb +89 -0
- 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
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
|
data/docker-compose.yml
ADDED
@@ -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,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,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
|