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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4ae180a72b1ebca9d3f4806d6adb646247900aff
4
- data.tar.gz: 658496ca213bf67761890117ec56497b534764ed
3
+ metadata.gz: 96ebda010e8ade01533266d0f06a7e13026fd746
4
+ data.tar.gz: a5e217afbf69ec2d4c0fb2635cce63ba4baf2c9a
5
5
  SHA512:
6
- metadata.gz: c83bcc6fb8e5ab284ff7a005a1abb4daa494251cdf8be8e3c5eecf3e81823b5b059c2e615c67e0320cfa208ebec72dd5a001c826a9efe2cb5b83de43f029f71f
7
- data.tar.gz: 75da8ae6628b64fb630c010dc88a29ff7aef5f1cd7e58c4886a8cf1b8647d4b70511b50ed42f3044f946785db79bcf48a764febed8b7c088900864d5719aa5d2
6
+ metadata.gz: 8f7a065899bd65bc13b9dee409796d9778c3104a7bb2addc7a65e33409e3f94de247fa3d4ae2a3dd826d5ee630b9beb1af32a0f96a410c65dd629b616e88716d
7
+ data.tar.gz: f4c8bd00c8c74a02517594fd55e45ba08f46ebb44ad87104ddc87d13e59c75acbfc48a04dc13a48a048a9add4676c5ce39c7a1383c3a50777e5a02b5d1c238c5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 0.2.2
2
+ - RequestLogger, in addition to logging response bodies on error, logs id/uuid fields from request body and
3
+ response body if there's no error
4
+ - support a logger instead of a device in FaradayOutputter
5
+
1
6
  # 0.2.1
2
7
  - small RequestLogger tweaks
3
8
 
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
- # outputs the response body to the given output device (defaulting to STDOUT)
5
- class FaradayOutputter < Faraday::Middleware
6
- def initialize(app, options={})
7
- @app=app
8
- @options = options
9
- @outdev = @options[:outdev] || STDOUT
10
- end
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
- def call(request_env)
13
- @app.call(request_env).on_complete do |response_env|
14
- @outdev.puts(response_env[:body] || '')
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
- # defines a method with the given name, applying coloring defined by any additional arguments.
24
- # if @options[:color] is set, respects that; otherwise, applies color if the output device is a tty.
25
- def self.color(name, *color_args)
26
- define_method(name) do |arg|
27
- if color?
28
- require 'term/ansicolor'
29
- color_args.inject(arg) do |result, color_arg|
30
- Term::ANSIColor.send(color_arg, result)
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
- color :info, :intense_yellow
39
- color :info_body, :yellow
40
- color :protocol
44
+ color :info, :intense_yellow
45
+ color :info_body, :yellow
46
+ color :protocol
41
47
 
42
- color :request, :intense_cyan
43
- color :request_verb, :bold
44
- color :request_header
45
- color :request_blankline, :intense_cyan, :bold
48
+ color :request, :intense_cyan
49
+ color :request_verb, :bold
50
+ color :request_header
51
+ color :request_blankline, :intense_cyan, :bold
46
52
 
47
- color :response, :intense_green
48
- color :response_status, :bold, :green
49
- color :response_header
50
- color :response_blankline, :intense_green, :bold
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
- def call(request_env)
53
- @outdev.puts "#{info('*')} #{info_body("connect to #{request_env[:url].host} on port #{request_env[:url].port}")}"
54
- @outdev.puts "#{info('*')} #{info_body("getting our SSL on")}" if request_env[:url].scheme=='https'
55
- @outdev.puts "#{request('>')} #{request_verb(request_env[:method].to_s.upcase)} #{request_env[:url].request_uri} #{protocol("HTTP/#{Net::HTTP::HTTPVersion}")}"
56
- request_env[:request_headers].each do |k, v|
57
- @outdev.puts "#{request('>')} #{request_header(k)}#{request(':')} #{v}"
58
- end
59
- @outdev.puts "#{request_blankline('>')} "
60
- request_body = alter_body_by_content_type(request_env[:body], request_env[:request_headers]['Content-Type'])
61
- (request_body || '').split("\n", -1).each do |line|
62
- @outdev.puts "#{request('>')} #{line}"
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
- @outdev.puts "#{response_blankline ('<')} "
70
- response_body = alter_body_by_content_type(response_env[:body], response_env[:response_headers]['Content-Type'])
71
- (response_body || '').split("\n", -1).each do |line|
72
- @outdev.puts "#{response('<')} #{line}"
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
- def pretty?
78
- @options[:pretty].nil? ? true : @options[:pretty]
79
- end
83
+ def pretty?
84
+ @options[:pretty].nil? ? true : @options[:pretty]
85
+ end
80
86
 
81
- # whether to use color
82
- def color?
83
- @options[:color].nil? ? @outdev.tty? : @options[:color]
84
- end
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
- # a mapping for each registered CodeRay scanner to the Media Types which represent
87
- # that language. extremely incomplete!
88
- CodeRayForMediaTypes = {
89
- :c => [],
90
- :cpp => [],
91
- :clojure => [],
92
- :css => ['text/css', 'application/css-stylesheet'],
93
- :delphi => [],
94
- :diff => [],
95
- :erb => [],
96
- :groovy => [],
97
- :haml => [],
98
- :html => ['text/html'],
99
- :java => [],
100
- :java_script => ['application/javascript', 'text/javascript', 'application/x-javascript'],
101
- :json => ['application/json'],
102
- :php => [],
103
- :python => ['text/x-python'],
104
- :ruby => [],
105
- :sql => [],
106
- :xml => ['text/xml', 'application/xml', %r(\Aapplication/.*\+xml\z)],
107
- :yaml => [],
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
- # takes a body and a content type; returns the body, altered according to options.
111
- #
112
- # - with coloring (ansi colors for terminals) possibly added, if it's a recognized content type and
113
- # #color? is true
114
- # - formatted prettily if #pretty? is true
115
- def alter_body_by_content_type(body, content_type)
116
- return body unless body.is_a?(String)
117
- media_type = ::Rack::Request.new({'CONTENT_TYPE' => content_type}).media_type
118
- if pretty?
119
- case media_type
120
- when 'application/json'
121
- require 'json'
122
- begin
123
- body = JSON.pretty_generate(JSON.parse(body))
124
- rescue JSON::ParserError
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
- end
128
- if color?
129
- coderay_scanner = CodeRayForMediaTypes.reject{|k,v| !v.any?{|type| type === media_type} }.keys.first
130
- if coderay_scanner
131
- require 'coderay'
132
- body = CodeRay.scan(body, coderay_scanner).encode(:terminal)
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module ApiHammer
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
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.1
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-05-23 00:00:00.000000000 Z
11
+ date: 2014-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack