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