api_hammer 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|