api_hammer 0.2.1 → 0.2.2
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 +4 -4
- data/CHANGELOG.md +5 -0
- data/bin/hc +1 -1
- data/lib/api_hammer/faraday/outputter.rb +121 -109
- data/lib/api_hammer/halt.rb +10 -0
- data/lib/api_hammer/request_logger.rb +22 -3
- data/lib/api_hammer/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96ebda010e8ade01533266d0f06a7e13026fd746
|
4
|
+
data.tar.gz: a5e217afbf69ec2d4c0fb2635cce63ba4baf2c9a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f7a065899bd65bc13b9dee409796d9778c3104a7bb2addc7a65e33409e3f94de247fa3d4ae2a3dd826d5ee630b9beb1af32a0f96a410c65dd629b616e88716d
|
7
|
+
data.tar.gz: f4c8bd00c8c74a02517594fd55e45ba08f46ebb44ad87104ddc87d13e59c75acbfc48a04dc13a48a048a9add4676c5ce39c7a1383c3a50777e5a02b5d1c238c5
|
data/CHANGELOG.md
CHANGED
data/bin/hc
CHANGED
@@ -88,7 +88,7 @@ connection = Faraday.new(faraday_options) do |builder|
|
|
88
88
|
OAuthenticator::FaradaySigner
|
89
89
|
builder.use OAuthenticator::FaradaySigner, $oauth
|
90
90
|
end
|
91
|
-
builder.use($options[:verbose] ? FaradayCurlVOutputter : FaradayOutputter, $options)
|
91
|
+
builder.use($options[:verbose] ? ApiHammer::FaradayCurlVOutputter : ApiHammer::FaradayOutputter, $options)
|
92
92
|
builder.adapter Faraday.default_adapter
|
93
93
|
end
|
94
94
|
|
@@ -1,137 +1,149 @@
|
|
1
1
|
require 'faraday'
|
2
2
|
require 'rack'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
module ApiHammer
|
5
|
+
# outputs the response body to the given logger or output device (defaulting to STDOUT)
|
6
|
+
class FaradayOutputter < Faraday::Middleware
|
7
|
+
def initialize(app, options={})
|
8
|
+
@app=app
|
9
|
+
@options = options
|
10
|
+
@outdev = @options[:outdev] || STDOUT
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(request_env)
|
14
|
+
@app.call(request_env).on_complete do |response_env|
|
15
|
+
puts(response_env[:body] || '')
|
16
|
+
end
|
17
|
+
end
|
11
18
|
|
12
|
-
|
13
|
-
|
14
|
-
|
19
|
+
def puts(str)
|
20
|
+
meth = @options[:logger] ? @options[:logger].method(:info) : (@options[:outdev] || STDOUT).method(:puts)
|
21
|
+
meth.call(str)
|
15
22
|
end
|
16
23
|
end
|
17
|
-
end
|
18
24
|
|
19
|
-
# this is to approximate `curl -v`s output. but it's all faked, whereas curl gives you
|
20
|
-
# the real text written and read for request and response. whatever, close enough.
|
21
|
-
class FaradayCurlVOutputter < FaradayOutputter
|
25
|
+
# this is to approximate `curl -v`s output. but it's all faked, whereas curl gives you
|
26
|
+
# the real text written and read for request and response. whatever, close enough.
|
27
|
+
class FaradayCurlVOutputter < FaradayOutputter
|
22
28
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
# defines a method with the given name, applying coloring defined by any additional arguments.
|
30
|
+
# if @options[:color] is set, respects that; otherwise, applies color if the output device is a tty.
|
31
|
+
def self.color(name, *color_args)
|
32
|
+
define_method(name) do |arg|
|
33
|
+
if color?
|
34
|
+
require 'term/ansicolor'
|
35
|
+
color_args.inject(arg) do |result, color_arg|
|
36
|
+
Term::ANSIColor.send(color_arg, result)
|
37
|
+
end
|
38
|
+
else
|
39
|
+
arg
|
31
40
|
end
|
32
|
-
else
|
33
|
-
arg
|
34
41
|
end
|
35
42
|
end
|
36
|
-
end
|
37
43
|
|
38
|
-
|
39
|
-
|
40
|
-
|
44
|
+
color :info, :intense_yellow
|
45
|
+
color :info_body, :yellow
|
46
|
+
color :protocol
|
41
47
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
48
|
+
color :request, :intense_cyan
|
49
|
+
color :request_verb, :bold
|
50
|
+
color :request_header
|
51
|
+
color :request_blankline, :intense_cyan, :bold
|
46
52
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
53
|
+
color :response, :intense_green
|
54
|
+
color :response_status, :bold, :green
|
55
|
+
color :response_header
|
56
|
+
color :response_blankline, :intense_green, :bold
|
51
57
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
@app.call(request_env).on_complete do |response_env|
|
65
|
-
@outdev.puts "#{response('<')} #{protocol('HTTP/1.1' || 'or something - TODO')} #{response_status(response_env[:status].to_s)}"
|
66
|
-
request_env[:response_headers].each do |k, v|
|
67
|
-
@outdev.puts "#{response('<')} #{response_header(k)}#{response(':')} #{v}"
|
58
|
+
def call(request_env)
|
59
|
+
puts "#{info('*')} #{info_body("connect to #{request_env[:url].host} on port #{request_env[:url].port}")}"
|
60
|
+
puts "#{info('*')} #{info_body("getting our SSL on")}" if request_env[:url].scheme=='https'
|
61
|
+
puts "#{request('>')} #{request_verb(request_env[:method].to_s.upcase)} #{request_env[:url].request_uri} #{protocol("HTTP/#{Net::HTTP::HTTPVersion}")}"
|
62
|
+
request_env[:request_headers].each do |k, v|
|
63
|
+
puts "#{request('>')} #{request_header(k)}#{request(':')} #{v}"
|
64
|
+
end
|
65
|
+
puts "#{request_blankline('>')} "
|
66
|
+
request_body = alter_body_by_content_type(request_env[:body], request_env[:request_headers]['Content-Type'])
|
67
|
+
(request_body || '').split("\n", -1).each do |line|
|
68
|
+
puts "#{request('>')} #{line}"
|
68
69
|
end
|
69
|
-
@
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
@app.call(request_env).on_complete do |response_env|
|
71
|
+
puts "#{response('<')} #{protocol("HTTP/#{Net::HTTP::HTTPVersion}")} #{response_status(response_env[:status].to_s)}"
|
72
|
+
request_env[:response_headers].each do |k, v|
|
73
|
+
puts "#{response('<')} #{response_header(k)}#{response(':')} #{v}"
|
74
|
+
end
|
75
|
+
puts "#{response_blankline ('<')} "
|
76
|
+
response_body = alter_body_by_content_type(response_env[:body], response_env[:response_headers]['Content-Type'])
|
77
|
+
(response_body || '').split("\n", -1).each do |line|
|
78
|
+
puts "#{response('<')} #{line}"
|
79
|
+
end
|
73
80
|
end
|
74
81
|
end
|
75
|
-
end
|
76
82
|
|
77
|
-
|
78
|
-
|
79
|
-
|
83
|
+
def pretty?
|
84
|
+
@options[:pretty].nil? ? true : @options[:pretty]
|
85
|
+
end
|
80
86
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
87
|
+
# whether to use color. if we're writing to a device, check if it's a tty; otherwise we should have a
|
88
|
+
# logger and just assume color.
|
89
|
+
def color?
|
90
|
+
if @options[:color].nil?
|
91
|
+
@options[:outdev] ? @options[:outdev].tty? : true
|
92
|
+
else
|
93
|
+
@options[:color]
|
94
|
+
end
|
95
|
+
end
|
85
96
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
97
|
+
# a mapping for each registered CodeRay scanner to the Media Types which represent
|
98
|
+
# that language. extremely incomplete!
|
99
|
+
CodeRayForMediaTypes = {
|
100
|
+
:c => [],
|
101
|
+
:cpp => [],
|
102
|
+
:clojure => [],
|
103
|
+
:css => ['text/css', 'application/css-stylesheet'],
|
104
|
+
:delphi => [],
|
105
|
+
:diff => [],
|
106
|
+
:erb => [],
|
107
|
+
:groovy => [],
|
108
|
+
:haml => [],
|
109
|
+
:html => ['text/html'],
|
110
|
+
:java => [],
|
111
|
+
:java_script => ['application/javascript', 'text/javascript', 'application/x-javascript'],
|
112
|
+
:json => ['application/json'],
|
113
|
+
:php => [],
|
114
|
+
:python => ['text/x-python'],
|
115
|
+
:ruby => [],
|
116
|
+
:sql => [],
|
117
|
+
:xml => ['text/xml', 'application/xml', %r(\Aapplication/.*\+xml\z)],
|
118
|
+
:yaml => [],
|
119
|
+
}
|
109
120
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
121
|
+
# takes a body and a content type; returns the body, altered according to options.
|
122
|
+
#
|
123
|
+
# - with coloring (ansi colors for terminals) possibly added, if it's a recognized content type and
|
124
|
+
# #color? is true
|
125
|
+
# - formatted prettily if #pretty? is true
|
126
|
+
def alter_body_by_content_type(body, content_type)
|
127
|
+
return body unless body.is_a?(String)
|
128
|
+
media_type = ::Rack::Request.new({'CONTENT_TYPE' => content_type}).media_type
|
129
|
+
if pretty?
|
130
|
+
case media_type
|
131
|
+
when 'application/json'
|
132
|
+
require 'json'
|
133
|
+
begin
|
134
|
+
body = JSON.pretty_generate(JSON.parse(body))
|
135
|
+
rescue JSON::ParserError
|
136
|
+
end
|
125
137
|
end
|
126
138
|
end
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
139
|
+
if color?
|
140
|
+
coderay_scanner = CodeRayForMediaTypes.reject{|k,v| !v.any?{|type| type === media_type} }.keys.first
|
141
|
+
if coderay_scanner
|
142
|
+
require 'coderay'
|
143
|
+
body = CodeRay.scan(body, coderay_scanner).encode(:terminal)
|
144
|
+
end
|
133
145
|
end
|
146
|
+
body
|
134
147
|
end
|
135
|
-
body
|
136
148
|
end
|
137
149
|
end
|
data/lib/api_hammer/halt.rb
CHANGED
@@ -45,6 +45,16 @@ module ApiHammer::Rails
|
|
45
45
|
|
46
46
|
# halt and render the given errors in the body on the 'errors' key
|
47
47
|
def halt_error(status, errors, render_options = {})
|
48
|
+
errors_as_json = errors.respond_to?(:as_json) ? errors.as_json : errors
|
49
|
+
unless errors_as_json.is_a?(Hash)
|
50
|
+
raise ArgumentError, "errors be an object representable in JSON as a Hash; got errors = #{errors.inspect}"
|
51
|
+
end
|
52
|
+
unless errors_as_json.keys.all? { |k| k.is_a?(String) || k.is_a?(Symbol) }
|
53
|
+
raise ArgumentError, "errors keys must all be string or symbol; got errors = #{errors.inspect}"
|
54
|
+
end
|
55
|
+
unless errors_as_json.values.all? { |v| v.is_a?(Array) && v.all? { |e| e.is_a?(String) } }
|
56
|
+
raise ArgumentError, "errors values must all be arrays of strings; got errors = #{errors.inspect}"
|
57
|
+
end
|
48
58
|
halt(status, {'errors' => errors}, render_options)
|
49
59
|
end
|
50
60
|
|
@@ -24,7 +24,7 @@ module ApiHammer
|
|
24
24
|
env["rack.input"].rewind
|
25
25
|
|
26
26
|
log_tags = Thread.current[:activesupport_tagged_logging_tags]
|
27
|
-
@log_tags = log_tags.dup if log_tags
|
27
|
+
@log_tags = log_tags.dup if log_tags && log_tags.any?
|
28
28
|
|
29
29
|
status, headers, body = @app.call(env)
|
30
30
|
headers = ::Rack::Utils::HeaderHash.new(headers)
|
@@ -76,8 +76,6 @@ module ApiHammer
|
|
76
76
|
'length' => headers['Content-Length'] || body.to_enum.map(&::Rack::Utils.method(:bytesize)).inject(0, &:+),
|
77
77
|
'Location' => response.location,
|
78
78
|
'Content-Type' => response.content_type,
|
79
|
-
# only log response body if there was an error (either client or server)
|
80
|
-
'body' => (400..599).include?(status.to_i) ? body.to_enum.to_a.join('') : nil
|
81
79
|
}.reject{|k,v| v.nil? },
|
82
80
|
'processing' => {
|
83
81
|
'began_at' => began_at.utc.to_i,
|
@@ -85,6 +83,27 @@ module ApiHammer
|
|
85
83
|
'activesupport_tagged_logging_tags' => @log_tags,
|
86
84
|
}.merge(env['request_logger.info'] || {}).merge(Thread.current['request_logger.info'] || {}).reject{|k,v| v.nil? },
|
87
85
|
}
|
86
|
+
ids_from_body = proc do |body_string, content_type|
|
87
|
+
media_type = ::Rack::Request.new({'CONTENT_TYPE' => content_type}).media_type
|
88
|
+
if media_type == 'application/json'
|
89
|
+
body_object = JSON.parse(body_string) rescue nil
|
90
|
+
# TODO form encoded
|
91
|
+
end
|
92
|
+
if body_object.is_a?(Hash)
|
93
|
+
body_object.reject { |key, value| !(key =~ /\b(uu)?id\b/ && value.is_a?(String)) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
request_body_ids = ids_from_body.call(@request_body, request.content_type)
|
97
|
+
data['request']['body_ids'] = request_body_ids if request_body_ids && request_body_ids.any?
|
98
|
+
response_body_string = body.to_enum.to_a.join('')
|
99
|
+
if (400..599).include?(status.to_i)
|
100
|
+
# only log response body if there was an error (either client or server)
|
101
|
+
data['response']['body'] = response_body_string
|
102
|
+
else
|
103
|
+
# otherwise, log id and uuid fields
|
104
|
+
response_body_ids = ids_from_body.call(response_body_string, response.content_type)
|
105
|
+
data['response']['body_ids'] = response_body_ids if response_body_ids && response_body_ids.any?
|
106
|
+
end
|
88
107
|
Thread.current['request_logger.info'] = nil
|
89
108
|
json_data = JSON.dump(data)
|
90
109
|
dolog = proc do
|
data/lib/api_hammer/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api_hammer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ethan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|