liveqa 1.4.6
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 +4 -0
- data/.rspec +1 -0
- data/.rubocop.yml +40 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -0
- data/README.md +40 -0
- data/Rakefile +4 -0
- data/lib/liveqa/api_operation/save.rb +57 -0
- data/lib/liveqa/api_resource.rb +123 -0
- data/lib/liveqa/async_handlers/base.rb +17 -0
- data/lib/liveqa/async_handlers/sidekiq.rb +33 -0
- data/lib/liveqa/config.rb +116 -0
- data/lib/liveqa/errors.rb +41 -0
- data/lib/liveqa/event.rb +22 -0
- data/lib/liveqa/library_name.rb +3 -0
- data/lib/liveqa/liveqa_object.rb +139 -0
- data/lib/liveqa/message.rb +50 -0
- data/lib/liveqa/plugins/rack/middleware.rb +121 -0
- data/lib/liveqa/plugins/rails/middleware_data.rb +44 -0
- data/lib/liveqa/plugins/rails/railtie.rb +23 -0
- data/lib/liveqa/plugins/sidekiq/client_middleware.rb +19 -0
- data/lib/liveqa/plugins/sidekiq/load.rb +14 -0
- data/lib/liveqa/plugins/sidekiq/server_middleware.rb +47 -0
- data/lib/liveqa/plugins.rb +16 -0
- data/lib/liveqa/request.rb +132 -0
- data/lib/liveqa/store.rb +49 -0
- data/lib/liveqa/util.rb +148 -0
- data/lib/liveqa/version.rb +3 -0
- data/lib/liveqa.rb +105 -0
- data/liveqa.gemspec +26 -0
- data/spec/lib/liveqa/async_handlers/base_spec.rb +19 -0
- data/spec/lib/liveqa/async_handlers/sidekiq_spec.rb +40 -0
- data/spec/lib/liveqa/config_spec.rb +40 -0
- data/spec/lib/liveqa/event_spec.rb +36 -0
- data/spec/lib/liveqa/liveqa_object_spec.rb +72 -0
- data/spec/lib/liveqa/message_spec.rb +101 -0
- data/spec/lib/liveqa/plugins/rack/middleware_spec.rb +25 -0
- data/spec/lib/liveqa/plugins/rails/middleware_data_spec.rb +67 -0
- data/spec/lib/liveqa/plugins/sidekiq/client_middleware_spec.rb +15 -0
- data/spec/lib/liveqa/plugins/sidekiq/server_middleware_spec.rb +63 -0
- data/spec/lib/liveqa/store_spec.rb +82 -0
- data/spec/lib/liveqa/util_spec.rb +123 -0
- data/spec/lib/liveqa_spec.rb +78 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/rack_app.rb +12 -0
- metadata +176 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
module LiveQA
|
2
|
+
##
|
3
|
+
# == LiveQA \Message
|
4
|
+
#
|
5
|
+
# Return the base message for an event
|
6
|
+
class Message
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def to_h
|
10
|
+
Util.deep_compact(
|
11
|
+
tracker_id: tracker_id,
|
12
|
+
message_id: SecureRandom.uuid,
|
13
|
+
timestamp: Time.now.utc.iso8601,
|
14
|
+
session_tracker_id: LiveQA::Store.get(:tracker_id),
|
15
|
+
library: library,
|
16
|
+
server: server,
|
17
|
+
request: LiveQA::Store.get(:request),
|
18
|
+
worker: LiveQA::Store.get(:worker),
|
19
|
+
stack: LiveQA::Store.get(:stack),
|
20
|
+
environement: LiveQA::Store.get(:environement)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def tracker_id
|
27
|
+
return LiveQA::Store.get(:tracker_id) if LiveQA::Store.exist?(:tracker_id)
|
28
|
+
|
29
|
+
LiveQA::Store.set(:tracker_id, SecureRandom.uuid)
|
30
|
+
end
|
31
|
+
|
32
|
+
def library
|
33
|
+
{
|
34
|
+
name: LiveQA::LIBRARY_NAME,
|
35
|
+
language: 'ruby',
|
36
|
+
version: LiveQA::VERSION
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def server
|
41
|
+
{
|
42
|
+
host: Socket.gethostname,
|
43
|
+
pid: Process.pid,
|
44
|
+
software: LiveQA::Store.get(:server_software)
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module LiveQA
|
4
|
+
module Plugins
|
5
|
+
module Rack
|
6
|
+
##
|
7
|
+
# LiveQA \Plugins \Rack \Middleware
|
8
|
+
#
|
9
|
+
# Middleware for Rack
|
10
|
+
#
|
11
|
+
class Middleware
|
12
|
+
|
13
|
+
def initialize(app)
|
14
|
+
@app = app
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
request = ::Rack::Request.new(env)
|
19
|
+
|
20
|
+
store_tracker(request)
|
21
|
+
store_request_data(request)
|
22
|
+
store_stack
|
23
|
+
|
24
|
+
LiveQA::Plugins::Rails::MiddlewareData.store_data(request) if defined?(::Rails)
|
25
|
+
|
26
|
+
status, headers, body = @app.call(env)
|
27
|
+
|
28
|
+
write_cookie_tracker_id!(headers)
|
29
|
+
|
30
|
+
[status, headers, body]
|
31
|
+
ensure
|
32
|
+
LiveQA::Store.clear!
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def tracker_id_name
|
38
|
+
'liveqa_tracker_id'.freeze
|
39
|
+
end
|
40
|
+
|
41
|
+
def store_tracker(request)
|
42
|
+
LiveQA::Store.set(
|
43
|
+
:tracker_id,
|
44
|
+
request.cookies[tracker_id_name] || SecureRandom.uuid
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
# rubocop:disable Metrics/MethodLength
|
49
|
+
def store_request_data(request)
|
50
|
+
LiveQA::Store.set(
|
51
|
+
:request,
|
52
|
+
url: obfuscate_uri(request.url),
|
53
|
+
ssl: request.ssl?,
|
54
|
+
host: request.host,
|
55
|
+
port: request.port,
|
56
|
+
path: request.path,
|
57
|
+
referrer: obfuscate_uri(request.referrer),
|
58
|
+
method: request.request_method,
|
59
|
+
xhr: request.xhr?,
|
60
|
+
user_agent: request.user_agent,
|
61
|
+
ip: request.ip,
|
62
|
+
get_params: Util.deep_obfuscate_value(
|
63
|
+
request.GET,
|
64
|
+
LiveQA.configurations.obfuscated_fields
|
65
|
+
),
|
66
|
+
post_params: Util.deep_obfuscate_value(
|
67
|
+
request.POST,
|
68
|
+
LiveQA.configurations.obfuscated_fields
|
69
|
+
)
|
70
|
+
)
|
71
|
+
|
72
|
+
LiveQA::Store.bulk_set(
|
73
|
+
server_software: request.env['SERVER_SOFTWARE']
|
74
|
+
)
|
75
|
+
end
|
76
|
+
# rubocop:enable Metrics/MethodLength
|
77
|
+
|
78
|
+
def store_stack
|
79
|
+
stack = LiveQA::Store.get(:stack) || []
|
80
|
+
|
81
|
+
LiveQA::Store.set(
|
82
|
+
:stack,
|
83
|
+
stack.push(
|
84
|
+
name: 'rack',
|
85
|
+
version: ::Rack.version
|
86
|
+
)
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def write_cookie_tracker_id!(headers)
|
91
|
+
::Rack::Utils.set_cookie_header!(
|
92
|
+
headers || {},
|
93
|
+
tracker_id_name,
|
94
|
+
value: LiveQA::Store.get(:tracker_id),
|
95
|
+
path: '/'
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
def obfuscate_uri(url)
|
100
|
+
uri = URI.parse(url)
|
101
|
+
|
102
|
+
params =
|
103
|
+
Util.deep_obfuscate_value(
|
104
|
+
::Rack::Utils.parse_query(uri.query),
|
105
|
+
LiveQA.configurations.obfuscated_fields,
|
106
|
+
'HIDDEN'
|
107
|
+
)
|
108
|
+
|
109
|
+
return url if params.blank?
|
110
|
+
|
111
|
+
uri.merge(
|
112
|
+
"?#{::Rack::Utils.build_query(params)}"
|
113
|
+
).to_s
|
114
|
+
rescue
|
115
|
+
''
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module LiveQA
|
2
|
+
module Plugins
|
3
|
+
module Rails
|
4
|
+
class MiddlewareData
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def store_data(request)
|
9
|
+
LiveQA::Store.bulk_set(
|
10
|
+
environement: ::Rails.env
|
11
|
+
)
|
12
|
+
store_request_data(request)
|
13
|
+
store_framework
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def store_request_data(request)
|
19
|
+
object = LiveQA::Store.get(:request) || {}
|
20
|
+
|
21
|
+
LiveQA::Store.set(
|
22
|
+
:request,
|
23
|
+
object.merge(id: request.env['action_dispatch.request_id'])
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def store_framework
|
28
|
+
stack = LiveQA::Store.get(:stack) || []
|
29
|
+
|
30
|
+
LiveQA::Store.set(
|
31
|
+
:stack,
|
32
|
+
stack.push(
|
33
|
+
name: 'rails',
|
34
|
+
version: ::Rails.version
|
35
|
+
)
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module LiveQA
|
2
|
+
module Plugins
|
3
|
+
module Rails
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
|
6
|
+
initializer 'liveqa.middleware' do |app|
|
7
|
+
app.config.middleware.use(LiveQA::Plugins::Rack::Middleware)
|
8
|
+
|
9
|
+
if ActiveSupport.const_defined?(:Reloader) && ActiveSupport::Reloader.respond_to?(:to_complete)
|
10
|
+
ActiveSupport::Reloader.to_complete do
|
11
|
+
LiveQA::Store.clear!
|
12
|
+
end
|
13
|
+
elsif ActionDispatch.const_defined?(:Reloader) && ActionDispatch::Reloader.respond_to?(:to_cleanup)
|
14
|
+
ActionDispatch::Reloader.to_cleanup do
|
15
|
+
LiveQA::Store.clear!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module LiveQA
|
2
|
+
module Plugins
|
3
|
+
module Sidekiq
|
4
|
+
##
|
5
|
+
# LiveQA \Plugins \Sidekiq \Client \Middleware
|
6
|
+
#
|
7
|
+
# Client middleware for sidekiq
|
8
|
+
#
|
9
|
+
class ClientMiddleware
|
10
|
+
|
11
|
+
def call(_worker_class, job, _queue, _redis_pool)
|
12
|
+
job['liveqa_store'] = LiveQA::Store.store
|
13
|
+
yield
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'liveqa/plugins/sidekiq/client_middleware'
|
2
|
+
require 'liveqa/plugins/sidekiq/server_middleware'
|
3
|
+
|
4
|
+
Sidekiq.configure_client do |config|
|
5
|
+
config.client_middleware do |chain|
|
6
|
+
chain.add LiveQA::Plugins::Sidekiq::ClientMiddleware
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
Sidekiq.configure_server do |config|
|
11
|
+
config.server_middleware do |chain|
|
12
|
+
chain.add LiveQA::Plugins::Sidekiq::ServerMiddleware
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module LiveQA
|
2
|
+
module Plugins
|
3
|
+
module Sidekiq
|
4
|
+
##
|
5
|
+
# LiveQA \Plugins \Sidekiq \Server \Middleware
|
6
|
+
#
|
7
|
+
# Server middleware for sidekiq
|
8
|
+
#
|
9
|
+
class ServerMiddleware
|
10
|
+
|
11
|
+
def call(_worker, job, _queue)
|
12
|
+
LiveQA::Store.load_from_hash(job['liveqa_store'])
|
13
|
+
store_worker_data(job)
|
14
|
+
yield
|
15
|
+
ensure
|
16
|
+
LiveQA::Store.clear!
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def store_worker_data(job)
|
22
|
+
LiveQA::Store.set(
|
23
|
+
:worker,
|
24
|
+
name: 'sidekiq',
|
25
|
+
version: ::Sidekiq::VERSION,
|
26
|
+
queue: job['queue'],
|
27
|
+
class: job['class'],
|
28
|
+
id: job['jid'],
|
29
|
+
created_at: convert_to_iso8601(job['created_at']),
|
30
|
+
process_at: convert_to_iso8601(job['enqueued_at']),
|
31
|
+
args: job['args'],
|
32
|
+
retried: job['retry_count'].is_a?(Integer),
|
33
|
+
retry_number: job['retry_count'],
|
34
|
+
failed_at: convert_to_iso8601(job['failed_at'])
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def convert_to_iso8601(int_time)
|
39
|
+
Time.at(int_time).utc.iso8601
|
40
|
+
rescue
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
##
|
2
|
+
# Rack & Rails
|
3
|
+
#
|
4
|
+
if defined?(Rack)
|
5
|
+
require 'liveqa/plugins/rack/middleware'
|
6
|
+
|
7
|
+
if defined?(Rails)
|
8
|
+
require 'liveqa/plugins/rails/railtie'
|
9
|
+
require 'liveqa/plugins/rails/middleware_data'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Sidekiq
|
15
|
+
#
|
16
|
+
require 'liveqa/plugins/sidekiq/load' if defined?(Sidekiq)
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module LiveQA
|
2
|
+
##
|
3
|
+
# == LiveQA \Request
|
4
|
+
#
|
5
|
+
# Build request
|
6
|
+
class Request
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def execute(params = {})
|
10
|
+
klass = new(params)
|
11
|
+
|
12
|
+
if params[:method] == :post
|
13
|
+
klass.post
|
14
|
+
elsif params[:method] == :put
|
15
|
+
klass.put
|
16
|
+
elsif params[:method] == :delete
|
17
|
+
klass.delete
|
18
|
+
elsif params[:method] == :get
|
19
|
+
klass.get
|
20
|
+
else
|
21
|
+
raise LiveQA::UnknownRequestMethod, "#{params[:method]} haven't been implemented"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def post(params = {})
|
26
|
+
new(params).post
|
27
|
+
end
|
28
|
+
|
29
|
+
def put(params = {})
|
30
|
+
new(params).put
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(params = {})
|
34
|
+
new(params).get
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete(params = {})
|
38
|
+
new(params).delete
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :params, :http, :request
|
43
|
+
|
44
|
+
def initialize(params = {})
|
45
|
+
@params = params
|
46
|
+
@http = Net::HTTP.new(uri.host, uri.port, uri_proxy.host, uri_proxy.port)
|
47
|
+
|
48
|
+
setup_ssl if params[:use_ssl]
|
49
|
+
end
|
50
|
+
|
51
|
+
def post
|
52
|
+
@request = Net::HTTP::Post.new(uri.path)
|
53
|
+
|
54
|
+
set_header
|
55
|
+
request.body = params[:payload]
|
56
|
+
|
57
|
+
handle_request
|
58
|
+
end
|
59
|
+
|
60
|
+
def put
|
61
|
+
@request = Net::HTTP::Put.new(uri.path)
|
62
|
+
|
63
|
+
request.body = params[:payload]
|
64
|
+
|
65
|
+
handle_request
|
66
|
+
end
|
67
|
+
|
68
|
+
def get
|
69
|
+
@request = Net::HTTP::Get.new(uri)
|
70
|
+
|
71
|
+
set_header
|
72
|
+
|
73
|
+
handle_request
|
74
|
+
end
|
75
|
+
|
76
|
+
def delete
|
77
|
+
@request = Net::HTTP::Delete.new(uri.path)
|
78
|
+
|
79
|
+
request.body = params[:payload]
|
80
|
+
|
81
|
+
handle_request
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def handle_request
|
87
|
+
response = http.request(request)
|
88
|
+
|
89
|
+
case [response.code_type]
|
90
|
+
when [Net::HTTPOK], [Net::HTTPCreated], [Net::HTTPAccepted]
|
91
|
+
response
|
92
|
+
when [Net::HTTPUnauthorized]
|
93
|
+
raise RequestError.new(response, message: 'Unauthorized, please check your api key')
|
94
|
+
when [Net::HTTPNotFound]
|
95
|
+
raise RequestError.new(response, message: 'Requested Resource not found')
|
96
|
+
when [Net::HTTPRequestTimeOut]
|
97
|
+
raise RequestError.new(response, message: 'Server timeout, verify status of the server')
|
98
|
+
when [Net::HTTPInternalServerError]
|
99
|
+
raise RequestError.new(response, message: 'Server Error, please check with LiveQA')
|
100
|
+
else
|
101
|
+
raise RequestError, response
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def set_header
|
106
|
+
params[:headers].each do |(type, value)|
|
107
|
+
formated_type = type.to_s.split(/_/).map(&:capitalize).join('-')
|
108
|
+
request[formated_type] = value
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def setup_ssl
|
113
|
+
http.use_ssl = true
|
114
|
+
http.ca_file = params[:ca_file]
|
115
|
+
http.verify_mode = params[:verify_mode]
|
116
|
+
end
|
117
|
+
|
118
|
+
def uri
|
119
|
+
@uri ||= URI(params[:url])
|
120
|
+
end
|
121
|
+
|
122
|
+
def uri_proxy
|
123
|
+
@uri_proxy ||=
|
124
|
+
if params[:proxy]
|
125
|
+
URI(params[:proxy])
|
126
|
+
else
|
127
|
+
OpenStruct.new
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
data/lib/liveqa/store.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module LiveQA
|
2
|
+
##
|
3
|
+
# == LiveQA \Store
|
4
|
+
#
|
5
|
+
# Store environement data
|
6
|
+
#
|
7
|
+
class Store
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def store
|
11
|
+
Thread.current[:request_store] ||= {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def clear!
|
15
|
+
Thread.current[:request_store] = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_from_hash(object = {})
|
19
|
+
clear!
|
20
|
+
bulk_set(Util.deep_symbolize_key(object)) if object.is_a?(Hash)
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(key)
|
24
|
+
store[key]
|
25
|
+
end
|
26
|
+
alias [] get
|
27
|
+
|
28
|
+
def set(key, value)
|
29
|
+
store[key] = value
|
30
|
+
end
|
31
|
+
alias []= set
|
32
|
+
|
33
|
+
def bulk_set(attributes = {})
|
34
|
+
attributes.each do |(key, value)|
|
35
|
+
set(key, value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def exist?(key)
|
40
|
+
store.key?(key)
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete(key, &block)
|
44
|
+
store.delete(key, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/liveqa/util.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
module LiveQA
|
2
|
+
module Util
|
3
|
+
class << self
|
4
|
+
|
5
|
+
DATE_FORMAT = '%Y/%m/%d'.freeze
|
6
|
+
OBFUSCATED = '[HIDDEN]'.freeze
|
7
|
+
|
8
|
+
##
|
9
|
+
# Remove nil value from hash
|
10
|
+
#
|
11
|
+
# @return [Hash]
|
12
|
+
def compact(hash)
|
13
|
+
hash.reject { |_, value| value.nil? }
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Remove nil value from hash recursively
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
def deep_compact(object)
|
21
|
+
object.each_with_object({}) do |(key, value), result|
|
22
|
+
if value.is_a?(Hash)
|
23
|
+
value = deep_compact(value)
|
24
|
+
result[key] = value if !value.nil? && !value.empty?
|
25
|
+
else
|
26
|
+
result[key] = value unless value.nil?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Encodes a hash of parameters in a way that's suitable for use as query
|
33
|
+
# parameters in a URI or as form parameters in a request body. This mainly
|
34
|
+
# involves escaping special characters from parameter keys and values (e.g.
|
35
|
+
# `&`).
|
36
|
+
def encode_parameters(params = {})
|
37
|
+
params.map { |k, v| "#{url_encode(k)}=#{url_encode(v)}" }.join('&')
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Encodes a string in a way that makes it suitable for use in a set of
|
42
|
+
# query parameters in a URI or in a set of form parameters in a request
|
43
|
+
# body.
|
44
|
+
def url_encode(key)
|
45
|
+
CGI.escape(key.to_s).gsub('%5B', '[').gsub('%5D', ']')
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Convert string to underscore
|
50
|
+
#
|
51
|
+
# @param [String]
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# underscore('MyModel') => 'my_model'
|
55
|
+
def underscore(string)
|
56
|
+
string
|
57
|
+
.gsub(/::/, '/')
|
58
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
59
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
60
|
+
.tr('-', '_')
|
61
|
+
.downcase
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Deep convert hash to underscore case keys
|
66
|
+
#
|
67
|
+
# @param [Hash] hash to transform
|
68
|
+
#
|
69
|
+
# @return [Hash] transformed
|
70
|
+
def deep_underscore_key(hash_object)
|
71
|
+
deep_transform_keys_in_object(hash_object) do |key|
|
72
|
+
begin
|
73
|
+
underscore(key).to_sym
|
74
|
+
rescue
|
75
|
+
key
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Deep convert hash to string keys
|
82
|
+
#
|
83
|
+
# @param [Hash] hash to transform
|
84
|
+
#
|
85
|
+
# @return [Hash] transformed
|
86
|
+
def deep_stringify_key(hash_object)
|
87
|
+
deep_transform_keys_in_object(hash_object) do |key|
|
88
|
+
begin
|
89
|
+
key.to_s
|
90
|
+
rescue
|
91
|
+
key
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Deep convert hash to symbol keys
|
98
|
+
#
|
99
|
+
# @param [Hash] hash to transform
|
100
|
+
#
|
101
|
+
# @return [Hash] transformed
|
102
|
+
def deep_symbolize_key(hash_object)
|
103
|
+
deep_transform_keys_in_object(hash_object) do |key|
|
104
|
+
begin
|
105
|
+
key.to_sym
|
106
|
+
rescue
|
107
|
+
key
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Deep remove key from hash
|
114
|
+
#
|
115
|
+
# @param [Hash] hash to obfuscate
|
116
|
+
#
|
117
|
+
# @return [Hash] hash obfuscated
|
118
|
+
def deep_obfuscate_value(object, fields, obfuscate_name = OBFUSCATED)
|
119
|
+
case object
|
120
|
+
when Hash
|
121
|
+
object.each_with_object({}) do |(key, value), result|
|
122
|
+
result[key] = fields.include?(key.to_s) ? obfuscate_name : deep_obfuscate_value(value, fields)
|
123
|
+
end
|
124
|
+
when Array
|
125
|
+
object.map { |e| deep_obfuscate_value(e, fields) }
|
126
|
+
else
|
127
|
+
object
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def deep_transform_keys_in_object(object, &block)
|
134
|
+
case object
|
135
|
+
when Hash
|
136
|
+
object.each_with_object({}) do |(key, value), result|
|
137
|
+
result[yield(key)] = deep_transform_keys_in_object(value, &block)
|
138
|
+
end
|
139
|
+
when Array
|
140
|
+
object.map { |e| deep_transform_keys_in_object(e, &block) }
|
141
|
+
else
|
142
|
+
object
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|