chassis 0.1.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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +362 -0
- data/Rakefile +33 -0
- data/chassis.gemspec +41 -0
- data/examples/repo.rb +40 -0
- data/lib/chassis.rb +81 -0
- data/lib/chassis/array_utils.rb +8 -0
- data/lib/chassis/circuit_panel.rb +22 -0
- data/lib/chassis/core_ext/array.rb +5 -0
- data/lib/chassis/core_ext/hash.rb +5 -0
- data/lib/chassis/core_ext/string.rb +13 -0
- data/lib/chassis/delegate.rb +29 -0
- data/lib/chassis/dirty_session.rb +105 -0
- data/lib/chassis/error.rb +7 -0
- data/lib/chassis/faraday.rb +226 -0
- data/lib/chassis/form.rb +56 -0
- data/lib/chassis/hash_utils.rb +16 -0
- data/lib/chassis/heroku.rb +5 -0
- data/lib/chassis/initializable.rb +11 -0
- data/lib/chassis/logger.rb +8 -0
- data/lib/chassis/observable.rb +19 -0
- data/lib/chassis/persistence.rb +49 -0
- data/lib/chassis/rack/bouncer.rb +33 -0
- data/lib/chassis/rack/builder_shim_patch.rb +7 -0
- data/lib/chassis/rack/health_check.rb +45 -0
- data/lib/chassis/rack/instrumentation.rb +20 -0
- data/lib/chassis/rack/json_body_parser.rb +20 -0
- data/lib/chassis/rack/no_robots.rb +24 -0
- data/lib/chassis/registry.rb +30 -0
- data/lib/chassis/repo.rb +73 -0
- data/lib/chassis/repo/base_repo.rb +99 -0
- data/lib/chassis/repo/delegation.rb +78 -0
- data/lib/chassis/repo/lazy_association.rb +57 -0
- data/lib/chassis/repo/memory_repo.rb +7 -0
- data/lib/chassis/repo/null_repo.rb +64 -0
- data/lib/chassis/repo/pstore_repo.rb +54 -0
- data/lib/chassis/repo/record_map.rb +44 -0
- data/lib/chassis/repo/redis_repo.rb +55 -0
- data/lib/chassis/serializable.rb +52 -0
- data/lib/chassis/string_utils.rb +50 -0
- data/lib/chassis/version.rb +3 -0
- data/lib/chassis/web_service.rb +61 -0
- data/test/array_utils_test.rb +23 -0
- data/test/chassis_test.rb +7 -0
- data/test/circuit_panel_test.rb +22 -0
- data/test/core_ext/array_test.rb +8 -0
- data/test/core_ext/hash_test.rb +8 -0
- data/test/core_ext/string_test.rb +16 -0
- data/test/delegate_test.rb +41 -0
- data/test/dirty_session_test.rb +138 -0
- data/test/error_test.rb +12 -0
- data/test/faraday_test.rb +749 -0
- data/test/form_test.rb +29 -0
- data/test/hash_utils_test.rb +17 -0
- data/test/initializable_test.rb +22 -0
- data/test/logger_test.rb +43 -0
- data/test/observable_test.rb +27 -0
- data/test/persistence_test.rb +112 -0
- data/test/prox_test.rb +7 -0
- data/test/rack/bouncer_test.rb +42 -0
- data/test/rack/builder_patch_test.rb +36 -0
- data/test/rack/health_check_test.rb +35 -0
- data/test/rack/instrumentation_test.rb +38 -0
- data/test/rack/json_body_parser_test.rb +38 -0
- data/test/rack/no_robots_test.rb +34 -0
- data/test/registry_test.rb +26 -0
- data/test/repo/delegation_test.rb +101 -0
- data/test/repo/lazy_association_test.rb +115 -0
- data/test/repo/memory_repo_test.rb +25 -0
- data/test/repo/null_repo_test.rb +48 -0
- data/test/repo/pstore_repo_test.rb +28 -0
- data/test/repo/redis_repo_test.rb +26 -0
- data/test/repo/repo_tests.rb +120 -0
- data/test/repo_test.rb +76 -0
- data/test/serializable_test.rb +77 -0
- data/test/string_utils_test.rb +21 -0
- data/test/test_helper.rb +10 -0
- data/test/web_service_test.rb +107 -0
- metadata +426 -0
data/chassis.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'chassis/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "chassis"
|
8
|
+
spec.version = Chassis::VERSION
|
9
|
+
spec.authors = ["ahawkins"]
|
10
|
+
spec.email = ["adam@hawkins.io"]
|
11
|
+
spec.description = %q{A collection of modules and helpers for building mantainable Ruby applications}
|
12
|
+
spec.summary = %q{}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "sinatra"
|
22
|
+
spec.add_dependency "sinatra-contrib"
|
23
|
+
spec.add_dependency "rack-contrib"
|
24
|
+
spec.add_dependency "manifold"
|
25
|
+
spec.add_dependency "prox"
|
26
|
+
spec.add_dependency "harness"
|
27
|
+
spec.add_dependency "harness-rack"
|
28
|
+
spec.add_dependency "virtus"
|
29
|
+
spec.add_dependency "virtus-dirty_attribute"
|
30
|
+
spec.add_dependency "faraday", "~> 0.9.0"
|
31
|
+
spec.add_dependency "logger-better"
|
32
|
+
spec.add_dependency "breaker"
|
33
|
+
spec.add_dependency "interchange"
|
34
|
+
spec.add_dependency "tnt"
|
35
|
+
|
36
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
37
|
+
spec.add_development_dependency "rake"
|
38
|
+
spec.add_development_dependency "rack-test"
|
39
|
+
spec.add_development_dependency "mocha"
|
40
|
+
spec.add_development_dependency "redis"
|
41
|
+
end
|
data/examples/repo.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'chassis'
|
2
|
+
|
3
|
+
class Post
|
4
|
+
attr_accessor :id, :title, :text
|
5
|
+
end
|
6
|
+
|
7
|
+
repo = Chassis::Repo.default
|
8
|
+
|
9
|
+
puts repo.empty? Post #=> true
|
10
|
+
|
11
|
+
post = Post.new
|
12
|
+
post.title = 'Such Repos'
|
13
|
+
post.text = 'Very wow. Much design.'
|
14
|
+
|
15
|
+
repo.save post
|
16
|
+
|
17
|
+
puts post.id #=> 1
|
18
|
+
|
19
|
+
found_post = repo.find Post, post.id
|
20
|
+
found_post == post #=> true (no difference between objects in memory)
|
21
|
+
|
22
|
+
post.title = 'Such updates'
|
23
|
+
post.text = 'Very easy. Wow'
|
24
|
+
|
25
|
+
repo.save post
|
26
|
+
|
27
|
+
repo.find(Post, post.id).text #=> 'Very easy. Wow.'
|
28
|
+
|
29
|
+
class PostRepo
|
30
|
+
extend Chassis::Repo::Delegation
|
31
|
+
end
|
32
|
+
|
33
|
+
post = PostRepo.find post.id
|
34
|
+
post.text #=> 'Very Easy. Wow'
|
35
|
+
|
36
|
+
PostRepo.all #=> [post]
|
37
|
+
# etc
|
38
|
+
|
39
|
+
PostRepo.delete post
|
40
|
+
PostRepo.empty? #=> true
|
data/lib/chassis.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require "chassis/version"
|
2
|
+
|
3
|
+
require 'logger-better'
|
4
|
+
|
5
|
+
require 'sinatra'
|
6
|
+
require 'manifold'
|
7
|
+
require 'rack/contrib/bounce_favicon'
|
8
|
+
require 'rack/contrib/post_body_content_type_parser'
|
9
|
+
|
10
|
+
require 'harness'
|
11
|
+
require 'harness/rack'
|
12
|
+
|
13
|
+
require 'virtus'
|
14
|
+
require 'virtus/dirty_attribute'
|
15
|
+
|
16
|
+
require 'prox'
|
17
|
+
|
18
|
+
require 'faraday'
|
19
|
+
|
20
|
+
require 'breaker'
|
21
|
+
|
22
|
+
require 'interchange'
|
23
|
+
|
24
|
+
require 'tnt'
|
25
|
+
|
26
|
+
module Chassis
|
27
|
+
Proxy = Prox
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def stream
|
31
|
+
@stream
|
32
|
+
end
|
33
|
+
|
34
|
+
def stream=(stream)
|
35
|
+
@stream = stream
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
require_relative 'chassis/hash_utils'
|
41
|
+
require_relative 'chassis/string_utils'
|
42
|
+
require_relative 'chassis/array_utils'
|
43
|
+
|
44
|
+
require_relative 'chassis/error'
|
45
|
+
|
46
|
+
require_relative 'chassis/logger'
|
47
|
+
|
48
|
+
require_relative 'chassis/faraday'
|
49
|
+
|
50
|
+
require_relative 'chassis/form'
|
51
|
+
|
52
|
+
require_relative 'chassis/persistence'
|
53
|
+
|
54
|
+
require_relative 'chassis/initializable'
|
55
|
+
|
56
|
+
require_relative 'chassis/serializable'
|
57
|
+
|
58
|
+
require_relative 'chassis/observable'
|
59
|
+
|
60
|
+
require_relative 'chassis/dirty_session'
|
61
|
+
|
62
|
+
require_relative 'chassis/circuit_panel'
|
63
|
+
|
64
|
+
require_relative 'chassis/registry'
|
65
|
+
|
66
|
+
require_relative 'chassis/repo'
|
67
|
+
|
68
|
+
require_relative 'chassis/delegate'
|
69
|
+
|
70
|
+
require_relative 'chassis/rack/bouncer'
|
71
|
+
require_relative 'chassis/rack/builder_shim_patch'
|
72
|
+
require_relative 'chassis/rack/health_check'
|
73
|
+
require_relative 'chassis/rack/instrumentation'
|
74
|
+
require_relative 'chassis/rack/no_robots'
|
75
|
+
require_relative 'chassis/rack/json_body_parser'
|
76
|
+
|
77
|
+
require_relative 'chassis/web_service'
|
78
|
+
|
79
|
+
Chassis.repo.register :memory, Chassis::MemoryRepo.new
|
80
|
+
Chassis.repo.register :null, Chassis::NullRepo.new
|
81
|
+
Chassis.repo.use :memory
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Chassis
|
2
|
+
class CircuitPanel
|
3
|
+
class << self
|
4
|
+
def build(&block)
|
5
|
+
raise ArgumentError, "block required" unless block
|
6
|
+
Class.new self, &block
|
7
|
+
end
|
8
|
+
|
9
|
+
def circuit(name, options = {})
|
10
|
+
define_method name do
|
11
|
+
Breaker.circuit name, options
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def circuit_panel(&block)
|
19
|
+
CircuitPanel.build &block
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Chassis
|
2
|
+
DelegationError = Chassis.error do |method, delegate|
|
3
|
+
"Cannot delegate #{method} without #{delegate}"
|
4
|
+
end
|
5
|
+
|
6
|
+
class Delegation < Module
|
7
|
+
def initialize(*methods)
|
8
|
+
options = methods.last.is_a?(Hash) ? methods.pop : { }
|
9
|
+
|
10
|
+
delegate = options.fetch :to do
|
11
|
+
fail ArgumentError, ":to not given"
|
12
|
+
end
|
13
|
+
|
14
|
+
methods.each do |method|
|
15
|
+
define_method method do |*args, &block|
|
16
|
+
object = send delegate
|
17
|
+
fail DelegationError.new method, delegate unless object
|
18
|
+
object.send(method, *args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def delegate(*methods)
|
26
|
+
Delegation.new *methods
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Chassis
|
4
|
+
class DirtySession < Proxy
|
5
|
+
def initialize(*args)
|
6
|
+
super
|
7
|
+
@original_values = { }
|
8
|
+
@new_values = { }
|
9
|
+
end
|
10
|
+
|
11
|
+
def clean?
|
12
|
+
new_values.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
def dirty?
|
16
|
+
!clean?
|
17
|
+
end
|
18
|
+
|
19
|
+
def original_values
|
20
|
+
@original_values
|
21
|
+
end
|
22
|
+
|
23
|
+
def new_values
|
24
|
+
@new_values
|
25
|
+
end
|
26
|
+
|
27
|
+
def changes
|
28
|
+
Set.new original_values.keys
|
29
|
+
end
|
30
|
+
|
31
|
+
def reset!
|
32
|
+
original_values.clear
|
33
|
+
new_values.clear
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_missing(name, *args, &block)
|
37
|
+
raise MissingObject, name unless __getobj__
|
38
|
+
|
39
|
+
if writer_method?(name)
|
40
|
+
handle_writer_method name, *args, &block
|
41
|
+
elsif changed_method?(name)
|
42
|
+
handle_changed_method name
|
43
|
+
elsif original_method?(name)
|
44
|
+
handle_original_method name
|
45
|
+
else
|
46
|
+
__getobj__.send name, *args, &block
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def respond_to_missing?(name, include_private = false)
|
51
|
+
if changed_method?(name) || original_method?(name)
|
52
|
+
__getobj__.respond_to? reader_method(name)
|
53
|
+
else
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def handle_writer_method(name, *args, &block)
|
60
|
+
method_key = reader_method name
|
61
|
+
|
62
|
+
original_value = __getobj__.send method_key
|
63
|
+
new_value = __getobj__.send name, *args, &block
|
64
|
+
|
65
|
+
if new_value != original_value
|
66
|
+
original_values[method_key] = original_value unless original_values.key? method_key
|
67
|
+
new_values[method_key] = new_value
|
68
|
+
end
|
69
|
+
|
70
|
+
new_value
|
71
|
+
end
|
72
|
+
|
73
|
+
def writer_method?(name)
|
74
|
+
name =~ /=$/
|
75
|
+
end
|
76
|
+
|
77
|
+
def reader_method(name)
|
78
|
+
method_name = name.to_s
|
79
|
+
|
80
|
+
if writer_method? method_name
|
81
|
+
method_name.match(/(.+)=$/)[1].to_sym
|
82
|
+
elsif changed_method? method_name
|
83
|
+
method_name.match(/(.+)_changed\?$/)[1].to_sym
|
84
|
+
elsif original_method? method_name
|
85
|
+
method_name.match(/original_(.+)$/)[1].to_sym
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def original_method?(name)
|
90
|
+
name =~ /original_.+$/
|
91
|
+
end
|
92
|
+
|
93
|
+
def changed_method?(name)
|
94
|
+
name =~ /_changed\?$/
|
95
|
+
end
|
96
|
+
|
97
|
+
def handle_changed_method(name)
|
98
|
+
original_values.key? reader_method(name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def handle_original_method(name)
|
102
|
+
original_values[reader_method(name)]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module Chassis
|
2
|
+
class HttpRequestFailedError < StandardError
|
3
|
+
def initialize(env)
|
4
|
+
@env = env
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
"#{status} - #{description} : #{method} #{url}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def method
|
12
|
+
env.fetch(:method).upcase
|
13
|
+
end
|
14
|
+
|
15
|
+
def body
|
16
|
+
env.fetch :body
|
17
|
+
end
|
18
|
+
|
19
|
+
def url
|
20
|
+
env.fetch(:url)
|
21
|
+
end
|
22
|
+
|
23
|
+
def status
|
24
|
+
env.fetch :status
|
25
|
+
end
|
26
|
+
|
27
|
+
def description
|
28
|
+
::Rack::Utils::HTTP_STATUS_CODES.fetch status, 'UNKOWN'
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def env
|
33
|
+
@env
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# 4xx errors
|
38
|
+
HttpClientError = Class.new HttpRequestFailedError
|
39
|
+
HttpBadRequestError = Class.new HttpClientError
|
40
|
+
HttpUnauthorizedError = Class.new HttpClientError
|
41
|
+
HttpPaymentRequiredError = Class.new HttpClientError
|
42
|
+
HttpForbiddenError = Class.new HttpClientError
|
43
|
+
HttpNotFoundError = Class.new HttpClientError
|
44
|
+
HttpMethodNotAllowedError = Class.new HttpClientError
|
45
|
+
HttpNotAcceptableError = Class.new HttpClientError
|
46
|
+
HttpProxyAuthenticationRequiredError = Class.new HttpClientError
|
47
|
+
HttpRequestTimeoutError = Class.new HttpClientError
|
48
|
+
HttpConflictError = Class.new HttpClientError
|
49
|
+
HttpGoneError = Class.new HttpClientError
|
50
|
+
HttpLengthRequiredError = Class.new HttpClientError
|
51
|
+
HttpPreconditionFailedError = Class.new HttpClientError
|
52
|
+
HttpRequestEntityTooLargeError = Class.new HttpClientError
|
53
|
+
HttpRequestUriTooLongError = Class.new HttpClientError
|
54
|
+
HttpUnsupportedMediaTypeError = Class.new HttpClientError
|
55
|
+
HttpRequestRangeNotSatisfiableError = Class.new HttpClientError
|
56
|
+
HttpExpectationFailedError = Class.new HttpClientError
|
57
|
+
HttpUnprocessableEntityError = Class.new HttpClientError
|
58
|
+
HttpLockedError = Class.new HttpClientError
|
59
|
+
HttpFailedDependencyError = Class.new HttpClientError
|
60
|
+
HttpUpgradeRequiredError = Class.new HttpClientError
|
61
|
+
|
62
|
+
# 5xx errors
|
63
|
+
HttpServerError = Class.new HttpRequestFailedError
|
64
|
+
HttpInternalServerError = Class.new HttpServerError
|
65
|
+
HttpNotImplementedError = Class.new HttpServerError
|
66
|
+
HttpBadGatewayError = Class.new HttpServerError
|
67
|
+
HttpServiceUnavailableError = Class.new HttpServerError
|
68
|
+
HttpGatewayTimeoutError = Class.new HttpServerError
|
69
|
+
HttpVersionNotSupportedError = Class.new HttpServerError
|
70
|
+
HttpInsufficientStorageError = Class.new HttpServerError
|
71
|
+
HttpNotExtendedError = Class.new HttpServerError
|
72
|
+
|
73
|
+
HTTP_STATUS_CODE_ERROR_MAP = {
|
74
|
+
400 => HttpBadRequestError,
|
75
|
+
401 => HttpUnauthorizedError,
|
76
|
+
402 => HttpPaymentRequiredError,
|
77
|
+
403 => HttpForbiddenError,
|
78
|
+
404 => HttpNotFoundError,
|
79
|
+
405 => HttpMethodNotAllowedError,
|
80
|
+
406 => HttpNotAcceptableError,
|
81
|
+
407 => HttpProxyAuthenticationRequiredError,
|
82
|
+
408 => HttpRequestTimeoutError,
|
83
|
+
409 => HttpConflictError,
|
84
|
+
410 => HttpGoneError,
|
85
|
+
411 => HttpLengthRequiredError,
|
86
|
+
412 => HttpPreconditionFailedError,
|
87
|
+
413 => HttpRequestEntityTooLargeError,
|
88
|
+
414 => HttpRequestUriTooLongError,
|
89
|
+
415 => HttpUnsupportedMediaTypeError,
|
90
|
+
416 => HttpRequestRangeNotSatisfiableError,
|
91
|
+
417 => HttpExpectationFailedError,
|
92
|
+
422 => HttpUnprocessableEntityError,
|
93
|
+
423 => HttpLockedError,
|
94
|
+
424 => HttpFailedDependencyError,
|
95
|
+
426 => HttpUpgradeRequiredError,
|
96
|
+
|
97
|
+
# 5xx errors
|
98
|
+
500 => HttpInternalServerError,
|
99
|
+
501 => HttpNotImplementedError,
|
100
|
+
502 => HttpBadGatewayError,
|
101
|
+
503 => HttpServiceUnavailableError,
|
102
|
+
504 => HttpGatewayTimeoutError,
|
103
|
+
505 => HttpVersionNotSupportedError,
|
104
|
+
507 => HttpInsufficientStorageError,
|
105
|
+
510 => HttpNotExtendedError
|
106
|
+
}
|
107
|
+
|
108
|
+
class ServerErrorHandler < ::Faraday::Response::Middleware
|
109
|
+
def on_complete(env)
|
110
|
+
status = env.fetch :status
|
111
|
+
return unless (400..600).include? status
|
112
|
+
raise HTTP_STATUS_CODE_ERROR_MAP.fetch(status), env
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class ParseJson < ::Faraday::Response::Middleware
|
117
|
+
def on_complete(env)
|
118
|
+
return if [204, 304].include? env.fetch(:status)
|
119
|
+
|
120
|
+
content_type = env.fetch(:response_headers).fetch('Content-Type', nil)
|
121
|
+
|
122
|
+
return unless content_type
|
123
|
+
return unless content_type =~ /json/
|
124
|
+
|
125
|
+
body = env.body
|
126
|
+
|
127
|
+
return unless body.respond_to? :to_str
|
128
|
+
|
129
|
+
content = body.to_str.strip
|
130
|
+
|
131
|
+
return if content.empty?
|
132
|
+
|
133
|
+
env[:body] = JSON.load content
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Instrumentation < ::Faraday::Middleware
|
138
|
+
include Harness::Instrumentation
|
139
|
+
|
140
|
+
def initialize(app, progname = 'faraday')
|
141
|
+
@app, @progname = app, progname
|
142
|
+
end
|
143
|
+
|
144
|
+
def call(env)
|
145
|
+
time "#{@progname}.#{env.method}" do
|
146
|
+
@app.call env
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class EncodeJson < ::Faraday::Middleware
|
152
|
+
def initialize(app)
|
153
|
+
@app = app
|
154
|
+
end
|
155
|
+
|
156
|
+
def call(env)
|
157
|
+
if env.body
|
158
|
+
env[:request_headers]['Content-Type'] = 'application/json'
|
159
|
+
env.body = JSON.dump env.body
|
160
|
+
end
|
161
|
+
|
162
|
+
@app.call env
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class Logging < ::Faraday::Response::Middleware
|
167
|
+
def initialize(app, logger)
|
168
|
+
@app, @logger = app, logger
|
169
|
+
end
|
170
|
+
|
171
|
+
def call(env)
|
172
|
+
dump_request env
|
173
|
+
super
|
174
|
+
end
|
175
|
+
|
176
|
+
def on_complete(env)
|
177
|
+
dump_response env
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
def dump_request(env)
|
182
|
+
request_line = "> #{env.fetch(:method).to_s.upcase} #{env.fetch(:url)}"
|
183
|
+
headers = env.fetch(:request_headers).map do |name, value|
|
184
|
+
"> #{name}: #{value}"
|
185
|
+
end.join("\n")
|
186
|
+
|
187
|
+
@logger.debug [request_line, headers, env.body].compact.join("\n")
|
188
|
+
end
|
189
|
+
|
190
|
+
def dump_response(env)
|
191
|
+
status = env.fetch :status
|
192
|
+
status_text = ::Rack::Utils::HTTP_STATUS_CODES.fetch status, 'Unknown'
|
193
|
+
request_line = "> #{status.to_s.upcase} #{status_text}"
|
194
|
+
headers = env.fetch(:response_headers).map do |name, value|
|
195
|
+
"> #{name}: #{value}"
|
196
|
+
end.join("\n")
|
197
|
+
|
198
|
+
@logger.debug [request_line, headers, env.body].compact.join("\n")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
::Faraday::Request.register_middleware instrumentation: Instrumentation
|
203
|
+
::Faraday::Request.register_middleware encode_json: EncodeJson
|
204
|
+
|
205
|
+
::Faraday::Response.register_middleware parse_json: ParseJson
|
206
|
+
::Faraday::Response.register_middleware server_error_handler: ServerErrorHandler
|
207
|
+
::Faraday::Response.register_middleware logging: Logging
|
208
|
+
|
209
|
+
class << self
|
210
|
+
def faraday(host, options = {})
|
211
|
+
namespace = options.delete :namespace
|
212
|
+
logger = options.delete(:logger) || Logger.new.tap { |l| l.progname = 'faraday' }
|
213
|
+
|
214
|
+
Faraday.new host, options do |conn|
|
215
|
+
conn.request :instrumentation, namespace
|
216
|
+
conn.request :encode_json
|
217
|
+
|
218
|
+
conn.response :parse_json
|
219
|
+
conn.response :server_error_handler
|
220
|
+
conn.response :logging, logger
|
221
|
+
|
222
|
+
yield conn if block_given?
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|