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 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