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