active_rest_client 0.9.58
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/.rspec +2 -0
- data/.simplecov +4 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +585 -0
- data/Rakefile +3 -0
- data/active_rest_client.gemspec +34 -0
- data/lib/active_rest_client.rb +23 -0
- data/lib/active_rest_client/base.rb +128 -0
- data/lib/active_rest_client/caching.rb +84 -0
- data/lib/active_rest_client/configuration.rb +69 -0
- data/lib/active_rest_client/connection.rb +76 -0
- data/lib/active_rest_client/connection_manager.rb +21 -0
- data/lib/active_rest_client/headers_list.rb +47 -0
- data/lib/active_rest_client/instrumentation.rb +62 -0
- data/lib/active_rest_client/lazy_association_loader.rb +95 -0
- data/lib/active_rest_client/lazy_loader.rb +23 -0
- data/lib/active_rest_client/logger.rb +67 -0
- data/lib/active_rest_client/mapping.rb +65 -0
- data/lib/active_rest_client/proxy_base.rb +143 -0
- data/lib/active_rest_client/recording.rb +24 -0
- data/lib/active_rest_client/request.rb +412 -0
- data/lib/active_rest_client/request_filtering.rb +52 -0
- data/lib/active_rest_client/result_iterator.rb +66 -0
- data/lib/active_rest_client/validation.rb +60 -0
- data/lib/active_rest_client/version.rb +3 -0
- data/spec/lib/base_spec.rb +245 -0
- data/spec/lib/caching_spec.rb +179 -0
- data/spec/lib/configuration_spec.rb +105 -0
- data/spec/lib/connection_manager_spec.rb +36 -0
- data/spec/lib/connection_spec.rb +73 -0
- data/spec/lib/headers_list_spec.rb +61 -0
- data/spec/lib/instrumentation_spec.rb +59 -0
- data/spec/lib/lazy_association_loader_spec.rb +118 -0
- data/spec/lib/lazy_loader_spec.rb +25 -0
- data/spec/lib/logger_spec.rb +63 -0
- data/spec/lib/mapping_spec.rb +48 -0
- data/spec/lib/proxy_spec.rb +154 -0
- data/spec/lib/recording_spec.rb +34 -0
- data/spec/lib/request_filtering_spec.rb +72 -0
- data/spec/lib/request_spec.rb +471 -0
- data/spec/lib/result_iterator_spec.rb +104 -0
- data/spec/lib/validation_spec.rb +113 -0
- data/spec/spec_helper.rb +22 -0
- metadata +265 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
module ActiveRestClient
|
2
|
+
class HeadersList
|
3
|
+
STORE_MULTIPLE_VALUES = ["set-cookie"]
|
4
|
+
def initialize
|
5
|
+
@store = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def []=(key,value)
|
9
|
+
key = find_existing(key)
|
10
|
+
if STORE_MULTIPLE_VALUES.include?(key.downcase)
|
11
|
+
@store[key] ||= []
|
12
|
+
@store[key] << value
|
13
|
+
else
|
14
|
+
@store[key] = value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
key = find_existing(key)
|
20
|
+
@store[key]
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(split_multiple_headers = false)
|
24
|
+
@store.keys.each do |key|
|
25
|
+
value = @store[key]
|
26
|
+
if value.is_a?(Array) && split_multiple_headers
|
27
|
+
value.each do |inner_value|
|
28
|
+
yield(key, inner_value)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
yield(key, value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def find_existing(key)
|
39
|
+
key_downcase = key.downcase
|
40
|
+
@store.keys.each do |found_key|
|
41
|
+
return found_key if found_key.downcase == key_downcase
|
42
|
+
end
|
43
|
+
key
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module ActiveRestClient
|
2
|
+
class Instrumentation < ActiveSupport::LogSubscriber
|
3
|
+
def request_call(event)
|
4
|
+
self.class.time_spent += event.duration
|
5
|
+
self.class.calls_made += 1
|
6
|
+
name = '%s (%.1fms)' % [ActiveRestClient::NAME, event.duration]
|
7
|
+
ActiveRestClient::Logger.debug " \033[1;4;32m#{name}\033[0m #{event.payload[:name]}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.time_spent=(value)
|
11
|
+
@@time_spent = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.time_spent
|
15
|
+
@@time_spent ||= 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.calls_made=(value)
|
19
|
+
@@calls_made = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.calls_made
|
23
|
+
@@calls_made ||= 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.reset
|
27
|
+
@@time_spent = 0
|
28
|
+
@@calls_made = 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def logger
|
32
|
+
ActiveRestClient::Logger
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module ControllerInstrumentation
|
37
|
+
extend ActiveSupport::Concern
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def append_info_to_payload(payload)
|
42
|
+
super
|
43
|
+
payload[:active_rest_client_time_spent] = ActiveRestClient::Instrumentation.time_spent
|
44
|
+
payload[:active_rest_client_calls_made] = ActiveRestClient::Instrumentation.calls_made
|
45
|
+
end
|
46
|
+
|
47
|
+
module ClassMethods
|
48
|
+
def log_process_action(payload)
|
49
|
+
messages, time_spent, calls_made = super, payload[:active_rest_client_time_spent], payload[:active_rest_client_calls_made]
|
50
|
+
messages << ("#{ActiveRestClient::NAME}: %.1fms for %d calls" % [time_spent.to_f, calls_made]) if calls_made
|
51
|
+
ActiveRestClient::Instrumentation.reset
|
52
|
+
messages
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
ActiveRestClient::Instrumentation.attach_to :active_rest_client
|
59
|
+
|
60
|
+
ActiveSupport.on_load(:action_controller) do
|
61
|
+
include ActiveRestClient::ControllerInstrumentation
|
62
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'active_support/hash_with_indifferent_access'
|
2
|
+
|
3
|
+
module ActiveRestClient
|
4
|
+
class LazyAssociationLoader
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(name, value, request, options = {})
|
8
|
+
@name = name
|
9
|
+
@request = request
|
10
|
+
@object = nil
|
11
|
+
@options = options
|
12
|
+
if value.is_a? Array
|
13
|
+
@subloaders = value.map {|url| LazyAssociationLoader.new(name, url, request, options)}
|
14
|
+
elsif value.is_a?(Hash) && (value.has_key?("url") || value.has_key?(:url))
|
15
|
+
@url = (value["url"] || value[:url])
|
16
|
+
elsif value.is_a?(Hash) && (value.has_key?("href") || value.has_key?(:href)) # HAL
|
17
|
+
@url = (value["href"] || value[:href])
|
18
|
+
@_hal_attributes = HashWithIndifferentAccess.new(value)
|
19
|
+
elsif value.is_a?(Hash)
|
20
|
+
mapped = {}
|
21
|
+
value.each do |k,v|
|
22
|
+
mapped[k.to_sym] = LazyAssociationLoader.new(name, v, request, options)
|
23
|
+
end
|
24
|
+
@subloaders = mapped
|
25
|
+
# Need to also ensure that the hash/wrapped object is returned when the property is accessed
|
26
|
+
elsif value.is_a? String
|
27
|
+
@url = value
|
28
|
+
else
|
29
|
+
raise InvalidLazyAssociationContentException.new("Invalid content for #{@name}, expected Array, String or Hash containing 'url' key")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def _hal_attributes(key)
|
34
|
+
@_hal_attributes[key]
|
35
|
+
end
|
36
|
+
|
37
|
+
def size
|
38
|
+
if @subloaders
|
39
|
+
@subloaders.size
|
40
|
+
else
|
41
|
+
ensure_lazy_loaded
|
42
|
+
@object.size
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def each
|
47
|
+
if @subloaders
|
48
|
+
if @subloaders.is_a? Array
|
49
|
+
@subloaders.each do |loader|
|
50
|
+
yield loader
|
51
|
+
end
|
52
|
+
elsif @subloaders.is_a? Hash
|
53
|
+
@subloaders.each do |key,value|
|
54
|
+
yield key, value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
ensure_lazy_loaded
|
59
|
+
@object.each do |obj|
|
60
|
+
yield obj
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def keys
|
66
|
+
@subloaders.keys
|
67
|
+
end
|
68
|
+
|
69
|
+
def method_missing(name, *args)
|
70
|
+
if @subloaders.is_a? Hash
|
71
|
+
return @subloaders[name.to_sym]
|
72
|
+
end
|
73
|
+
ensure_lazy_loaded
|
74
|
+
if @object
|
75
|
+
@object.send(name, *args)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def ensure_lazy_loaded
|
82
|
+
if @object.nil?
|
83
|
+
@request.method[:method] = :get
|
84
|
+
@request.method[:options][:url] = @url
|
85
|
+
@request.method[:options][:overriden_name] = @options[:overriden_name]
|
86
|
+
request = ActiveRestClient::Request.new(@request.method, @request.object)
|
87
|
+
request.url = request.forced_url = @url
|
88
|
+
@object = request.call
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class InvalidLazyAssociationContentException < Exception ; end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRestClient
|
2
|
+
class LazyLoader
|
3
|
+
def initialize(request, params = nil)
|
4
|
+
@request = request
|
5
|
+
@params = params
|
6
|
+
@result = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(name, *args)
|
10
|
+
if @result.nil?
|
11
|
+
@result = @request.call(@params)
|
12
|
+
end
|
13
|
+
@result.send(name, *args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to?(name)
|
17
|
+
if @result.nil?
|
18
|
+
@result = @request.call(@params)
|
19
|
+
end
|
20
|
+
@result.respond_to?(name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module ActiveRestClient
|
2
|
+
class Logger
|
3
|
+
@logfile = nil
|
4
|
+
@messages = []
|
5
|
+
|
6
|
+
def self.logfile=(value)
|
7
|
+
@logfile = value
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.messages
|
11
|
+
@messages
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.reset!
|
15
|
+
@logfile = nil
|
16
|
+
@messages = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.debug(message)
|
20
|
+
if defined?(Rails) && Rails.respond_to?(:logger)
|
21
|
+
Rails.logger.debug(message)
|
22
|
+
elsif @logfile
|
23
|
+
File.open(@logfile, "a") do |f|
|
24
|
+
f << message
|
25
|
+
end
|
26
|
+
else
|
27
|
+
@messages << message
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.info(message)
|
32
|
+
if defined?(Rails) && Rails.respond_to?(:logger)
|
33
|
+
Rails.logger.info(message)
|
34
|
+
elsif @logfile
|
35
|
+
File.open(@logfile, "a") do |f|
|
36
|
+
f << message
|
37
|
+
end
|
38
|
+
else
|
39
|
+
@messages << message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.warn(message)
|
44
|
+
if defined?(Rails) && Rails.respond_to?(:logger)
|
45
|
+
Rails.logger.warn(message)
|
46
|
+
elsif @logfile
|
47
|
+
File.open(@logfile, "a") do |f|
|
48
|
+
f << message
|
49
|
+
end
|
50
|
+
else
|
51
|
+
@messages << message
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.error(message)
|
56
|
+
if defined?(Rails) && Rails.respond_to?(:logger)
|
57
|
+
Rails.logger.error(message)
|
58
|
+
elsif @logfile
|
59
|
+
File.open(@logfile, "a") do |f|
|
60
|
+
f << message
|
61
|
+
end
|
62
|
+
else
|
63
|
+
@messages << message
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ActiveRestClient
|
2
|
+
module Mapping
|
3
|
+
module ClassMethods
|
4
|
+
def get(name, url, options = {})
|
5
|
+
_map_call(name, url:url, method: :get, options:options)
|
6
|
+
end
|
7
|
+
|
8
|
+
def put(name, url, options = {})
|
9
|
+
_map_call(name, url:url, method: :put, options:options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def post(name, url, options = {})
|
13
|
+
_map_call(name, url:url, method: :post, options:options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(name, url, options = {})
|
17
|
+
_map_call(name, url:url, method: :delete, options:options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def _map_call(name, details)
|
21
|
+
_calls[name] = {name:name}.merge(details)
|
22
|
+
_calls["lazy_#{name}".to_sym] = {name:name}.merge(details)
|
23
|
+
self.class.send(:define_method, name) do |options={}|
|
24
|
+
_call(name, options)
|
25
|
+
end
|
26
|
+
self.class.send(:define_method, "lazy_#{name}".to_sym) do |options={}|
|
27
|
+
_call("lazy_#{name}", options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def _call(name, options)
|
32
|
+
mapped = _calls[name]
|
33
|
+
lazy_forced = false
|
34
|
+
if mapped.nil? && name.to_s[/^lazy_/]
|
35
|
+
mapped = _calls[name.to_s.gsub(/^lazy_/, '').to_sym]
|
36
|
+
lazy_forced = true
|
37
|
+
end
|
38
|
+
request = Request.new(mapped, self, options)
|
39
|
+
if lazy_load? || lazy_forced
|
40
|
+
ActiveRestClient::LazyLoader.new(request)
|
41
|
+
else
|
42
|
+
request.call
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def _calls
|
47
|
+
@_calls
|
48
|
+
end
|
49
|
+
|
50
|
+
def _mapped_method(name)
|
51
|
+
_calls[name]
|
52
|
+
end
|
53
|
+
|
54
|
+
def inherited(subclass)
|
55
|
+
subclass.instance_variable_set(:@_calls, {})
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.included(base)
|
61
|
+
base.extend(ClassMethods)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module ActiveRestClient
|
2
|
+
class ProxyBase
|
3
|
+
cattr_accessor :mappings, :request, :original_handler
|
4
|
+
cattr_accessor :original_body, :original_get_params, :original_post_params, :original_url
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def get(match, &block)
|
8
|
+
add_mapping(:get, match, block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def post(match, &block)
|
12
|
+
add_mapping(:post, match, block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def put(match, &block)
|
16
|
+
add_mapping(:put, match, block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(match, &block)
|
20
|
+
add_mapping(:delete, match, block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_mapping(method_type, match, block)
|
24
|
+
@mappings ||= []
|
25
|
+
|
26
|
+
if match.is_a?(String) && (param_keys = match.scan(/:\w+/)) && param_keys.any?
|
27
|
+
param_keys.each do |key|
|
28
|
+
match.gsub!(key, "([^/]+)")
|
29
|
+
end
|
30
|
+
param_keys = param_keys.map {|k| k.gsub(":", "").to_sym}
|
31
|
+
match = Regexp.new(match)
|
32
|
+
end
|
33
|
+
|
34
|
+
@mappings << OpenStruct.new(http_method:method_type, match:match, block:block, param_keys:param_keys)
|
35
|
+
end
|
36
|
+
|
37
|
+
def body(value = nil)
|
38
|
+
@body = value if value
|
39
|
+
@body
|
40
|
+
end
|
41
|
+
|
42
|
+
def url(value = nil)
|
43
|
+
@url = value if value
|
44
|
+
@url
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_params(value = nil)
|
48
|
+
@get_params = value if value
|
49
|
+
@get_params
|
50
|
+
end
|
51
|
+
|
52
|
+
def post_params(value = nil)
|
53
|
+
@post_params = value if value
|
54
|
+
@post_params
|
55
|
+
end
|
56
|
+
|
57
|
+
def params(value = nil)
|
58
|
+
@params = value if value
|
59
|
+
@params
|
60
|
+
end
|
61
|
+
|
62
|
+
def passthrough
|
63
|
+
rebuild_request
|
64
|
+
@original_handler.call(@request)
|
65
|
+
end
|
66
|
+
|
67
|
+
def translate(result, options = {})
|
68
|
+
result.headers["content-type"] = "application/hal+json"
|
69
|
+
result = OpenStruct.new(status:result.status, headers:result.headers, body:result.body)
|
70
|
+
obj = Oj.load(result.body)
|
71
|
+
result.body = yield obj
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
75
|
+
def rebuild_request
|
76
|
+
if @url != @original_url
|
77
|
+
@request.forced_url = @request.url = @url
|
78
|
+
end
|
79
|
+
if @body != @original_body
|
80
|
+
@request.body = @body
|
81
|
+
elsif @post_params != @original_post_params
|
82
|
+
@request.body = nil
|
83
|
+
@request.prepare_request_body(@post_params)
|
84
|
+
end
|
85
|
+
if @get_params != @original_get_params
|
86
|
+
@request.get_params = @get_params
|
87
|
+
@request.prepare_url
|
88
|
+
@request.append_get_parameters
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def handle(request, &block)
|
93
|
+
@request = request
|
94
|
+
@original_handler = block
|
95
|
+
|
96
|
+
@original_body = request.body
|
97
|
+
@body = @original_body.dup
|
98
|
+
|
99
|
+
@original_get_params = request.get_params
|
100
|
+
@get_params = @original_get_params.dup
|
101
|
+
|
102
|
+
@original_post_params = request.post_params
|
103
|
+
@post_params = (@original_post_params || {}).dup
|
104
|
+
|
105
|
+
@original_url = request.url
|
106
|
+
@url = @original_url.dup
|
107
|
+
|
108
|
+
if mapping = find_mapping_for_current_request
|
109
|
+
self.class_eval(&mapping.block)
|
110
|
+
else
|
111
|
+
passthrough
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def find_mapping_for_current_request
|
116
|
+
uri = URI.parse(@original_url)
|
117
|
+
@mappings ||= []
|
118
|
+
@params = {}
|
119
|
+
@mappings.each do |mapping|
|
120
|
+
match = mapping.match
|
121
|
+
if (match_data = uri.path.match(match)) && @request.http_method.to_sym == mapping.http_method
|
122
|
+
matches = match_data.to_a
|
123
|
+
matches.shift
|
124
|
+
matches.each_with_index do |value, index|
|
125
|
+
@params[mapping.param_keys[index]] = value
|
126
|
+
end
|
127
|
+
return mapping
|
128
|
+
end
|
129
|
+
end
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
def render(body, status=200, content_type="application/javascript", headers={})
|
134
|
+
headers["Content-type"] = content_type
|
135
|
+
OpenStruct.new(body:body, status:status, headers:headers, proxied:true)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.inherited(base)
|
140
|
+
base.extend(ClassMethods)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|